]> arthur.barton.de Git - netatalk.git/blob - etc/cnid_dbd/cmd_dbd_scanvol.c
0ceb9e09508375fe715c6282efcbe2657a323884
[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
37 #include "cmd_dbd.h"
38 #include "dbif.h"
39 #include "db_param.h"
40 #include "dbd.h"
41
42 /* Some defines to ease code parsing */
43 #define ADDIR_OK (addir_ok == 0)
44 #define ADFILE_OK (adfile_ok == 0)
45
46 /* These must be accessible for cmd_dbd_* funcs */
47 struct volinfo        *volinfo;
48 char                  cwdbuf[MAXPATHLEN+1];
49
50 /* Some static vars */
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 ((volinfo->v_casefold & AFPVOL_UTOMUPPER))
88         flags |= CONV_TOUPPER;
89     else if ((volinfo->v_casefold & AFPVOL_UTOMLOWER))
90         flags |= CONV_TOLOWER;
91
92     if ((volinfo->v_flags & AFPVOL_EILSEQ)) {
93         flags |= CONV__EILSEQ;
94     }
95
96     /* convert charsets */
97     if ((size_t)-1 == ( outlen = convert_charset(volinfo->v_volcharset,
98                                                  CH_UTF8_MAC,
99                                                  volinfo->v_maccharset,
100                                                  u, outlen, mpath, MAXPATHLEN, &flags)) ) {
101         dbd_log( LOGSTD, "Conversion from %s to %s for %s failed.",
102                  volinfo->v_volcodepage, volinfo->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 (!(volinfo->v_flags & AFPVOL_NOHEX))
129         flags |= CONV_ESCAPEHEX;
130     if (!(volinfo->v_flags & AFPVOL_USEDOTS))
131         flags |= CONV_ESCAPEDOTS;
132
133     if ((volinfo->v_casefold & AFPVOL_MTOUUPPER))
134         flags |= CONV_TOUPPER;
135     else if ((volinfo->v_casefold & AFPVOL_MTOULOWER))
136         flags |= CONV_TOLOWER;
137
138     if ((volinfo->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                                                 volinfo->v_volcharset,
150                                                 volinfo->v_maccharset,
151                                                 m, inplen, u, outlen, &flags)) ) {
152         dbd_log( LOGSTD, "conversion from UTF8-MAC to %s for %s failed.",
153                  volinfo->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 (volinfo->v_path[i]) {
227         if ((pathbuf[i] == 0) || (volinfo->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, adflags;
297     struct adouble ad;
298     char *adname;
299
300     if (dbd_flags & DBD_FLAGS_CLEANUP)
301         return 0;
302
303     if (S_ISREG(st->st_mode))
304         adflags = 0;
305     else
306         adflags = ADFLAGS_DIR;
307
308     adname = volinfo->ad_path(fname, adflags);
309
310     if ((ret = access( adname, F_OK)) != 0) {
311         if (errno != ENOENT) {
312             dbd_log(LOGSTD, "Access error for ad-file '%s/%s': %s",
313                     cwdbuf, adname, strerror(errno));
314             return -1;
315         }
316         /* Missing. Log and create it */
317         dbd_log(LOGSTD, "Missing AppleDouble file '%s/%s'", cwdbuf, adname);
318
319         if (dbd_flags & DBD_FLAGS_SCAN)
320             /* Scan only requested, dont change anything */
321             return -1;
322
323         /* Create ad file */
324         ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
325
326         if ((ret = ad_open_metadata( fname, adflags, O_CREAT, &ad)) != 0) {
327             dbd_log( LOGSTD, "Error creating AppleDouble file '%s/%s': %s",
328                      cwdbuf, adname, strerror(errno));
329
330             return -1;
331         }
332
333         /* Set name in ad-file */
334         ad_setname(&ad, utompath((char *)fname));
335         ad_flush(&ad);
336         ad_close_metadata(&ad);
337
338         chown(adname, st->st_uid, st->st_gid);
339         /* FIXME: should we inherit mode too here ? */
340 #if 0
341         chmod(adname, st->st_mode);
342 #endif
343     } else {
344         ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
345         if (ad_open_metadata( fname, adflags, O_RDONLY, &ad) != 0) {
346             dbd_log( LOGSTD, "Error opening AppleDouble file for '%s/%s'", cwdbuf, fname);
347             return -1;
348         }
349         ad_close_metadata(&ad);
350     }
351     return 0;
352 }
353
354 /* 
355    Remove all files with file::EA* from adouble dir
356 */
357 static void remove_eafiles(const char *name, struct ea *ea)
358 {
359     DIR *dp = NULL;
360     struct dirent *ep;
361     char eaname[MAXPATHLEN];
362
363     strlcpy(eaname, name, sizeof(eaname));
364     if (strlcat(eaname, "::EA", sizeof(eaname)) >= sizeof(eaname)) {
365         dbd_log(LOGSTD, "name too long: '%s/%s/%s'", cwdbuf, ADv2_DIRNAME, name);
366         return;
367     }
368
369     if ((chdir(ADv2_DIRNAME)) != 0) {
370         dbd_log(LOGSTD, "Couldn't chdir to '%s/%s': %s",
371                 cwdbuf, ADv2_DIRNAME, strerror(errno));
372         return;
373     }
374
375     if ((dp = opendir(".")) == NULL) {
376         dbd_log(LOGSTD, "Couldn't open the directory '%s/%s': %s",
377                 cwdbuf, ADv2_DIRNAME, strerror(errno));
378         return;
379     }
380
381     while ((ep = readdir(dp))) {
382         if (strstr(ep->d_name, eaname) != NULL) {
383             dbd_log(LOGSTD, "Removing EA file: '%s/%s/%s'",
384                     cwdbuf, ADv2_DIRNAME, ep->d_name);
385             if ((unlink(ep->d_name)) != 0) {
386                 dbd_log(LOGSTD, "Error unlinking EA file '%s/%s/%s': %s",
387                         cwdbuf, ADv2_DIRNAME, ep->d_name, strerror(errno));
388             }
389         } /* if */
390     } /* while */
391
392     if (dp)
393         closedir(dp);
394
395 }
396
397 /*
398   Check Extended Attributes files
399 */
400 static int check_eafiles(const char *fname)
401 {
402     unsigned int  count = 0;
403     int ret = 0, remove;
404     struct ea ea;
405     struct stat st;
406     char *eaname;
407
408     if ((ret = ea_open(&volume, fname, EA_RDWR, &ea)) != 0) {
409         if (errno == ENOENT)
410             return 0;
411         dbd_log(LOGSTD, "Error calling ea_open for file: %s/%s, removing EA files", cwdbuf, fname);
412         if ( ! (dbd_flags & DBD_FLAGS_SCAN))
413             remove_eafiles(fname, &ea);
414         return -1;
415     }
416
417     /* Check all EAs */
418     while (count < ea.ea_count) {        
419         dbd_log(LOGDEBUG, "EA: %s", (*ea.ea_entries)[count].ea_name);
420         remove = 0;
421
422         eaname = ea_path(&ea, (*ea.ea_entries)[count].ea_name, 0);
423
424         if (lstat(eaname, &st) != 0) {
425             if (errno == ENOENT)
426                 dbd_log(LOGSTD, "Missing EA: %s/%s", cwdbuf, eaname);
427             else
428                 dbd_log(LOGSTD, "Bogus EA: %s/%s", cwdbuf, eaname);
429             remove = 1;
430         } else if (st.st_size != (*ea.ea_entries)[count].ea_size) {
431             dbd_log(LOGSTD, "Bogus EA: %s/%s, removing it...", cwdbuf, eaname);
432             remove = 1;
433             if ((unlink(eaname)) != 0)
434                 dbd_log(LOGSTD, "Error removing EA file '%s/%s': %s",
435                         cwdbuf, eaname, strerror(errno));
436         }
437
438         if (remove) {
439             /* Be CAREFUL here! This should do what ea_delentry does. ea_close relies on it !*/
440             free((*ea.ea_entries)[count].ea_name);
441             (*ea.ea_entries)[count].ea_name = NULL;
442         }
443
444         count++;
445     } /* while */
446
447     ea_close(&ea);
448     return ret;
449 }
450
451 /*
452   Check for .AppleDouble folder and .Parent, create if missing
453 */
454 static int check_addir(int volroot)
455 {
456     int addir_ok, adpar_ok;
457     struct stat st;
458     struct adouble ad;
459     char *mname = NULL;
460
461     if (dbd_flags & DBD_FLAGS_CLEANUP)
462         return 0;
463
464     /* Check for ad-dir */
465     if ( (addir_ok = access(ADv2_DIRNAME, F_OK)) != 0) {
466         if (errno != ENOENT) {
467             dbd_log(LOGSTD, "Access error in directory %s: %s", cwdbuf, strerror(errno));
468             return -1;
469         }
470         dbd_log(LOGSTD, "Missing %s for '%s'", ADv2_DIRNAME, cwdbuf);
471     }
472
473     /* Check for ".Parent" */
474     if ( (adpar_ok = access(volinfo->ad_path(".", ADFLAGS_DIR), F_OK)) != 0) {
475         if (errno != ENOENT) {
476             dbd_log(LOGSTD, "Access error on '%s/%s': %s",
477                     cwdbuf, volinfo->ad_path(".", ADFLAGS_DIR), strerror(errno));
478             return -1;
479         }
480         dbd_log(LOGSTD, "Missing .AppleDouble/.Parent for '%s'", cwdbuf);
481     }
482
483     /* Is one missing ? */
484     if ((addir_ok != 0) || (adpar_ok != 0)) {
485         /* Yes, but are we only scanning ? */
486         if (dbd_flags & DBD_FLAGS_SCAN) {
487             /* Yes:  missing .Parent is not a problem, but missing ad-dir
488                causes later checking of ad-files to fail. So we have to return appropiately */
489             if (addir_ok != 0)
490                 return -1;
491             else  /* (adpar_ok != 0) */
492                 return 0;
493         }
494
495         /* Create ad dir and set name */
496         ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
497
498         if (ad_open_metadata( ".", ADFLAGS_DIR, O_CREAT, &ad) != 0) {
499             dbd_log( LOGSTD, "Error creating AppleDouble dir in %s: %s", cwdbuf, strerror(errno));
500             return -1;
501         }
502
503         /* Get basename of cwd from cwdbuf */
504         mname = utompath(strrchr(cwdbuf, '/') + 1);
505
506         /* Update name in ad file */
507         ad_setname(&ad, mname);
508         ad_flush(&ad);
509         ad_close_metadata(&ad);
510
511         /* Inherit owner/group from "." to ".AppleDouble" and ".Parent" */
512         if ((lstat(".", &st)) != 0) {
513             dbd_log( LOGSTD, "Couldnt stat %s: %s", cwdbuf, strerror(errno));
514             return -1;
515         }
516         chown(ADv2_DIRNAME, st.st_uid, st.st_gid);
517         chown(volinfo->ad_path(".", ADFLAGS_DIR), st.st_uid, st.st_gid);
518     }
519
520     return 0;
521 }
522
523 /*
524   Check if file cotains "::EA" and if it does check if its correspondig data fork exists.
525   Returns:
526   0 = name is not an EA file
527   1 = name is an EA file and no problem was found
528   -1 = name is an EA file and data fork is gone
529  */
530 static int check_eafile_in_adouble(const char *name)
531 {
532     int ret = 0;
533     char *namep, *namedup = NULL;
534
535     /* Check if this is an AFPVOL_EA_AD vol */
536     if (volinfo->v_vfs_ea == AFPVOL_EA_AD) {
537         /* Does the filename contain "::EA" ? */
538         namedup = strdup(name);
539         if ((namep = strstr(namedup, "::EA")) == NULL) {
540             ret = 0;
541             goto ea_check_done;
542         } else {
543             /* File contains "::EA" so it's an EA file. Check for data file  */
544
545             /* Get string before "::EA" from EA filename */
546             namep[0] = 0;
547             strlcpy(pname + 3, namedup, sizeof(pname)); /* Prepends "../" */
548
549             if ((access( pname, F_OK)) == 0) {
550                 ret = 1;
551                 goto ea_check_done;
552             } else {
553                 ret = -1;
554                 if (errno != ENOENT) {
555                     dbd_log(LOGSTD, "Access error for file '%s/%s': %s",
556                             cwdbuf, name, strerror(errno));
557                     goto ea_check_done;
558                 }
559
560                 /* Orphaned EA file*/
561                 dbd_log(LOGSTD, "Orphaned Extended Attribute file '%s/%s/%s'",
562                         cwdbuf, ADv2_DIRNAME, name);
563
564                 if (dbd_flags & DBD_FLAGS_SCAN)
565                     /* Scan only requested, dont change anything */
566                     goto ea_check_done;
567
568                 if ((unlink(name)) != 0) {
569                     dbd_log(LOGSTD, "Error unlinking orphaned Extended Attribute file '%s/%s/%s'",
570                             cwdbuf, ADv2_DIRNAME, name);
571                 }
572             } /* if (access) */
573         } /* if strstr */
574     } /* if AFPVOL_EA_AD */
575
576 ea_check_done:
577     if (namedup)
578         free(namedup);
579
580     return ret;
581 }
582
583 /*
584   Check files and dirs inside .AppleDouble folder:
585   - remove orphaned files
586   - bail on dirs
587 */
588 static int read_addir(void)
589 {
590     DIR *dp;
591     struct dirent *ep;
592     struct stat st;
593
594     if ((chdir(ADv2_DIRNAME)) != 0) {
595         dbd_log(LOGSTD, "Couldn't chdir to '%s/%s': %s",
596                 cwdbuf, ADv2_DIRNAME, strerror(errno));
597         return -1;
598     }
599
600     if ((dp = opendir(".")) == NULL) {
601         dbd_log(LOGSTD, "Couldn't open the directory '%s/%s': %s",
602                 cwdbuf, ADv2_DIRNAME, strerror(errno));
603         return -1;
604     }
605
606     while ((ep = readdir(dp))) {
607         /* Check if its "." or ".." */
608         if (DIR_DOT_OR_DOTDOT(ep->d_name))
609             continue;
610         /* Skip ".Parent" */
611         if (STRCMP(ep->d_name, ==, ".Parent"))
612             continue;
613
614         if ((lstat(ep->d_name, &st)) < 0) {
615             dbd_log( LOGSTD, "Lost file or dir while enumeratin dir '%s/%s/%s', probably removed: %s",
616                      cwdbuf, ADv2_DIRNAME, ep->d_name, strerror(errno));
617             continue;
618         }
619
620         /* Check for dirs */
621         if (S_ISDIR(st.st_mode)) {
622             dbd_log( LOGSTD, "Unexpected directory '%s' in AppleDouble dir '%s/%s'",
623                      ep->d_name, cwdbuf, ADv2_DIRNAME);
624             continue;
625         }
626
627         /* Check if for orphaned and corrupt Extended Attributes file */
628         if (check_eafile_in_adouble(ep->d_name) != 0)
629             continue;
630
631         /* Check for data file */
632         strcpy(pname + 3, ep->d_name);
633         if ((access( pname, F_OK)) != 0) {
634             if (errno != ENOENT) {
635                 dbd_log(LOGSTD, "Access error for file '%s/%s': %s",
636                         cwdbuf, pname, strerror(errno));
637                 continue;
638             }
639             /* Orphaned ad-file*/
640             dbd_log(LOGSTD, "Orphaned AppleDoube file '%s/%s/%s'",
641                     cwdbuf, ADv2_DIRNAME, ep->d_name);
642
643             if (dbd_flags & DBD_FLAGS_SCAN)
644                 /* Scan only requested, dont change anything */
645                 continue;;
646
647             if ((unlink(ep->d_name)) != 0) {
648                 dbd_log(LOGSTD, "Error unlinking orphaned AppleDoube file '%s/%s/%s'",
649                         cwdbuf, ADv2_DIRNAME, ep->d_name);
650
651             }
652         }
653     }
654
655     if ((chdir("..")) != 0) {
656         dbd_log(LOGSTD, "Couldn't chdir back to '%s' from AppleDouble dir: %s",
657                 cwdbuf, strerror(errno));
658         /* This really is EOT! */
659         longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
660     }
661
662     closedir(dp);
663
664     return 0;
665 }
666
667 /*
668   Check CNID for a file/dir, both from db and from ad-file.
669   For detailed specs see intro.
670 */
671 static cnid_t check_cnid(const char *name, cnid_t did, struct stat *st, int adfile_ok, int adflags)
672 {
673     int ret;
674     cnid_t db_cnid, ad_cnid;
675     struct adouble ad;
676
677     /* Get CNID from ad-file if volume is using AFPVOL_CACHE */
678     ad_cnid = 0;
679     if ( (volinfo->v_flags & AFPVOL_CACHE) && ADFILE_OK) {
680         ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
681         if (ad_open_metadata( name, adflags, O_RDWR, &ad) != 0) {
682             
683             if (dbd_flags & DBD_FLAGS_CLEANUP)
684                 return 0;
685
686             dbd_log( LOGSTD, "Error opening AppleDouble file for '%s/%s': %s", cwdbuf, name, strerror(errno));
687             return 0;
688         }
689
690         if (dbd_flags & DBD_FLAGS_FORCE) {
691             ad_cnid = ad_forcegetid(&ad);
692             /* This ensures the changed stamp is written */
693             ad_setid( &ad, st->st_dev, st->st_ino, ad_cnid, did, stamp);
694             ad_flush(&ad);
695         }
696         else
697             ad_cnid = ad_getid(&ad, st->st_dev, st->st_ino, did, stamp);
698
699         if (ad_cnid == 0)
700             dbd_log( LOGSTD, "Bad CNID in adouble file of '%s/%s'", cwdbuf, name);
701         else
702             dbd_log( LOGDEBUG, "CNID from .AppleDouble file for '%s/%s': %u", cwdbuf, name, ntohl(ad_cnid));
703
704         ad_close_metadata(&ad);
705     }
706
707     /* Get CNID from database */
708
709     /* Prepare request data */
710     memset(&rqst, 0, sizeof(struct cnid_dbd_rqst));
711     memset(&rply, 0, sizeof(struct cnid_dbd_rply));
712     rqst.did = did;
713     rqst.cnid = ad_cnid;
714     if ( ! (volinfo->v_flags & AFPVOL_NODEV))
715         rqst.dev = st->st_dev;
716     rqst.ino = st->st_ino;
717     rqst.type = S_ISDIR(st->st_mode)?1:0;
718     rqst.name = (char *)name;
719     rqst.namelen = strlen(name);
720
721     /* Query the database */
722     ret = dbd_lookup(dbd, &rqst, &rply, (dbd_flags & DBD_FLAGS_SCAN) ? 1 : 0);
723     dbif_txn_close(dbd, ret);
724     if (rply.result == CNID_DBD_RES_OK) {
725         db_cnid = rply.cnid;
726     } else if (rply.result == CNID_DBD_RES_NOTFOUND) {
727         if ( ! (dbd_flags & DBD_FLAGS_FORCE))
728             dbd_log( LOGSTD, "No CNID for '%s/%s' in database", cwdbuf, name);
729         db_cnid = 0;
730     } else {
731         dbd_log( LOGSTD, "Fatal error resolving '%s/%s'", cwdbuf, name);
732         db_cnid = 0;
733     }
734
735     /* Compare results from both CNID searches */
736     if (ad_cnid && db_cnid && (ad_cnid == db_cnid)) {
737         /* Everything is fine */
738         return db_cnid;
739     } else if (ad_cnid && db_cnid && (ad_cnid != db_cnid)) {
740         /* Mismatch ? Delete both from db and re-add data from file */
741         dbd_log( LOGSTD, "CNID mismatch for '%s/%s', db: %u, ad-file: %u", cwdbuf, name, ntohl(db_cnid), ntohl(ad_cnid));
742         if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
743             rqst.cnid = db_cnid;
744             ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
745             dbif_txn_close(dbd, ret);
746
747             rqst.cnid = ad_cnid;
748             ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
749             dbif_txn_close(dbd, ret);
750
751             ret = dbd_rebuild_add(dbd, &rqst, &rply);
752             dbif_txn_close(dbd, ret);
753         }
754         return ad_cnid;
755     } else if (ad_cnid && (db_cnid == 0)) {
756         /* in ad-file but not in db */
757         if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
758             /* Ensure the cnid from the ad-file is not already occupied by another file */
759             dbd_log(LOGDEBUG, "Checking whether CNID %u from ad-file is occupied",
760                     ntohl(ad_cnid));
761
762             rqst.cnid = ad_cnid;
763             ret = dbd_resolve(dbd, &rqst, &rply);
764             if (ret == CNID_DBD_RES_OK) {
765                 /* Occupied! Choose another, update ad-file */
766                 ret = dbd_add(dbd, &rqst, &rply, 1);
767                 dbif_txn_close(dbd, ret);
768                 db_cnid = rply.cnid;
769                 dbd_log(LOGSTD, "New CNID for '%s/%s': %u", cwdbuf, name, ntohl(db_cnid));
770
771                 if ((volinfo->v_flags & AFPVOL_CACHE)
772                     && ADFILE_OK
773                     && ( ! (dbd_flags & DBD_FLAGS_SCAN))) {
774                     dbd_log(LOGSTD, "Writing CNID data for '%s/%s' to AppleDouble file",
775                             cwdbuf, name, ntohl(db_cnid));
776                     ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
777                     if (ad_open_metadata( name, adflags, O_RDWR, &ad) != 0) {
778                         dbd_log(LOGSTD, "Error opening AppleDouble file for '%s/%s': %s",
779                                 cwdbuf, name, strerror(errno));
780                         return 0;
781                     }
782                     ad_setid( &ad, st->st_dev, st->st_ino, db_cnid, did, stamp);
783                     ad_flush(&ad);
784                     ad_close_metadata(&ad);
785                 }
786                 return db_cnid;
787             }
788
789             dbd_log(LOGDEBUG, "CNID rebuild add '%s/%s' with CNID from ad-file %u",
790                     cwdbuf, name, ntohl(ad_cnid));
791             rqst.cnid = ad_cnid;
792             ret = dbd_rebuild_add(dbd, &rqst, &rply);
793             dbif_txn_close(dbd, ret);
794         }
795         return ad_cnid;
796     } else if ((db_cnid == 0) && (ad_cnid == 0)) {
797         /* No CNID at all, we clearly have to allocate a fresh one... */
798         /* Note: the next test will use this new CNID too! */
799         if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
800             /* add to db */
801             ret = dbd_add(dbd, &rqst, &rply, 1);
802             dbif_txn_close(dbd, ret);
803             db_cnid = rply.cnid;
804             dbd_log(LOGSTD, "New CNID for '%s/%s': %u", cwdbuf, name, ntohl(db_cnid));
805         }
806     }
807
808     if ((ad_cnid == 0) && db_cnid) {
809         /* in db but zeroID in ad-file, write it to ad-file if AFPVOL_CACHE */
810         if ((volinfo->v_flags & AFPVOL_CACHE) && ADFILE_OK) {
811             if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
812                 dbd_log(LOGSTD, "Writing CNID data for '%s/%s' to AppleDouble file",
813                         cwdbuf, name, ntohl(db_cnid));
814                 ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
815                 if (ad_open_metadata( name, adflags, O_RDWR, &ad) != 0) {
816                     dbd_log(LOGSTD, "Error opening AppleDouble file for '%s/%s': %s",
817                             cwdbuf, name, strerror(errno));
818                     return 0;
819                 }
820                 ad_setid( &ad, st->st_dev, st->st_ino, db_cnid, did, stamp);
821                 ad_flush(&ad);
822                 ad_close_metadata(&ad);
823             }
824         }
825         return db_cnid;
826     }
827
828     return 0;
829 }
830
831 /*
832   This is called recursively for all dirs.
833   volroot=1 means we're in the volume root dir, 0 means we aren't.
834   We use this when checking for netatalk private folders like .AppleDB.
835   did is our parents CNID.
836 */
837 static int dbd_readdir(int volroot, cnid_t did)
838 {
839     int cwd, ret = 0, adflags, adfile_ok, addir_ok, encoding_ok;
840     cnid_t cnid = 0;
841     const char *name;
842     DIR *dp;
843     struct dirent *ep;
844     static struct stat st;      /* Save some stack space */
845
846     /* Check again for .AppleDouble folder, check_adfile also checks/creates it */
847     if ((addir_ok = check_addir(volroot)) != 0)
848         if ( ! (dbd_flags & DBD_FLAGS_SCAN))
849             /* Fatal on rebuild run, continue if only scanning ! */
850             return -1;
851
852     /* Check AppleDouble files in AppleDouble folder, but only if it exists or could be created */
853     if (ADDIR_OK)
854         if ((read_addir()) != 0)
855             if ( ! (dbd_flags & DBD_FLAGS_SCAN))
856                 /* Fatal on rebuild run, continue if only scanning ! */
857                 return -1;
858
859     if ((dp = opendir (".")) == NULL) {
860         dbd_log(LOGSTD, "Couldn't open the directory: %s",strerror(errno));
861         return -1;
862     }
863
864     while ((ep = readdir (dp))) {
865         /* Check if we got a termination signal */
866         if (alarmed)
867             longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
868
869         /* Check if its "." or ".." */
870         if (DIR_DOT_OR_DOTDOT(ep->d_name))
871             continue;
872
873         /* Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop" */
874         if ((name = check_netatalk_dirs(ep->d_name)) != NULL) {
875             if (! volroot)
876                 dbd_log(LOGSTD, "Nested %s in %s", name, cwdbuf);
877             continue;
878         }
879
880         /* Check for special folders in volume root e.g. ".zfs" */
881         if (volroot) {
882             if ((name = check_special_dirs(ep->d_name)) != NULL)
883                 dbd_log(LOGSTD, "Ignoring special dir \"%s\"", name);
884             continue;
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 (volinfo->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     volinfo = 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 = volinfo->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 (volinfo->v_path[strlen(volinfo->v_path) - 1] == '/')
1017         volinfo->v_path[strlen(volinfo->v_path) - 1] = 0;
1018     strcpy(cwdbuf, volinfo->v_path);
1019     chdir(volinfo->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
1051         if (deleted > 50) {
1052             deleted = 0;
1053             if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0) {
1054                 dbd_log(LOGSTD, "Error checkpointing!");
1055                 goto cleanup;
1056             }
1057         }
1058
1059         /* This should be the normal case: CNID is in both dbs */
1060         if (dbd_cnid == rebuild_cnid) {
1061             /* Get next CNID from rebuild db */
1062             if ((ret = dbif_idwalk(dbd_rebuild, &rebuild_cnid, 0)) == -1) {
1063                 /* Some error */
1064                 goto cleanup;
1065             } else if (ret == 0) {
1066                 /* end of rebuild_cnid, delete all remaining CNIDs from dbd */
1067                 while ((dbif_idwalk(dbd, &dbd_cnid, 0)) == 1) {
1068                     dbd_log(LOGSTD, "Orphaned CNID in database: %u", dbd_cnid);
1069                     if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
1070                         rqst.cnid = htonl(dbd_cnid);
1071                         ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
1072                         dbif_txn_close(dbd, ret);
1073                         deleted++;
1074                     }
1075                 }
1076                 return;
1077             } else
1078                 /* Normal case (ret=1): continue while loop */
1079                 continue;
1080         }
1081
1082         if (dbd_cnid < rebuild_cnid) {
1083             /* CNID is orphaned -> delete */
1084             dbd_log(LOGSTD, "Orphaned CNID in database: %u.", dbd_cnid);
1085             if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
1086                 rqst.cnid = htonl(dbd_cnid);
1087                 ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
1088                 dbif_txn_close(dbd, ret);
1089                 deleted++;
1090             }
1091             continue;
1092         }
1093
1094         if (dbd_cnid > rebuild_cnid) {
1095             dbd_log(LOGSTD, "Ghost CNID: %u. This is fatal! Dumping rebuild db:\n", rebuild_cnid);
1096             dbif_dump(dbd_rebuild, 0);
1097             dbd_log(LOGSTD, "Send this dump and a `dbd -d ...` dump to the Netatalk Dev team!");
1098             dbif_txn_close(dbd, ret);
1099             dbif_idwalk(dbd, NULL, 1); /* Close cursor */
1100             dbif_idwalk(dbd_rebuild, NULL, 1); /* Close cursor */
1101             goto cleanup;
1102         }
1103     }
1104
1105 cleanup:
1106     dbif_idwalk(dbd, NULL, 1); /* Close cursor */
1107     dbif_idwalk(dbd_rebuild, NULL, 1); /* Close cursor */
1108     return;
1109 }
1110
1111 /*
1112   Main func called from cmd_dbd.c
1113 */
1114 int cmd_dbd_scanvol(DBD *dbd_ref, struct volinfo *volinfo, dbd_flags_t flags)
1115 {
1116     int ret = 0;
1117     struct db_param db_param = { 0 };
1118
1119     /* Set cachesize for in-memory rebuild db */
1120     db_param.cachesize = 128 * 1024 * 1024; /* 128 MB */
1121
1122     /* Make it accessible for all funcs */
1123     dbd = dbd_ref;
1124
1125     /* We only support unicode volumes ! */
1126     if ( volinfo->v_volcharset != CH_UTF8) {
1127         dbd_log( LOGSTD, "Not a Unicode volume: %s, %u != %u", volinfo->v_volcodepage, volinfo->v_volcharset, CH_UTF8);
1128         return -1;
1129     }
1130
1131     if (! nocniddb) {
1132         /* Get volume stamp */
1133         dbd_getstamp(dbd, &rqst, &rply);
1134         if (rply.result != CNID_DBD_RES_OK)
1135             goto exit_cleanup;
1136         memcpy(stamp, rply.name, CNID_DEV_LEN);
1137
1138         /* open/create rebuild dbd, copy rootinfo key */
1139         if (NULL == (dbd_rebuild = dbif_init(NULL, NULL)))
1140             return -1;
1141         if (0 != (dbif_open(dbd_rebuild, &db_param, 0)))
1142             return -1;
1143         if (0 != (dbif_copy_rootinfokey(dbd, dbd_rebuild)))
1144             goto exit_cleanup;
1145     }
1146
1147     if (setjmp(jmp) != 0)
1148         goto exit_cleanup;      /* Got signal, jump from dbd_readdir */
1149
1150     /* scanvol */
1151     if ( (scanvol(volinfo, flags)) != 0)
1152         return -1;
1153
1154     if (! nocniddb) {
1155         /* We can only do this in exclusive mode, otherwise we might delete CNIDs added from
1156            other clients in between our pass 1 and 2 */
1157         if (flags & DBD_FLAGS_EXCL)
1158             delete_orphaned_cnids(dbd, dbd_rebuild, flags);
1159     }
1160
1161 exit_cleanup:
1162     if (! nocniddb)
1163         dbif_close(dbd_rebuild);
1164     return ret;
1165 }