2 $Id: cmd_dbd_scanvol.c,v 1.4 2009-05-22 20:48:44 franklahm Exp $
4 Copyright (c) 2009 Frank Lahm <franklahm@gmail.com>
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
19 #endif /* HAVE_CONFIG_H */
23 #include <sys/types.h>
30 #include <atalk/adouble.h>
31 #include <atalk/unicode.h>
32 #include <atalk/volinfo.h>
33 #include <atalk/cnid_dbd_private.h>
39 /* Some defines to ease code parsing */
40 #define ADDIR_OK (addir_ok == 0)
41 #define ADFILE_OK (adfile_ok == 0)
43 /* These must be accessible for cmd_dbd_* funcs */
44 struct volinfo *volinfo;
45 char cwdbuf[MAXPATHLEN+1];
47 /* Some static vars */
49 static DBD *dbd_rebuild;
50 static dbd_flags_t dbd_flags;
51 static char stamp[CNID_DEV_LEN];
52 static char *netatalk_dirs[] = {
57 static struct cnid_dbd_rqst rqst;
58 static struct cnid_dbd_rply rply;
62 Taken form afpd/desktop.c
64 static char *utompath(char *upath)
66 static char mpath[ MAXPATHLEN + 2]; /* for convert_charset dest_len parameter +2 */
68 uint16_t flags = CONV_IGNORE | CONV_UNESCAPEHEX;
76 outlen = strlen(upath);
78 if ((volinfo->v_casefold & AFPVOL_UTOMUPPER))
79 flags |= CONV_TOUPPER;
80 else if ((volinfo->v_casefold & AFPVOL_UTOMLOWER))
81 flags |= CONV_TOLOWER;
83 if ((volinfo->v_flags & AFPVOL_EILSEQ)) {
84 flags |= CONV__EILSEQ;
87 /* convert charsets */
88 if ((size_t)-1 == ( outlen = convert_charset(volinfo->v_volcharset,
90 volinfo->v_maccharset,
91 u, outlen, mpath, MAXPATHLEN, &flags)) ) {
92 dbd_log( LOGSTD, "Conversion from %s to %s for %s failed.",
93 volinfo->v_volcodepage, volinfo->v_maccodepage, u);
101 Taken form afpd/desktop.c
103 static char *mtoupath(char *mpath)
105 static char upath[ MAXPATHLEN + 2]; /* for convert_charset dest_len parameter +2 */
114 if ( *mpath == '\0' ) {
118 /* set conversion flags */
119 if (!(volinfo->v_flags & AFPVOL_NOHEX))
120 flags |= CONV_ESCAPEHEX;
121 if (!(volinfo->v_flags & AFPVOL_USEDOTS))
122 flags |= CONV_ESCAPEDOTS;
124 if ((volinfo->v_casefold & AFPVOL_MTOUUPPER))
125 flags |= CONV_TOUPPER;
126 else if ((volinfo->v_casefold & AFPVOL_MTOULOWER))
127 flags |= CONV_TOLOWER;
129 if ((volinfo->v_flags & AFPVOL_EILSEQ)) {
130 flags |= CONV__EILSEQ;
139 if ((size_t)-1 == (outlen = convert_charset(CH_UTF8_MAC,
140 volinfo->v_volcharset,
141 volinfo->v_maccharset,
142 m, inplen, u, outlen, &flags)) ) {
143 dbd_log( LOGSTD, "conversion from UTF8-MAC to %s for %s failed.",
144 volinfo->v_volcodepage, mpath);
152 Check for wrong encoding e.g. "." at the beginning is not CAP encoded (:2e) although volume is default !AFPVOL_USEDOTS.
153 We do it by roundtripiping from volcharset to UTF8-MAC and back and then compare the result.
155 static int check_name_encoding(char *uname)
159 roundtripped = mtoupath(utompath(uname));
161 dbd_log( LOGSTD, "Error checking encoding for '%s/%s'", cwdbuf, uname);
165 if ( STRCMP(uname, !=, roundtripped)) {
166 dbd_log( LOGSTD, "Bad encoding for '%s/%s'", cwdbuf, uname);
174 Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop"
175 Returns pointer to name or NULL.
177 static const char *check_netatalk_dirs(const char *name)
181 for (c=0; netatalk_dirs[c]; c++) {
182 if ((strcmp(name, netatalk_dirs[c])) == 0)
183 return netatalk_dirs[c];
189 Check for .AppleDouble file, create if missing
191 static int check_adfile(const char *fname, const struct stat *st)
197 if (S_ISREG(st->st_mode))
200 adflags = ADFLAGS_DIR;
202 adname = volinfo->ad_path(fname, adflags);
204 if ((ret = access( adname, F_OK)) != 0) {
205 if (errno != ENOENT) {
206 dbd_log(LOGSTD, "Access error for ad-file '%s/%s': %s",
207 cwdbuf, adname, strerror(errno));
210 /* Missing. Log and create it */
211 dbd_log(LOGSTD, "Missing AppleDoube file '%s/%s'", cwdbuf, adname);
213 if (dbd_flags & DBD_FLAGS_SCAN)
214 /* Scan only requested, dont change anything */
218 ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
220 if ((ret = ad_open_metadata( fname, adflags, O_CREAT, &ad)) != 0) {
221 dbd_log( LOGSTD, "Error creating AppleDouble file '%s/%s': %s",
222 cwdbuf, adname, strerror(errno));
226 /* Set name in ad-file */
227 ad_setname(&ad, utompath((char *)fname));
229 ad_close_metadata(&ad);
231 chown(adname, st->st_uid, st->st_gid);
232 /* FIXME: should we inherit mode too here ? */
234 chmod(adname, st->st_mode);
241 Check for .AppleDouble folder and .Parent, create if missing
243 static int check_addir(int volroot)
245 int addir_ok, adpar_ok;
249 /* Check for ad-dir */
250 if ( (addir_ok = access(ADv2_DIRNAME, F_OK)) != 0) {
251 if (errno != ENOENT) {
252 dbd_log(LOGSTD, "Access error in directory %s: %s", cwdbuf, strerror(errno));
255 dbd_log(LOGSTD, "Missing %s for '%s'", ADv2_DIRNAME, cwdbuf);
258 /* Check for ".Parent" */
259 if ( (adpar_ok = access(volinfo->ad_path(".", ADFLAGS_DIR), F_OK)) != 0) {
260 if (errno != ENOENT) {
261 dbd_log(LOGSTD, "Access error on '%s/%s': %s",
262 cwdbuf, volinfo->ad_path(".", ADFLAGS_DIR), strerror(errno));
265 dbd_log(LOGSTD, "Missing .AppleDouble/.Parent for '%s'", cwdbuf);
268 /* Is one missing ? */
269 if ((addir_ok != 0) || (adpar_ok != 0)) {
270 /* Yes, but are we only scanning ? */
271 if (dbd_flags & DBD_FLAGS_SCAN) {
272 /* Yes: missing .Parent is not a problem, but missing ad-dir
273 causes later checking of ad-files to fail. So we have to return appropiately */
276 else /* (adpar_ok != 0) */
280 /* Create ad dir and set name */
281 ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
283 if (ad_open_metadata( ".", ADFLAGS_DIR, O_CREAT, &ad) != 0) {
284 dbd_log( LOGSTD, "Error creating AppleDouble dir in %s: %s", cwdbuf, strerror(errno));
288 /* Get basename of cwd from cwdbuf */
289 char *mname = utompath(strrchr(cwdbuf, '/') + 1);
291 /* Update name in ad file */
292 ad_setname(&ad, mname);
294 ad_close_metadata(&ad);
296 /* Inherit owner/group from "." to ".AppleDouble" and ".Parent" */
297 if ((stat(".", &st)) != 0) {
298 dbd_log( LOGSTD, "Couldnt stat %s: %s", cwdbuf, strerror(errno));
301 chown(ADv2_DIRNAME, st.st_uid, st.st_gid);
302 chown(volinfo->ad_path(".", ADFLAGS_DIR), st.st_uid, st.st_gid);
309 Check files and dirs inside .AppleDouble folder:
310 - remove orphaned files
313 static int read_addir(void)
318 static char fname[MAXPATHLEN] = "../";
320 if ((chdir(ADv2_DIRNAME)) != 0) {
321 dbd_log(LOGSTD, "Couldn't chdir to '%s/%s': %s",
322 cwdbuf, ADv2_DIRNAME, strerror(errno));
326 if ((dp = opendir(".")) == NULL) {
327 dbd_log(LOGSTD, "Couldn't open the directory '%s/%s': %s",
328 cwdbuf, ADv2_DIRNAME, strerror(errno));
332 while ((ep = readdir(dp))) {
333 /* Check if its "." or ".." */
334 if (DIR_DOT_OR_DOTDOT(ep->d_name))
337 if (STRCMP(ep->d_name, ==, ".Parent"))
340 if ((stat(ep->d_name, &st)) < 0) {
341 dbd_log( LOGSTD, "Lost file or dir while enumeratin dir '%s/%s/%s', probably removed: %s",
342 cwdbuf, ADv2_DIRNAME, ep->d_name, strerror(errno));
347 if (S_ISDIR(st.st_mode)) {
348 dbd_log( LOGSTD, "Unexpected directory '%s' in AppleDouble dir '%s/%s'",
349 ep->d_name, cwdbuf, ADv2_DIRNAME);
353 /* Check for data file */
354 strcpy(fname+3, ep->d_name);
355 if ((access( fname, F_OK)) != 0) {
356 if (errno != ENOENT) {
357 dbd_log(LOGSTD, "Access error for file '%s/%s': %s",
358 cwdbuf, ep->d_name, strerror(errno));
361 /* Orphaned ad-file*/
362 dbd_log(LOGSTD, "Orphaned AppleDoube file '%s/%s/%s'",
363 cwdbuf, ADv2_DIRNAME, ep->d_name);
365 if (dbd_flags & DBD_FLAGS_SCAN)
366 /* Scan only requested, dont change anything */
369 if ((unlink(ep->d_name)) != 0) {
370 dbd_log(LOGSTD, "Error unlinking orphaned AppleDoube file '%s/%s/%s'",
371 cwdbuf, ADv2_DIRNAME, ep->d_name);
377 if ((chdir("..")) != 0) {
378 dbd_log(LOGSTD, "Couldn't chdir back to '%s' from AppleDouble dir: %s",
379 cwdbuf, strerror(errno));
380 /* This really is EOT! */
390 Check CNID for a file/dir, both from db and from ad-file.
391 For detailed specs see intro.
393 static cnid_t check_cnid(const char *name, cnid_t did, struct stat *st, int adfile_ok, int adflags)
396 cnid_t db_cnid, ad_cnid;
399 /* Get CNID from ad-file if volume is using AFPVOL_CACHE */
401 if ( (volinfo->v_flags & AFPVOL_CACHE) && ADFILE_OK) {
402 ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
403 if (ad_open_metadata( name, adflags, O_RDWR, &ad) != 0) {
404 dbd_log( LOGSTD, "Error opening AppleDouble file for '%s/%s': %s", cwdbuf, name, strerror(errno));
408 if (dbd_flags & DBD_FLAGS_FORCE)
409 ad_cnid = ad_forcegetid(&ad);
411 ad_cnid = ad_getid(&ad, st->st_dev, st->st_ino, did, stamp);
413 dbd_log( LOGSTD, "Incorrect CNID data in .AppleDouble data for '%s/%s' (bad stamp?)", cwdbuf, name);
415 ad_close_metadata(&ad);
418 /* Get CNID from database */
420 /* Prepare request data */
421 memset(&rqst, 0, sizeof(struct cnid_dbd_rqst));
422 memset(&rply, 0, sizeof(struct cnid_dbd_rply));
424 if ( ! (volinfo->v_flags & AFPVOL_NODEV))
425 rqst.dev = st->st_dev;
426 rqst.ino = st->st_ino;
427 rqst.type = S_ISDIR(st->st_mode)?1:0;
428 rqst.name = (char *)name;
429 rqst.namelen = strlen(name);
431 /* Query the database */
432 ret = cmd_dbd_lookup(dbd, &rqst, &rply, (dbd_flags & DBD_FLAGS_SCAN) ? 1 : 0);
433 dbif_txn_close(dbd, ret);
434 if (rply.result == CNID_DBD_RES_OK) {
436 } else if (rply.result == CNID_DBD_RES_NOTFOUND) {
437 dbd_log( LOGSTD, "No CNID for '%s/%s' in database", cwdbuf, name);
440 dbd_log( LOGSTD, "Fatal error resolving '%s/%s'", cwdbuf, name);
444 /* Compare results from both CNID searches */
445 if (ad_cnid && db_cnid && (ad_cnid == db_cnid)) {
446 /* Everything is fine */
448 } else if (ad_cnid && db_cnid && (ad_cnid != db_cnid)) {
449 /* Mismatch ? Delete both from db and re-add data from file */
450 dbd_log( LOGSTD, "CNID mismatch for '%s/%s', db: %u, ad-file: %u", cwdbuf, name, ntohl(db_cnid), ntohl(ad_cnid));
451 if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
453 ret = dbd_delete(dbd, &rqst, &rply);
454 dbif_txn_close(dbd, ret);
457 ret = dbd_delete(dbd, &rqst, &rply);
458 dbif_txn_close(dbd, ret);
460 ret = dbd_rebuild_add(dbd, &rqst, &rply);
461 dbif_txn_close(dbd, ret);
464 } else if (ad_cnid && (db_cnid == 0)) {
465 /* in ad-file but not in db */
466 if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
467 dbd_log( LOGSTD, "CNID rebuild add for '%s/%s', adding with CNID from ad-file: %u", cwdbuf, name, ntohl(ad_cnid));
469 ret = dbd_delete(dbd, &rqst, &rply);
470 dbif_txn_close(dbd, ret);
471 ret = dbd_rebuild_add(dbd, &rqst, &rply);
472 dbif_txn_close(dbd, ret);
475 } else if ((db_cnid == 0) && (ad_cnid == 0)) {
476 /* No CNID at all, we clearly have to allocate a fresh one... */
477 /* Note: the next test will use this new CNID too! */
478 if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
480 ret = cmd_dbd_add(dbd, &rqst, &rply);
481 dbif_txn_close(dbd, ret);
483 dbd_log( LOGSTD, "New CNID for '%s/%s': %u", cwdbuf, name, ntohl(db_cnid));
487 if ((ad_cnid == 0) && db_cnid) {
488 /* in db but zeroID in ad-file, write it to ad-file if AFPVOL_CACHE */
489 if ((volinfo->v_flags & AFPVOL_CACHE) && ADFILE_OK) {
490 if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
491 dbd_log( LOGSTD, "Writing CNID data for '%s/%s' to AppleDouble file", cwdbuf, name, ntohl(db_cnid));
492 ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
493 if (ad_open_metadata( name, adflags, O_RDWR, &ad) != 0) {
494 dbd_log( LOGSTD, "Error opening AppleDouble file for '%s/%s': %s", cwdbuf, name, strerror(errno));
497 ad_setid( &ad, st->st_dev, st->st_ino, db_cnid, did, stamp);
499 ad_close_metadata(&ad);
509 This is called recursively for all dirs.
510 volroot=1 means we're in the volume root dir, 0 means we aren't.
511 We use this when checking for netatalk private folders like .AppleDB.
512 did is our parents CNID.
514 static int dbd_readdir(int volroot, cnid_t did)
516 int cwd, ret = 0, adflags, adfile_ok, addir_ok, encoding_ok;
521 static struct stat st; /* Save some stack space */
523 /* Check again for .AppleDouble folder, check_adfile also checks/creates it */
524 if ((addir_ok = check_addir(volroot)) != 0)
525 if ( ! (dbd_flags & DBD_FLAGS_SCAN))
526 /* Fatal on rebuild run, continue if only scanning ! */
529 /* Check AppleDouble files in AppleDouble folder, but only if it exists or could be created */
531 if ((read_addir()) != 0)
532 if ( ! (dbd_flags & DBD_FLAGS_SCAN))
533 /* Fatal on rebuild run, continue if only scanning ! */
536 if ((dp = opendir (".")) == NULL) {
537 dbd_log(LOGSTD, "Couldn't open the directory: %s",strerror(errno));
541 while ((ep = readdir (dp))) {
542 /* Check if we got a termination signal */
544 longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
546 /* Check if its "." or ".." */
547 if (DIR_DOT_OR_DOTDOT(ep->d_name))
550 /* Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop" */
551 if ((name = check_netatalk_dirs(ep->d_name)) != NULL) {
553 dbd_log(LOGSTD, "Nested %s in %s", name, cwdbuf);
557 /* Skip .AppleDouble dir in this loop */
558 if (STRCMP(ep->d_name, == , ADv2_DIRNAME))
561 if ((ret = stat(ep->d_name, &st)) < 0) {
562 dbd_log( LOGSTD, "Lost file while reading dir '%s/%s', probably removed: %s", cwdbuf, ep->d_name, strerror(errno));
565 if (S_ISREG(st.st_mode))
568 adflags = ADFLAGS_DIR;
570 /**************************************************************************
572 **************************************************************************/
575 if ( -1 == (encoding_ok = check_name_encoding(ep->d_name)) ) {
576 /* If its a file: skipp all other tests now ! */
577 /* For dirs we could try to get a CNID for it and recurse, but currently I prefer not to */
581 /* Check for appledouble file, create if missing, but only if we have addir */
584 adfile_ok = check_adfile(ep->d_name, &st);
587 cnid = check_cnid(ep->d_name, did, &st, adfile_ok, adflags);
589 /* Now add this object to our rebuild dbd */
591 rqst.cnid = rply.cnid;
592 dbd_rebuild_add(dbd_rebuild, &rqst, &rply);
593 if (rply.result != CNID_DBD_RES_OK) {
594 dbd_log( LOGDEBUG, "Fatal error adding CNID: %u for '%s/%s' to in-memory rebuild-db",
595 cnid, cwdbuf, ep->d_name);
600 /**************************************************************************
602 **************************************************************************/
603 if (S_ISDIR(st.st_mode) && cnid) { /* If we have no cnid for it we cant recur */
605 strcat(cwdbuf, ep->d_name);
606 dbd_log( LOGDEBUG, "Entering directory: %s", cwdbuf);
607 if (-1 == (cwd = open(".", O_RDONLY))) {
608 dbd_log( LOGSTD, "Cant open directory '%s': %s", cwdbuf, strerror(errno));
611 if (0 != chdir(ep->d_name)) {
612 dbd_log( LOGSTD, "Cant chdir to directory '%s': %s", cwdbuf, strerror(errno));
617 ret = dbd_readdir(0, cnid);
621 *(strrchr(cwdbuf, '/')) = 0;
628 Use results of previous checks
635 static int scanvol(struct volinfo *vi, dbd_flags_t flags)
637 /* Dont scanvol on no-appledouble vols */
638 if (vi->v_flags & AFPVOL_NOADOUBLE) {
639 dbd_log( LOGSTD, "Volume without AppleDouble support: skipping volume scanning.");
643 /* Make this stuff accessible from all funcs easily */
647 /* Run with umask 0 */
651 strcpy(cwdbuf, volinfo->v_path);
652 if (cwdbuf[strlen(cwdbuf) - 1] == '/')
653 cwdbuf[strlen(cwdbuf) - 1] = 0;
654 chdir(volinfo->v_path);
656 /* Start recursion */
657 if (dbd_readdir(1, htonl(2)) < 0) /* 2 = volumeroot CNID */
664 Remove all CNIDs from dbd that are not in dbd_rebuild
666 void delete_orphaned_cnids(DBD *dbd, DBD *dbd_rebuild, dbd_flags_t flags)
668 int ret, deleted = 0;
669 cnid_t dbd_cnid = 0, rebuild_cnid = 0;
670 struct cnid_dbd_rqst rqst;
671 struct cnid_dbd_rply rply;
673 /* jump over rootinfo key */
674 if ( dbif_idwalk(dbd, &dbd_cnid, 0) != 1)
676 if ( dbif_idwalk(dbd_rebuild, &rebuild_cnid, 0) != 1)
679 /* Get first id from dbd_rebuild */
680 if ((dbif_idwalk(dbd_rebuild, &rebuild_cnid, 0)) == -1)
683 /* Start main loop through dbd: get CNID from dbd */
684 while ((dbif_idwalk(dbd, &dbd_cnid, 0)) == 1) {
688 if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0) {
689 dbd_log(LOGSTD, "Error checkpointing!");
694 /* This should be the normal case: CNID is in both dbs */
695 if (dbd_cnid == rebuild_cnid) {
696 /* Get next CNID from rebuild db */
697 if ((ret = dbif_idwalk(dbd_rebuild, &rebuild_cnid, 0)) == -1) {
700 } else if (ret == 0) {
701 /* end of rebuild_cnid, delete all remaining CNIDs from dbd */
702 while ((dbif_idwalk(dbd, &dbd_cnid, 0)) == 1) {
703 dbd_log(LOGSTD, "Orphaned CNID in database: %u", dbd_cnid);
704 if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
705 rqst.cnid = htonl(dbd_cnid);
706 ret = dbd_delete(dbd, &rqst, &rply);
707 dbif_txn_close(dbd, ret);
713 /* Normal case (ret=1): continue while loop */
717 if (dbd_cnid < rebuild_cnid) {
718 /* CNID is orphaned -> delete */
719 dbd_log(LOGSTD, "Orphaned CNID in database: %u.", dbd_cnid);
720 if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
721 rqst.cnid = htonl(dbd_cnid);
722 ret = dbd_delete(dbd, &rqst, &rply);
723 dbif_txn_close(dbd, ret);
729 if (dbd_cnid > rebuild_cnid) {
730 dbd_log(LOGSTD, "Ghost CNID: %u. This is fatal! Dumping rebuild db:\n", rebuild_cnid);
731 dbif_dump(dbd_rebuild, 0);
732 dbd_log(LOGSTD, "Send this dump and a `dbd -d ...` dump to the Netatalk Dev team!");
733 dbif_txn_close(dbd, ret);
734 dbif_idwalk(dbd, NULL, 1); /* Close cursor */
735 dbif_idwalk(dbd_rebuild, NULL, 1); /* Close cursor */
741 dbif_idwalk(dbd, NULL, 1); /* Close cursor */
742 dbif_idwalk(dbd_rebuild, NULL, 1); /* Close cursor */
747 Main func called from cmd_dbd.c
749 int cmd_dbd_scanvol(DBD *dbd_ref, struct volinfo *volinfo, dbd_flags_t flags)
753 /* Make it accessible for all funcs */
756 /* We only support unicode volumes ! */
757 if ( volinfo->v_volcharset != CH_UTF8) {
758 dbd_log( LOGSTD, "Not a Unicode volume: %s, %u != %u", volinfo->v_volcodepage, volinfo->v_volcharset, CH_UTF8);
762 /* Get volume stamp */
763 dbd_getstamp(dbd, &rqst, &rply);
764 if (rply.result != CNID_DBD_RES_OK)
766 memcpy(stamp, rply.name, CNID_DEV_LEN);
768 /* open/create rebuild dbd, copy rootinfo key */
769 if (NULL == (dbd_rebuild = dbif_init(NULL, NULL)))
771 if (0 != (dbif_open(dbd_rebuild, NULL, 0)))
773 if (0 != (dbif_copy_rootinfokey(dbd, dbd_rebuild)))
776 if (setjmp(jmp) != 0)
777 goto exit_cleanup; /* Got signal, jump from dbd_readdir */
780 if ( (scanvol(volinfo, flags)) != 0)
783 /* We can only do this in excluse mode, otherwise we might delete CNIDs added from
784 other clients in between our pass 1 and 2 */
785 if (flags & DBD_FLAGS_EXCL)
786 delete_orphaned_cnids(dbd, dbd_rebuild, flags);
789 dbif_close(dbd_rebuild);