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)
48 static struct volinfo *myvolinfo;
49 static char cwdbuf[MAXPATHLEN+1];
51 static DBD *dbd_rebuild;
52 static dbd_flags_t dbd_flags;
53 static char stamp[CNID_DEV_LEN];
54 static char *netatalk_dirs[] = {
59 static char *special_dirs[] = {
63 static struct cnid_dbd_rqst rqst;
64 static struct cnid_dbd_rply rply;
66 static struct vol volume; /* fake it for ea_open */
67 static char pname[MAXPATHLEN] = "../";
70 Taken form afpd/desktop.c
72 static char *utompath(char *upath)
74 static char mpath[ MAXPATHLEN + 2]; /* for convert_charset dest_len parameter +2 */
76 uint16_t flags = CONV_IGNORE | CONV_UNESCAPEHEX;
84 outlen = strlen(upath);
86 if ((myvolinfo->v_casefold & AFPVOL_UTOMUPPER))
87 flags |= CONV_TOUPPER;
88 else if ((myvolinfo->v_casefold & AFPVOL_UTOMLOWER))
89 flags |= CONV_TOLOWER;
91 if ((myvolinfo->v_flags & AFPVOL_EILSEQ)) {
92 flags |= CONV__EILSEQ;
95 /* convert charsets */
96 if ((size_t)-1 == ( outlen = convert_charset(myvolinfo->v_volcharset,
98 myvolinfo->v_maccharset,
99 u, outlen, mpath, MAXPATHLEN, &flags)) ) {
100 dbd_log( LOGSTD, "Conversion from %s to %s for %s failed.",
101 myvolinfo->v_volcodepage, myvolinfo->v_maccodepage, u);
109 Taken form afpd/desktop.c
111 static char *mtoupath(char *mpath)
113 static char upath[ MAXPATHLEN + 2]; /* for convert_charset dest_len parameter +2 */
122 if ( *mpath == '\0' ) {
126 /* set conversion flags */
127 if (!(myvolinfo->v_flags & AFPVOL_NOHEX))
128 flags |= CONV_ESCAPEHEX;
129 if (!(myvolinfo->v_flags & AFPVOL_USEDOTS))
130 flags |= CONV_ESCAPEDOTS;
132 if ((myvolinfo->v_casefold & AFPVOL_MTOUUPPER))
133 flags |= CONV_TOUPPER;
134 else if ((myvolinfo->v_casefold & AFPVOL_MTOULOWER))
135 flags |= CONV_TOLOWER;
137 if ((myvolinfo->v_flags & AFPVOL_EILSEQ)) {
138 flags |= CONV__EILSEQ;
147 if ((size_t)-1 == (outlen = convert_charset(CH_UTF8_MAC,
148 myvolinfo->v_volcharset,
149 myvolinfo->v_maccharset,
150 m, inplen, u, outlen, &flags)) ) {
151 dbd_log( LOGSTD, "conversion from UTF8-MAC to %s for %s failed.",
152 myvolinfo->v_volcodepage, mpath);
161 Check if "name" is pointing to
162 a) an object inside the current volume (return 0)
163 b) an object outside the current volume (return 1)
164 Then stats the pointed to object and if it is a dir ors ADFLAGS_DIR to *adflags
165 Return -1 on any serious error.
167 static int check_symlink(const char *name, int *adflags)
171 char pathbuf[MAXPATHLEN + 1];
175 if ((len = readlink(name, pathbuf, MAXPATHLEN)) == -1) {
176 dbd_log(LOGSTD, "Error reading link info for '%s/%s': %s",
177 cwdbuf, name, strerror(errno));
182 if ((stat(pathbuf, &st)) != 0) {
183 dbd_log(LOGSTD, "stat error '%s': %s", pathbuf, strerror(errno));
187 if ((cwd = open(".", O_RDONLY)) < 0) {
188 dbd_log(LOGSTD, "error opening cwd '%s': %s", cwdbuf, strerror(errno));
192 if (S_ISDIR(st.st_mode)) {
193 *adflags |= ADFLAGS_DIR;
195 /* get basename from path */
196 if ((sep = strrchr(pathbuf, '/')) == NULL)
197 /* just a file at the same level */
199 sep = 0; /* pathbuf now contains the directory*/
202 /* fchdir() to pathbuf so we can easily get its path with getcwd() */
203 if ((chdir(pathbuf)) != 0) {
204 dbd_log(LOGSTD, "Cant chdir to '%s': %s", pathbuf, strerror(errno));
208 if ((getcwd(pathbuf, MAXPATHLEN)) == NULL) {
209 dbd_log(LOGSTD, "Cant get symlink'ed dir '%s/%s': %s", cwdbuf, pathbuf, strerror(errno));
210 if ((fchdir(cwd)) != 0)
212 longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
216 if ((fchdir(cwd)) != 0)
218 longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
221 We now have the symlink target dir as absoulte path in pathbuf
222 and can compare it with the currents volume path
225 while (myvolinfo->v_path[i]) {
226 if ((pathbuf[i] == 0) || (myvolinfo->v_path[i] != pathbuf[i])) {
227 dbd_log( LOGDEBUG, "extra-share symlink '%s/%s', following", cwdbuf, name);
233 dbd_log( LOGDEBUG, "intra-share symlink '%s/%s', not following", cwdbuf, name);
239 Check for wrong encoding e.g. "." at the beginning is not CAP encoded (:2e) although volume is default !AFPVOL_USEDOTS.
240 We do it by roundtripiping from volcharset to UTF8-MAC and back and then compare the result.
242 static int check_name_encoding(char *uname)
246 roundtripped = mtoupath(utompath(uname));
248 dbd_log( LOGSTD, "Error checking encoding for '%s/%s'", cwdbuf, uname);
252 if ( STRCMP(uname, !=, roundtripped)) {
253 dbd_log( LOGSTD, "Bad encoding for '%s/%s'", cwdbuf, uname);
261 Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop"
262 Returns pointer to name or NULL.
264 static const char *check_netatalk_dirs(const char *name)
268 for (c=0; netatalk_dirs[c]; c++) {
269 if ((strcmp(name, netatalk_dirs[c])) == 0)
270 return netatalk_dirs[c];
276 Check for special names
277 Returns pointer to name or NULL.
279 static const char *check_special_dirs(const char *name)
283 for (c=0; special_dirs[c]; c++) {
284 if ((strcmp(name, special_dirs[c])) == 0)
285 return special_dirs[c];
291 Check for .AppleDouble file, create if missing
293 static int check_adfile(const char *fname, const struct stat *st)
299 if (dbd_flags & DBD_FLAGS_CLEANUP)
302 if (S_ISREG(st->st_mode))
305 adflags = ADFLAGS_DIR;
307 adname = myvolinfo->ad_path(fname, adflags);
309 if ((ret = access( adname, F_OK)) != 0) {
310 if (errno != ENOENT) {
311 dbd_log(LOGSTD, "Access error for ad-file '%s/%s': %s",
312 cwdbuf, adname, strerror(errno));
315 /* Missing. Log and create it */
316 dbd_log(LOGSTD, "Missing AppleDouble file '%s/%s'", cwdbuf, adname);
318 if (dbd_flags & DBD_FLAGS_SCAN)
319 /* Scan only requested, dont change anything */
323 ad_init(&ad, myvolinfo->v_adouble, myvolinfo->v_ad_options);
325 if ((ret = ad_open_metadata( fname, adflags, O_CREAT, &ad)) != 0) {
326 dbd_log( LOGSTD, "Error creating AppleDouble file '%s/%s': %s",
327 cwdbuf, adname, strerror(errno));
332 /* Set name in ad-file */
333 ad_setname(&ad, utompath((char *)fname));
335 ad_close_metadata(&ad);
337 chown(adname, st->st_uid, st->st_gid);
338 /* FIXME: should we inherit mode too here ? */
340 chmod(adname, st->st_mode);
343 ad_init(&ad, myvolinfo->v_adouble, myvolinfo->v_ad_options);
344 if (ad_open_metadata( fname, adflags, O_RDONLY, &ad) != 0) {
345 dbd_log( LOGSTD, "Error opening AppleDouble file for '%s/%s'", cwdbuf, fname);
348 ad_close_metadata(&ad);
354 Remove all files with file::EA* from adouble dir
356 static void remove_eafiles(const char *name, struct ea *ea)
360 char eaname[MAXPATHLEN];
362 strlcpy(eaname, name, sizeof(eaname));
363 if (strlcat(eaname, "::EA", sizeof(eaname)) >= sizeof(eaname)) {
364 dbd_log(LOGSTD, "name too long: '%s/%s/%s'", cwdbuf, ADv2_DIRNAME, name);
368 if ((chdir(ADv2_DIRNAME)) != 0) {
369 dbd_log(LOGSTD, "Couldn't chdir to '%s/%s': %s",
370 cwdbuf, ADv2_DIRNAME, strerror(errno));
374 if ((dp = opendir(".")) == NULL) {
375 dbd_log(LOGSTD, "Couldn't open the directory '%s/%s': %s",
376 cwdbuf, ADv2_DIRNAME, strerror(errno));
380 while ((ep = readdir(dp))) {
381 if (strstr(ep->d_name, eaname) != NULL) {
382 dbd_log(LOGSTD, "Removing EA file: '%s/%s/%s'",
383 cwdbuf, ADv2_DIRNAME, ep->d_name);
384 if ((unlink(ep->d_name)) != 0) {
385 dbd_log(LOGSTD, "Error unlinking EA file '%s/%s/%s': %s",
386 cwdbuf, ADv2_DIRNAME, ep->d_name, strerror(errno));
397 Check Extended Attributes files
399 static int check_eafiles(const char *fname)
401 unsigned int count = 0;
407 if ((ret = ea_open(&volume, fname, EA_RDWR, &ea)) != 0) {
410 dbd_log(LOGSTD, "Error calling ea_open for file: %s/%s, removing EA files", cwdbuf, fname);
411 if ( ! (dbd_flags & DBD_FLAGS_SCAN))
412 remove_eafiles(fname, &ea);
417 while (count < ea.ea_count) {
418 dbd_log(LOGDEBUG, "EA: %s", (*ea.ea_entries)[count].ea_name);
421 eaname = ea_path(&ea, (*ea.ea_entries)[count].ea_name, 0);
423 if (lstat(eaname, &st) != 0) {
425 dbd_log(LOGSTD, "Missing EA: %s/%s", cwdbuf, eaname);
427 dbd_log(LOGSTD, "Bogus EA: %s/%s", cwdbuf, eaname);
429 } else if (st.st_size != (*ea.ea_entries)[count].ea_size) {
430 dbd_log(LOGSTD, "Bogus EA: %s/%s, removing it...", cwdbuf, eaname);
432 if ((unlink(eaname)) != 0)
433 dbd_log(LOGSTD, "Error removing EA file '%s/%s': %s",
434 cwdbuf, eaname, strerror(errno));
438 /* Be CAREFUL here! This should do what ea_delentry does. ea_close relies on it !*/
439 free((*ea.ea_entries)[count].ea_name);
440 (*ea.ea_entries)[count].ea_name = NULL;
451 Check for .AppleDouble folder and .Parent, create if missing
453 static int check_addir(int volroot)
455 int addir_ok, adpar_ok;
460 if (dbd_flags & DBD_FLAGS_CLEANUP)
463 /* Check for ad-dir */
464 if ( (addir_ok = access(ADv2_DIRNAME, F_OK)) != 0) {
465 if (errno != ENOENT) {
466 dbd_log(LOGSTD, "Access error in directory %s: %s", cwdbuf, strerror(errno));
469 dbd_log(LOGSTD, "Missing %s for '%s'", ADv2_DIRNAME, cwdbuf);
472 /* Check for ".Parent" */
473 if ( (adpar_ok = access(myvolinfo->ad_path(".", ADFLAGS_DIR), F_OK)) != 0) {
474 if (errno != ENOENT) {
475 dbd_log(LOGSTD, "Access error on '%s/%s': %s",
476 cwdbuf, myvolinfo->ad_path(".", ADFLAGS_DIR), strerror(errno));
479 dbd_log(LOGSTD, "Missing .AppleDouble/.Parent for '%s'", cwdbuf);
482 /* Is one missing ? */
483 if ((addir_ok != 0) || (adpar_ok != 0)) {
484 /* Yes, but are we only scanning ? */
485 if (dbd_flags & DBD_FLAGS_SCAN) {
486 /* Yes: missing .Parent is not a problem, but missing ad-dir
487 causes later checking of ad-files to fail. So we have to return appropiately */
490 else /* (adpar_ok != 0) */
494 /* Create ad dir and set name */
495 ad_init(&ad, myvolinfo->v_adouble, myvolinfo->v_ad_options);
497 if (ad_open_metadata( ".", ADFLAGS_DIR, O_CREAT, &ad) != 0) {
498 dbd_log( LOGSTD, "Error creating AppleDouble dir in %s: %s", cwdbuf, strerror(errno));
502 /* Get basename of cwd from cwdbuf */
503 mname = utompath(strrchr(cwdbuf, '/') + 1);
505 /* Update name in ad file */
506 ad_setname(&ad, mname);
508 ad_close_metadata(&ad);
510 /* Inherit owner/group from "." to ".AppleDouble" and ".Parent" */
511 if ((lstat(".", &st)) != 0) {
512 dbd_log( LOGSTD, "Couldnt stat %s: %s", cwdbuf, strerror(errno));
515 chown(ADv2_DIRNAME, st.st_uid, st.st_gid);
516 chown(myvolinfo->ad_path(".", ADFLAGS_DIR), st.st_uid, st.st_gid);
523 Check if file cotains "::EA" and if it does check if its correspondig data fork exists.
525 0 = name is not an EA file
526 1 = name is an EA file and no problem was found
527 -1 = name is an EA file and data fork is gone
529 static int check_eafile_in_adouble(const char *name)
532 char *namep, *namedup = NULL;
534 /* Check if this is an AFPVOL_EA_AD vol */
535 if (myvolinfo->v_vfs_ea == AFPVOL_EA_AD) {
536 /* Does the filename contain "::EA" ? */
537 namedup = strdup(name);
538 if ((namep = strstr(namedup, "::EA")) == NULL) {
542 /* File contains "::EA" so it's an EA file. Check for data file */
544 /* Get string before "::EA" from EA filename */
546 strlcpy(pname + 3, namedup, sizeof(pname)); /* Prepends "../" */
548 if ((access( pname, F_OK)) == 0) {
553 if (errno != ENOENT) {
554 dbd_log(LOGSTD, "Access error for file '%s/%s': %s",
555 cwdbuf, name, strerror(errno));
559 /* Orphaned EA file*/
560 dbd_log(LOGSTD, "Orphaned Extended Attribute file '%s/%s/%s'",
561 cwdbuf, ADv2_DIRNAME, name);
563 if (dbd_flags & DBD_FLAGS_SCAN)
564 /* Scan only requested, dont change anything */
567 if ((unlink(name)) != 0) {
568 dbd_log(LOGSTD, "Error unlinking orphaned Extended Attribute file '%s/%s/%s'",
569 cwdbuf, ADv2_DIRNAME, name);
573 } /* if AFPVOL_EA_AD */
583 Check files and dirs inside .AppleDouble folder:
584 - remove orphaned files
587 static int read_addir(void)
593 if ((chdir(ADv2_DIRNAME)) != 0) {
594 dbd_log(LOGSTD, "Couldn't chdir to '%s/%s': %s",
595 cwdbuf, ADv2_DIRNAME, strerror(errno));
599 if ((dp = opendir(".")) == NULL) {
600 dbd_log(LOGSTD, "Couldn't open the directory '%s/%s': %s",
601 cwdbuf, ADv2_DIRNAME, strerror(errno));
605 while ((ep = readdir(dp))) {
606 /* Check if its "." or ".." */
607 if (DIR_DOT_OR_DOTDOT(ep->d_name))
610 if (STRCMP(ep->d_name, ==, ".Parent"))
613 if ((lstat(ep->d_name, &st)) < 0) {
614 dbd_log( LOGSTD, "Lost file or dir while enumeratin dir '%s/%s/%s', probably removed: %s",
615 cwdbuf, ADv2_DIRNAME, ep->d_name, strerror(errno));
620 if (S_ISDIR(st.st_mode)) {
621 dbd_log( LOGSTD, "Unexpected directory '%s' in AppleDouble dir '%s/%s'",
622 ep->d_name, cwdbuf, ADv2_DIRNAME);
626 /* Check if for orphaned and corrupt Extended Attributes file */
627 if (check_eafile_in_adouble(ep->d_name) != 0)
630 /* Check for data file */
631 strcpy(pname + 3, ep->d_name);
632 if ((access( pname, F_OK)) != 0) {
633 if (errno != ENOENT) {
634 dbd_log(LOGSTD, "Access error for file '%s/%s': %s",
635 cwdbuf, pname, strerror(errno));
638 /* Orphaned ad-file*/
639 dbd_log(LOGSTD, "Orphaned AppleDoube file '%s/%s/%s'",
640 cwdbuf, ADv2_DIRNAME, ep->d_name);
642 if (dbd_flags & DBD_FLAGS_SCAN)
643 /* Scan only requested, dont change anything */
646 if ((unlink(ep->d_name)) != 0) {
647 dbd_log(LOGSTD, "Error unlinking orphaned AppleDoube file '%s/%s/%s'",
648 cwdbuf, ADv2_DIRNAME, ep->d_name);
654 if ((chdir("..")) != 0) {
655 dbd_log(LOGSTD, "Couldn't chdir back to '%s' from AppleDouble dir: %s",
656 cwdbuf, strerror(errno));
657 /* This really is EOT! */
658 longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
667 Check CNID for a file/dir, both from db and from ad-file.
668 For detailed specs see intro.
670 @return Correct CNID of object or CNID_INVALID (ie 0) on error
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 ( (myvolinfo->v_flags & AFPVOL_CACHE) && ADFILE_OK) {
692 ad_init(&ad, myvolinfo->v_adouble, myvolinfo->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 ( ! (myvolinfo->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 if (dbif_txn_close(dbd, ret) != 0)
737 if (rply.result == CNID_DBD_RES_OK) {
739 } else if (rply.result == CNID_DBD_RES_NOTFOUND) {
740 if ( ! (dbd_flags & DBD_FLAGS_FORCE))
741 dbd_log( LOGSTD, "No CNID for '%s/%s' in database", cwdbuf, name);
744 dbd_log( LOGSTD, "Fatal error resolving '%s/%s'", cwdbuf, name);
748 /* Compare results from both CNID searches */
749 if (ad_cnid && db_cnid && (ad_cnid == db_cnid)) {
750 /* Everything is fine */
752 } else if (ad_cnid && db_cnid && (ad_cnid != db_cnid)) {
753 /* Mismatch ? Delete both from db and re-add data from file */
754 dbd_log( LOGSTD, "CNID mismatch for '%s/%s', db: %u, ad-file: %u", cwdbuf, name, ntohl(db_cnid), ntohl(ad_cnid));
755 if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
757 ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
758 if (dbif_txn_close(dbd, ret) != 0)
762 ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
763 if (dbif_txn_close(dbd, ret) != 0)
766 ret = dbd_rebuild_add(dbd, &rqst, &rply);
767 if (dbif_txn_close(dbd, ret) != 0)
771 } else if (ad_cnid && (db_cnid == 0)) {
772 /* in ad-file but not in db */
773 if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
774 /* Ensure the cnid from the ad-file is not already occupied by another file */
775 dbd_log(LOGDEBUG, "Checking whether CNID %u from ad-file is occupied",
779 ret = dbd_resolve(dbd, &rqst, &rply);
780 if (ret == CNID_DBD_RES_OK) {
781 /* Occupied! Choose another, update ad-file */
782 ret = dbd_add(dbd, &rqst, &rply, 1);
783 if (dbif_txn_close(dbd, ret) != 0)
786 dbd_log(LOGSTD, "New CNID for '%s/%s': %u", cwdbuf, name, ntohl(db_cnid));
788 if ((myvolinfo->v_flags & AFPVOL_CACHE)
790 && ( ! (dbd_flags & DBD_FLAGS_SCAN))) {
791 dbd_log(LOGSTD, "Writing CNID data for '%s/%s' to AppleDouble file",
792 cwdbuf, name, ntohl(db_cnid));
793 ad_init(&ad, myvolinfo->v_adouble, myvolinfo->v_ad_options);
794 if (ad_open_metadata( name, adflags, O_RDWR, &ad) != 0) {
795 dbd_log(LOGSTD, "Error opening AppleDouble file for '%s/%s': %s",
796 cwdbuf, name, strerror(errno));
799 ad_setid( &ad, st->st_dev, st->st_ino, db_cnid, did, stamp);
801 ad_close_metadata(&ad);
806 dbd_log(LOGDEBUG, "CNID rebuild add '%s/%s' with CNID from ad-file %u",
807 cwdbuf, name, ntohl(ad_cnid));
809 ret = dbd_rebuild_add(dbd, &rqst, &rply);
810 if (dbif_txn_close(dbd, ret) != 0)
814 } else if ((db_cnid == 0) && (ad_cnid == 0)) {
815 /* No CNID at all, we clearly have to allocate a fresh one... */
816 /* Note: the next test will use this new CNID too! */
817 if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
819 ret = dbd_add(dbd, &rqst, &rply, 1);
820 if (dbif_txn_close(dbd, ret) != 0)
823 dbd_log(LOGSTD, "New CNID for '%s/%s': %u", cwdbuf, name, ntohl(db_cnid));
827 if ((ad_cnid == 0) && db_cnid) {
828 /* in db but zeroID in ad-file, write it to ad-file if AFPVOL_CACHE */
829 if ((myvolinfo->v_flags & AFPVOL_CACHE) && ADFILE_OK) {
830 if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
831 dbd_log(LOGSTD, "Writing CNID data for '%s/%s' to AppleDouble file",
832 cwdbuf, name, ntohl(db_cnid));
833 ad_init(&ad, myvolinfo->v_adouble, myvolinfo->v_ad_options);
834 if (ad_open_metadata( name, adflags, O_RDWR, &ad) != 0) {
835 dbd_log(LOGSTD, "Error opening AppleDouble file for '%s/%s': %s",
836 cwdbuf, name, strerror(errno));
839 ad_setid( &ad, st->st_dev, st->st_ino, db_cnid, did, stamp);
841 ad_close_metadata(&ad);
851 This is called recursively for all dirs.
852 volroot=1 means we're in the volume root dir, 0 means we aren't.
853 We use this when checking for netatalk private folders like .AppleDB.
854 did is our parents CNID.
856 static int dbd_readdir(int volroot, cnid_t did)
858 int cwd, ret = 0, adflags, adfile_ok, addir_ok, encoding_ok;
863 static struct stat st; /* Save some stack space */
865 /* keep trying to get the lock */
867 if ((db_locked = get_lock(1, NULL)) == -1)
870 /* Check again for .AppleDouble folder, check_adfile also checks/creates it */
871 if ((addir_ok = check_addir(volroot)) != 0)
872 if ( ! (dbd_flags & DBD_FLAGS_SCAN))
873 /* Fatal on rebuild run, continue if only scanning ! */
876 /* Check AppleDouble files in AppleDouble folder, but only if it exists or could be created */
878 if ((read_addir()) != 0)
879 if ( ! (dbd_flags & DBD_FLAGS_SCAN))
880 /* Fatal on rebuild run, continue if only scanning ! */
883 if ((dp = opendir (".")) == NULL) {
884 dbd_log(LOGSTD, "Couldn't open the directory: %s",strerror(errno));
888 while ((ep = readdir (dp))) {
889 /* Check if we got a termination signal */
891 longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
893 /* Check if its "." or ".." */
894 if (DIR_DOT_OR_DOTDOT(ep->d_name))
897 /* Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop" */
898 if ((name = check_netatalk_dirs(ep->d_name)) != NULL) {
900 dbd_log(LOGSTD, "Nested %s in %s", name, cwdbuf);
904 /* Check for special folders in volume root e.g. ".zfs" */
906 if ((name = check_special_dirs(ep->d_name)) != NULL) {
907 dbd_log(LOGSTD, "Ignoring special dir \"%s\"", name);
912 /* Skip .AppleDouble dir in this loop */
913 if (STRCMP(ep->d_name, == , ADv2_DIRNAME))
916 if ((ret = lstat(ep->d_name, &st)) < 0) {
917 dbd_log( LOGSTD, "Lost file while reading dir '%s/%s', probably removed: %s",
918 cwdbuf, ep->d_name, strerror(errno));
922 switch (st.st_mode & S_IFMT) {
927 adflags = ADFLAGS_DIR;
930 dbd_log(LOGDEBUG, "Ignoring symlink %s/%s", cwdbuf, ep->d_name);
932 ret = check_symlink(ep->d_name, &adflags);
936 dbd_log(LOGSTD, "Error checking symlink %s/%s", cwdbuf, ep->d_name);
940 dbd_log(LOGSTD, "Bad filetype: %s/%s", cwdbuf, ep->d_name);
941 if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
942 if ((unlink(ep->d_name)) != 0) {
943 dbd_log(LOGSTD, "Error removing: %s/%s: %s", cwdbuf, ep->d_name, strerror(errno));
949 /**************************************************************************
951 **************************************************************************/
952 static unsigned long long statcount = 0;
959 if ((statcount % 10000) == 0) {
960 if (dbd_flags & DBD_FLAGS_STATS)
961 dbd_log(LOGSTD, "Scanned: %10llu, time: %10llu s",
962 statcount, (unsigned long long)(time(NULL) - t));
965 /**************************************************************************
967 **************************************************************************/
970 if ( -1 == (encoding_ok = check_name_encoding(ep->d_name)) ) {
971 /* If its a file: skipp all other tests now ! */
972 /* For dirs we could try to get a CNID for it and recurse, but currently I prefer not to */
976 /* Check for appledouble file, create if missing, but only if we have addir */
979 adfile_ok = check_adfile(ep->d_name, &st);
983 cnid = check_cnid(ep->d_name, did, &st, adfile_ok, adflags);
985 /* Now add this object to our rebuild dbd */
986 if (cnid && dbd_rebuild) {
987 static uint count = 0;
988 rqst.cnid = rply.cnid;
989 ret = dbd_rebuild_add(dbd_rebuild, &rqst, &rply);
990 if (dbif_txn_close(dbd_rebuild, ret) != 0)
992 if (rply.result != CNID_DBD_RES_OK) {
993 dbd_log( LOGSTD, "Fatal error adding CNID: %u for '%s/%s' to in-memory rebuild-db",
994 cnid, cwdbuf, ep->d_name);
998 if (count == 10000) {
999 if (dbif_txn_checkpoint(dbd_rebuild, 0, 0, 0) < 0) {
1000 dbd_log(LOGSTD, "Error checkpointing!");
1008 /* Check EA files */
1009 if (myvolinfo->v_vfs_ea == AFPVOL_EA_AD)
1010 check_eafiles(ep->d_name);
1012 /**************************************************************************
1014 **************************************************************************/
1015 if (S_ISDIR(st.st_mode) && (cnid || nocniddb)) { /* If we have no cnid for it we cant recur */
1016 strcat(cwdbuf, "/");
1017 strcat(cwdbuf, ep->d_name);
1018 dbd_log( LOGDEBUG, "Entering directory: %s", cwdbuf);
1019 if (-1 == (cwd = open(".", O_RDONLY))) {
1020 dbd_log( LOGSTD, "Cant open directory '%s': %s", cwdbuf, strerror(errno));
1023 if (0 != chdir(ep->d_name)) {
1024 dbd_log( LOGSTD, "Cant chdir to directory '%s': %s", cwdbuf, strerror(errno));
1029 ret = dbd_readdir(0, cnid);
1033 *(strrchr(cwdbuf, '/')) = 0;
1040 Use results of previous checks
1047 static int scanvol(struct volinfo *vi, dbd_flags_t flags)
1049 /* Dont scanvol on no-appledouble vols */
1050 if (vi->v_flags & AFPVOL_NOADOUBLE) {
1051 dbd_log( LOGSTD, "Volume without AppleDouble support: skipping volume scanning.");
1055 /* Make this stuff accessible from all funcs easily */
1059 /* Init a fake struct vol with just enough so we can call ea_open and friends */
1060 volume.v_adouble = AD_VERSION2;
1061 volume.v_vfs_ea = myvolinfo->v_vfs_ea;
1062 initvol_vfs(&volume);
1064 /* Run with umask 0 */
1067 /* Remove trailing slash from volume, chdir to vol */
1068 if (myvolinfo->v_path[strlen(myvolinfo->v_path) - 1] == '/')
1069 myvolinfo->v_path[strlen(myvolinfo->v_path) - 1] = 0;
1070 strcpy(cwdbuf, myvolinfo->v_path);
1071 chdir(myvolinfo->v_path);
1073 /* Start recursion */
1074 if (dbd_readdir(1, htonl(2)) < 0) /* 2 = volumeroot CNID */
1081 Remove all CNIDs from dbd that are not in dbd_rebuild
1083 static void delete_orphaned_cnids(DBD *dbd, DBD *dbd_rebuild, dbd_flags_t flags)
1085 int ret = 0, deleted = 0;
1086 cnid_t dbd_cnid = 0, rebuild_cnid = 0;
1087 struct cnid_dbd_rqst rqst;
1088 struct cnid_dbd_rply rply;
1090 /* jump over rootinfo key */
1091 if ( dbif_idwalk(dbd, &dbd_cnid, 0) != 1)
1093 if ( dbif_idwalk(dbd_rebuild, &rebuild_cnid, 0) != 1)
1096 /* Get first id from dbd_rebuild */
1097 if ((dbif_idwalk(dbd_rebuild, &rebuild_cnid, 0)) == -1)
1100 /* Start main loop through dbd: get CNID from dbd */
1101 while ((dbif_idwalk(dbd, &dbd_cnid, 0)) == 1) {
1102 /* Check if we got a termination signal */
1104 longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
1106 if (deleted > 1000) {
1108 if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0) {
1109 dbd_log(LOGSTD, "Error checkpointing!");
1114 /* This should be the normal case: CNID is in both dbs */
1115 if (dbd_cnid == rebuild_cnid) {
1116 /* Get next CNID from rebuild db */
1117 if ((ret = dbif_idwalk(dbd_rebuild, &rebuild_cnid, 0)) == -1) {
1120 } else if (ret == 0) {
1121 /* end of rebuild_cnid, delete all remaining CNIDs from dbd */
1122 while ((dbif_idwalk(dbd, &dbd_cnid, 0)) == 1) {
1123 dbd_log(LOGSTD, "Orphaned CNID in database: %u", dbd_cnid);
1124 if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
1125 rqst.cnid = htonl(dbd_cnid);
1126 if ((ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID)) == -1) {
1127 dbd_log(LOGSTD, "Error deleting CNID %u", dbd_cnid);
1128 (void)dbif_txn_abort(dbd);
1132 if (dbif_txn_close(dbd, ret) != 0)
1136 /* Check if we got a termination signal */
1138 longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
1142 /* Normal case (ret=1): continue while loop */
1146 if (dbd_cnid < rebuild_cnid) {
1147 /* CNID is orphaned -> delete */
1148 dbd_log(LOGSTD, "One orphaned CNID in database: %u.", dbd_cnid);
1149 if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
1150 rqst.cnid = htonl(dbd_cnid);
1151 if ((ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID)) == -1) {
1152 dbd_log(LOGSTD, "Error deleting CNID %u", dbd_cnid);
1153 (void)dbif_txn_abort(dbd);
1156 if (dbif_txn_close(dbd, ret) != 0)
1163 if (dbd_cnid > rebuild_cnid) {
1164 dbif_idwalk(dbd, NULL, 1); /* Close cursor */
1165 dbif_idwalk(dbd_rebuild, NULL, 1); /* Close cursor */
1166 (void)dbif_txn_close(dbd, 2);
1167 (void)dbif_txn_close(dbd_rebuild, 2);
1168 dbd_log(LOGSTD, "Ghost CNID: %u. This is fatal! Dumping rebuild db:\n", rebuild_cnid);
1169 dbif_dump(dbd_rebuild, 0);
1170 dbd_log(LOGSTD, "Send this dump and a `dbd -d ...` dump to the Netatalk Dev team!");
1173 } /* while ((dbif_idwalk(dbd, &dbd_cnid, 0)) == 1) */
1176 dbif_idwalk(dbd, NULL, 1); /* Close cursor */
1177 dbif_idwalk(dbd_rebuild, NULL, 1); /* Close cursor */
1181 static const char *get_tmpdb_path(void)
1183 pid_t pid = getpid();
1184 static char path[MAXPATHLEN];
1185 snprintf(path, MAXPATHLEN, "/tmp/tmpdb-dbd.%u", pid);
1186 if (mkdir(path, 0755) != 0)
1192 Main func called from cmd_dbd.c
1194 int cmd_dbd_scanvol(DBD *dbd_ref, struct volinfo *vi, dbd_flags_t flags)
1197 struct db_param db_param = { 0 };
1198 const char *tmpdb_path = NULL;
1200 /* Set cachesize for in-memory rebuild db */
1201 db_param.cachesize = 64 * 1024; /* 64 MB */
1202 db_param.maxlocks = DEFAULT_MAXLOCKS;
1203 db_param.maxlockobjs = DEFAULT_MAXLOCKOBJS;
1204 db_param.logfile_autoremove = 1;
1206 /* Make it accessible for all funcs */
1209 /* We only support unicode volumes ! */
1210 if ( vi->v_volcharset != CH_UTF8) {
1211 dbd_log( LOGSTD, "Not a Unicode volume: %s, %u != %u", vi->v_volcodepage, vi->v_volcharset, CH_UTF8);
1215 /* temporary rebuild db, used with -re rebuild to delete unused CNIDs, not used with -f */
1216 if (! nocniddb && !(flags & DBD_FLAGS_FORCE)) {
1217 /* Get volume stamp */
1218 dbd_getstamp(dbd, &rqst, &rply);
1219 if (rply.result != CNID_DBD_RES_OK)
1221 memcpy(stamp, rply.name, CNID_DEV_LEN);
1223 /* open/create rebuild dbd, copy rootinfo key */
1224 tmpdb_path = get_tmpdb_path();
1225 if (NULL == (dbd_rebuild = dbif_init(tmpdb_path, "cnid2.db"))) {
1230 if (dbif_env_open(dbd_rebuild,
1232 DB_CREATE | DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_TXN) < 0) {
1233 dbd_log(LOGSTD, "error opening tmp database!");
1237 if (0 != (dbif_open(dbd_rebuild, NULL, 0))) {
1242 if (0 != (dbif_copy_rootinfokey(dbd, dbd_rebuild))) {
1248 if (setjmp(jmp) != 0) {
1249 ret = 0; /* Got signal, jump from dbd_readdir */
1254 if ( (scanvol(vi, flags)) != 0) {
1261 if (dbif_txn_close(dbd, 2) != 0)
1264 if (dbif_txn_close(dbd_rebuild, 2) != 0)
1266 if ((ret == 0) && dbd_rebuild && (flags & DBD_FLAGS_EXCL) && !(flags & DBD_FLAGS_FORCE))
1267 /* We can only do this in exclusive mode, otherwise we might delete CNIDs added from
1268 other clients in between our pass 1 and 2 */
1269 delete_orphaned_cnids(dbd, dbd_rebuild, flags);
1273 dbd_log(LOGDEBUG, "Closing tmp db");
1274 dbif_close(dbd_rebuild);
1277 char cmd[8 + MAXPATHLEN];
1278 snprintf(cmd, 8 + MAXPATHLEN, "rm -f %s/*", tmpdb_path);
1279 dbd_log( LOGDEBUG, "Removing temp database '%s'", tmpdb_path);
1281 snprintf(cmd, 8 + MAXPATHLEN, "rmdir %s", tmpdb_path);