]> arthur.barton.de Git - netatalk.git/blob - etc/cnid_dbd/cmd_dbd_scanvol.c
Get the db stamp in cnid_get_stamp()
[netatalk.git] / etc / cnid_dbd / cmd_dbd_scanvol.c
1 /*
2   Copyright (c) 2009 Frank Lahm <franklahm@gmail.com>
3
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.
8
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.
13 */
14
15 #ifdef HAVE_CONFIG_H
16 #include "config.h"
17 #endif /* HAVE_CONFIG_H */
18
19 #include <unistd.h>
20 #include <stdlib.h>
21 #include <sys/types.h>
22 #include <sys/stat.h>
23 #include <dirent.h>
24 #include <fcntl.h>
25 #include <string.h>
26 #include <errno.h>
27 #include <setjmp.h>
28
29 #include <atalk/adouble.h>
30 #include <atalk/unicode.h>
31 #include <atalk/netatalk_conf.h>
32 //#include <atalk/cnid_dbd_private.h>
33 #include <atalk/volume.h>
34 #include <atalk/ea.h>
35 #include <atalk/util.h>
36 #include <atalk/acl.h>
37 #include <atalk/compat.h>
38 #include <atalk/cnid.h>
39
40 #include "cmd_dbd.h"
41 #include "dbif.h"
42 #include "db_param.h"
43 #include "dbd.h"
44
45 /* Some defines to ease code parsing */
46 #define ADDIR_OK (addir_ok == 0)
47 #define ADFILE_OK (adfile_ok == 0)
48
49
50 static struct vol     *myvol;
51 static char           cwdbuf[MAXPATHLEN+1];
52 static struct vol     *vol;
53 static DBD            *dbd_rebuild;
54 static dbd_flags_t    dbd_flags;
55 static char           stamp[CNID_DEV_LEN];
56 static char           *netatalk_dirs[] = {
57     ".AppleDB",
58     ".AppleDesktop",
59     NULL
60 };
61 static char           *special_dirs[] = {
62     ".zfs",
63     NULL
64 };
65 static struct cnid_dbd_rqst rqst;
66 static struct cnid_dbd_rply rply;
67 static jmp_buf jmp;
68 static char pname[MAXPATHLEN] = "../";
69 static char cnidResBuf[12 + MAXPATHLEN + 1];
70
71 /*
72   Taken form afpd/desktop.c
73 */
74 static char *utompath(char *upath)
75 {
76     static char  mpath[ MAXPATHLEN + 2]; /* for convert_charset dest_len parameter +2 */
77     char         *m, *u;
78     uint16_t     flags = CONV_IGNORE | CONV_UNESCAPEHEX;
79     size_t       outlen;
80
81     if (!upath)
82         return NULL;
83
84     m = mpath;
85     u = upath;
86     outlen = strlen(upath);
87
88     if ((myvol->v_casefold & AFPVOL_UTOMUPPER))
89         flags |= CONV_TOUPPER;
90     else if ((myvol->v_casefold & AFPVOL_UTOMLOWER))
91         flags |= CONV_TOLOWER;
92
93     if ((myvol->v_flags & AFPVOL_EILSEQ)) {
94         flags |= CONV__EILSEQ;
95     }
96
97     /* convert charsets */
98     if ((size_t)-1 == ( outlen = convert_charset(myvol->v_volcharset,
99                                                  CH_UTF8_MAC,
100                                                  myvol->v_maccharset,
101                                                  u, outlen, mpath, MAXPATHLEN, &flags)) ) {
102         dbd_log( LOGSTD, "Conversion from %s to %s for %s failed.",
103                  myvol->v_volcodepage, myvol->v_maccodepage, u);
104         return NULL;
105     }
106
107     return(m);
108 }
109
110 /*
111   Taken form afpd/desktop.c
112 */
113 static char *mtoupath(char *mpath)
114 {
115     static char  upath[ MAXPATHLEN + 2]; /* for convert_charset dest_len parameter +2 */
116     char    *m, *u;
117     size_t       inplen;
118     size_t       outlen;
119     u_int16_t    flags = 0;
120
121     if (!mpath)
122         return NULL;
123
124     if ( *mpath == '\0' ) {
125         return( "." );
126     }
127
128     /* set conversion flags */
129     if ((myvol->v_casefold & AFPVOL_MTOUUPPER))
130         flags |= CONV_TOUPPER;
131     else if ((myvol->v_casefold & AFPVOL_MTOULOWER))
132         flags |= CONV_TOLOWER;
133
134     if ((myvol->v_flags & AFPVOL_EILSEQ)) {
135         flags |= CONV__EILSEQ;
136     }
137
138     m = mpath;
139     u = upath;
140
141     inplen = strlen(m);
142     outlen = MAXPATHLEN;
143
144     if ((size_t)-1 == (outlen = convert_charset(CH_UTF8_MAC,
145                                                 myvol->v_volcharset,
146                                                 myvol->v_maccharset,
147                                                 m, inplen, u, outlen, &flags)) ) {
148         dbd_log( LOGSTD, "conversion from UTF8-MAC to %s for %s failed.",
149                  myvol->v_volcodepage, mpath);
150         return NULL;
151     }
152
153     return( upath );
154 }
155
156 /*
157   Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop"
158   Returns pointer to name or NULL.
159 */
160 static const char *check_netatalk_dirs(const char *name)
161 {
162     int c;
163
164     for (c=0; netatalk_dirs[c]; c++) {
165         if ((strcmp(name, netatalk_dirs[c])) == 0)
166             return netatalk_dirs[c];
167     }
168     return NULL;
169 }
170
171 /*
172   Check for special names
173   Returns pointer to name or NULL.
174 */
175 static const char *check_special_dirs(const char *name)
176 {
177     int c;
178
179     for (c=0; special_dirs[c]; c++) {
180         if ((strcmp(name, special_dirs[c])) == 0)
181             return special_dirs[c];
182     }
183     return NULL;
184 }
185
186 /*
187  * We unCAPed a name, update CNID db
188  */
189 static int update_cnid(cnid_t did, const struct stat *sp, const char *oldname, const char *newname)
190 {
191     int ret;
192     cnid_t id;
193
194     /* Query the database */
195     if ((id = cnid_lookup(vol->v_cdb, sp, did, (char *)oldname, strlen(oldname))) == CNID_INVALID)
196         /* not in db, no need to update */
197         return 0;
198
199     /* Update the database */
200     if (cnid_update(vol->v_cdb, id, sp, did, (char *)newname, strlen(newname)) < 0)
201         return -1;
202
203     return 0;
204 }
205
206 /*
207   Check for .AppleDouble file, create if missing
208 */
209 static int check_adfile(const char *fname, const struct stat *st, const char **newname)
210 {
211     int ret;
212     int adflags = ADFLAGS_HF;
213     struct adouble ad;
214     const char *adname;
215
216     *newname = NULL;
217
218     if (myvol->v_adouble == AD_VERSION_EA) {
219         if (!(dbd_flags & DBD_FLAGS_V2TOEA))
220             return 0;
221         if (ad_convert(fname, st, myvol, newname) != 0) {
222             switch (errno) {
223             case ENOENT:
224                 break;
225             default:
226                 dbd_log(LOGSTD, "Conversion error for \"%s/%s\": %s", cwdbuf, fname, strerror(errno));
227                 break;
228             }
229         }
230         return 0;
231     }
232     
233     if (S_ISDIR(st->st_mode))
234         adflags |= ADFLAGS_DIR;
235
236     adname = myvol->ad_path(fname, adflags);
237
238     if ((ret = access( adname, F_OK)) != 0) {
239         if (errno != ENOENT) {
240             dbd_log(LOGSTD, "Access error for ad-file '%s/%s': %s",
241                     cwdbuf, adname, strerror(errno));
242             return -1;
243         }
244         /* Missing. Log and create it */
245         dbd_log(LOGSTD, "Missing AppleDouble file '%s/%s'", cwdbuf, adname);
246
247         if (dbd_flags & DBD_FLAGS_SCAN)
248             /* Scan only requested, dont change anything */
249             return -1;
250
251         /* Create ad file */
252         ad_init(&ad, myvol);
253
254         if ((ret = ad_open(&ad, fname, adflags | ADFLAGS_CREATE | ADFLAGS_RDWR, 0666)) != 0) {
255             dbd_log( LOGSTD, "Error creating AppleDouble file '%s/%s': %s",
256                      cwdbuf, adname, strerror(errno));
257
258             return -1;
259         }
260
261         /* Set name in ad-file */
262         ad_setname(&ad, utompath((char *)fname));
263         ad_flush(&ad);
264         ad_close(&ad, ADFLAGS_HF);
265
266         chown(adname, st->st_uid, st->st_gid);
267         /* FIXME: should we inherit mode too here ? */
268 #if 0
269         chmod(adname, st->st_mode);
270 #endif
271     } else {
272         ad_init(&ad, myvol);
273         if (ad_open(&ad, fname, adflags | ADFLAGS_RDONLY) != 0) {
274             dbd_log( LOGSTD, "Error opening AppleDouble file for '%s/%s'", cwdbuf, fname);
275             return -1;
276         }
277         ad_close(&ad, ADFLAGS_HF);
278     }
279     return 0;
280 }
281
282 /* 
283    Remove all files with file::EA* from adouble dir
284 */
285 static void remove_eafiles(const char *name, struct ea *ea)
286 {
287     DIR *dp = NULL;
288     struct dirent *ep;
289     char eaname[MAXPATHLEN];
290
291     strlcpy(eaname, name, sizeof(eaname));
292     if (strlcat(eaname, "::EA", sizeof(eaname)) >= sizeof(eaname)) {
293         dbd_log(LOGSTD, "name too long: '%s/%s/%s'", cwdbuf, ADv2_DIRNAME, name);
294         return;
295     }
296
297     if ((chdir(ADv2_DIRNAME)) != 0) {
298         dbd_log(LOGSTD, "Couldn't chdir to '%s/%s': %s",
299                 cwdbuf, ADv2_DIRNAME, strerror(errno));
300         return;
301     }
302
303     if ((dp = opendir(".")) == NULL) {
304         dbd_log(LOGSTD, "Couldn't open the directory '%s/%s': %s",
305                 cwdbuf, ADv2_DIRNAME, strerror(errno));
306         goto exit;
307     }
308
309     while ((ep = readdir(dp))) {
310         if (strstr(ep->d_name, eaname) != NULL) {
311             dbd_log(LOGSTD, "Removing EA file: '%s/%s/%s'",
312                     cwdbuf, ADv2_DIRNAME, ep->d_name);
313             if ((unlink(ep->d_name)) != 0) {
314                 dbd_log(LOGSTD, "Error unlinking EA file '%s/%s/%s': %s",
315                         cwdbuf, ADv2_DIRNAME, ep->d_name, strerror(errno));
316             }
317         } /* if */
318     } /* while */
319
320 exit:
321     if (dp)
322         closedir(dp);
323     if ((chdir("..")) != 0) {
324         dbd_log(LOGSTD, "Couldn't chdir to '%s': %s", cwdbuf, strerror(errno));
325         /* we can't proceed */
326         longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
327     }    
328 }
329
330 /*
331   Check Extended Attributes files
332 */
333 static int check_eafiles(const char *fname)
334 {
335     unsigned int  count = 0;
336     int ret = 0, remove;
337     struct ea ea;
338     struct stat st;
339     char *eaname;
340
341     if ((ret = ea_open(myvol, fname, EA_RDWR, &ea)) != 0) {
342         if (errno == ENOENT)
343             return 0;
344         dbd_log(LOGSTD, "Error calling ea_open for file: %s/%s, removing EA files", cwdbuf, fname);
345         if ( ! (dbd_flags & DBD_FLAGS_SCAN))
346             remove_eafiles(fname, &ea);
347         return -1;
348     }
349
350     /* Check all EAs */
351     while (count < ea.ea_count) {        
352         dbd_log(LOGDEBUG, "EA: %s", (*ea.ea_entries)[count].ea_name);
353         remove = 0;
354
355         eaname = ea_path(&ea, (*ea.ea_entries)[count].ea_name, 0);
356
357         if (lstat(eaname, &st) != 0) {
358             if (errno == ENOENT)
359                 dbd_log(LOGSTD, "Missing EA: %s/%s", cwdbuf, eaname);
360             else
361                 dbd_log(LOGSTD, "Bogus EA: %s/%s", cwdbuf, eaname);
362             remove = 1;
363         } else if (st.st_size != (*ea.ea_entries)[count].ea_size) {
364             dbd_log(LOGSTD, "Bogus EA: %s/%s, removing it...", cwdbuf, eaname);
365             remove = 1;
366             if ((unlink(eaname)) != 0)
367                 dbd_log(LOGSTD, "Error removing EA file '%s/%s': %s",
368                         cwdbuf, eaname, strerror(errno));
369         }
370
371         if (remove) {
372             /* Be CAREFUL here! This should do what ea_delentry does. ea_close relies on it !*/
373             free((*ea.ea_entries)[count].ea_name);
374             (*ea.ea_entries)[count].ea_name = NULL;
375         }
376
377         count++;
378     } /* while */
379
380     ea_close(&ea);
381     return ret;
382 }
383
384 /*
385   Check for .AppleDouble folder and .Parent, create if missing
386 */
387 static int check_addir(int volroot)
388 {
389     int addir_ok, adpar_ok;
390     struct stat st;
391     struct adouble ad;
392     char *mname = NULL;
393
394     if (myvol->v_adouble == AD_VERSION_EA)
395         return 0;
396
397     /* Check for ad-dir */
398     if ( (addir_ok = access(ADv2_DIRNAME, F_OK)) != 0) {
399         if (errno != ENOENT) {
400             dbd_log(LOGSTD, "Access error in directory %s: %s", cwdbuf, strerror(errno));
401             return -1;
402         }
403         dbd_log(LOGSTD, "Missing %s for '%s'", ADv2_DIRNAME, cwdbuf);
404     }
405
406     /* Check for ".Parent" */
407     if ( (adpar_ok = access(myvol->ad_path(".", ADFLAGS_DIR), F_OK)) != 0) {
408         if (errno != ENOENT) {
409             dbd_log(LOGSTD, "Access error on '%s/%s': %s",
410                     cwdbuf, myvol->ad_path(".", ADFLAGS_DIR), strerror(errno));
411             return -1;
412         }
413         dbd_log(LOGSTD, "Missing .AppleDouble/.Parent for '%s'", cwdbuf);
414     }
415
416     /* Is one missing ? */
417     if ((addir_ok != 0) || (adpar_ok != 0)) {
418         /* Yes, but are we only scanning ? */
419         if (dbd_flags & DBD_FLAGS_SCAN) {
420             /* Yes:  missing .Parent is not a problem, but missing ad-dir
421                causes later checking of ad-files to fail. So we have to return appropiately */
422             if (addir_ok != 0)
423                 return -1;
424             else  /* (adpar_ok != 0) */
425                 return 0;
426         }
427
428         /* Create ad dir and set name */
429         ad_init(&ad, myvol);
430
431         if (ad_open(&ad, ".", ADFLAGS_HF | ADFLAGS_DIR | ADFLAGS_CREATE | ADFLAGS_RDWR, 0777) != 0) {
432             dbd_log( LOGSTD, "Error creating AppleDouble dir in %s: %s", cwdbuf, strerror(errno));
433             return -1;
434         }
435
436         /* Get basename of cwd from cwdbuf */
437         mname = utompath(strrchr(cwdbuf, '/') + 1);
438
439         /* Update name in ad file */
440         ad_setname(&ad, mname);
441         ad_flush(&ad);
442         ad_close(&ad, ADFLAGS_HF);
443
444         /* Inherit owner/group from "." to ".AppleDouble" and ".Parent" */
445         if ((lstat(".", &st)) != 0) {
446             dbd_log( LOGSTD, "Couldnt stat %s: %s", cwdbuf, strerror(errno));
447             return -1;
448         }
449         chown(ADv2_DIRNAME, st.st_uid, st.st_gid);
450         chown(myvol->ad_path(".", ADFLAGS_DIR), st.st_uid, st.st_gid);
451     }
452
453     return 0;
454 }
455
456 /*
457   Check if file cotains "::EA" and if it does check if its correspondig data fork exists.
458   Returns:
459   0 = name is not an EA file
460   1 = name is an EA file and no problem was found
461   -1 = name is an EA file and data fork is gone
462  */
463 static int check_eafile_in_adouble(const char *name)
464 {
465     int ret = 0;
466     char *namep, *namedup = NULL;
467
468     /* Check if this is an AFPVOL_EA_AD vol */
469     if (myvol->v_vfs_ea == AFPVOL_EA_AD) {
470         /* Does the filename contain "::EA" ? */
471         namedup = strdup(name);
472         if ((namep = strstr(namedup, "::EA")) == NULL) {
473             ret = 0;
474             goto ea_check_done;
475         } else {
476             /* File contains "::EA" so it's an EA file. Check for data file  */
477
478             /* Get string before "::EA" from EA filename */
479             namep[0] = 0;
480             strlcpy(pname + 3, namedup, sizeof(pname)); /* Prepends "../" */
481
482             if ((access( pname, F_OK)) == 0) {
483                 ret = 1;
484                 goto ea_check_done;
485             } else {
486                 ret = -1;
487                 if (errno != ENOENT) {
488                     dbd_log(LOGSTD, "Access error for file '%s/%s': %s",
489                             cwdbuf, name, strerror(errno));
490                     goto ea_check_done;
491                 }
492
493                 /* Orphaned EA file*/
494                 dbd_log(LOGSTD, "Orphaned Extended Attribute file '%s/%s/%s'",
495                         cwdbuf, ADv2_DIRNAME, name);
496
497                 if (dbd_flags & DBD_FLAGS_SCAN)
498                     /* Scan only requested, dont change anything */
499                     goto ea_check_done;
500
501                 if ((unlink(name)) != 0) {
502                     dbd_log(LOGSTD, "Error unlinking orphaned Extended Attribute file '%s/%s/%s'",
503                             cwdbuf, ADv2_DIRNAME, name);
504                 }
505             } /* if (access) */
506         } /* if strstr */
507     } /* if AFPVOL_EA_AD */
508
509 ea_check_done:
510     if (namedup)
511         free(namedup);
512
513     return ret;
514 }
515
516 /*
517   Check files and dirs inside .AppleDouble folder:
518   - remove orphaned files
519   - bail on dirs
520 */
521 static int read_addir(void)
522 {
523     DIR *dp;
524     struct dirent *ep;
525     struct stat st;
526
527     if ((chdir(ADv2_DIRNAME)) != 0) {
528         if (myvol->v_adouble == AD_VERSION_EA) {
529             return 0;
530         }
531         dbd_log(LOGSTD, "Couldn't chdir to '%s/%s': %s",
532                 cwdbuf, ADv2_DIRNAME, strerror(errno));
533         return -1;
534     }
535
536     if ((dp = opendir(".")) == NULL) {
537         dbd_log(LOGSTD, "Couldn't open the directory '%s/%s': %s",
538                 cwdbuf, ADv2_DIRNAME, strerror(errno));
539         return -1;
540     }
541
542     while ((ep = readdir(dp))) {
543         /* Check if its "." or ".." */
544         if (DIR_DOT_OR_DOTDOT(ep->d_name))
545             continue;
546
547         /* Skip ".Parent" */
548         if (STRCMP(ep->d_name, ==, ".Parent"))
549             continue;
550
551         if ((lstat(ep->d_name, &st)) < 0) {
552             dbd_log( LOGSTD, "Lost file or dir while enumeratin dir '%s/%s/%s', probably removed: %s",
553                      cwdbuf, ADv2_DIRNAME, ep->d_name, strerror(errno));
554             continue;
555         }
556
557         /* Check for dirs */
558         if (S_ISDIR(st.st_mode)) {
559             dbd_log( LOGSTD, "Unexpected directory '%s' in AppleDouble dir '%s/%s'",
560                      ep->d_name, cwdbuf, ADv2_DIRNAME);
561             continue;
562         }
563
564         /* Check if for orphaned and corrupt Extended Attributes file */
565         if (check_eafile_in_adouble(ep->d_name) != 0)
566             continue;
567
568         /* Check for data file */
569         strcpy(pname + 3, ep->d_name);
570         if ((access( pname, F_OK)) != 0) {
571             if (errno != ENOENT) {
572                 dbd_log(LOGSTD, "Access error for file '%s/%s': %s",
573                         cwdbuf, pname, strerror(errno));
574                 continue;
575             }
576             /* Orphaned ad-file*/
577             dbd_log(LOGSTD, "Orphaned AppleDoube file '%s/%s/%s'",
578                     cwdbuf, ADv2_DIRNAME, ep->d_name);
579
580             if (dbd_flags & DBD_FLAGS_SCAN)
581                 /* Scan only requested, dont change anything */
582                 continue;;
583
584             if ((unlink(ep->d_name)) != 0) {
585                 dbd_log(LOGSTD, "Error unlinking orphaned AppleDoube file '%s/%s/%s'",
586                         cwdbuf, ADv2_DIRNAME, ep->d_name);
587
588             }
589         }
590     }
591
592     if ((chdir("..")) != 0) {
593         dbd_log(LOGSTD, "Couldn't chdir back to '%s' from AppleDouble dir: %s",
594                 cwdbuf, strerror(errno));
595         /* This really is EOT! */
596         longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
597     }
598
599     closedir(dp);
600
601     return 0;
602 }
603
604 /*
605   Check CNID for a file/dir, both from db and from ad-file.
606   For detailed specs see intro.
607
608   @return Correct CNID of object or CNID_INVALID (ie 0) on error
609 */
610 static cnid_t check_cnid(const char *name, cnid_t did, struct stat *st, int adfile_ok)
611 {
612     int ret, adflags = ADFLAGS_HF;
613     cnid_t db_cnid, ad_cnid, tmpid;
614     struct adouble ad;
615
616     adflags = ADFLAGS_HF | (S_ISDIR(st->st_mode) ? ADFLAGS_DIR : 0);
617
618     /* Get CNID from ad-file */
619     ad_cnid = CNID_INVALID;
620     if (ADFILE_OK) {
621         ad_init(&ad, myvol);
622         if (ad_open(&ad, name, adflags | ADFLAGS_RDWR) != 0) {
623             
624             if (myvol->v_adouble != AD_VERSION_EA) {
625                 dbd_log( LOGSTD, "Error opening AppleDouble file for '%s/%s': %s", cwdbuf, name, strerror(errno));
626                 return CNID_INVALID;
627             }
628             dbd_log( LOGDEBUG, "File without meta EA: \"%s/%s\"", cwdbuf, name);
629             adfile_ok = 1;
630         } else {
631             ad_cnid = ad_getid(&ad, st->st_dev, st->st_ino, 0, stamp);
632             if (ad_cnid == CNID_INVALID)
633                 dbd_log( LOGSTD, "Bad CNID in adouble file of '%s/%s'", cwdbuf, name);
634             else
635                 dbd_log( LOGDEBUG, "CNID from .AppleDouble file for '%s/%s': %u", cwdbuf, name, ntohl(ad_cnid));
636             ad_close(&ad, ADFLAGS_HF);
637         }
638     }
639
640     /* Get CNID from database */
641     if ((db_cnid = cnid_add(vol->v_cdb, st, did, (char *)name, strlen(name), ad_cnid)) == CNID_INVALID)
642         return CNID_INVALID;
643
644     /* Compare CNID from db and adouble file */
645     if (ad_cnid != db_cnid && adfile_ok == 0) {
646         /* Mismatch, overwrite ad file with value from db */
647         dbd_log(LOGSTD, "CNID mismatch for '%s/%s', CNID db: %u, ad-file: %u",
648                 cwdbuf, name, ntohl(db_cnid), ntohl(ad_cnid));
649         ad_init(&ad, myvol);
650         if (ad_open(&ad, name, adflags | ADFLAGS_HF | ADFLAGS_RDWR) != 0) {
651             dbd_log(LOGSTD, "Error opening AppleDouble file for '%s/%s': %s",
652                     cwdbuf, name, strerror(errno));
653             return CNID_INVALID;
654         }
655         ad_setid( &ad, st->st_dev, st->st_ino, db_cnid, did, stamp);
656         ad_flush(&ad);
657         ad_close(&ad, ADFLAGS_HF);
658     }
659
660     return db_cnid;
661 }
662
663 /*
664   This is called recursively for all dirs.
665   volroot=1 means we're in the volume root dir, 0 means we aren't.
666   We use this when checking for netatalk private folders like .AppleDB.
667   did is our parents CNID.
668 */
669 static int dbd_readdir(int volroot, cnid_t did)
670 {
671     int cwd, ret = 0, adfile_ok, addir_ok, encoding_ok;
672     cnid_t cnid = 0;
673     const char *name;
674     DIR *dp;
675     struct dirent *ep;
676     static struct stat st;      /* Save some stack space */
677
678     /* Check again for .AppleDouble folder, check_adfile also checks/creates it */
679     if ((addir_ok = check_addir(volroot)) != 0)
680         if ( ! (dbd_flags & DBD_FLAGS_SCAN))
681             /* Fatal on rebuild run, continue if only scanning ! */
682             return -1;
683
684     /* Check AppleDouble files in AppleDouble folder, but only if it exists or could be created */
685     if (ADDIR_OK)
686         if ((read_addir()) != 0)
687             if ( ! (dbd_flags & DBD_FLAGS_SCAN))
688                 /* Fatal on rebuild run, continue if only scanning ! */
689                 return -1;
690
691     if ((dp = opendir (".")) == NULL) {
692         dbd_log(LOGSTD, "Couldn't open the directory: %s",strerror(errno));
693         return -1;
694     }
695
696     while ((ep = readdir (dp))) {
697         /* Check if we got a termination signal */
698         if (alarmed)
699             longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
700
701         /* Check if its "." or ".." */
702         if (DIR_DOT_OR_DOTDOT(ep->d_name))
703             continue;
704
705         /* Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop" */
706         if ((name = check_netatalk_dirs(ep->d_name)) != NULL) {
707             if (! volroot)
708                 dbd_log(LOGSTD, "Nested %s in %s", name, cwdbuf);
709             continue;
710         }
711
712         /* Check for special folders in volume root e.g. ".zfs" */
713         if (volroot) {
714             if ((name = check_special_dirs(ep->d_name)) != NULL) {
715                 dbd_log(LOGSTD, "Ignoring special dir \"%s\"", name);
716                 continue;
717             }
718         }
719
720         /* Skip .AppleDouble dir in this loop */
721         if (STRCMP(ep->d_name, == , ADv2_DIRNAME))
722             continue;
723
724         if (!myvol->vfs->vfs_validupath(myvol, ep->d_name)) {
725             dbd_log(LOGDEBUG, "Ignoring \"%s\"", ep->d_name);
726             continue;
727         }
728
729         if ((ret = lstat(ep->d_name, &st)) < 0) {
730             dbd_log( LOGSTD, "Lost file while reading dir '%s/%s', probably removed: %s",
731                      cwdbuf, ep->d_name, strerror(errno));
732             continue;
733         }
734         
735         switch (st.st_mode & S_IFMT) {
736         case S_IFREG:
737         case S_IFDIR:
738             break;
739         case S_IFLNK:
740             dbd_log(LOGDEBUG, "Ignoring symlink %s/%s", cwdbuf, ep->d_name);
741             continue;
742         default:
743             dbd_log(LOGSTD, "Bad filetype: %s/%s", cwdbuf, ep->d_name);
744             if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
745                 if ((unlink(ep->d_name)) != 0) {
746                     dbd_log(LOGSTD, "Error removing: %s/%s: %s", cwdbuf, ep->d_name, strerror(errno));
747                 }
748             }
749             continue;
750         }
751
752         /**************************************************************************
753            Statistics
754          **************************************************************************/
755         static unsigned long long statcount = 0;
756         static time_t t = 0;
757
758         if (t == 0)
759             t = time(NULL);
760
761         statcount++;
762         if ((statcount % 10000) == 0) {
763             if (dbd_flags & DBD_FLAGS_STATS)            
764                 dbd_log(LOGSTD, "Scanned: %10llu, time: %10llu s",
765                         statcount, (unsigned long long)(time(NULL) - t));
766         }
767
768         /**************************************************************************
769            Tests
770         **************************************************************************/
771
772         /* Check for appledouble file, create if missing, but only if we have addir */
773         const char *name = NULL;
774         adfile_ok = -1;
775         if (ADDIR_OK)
776             adfile_ok = check_adfile(ep->d_name, &st, &name);
777
778         if (name == NULL) {
779             name = ep->d_name;
780         } else {
781             update_cnid(did, &st, ep->d_name, name);
782         }
783
784         /* Check CNIDs */
785         cnid = check_cnid(name, did, &st, adfile_ok);
786
787         /* Now add this object to our rebuild dbd */
788         if (cnid && dbd_rebuild) {
789             static uint count = 0;
790             rqst.cnid = rply.cnid;
791             ret = dbd_rebuild_add(dbd_rebuild, &rqst, &rply);
792             if (dbif_txn_close(dbd_rebuild, ret) != 0)
793                 return -1;
794             if (rply.result != CNID_DBD_RES_OK) {
795                 dbd_log( LOGSTD, "Fatal error adding CNID: %u for '%s/%s' to in-memory rebuild-db",
796                          cnid, cwdbuf, name);
797                 return -1;
798             }
799             count++;
800             if (count == 10000) {
801                 if (dbif_txn_checkpoint(dbd_rebuild, 0, 0, 0) < 0) {
802                     dbd_log(LOGSTD, "Error checkpointing!");
803                     return -1;
804                 }
805                 count = 0;
806             }
807         }
808
809         /* Check EA files */
810         if (myvol->v_vfs_ea == AFPVOL_EA_AD)
811             check_eafiles(name);
812
813         /**************************************************************************
814           Recursion
815         **************************************************************************/
816         if (S_ISDIR(st.st_mode) && cnid) { /* If we have no cnid for it we cant enter recursion */
817             strcat(cwdbuf, "/");
818             strcat(cwdbuf, name);
819             dbd_log( LOGDEBUG, "Entering directory: %s", cwdbuf);
820             if (-1 == (cwd = open(".", O_RDONLY))) {
821                 dbd_log( LOGSTD, "Cant open directory '%s': %s", cwdbuf, strerror(errno));
822                 continue;
823             }
824             if (0 != chdir(name)) {
825                 dbd_log( LOGSTD, "Cant chdir to directory '%s': %s", cwdbuf, strerror(errno));
826                 close(cwd);
827                 continue;
828             }
829
830             ret = dbd_readdir(0, cnid);
831
832             fchdir(cwd);
833             close(cwd);
834             *(strrchr(cwdbuf, '/')) = 0;
835             if (ret < 0)
836                 return -1;
837         }
838     }
839
840     /*
841       Use results of previous checks
842     */
843     if ((myvol->v_adouble == AD_VERSION_EA) && (dbd_flags & DBD_FLAGS_V2TOEA)) {
844         if (rmdir(ADv2_DIRNAME) != 0) {
845             switch (errno) {
846             case ENOENT:
847                 break;
848             default:
849                 dbd_log(LOGSTD, "Error removing adouble dir \"%s/%s\": %s", cwdbuf, ADv2_DIRNAME, strerror(errno));
850                 break;
851             }
852         }
853     }
854     closedir(dp);
855     return ret;
856 }
857
858 static int scanvol(struct vol *vol, dbd_flags_t flags)
859 {
860     struct stat st;
861
862     /* Make this stuff accessible from all funcs easily */
863     myvol = vol;
864     dbd_flags = flags;
865
866     /* Run with umask 0 */
867     umask(0);
868
869     strcpy(cwdbuf, myvol->v_path);
870     chdir(myvol->v_path);
871
872     if ((myvol->v_adouble == AD_VERSION_EA) && (dbd_flags & DBD_FLAGS_V2TOEA)) {
873         if (lstat(".", &st) != 0)
874             return -1;
875         if (ad_convert(".", &st, vol, NULL) != 0) {
876             switch (errno) {
877             case ENOENT:
878                 break;
879             default:
880                 dbd_log(LOGSTD, "Conversion error for \"%s\": %s", myvol->v_path, strerror(errno));
881                 break;
882             }
883         }
884     }
885
886     /* Start recursion */
887     if (dbd_readdir(1, htonl(2)) < 0)  /* 2 = volumeroot CNID */
888         return -1;
889
890     return 0;
891 }
892
893 /*
894   Main func called from cmd_dbd.c
895 */
896 int cmd_dbd_scanvol(struct vol *vol_in, dbd_flags_t flags)
897 {
898     int ret = 0;
899
900     /* Make vol accessible for all funcs */
901     vol = vol_in;
902
903     /* We only support unicode volumes ! */
904     if (vol->v_volcharset != CH_UTF8) {
905         dbd_log(LOGSTD, "Not a Unicode volume: %s, %u != %u", vol->v_volcodepage, vol->v_volcharset, CH_UTF8);
906         return -1;
907     }
908
909     /*
910      * Get CNID database stamp, cnid_getstamp() passes the buffer,
911      * then cnid_resolve() actually gets the value from the db
912      */
913     cnid_getstamp(vol->v_cdb, stamp, sizeof(stamp));
914
915     if (setjmp(jmp) != 0) {
916         ret = 0;                /* Got signal, jump from dbd_readdir */
917         goto exit;
918     }
919
920     /* scanvol */
921     if ((scanvol(vol, flags)) != 0) {
922         ret = -1;
923         goto exit;
924     }
925
926 exit:
927     return ret;
928 }