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