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>
37 #include <atalk/compat.h>
44 /* Some defines to ease code parsing */
45 #define ADDIR_OK (addir_ok == 0)
46 #define ADFILE_OK (adfile_ok == 0)
49 static struct volinfo *myvolinfo;
50 static char cwdbuf[MAXPATHLEN+1];
52 static DBD *dbd_rebuild;
53 static dbd_flags_t dbd_flags;
54 static char stamp[CNID_DEV_LEN];
55 static char *netatalk_dirs[] = {
60 static char *special_dirs[] = {
64 static struct cnid_dbd_rqst rqst;
65 static struct cnid_dbd_rply rply;
67 static struct vol volume; /* fake it for ea_open */
68 static char pname[MAXPATHLEN] = "../";
71 Taken form afpd/desktop.c
73 static char *utompath(char *upath)
75 static char mpath[ MAXPATHLEN + 2]; /* for convert_charset dest_len parameter +2 */
77 uint16_t flags = CONV_IGNORE | CONV_UNESCAPEHEX;
85 outlen = strlen(upath);
87 if ((myvolinfo->v_casefold & AFPVOL_UTOMUPPER))
88 flags |= CONV_TOUPPER;
89 else if ((myvolinfo->v_casefold & AFPVOL_UTOMLOWER))
90 flags |= CONV_TOLOWER;
92 if ((myvolinfo->v_flags & AFPVOL_EILSEQ)) {
93 flags |= CONV__EILSEQ;
96 /* convert charsets */
97 if ((size_t)-1 == ( outlen = convert_charset(myvolinfo->v_volcharset,
99 myvolinfo->v_maccharset,
100 u, outlen, mpath, MAXPATHLEN, &flags)) ) {
101 dbd_log( LOGSTD, "Conversion from %s to %s for %s failed.",
102 myvolinfo->v_volcodepage, myvolinfo->v_maccodepage, u);
110 Taken form afpd/desktop.c
112 static char *mtoupath(char *mpath)
114 static char upath[ MAXPATHLEN + 2]; /* for convert_charset dest_len parameter +2 */
123 if ( *mpath == '\0' ) {
127 /* set conversion flags */
128 if (!(myvolinfo->v_flags & AFPVOL_NOHEX))
129 flags |= CONV_ESCAPEHEX;
130 if (!(myvolinfo->v_flags & AFPVOL_USEDOTS))
131 flags |= CONV_ESCAPEDOTS;
133 if ((myvolinfo->v_casefold & AFPVOL_MTOUUPPER))
134 flags |= CONV_TOUPPER;
135 else if ((myvolinfo->v_casefold & AFPVOL_MTOULOWER))
136 flags |= CONV_TOLOWER;
138 if ((myvolinfo->v_flags & AFPVOL_EILSEQ)) {
139 flags |= CONV__EILSEQ;
148 if ((size_t)-1 == (outlen = convert_charset(CH_UTF8_MAC,
149 myvolinfo->v_volcharset,
150 myvolinfo->v_maccharset,
151 m, inplen, u, outlen, &flags)) ) {
152 dbd_log( LOGSTD, "conversion from UTF8-MAC to %s for %s failed.",
153 myvolinfo->v_volcodepage, mpath);
162 Check if "name" is pointing to
163 a) an object inside the current volume (return 0)
164 b) an object outside the current volume (return 1)
165 Then stats the pointed to object and if it is a dir ors ADFLAGS_DIR to *adflags
166 Return -1 on any serious error.
168 static int check_symlink(const char *name, int *adflags)
172 char pathbuf[MAXPATHLEN + 1];
176 if ((len = readlink(name, pathbuf, MAXPATHLEN)) == -1) {
177 dbd_log(LOGSTD, "Error reading link info for '%s/%s': %s",
178 cwdbuf, name, strerror(errno));
183 if ((stat(pathbuf, &st)) != 0) {
184 dbd_log(LOGSTD, "stat error '%s': %s", pathbuf, strerror(errno));
188 if ((cwd = open(".", O_RDONLY)) < 0) {
189 dbd_log(LOGSTD, "error opening cwd '%s': %s", cwdbuf, strerror(errno));
193 if (S_ISDIR(st.st_mode)) {
194 *adflags |= ADFLAGS_DIR;
196 /* get basename from path */
197 if ((sep = strrchr(pathbuf, '/')) == NULL)
198 /* just a file at the same level */
200 sep = 0; /* pathbuf now contains the directory*/
203 /* fchdir() to pathbuf so we can easily get its path with getcwd() */
204 if ((chdir(pathbuf)) != 0) {
205 dbd_log(LOGSTD, "Cant chdir to '%s': %s", pathbuf, strerror(errno));
209 if ((getcwd(pathbuf, MAXPATHLEN)) == NULL) {
210 dbd_log(LOGSTD, "Cant get symlink'ed dir '%s/%s': %s", cwdbuf, pathbuf, strerror(errno));
211 if ((fchdir(cwd)) != 0)
213 longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
217 if ((fchdir(cwd)) != 0)
219 longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
222 We now have the symlink target dir as absoulte path in pathbuf
223 and can compare it with the currents volume path
226 while (myvolinfo->v_path[i]) {
227 if ((pathbuf[i] == 0) || (myvolinfo->v_path[i] != pathbuf[i])) {
228 dbd_log( LOGDEBUG, "extra-share symlink '%s/%s', following", cwdbuf, name);
234 dbd_log( LOGDEBUG, "intra-share symlink '%s/%s', not following", cwdbuf, name);
240 Check for wrong encoding e.g. "." at the beginning is not CAP encoded (:2e) although volume is default !AFPVOL_USEDOTS.
241 We do it by roundtripiping from volcharset to UTF8-MAC and back and then compare the result.
243 static int check_name_encoding(char *uname)
247 roundtripped = mtoupath(utompath(uname));
249 dbd_log( LOGSTD, "Error checking encoding for '%s/%s'", cwdbuf, uname);
253 if ( STRCMP(uname, !=, roundtripped)) {
254 dbd_log( LOGSTD, "Bad encoding for '%s/%s'", cwdbuf, uname);
262 Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop"
263 Returns pointer to name or NULL.
265 static const char *check_netatalk_dirs(const char *name)
269 for (c=0; netatalk_dirs[c]; c++) {
270 if ((strcmp(name, netatalk_dirs[c])) == 0)
271 return netatalk_dirs[c];
277 Check for special names
278 Returns pointer to name or NULL.
280 static const char *check_special_dirs(const char *name)
284 for (c=0; special_dirs[c]; c++) {
285 if ((strcmp(name, special_dirs[c])) == 0)
286 return special_dirs[c];
292 Check for .AppleDouble file, create if missing
294 static int check_adfile(const char *fname, const struct stat *st)
297 int adflags = ADFLAGS_HF;
301 if (dbd_flags & DBD_FLAGS_CLEANUP)
304 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(&ad, fname, adflags, O_CREAT | O_RDWR, 0666)) != 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(&ad, fname, adflags, O_RDONLY) != 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(&ad, ".", ADFLAGS_HF | ADFLAGS_DIR, O_CREAT | O_RDWR, 0777) != 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(&ad, name, adflags, O_RDWR) != 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(&ad, name, adflags, O_RDWR) != 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(&ad, name, adflags, O_RDWR) != 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 /* Check again for .AppleDouble folder, check_adfile also checks/creates it */
866 if ((addir_ok = check_addir(volroot)) != 0)
867 if ( ! (dbd_flags & DBD_FLAGS_SCAN))
868 /* Fatal on rebuild run, continue if only scanning ! */
871 /* Check AppleDouble files in AppleDouble folder, but only if it exists or could be created */
873 if ((read_addir()) != 0)
874 if ( ! (dbd_flags & DBD_FLAGS_SCAN))
875 /* Fatal on rebuild run, continue if only scanning ! */
878 if ((dp = opendir (".")) == NULL) {
879 dbd_log(LOGSTD, "Couldn't open the directory: %s",strerror(errno));
883 while ((ep = readdir (dp))) {
884 /* Check if we got a termination signal */
886 longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
888 /* Check if its "." or ".." */
889 if (DIR_DOT_OR_DOTDOT(ep->d_name))
892 /* Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop" */
893 if ((name = check_netatalk_dirs(ep->d_name)) != NULL) {
895 dbd_log(LOGSTD, "Nested %s in %s", name, cwdbuf);
899 /* Check for special folders in volume root e.g. ".zfs" */
901 if ((name = check_special_dirs(ep->d_name)) != NULL) {
902 dbd_log(LOGSTD, "Ignoring special dir \"%s\"", name);
907 /* Skip .AppleDouble dir in this loop */
908 if (STRCMP(ep->d_name, == , ADv2_DIRNAME))
911 if ((ret = lstat(ep->d_name, &st)) < 0) {
912 dbd_log( LOGSTD, "Lost file while reading dir '%s/%s', probably removed: %s",
913 cwdbuf, ep->d_name, strerror(errno));
917 switch (st.st_mode & S_IFMT) {
922 adflags = ADFLAGS_DIR;
925 dbd_log(LOGDEBUG, "Ignoring symlink %s/%s", cwdbuf, ep->d_name);
927 ret = check_symlink(ep->d_name, &adflags);
931 dbd_log(LOGSTD, "Error checking symlink %s/%s", cwdbuf, ep->d_name);
935 dbd_log(LOGSTD, "Bad filetype: %s/%s", cwdbuf, ep->d_name);
936 if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
937 if ((unlink(ep->d_name)) != 0) {
938 dbd_log(LOGSTD, "Error removing: %s/%s: %s", cwdbuf, ep->d_name, strerror(errno));
944 /**************************************************************************
946 **************************************************************************/
947 static unsigned long long statcount = 0;
954 if ((statcount % 10000) == 0) {
955 if (dbd_flags & DBD_FLAGS_STATS)
956 dbd_log(LOGSTD, "Scanned: %10llu, time: %10llu s",
957 statcount, (unsigned long long)(time(NULL) - t));
960 /**************************************************************************
962 **************************************************************************/
965 if ( -1 == (encoding_ok = check_name_encoding(ep->d_name)) ) {
966 /* If its a file: skipp all other tests now ! */
967 /* For dirs we could try to get a CNID for it and recurse, but currently I prefer not to */
971 /* Check for appledouble file, create if missing, but only if we have addir */
974 adfile_ok = check_adfile(ep->d_name, &st);
978 cnid = check_cnid(ep->d_name, did, &st, adfile_ok, adflags);
980 /* Now add this object to our rebuild dbd */
981 if (cnid && dbd_rebuild) {
982 static uint count = 0;
983 rqst.cnid = rply.cnid;
984 ret = dbd_rebuild_add(dbd_rebuild, &rqst, &rply);
985 if (dbif_txn_close(dbd_rebuild, ret) != 0)
987 if (rply.result != CNID_DBD_RES_OK) {
988 dbd_log( LOGSTD, "Fatal error adding CNID: %u for '%s/%s' to in-memory rebuild-db",
989 cnid, cwdbuf, ep->d_name);
993 if (count == 10000) {
994 if (dbif_txn_checkpoint(dbd_rebuild, 0, 0, 0) < 0) {
995 dbd_log(LOGSTD, "Error checkpointing!");
1003 /* Check EA files */
1004 if (myvolinfo->v_vfs_ea == AFPVOL_EA_AD)
1005 check_eafiles(ep->d_name);
1007 /**************************************************************************
1009 **************************************************************************/
1010 if (S_ISDIR(st.st_mode) && (cnid || nocniddb)) { /* If we have no cnid for it we cant recur */
1011 strcat(cwdbuf, "/");
1012 strcat(cwdbuf, ep->d_name);
1013 dbd_log( LOGDEBUG, "Entering directory: %s", cwdbuf);
1014 if (-1 == (cwd = open(".", O_RDONLY))) {
1015 dbd_log( LOGSTD, "Cant open directory '%s': %s", cwdbuf, strerror(errno));
1018 if (0 != chdir(ep->d_name)) {
1019 dbd_log( LOGSTD, "Cant chdir to directory '%s': %s", cwdbuf, strerror(errno));
1024 ret = dbd_readdir(0, cnid);
1028 *(strrchr(cwdbuf, '/')) = 0;
1035 Use results of previous checks
1042 static int scanvol(struct volinfo *vi, dbd_flags_t flags)
1044 /* Dont scanvol on no-appledouble vols */
1045 if (vi->v_flags & AFPVOL_NOADOUBLE) {
1046 dbd_log( LOGSTD, "Volume without AppleDouble support: skipping volume scanning.");
1050 /* Make this stuff accessible from all funcs easily */
1054 /* Init a fake struct vol with just enough so we can call ea_open and friends */
1055 volume.v_adouble = AD_VERSION2;
1056 volume.v_vfs_ea = myvolinfo->v_vfs_ea;
1057 initvol_vfs(&volume);
1059 /* Run with umask 0 */
1062 /* Remove trailing slash from volume, chdir to vol */
1063 if (myvolinfo->v_path[strlen(myvolinfo->v_path) - 1] == '/')
1064 myvolinfo->v_path[strlen(myvolinfo->v_path) - 1] = 0;
1065 strcpy(cwdbuf, myvolinfo->v_path);
1066 chdir(myvolinfo->v_path);
1068 /* Start recursion */
1069 if (dbd_readdir(1, htonl(2)) < 0) /* 2 = volumeroot CNID */
1076 Remove all CNIDs from dbd that are not in dbd_rebuild
1078 static void delete_orphaned_cnids(DBD *dbd, DBD *dbd_rebuild, dbd_flags_t flags)
1080 int ret = 0, deleted = 0;
1081 cnid_t dbd_cnid = 0, rebuild_cnid = 0;
1082 struct cnid_dbd_rqst rqst;
1083 struct cnid_dbd_rply rply;
1085 /* jump over rootinfo key */
1086 if ( dbif_idwalk(dbd, &dbd_cnid, 0) != 1)
1088 if ( dbif_idwalk(dbd_rebuild, &rebuild_cnid, 0) != 1)
1091 /* Get first id from dbd_rebuild */
1092 if ((dbif_idwalk(dbd_rebuild, &rebuild_cnid, 0)) == -1)
1095 /* Start main loop through dbd: get CNID from dbd */
1096 while ((dbif_idwalk(dbd, &dbd_cnid, 0)) == 1) {
1097 /* Check if we got a termination signal */
1099 longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
1101 if (deleted > 1000) {
1103 if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0) {
1104 dbd_log(LOGSTD, "Error checkpointing!");
1109 /* This should be the normal case: CNID is in both dbs */
1110 if (dbd_cnid == rebuild_cnid) {
1111 /* Get next CNID from rebuild db */
1112 if ((ret = dbif_idwalk(dbd_rebuild, &rebuild_cnid, 0)) == -1) {
1115 } else if (ret == 0) {
1116 /* end of rebuild_cnid, delete all remaining CNIDs from dbd */
1117 while ((dbif_idwalk(dbd, &dbd_cnid, 0)) == 1) {
1118 dbd_log(LOGSTD, "Orphaned CNID in database: %u", dbd_cnid);
1119 if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
1120 rqst.cnid = htonl(dbd_cnid);
1121 if ((ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID)) == -1) {
1122 dbd_log(LOGSTD, "Error deleting CNID %u", dbd_cnid);
1123 (void)dbif_txn_abort(dbd);
1127 if (dbif_txn_close(dbd, ret) != 0)
1131 /* Check if we got a termination signal */
1133 longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
1137 /* Normal case (ret=1): continue while loop */
1141 if (dbd_cnid < rebuild_cnid) {
1142 /* CNID is orphaned -> delete */
1143 dbd_log(LOGSTD, "One orphaned CNID in database: %u.", dbd_cnid);
1144 if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
1145 rqst.cnid = htonl(dbd_cnid);
1146 if ((ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID)) == -1) {
1147 dbd_log(LOGSTD, "Error deleting CNID %u", dbd_cnid);
1148 (void)dbif_txn_abort(dbd);
1151 if (dbif_txn_close(dbd, ret) != 0)
1158 if (dbd_cnid > rebuild_cnid) {
1159 dbif_idwalk(dbd, NULL, 1); /* Close cursor */
1160 dbif_idwalk(dbd_rebuild, NULL, 1); /* Close cursor */
1161 (void)dbif_txn_close(dbd, 2);
1162 (void)dbif_txn_close(dbd_rebuild, 2);
1163 dbd_log(LOGSTD, "Ghost CNID: %u. This is fatal! Dumping rebuild db:\n", rebuild_cnid);
1164 dbif_dump(dbd_rebuild, 0);
1165 dbd_log(LOGSTD, "Send this dump and a `dbd -d ...` dump to the Netatalk Dev team!");
1168 } /* while ((dbif_idwalk(dbd, &dbd_cnid, 0)) == 1) */
1171 dbif_idwalk(dbd, NULL, 1); /* Close cursor */
1172 dbif_idwalk(dbd_rebuild, NULL, 1); /* Close cursor */
1176 static const char *get_tmpdb_path(void)
1178 pid_t pid = getpid();
1179 static char path[MAXPATHLEN];
1180 snprintf(path, MAXPATHLEN, "/tmp/tmpdb-dbd.%u", pid);
1181 if (mkdir(path, 0755) != 0)
1187 Main func called from cmd_dbd.c
1189 int cmd_dbd_scanvol(DBD *dbd_ref, struct volinfo *vi, dbd_flags_t flags)
1192 struct db_param db_param = { 0 };
1193 const char *tmpdb_path = NULL;
1195 /* Set cachesize for in-memory rebuild db */
1196 db_param.cachesize = 64 * 1024; /* 64 MB */
1197 db_param.maxlocks = DEFAULT_MAXLOCKS;
1198 db_param.maxlockobjs = DEFAULT_MAXLOCKOBJS;
1199 db_param.logfile_autoremove = 1;
1201 /* Make it accessible for all funcs */
1204 /* We only support unicode volumes ! */
1205 if ( vi->v_volcharset != CH_UTF8) {
1206 dbd_log( LOGSTD, "Not a Unicode volume: %s, %u != %u", vi->v_volcodepage, vi->v_volcharset, CH_UTF8);
1210 /* Get volume stamp */
1211 dbd_getstamp(dbd, &rqst, &rply);
1212 if (rply.result != CNID_DBD_RES_OK) {
1216 memcpy(stamp, rply.name, CNID_DEV_LEN);
1218 /* temporary rebuild db, used with -re rebuild to delete unused CNIDs, not used with -f */
1219 if (! nocniddb && (flags & DBD_FLAGS_EXCL) && !(flags & DBD_FLAGS_FORCE)) {
1220 tmpdb_path = get_tmpdb_path();
1221 if (NULL == (dbd_rebuild = dbif_init(tmpdb_path, "cnid2.db"))) {
1226 if (dbif_env_open(dbd_rebuild,
1228 DB_CREATE | DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_TXN) < 0) {
1229 dbd_log(LOGSTD, "error opening tmp database!");
1233 if (0 != (dbif_open(dbd_rebuild, NULL, 0))) {
1238 if (0 != (dbif_copy_rootinfokey(dbd, dbd_rebuild))) {
1244 if (setjmp(jmp) != 0) {
1245 ret = 0; /* Got signal, jump from dbd_readdir */
1250 if ( (scanvol(vi, flags)) != 0) {
1257 if (dbif_txn_close(dbd, ret == 0 ? 1 : 0) != 0)
1260 if (dbif_txn_close(dbd_rebuild, ret == 0 ? 1 : 0) != 0)
1262 if ((ret == 0) && dbd_rebuild && (flags & DBD_FLAGS_EXCL) && !(flags & DBD_FLAGS_FORCE))
1263 /* We can only do this in exclusive mode, otherwise we might delete CNIDs added from
1264 other clients in between our pass 1 and 2 */
1265 delete_orphaned_cnids(dbd, dbd_rebuild, flags);
1269 dbd_log(LOGDEBUG, "Closing tmp db");
1270 dbif_close(dbd_rebuild);
1273 char cmd[8 + MAXPATHLEN];
1274 snprintf(cmd, 8 + MAXPATHLEN, "rm -f %s/*", tmpdb_path);
1275 dbd_log( LOGDEBUG, "Removing temp database '%s'", tmpdb_path);
1277 snprintf(cmd, 8 + MAXPATHLEN, "rmdir %s", tmpdb_path);