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