]> arthur.barton.de Git - netatalk.git/blob - etc/cnid_dbd/cmd_dbd_scanvol.c
on disk rebuild db
[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/volinfo.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
38 #include "cmd_dbd.h"
39 #include "dbif.h"
40 #include "db_param.h"
41 #include "dbd.h"
42
43 /* Some defines to ease code parsing */
44 #define ADDIR_OK (addir_ok == 0)
45 #define ADFILE_OK (adfile_ok == 0)
46
47 /* These must be accessible for cmd_dbd_* funcs */
48 struct volinfo        *volinfo;
49 char                  cwdbuf[MAXPATHLEN+1];
50
51 /* Some static vars */
52 static DBD            *dbd;
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 struct vol volume; /* fake it for ea_open */
69 static char pname[MAXPATHLEN] = "../";
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 ((volinfo->v_casefold & AFPVOL_UTOMUPPER))
89         flags |= CONV_TOUPPER;
90     else if ((volinfo->v_casefold & AFPVOL_UTOMLOWER))
91         flags |= CONV_TOLOWER;
92
93     if ((volinfo->v_flags & AFPVOL_EILSEQ)) {
94         flags |= CONV__EILSEQ;
95     }
96
97     /* convert charsets */
98     if ((size_t)-1 == ( outlen = convert_charset(volinfo->v_volcharset,
99                                                  CH_UTF8_MAC,
100                                                  volinfo->v_maccharset,
101                                                  u, outlen, mpath, MAXPATHLEN, &flags)) ) {
102         dbd_log( LOGSTD, "Conversion from %s to %s for %s failed.",
103                  volinfo->v_volcodepage, volinfo->v_maccodepage, u);
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 (!(volinfo->v_flags & AFPVOL_NOHEX))
130         flags |= CONV_ESCAPEHEX;
131     if (!(volinfo->v_flags & AFPVOL_USEDOTS))
132         flags |= CONV_ESCAPEDOTS;
133
134     if ((volinfo->v_casefold & AFPVOL_MTOUUPPER))
135         flags |= CONV_TOUPPER;
136     else if ((volinfo->v_casefold & AFPVOL_MTOULOWER))
137         flags |= CONV_TOLOWER;
138
139     if ((volinfo->v_flags & AFPVOL_EILSEQ)) {
140         flags |= CONV__EILSEQ;
141     }
142
143     m = mpath;
144     u = upath;
145
146     inplen = strlen(m);
147     outlen = MAXPATHLEN;
148
149     if ((size_t)-1 == (outlen = convert_charset(CH_UTF8_MAC,
150                                                 volinfo->v_volcharset,
151                                                 volinfo->v_maccharset,
152                                                 m, inplen, u, outlen, &flags)) ) {
153         dbd_log( LOGSTD, "conversion from UTF8-MAC to %s for %s failed.",
154                  volinfo->v_volcodepage, mpath);
155         return NULL;
156     }
157
158     return( upath );
159 }
160
161 #if 0
162 /* 
163    Check if "name" is pointing to
164    a) an object inside the current volume (return 0)
165    b) an object outside the current volume (return 1)
166    Then stats the pointed to object and if it is a dir ors ADFLAGS_DIR to *adflags
167    Return -1 on any serious error.
168  */
169 static int check_symlink(const char *name, int *adflags)
170 {
171     int cwd;
172     ssize_t len;
173     char pathbuf[MAXPATHLEN + 1];
174     char *sep;
175     struct stat st;
176
177     if ((len = readlink(name, pathbuf, MAXPATHLEN)) == -1) {
178         dbd_log(LOGSTD, "Error reading link info for '%s/%s': %s", 
179                 cwdbuf, name, strerror(errno));
180         return -1;
181     }
182     pathbuf[len] = 0;
183
184     if ((stat(pathbuf, &st)) != 0) {
185         dbd_log(LOGSTD, "stat error '%s': %s", pathbuf, strerror(errno));
186     }
187
188     /* Remember cwd */
189     if ((cwd = open(".", O_RDONLY)) < 0) {
190         dbd_log(LOGSTD, "error opening cwd '%s': %s", cwdbuf, strerror(errno));
191         return -1;
192     }
193
194     if (S_ISDIR(st.st_mode)) {
195         *adflags |= ADFLAGS_DIR;
196     } else { /* file */
197         /* get basename from path */
198         if ((sep = strrchr(pathbuf, '/')) == NULL)
199             /* just a file at the same level */
200             return 0;
201         sep = 0; /* pathbuf now contains the directory*/
202     }
203
204     /* fchdir() to pathbuf so we can easily get its path with getcwd() */
205     if ((chdir(pathbuf)) != 0) {
206         dbd_log(LOGSTD, "Cant chdir to '%s': %s", pathbuf, strerror(errno));
207         return -1;
208     }
209
210     if ((getcwd(pathbuf, MAXPATHLEN)) == NULL) {
211         dbd_log(LOGSTD, "Cant get symlink'ed dir '%s/%s': %s", cwdbuf, pathbuf, strerror(errno));
212         if ((fchdir(cwd)) != 0)
213             /* We're foobared */
214             longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
215         return -1;
216     }
217
218     if ((fchdir(cwd)) != 0)
219         /* We're foobared */
220         longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
221
222     /*
223       We now have the symlink target dir as absoulte path in pathbuf
224       and can compare it with the currents volume path
225     */
226     int i = 0;
227     while (volinfo->v_path[i]) {
228         if ((pathbuf[i] == 0) || (volinfo->v_path[i] != pathbuf[i])) {
229             dbd_log( LOGDEBUG, "extra-share symlink '%s/%s', following", cwdbuf, name);
230             return 1;
231         }
232         i++;
233     }
234
235     dbd_log( LOGDEBUG, "intra-share symlink '%s/%s', not following", cwdbuf, name);
236     return 0;
237 }
238 #endif
239
240 /*
241   Check for wrong encoding e.g. "." at the beginning is not CAP encoded (:2e) although volume is default !AFPVOL_USEDOTS.
242   We do it by roundtripiping from volcharset to UTF8-MAC and back and then compare the result.
243 */
244 static int check_name_encoding(char *uname)
245 {
246     char *roundtripped;
247
248     roundtripped = mtoupath(utompath(uname));
249     if (!roundtripped) {
250         dbd_log( LOGSTD, "Error checking encoding for '%s/%s'", cwdbuf, uname);
251         return -1;
252     }
253
254     if ( STRCMP(uname, !=, roundtripped)) {
255         dbd_log( LOGSTD, "Bad encoding for '%s/%s'", cwdbuf, uname);
256         return -1;
257     }
258
259     return 0;
260 }
261
262 /*
263   Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop"
264   Returns pointer to name or NULL.
265 */
266 static const char *check_netatalk_dirs(const char *name)
267 {
268     int c;
269
270     for (c=0; netatalk_dirs[c]; c++) {
271         if ((strcmp(name, netatalk_dirs[c])) == 0)
272             return netatalk_dirs[c];
273     }
274     return NULL;
275 }
276
277 /*
278   Check for special names
279   Returns pointer to name or NULL.
280 */
281 static const char *check_special_dirs(const char *name)
282 {
283     int c;
284
285     for (c=0; special_dirs[c]; c++) {
286         if ((strcmp(name, special_dirs[c])) == 0)
287             return special_dirs[c];
288     }
289     return NULL;
290 }
291
292 /*
293   Check for .AppleDouble file, create if missing
294 */
295 static int check_adfile(const char *fname, const struct stat *st)
296 {
297     int ret, adflags;
298     struct adouble ad;
299     char *adname;
300
301     if (dbd_flags & DBD_FLAGS_CLEANUP)
302         return 0;
303
304     if (S_ISREG(st->st_mode))
305         adflags = 0;
306     else
307         adflags = ADFLAGS_DIR;
308
309     adname = volinfo->ad_path(fname, adflags);
310
311     if ((ret = access( adname, F_OK)) != 0) {
312         if (errno != ENOENT) {
313             dbd_log(LOGSTD, "Access error for ad-file '%s/%s': %s",
314                     cwdbuf, adname, strerror(errno));
315             return -1;
316         }
317         /* Missing. Log and create it */
318         dbd_log(LOGSTD, "Missing AppleDouble file '%s/%s'", cwdbuf, adname);
319
320         if (dbd_flags & DBD_FLAGS_SCAN)
321             /* Scan only requested, dont change anything */
322             return -1;
323
324         /* Create ad file */
325         ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
326
327         if ((ret = ad_open_metadata( fname, adflags, O_CREAT, &ad)) != 0) {
328             dbd_log( LOGSTD, "Error creating AppleDouble file '%s/%s': %s",
329                      cwdbuf, adname, strerror(errno));
330
331             return -1;
332         }
333
334         /* Set name in ad-file */
335         ad_setname(&ad, utompath((char *)fname));
336         ad_flush(&ad);
337         ad_close_metadata(&ad);
338
339         chown(adname, st->st_uid, st->st_gid);
340         /* FIXME: should we inherit mode too here ? */
341 #if 0
342         chmod(adname, st->st_mode);
343 #endif
344     } else {
345         ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
346         if (ad_open_metadata( fname, adflags, O_RDONLY, &ad) != 0) {
347             dbd_log( LOGSTD, "Error opening AppleDouble file for '%s/%s'", cwdbuf, fname);
348             return -1;
349         }
350         ad_close_metadata(&ad);
351     }
352     return 0;
353 }
354
355 /* 
356    Remove all files with file::EA* from adouble dir
357 */
358 static void remove_eafiles(const char *name, struct ea *ea)
359 {
360     DIR *dp = NULL;
361     struct dirent *ep;
362     char eaname[MAXPATHLEN];
363
364     strlcpy(eaname, name, sizeof(eaname));
365     if (strlcat(eaname, "::EA", sizeof(eaname)) >= sizeof(eaname)) {
366         dbd_log(LOGSTD, "name too long: '%s/%s/%s'", cwdbuf, ADv2_DIRNAME, name);
367         return;
368     }
369
370     if ((chdir(ADv2_DIRNAME)) != 0) {
371         dbd_log(LOGSTD, "Couldn't chdir to '%s/%s': %s",
372                 cwdbuf, ADv2_DIRNAME, strerror(errno));
373         return;
374     }
375
376     if ((dp = opendir(".")) == NULL) {
377         dbd_log(LOGSTD, "Couldn't open the directory '%s/%s': %s",
378                 cwdbuf, ADv2_DIRNAME, strerror(errno));
379         return;
380     }
381
382     while ((ep = readdir(dp))) {
383         if (strstr(ep->d_name, eaname) != NULL) {
384             dbd_log(LOGSTD, "Removing EA file: '%s/%s/%s'",
385                     cwdbuf, ADv2_DIRNAME, ep->d_name);
386             if ((unlink(ep->d_name)) != 0) {
387                 dbd_log(LOGSTD, "Error unlinking EA file '%s/%s/%s': %s",
388                         cwdbuf, ADv2_DIRNAME, ep->d_name, strerror(errno));
389             }
390         } /* if */
391     } /* while */
392
393     if (dp)
394         closedir(dp);
395
396 }
397
398 /*
399   Check Extended Attributes files
400 */
401 static int check_eafiles(const char *fname)
402 {
403     unsigned int  count = 0;
404     int ret = 0, remove;
405     struct ea ea;
406     struct stat st;
407     char *eaname;
408
409     if ((ret = ea_open(&volume, fname, EA_RDWR, &ea)) != 0) {
410         if (errno == ENOENT)
411             return 0;
412         dbd_log(LOGSTD, "Error calling ea_open for file: %s/%s, removing EA files", cwdbuf, fname);
413         if ( ! (dbd_flags & DBD_FLAGS_SCAN))
414             remove_eafiles(fname, &ea);
415         return -1;
416     }
417
418     /* Check all EAs */
419     while (count < ea.ea_count) {        
420         dbd_log(LOGDEBUG, "EA: %s", (*ea.ea_entries)[count].ea_name);
421         remove = 0;
422
423         eaname = ea_path(&ea, (*ea.ea_entries)[count].ea_name, 0);
424
425         if (lstat(eaname, &st) != 0) {
426             if (errno == ENOENT)
427                 dbd_log(LOGSTD, "Missing EA: %s/%s", cwdbuf, eaname);
428             else
429                 dbd_log(LOGSTD, "Bogus EA: %s/%s", cwdbuf, eaname);
430             remove = 1;
431         } else if (st.st_size != (*ea.ea_entries)[count].ea_size) {
432             dbd_log(LOGSTD, "Bogus EA: %s/%s, removing it...", cwdbuf, eaname);
433             remove = 1;
434             if ((unlink(eaname)) != 0)
435                 dbd_log(LOGSTD, "Error removing EA file '%s/%s': %s",
436                         cwdbuf, eaname, strerror(errno));
437         }
438
439         if (remove) {
440             /* Be CAREFUL here! This should do what ea_delentry does. ea_close relies on it !*/
441             free((*ea.ea_entries)[count].ea_name);
442             (*ea.ea_entries)[count].ea_name = NULL;
443         }
444
445         count++;
446     } /* while */
447
448     ea_close(&ea);
449     return ret;
450 }
451
452 /*
453   Check for .AppleDouble folder and .Parent, create if missing
454 */
455 static int check_addir(int volroot)
456 {
457     int addir_ok, adpar_ok;
458     struct stat st;
459     struct adouble ad;
460     char *mname = NULL;
461
462     if (dbd_flags & DBD_FLAGS_CLEANUP)
463         return 0;
464
465     /* Check for ad-dir */
466     if ( (addir_ok = access(ADv2_DIRNAME, F_OK)) != 0) {
467         if (errno != ENOENT) {
468             dbd_log(LOGSTD, "Access error in directory %s: %s", cwdbuf, strerror(errno));
469             return -1;
470         }
471         dbd_log(LOGSTD, "Missing %s for '%s'", ADv2_DIRNAME, cwdbuf);
472     }
473
474     /* Check for ".Parent" */
475     if ( (adpar_ok = access(volinfo->ad_path(".", ADFLAGS_DIR), F_OK)) != 0) {
476         if (errno != ENOENT) {
477             dbd_log(LOGSTD, "Access error on '%s/%s': %s",
478                     cwdbuf, volinfo->ad_path(".", ADFLAGS_DIR), strerror(errno));
479             return -1;
480         }
481         dbd_log(LOGSTD, "Missing .AppleDouble/.Parent for '%s'", cwdbuf);
482     }
483
484     /* Is one missing ? */
485     if ((addir_ok != 0) || (adpar_ok != 0)) {
486         /* Yes, but are we only scanning ? */
487         if (dbd_flags & DBD_FLAGS_SCAN) {
488             /* Yes:  missing .Parent is not a problem, but missing ad-dir
489                causes later checking of ad-files to fail. So we have to return appropiately */
490             if (addir_ok != 0)
491                 return -1;
492             else  /* (adpar_ok != 0) */
493                 return 0;
494         }
495
496         /* Create ad dir and set name */
497         ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
498
499         if (ad_open_metadata( ".", ADFLAGS_DIR, O_CREAT, &ad) != 0) {
500             dbd_log( LOGSTD, "Error creating AppleDouble dir in %s: %s", cwdbuf, strerror(errno));
501             return -1;
502         }
503
504         /* Get basename of cwd from cwdbuf */
505         mname = utompath(strrchr(cwdbuf, '/') + 1);
506
507         /* Update name in ad file */
508         ad_setname(&ad, mname);
509         ad_flush(&ad);
510         ad_close_metadata(&ad);
511
512         /* Inherit owner/group from "." to ".AppleDouble" and ".Parent" */
513         if ((lstat(".", &st)) != 0) {
514             dbd_log( LOGSTD, "Couldnt stat %s: %s", cwdbuf, strerror(errno));
515             return -1;
516         }
517         chown(ADv2_DIRNAME, st.st_uid, st.st_gid);
518         chown(volinfo->ad_path(".", ADFLAGS_DIR), st.st_uid, st.st_gid);
519     }
520
521     return 0;
522 }
523
524 /*
525   Check if file cotains "::EA" and if it does check if its correspondig data fork exists.
526   Returns:
527   0 = name is not an EA file
528   1 = name is an EA file and no problem was found
529   -1 = name is an EA file and data fork is gone
530  */
531 static int check_eafile_in_adouble(const char *name)
532 {
533     int ret = 0;
534     char *namep, *namedup = NULL;
535
536     /* Check if this is an AFPVOL_EA_AD vol */
537     if (volinfo->v_vfs_ea == AFPVOL_EA_AD) {
538         /* Does the filename contain "::EA" ? */
539         namedup = strdup(name);
540         if ((namep = strstr(namedup, "::EA")) == NULL) {
541             ret = 0;
542             goto ea_check_done;
543         } else {
544             /* File contains "::EA" so it's an EA file. Check for data file  */
545
546             /* Get string before "::EA" from EA filename */
547             namep[0] = 0;
548             strlcpy(pname + 3, namedup, sizeof(pname)); /* Prepends "../" */
549
550             if ((access( pname, F_OK)) == 0) {
551                 ret = 1;
552                 goto ea_check_done;
553             } else {
554                 ret = -1;
555                 if (errno != ENOENT) {
556                     dbd_log(LOGSTD, "Access error for file '%s/%s': %s",
557                             cwdbuf, name, strerror(errno));
558                     goto ea_check_done;
559                 }
560
561                 /* Orphaned EA file*/
562                 dbd_log(LOGSTD, "Orphaned Extended Attribute file '%s/%s/%s'",
563                         cwdbuf, ADv2_DIRNAME, name);
564
565                 if (dbd_flags & DBD_FLAGS_SCAN)
566                     /* Scan only requested, dont change anything */
567                     goto ea_check_done;
568
569                 if ((unlink(name)) != 0) {
570                     dbd_log(LOGSTD, "Error unlinking orphaned Extended Attribute file '%s/%s/%s'",
571                             cwdbuf, ADv2_DIRNAME, name);
572                 }
573             } /* if (access) */
574         } /* if strstr */
575     } /* if AFPVOL_EA_AD */
576
577 ea_check_done:
578     if (namedup)
579         free(namedup);
580
581     return ret;
582 }
583
584 /*
585   Check files and dirs inside .AppleDouble folder:
586   - remove orphaned files
587   - bail on dirs
588 */
589 static int read_addir(void)
590 {
591     DIR *dp;
592     struct dirent *ep;
593     struct stat st;
594
595     if ((chdir(ADv2_DIRNAME)) != 0) {
596         dbd_log(LOGSTD, "Couldn't chdir to '%s/%s': %s",
597                 cwdbuf, ADv2_DIRNAME, strerror(errno));
598         return -1;
599     }
600
601     if ((dp = opendir(".")) == NULL) {
602         dbd_log(LOGSTD, "Couldn't open the directory '%s/%s': %s",
603                 cwdbuf, ADv2_DIRNAME, strerror(errno));
604         return -1;
605     }
606
607     while ((ep = readdir(dp))) {
608         /* Check if its "." or ".." */
609         if (DIR_DOT_OR_DOTDOT(ep->d_name))
610             continue;
611         /* Skip ".Parent" */
612         if (STRCMP(ep->d_name, ==, ".Parent"))
613             continue;
614
615         if ((lstat(ep->d_name, &st)) < 0) {
616             dbd_log( LOGSTD, "Lost file or dir while enumeratin dir '%s/%s/%s', probably removed: %s",
617                      cwdbuf, ADv2_DIRNAME, ep->d_name, strerror(errno));
618             continue;
619         }
620
621         /* Check for dirs */
622         if (S_ISDIR(st.st_mode)) {
623             dbd_log( LOGSTD, "Unexpected directory '%s' in AppleDouble dir '%s/%s'",
624                      ep->d_name, cwdbuf, ADv2_DIRNAME);
625             continue;
626         }
627
628         /* Check if for orphaned and corrupt Extended Attributes file */
629         if (check_eafile_in_adouble(ep->d_name) != 0)
630             continue;
631
632         /* Check for data file */
633         strcpy(pname + 3, ep->d_name);
634         if ((access( pname, F_OK)) != 0) {
635             if (errno != ENOENT) {
636                 dbd_log(LOGSTD, "Access error for file '%s/%s': %s",
637                         cwdbuf, pname, strerror(errno));
638                 continue;
639             }
640             /* Orphaned ad-file*/
641             dbd_log(LOGSTD, "Orphaned AppleDoube file '%s/%s/%s'",
642                     cwdbuf, ADv2_DIRNAME, ep->d_name);
643
644             if (dbd_flags & DBD_FLAGS_SCAN)
645                 /* Scan only requested, dont change anything */
646                 continue;;
647
648             if ((unlink(ep->d_name)) != 0) {
649                 dbd_log(LOGSTD, "Error unlinking orphaned AppleDoube file '%s/%s/%s'",
650                         cwdbuf, ADv2_DIRNAME, ep->d_name);
651
652             }
653         }
654     }
655
656     if ((chdir("..")) != 0) {
657         dbd_log(LOGSTD, "Couldn't chdir back to '%s' from AppleDouble dir: %s",
658                 cwdbuf, strerror(errno));
659         /* This really is EOT! */
660         longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
661     }
662
663     closedir(dp);
664
665     return 0;
666 }
667
668 /*
669   Check CNID for a file/dir, both from db and from ad-file.
670   For detailed specs see intro.
671 */
672 static cnid_t check_cnid(const char *name, cnid_t did, struct stat *st, int adfile_ok, int adflags)
673 {
674     int ret;
675     cnid_t db_cnid, ad_cnid;
676     struct adouble ad;
677
678     /* Get CNID from ad-file if volume is using AFPVOL_CACHE */
679     ad_cnid = 0;
680     if ( (volinfo->v_flags & AFPVOL_CACHE) && ADFILE_OK) {
681         ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
682         if (ad_open_metadata( name, adflags, O_RDWR, &ad) != 0) {
683             
684             if (dbd_flags & DBD_FLAGS_CLEANUP)
685                 return 0;
686
687             dbd_log( LOGSTD, "Error opening AppleDouble file for '%s/%s': %s", cwdbuf, name, strerror(errno));
688             return 0;
689         }
690
691         if (dbd_flags & DBD_FLAGS_FORCE) {
692             ad_cnid = ad_forcegetid(&ad);
693             /* This ensures the changed stamp is written */
694             ad_setid( &ad, st->st_dev, st->st_ino, ad_cnid, did, stamp);
695             ad_flush(&ad);
696         }
697         else
698             ad_cnid = ad_getid(&ad, st->st_dev, st->st_ino, did, stamp);
699
700         if (ad_cnid == 0)
701             dbd_log( LOGSTD, "Bad CNID in adouble file of '%s/%s'", cwdbuf, name);
702         else
703             dbd_log( LOGDEBUG, "CNID from .AppleDouble file for '%s/%s': %u", cwdbuf, name, ntohl(ad_cnid));
704
705         ad_close_metadata(&ad);
706     }
707
708     /* Get CNID from database */
709
710     /* Prepare request data */
711     memset(&rqst, 0, sizeof(struct cnid_dbd_rqst));
712     memset(&rply, 0, sizeof(struct cnid_dbd_rply));
713     rqst.did = did;
714     rqst.cnid = ad_cnid;
715     if ( ! (volinfo->v_flags & AFPVOL_NODEV))
716         rqst.dev = st->st_dev;
717     rqst.ino = st->st_ino;
718     rqst.type = S_ISDIR(st->st_mode)?1:0;
719     rqst.name = (char *)name;
720     rqst.namelen = strlen(name);
721
722     /* Query the database */
723     ret = dbd_lookup(dbd, &rqst, &rply, (dbd_flags & DBD_FLAGS_SCAN) ? 1 : 0);
724     dbif_txn_close(dbd, ret);
725     if (rply.result == CNID_DBD_RES_OK) {
726         db_cnid = rply.cnid;
727     } else if (rply.result == CNID_DBD_RES_NOTFOUND) {
728         if ( ! (dbd_flags & DBD_FLAGS_FORCE))
729             dbd_log( LOGSTD, "No CNID for '%s/%s' in database", cwdbuf, name);
730         db_cnid = 0;
731     } else {
732         dbd_log( LOGSTD, "Fatal error resolving '%s/%s'", cwdbuf, name);
733         db_cnid = 0;
734     }
735
736     /* Compare results from both CNID searches */
737     if (ad_cnid && db_cnid && (ad_cnid == db_cnid)) {
738         /* Everything is fine */
739         return db_cnid;
740     } else if (ad_cnid && db_cnid && (ad_cnid != db_cnid)) {
741         /* Mismatch ? Delete both from db and re-add data from file */
742         dbd_log( LOGSTD, "CNID mismatch for '%s/%s', db: %u, ad-file: %u", cwdbuf, name, ntohl(db_cnid), ntohl(ad_cnid));
743         if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
744             rqst.cnid = db_cnid;
745             ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
746             dbif_txn_close(dbd, ret);
747
748             rqst.cnid = ad_cnid;
749             ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
750             dbif_txn_close(dbd, ret);
751
752             ret = dbd_rebuild_add(dbd, &rqst, &rply);
753             dbif_txn_close(dbd, ret);
754         }
755         return ad_cnid;
756     } else if (ad_cnid && (db_cnid == 0)) {
757         /* in ad-file but not in db */
758         if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
759             /* Ensure the cnid from the ad-file is not already occupied by another file */
760             dbd_log(LOGDEBUG, "Checking whether CNID %u from ad-file is occupied",
761                     ntohl(ad_cnid));
762
763             rqst.cnid = ad_cnid;
764             ret = dbd_resolve(dbd, &rqst, &rply);
765             if (ret == CNID_DBD_RES_OK) {
766                 /* Occupied! Choose another, update ad-file */
767                 ret = dbd_add(dbd, &rqst, &rply, 1);
768                 dbif_txn_close(dbd, ret);
769                 db_cnid = rply.cnid;
770                 dbd_log(LOGSTD, "New CNID for '%s/%s': %u", cwdbuf, name, ntohl(db_cnid));
771
772                 if ((volinfo->v_flags & AFPVOL_CACHE)
773                     && ADFILE_OK
774                     && ( ! (dbd_flags & DBD_FLAGS_SCAN))) {
775                     dbd_log(LOGSTD, "Writing CNID data for '%s/%s' to AppleDouble file",
776                             cwdbuf, name, ntohl(db_cnid));
777                     ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
778                     if (ad_open_metadata( name, adflags, O_RDWR, &ad) != 0) {
779                         dbd_log(LOGSTD, "Error opening AppleDouble file for '%s/%s': %s",
780                                 cwdbuf, name, strerror(errno));
781                         return 0;
782                     }
783                     ad_setid( &ad, st->st_dev, st->st_ino, db_cnid, did, stamp);
784                     ad_flush(&ad);
785                     ad_close_metadata(&ad);
786                 }
787                 return db_cnid;
788             }
789
790             dbd_log(LOGDEBUG, "CNID rebuild add '%s/%s' with CNID from ad-file %u",
791                     cwdbuf, name, ntohl(ad_cnid));
792             rqst.cnid = ad_cnid;
793             ret = dbd_rebuild_add(dbd, &rqst, &rply);
794             dbif_txn_close(dbd, ret);
795         }
796         return ad_cnid;
797     } else if ((db_cnid == 0) && (ad_cnid == 0)) {
798         /* No CNID at all, we clearly have to allocate a fresh one... */
799         /* Note: the next test will use this new CNID too! */
800         if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
801             /* add to db */
802             ret = dbd_add(dbd, &rqst, &rply, 1);
803             dbif_txn_close(dbd, ret);
804             db_cnid = rply.cnid;
805             dbd_log(LOGSTD, "New CNID for '%s/%s': %u", cwdbuf, name, ntohl(db_cnid));
806         }
807     }
808
809     if ((ad_cnid == 0) && db_cnid) {
810         /* in db but zeroID in ad-file, write it to ad-file if AFPVOL_CACHE */
811         if ((volinfo->v_flags & AFPVOL_CACHE) && ADFILE_OK) {
812             if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
813                 dbd_log(LOGSTD, "Writing CNID data for '%s/%s' to AppleDouble file",
814                         cwdbuf, name, ntohl(db_cnid));
815                 ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
816                 if (ad_open_metadata( name, adflags, O_RDWR, &ad) != 0) {
817                     dbd_log(LOGSTD, "Error opening AppleDouble file for '%s/%s': %s",
818                             cwdbuf, name, strerror(errno));
819                     return 0;
820                 }
821                 ad_setid( &ad, st->st_dev, st->st_ino, db_cnid, did, stamp);
822                 ad_flush(&ad);
823                 ad_close_metadata(&ad);
824             }
825         }
826         return db_cnid;
827     }
828
829     return 0;
830 }
831
832 /*
833   This is called recursively for all dirs.
834   volroot=1 means we're in the volume root dir, 0 means we aren't.
835   We use this when checking for netatalk private folders like .AppleDB.
836   did is our parents CNID.
837 */
838 static int dbd_readdir(int volroot, cnid_t did)
839 {
840     int cwd, ret = 0, adflags, adfile_ok, addir_ok, encoding_ok;
841     cnid_t cnid = 0;
842     const char *name;
843     DIR *dp;
844     struct dirent *ep;
845     static struct stat st;      /* Save some stack space */
846
847     /* Check again for .AppleDouble folder, check_adfile also checks/creates it */
848     if ((addir_ok = check_addir(volroot)) != 0)
849         if ( ! (dbd_flags & DBD_FLAGS_SCAN))
850             /* Fatal on rebuild run, continue if only scanning ! */
851             return -1;
852
853     /* Check AppleDouble files in AppleDouble folder, but only if it exists or could be created */
854     if (ADDIR_OK)
855         if ((read_addir()) != 0)
856             if ( ! (dbd_flags & DBD_FLAGS_SCAN))
857                 /* Fatal on rebuild run, continue if only scanning ! */
858                 return -1;
859
860     if ((dp = opendir (".")) == NULL) {
861         dbd_log(LOGSTD, "Couldn't open the directory: %s",strerror(errno));
862         return -1;
863     }
864
865     while ((ep = readdir (dp))) {
866         /* Check if we got a termination signal */
867         if (alarmed)
868             longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
869
870         /* Check if its "." or ".." */
871         if (DIR_DOT_OR_DOTDOT(ep->d_name))
872             continue;
873
874         /* Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop" */
875         if ((name = check_netatalk_dirs(ep->d_name)) != NULL) {
876             if (! volroot)
877                 dbd_log(LOGSTD, "Nested %s in %s", name, cwdbuf);
878             continue;
879         }
880
881         /* Check for special folders in volume root e.g. ".zfs" */
882         if (volroot) {
883             if ((name = check_special_dirs(ep->d_name)) != NULL) {
884                 dbd_log(LOGSTD, "Ignoring special dir \"%s\"", name);
885                 continue;
886             }
887         }
888
889         /* Skip .AppleDouble dir in this loop */
890         if (STRCMP(ep->d_name, == , ADv2_DIRNAME))
891             continue;
892
893         if ((ret = lstat(ep->d_name, &st)) < 0) {
894             dbd_log( LOGSTD, "Lost file while reading dir '%s/%s', probably removed: %s",
895                      cwdbuf, ep->d_name, strerror(errno));
896             continue;
897         }
898         
899         switch (st.st_mode & S_IFMT) {
900         case S_IFREG:
901             adflags = 0;
902             break;
903         case S_IFDIR:
904             adflags = ADFLAGS_DIR;
905             break;
906         case S_IFLNK:
907             dbd_log(LOGDEBUG, "Ignoring symlink %s/%s", cwdbuf, ep->d_name);
908 #if 0
909             ret = check_symlink(ep->d_name, &adflags);
910             if (ret == 1)
911                 break;
912             if (ret == -1)
913                 dbd_log(LOGSTD, "Error checking symlink %s/%s", cwdbuf, ep->d_name);
914 #endif
915             continue;
916         default:
917             dbd_log(LOGSTD, "Bad filetype: %s/%s", cwdbuf, ep->d_name);
918             if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
919                 if ((unlink(ep->d_name)) != 0) {
920                     dbd_log(LOGSTD, "Error removing: %s/%s: %s", cwdbuf, ep->d_name, strerror(errno));
921                 }
922             }
923             continue;
924         }
925
926         /**************************************************************************
927            Statistics
928          **************************************************************************/
929         static unsigned long long statcount = 0;
930         static time_t t = 0;
931
932         if (t == 0)
933             t = time(NULL);
934
935         statcount++;
936         if ((statcount % 10000) == 0) {
937             if (dbd_flags & DBD_FLAGS_STATS)            
938                 dbd_log(LOGSTD, "Scanned: %10llu, time: %10llu s",
939                         statcount, (unsigned long long)(time(NULL) - t));
940         }
941
942         /**************************************************************************
943            Tests
944         **************************************************************************/
945
946         /* Check encoding */
947         if ( -1 == (encoding_ok = check_name_encoding(ep->d_name)) ) {
948             /* If its a file: skipp all other tests now ! */
949             /* For dirs we could try to get a CNID for it and recurse, but currently I prefer not to */
950             continue;
951         }
952
953         /* Check for appledouble file, create if missing, but only if we have addir */
954         adfile_ok = -1;
955         if (ADDIR_OK)
956             adfile_ok = check_adfile(ep->d_name, &st);
957
958         if ( ! nocniddb) {
959             /* Check CNIDs */
960             cnid = check_cnid(ep->d_name, did, &st, adfile_ok, adflags);
961
962             /* Now add this object to our rebuild dbd */
963             if (cnid) {
964                 rqst.cnid = rply.cnid;
965                 ret = dbd_rebuild_add(dbd_rebuild, &rqst, &rply);
966                 dbif_txn_close(dbd_rebuild, ret);
967                 if (rply.result != CNID_DBD_RES_OK) {
968                     dbd_log( LOGDEBUG, "Fatal error adding CNID: %u for '%s/%s' to in-memory rebuild-db",
969                              cnid, cwdbuf, ep->d_name);
970                     longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
971                 }
972             }
973         }
974
975         /* Check EA files */
976         if (volinfo->v_vfs_ea == AFPVOL_EA_AD)
977             check_eafiles(ep->d_name);
978
979         /**************************************************************************
980           Recursion
981         **************************************************************************/
982         if (S_ISDIR(st.st_mode) && (cnid || nocniddb)) { /* If we have no cnid for it we cant recur */
983             strcat(cwdbuf, "/");
984             strcat(cwdbuf, ep->d_name);
985             dbd_log( LOGDEBUG, "Entering directory: %s", cwdbuf);
986             if (-1 == (cwd = open(".", O_RDONLY))) {
987                 dbd_log( LOGSTD, "Cant open directory '%s': %s", cwdbuf, strerror(errno));
988                 continue;
989             }
990             if (0 != chdir(ep->d_name)) {
991                 dbd_log( LOGSTD, "Cant chdir to directory '%s': %s", cwdbuf, strerror(errno));
992                 close(cwd);
993                 continue;
994             }
995
996             ret = dbd_readdir(0, cnid);
997
998             fchdir(cwd);
999             close(cwd);
1000             *(strrchr(cwdbuf, '/')) = 0;
1001             if (ret < 0)
1002                 continue;
1003         }
1004     }
1005
1006     /*
1007       Use results of previous checks
1008     */
1009
1010     closedir(dp);
1011     return ret;
1012 }
1013
1014 static int scanvol(struct volinfo *vi, dbd_flags_t flags)
1015 {
1016     /* Dont scanvol on no-appledouble vols */
1017     if (vi->v_flags & AFPVOL_NOADOUBLE) {
1018         dbd_log( LOGSTD, "Volume without AppleDouble support: skipping volume scanning.");
1019         return 0;
1020     }
1021
1022     /* Make this stuff accessible from all funcs easily */
1023     volinfo = vi;
1024     dbd_flags = flags;
1025
1026     /* Init a fake struct vol with just enough so we can call ea_open and friends */
1027     volume.v_adouble = AD_VERSION2;
1028     volume.v_vfs_ea = volinfo->v_vfs_ea;
1029     initvol_vfs(&volume);
1030
1031     /* Run with umask 0 */
1032     umask(0);
1033
1034     /* Remove trailing slash from volume, chdir to vol */
1035     if (volinfo->v_path[strlen(volinfo->v_path) - 1] == '/')
1036         volinfo->v_path[strlen(volinfo->v_path) - 1] = 0;
1037     strcpy(cwdbuf, volinfo->v_path);
1038     chdir(volinfo->v_path);
1039
1040     /* Start recursion */
1041     if (dbd_readdir(1, htonl(2)) < 0)  /* 2 = volumeroot CNID */
1042         return -1;
1043
1044     return 0;
1045 }
1046
1047 /*
1048   Remove all CNIDs from dbd that are not in dbd_rebuild
1049 */
1050 static void delete_orphaned_cnids(DBD *dbd, DBD *dbd_rebuild, dbd_flags_t flags)
1051 {
1052     int ret = 0, deleted = 0;
1053     cnid_t dbd_cnid = 0, rebuild_cnid = 0;
1054     struct cnid_dbd_rqst rqst;
1055     struct cnid_dbd_rply rply;
1056
1057     /* jump over rootinfo key */
1058     if ( dbif_idwalk(dbd, &dbd_cnid, 0) != 1)
1059         return;
1060     if ( dbif_idwalk(dbd_rebuild, &rebuild_cnid, 0) != 1)
1061         return;
1062
1063     /* Get first id from dbd_rebuild */
1064     if ((dbif_idwalk(dbd_rebuild, &rebuild_cnid, 0)) == -1)
1065         return;
1066
1067     /* Start main loop through dbd: get CNID from dbd */
1068     while ((dbif_idwalk(dbd, &dbd_cnid, 0)) == 1) {
1069         /* Check if we got a termination signal */
1070         if (alarmed)
1071             longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
1072
1073         if (deleted > 1000) {
1074             deleted = 0;
1075             if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0) {
1076                 dbd_log(LOGSTD, "Error checkpointing!");
1077                 goto cleanup;
1078             }
1079         }
1080
1081         /* This should be the normal case: CNID is in both dbs */
1082         if (dbd_cnid == rebuild_cnid) {
1083             /* Get next CNID from rebuild db */
1084             if ((ret = dbif_idwalk(dbd_rebuild, &rebuild_cnid, 0)) == -1) {
1085                 /* Some error */
1086                 goto cleanup;
1087             } else if (ret == 0) {
1088                 /* end of rebuild_cnid, delete all remaining CNIDs from dbd */
1089                 while ((dbif_idwalk(dbd, &dbd_cnid, 0)) == 1) {
1090                     dbd_log(LOGSTD, "Orphaned CNID in database: %u", dbd_cnid);
1091                     if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
1092                         rqst.cnid = htonl(dbd_cnid);
1093                         ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
1094                         dbif_txn_close(dbd, ret);
1095                         deleted++;
1096                     }
1097                     /* Check if we got a termination signal */
1098                     if (alarmed)
1099                         longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
1100                 }
1101                 return;
1102             } else
1103                 /* Normal case (ret=1): continue while loop */
1104                 continue;
1105         }
1106
1107         if (dbd_cnid < rebuild_cnid) {
1108             /* CNID is orphaned -> delete */
1109             dbd_log(LOGSTD, "Orphaned CNID in database: %u.", dbd_cnid);
1110             if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
1111                 rqst.cnid = htonl(dbd_cnid);
1112                 ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
1113                 dbif_txn_close(dbd, ret);
1114                 deleted++;
1115             }
1116             continue;
1117         }
1118
1119         if (dbd_cnid > rebuild_cnid) {
1120             dbd_log(LOGSTD, "Ghost CNID: %u. This is fatal! Dumping rebuild db:\n", rebuild_cnid);
1121             dbif_dump(dbd_rebuild, 0);
1122             dbd_log(LOGSTD, "Send this dump and a `dbd -d ...` dump to the Netatalk Dev team!");
1123             dbif_txn_close(dbd, ret);
1124             dbif_idwalk(dbd, NULL, 1); /* Close cursor */
1125             dbif_idwalk(dbd_rebuild, NULL, 1); /* Close cursor */
1126             goto cleanup;
1127         }
1128     }
1129
1130 cleanup:
1131     dbif_idwalk(dbd, NULL, 1); /* Close cursor */
1132     dbif_idwalk(dbd_rebuild, NULL, 1); /* Close cursor */
1133     return;
1134 }
1135
1136 static const char *get_tmpdb_path(void)
1137 {
1138     pid_t pid = getpid();
1139     static char path[MAXPATHLEN];
1140     snprintf(path, MAXPATHLEN, "/tmp/tmpdb-dbd.%u", pid);
1141     if (mkdir(path, 0755) != 0)
1142         return NULL;
1143     return path;
1144 }
1145
1146 /*
1147   Main func called from cmd_dbd.c
1148 */
1149 int cmd_dbd_scanvol(DBD *dbd_ref, struct volinfo *volinfo, dbd_flags_t flags)
1150 {
1151     int ret = 0;
1152     struct db_param db_param = { 0 };
1153     const char *tmpdb_path = NULL;
1154
1155     /* Set cachesize for in-memory rebuild db */
1156     db_param.cachesize = 128 * 1024 * 1024; /* 128 MB */
1157
1158     /* Make it accessible for all funcs */
1159     dbd = dbd_ref;
1160
1161     /* We only support unicode volumes ! */
1162     if ( volinfo->v_volcharset != CH_UTF8) {
1163         dbd_log( LOGSTD, "Not a Unicode volume: %s, %u != %u", volinfo->v_volcodepage, volinfo->v_volcharset, CH_UTF8);
1164         return -1;
1165     }
1166
1167     if (! nocniddb) {
1168         /* Get volume stamp */
1169         dbd_getstamp(dbd, &rqst, &rply);
1170         if (rply.result != CNID_DBD_RES_OK)
1171             goto exit;
1172         memcpy(stamp, rply.name, CNID_DEV_LEN);
1173
1174         /* open/create rebuild dbd, copy rootinfo key */
1175         tmpdb_path = get_tmpdb_path();
1176         if (NULL == (dbd_rebuild = dbif_init(tmpdb_path, "cnid2.db"))) {
1177             ret = -1;
1178             goto exit;
1179         }
1180
1181         if (dbif_env_open(dbd_rebuild,
1182                           &db_param,
1183                           DB_CREATE | DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_TXN) < 0) {
1184             dbd_log(LOGSTD, "error opening tmp database!");
1185             goto exit;
1186         }
1187
1188         if (0 != (dbif_open(dbd_rebuild, NULL, 0))) {
1189             ret = -1;
1190             goto exit;
1191         }
1192
1193         if (0 != (dbif_copy_rootinfokey(dbd, dbd_rebuild))) {
1194             ret = -1;
1195             goto exit;
1196         }
1197     }
1198
1199     if (setjmp(jmp) != 0) {
1200         ret = 0;                /* Got signal, jump from dbd_readdir */
1201         goto exit;              
1202     }
1203
1204     /* scanvol */
1205     if ( (scanvol(volinfo, flags)) != 0) {
1206         ret = -1;
1207         goto exit;
1208     }
1209
1210     if (! nocniddb) {
1211         /* We can only do this in exclusive mode, otherwise we might delete CNIDs added from
1212            other clients in between our pass 1 and 2 */
1213         if (flags & DBD_FLAGS_EXCL)
1214             delete_orphaned_cnids(dbd, dbd_rebuild, flags);
1215     }
1216
1217 exit:
1218     if (dbd_rebuild) {
1219         dbd_log(LOGDEBUG, "Closing tmp db");
1220         dbif_close(dbd_rebuild);
1221 #if 0
1222         if (tmpdb_path) {
1223             char cmd[8 + MAXPATHLEN];
1224             snprintf(cmd, 8 + MAXPATHLEN, "rm -f %s/*", tmpdb_path);
1225             dbd_log( LOGDEBUG, "Removing temp database '%s'", tmpdb_path);
1226             system(cmd);
1227             snprintf(cmd, 8 + MAXPATHLEN, "rmdir %s", tmpdb_path);
1228             system(cmd);
1229         }        
1230 #endif
1231     }
1232     return ret;
1233 }