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