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