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