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 /* keep trying to get the lock */
860 if ((db_locked = get_lock(1, NULL)) == -1)
863 /* Check again for .AppleDouble folder, check_adfile also checks/creates it */
864 if ((addir_ok = check_addir(volroot)) != 0)
865 if ( ! (dbd_flags & DBD_FLAGS_SCAN))
866 /* Fatal on rebuild run, continue if only scanning ! */
869 /* Check AppleDouble files in AppleDouble folder, but only if it exists or could be created */
871 if ((read_addir()) != 0)
872 if ( ! (dbd_flags & DBD_FLAGS_SCAN))
873 /* Fatal on rebuild run, continue if only scanning ! */
876 if ((dp = opendir (".")) == NULL) {
877 dbd_log(LOGSTD, "Couldn't open the directory: %s",strerror(errno));
881 while ((ep = readdir (dp))) {
882 /* Check if we got a termination signal */
884 longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
886 /* Check if its "." or ".." */
887 if (DIR_DOT_OR_DOTDOT(ep->d_name))
890 /* Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop" */
891 if ((name = check_netatalk_dirs(ep->d_name)) != NULL) {
893 dbd_log(LOGSTD, "Nested %s in %s", name, cwdbuf);
897 /* Check for special folders in volume root e.g. ".zfs" */
899 if ((name = check_special_dirs(ep->d_name)) != NULL) {
900 dbd_log(LOGSTD, "Ignoring special dir \"%s\"", name);
905 /* Skip .AppleDouble dir in this loop */
906 if (STRCMP(ep->d_name, == , ADv2_DIRNAME))
909 if ((ret = lstat(ep->d_name, &st)) < 0) {
910 dbd_log( LOGSTD, "Lost file while reading dir '%s/%s', probably removed: %s",
911 cwdbuf, ep->d_name, strerror(errno));
915 switch (st.st_mode & S_IFMT) {
920 adflags = ADFLAGS_DIR;
923 dbd_log(LOGDEBUG, "Ignoring symlink %s/%s", cwdbuf, ep->d_name);
925 ret = check_symlink(ep->d_name, &adflags);
929 dbd_log(LOGSTD, "Error checking symlink %s/%s", cwdbuf, ep->d_name);
933 dbd_log(LOGSTD, "Bad filetype: %s/%s", cwdbuf, ep->d_name);
934 if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
935 if ((unlink(ep->d_name)) != 0) {
936 dbd_log(LOGSTD, "Error removing: %s/%s: %s", cwdbuf, ep->d_name, strerror(errno));
942 /**************************************************************************
944 **************************************************************************/
945 static unsigned long long statcount = 0;
952 if ((statcount % 10000) == 0) {
953 if (dbd_flags & DBD_FLAGS_STATS)
954 dbd_log(LOGSTD, "Scanned: %10llu, time: %10llu s",
955 statcount, (unsigned long long)(time(NULL) - t));
958 /**************************************************************************
960 **************************************************************************/
963 if ( -1 == (encoding_ok = check_name_encoding(ep->d_name)) ) {
964 /* If its a file: skipp all other tests now ! */
965 /* For dirs we could try to get a CNID for it and recurse, but currently I prefer not to */
969 /* Check for appledouble file, create if missing, but only if we have addir */
972 adfile_ok = check_adfile(ep->d_name, &st);
976 cnid = check_cnid(ep->d_name, did, &st, adfile_ok, adflags);
978 /* Now add this object to our rebuild dbd */
979 if (cnid && dbd_rebuild) {
980 static uint count = 0;
981 rqst.cnid = rply.cnid;
982 ret = dbd_rebuild_add(dbd_rebuild, &rqst, &rply);
983 dbif_txn_close(dbd_rebuild, ret);
984 if (rply.result != CNID_DBD_RES_OK) {
985 dbd_log( LOGDEBUG, "Fatal error adding CNID: %u for '%s/%s' to in-memory rebuild-db",
986 cnid, cwdbuf, ep->d_name);
987 longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
990 if (count == 10000) {
991 if (dbif_txn_checkpoint(dbd_rebuild, 0, 0, 0) < 0) {
992 dbd_log(LOGSTD, "Error checkpointing!");
1000 /* Check EA files */
1001 if (volinfo->v_vfs_ea == AFPVOL_EA_AD)
1002 check_eafiles(ep->d_name);
1004 /**************************************************************************
1006 **************************************************************************/
1007 if (S_ISDIR(st.st_mode) && (cnid || nocniddb)) { /* If we have no cnid for it we cant recur */
1008 strcat(cwdbuf, "/");
1009 strcat(cwdbuf, ep->d_name);
1010 dbd_log( LOGDEBUG, "Entering directory: %s", cwdbuf);
1011 if (-1 == (cwd = open(".", O_RDONLY))) {
1012 dbd_log( LOGSTD, "Cant open directory '%s': %s", cwdbuf, strerror(errno));
1015 if (0 != chdir(ep->d_name)) {
1016 dbd_log( LOGSTD, "Cant chdir to directory '%s': %s", cwdbuf, strerror(errno));
1021 ret = dbd_readdir(0, cnid);
1025 *(strrchr(cwdbuf, '/')) = 0;
1032 Use results of previous checks
1039 static int scanvol(struct volinfo *vi, dbd_flags_t flags)
1041 /* Dont scanvol on no-appledouble vols */
1042 if (vi->v_flags & AFPVOL_NOADOUBLE) {
1043 dbd_log( LOGSTD, "Volume without AppleDouble support: skipping volume scanning.");
1047 /* Make this stuff accessible from all funcs easily */
1051 /* Init a fake struct vol with just enough so we can call ea_open and friends */
1052 volume.v_adouble = AD_VERSION2;
1053 volume.v_vfs_ea = volinfo->v_vfs_ea;
1054 initvol_vfs(&volume);
1056 /* Run with umask 0 */
1059 /* Remove trailing slash from volume, chdir to vol */
1060 if (volinfo->v_path[strlen(volinfo->v_path) - 1] == '/')
1061 volinfo->v_path[strlen(volinfo->v_path) - 1] = 0;
1062 strcpy(cwdbuf, volinfo->v_path);
1063 chdir(volinfo->v_path);
1065 /* Start recursion */
1066 if (dbd_readdir(1, htonl(2)) < 0) /* 2 = volumeroot CNID */
1073 Remove all CNIDs from dbd that are not in dbd_rebuild
1075 static void delete_orphaned_cnids(DBD *dbd, DBD *dbd_rebuild, dbd_flags_t flags)
1077 int ret = 0, deleted = 0;
1078 cnid_t dbd_cnid = 0, rebuild_cnid = 0;
1079 struct cnid_dbd_rqst rqst;
1080 struct cnid_dbd_rply rply;
1082 /* jump over rootinfo key */
1083 if ( dbif_idwalk(dbd, &dbd_cnid, 0) != 1)
1085 if ( dbif_idwalk(dbd_rebuild, &rebuild_cnid, 0) != 1)
1088 /* Get first id from dbd_rebuild */
1089 if ((dbif_idwalk(dbd_rebuild, &rebuild_cnid, 0)) == -1)
1092 /* Start main loop through dbd: get CNID from dbd */
1093 while ((dbif_idwalk(dbd, &dbd_cnid, 0)) == 1) {
1094 /* Check if we got a termination signal */
1096 longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
1098 if (deleted > 1000) {
1100 if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0) {
1101 dbd_log(LOGSTD, "Error checkpointing!");
1106 /* This should be the normal case: CNID is in both dbs */
1107 if (dbd_cnid == rebuild_cnid) {
1108 /* Get next CNID from rebuild db */
1109 if ((ret = dbif_idwalk(dbd_rebuild, &rebuild_cnid, 0)) == -1) {
1112 } else if (ret == 0) {
1113 /* end of rebuild_cnid, delete all remaining CNIDs from dbd */
1114 while ((dbif_idwalk(dbd, &dbd_cnid, 0)) == 1) {
1115 dbd_log(LOGSTD, "Orphaned CNID in database: %u", dbd_cnid);
1116 if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
1117 rqst.cnid = htonl(dbd_cnid);
1118 ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
1119 dbif_txn_close(dbd, ret);
1122 /* Check if we got a termination signal */
1124 longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
1128 /* Normal case (ret=1): continue while loop */
1132 if (dbd_cnid < rebuild_cnid) {
1133 /* CNID is orphaned -> delete */
1134 dbd_log(LOGSTD, "Orphaned CNID in database: %u.", dbd_cnid);
1135 if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
1136 rqst.cnid = htonl(dbd_cnid);
1137 ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
1138 dbif_txn_close(dbd, ret);
1144 if (dbd_cnid > rebuild_cnid) {
1145 dbif_idwalk(dbd, NULL, 1); /* Close cursor */
1146 dbif_idwalk(dbd_rebuild, NULL, 1); /* Close cursor */
1147 dbif_txn_close(dbd, 2);
1148 dbif_txn_close(dbd_rebuild, 2);
1149 dbd_log(LOGSTD, "Ghost CNID: %u. This is fatal! Dumping rebuild db:\n", rebuild_cnid);
1150 dbif_dump(dbd_rebuild, 0);
1151 dbd_log(LOGSTD, "Send this dump and a `dbd -d ...` dump to the Netatalk Dev team!");
1157 dbif_idwalk(dbd, NULL, 1); /* Close cursor */
1158 dbif_idwalk(dbd_rebuild, NULL, 1); /* Close cursor */
1162 static const char *get_tmpdb_path(void)
1164 pid_t pid = getpid();
1165 static char path[MAXPATHLEN];
1166 snprintf(path, MAXPATHLEN, "/tmp/tmpdb-dbd.%u", pid);
1167 if (mkdir(path, 0755) != 0)
1173 Main func called from cmd_dbd.c
1175 int cmd_dbd_scanvol(DBD *dbd_ref, struct volinfo *volinfo, dbd_flags_t flags)
1178 struct db_param db_param = { 0 };
1179 const char *tmpdb_path = NULL;
1181 /* Set cachesize for in-memory rebuild db */
1182 db_param.cachesize = 64 * 1024; /* 64 MB */
1183 db_param.logfile_autoremove = 1;
1185 /* Make it accessible for all funcs */
1188 /* We only support unicode volumes ! */
1189 if ( volinfo->v_volcharset != CH_UTF8) {
1190 dbd_log( LOGSTD, "Not a Unicode volume: %s, %u != %u", volinfo->v_volcodepage, volinfo->v_volcharset, CH_UTF8);
1194 /* Get volume stamp */
1195 dbd_getstamp(dbd, &rqst, &rply);
1196 if (rply.result != CNID_DBD_RES_OK) {
1200 memcpy(stamp, rply.name, CNID_DEV_LEN);
1202 /* temporary rebuild db, used with -re rebuild to delete unused CNIDs, not used with -f */
1203 if (! nocniddb && (flags & DBD_FLAGS_EXCL) && !(flags & DBD_FLAGS_FORCE)) {
1204 tmpdb_path = get_tmpdb_path();
1205 if (NULL == (dbd_rebuild = dbif_init(tmpdb_path, "cnid2.db"))) {
1210 if (dbif_env_open(dbd_rebuild,
1212 DB_CREATE | DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_TXN) < 0) {
1213 dbd_log(LOGSTD, "error opening tmp database!");
1217 if (0 != (dbif_open(dbd_rebuild, NULL, 0))) {
1222 if (0 != (dbif_copy_rootinfokey(dbd, dbd_rebuild))) {
1228 if (setjmp(jmp) != 0) {
1229 ret = 0; /* Got signal, jump from dbd_readdir */
1234 if ( (scanvol(volinfo, flags)) != 0) {
1241 dbif_txn_close(dbd, 1);
1243 dbif_txn_close(dbd_rebuild, 1);
1244 if ((flags & DBD_FLAGS_EXCL) && !(flags & DBD_FLAGS_FORCE))
1245 /* We can only do this in exclusive mode, otherwise we might delete CNIDs added from
1246 other clients in between our pass 1 and 2 */
1247 delete_orphaned_cnids(dbd, dbd_rebuild, flags);
1251 dbd_log(LOGDEBUG, "Closing tmp db");
1252 dbif_close(dbd_rebuild);
1255 char cmd[8 + MAXPATHLEN];
1256 snprintf(cmd, 8 + MAXPATHLEN, "rm -f %s/*", tmpdb_path);
1257 dbd_log( LOGDEBUG, "Removing temp database '%s'", tmpdb_path);
1259 snprintf(cmd, 8 + MAXPATHLEN, "rmdir %s", tmpdb_path);