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