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