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