2 Copyright (c) 2009 Frank Lahm <franklahm@gmail.com>
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
17 #endif /* HAVE_CONFIG_H */
21 #include <sys/types.h>
29 #include <atalk/adouble.h>
30 #include <atalk/unicode.h>
31 #include <atalk/volinfo.h>
32 #include <atalk/cnid_dbd_private.h>
33 #include <atalk/volume.h>
35 #include <atalk/util.h>
36 #include <atalk/acl.h>
43 /* Some defines to ease code parsing */
44 #define ADDIR_OK (addir_ok == 0)
45 #define ADFILE_OK (adfile_ok == 0)
47 /* These must be accessible for cmd_dbd_* funcs */
48 struct volinfo *volinfo;
49 char cwdbuf[MAXPATHLEN+1];
51 /* Some static vars */
53 static DBD *dbd_rebuild;
54 static dbd_flags_t dbd_flags;
55 static char stamp[CNID_DEV_LEN];
56 static char *netatalk_dirs[] = {
61 static char *special_dirs[] = {
65 static struct cnid_dbd_rqst rqst;
66 static struct cnid_dbd_rply rply;
68 static struct vol volume; /* fake it for ea_open */
69 static char pname[MAXPATHLEN] = "../";
72 Taken form afpd/desktop.c
74 static char *utompath(char *upath)
76 static char mpath[ MAXPATHLEN + 2]; /* for convert_charset dest_len parameter +2 */
78 uint16_t flags = CONV_IGNORE | CONV_UNESCAPEHEX;
86 outlen = strlen(upath);
88 if ((volinfo->v_casefold & AFPVOL_UTOMUPPER))
89 flags |= CONV_TOUPPER;
90 else if ((volinfo->v_casefold & AFPVOL_UTOMLOWER))
91 flags |= CONV_TOLOWER;
93 if ((volinfo->v_flags & AFPVOL_EILSEQ)) {
94 flags |= CONV__EILSEQ;
97 /* convert charsets */
98 if ((size_t)-1 == ( outlen = convert_charset(volinfo->v_volcharset,
100 volinfo->v_maccharset,
101 u, outlen, mpath, MAXPATHLEN, &flags)) ) {
102 dbd_log( LOGSTD, "Conversion from %s to %s for %s failed.",
103 volinfo->v_volcodepage, volinfo->v_maccodepage, u);
111 Taken form afpd/desktop.c
113 static char *mtoupath(char *mpath)
115 static char upath[ MAXPATHLEN + 2]; /* for convert_charset dest_len parameter +2 */
124 if ( *mpath == '\0' ) {
128 /* set conversion flags */
129 if (!(volinfo->v_flags & AFPVOL_NOHEX))
130 flags |= CONV_ESCAPEHEX;
131 if (!(volinfo->v_flags & AFPVOL_USEDOTS))
132 flags |= CONV_ESCAPEDOTS;
134 if ((volinfo->v_casefold & AFPVOL_MTOUUPPER))
135 flags |= CONV_TOUPPER;
136 else if ((volinfo->v_casefold & AFPVOL_MTOULOWER))
137 flags |= CONV_TOLOWER;
139 if ((volinfo->v_flags & AFPVOL_EILSEQ)) {
140 flags |= CONV__EILSEQ;
149 if ((size_t)-1 == (outlen = convert_charset(CH_UTF8_MAC,
150 volinfo->v_volcharset,
151 volinfo->v_maccharset,
152 m, inplen, u, outlen, &flags)) ) {
153 dbd_log( LOGSTD, "conversion from UTF8-MAC to %s for %s failed.",
154 volinfo->v_volcodepage, mpath);
163 Check if "name" is pointing to
164 a) an object inside the current volume (return 0)
165 b) an object outside the current volume (return 1)
166 Then stats the pointed to object and if it is a dir ors ADFLAGS_DIR to *adflags
167 Return -1 on any serious error.
169 static int check_symlink(const char *name, int *adflags)
173 char pathbuf[MAXPATHLEN + 1];
177 if ((len = readlink(name, pathbuf, MAXPATHLEN)) == -1) {
178 dbd_log(LOGSTD, "Error reading link info for '%s/%s': %s",
179 cwdbuf, name, strerror(errno));
184 if ((stat(pathbuf, &st)) != 0) {
185 dbd_log(LOGSTD, "stat error '%s': %s", pathbuf, strerror(errno));
189 if ((cwd = open(".", O_RDONLY)) < 0) {
190 dbd_log(LOGSTD, "error opening cwd '%s': %s", cwdbuf, strerror(errno));
194 if (S_ISDIR(st.st_mode)) {
195 *adflags |= ADFLAGS_DIR;
197 /* get basename from path */
198 if ((sep = strrchr(pathbuf, '/')) == NULL)
199 /* just a file at the same level */
201 sep = 0; /* pathbuf now contains the directory*/
204 /* fchdir() to pathbuf so we can easily get its path with getcwd() */
205 if ((chdir(pathbuf)) != 0) {
206 dbd_log(LOGSTD, "Cant chdir to '%s': %s", pathbuf, strerror(errno));
210 if ((getcwd(pathbuf, MAXPATHLEN)) == NULL) {
211 dbd_log(LOGSTD, "Cant get symlink'ed dir '%s/%s': %s", cwdbuf, pathbuf, strerror(errno));
212 if ((fchdir(cwd)) != 0)
214 longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
218 if ((fchdir(cwd)) != 0)
220 longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
223 We now have the symlink target dir as absoulte path in pathbuf
224 and can compare it with the currents volume path
227 while (volinfo->v_path[i]) {
228 if ((pathbuf[i] == 0) || (volinfo->v_path[i] != pathbuf[i])) {
229 dbd_log( LOGDEBUG, "extra-share symlink '%s/%s', following", cwdbuf, name);
235 dbd_log( LOGDEBUG, "intra-share symlink '%s/%s', not following", cwdbuf, name);
241 Check for wrong encoding e.g. "." at the beginning is not CAP encoded (:2e) although volume is default !AFPVOL_USEDOTS.
242 We do it by roundtripiping from volcharset to UTF8-MAC and back and then compare the result.
244 static int check_name_encoding(char *uname)
248 roundtripped = mtoupath(utompath(uname));
250 dbd_log( LOGSTD, "Error checking encoding for '%s/%s'", cwdbuf, uname);
254 if ( STRCMP(uname, !=, roundtripped)) {
255 dbd_log( LOGSTD, "Bad encoding for '%s/%s'", cwdbuf, uname);
263 Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop"
264 Returns pointer to name or NULL.
266 static const char *check_netatalk_dirs(const char *name)
270 for (c=0; netatalk_dirs[c]; c++) {
271 if ((strcmp(name, netatalk_dirs[c])) == 0)
272 return netatalk_dirs[c];
278 Check for special names
279 Returns pointer to name or NULL.
281 static const char *check_special_dirs(const char *name)
285 for (c=0; special_dirs[c]; c++) {
286 if ((strcmp(name, special_dirs[c])) == 0)
287 return special_dirs[c];
293 Check for .AppleDouble file, create if missing
295 static int check_adfile(const char *fname, const struct stat *st)
301 if (dbd_flags & DBD_FLAGS_CLEANUP)
304 if (S_ISREG(st->st_mode))
307 adflags = ADFLAGS_DIR;
309 adname = volinfo->ad_path(fname, adflags);
311 if ((ret = access( adname, F_OK)) != 0) {
312 if (errno != ENOENT) {
313 dbd_log(LOGSTD, "Access error for ad-file '%s/%s': %s",
314 cwdbuf, adname, strerror(errno));
317 /* Missing. Log and create it */
318 dbd_log(LOGSTD, "Missing AppleDouble file '%s/%s'", cwdbuf, adname);
320 if (dbd_flags & DBD_FLAGS_SCAN)
321 /* Scan only requested, dont change anything */
325 ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
327 if ((ret = ad_open_metadata( fname, adflags, O_CREAT, &ad)) != 0) {
328 dbd_log( LOGSTD, "Error creating AppleDouble file '%s/%s': %s",
329 cwdbuf, adname, strerror(errno));
334 /* Set name in ad-file */
335 ad_setname(&ad, utompath((char *)fname));
337 ad_close_metadata(&ad);
339 chown(adname, st->st_uid, st->st_gid);
340 /* FIXME: should we inherit mode too here ? */
342 chmod(adname, st->st_mode);
345 ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
346 if (ad_open_metadata( fname, adflags, O_RDONLY, &ad) != 0) {
347 dbd_log( LOGSTD, "Error opening AppleDouble file for '%s/%s'", cwdbuf, fname);
350 ad_close_metadata(&ad);
356 Remove all files with file::EA* from adouble dir
358 static void remove_eafiles(const char *name, struct ea *ea)
362 char eaname[MAXPATHLEN];
364 strlcpy(eaname, name, sizeof(eaname));
365 if (strlcat(eaname, "::EA", sizeof(eaname)) >= sizeof(eaname)) {
366 dbd_log(LOGSTD, "name too long: '%s/%s/%s'", cwdbuf, ADv2_DIRNAME, name);
370 if ((chdir(ADv2_DIRNAME)) != 0) {
371 dbd_log(LOGSTD, "Couldn't chdir to '%s/%s': %s",
372 cwdbuf, ADv2_DIRNAME, strerror(errno));
376 if ((dp = opendir(".")) == NULL) {
377 dbd_log(LOGSTD, "Couldn't open the directory '%s/%s': %s",
378 cwdbuf, ADv2_DIRNAME, strerror(errno));
382 while ((ep = readdir(dp))) {
383 if (strstr(ep->d_name, eaname) != NULL) {
384 dbd_log(LOGSTD, "Removing EA file: '%s/%s/%s'",
385 cwdbuf, ADv2_DIRNAME, ep->d_name);
386 if ((unlink(ep->d_name)) != 0) {
387 dbd_log(LOGSTD, "Error unlinking EA file '%s/%s/%s': %s",
388 cwdbuf, ADv2_DIRNAME, ep->d_name, strerror(errno));
399 Check Extended Attributes files
401 static int check_eafiles(const char *fname)
403 unsigned int count = 0;
409 if ((ret = ea_open(&volume, fname, EA_RDWR, &ea)) != 0) {
412 dbd_log(LOGSTD, "Error calling ea_open for file: %s/%s, removing EA files", cwdbuf, fname);
413 if ( ! (dbd_flags & DBD_FLAGS_SCAN))
414 remove_eafiles(fname, &ea);
419 while (count < ea.ea_count) {
420 dbd_log(LOGDEBUG, "EA: %s", (*ea.ea_entries)[count].ea_name);
423 eaname = ea_path(&ea, (*ea.ea_entries)[count].ea_name, 0);
425 if (lstat(eaname, &st) != 0) {
427 dbd_log(LOGSTD, "Missing EA: %s/%s", cwdbuf, eaname);
429 dbd_log(LOGSTD, "Bogus EA: %s/%s", cwdbuf, eaname);
431 } else if (st.st_size != (*ea.ea_entries)[count].ea_size) {
432 dbd_log(LOGSTD, "Bogus EA: %s/%s, removing it...", cwdbuf, eaname);
434 if ((unlink(eaname)) != 0)
435 dbd_log(LOGSTD, "Error removing EA file '%s/%s': %s",
436 cwdbuf, eaname, strerror(errno));
440 /* Be CAREFUL here! This should do what ea_delentry does. ea_close relies on it !*/
441 free((*ea.ea_entries)[count].ea_name);
442 (*ea.ea_entries)[count].ea_name = NULL;
453 Check for .AppleDouble folder and .Parent, create if missing
455 static int check_addir(int volroot)
457 int addir_ok, adpar_ok;
462 if (dbd_flags & DBD_FLAGS_CLEANUP)
465 /* Check for ad-dir */
466 if ( (addir_ok = access(ADv2_DIRNAME, F_OK)) != 0) {
467 if (errno != ENOENT) {
468 dbd_log(LOGSTD, "Access error in directory %s: %s", cwdbuf, strerror(errno));
471 dbd_log(LOGSTD, "Missing %s for '%s'", ADv2_DIRNAME, cwdbuf);
474 /* Check for ".Parent" */
475 if ( (adpar_ok = access(volinfo->ad_path(".", ADFLAGS_DIR), F_OK)) != 0) {
476 if (errno != ENOENT) {
477 dbd_log(LOGSTD, "Access error on '%s/%s': %s",
478 cwdbuf, volinfo->ad_path(".", ADFLAGS_DIR), strerror(errno));
481 dbd_log(LOGSTD, "Missing .AppleDouble/.Parent for '%s'", cwdbuf);
484 /* Is one missing ? */
485 if ((addir_ok != 0) || (adpar_ok != 0)) {
486 /* Yes, but are we only scanning ? */
487 if (dbd_flags & DBD_FLAGS_SCAN) {
488 /* Yes: missing .Parent is not a problem, but missing ad-dir
489 causes later checking of ad-files to fail. So we have to return appropiately */
492 else /* (adpar_ok != 0) */
496 /* Create ad dir and set name */
497 ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
499 if (ad_open_metadata( ".", ADFLAGS_DIR, O_CREAT, &ad) != 0) {
500 dbd_log( LOGSTD, "Error creating AppleDouble dir in %s: %s", cwdbuf, strerror(errno));
504 /* Get basename of cwd from cwdbuf */
505 mname = utompath(strrchr(cwdbuf, '/') + 1);
507 /* Update name in ad file */
508 ad_setname(&ad, mname);
510 ad_close_metadata(&ad);
512 /* Inherit owner/group from "." to ".AppleDouble" and ".Parent" */
513 if ((lstat(".", &st)) != 0) {
514 dbd_log( LOGSTD, "Couldnt stat %s: %s", cwdbuf, strerror(errno));
517 chown(ADv2_DIRNAME, st.st_uid, st.st_gid);
518 chown(volinfo->ad_path(".", ADFLAGS_DIR), st.st_uid, st.st_gid);
525 Check if file cotains "::EA" and if it does check if its correspondig data fork exists.
527 0 = name is not an EA file
528 1 = name is an EA file and no problem was found
529 -1 = name is an EA file and data fork is gone
531 static int check_eafile_in_adouble(const char *name)
534 char *namep, *namedup = NULL;
536 /* Check if this is an AFPVOL_EA_AD vol */
537 if (volinfo->v_vfs_ea == AFPVOL_EA_AD) {
538 /* Does the filename contain "::EA" ? */
539 namedup = strdup(name);
540 if ((namep = strstr(namedup, "::EA")) == NULL) {
544 /* File contains "::EA" so it's an EA file. Check for data file */
546 /* Get string before "::EA" from EA filename */
548 strlcpy(pname + 3, namedup, sizeof(pname)); /* Prepends "../" */
550 if ((access( pname, F_OK)) == 0) {
555 if (errno != ENOENT) {
556 dbd_log(LOGSTD, "Access error for file '%s/%s': %s",
557 cwdbuf, name, strerror(errno));
561 /* Orphaned EA file*/
562 dbd_log(LOGSTD, "Orphaned Extended Attribute file '%s/%s/%s'",
563 cwdbuf, ADv2_DIRNAME, name);
565 if (dbd_flags & DBD_FLAGS_SCAN)
566 /* Scan only requested, dont change anything */
569 if ((unlink(name)) != 0) {
570 dbd_log(LOGSTD, "Error unlinking orphaned Extended Attribute file '%s/%s/%s'",
571 cwdbuf, ADv2_DIRNAME, name);
575 } /* if AFPVOL_EA_AD */
585 Check files and dirs inside .AppleDouble folder:
586 - remove orphaned files
589 static int read_addir(void)
595 if ((chdir(ADv2_DIRNAME)) != 0) {
596 dbd_log(LOGSTD, "Couldn't chdir to '%s/%s': %s",
597 cwdbuf, ADv2_DIRNAME, strerror(errno));
601 if ((dp = opendir(".")) == NULL) {
602 dbd_log(LOGSTD, "Couldn't open the directory '%s/%s': %s",
603 cwdbuf, ADv2_DIRNAME, strerror(errno));
607 while ((ep = readdir(dp))) {
608 /* Check if its "." or ".." */
609 if (DIR_DOT_OR_DOTDOT(ep->d_name))
612 if (STRCMP(ep->d_name, ==, ".Parent"))
615 if ((lstat(ep->d_name, &st)) < 0) {
616 dbd_log( LOGSTD, "Lost file or dir while enumeratin dir '%s/%s/%s', probably removed: %s",
617 cwdbuf, ADv2_DIRNAME, ep->d_name, strerror(errno));
622 if (S_ISDIR(st.st_mode)) {
623 dbd_log( LOGSTD, "Unexpected directory '%s' in AppleDouble dir '%s/%s'",
624 ep->d_name, cwdbuf, ADv2_DIRNAME);
628 /* Check if for orphaned and corrupt Extended Attributes file */
629 if (check_eafile_in_adouble(ep->d_name) != 0)
632 /* Check for data file */
633 strcpy(pname + 3, ep->d_name);
634 if ((access( pname, F_OK)) != 0) {
635 if (errno != ENOENT) {
636 dbd_log(LOGSTD, "Access error for file '%s/%s': %s",
637 cwdbuf, pname, strerror(errno));
640 /* Orphaned ad-file*/
641 dbd_log(LOGSTD, "Orphaned AppleDoube file '%s/%s/%s'",
642 cwdbuf, ADv2_DIRNAME, ep->d_name);
644 if (dbd_flags & DBD_FLAGS_SCAN)
645 /* Scan only requested, dont change anything */
648 if ((unlink(ep->d_name)) != 0) {
649 dbd_log(LOGSTD, "Error unlinking orphaned AppleDoube file '%s/%s/%s'",
650 cwdbuf, ADv2_DIRNAME, ep->d_name);
656 if ((chdir("..")) != 0) {
657 dbd_log(LOGSTD, "Couldn't chdir back to '%s' from AppleDouble dir: %s",
658 cwdbuf, strerror(errno));
659 /* This really is EOT! */
660 longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
669 Check CNID for a file/dir, both from db and from ad-file.
670 For detailed specs see intro.
672 static cnid_t check_cnid(const char *name, cnid_t did, struct stat *st, int adfile_ok, int adflags)
675 cnid_t db_cnid, ad_cnid;
678 /* Force checkout every X items */
679 static int cnidcount = 0;
681 if (cnidcount > 10000) {
683 if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0) {
684 dbd_log(LOGSTD, "Error checkpointing!");
689 /* Get CNID from ad-file if volume is using AFPVOL_CACHE */
691 if ( (volinfo->v_flags & AFPVOL_CACHE) && ADFILE_OK) {
692 ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
693 if (ad_open_metadata( name, adflags, O_RDWR, &ad) != 0) {
695 if (dbd_flags & DBD_FLAGS_CLEANUP)
698 dbd_log( LOGSTD, "Error opening AppleDouble file for '%s/%s': %s", cwdbuf, name, strerror(errno));
702 if (dbd_flags & DBD_FLAGS_FORCE) {
703 ad_cnid = ad_forcegetid(&ad);
704 /* This ensures the changed stamp is written */
705 ad_setid( &ad, st->st_dev, st->st_ino, ad_cnid, did, stamp);
709 ad_cnid = ad_getid(&ad, st->st_dev, st->st_ino, did, stamp);
712 dbd_log( LOGSTD, "Bad CNID in adouble file of '%s/%s'", cwdbuf, name);
714 dbd_log( LOGDEBUG, "CNID from .AppleDouble file for '%s/%s': %u", cwdbuf, name, ntohl(ad_cnid));
716 ad_close_metadata(&ad);
719 /* Get CNID from database */
721 /* Prepare request data */
722 memset(&rqst, 0, sizeof(struct cnid_dbd_rqst));
723 memset(&rply, 0, sizeof(struct cnid_dbd_rply));
726 if ( ! (volinfo->v_flags & AFPVOL_NODEV))
727 rqst.dev = st->st_dev;
728 rqst.ino = st->st_ino;
729 rqst.type = S_ISDIR(st->st_mode)?1:0;
730 rqst.name = (char *)name;
731 rqst.namelen = strlen(name);
733 /* Query the database */
734 ret = dbd_lookup(dbd, &rqst, &rply, (dbd_flags & DBD_FLAGS_SCAN) ? 1 : 0);
735 dbif_txn_close(dbd, ret);
736 if (rply.result == CNID_DBD_RES_OK) {
738 } else if (rply.result == CNID_DBD_RES_NOTFOUND) {
739 if ( ! (dbd_flags & DBD_FLAGS_FORCE))
740 dbd_log( LOGSTD, "No CNID for '%s/%s' in database", cwdbuf, name);
743 dbd_log( LOGSTD, "Fatal error resolving '%s/%s'", cwdbuf, name);
747 /* Compare results from both CNID searches */
748 if (ad_cnid && db_cnid && (ad_cnid == db_cnid)) {
749 /* Everything is fine */
751 } else if (ad_cnid && db_cnid && (ad_cnid != db_cnid)) {
752 /* Mismatch ? Delete both from db and re-add data from file */
753 dbd_log( LOGSTD, "CNID mismatch for '%s/%s', db: %u, ad-file: %u", cwdbuf, name, ntohl(db_cnid), ntohl(ad_cnid));
754 if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
756 ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
757 dbif_txn_close(dbd, ret);
760 ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
761 dbif_txn_close(dbd, ret);
763 ret = dbd_rebuild_add(dbd, &rqst, &rply);
764 dbif_txn_close(dbd, ret);
767 } else if (ad_cnid && (db_cnid == 0)) {
768 /* in ad-file but not in db */
769 if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
770 /* Ensure the cnid from the ad-file is not already occupied by another file */
771 dbd_log(LOGDEBUG, "Checking whether CNID %u from ad-file is occupied",
775 ret = dbd_resolve(dbd, &rqst, &rply);
776 if (ret == CNID_DBD_RES_OK) {
777 /* Occupied! Choose another, update ad-file */
778 ret = dbd_add(dbd, &rqst, &rply, 1);
779 dbif_txn_close(dbd, ret);
781 dbd_log(LOGSTD, "New CNID for '%s/%s': %u", cwdbuf, name, ntohl(db_cnid));
783 if ((volinfo->v_flags & AFPVOL_CACHE)
785 && ( ! (dbd_flags & DBD_FLAGS_SCAN))) {
786 dbd_log(LOGSTD, "Writing CNID data for '%s/%s' to AppleDouble file",
787 cwdbuf, name, ntohl(db_cnid));
788 ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
789 if (ad_open_metadata( name, adflags, O_RDWR, &ad) != 0) {
790 dbd_log(LOGSTD, "Error opening AppleDouble file for '%s/%s': %s",
791 cwdbuf, name, strerror(errno));
794 ad_setid( &ad, st->st_dev, st->st_ino, db_cnid, did, stamp);
796 ad_close_metadata(&ad);
801 dbd_log(LOGDEBUG, "CNID rebuild add '%s/%s' with CNID from ad-file %u",
802 cwdbuf, name, ntohl(ad_cnid));
804 ret = dbd_rebuild_add(dbd, &rqst, &rply);
805 dbif_txn_close(dbd, ret);
808 } else if ((db_cnid == 0) && (ad_cnid == 0)) {
809 /* No CNID at all, we clearly have to allocate a fresh one... */
810 /* Note: the next test will use this new CNID too! */
811 if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
813 ret = dbd_add(dbd, &rqst, &rply, 1);
814 dbif_txn_close(dbd, ret);
816 dbd_log(LOGSTD, "New CNID for '%s/%s': %u", cwdbuf, name, ntohl(db_cnid));
820 if ((ad_cnid == 0) && db_cnid) {
821 /* in db but zeroID in ad-file, write it to ad-file if AFPVOL_CACHE */
822 if ((volinfo->v_flags & AFPVOL_CACHE) && ADFILE_OK) {
823 if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
824 dbd_log(LOGSTD, "Writing CNID data for '%s/%s' to AppleDouble file",
825 cwdbuf, name, ntohl(db_cnid));
826 ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
827 if (ad_open_metadata( name, adflags, O_RDWR, &ad) != 0) {
828 dbd_log(LOGSTD, "Error opening AppleDouble file for '%s/%s': %s",
829 cwdbuf, name, strerror(errno));
832 ad_setid( &ad, st->st_dev, st->st_ino, db_cnid, did, stamp);
834 ad_close_metadata(&ad);
844 This is called recursively for all dirs.
845 volroot=1 means we're in the volume root dir, 0 means we aren't.
846 We use this when checking for netatalk private folders like .AppleDB.
847 did is our parents CNID.
849 static int dbd_readdir(int volroot, cnid_t did)
851 int cwd, ret = 0, adflags, adfile_ok, addir_ok, encoding_ok;
856 static struct stat st; /* Save some stack space */
858 /* Check again for .AppleDouble folder, check_adfile also checks/creates it */
859 if ((addir_ok = check_addir(volroot)) != 0)
860 if ( ! (dbd_flags & DBD_FLAGS_SCAN))
861 /* Fatal on rebuild run, continue if only scanning ! */
864 /* Check AppleDouble files in AppleDouble folder, but only if it exists or could be created */
866 if ((read_addir()) != 0)
867 if ( ! (dbd_flags & DBD_FLAGS_SCAN))
868 /* Fatal on rebuild run, continue if only scanning ! */
871 if ((dp = opendir (".")) == NULL) {
872 dbd_log(LOGSTD, "Couldn't open the directory: %s",strerror(errno));
876 while ((ep = readdir (dp))) {
877 /* Check if we got a termination signal */
879 longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
881 /* Check if its "." or ".." */
882 if (DIR_DOT_OR_DOTDOT(ep->d_name))
885 /* Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop" */
886 if ((name = check_netatalk_dirs(ep->d_name)) != NULL) {
888 dbd_log(LOGSTD, "Nested %s in %s", name, cwdbuf);
892 /* Check for special folders in volume root e.g. ".zfs" */
894 if ((name = check_special_dirs(ep->d_name)) != NULL) {
895 dbd_log(LOGSTD, "Ignoring special dir \"%s\"", name);
900 /* Skip .AppleDouble dir in this loop */
901 if (STRCMP(ep->d_name, == , ADv2_DIRNAME))
904 if ((ret = lstat(ep->d_name, &st)) < 0) {
905 dbd_log( LOGSTD, "Lost file while reading dir '%s/%s', probably removed: %s",
906 cwdbuf, ep->d_name, strerror(errno));
910 switch (st.st_mode & S_IFMT) {
915 adflags = ADFLAGS_DIR;
918 dbd_log(LOGDEBUG, "Ignoring symlink %s/%s", cwdbuf, ep->d_name);
920 ret = check_symlink(ep->d_name, &adflags);
924 dbd_log(LOGSTD, "Error checking symlink %s/%s", cwdbuf, ep->d_name);
928 dbd_log(LOGSTD, "Bad filetype: %s/%s", cwdbuf, ep->d_name);
929 if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
930 if ((unlink(ep->d_name)) != 0) {
931 dbd_log(LOGSTD, "Error removing: %s/%s: %s", cwdbuf, ep->d_name, strerror(errno));
937 /**************************************************************************
939 **************************************************************************/
940 static unsigned long long statcount = 0;
947 if ((statcount % 10000) == 0) {
948 if (dbd_flags & DBD_FLAGS_STATS)
949 dbd_log(LOGSTD, "Scanned: %10llu, time: %10llu s",
950 statcount, (unsigned long long)(time(NULL) - t));
953 /**************************************************************************
955 **************************************************************************/
958 if ( -1 == (encoding_ok = check_name_encoding(ep->d_name)) ) {
959 /* If its a file: skipp all other tests now ! */
960 /* For dirs we could try to get a CNID for it and recurse, but currently I prefer not to */
964 /* Check for appledouble file, create if missing, but only if we have addir */
967 adfile_ok = check_adfile(ep->d_name, &st);
971 cnid = check_cnid(ep->d_name, did, &st, adfile_ok, adflags);
973 /* Now add this object to our rebuild dbd */
974 if (cnid && dbd_rebuild) {
975 static uint count = 0;
976 rqst.cnid = rply.cnid;
977 ret = dbd_rebuild_add(dbd_rebuild, &rqst, &rply);
978 dbif_txn_close(dbd_rebuild, ret);
979 if (rply.result != CNID_DBD_RES_OK) {
980 dbd_log( LOGDEBUG, "Fatal error adding CNID: %u for '%s/%s' to in-memory rebuild-db",
981 cnid, cwdbuf, ep->d_name);
982 longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
985 if (count == 10000) {
986 if (dbif_txn_checkpoint(dbd_rebuild, 0, 0, 0) < 0) {
987 dbd_log(LOGSTD, "Error checkpointing!");
996 if (volinfo->v_vfs_ea == AFPVOL_EA_AD)
997 check_eafiles(ep->d_name);
999 /**************************************************************************
1001 **************************************************************************/
1002 if (S_ISDIR(st.st_mode) && (cnid || nocniddb)) { /* If we have no cnid for it we cant recur */
1003 strcat(cwdbuf, "/");
1004 strcat(cwdbuf, ep->d_name);
1005 dbd_log( LOGDEBUG, "Entering directory: %s", cwdbuf);
1006 if (-1 == (cwd = open(".", O_RDONLY))) {
1007 dbd_log( LOGSTD, "Cant open directory '%s': %s", cwdbuf, strerror(errno));
1010 if (0 != chdir(ep->d_name)) {
1011 dbd_log( LOGSTD, "Cant chdir to directory '%s': %s", cwdbuf, strerror(errno));
1016 ret = dbd_readdir(0, cnid);
1020 *(strrchr(cwdbuf, '/')) = 0;
1027 Use results of previous checks
1034 static int scanvol(struct volinfo *vi, dbd_flags_t flags)
1036 /* Dont scanvol on no-appledouble vols */
1037 if (vi->v_flags & AFPVOL_NOADOUBLE) {
1038 dbd_log( LOGSTD, "Volume without AppleDouble support: skipping volume scanning.");
1042 /* Make this stuff accessible from all funcs easily */
1046 /* Init a fake struct vol with just enough so we can call ea_open and friends */
1047 volume.v_adouble = AD_VERSION2;
1048 volume.v_vfs_ea = volinfo->v_vfs_ea;
1049 initvol_vfs(&volume);
1051 /* Run with umask 0 */
1054 /* Remove trailing slash from volume, chdir to vol */
1055 if (volinfo->v_path[strlen(volinfo->v_path) - 1] == '/')
1056 volinfo->v_path[strlen(volinfo->v_path) - 1] = 0;
1057 strcpy(cwdbuf, volinfo->v_path);
1058 chdir(volinfo->v_path);
1060 /* Start recursion */
1061 if (dbd_readdir(1, htonl(2)) < 0) /* 2 = volumeroot CNID */
1068 Remove all CNIDs from dbd that are not in dbd_rebuild
1070 static void delete_orphaned_cnids(DBD *dbd, DBD *dbd_rebuild, dbd_flags_t flags)
1072 int ret = 0, deleted = 0;
1073 cnid_t dbd_cnid = 0, rebuild_cnid = 0;
1074 struct cnid_dbd_rqst rqst;
1075 struct cnid_dbd_rply rply;
1077 /* jump over rootinfo key */
1078 if ( dbif_idwalk(dbd, &dbd_cnid, 0) != 1)
1080 if ( dbif_idwalk(dbd_rebuild, &rebuild_cnid, 0) != 1)
1083 /* Get first id from dbd_rebuild */
1084 if ((dbif_idwalk(dbd_rebuild, &rebuild_cnid, 0)) == -1)
1087 /* Start main loop through dbd: get CNID from dbd */
1088 while ((dbif_idwalk(dbd, &dbd_cnid, 0)) == 1) {
1089 /* Check if we got a termination signal */
1091 longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
1093 if (deleted > 1000) {
1095 if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0) {
1096 dbd_log(LOGSTD, "Error checkpointing!");
1101 /* This should be the normal case: CNID is in both dbs */
1102 if (dbd_cnid == rebuild_cnid) {
1103 /* Get next CNID from rebuild db */
1104 if ((ret = dbif_idwalk(dbd_rebuild, &rebuild_cnid, 0)) == -1) {
1107 } else if (ret == 0) {
1108 /* end of rebuild_cnid, delete all remaining CNIDs from dbd */
1109 while ((dbif_idwalk(dbd, &dbd_cnid, 0)) == 1) {
1110 dbd_log(LOGSTD, "Orphaned CNID in database: %u", dbd_cnid);
1111 if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
1112 rqst.cnid = htonl(dbd_cnid);
1113 ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
1114 dbif_txn_close(dbd, ret);
1117 /* Check if we got a termination signal */
1119 longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
1123 /* Normal case (ret=1): continue while loop */
1127 if (dbd_cnid < rebuild_cnid) {
1128 /* CNID is orphaned -> delete */
1129 dbd_log(LOGSTD, "Orphaned CNID in database: %u.", dbd_cnid);
1130 if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
1131 rqst.cnid = htonl(dbd_cnid);
1132 ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
1133 dbif_txn_close(dbd, ret);
1139 if (dbd_cnid > rebuild_cnid) {
1140 dbif_idwalk(dbd, NULL, 1); /* Close cursor */
1141 dbif_idwalk(dbd_rebuild, NULL, 1); /* Close cursor */
1142 dbif_txn_close(dbd, 2);
1143 dbif_txn_close(dbd_rebuild, 2);
1144 dbd_log(LOGSTD, "Ghost CNID: %u. This is fatal! Dumping rebuild db:\n", rebuild_cnid);
1145 dbif_dump(dbd_rebuild, 0);
1146 dbd_log(LOGSTD, "Send this dump and a `dbd -d ...` dump to the Netatalk Dev team!");
1152 dbif_idwalk(dbd, NULL, 1); /* Close cursor */
1153 dbif_idwalk(dbd_rebuild, NULL, 1); /* Close cursor */
1157 static const char *get_tmpdb_path(void)
1159 pid_t pid = getpid();
1160 static char path[MAXPATHLEN];
1161 snprintf(path, MAXPATHLEN, "/tmp/tmpdb-dbd.%u", pid);
1162 if (mkdir(path, 0755) != 0)
1168 Main func called from cmd_dbd.c
1170 int cmd_dbd_scanvol(DBD *dbd_ref, struct volinfo *volinfo, dbd_flags_t flags)
1173 struct db_param db_param = { 0 };
1174 const char *tmpdb_path = NULL;
1176 /* Set cachesize for in-memory rebuild db */
1177 db_param.cachesize = 64 * 1024; /* 64 MB */
1178 db_param.logfile_autoremove = 1;
1180 /* Make it accessible for all funcs */
1183 /* We only support unicode volumes ! */
1184 if ( volinfo->v_volcharset != CH_UTF8) {
1185 dbd_log( LOGSTD, "Not a Unicode volume: %s, %u != %u", volinfo->v_volcodepage, volinfo->v_volcharset, CH_UTF8);
1189 /* Get volume stamp */
1190 dbd_getstamp(dbd, &rqst, &rply);
1191 if (rply.result != CNID_DBD_RES_OK) {
1195 memcpy(stamp, rply.name, CNID_DEV_LEN);
1197 /* temporary rebuild db, used with -re rebuild to delete unused CNIDs, not used with -f */
1198 if (! nocniddb && (flags & DBD_FLAGS_EXCL) && !(flags & DBD_FLAGS_FORCE)) {
1199 tmpdb_path = get_tmpdb_path();
1200 if (NULL == (dbd_rebuild = dbif_init(tmpdb_path, "cnid2.db"))) {
1205 if (dbif_env_open(dbd_rebuild,
1207 DB_CREATE | DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_TXN) < 0) {
1208 dbd_log(LOGSTD, "error opening tmp database!");
1212 if (0 != (dbif_open(dbd_rebuild, NULL, 0))) {
1217 if (0 != (dbif_copy_rootinfokey(dbd, dbd_rebuild))) {
1223 if (setjmp(jmp) != 0) {
1224 ret = 0; /* Got signal, jump from dbd_readdir */
1229 if ( (scanvol(volinfo, flags)) != 0) {
1236 dbif_txn_close(dbd, 1);
1238 dbif_txn_close(dbd_rebuild, 1);
1239 if ((flags & DBD_FLAGS_EXCL) && !(flags & DBD_FLAGS_FORCE))
1240 /* We can only do this in exclusive mode, otherwise we might delete CNIDs added from
1241 other clients in between our pass 1 and 2 */
1242 delete_orphaned_cnids(dbd, dbd_rebuild, flags);
1246 dbd_log(LOGDEBUG, "Closing tmp db");
1247 dbif_close(dbd_rebuild);
1250 char cmd[8 + MAXPATHLEN];
1251 snprintf(cmd, 8 + MAXPATHLEN, "rm -f %s/*", tmpdb_path);
1252 dbd_log( LOGDEBUG, "Removing temp database '%s'", tmpdb_path);
1254 snprintf(cmd, 8 + MAXPATHLEN, "rmdir %s", tmpdb_path);