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_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 char pname[MAXPATHLEN] = "../";
69 Taken form afpd/desktop.c
71 static char *utompath(char *upath)
73 static char mpath[ MAXPATHLEN + 2]; /* for convert_charset dest_len parameter +2 */
75 uint16_t flags = CONV_IGNORE | CONV_UNESCAPEHEX;
83 outlen = strlen(upath);
85 if ((vol->v_casefold & AFPVOL_UTOMUPPER))
86 flags |= CONV_TOUPPER;
87 else if ((vol->v_casefold & AFPVOL_UTOMLOWER))
88 flags |= CONV_TOLOWER;
90 if ((vol->v_flags & AFPVOL_EILSEQ)) {
91 flags |= CONV__EILSEQ;
94 /* convert charsets */
95 if ((size_t)-1 == ( outlen = convert_charset(vol->v_volcharset,
98 u, outlen, mpath, MAXPATHLEN, &flags)) ) {
99 dbd_log( LOGSTD, "Conversion from %s to %s for %s failed.",
100 vol->v_volcodepage, vol->v_maccodepage, u);
108 Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop"
109 Returns pointer to name or NULL.
111 static const char *check_netatalk_dirs(const char *name)
115 for (c=0; netatalk_dirs[c]; c++) {
116 if ((strcmp(name, netatalk_dirs[c])) == 0)
117 return netatalk_dirs[c];
123 Check for special names
124 Returns pointer to name or NULL.
126 static const char *check_special_dirs(const char *name)
130 for (c=0; special_dirs[c]; c++) {
131 if ((strcmp(name, special_dirs[c])) == 0)
132 return special_dirs[c];
138 * We unCAPed a name, update CNID db
140 static int update_cnid(cnid_t did, const struct stat *sp, const char *oldname, const char *newname)
144 /* Query the database */
145 if ((id = cnid_lookup(vol->v_cdb, sp, did, (char *)oldname, strlen(oldname))) == CNID_INVALID)
146 /* not in db, no need to update */
149 /* Update the database */
150 if (cnid_update(vol->v_cdb, id, sp, did, (char *)newname, strlen(newname)) < 0)
157 Check for .AppleDouble file, create if missing
159 static int check_adfile(const char *fname, const struct stat *st, const char **newname)
162 int adflags = ADFLAGS_HF;
168 if (vol->v_adouble == AD_VERSION_EA) {
169 if (!(dbd_flags & DBD_FLAGS_V2TOEA))
171 if (ad_convert(fname, st, vol, newname) != 0) {
176 dbd_log(LOGSTD, "Conversion error for \"%s/%s\": %s", cwdbuf, fname, strerror(errno));
183 if (S_ISDIR(st->st_mode))
184 adflags |= ADFLAGS_DIR;
186 adname = vol->ad_path(fname, adflags);
188 if ((ret = access( adname, F_OK)) != 0) {
189 if (errno != ENOENT) {
190 dbd_log(LOGSTD, "Access error for ad-file '%s/%s': %s",
191 cwdbuf, adname, strerror(errno));
194 /* Missing. Log and create it */
195 dbd_log(LOGSTD, "Missing AppleDouble file '%s/%s'", cwdbuf, adname);
197 if (dbd_flags & DBD_FLAGS_SCAN)
198 /* Scan only requested, dont change anything */
204 if ((ret = ad_open(&ad, fname, adflags | ADFLAGS_CREATE | ADFLAGS_RDWR, 0666)) != 0) {
205 dbd_log( LOGSTD, "Error creating AppleDouble file '%s/%s': %s",
206 cwdbuf, adname, strerror(errno));
211 /* Set name in ad-file */
212 ad_setname(&ad, utompath((char *)fname));
214 ad_close(&ad, ADFLAGS_HF);
216 chown(adname, st->st_uid, st->st_gid);
217 /* FIXME: should we inherit mode too here ? */
219 chmod(adname, st->st_mode);
223 if (ad_open(&ad, fname, adflags | ADFLAGS_RDONLY) != 0) {
224 dbd_log( LOGSTD, "Error opening AppleDouble file for '%s/%s'", cwdbuf, fname);
227 ad_close(&ad, ADFLAGS_HF);
233 Remove all files with file::EA* from adouble dir
235 static void remove_eafiles(const char *name, struct ea *ea)
239 char eaname[MAXPATHLEN];
241 strlcpy(eaname, name, sizeof(eaname));
242 if (strlcat(eaname, "::EA", sizeof(eaname)) >= sizeof(eaname)) {
243 dbd_log(LOGSTD, "name too long: '%s/%s/%s'", cwdbuf, ADv2_DIRNAME, name);
247 if ((chdir(ADv2_DIRNAME)) != 0) {
248 dbd_log(LOGSTD, "Couldn't chdir to '%s/%s': %s",
249 cwdbuf, ADv2_DIRNAME, strerror(errno));
253 if ((dp = opendir(".")) == NULL) {
254 dbd_log(LOGSTD, "Couldn't open the directory '%s/%s': %s",
255 cwdbuf, ADv2_DIRNAME, strerror(errno));
259 while ((ep = readdir(dp))) {
260 if (strstr(ep->d_name, eaname) != NULL) {
261 dbd_log(LOGSTD, "Removing EA file: '%s/%s/%s'",
262 cwdbuf, ADv2_DIRNAME, ep->d_name);
263 if ((unlink(ep->d_name)) != 0) {
264 dbd_log(LOGSTD, "Error unlinking EA file '%s/%s/%s': %s",
265 cwdbuf, ADv2_DIRNAME, ep->d_name, strerror(errno));
273 if ((chdir("..")) != 0) {
274 dbd_log(LOGSTD, "Couldn't chdir to '%s': %s", cwdbuf, strerror(errno));
275 /* we can't proceed */
276 longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
281 Check Extended Attributes files
283 static int check_eafiles(const char *fname)
285 unsigned int count = 0;
291 if ((ret = ea_open(vol, fname, EA_RDWR, &ea)) != 0) {
294 dbd_log(LOGSTD, "Error calling ea_open for file: %s/%s, removing EA files", cwdbuf, fname);
295 if ( ! (dbd_flags & DBD_FLAGS_SCAN))
296 remove_eafiles(fname, &ea);
301 while (count < ea.ea_count) {
302 dbd_log(LOGDEBUG, "EA: %s", (*ea.ea_entries)[count].ea_name);
305 eaname = ea_path(&ea, (*ea.ea_entries)[count].ea_name, 0);
307 if (lstat(eaname, &st) != 0) {
309 dbd_log(LOGSTD, "Missing EA: %s/%s", cwdbuf, eaname);
311 dbd_log(LOGSTD, "Bogus EA: %s/%s", cwdbuf, eaname);
313 } else if (st.st_size != (*ea.ea_entries)[count].ea_size) {
314 dbd_log(LOGSTD, "Bogus EA: %s/%s, removing it...", cwdbuf, eaname);
316 if ((unlink(eaname)) != 0)
317 dbd_log(LOGSTD, "Error removing EA file '%s/%s': %s",
318 cwdbuf, eaname, strerror(errno));
322 /* Be CAREFUL here! This should do what ea_delentry does. ea_close relies on it !*/
323 free((*ea.ea_entries)[count].ea_name);
324 (*ea.ea_entries)[count].ea_name = NULL;
335 Check for .AppleDouble folder and .Parent, create if missing
337 static int check_addir(int volroot)
339 int addir_ok, adpar_ok;
344 if (vol->v_adouble == AD_VERSION_EA)
347 /* Check for ad-dir */
348 if ( (addir_ok = access(ADv2_DIRNAME, F_OK)) != 0) {
349 if (errno != ENOENT) {
350 dbd_log(LOGSTD, "Access error in directory %s: %s", cwdbuf, strerror(errno));
353 dbd_log(LOGSTD, "Missing %s for '%s'", ADv2_DIRNAME, cwdbuf);
356 /* Check for ".Parent" */
357 if ( (adpar_ok = access(vol->ad_path(".", ADFLAGS_DIR), F_OK)) != 0) {
358 if (errno != ENOENT) {
359 dbd_log(LOGSTD, "Access error on '%s/%s': %s",
360 cwdbuf, vol->ad_path(".", ADFLAGS_DIR), strerror(errno));
363 dbd_log(LOGSTD, "Missing .AppleDouble/.Parent for '%s'", cwdbuf);
366 /* Is one missing ? */
367 if ((addir_ok != 0) || (adpar_ok != 0)) {
368 /* Yes, but are we only scanning ? */
369 if (dbd_flags & DBD_FLAGS_SCAN) {
370 /* Yes: missing .Parent is not a problem, but missing ad-dir
371 causes later checking of ad-files to fail. So we have to return appropiately */
374 else /* (adpar_ok != 0) */
378 /* Create ad dir and set name */
381 if (ad_open(&ad, ".", ADFLAGS_HF | ADFLAGS_DIR | ADFLAGS_CREATE | ADFLAGS_RDWR, 0777) != 0) {
382 dbd_log( LOGSTD, "Error creating AppleDouble dir in %s: %s", cwdbuf, strerror(errno));
386 /* Get basename of cwd from cwdbuf */
387 mname = utompath(strrchr(cwdbuf, '/') + 1);
389 /* Update name in ad file */
390 ad_setname(&ad, mname);
392 ad_close(&ad, ADFLAGS_HF);
394 /* Inherit owner/group from "." to ".AppleDouble" and ".Parent" */
395 if ((lstat(".", &st)) != 0) {
396 dbd_log( LOGSTD, "Couldnt stat %s: %s", cwdbuf, strerror(errno));
399 chown(ADv2_DIRNAME, st.st_uid, st.st_gid);
400 chown(vol->ad_path(".", ADFLAGS_DIR), st.st_uid, st.st_gid);
407 Check if file cotains "::EA" and if it does check if its correspondig data fork exists.
409 0 = name is not an EA file
410 1 = name is an EA file and no problem was found
411 -1 = name is an EA file and data fork is gone
413 static int check_eafile_in_adouble(const char *name)
416 char *namep, *namedup = NULL;
418 /* Check if this is an AFPVOL_EA_AD vol */
419 if (vol->v_vfs_ea == AFPVOL_EA_AD) {
420 /* Does the filename contain "::EA" ? */
421 namedup = strdup(name);
422 if ((namep = strstr(namedup, "::EA")) == NULL) {
426 /* File contains "::EA" so it's an EA file. Check for data file */
428 /* Get string before "::EA" from EA filename */
430 strlcpy(pname + 3, namedup, sizeof(pname)); /* Prepends "../" */
432 if ((access( pname, F_OK)) == 0) {
437 if (errno != ENOENT) {
438 dbd_log(LOGSTD, "Access error for file '%s/%s': %s",
439 cwdbuf, name, strerror(errno));
443 /* Orphaned EA file*/
444 dbd_log(LOGSTD, "Orphaned Extended Attribute file '%s/%s/%s'",
445 cwdbuf, ADv2_DIRNAME, name);
447 if (dbd_flags & DBD_FLAGS_SCAN)
448 /* Scan only requested, dont change anything */
451 if ((unlink(name)) != 0) {
452 dbd_log(LOGSTD, "Error unlinking orphaned Extended Attribute file '%s/%s/%s'",
453 cwdbuf, ADv2_DIRNAME, name);
457 } /* if AFPVOL_EA_AD */
467 Check files and dirs inside .AppleDouble folder:
468 - remove orphaned files
471 static int read_addir(void)
477 if ((chdir(ADv2_DIRNAME)) != 0) {
478 if (vol->v_adouble == AD_VERSION_EA) {
481 dbd_log(LOGSTD, "Couldn't chdir to '%s/%s': %s",
482 cwdbuf, ADv2_DIRNAME, strerror(errno));
486 if ((dp = opendir(".")) == NULL) {
487 dbd_log(LOGSTD, "Couldn't open the directory '%s/%s': %s",
488 cwdbuf, ADv2_DIRNAME, strerror(errno));
492 while ((ep = readdir(dp))) {
493 /* Check if its "." or ".." */
494 if (DIR_DOT_OR_DOTDOT(ep->d_name))
498 if (STRCMP(ep->d_name, ==, ".Parent"))
501 if ((lstat(ep->d_name, &st)) < 0) {
502 dbd_log( LOGSTD, "Lost file or dir while enumeratin dir '%s/%s/%s', probably removed: %s",
503 cwdbuf, ADv2_DIRNAME, ep->d_name, strerror(errno));
508 if (S_ISDIR(st.st_mode)) {
509 dbd_log( LOGSTD, "Unexpected directory '%s' in AppleDouble dir '%s/%s'",
510 ep->d_name, cwdbuf, ADv2_DIRNAME);
514 /* Check if for orphaned and corrupt Extended Attributes file */
515 if (check_eafile_in_adouble(ep->d_name) != 0)
518 /* Check for data file */
519 strcpy(pname + 3, ep->d_name);
520 if ((access( pname, F_OK)) != 0) {
521 if (errno != ENOENT) {
522 dbd_log(LOGSTD, "Access error for file '%s/%s': %s",
523 cwdbuf, pname, strerror(errno));
526 /* Orphaned ad-file*/
527 dbd_log(LOGSTD, "Orphaned AppleDoube file '%s/%s/%s'",
528 cwdbuf, ADv2_DIRNAME, ep->d_name);
530 if (dbd_flags & DBD_FLAGS_SCAN)
531 /* Scan only requested, dont change anything */
534 if ((unlink(ep->d_name)) != 0) {
535 dbd_log(LOGSTD, "Error unlinking orphaned AppleDoube file '%s/%s/%s'",
536 cwdbuf, ADv2_DIRNAME, ep->d_name);
542 if ((chdir("..")) != 0) {
543 dbd_log(LOGSTD, "Couldn't chdir back to '%s' from AppleDouble dir: %s",
544 cwdbuf, strerror(errno));
545 /* This really is EOT! */
546 longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
555 Check CNID for a file/dir, both from db and from ad-file.
556 For detailed specs see intro.
558 @return Correct CNID of object or CNID_INVALID (ie 0) on error
560 static cnid_t check_cnid(const char *name, cnid_t did, struct stat *st, int adfile_ok)
562 int adflags = ADFLAGS_HF;
563 cnid_t db_cnid, ad_cnid;
566 adflags = ADFLAGS_HF | (S_ISDIR(st->st_mode) ? ADFLAGS_DIR : 0);
568 /* Get CNID from ad-file */
569 ad_cnid = CNID_INVALID;
572 if (ad_open(&ad, name, adflags | ADFLAGS_RDWR) != 0) {
574 if (vol->v_adouble != AD_VERSION_EA) {
575 dbd_log( LOGSTD, "Error opening AppleDouble file for '%s/%s': %s", cwdbuf, name, strerror(errno));
578 dbd_log( LOGDEBUG, "File without meta EA: \"%s/%s\"", cwdbuf, name);
581 ad_cnid = ad_getid(&ad, st->st_dev, st->st_ino, 0, stamp);
582 if (ad_cnid == CNID_INVALID)
583 dbd_log( LOGSTD, "Bad CNID in adouble file of '%s/%s'", cwdbuf, name);
585 dbd_log( LOGDEBUG, "CNID from .AppleDouble file for '%s/%s': %u", cwdbuf, name, ntohl(ad_cnid));
586 ad_close(&ad, ADFLAGS_HF);
590 /* Get CNID from database */
591 if ((db_cnid = cnid_add(vol->v_cdb, st, did, (char *)name, strlen(name), ad_cnid)) == CNID_INVALID)
594 /* Compare CNID from db and adouble file */
595 if (ad_cnid != db_cnid && adfile_ok == 0) {
596 /* Mismatch, overwrite ad file with value from db */
597 dbd_log(LOGSTD, "CNID mismatch for '%s/%s', CNID db: %u, ad-file: %u",
598 cwdbuf, name, ntohl(db_cnid), ntohl(ad_cnid));
600 if (ad_open(&ad, name, adflags | ADFLAGS_HF | ADFLAGS_RDWR) != 0) {
601 dbd_log(LOGSTD, "Error opening AppleDouble file for '%s/%s': %s",
602 cwdbuf, name, strerror(errno));
605 ad_setid( &ad, st->st_dev, st->st_ino, db_cnid, did, stamp);
607 ad_close(&ad, ADFLAGS_HF);
614 This is called recursively for all dirs.
615 volroot=1 means we're in the volume root dir, 0 means we aren't.
616 We use this when checking for netatalk private folders like .AppleDB.
617 did is our parents CNID.
619 static int dbd_readdir(int volroot, cnid_t did)
621 int cwd, ret = 0, adfile_ok, addir_ok;
626 static struct stat st; /* Save some stack space */
628 /* Check again for .AppleDouble folder, check_adfile also checks/creates it */
629 if ((addir_ok = check_addir(volroot)) != 0)
630 if ( ! (dbd_flags & DBD_FLAGS_SCAN))
631 /* Fatal on rebuild run, continue if only scanning ! */
634 /* Check AppleDouble files in AppleDouble folder, but only if it exists or could be created */
636 if ((read_addir()) != 0)
637 if ( ! (dbd_flags & DBD_FLAGS_SCAN))
638 /* Fatal on rebuild run, continue if only scanning ! */
641 if ((dp = opendir (".")) == NULL) {
642 dbd_log(LOGSTD, "Couldn't open the directory: %s",strerror(errno));
646 while ((ep = readdir (dp))) {
647 /* Check if we got a termination signal */
649 longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
651 /* Check if its "." or ".." */
652 if (DIR_DOT_OR_DOTDOT(ep->d_name))
655 /* Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop" */
656 if ((name = check_netatalk_dirs(ep->d_name)) != NULL) {
658 dbd_log(LOGSTD, "Nested %s in %s", name, cwdbuf);
662 /* Check for special folders in volume root e.g. ".zfs" */
664 if ((name = check_special_dirs(ep->d_name)) != NULL) {
665 dbd_log(LOGSTD, "Ignoring special dir \"%s\"", name);
670 /* Skip .AppleDouble dir in this loop */
671 if (STRCMP(ep->d_name, == , ADv2_DIRNAME))
674 if (!vol->vfs->vfs_validupath(vol, ep->d_name)) {
675 dbd_log(LOGDEBUG, "Ignoring \"%s\"", ep->d_name);
679 if ((ret = lstat(ep->d_name, &st)) < 0) {
680 dbd_log( LOGSTD, "Lost file while reading dir '%s/%s', probably removed: %s",
681 cwdbuf, ep->d_name, strerror(errno));
685 switch (st.st_mode & S_IFMT) {
691 dbd_log(LOGSTD, "Bad filetype: %s/%s", cwdbuf, ep->d_name);
692 if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
693 if ((unlink(ep->d_name)) != 0) {
694 dbd_log(LOGSTD, "Error removing: %s/%s: %s", cwdbuf, ep->d_name, strerror(errno));
700 /**************************************************************************
702 **************************************************************************/
703 static unsigned long long statcount = 0;
710 if ((statcount % 10000) == 0) {
711 if (dbd_flags & DBD_FLAGS_STATS)
712 dbd_log(LOGSTD, "Scanned: %10llu, time: %10llu s",
713 statcount, (unsigned long long)(time(NULL) - t));
716 /**************************************************************************
718 **************************************************************************/
720 /* Check for appledouble file, create if missing, but only if we have addir */
721 const char *name = NULL;
724 adfile_ok = check_adfile(ep->d_name, &st, &name);
726 if (!S_ISLNK(st.st_mode)) {
730 update_cnid(did, &st, ep->d_name, name);
734 cnid = check_cnid(name, did, &st, adfile_ok);
737 if (vol->v_vfs_ea == AFPVOL_EA_AD)
741 /**************************************************************************
743 **************************************************************************/
744 if (S_ISDIR(st.st_mode) && cnid) { /* If we have no cnid for it we cant enter recursion */
746 strcat(cwdbuf, name);
747 dbd_log( LOGDEBUG, "Entering directory: %s", cwdbuf);
748 if (-1 == (cwd = open(".", O_RDONLY))) {
749 dbd_log( LOGSTD, "Cant open directory '%s': %s", cwdbuf, strerror(errno));
752 if (0 != chdir(name)) {
753 dbd_log( LOGSTD, "Cant chdir to directory '%s': %s", cwdbuf, strerror(errno));
758 ret = dbd_readdir(0, cnid);
762 *(strrchr(cwdbuf, '/')) = 0;
769 Use results of previous checks
771 if ((vol->v_adouble == AD_VERSION_EA) && (dbd_flags & DBD_FLAGS_V2TOEA)) {
772 if (rmdir(ADv2_DIRNAME) != 0) {
777 dbd_log(LOGSTD, "Error removing adouble dir \"%s/%s\": %s", cwdbuf, ADv2_DIRNAME, strerror(errno));
787 Main func called from cmd_dbd.c
789 int cmd_dbd_scanvol(struct vol *vol_in, dbd_flags_t flags)
794 /* Run with umask 0 */
797 /* Make vol accessible for all funcs */
801 /* We only support unicode volumes ! */
802 if (vol->v_volcharset != CH_UTF8) {
803 dbd_log(LOGSTD, "Not a Unicode volume: %s, %u != %u", vol->v_volcodepage, vol->v_volcharset, CH_UTF8);
808 * Get CNID database stamp, cnid_getstamp() passes the buffer,
809 * then cnid_resolve() actually gets the value from the db
811 cnid_getstamp(vol->v_cdb, stamp, sizeof(stamp));
813 if (setjmp(jmp) != 0) {
814 EC_EXIT_STATUS(0); /* Got signal, jump from dbd_readdir */
817 strcpy(cwdbuf, vol->v_path);
820 if ((vol->v_adouble == AD_VERSION_EA) && (dbd_flags & DBD_FLAGS_V2TOEA)) {
821 if (lstat(".", &st) != 0)
823 if (ad_convert(".", &st, vol, NULL) != 0) {
828 dbd_log(LOGSTD, "Conversion error for \"%s\": %s", vol->v_path, strerror(errno));
834 /* Start recursion */
835 EC_NEG1( dbd_readdir(1, htonl(2)) ); /* 2 = volumeroot CNID */