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/netatalk_conf.h>
32 #include <atalk/volume.h>
34 #include <atalk/util.h>
35 #include <atalk/acl.h>
36 #include <atalk/compat.h>
37 #include <atalk/cnid.h>
38 #include <atalk/errchk.h>
45 /* Some defines to ease code parsing */
46 #define ADDIR_OK (addir_ok == 0)
47 #define ADFILE_OK (adfile_ok == 0)
50 static char cwdbuf[MAXPATHLEN+1];
51 static struct vol *vol;
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 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 ((vol->v_casefold & AFPVOL_UTOMUPPER))
87 flags |= CONV_TOUPPER;
88 else if ((vol->v_casefold & AFPVOL_UTOMLOWER))
89 flags |= CONV_TOLOWER;
91 if ((vol->v_flags & AFPVOL_EILSEQ)) {
92 flags |= CONV__EILSEQ;
95 /* convert charsets */
96 if ((size_t)-1 == ( outlen = convert_charset(vol->v_volcharset,
99 u, outlen, mpath, MAXPATHLEN, &flags)) ) {
100 dbd_log( LOGSTD, "Conversion from %s to %s for %s failed.",
101 vol->v_volcodepage, vol->v_maccodepage, u);
109 Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop"
110 Returns pointer to name or NULL.
112 static const char *check_netatalk_dirs(const char *name)
116 for (c=0; netatalk_dirs[c]; c++) {
117 if ((strcmp(name, netatalk_dirs[c])) == 0)
118 return netatalk_dirs[c];
124 Check for special names
125 Returns pointer to name or NULL.
127 static const char *check_special_dirs(const char *name)
131 for (c=0; special_dirs[c]; c++) {
132 if ((strcmp(name, special_dirs[c])) == 0)
133 return special_dirs[c];
139 * We unCAPed a name, update CNID db
141 static int update_cnid(cnid_t did, const struct stat *sp, const char *oldname, const char *newname)
145 /* Query the database */
146 if ((id = cnid_lookup(vol->v_cdb, sp, did, (char *)oldname, strlen(oldname))) == CNID_INVALID)
147 /* not in db, no need to update */
150 /* Update the database */
151 if (cnid_update(vol->v_cdb, id, sp, did, (char *)newname, strlen(newname)) < 0)
158 Check for .AppleDouble file, create if missing
160 static int check_adfile(const char *fname, const struct stat *st, const char **newname)
163 int adflags = ADFLAGS_HF;
169 if (vol->v_adouble == AD_VERSION_EA) {
170 if (!(dbd_flags & DBD_FLAGS_V2TOEA))
172 if (ad_convert(fname, st, vol, newname) != 0) {
177 dbd_log(LOGSTD, "Conversion error for \"%s/%s\": %s", cwdbuf, fname, strerror(errno));
184 if (S_ISDIR(st->st_mode))
185 adflags |= ADFLAGS_DIR;
187 adname = vol->ad_path(fname, adflags);
189 if ((ret = access( adname, F_OK)) != 0) {
190 if (errno != ENOENT) {
191 dbd_log(LOGSTD, "Access error for ad-file '%s/%s': %s",
192 cwdbuf, adname, strerror(errno));
195 /* Missing. Log and create it */
196 dbd_log(LOGSTD, "Missing AppleDouble file '%s/%s'", cwdbuf, adname);
198 if (dbd_flags & DBD_FLAGS_SCAN)
199 /* Scan only requested, dont change anything */
205 if ((ret = ad_open(&ad, fname, adflags | ADFLAGS_CREATE | ADFLAGS_RDWR, 0666)) != 0) {
206 dbd_log( LOGSTD, "Error creating AppleDouble file '%s/%s': %s",
207 cwdbuf, adname, strerror(errno));
212 /* Set name in ad-file */
213 ad_setname(&ad, utompath((char *)fname));
215 ad_close(&ad, ADFLAGS_HF);
217 chown(adname, st->st_uid, st->st_gid);
218 /* FIXME: should we inherit mode too here ? */
220 chmod(adname, st->st_mode);
224 if (ad_open(&ad, fname, adflags | ADFLAGS_RDONLY) != 0) {
225 dbd_log( LOGSTD, "Error opening AppleDouble file for '%s/%s'", cwdbuf, fname);
228 ad_close(&ad, ADFLAGS_HF);
234 Remove all files with file::EA* from adouble dir
236 static void remove_eafiles(const char *name, struct ea *ea)
240 char eaname[MAXPATHLEN];
242 strlcpy(eaname, name, sizeof(eaname));
243 if (strlcat(eaname, "::EA", sizeof(eaname)) >= sizeof(eaname)) {
244 dbd_log(LOGSTD, "name too long: '%s/%s/%s'", cwdbuf, ADv2_DIRNAME, name);
248 if ((chdir(ADv2_DIRNAME)) != 0) {
249 dbd_log(LOGSTD, "Couldn't chdir to '%s/%s': %s",
250 cwdbuf, ADv2_DIRNAME, strerror(errno));
254 if ((dp = opendir(".")) == NULL) {
255 dbd_log(LOGSTD, "Couldn't open the directory '%s/%s': %s",
256 cwdbuf, ADv2_DIRNAME, strerror(errno));
260 while ((ep = readdir(dp))) {
261 if (strstr(ep->d_name, eaname) != NULL) {
262 dbd_log(LOGSTD, "Removing EA file: '%s/%s/%s'",
263 cwdbuf, ADv2_DIRNAME, ep->d_name);
264 if ((unlink(ep->d_name)) != 0) {
265 dbd_log(LOGSTD, "Error unlinking EA file '%s/%s/%s': %s",
266 cwdbuf, ADv2_DIRNAME, ep->d_name, strerror(errno));
274 if ((chdir("..")) != 0) {
275 dbd_log(LOGSTD, "Couldn't chdir to '%s': %s", cwdbuf, strerror(errno));
276 /* we can't proceed */
277 longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
282 Check Extended Attributes files
284 static int check_eafiles(const char *fname)
286 unsigned int count = 0;
292 if ((ret = ea_open(vol, fname, EA_RDWR, &ea)) != 0) {
295 dbd_log(LOGSTD, "Error calling ea_open for file: %s/%s, removing EA files", cwdbuf, fname);
296 if ( ! (dbd_flags & DBD_FLAGS_SCAN))
297 remove_eafiles(fname, &ea);
302 while (count < ea.ea_count) {
303 dbd_log(LOGDEBUG, "EA: %s", (*ea.ea_entries)[count].ea_name);
306 eaname = ea_path(&ea, (*ea.ea_entries)[count].ea_name, 0);
308 if (lstat(eaname, &st) != 0) {
310 dbd_log(LOGSTD, "Missing EA: %s/%s", cwdbuf, eaname);
312 dbd_log(LOGSTD, "Bogus EA: %s/%s", cwdbuf, eaname);
314 } else if (st.st_size != (*ea.ea_entries)[count].ea_size) {
315 dbd_log(LOGSTD, "Bogus EA: %s/%s, removing it...", cwdbuf, eaname);
317 if ((unlink(eaname)) != 0)
318 dbd_log(LOGSTD, "Error removing EA file '%s/%s': %s",
319 cwdbuf, eaname, strerror(errno));
323 /* Be CAREFUL here! This should do what ea_delentry does. ea_close relies on it !*/
324 free((*ea.ea_entries)[count].ea_name);
325 (*ea.ea_entries)[count].ea_name = NULL;
336 Check for .AppleDouble folder and .Parent, create if missing
338 static int check_addir(int volroot)
340 int addir_ok, adpar_ok;
345 if (vol->v_adouble == AD_VERSION_EA)
348 /* Check for ad-dir */
349 if ( (addir_ok = access(ADv2_DIRNAME, F_OK)) != 0) {
350 if (errno != ENOENT) {
351 dbd_log(LOGSTD, "Access error in directory %s: %s", cwdbuf, strerror(errno));
354 dbd_log(LOGSTD, "Missing %s for '%s'", ADv2_DIRNAME, cwdbuf);
357 /* Check for ".Parent" */
358 if ( (adpar_ok = access(vol->ad_path(".", ADFLAGS_DIR), F_OK)) != 0) {
359 if (errno != ENOENT) {
360 dbd_log(LOGSTD, "Access error on '%s/%s': %s",
361 cwdbuf, vol->ad_path(".", ADFLAGS_DIR), strerror(errno));
364 dbd_log(LOGSTD, "Missing .AppleDouble/.Parent for '%s'", cwdbuf);
367 /* Is one missing ? */
368 if ((addir_ok != 0) || (adpar_ok != 0)) {
369 /* Yes, but are we only scanning ? */
370 if (dbd_flags & DBD_FLAGS_SCAN) {
371 /* Yes: missing .Parent is not a problem, but missing ad-dir
372 causes later checking of ad-files to fail. So we have to return appropiately */
375 else /* (adpar_ok != 0) */
379 /* Create ad dir and set name */
382 if (ad_open(&ad, ".", ADFLAGS_HF | ADFLAGS_DIR | ADFLAGS_CREATE | ADFLAGS_RDWR, 0777) != 0) {
383 dbd_log( LOGSTD, "Error creating AppleDouble dir in %s: %s", cwdbuf, strerror(errno));
387 /* Get basename of cwd from cwdbuf */
388 mname = utompath(strrchr(cwdbuf, '/') + 1);
390 /* Update name in ad file */
391 ad_setname(&ad, mname);
393 ad_close(&ad, ADFLAGS_HF);
395 /* Inherit owner/group from "." to ".AppleDouble" and ".Parent" */
396 if ((lstat(".", &st)) != 0) {
397 dbd_log( LOGSTD, "Couldnt stat %s: %s", cwdbuf, strerror(errno));
400 chown(ADv2_DIRNAME, st.st_uid, st.st_gid);
401 chown(vol->ad_path(".", ADFLAGS_DIR), st.st_uid, st.st_gid);
408 Check if file cotains "::EA" and if it does check if its correspondig data fork exists.
410 0 = name is not an EA file
411 1 = name is an EA file and no problem was found
412 -1 = name is an EA file and data fork is gone
414 static int check_eafile_in_adouble(const char *name)
417 char *namep, *namedup = NULL;
419 /* Check if this is an AFPVOL_EA_AD vol */
420 if (vol->v_vfs_ea == AFPVOL_EA_AD) {
421 /* Does the filename contain "::EA" ? */
422 namedup = strdup(name);
423 if ((namep = strstr(namedup, "::EA")) == NULL) {
427 /* File contains "::EA" so it's an EA file. Check for data file */
429 /* Get string before "::EA" from EA filename */
431 strlcpy(pname + 3, namedup, sizeof(pname)); /* Prepends "../" */
433 if ((access( pname, F_OK)) == 0) {
438 if (errno != ENOENT) {
439 dbd_log(LOGSTD, "Access error for file '%s/%s': %s",
440 cwdbuf, name, strerror(errno));
444 /* Orphaned EA file*/
445 dbd_log(LOGSTD, "Orphaned Extended Attribute file '%s/%s/%s'",
446 cwdbuf, ADv2_DIRNAME, name);
448 if (dbd_flags & DBD_FLAGS_SCAN)
449 /* Scan only requested, dont change anything */
452 if ((unlink(name)) != 0) {
453 dbd_log(LOGSTD, "Error unlinking orphaned Extended Attribute file '%s/%s/%s'",
454 cwdbuf, ADv2_DIRNAME, name);
458 } /* if AFPVOL_EA_AD */
468 Check files and dirs inside .AppleDouble folder:
469 - remove orphaned files
472 static int read_addir(void)
478 if ((chdir(ADv2_DIRNAME)) != 0) {
479 if (vol->v_adouble == AD_VERSION_EA) {
482 dbd_log(LOGSTD, "Couldn't chdir to '%s/%s': %s",
483 cwdbuf, ADv2_DIRNAME, strerror(errno));
487 if ((dp = opendir(".")) == NULL) {
488 dbd_log(LOGSTD, "Couldn't open the directory '%s/%s': %s",
489 cwdbuf, ADv2_DIRNAME, strerror(errno));
493 while ((ep = readdir(dp))) {
494 /* Check if its "." or ".." */
495 if (DIR_DOT_OR_DOTDOT(ep->d_name))
499 if (STRCMP(ep->d_name, ==, ".Parent"))
502 if ((lstat(ep->d_name, &st)) < 0) {
503 dbd_log( LOGSTD, "Lost file or dir while enumeratin dir '%s/%s/%s', probably removed: %s",
504 cwdbuf, ADv2_DIRNAME, ep->d_name, strerror(errno));
509 if (S_ISDIR(st.st_mode)) {
510 dbd_log( LOGSTD, "Unexpected directory '%s' in AppleDouble dir '%s/%s'",
511 ep->d_name, cwdbuf, ADv2_DIRNAME);
515 /* Check if for orphaned and corrupt Extended Attributes file */
516 if (check_eafile_in_adouble(ep->d_name) != 0)
519 /* Check for data file */
520 strcpy(pname + 3, ep->d_name);
521 if ((access( pname, F_OK)) != 0) {
522 if (errno != ENOENT) {
523 dbd_log(LOGSTD, "Access error for file '%s/%s': %s",
524 cwdbuf, pname, strerror(errno));
527 /* Orphaned ad-file*/
528 dbd_log(LOGSTD, "Orphaned AppleDoube file '%s/%s/%s'",
529 cwdbuf, ADv2_DIRNAME, ep->d_name);
531 if (dbd_flags & DBD_FLAGS_SCAN)
532 /* Scan only requested, dont change anything */
535 if ((unlink(ep->d_name)) != 0) {
536 dbd_log(LOGSTD, "Error unlinking orphaned AppleDoube file '%s/%s/%s'",
537 cwdbuf, ADv2_DIRNAME, ep->d_name);
543 if ((chdir("..")) != 0) {
544 dbd_log(LOGSTD, "Couldn't chdir back to '%s' from AppleDouble dir: %s",
545 cwdbuf, strerror(errno));
546 /* This really is EOT! */
547 longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
556 Check CNID for a file/dir, both from db and from ad-file.
557 For detailed specs see intro.
559 @return Correct CNID of object or CNID_INVALID (ie 0) on error
561 static cnid_t check_cnid(const char *name, cnid_t did, struct stat *st, int adfile_ok)
563 int adflags = ADFLAGS_HF;
564 cnid_t db_cnid, ad_cnid;
567 adflags = ADFLAGS_HF | (S_ISDIR(st->st_mode) ? ADFLAGS_DIR : 0);
569 /* Get CNID from ad-file */
570 ad_cnid = CNID_INVALID;
573 if (ad_open(&ad, name, adflags | ADFLAGS_RDWR) != 0) {
575 if (vol->v_adouble != AD_VERSION_EA) {
576 dbd_log( LOGSTD, "Error opening AppleDouble file for '%s/%s': %s", cwdbuf, name, strerror(errno));
579 dbd_log( LOGDEBUG, "File without meta EA: \"%s/%s\"", cwdbuf, name);
582 ad_cnid = ad_getid(&ad, st->st_dev, st->st_ino, 0, stamp);
583 if (ad_cnid == CNID_INVALID)
584 dbd_log( LOGSTD, "Bad CNID in adouble file of '%s/%s'", cwdbuf, name);
586 dbd_log( LOGDEBUG, "CNID from .AppleDouble file for '%s/%s': %u", cwdbuf, name, ntohl(ad_cnid));
587 ad_close(&ad, ADFLAGS_HF);
591 /* Get CNID from database */
592 if ((db_cnid = cnid_add(vol->v_cdb, st, did, (char *)name, strlen(name), ad_cnid)) == CNID_INVALID)
595 /* Compare CNID from db and adouble file */
596 if (ad_cnid != db_cnid && adfile_ok == 0) {
597 /* Mismatch, overwrite ad file with value from db */
598 dbd_log(LOGSTD, "CNID mismatch for '%s/%s', CNID db: %u, ad-file: %u",
599 cwdbuf, name, ntohl(db_cnid), ntohl(ad_cnid));
601 if (ad_open(&ad, name, adflags | ADFLAGS_HF | ADFLAGS_RDWR) != 0) {
602 dbd_log(LOGSTD, "Error opening AppleDouble file for '%s/%s': %s",
603 cwdbuf, name, strerror(errno));
606 ad_setid( &ad, st->st_dev, st->st_ino, db_cnid, did, stamp);
608 ad_close(&ad, ADFLAGS_HF);
615 This is called recursively for all dirs.
616 volroot=1 means we're in the volume root dir, 0 means we aren't.
617 We use this when checking for netatalk private folders like .AppleDB.
618 did is our parents CNID.
620 static int dbd_readdir(int volroot, cnid_t did)
622 int cwd, ret = 0, adfile_ok, addir_ok;
627 static struct stat st; /* Save some stack space */
629 /* Check again for .AppleDouble folder, check_adfile also checks/creates it */
630 if ((addir_ok = check_addir(volroot)) != 0)
631 if ( ! (dbd_flags & DBD_FLAGS_SCAN))
632 /* Fatal on rebuild run, continue if only scanning ! */
635 /* Check AppleDouble files in AppleDouble folder, but only if it exists or could be created */
637 if ((read_addir()) != 0)
638 if ( ! (dbd_flags & DBD_FLAGS_SCAN))
639 /* Fatal on rebuild run, continue if only scanning ! */
642 if ((dp = opendir (".")) == NULL) {
643 dbd_log(LOGSTD, "Couldn't open the directory: %s",strerror(errno));
647 while ((ep = readdir (dp))) {
648 /* Check if we got a termination signal */
650 longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
652 /* Check if its "." or ".." */
653 if (DIR_DOT_OR_DOTDOT(ep->d_name))
656 /* Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop" */
657 if ((name = check_netatalk_dirs(ep->d_name)) != NULL) {
659 dbd_log(LOGSTD, "Nested %s in %s", name, cwdbuf);
663 /* Check for special folders in volume root e.g. ".zfs" */
665 if ((name = check_special_dirs(ep->d_name)) != NULL) {
666 dbd_log(LOGSTD, "Ignoring special dir \"%s\"", name);
671 /* Skip .AppleDouble dir in this loop */
672 if (STRCMP(ep->d_name, == , ADv2_DIRNAME))
675 if (!vol->vfs->vfs_validupath(vol, ep->d_name)) {
676 dbd_log(LOGDEBUG, "Ignoring \"%s\"", ep->d_name);
680 if ((ret = lstat(ep->d_name, &st)) < 0) {
681 dbd_log( LOGSTD, "Lost file while reading dir '%s/%s', probably removed: %s",
682 cwdbuf, ep->d_name, strerror(errno));
686 switch (st.st_mode & S_IFMT) {
691 dbd_log(LOGDEBUG, "Ignoring symlink %s/%s", cwdbuf, ep->d_name);
694 dbd_log(LOGSTD, "Bad filetype: %s/%s", cwdbuf, ep->d_name);
695 if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
696 if ((unlink(ep->d_name)) != 0) {
697 dbd_log(LOGSTD, "Error removing: %s/%s: %s", cwdbuf, ep->d_name, strerror(errno));
703 /**************************************************************************
705 **************************************************************************/
706 static unsigned long long statcount = 0;
713 if ((statcount % 10000) == 0) {
714 if (dbd_flags & DBD_FLAGS_STATS)
715 dbd_log(LOGSTD, "Scanned: %10llu, time: %10llu s",
716 statcount, (unsigned long long)(time(NULL) - t));
719 /**************************************************************************
721 **************************************************************************/
723 /* Check for appledouble file, create if missing, but only if we have addir */
724 const char *name = NULL;
727 adfile_ok = check_adfile(ep->d_name, &st, &name);
732 update_cnid(did, &st, ep->d_name, name);
736 cnid = check_cnid(name, did, &st, adfile_ok);
738 /* Now add this object to our rebuild dbd */
739 if (cnid && dbd_rebuild) {
740 static uint count = 0;
741 rqst.cnid = rply.cnid;
742 ret = dbd_rebuild_add(dbd_rebuild, &rqst, &rply);
743 if (dbif_txn_close(dbd_rebuild, ret) != 0)
745 if (rply.result != CNID_DBD_RES_OK) {
746 dbd_log( LOGSTD, "Fatal error adding CNID: %u for '%s/%s' to in-memory rebuild-db",
751 if (count == 10000) {
752 if (dbif_txn_checkpoint(dbd_rebuild, 0, 0, 0) < 0) {
753 dbd_log(LOGSTD, "Error checkpointing!");
761 if (vol->v_vfs_ea == AFPVOL_EA_AD)
764 /**************************************************************************
766 **************************************************************************/
767 if (S_ISDIR(st.st_mode) && cnid) { /* If we have no cnid for it we cant enter recursion */
769 strcat(cwdbuf, name);
770 dbd_log( LOGDEBUG, "Entering directory: %s", cwdbuf);
771 if (-1 == (cwd = open(".", O_RDONLY))) {
772 dbd_log( LOGSTD, "Cant open directory '%s': %s", cwdbuf, strerror(errno));
775 if (0 != chdir(name)) {
776 dbd_log( LOGSTD, "Cant chdir to directory '%s': %s", cwdbuf, strerror(errno));
781 ret = dbd_readdir(0, cnid);
785 *(strrchr(cwdbuf, '/')) = 0;
792 Use results of previous checks
794 if ((vol->v_adouble == AD_VERSION_EA) && (dbd_flags & DBD_FLAGS_V2TOEA)) {
795 if (rmdir(ADv2_DIRNAME) != 0) {
800 dbd_log(LOGSTD, "Error removing adouble dir \"%s/%s\": %s", cwdbuf, ADv2_DIRNAME, strerror(errno));
810 Main func called from cmd_dbd.c
812 int cmd_dbd_scanvol(struct vol *vol_in, dbd_flags_t flags)
817 /* Run with umask 0 */
820 /* Make vol accessible for all funcs */
824 /* We only support unicode volumes ! */
825 if (vol->v_volcharset != CH_UTF8) {
826 dbd_log(LOGSTD, "Not a Unicode volume: %s, %u != %u", vol->v_volcodepage, vol->v_volcharset, CH_UTF8);
831 * Get CNID database stamp, cnid_getstamp() passes the buffer,
832 * then cnid_resolve() actually gets the value from the db
834 cnid_getstamp(vol->v_cdb, stamp, sizeof(stamp));
836 if (setjmp(jmp) != 0) {
837 EC_EXIT_STATUS(0); /* Got signal, jump from dbd_readdir */
840 strcpy(cwdbuf, vol->v_path);
843 if ((vol->v_adouble == AD_VERSION_EA) && (dbd_flags & DBD_FLAGS_V2TOEA)) {
844 if (lstat(".", &st) != 0)
846 if (ad_convert(".", &st, vol, NULL) != 0) {
851 dbd_log(LOGSTD, "Conversion error for \"%s\": %s", vol->v_path, strerror(errno));
857 /* Start recursion */
858 EC_NEG1( dbd_readdir(1, htonl(2)) ); /* 2 = volumeroot CNID */