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