]> arthur.barton.de Git - netatalk.git/blob - etc/cnid_dbd/cmd_dbd_scanvol.c
Change CNID updating logic in case of inode reusage. See:
[netatalk.git] / etc / cnid_dbd / cmd_dbd_scanvol.c
1 /*
2   $Id: cmd_dbd_scanvol.c,v 1.7 2009-07-12 09:21:34 franklahm Exp $
3
4   Copyright (c) 2009 Frank Lahm <franklahm@gmail.com>
5
6   This program is free software; you can redistribute it and/or modify
7   it under the terms of the GNU General Public License as published by
8   the Free Software Foundation; either version 2 of the License, or
9   (at your option) any later version.
10
11   This program is distributed in the hope that it will be useful,
12   but WITHOUT ANY WARRANTY; without even the implied warranty of
13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   GNU General Public License for more details.
15 */
16
17 #ifdef HAVE_CONFIG_H
18 #include "config.h"
19 #endif /* HAVE_CONFIG_H */
20
21 #include <unistd.h>
22 #include <stdlib.h>
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <dirent.h>
26 #include <fcntl.h>
27 #include <string.h>
28 #include <errno.h>
29 #include <setjmp.h>
30 #include <atalk/adouble.h>
31 #include <atalk/unicode.h>
32 #include <atalk/volinfo.h>
33 #include <atalk/cnid_dbd_private.h>
34 #include "cmd_dbd.h"
35 #include "dbif.h"
36 #include "db_param.h"
37 #include "dbd.h"
38
39 /* Some defines to ease code parsing */
40 #define ADDIR_OK (addir_ok == 0)
41 #define ADFILE_OK (adfile_ok == 0)
42
43 /* These must be accessible for cmd_dbd_* funcs */
44 struct volinfo        *volinfo;
45 char                  cwdbuf[MAXPATHLEN+1];
46
47 /* Some static vars */
48 static DBD            *dbd;
49 static DBD            *dbd_rebuild;
50 static dbd_flags_t    dbd_flags;
51 static char           stamp[CNID_DEV_LEN];
52 static char           *netatalk_dirs[] = {
53     ".AppleDB",
54     ".AppleDesktop",
55     NULL
56 };
57 static struct cnid_dbd_rqst rqst;
58 static struct cnid_dbd_rply rply;
59 static jmp_buf jmp;
60
61 /*
62   Taken form afpd/desktop.c
63 */
64 static char *utompath(char *upath)
65 {
66     static char  mpath[ MAXPATHLEN + 2]; /* for convert_charset dest_len parameter +2 */
67     char         *m, *u;
68     uint16_t     flags = CONV_IGNORE | CONV_UNESCAPEHEX;
69     size_t       outlen;
70
71     if (!upath)
72         return NULL;
73
74     m = mpath;
75     u = upath;
76     outlen = strlen(upath);
77
78     if ((volinfo->v_casefold & AFPVOL_UTOMUPPER))
79         flags |= CONV_TOUPPER;
80     else if ((volinfo->v_casefold & AFPVOL_UTOMLOWER))
81         flags |= CONV_TOLOWER;
82
83     if ((volinfo->v_flags & AFPVOL_EILSEQ)) {
84         flags |= CONV__EILSEQ;
85     }
86
87     /* convert charsets */
88     if ((size_t)-1 == ( outlen = convert_charset(volinfo->v_volcharset,
89                                                  CH_UTF8_MAC,
90                                                  volinfo->v_maccharset,
91                                                  u, outlen, mpath, MAXPATHLEN, &flags)) ) {
92         dbd_log( LOGSTD, "Conversion from %s to %s for %s failed.",
93                  volinfo->v_volcodepage, volinfo->v_maccodepage, u);
94         return NULL;
95     }
96
97     return(m);
98 }
99
100 /*
101   Taken form afpd/desktop.c
102 */
103 static char *mtoupath(char *mpath)
104 {
105     static char  upath[ MAXPATHLEN + 2]; /* for convert_charset dest_len parameter +2 */
106     char    *m, *u;
107     size_t       inplen;
108     size_t       outlen;
109     u_int16_t    flags = 0;
110
111     if (!mpath)
112         return NULL;
113
114     if ( *mpath == '\0' ) {
115         return( "." );
116     }
117
118     /* set conversion flags */
119     if (!(volinfo->v_flags & AFPVOL_NOHEX))
120         flags |= CONV_ESCAPEHEX;
121     if (!(volinfo->v_flags & AFPVOL_USEDOTS))
122         flags |= CONV_ESCAPEDOTS;
123
124     if ((volinfo->v_casefold & AFPVOL_MTOUUPPER))
125         flags |= CONV_TOUPPER;
126     else if ((volinfo->v_casefold & AFPVOL_MTOULOWER))
127         flags |= CONV_TOLOWER;
128
129     if ((volinfo->v_flags & AFPVOL_EILSEQ)) {
130         flags |= CONV__EILSEQ;
131     }
132
133     m = mpath;
134     u = upath;
135
136     inplen = strlen(m);
137     outlen = MAXPATHLEN;
138
139     if ((size_t)-1 == (outlen = convert_charset(CH_UTF8_MAC,
140                                                 volinfo->v_volcharset,
141                                                 volinfo->v_maccharset,
142                                                 m, inplen, u, outlen, &flags)) ) {
143         dbd_log( LOGSTD, "conversion from UTF8-MAC to %s for %s failed.",
144                  volinfo->v_volcodepage, mpath);
145         return NULL;
146     }
147
148     return( upath );
149 }
150
151 /*
152   Check for wrong encoding e.g. "." at the beginning is not CAP encoded (:2e) although volume is default !AFPVOL_USEDOTS.
153   We do it by roundtripiping from volcharset to UTF8-MAC and back and then compare the result.
154 */
155 static int check_name_encoding(char *uname)
156 {
157     char *roundtripped;
158
159     roundtripped = mtoupath(utompath(uname));
160     if (!roundtripped) {
161         dbd_log( LOGSTD, "Error checking encoding for '%s/%s'", cwdbuf, uname);
162         return -1;
163     }
164
165     if ( STRCMP(uname, !=, roundtripped)) {
166         dbd_log( LOGSTD, "Bad encoding for '%s/%s'", cwdbuf, uname);
167         return -1;
168     }
169
170     return 0;
171 }
172
173 /*
174   Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop"
175   Returns pointer to name or NULL.
176 */
177 static const char *check_netatalk_dirs(const char *name)
178 {
179     int c;
180
181     for (c=0; netatalk_dirs[c]; c++) {
182         if ((strcmp(name, netatalk_dirs[c])) == 0)
183             return netatalk_dirs[c];
184     }
185     return NULL;
186 }
187
188 /*
189   Check for .AppleDouble file, create if missing
190 */
191 static int check_adfile(const char *fname, const struct stat *st)
192 {
193     int ret, adflags;
194     struct adouble ad;
195     char *adname;
196
197     if (S_ISREG(st->st_mode))
198         adflags = 0;
199     else
200         adflags = ADFLAGS_DIR;
201
202     adname = volinfo->ad_path(fname, adflags);
203
204     if ((ret = access( adname, F_OK)) != 0) {
205         if (errno != ENOENT) {
206             dbd_log(LOGSTD, "Access error for ad-file '%s/%s': %s",
207                     cwdbuf, adname, strerror(errno));
208             return -1;
209         }
210         /* Missing. Log and create it */
211         dbd_log(LOGSTD, "Missing AppleDoube file '%s/%s'", cwdbuf, adname);
212
213         if (dbd_flags & DBD_FLAGS_SCAN)
214             /* Scan only requested, dont change anything */
215             return -1;
216
217         /* Create ad file */
218         ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
219
220         if ((ret = ad_open_metadata( fname, adflags, O_CREAT, &ad)) != 0) {
221             dbd_log( LOGSTD, "Error creating AppleDouble file '%s/%s': %s",
222                      cwdbuf, adname, strerror(errno));
223             return -1;
224         }
225
226         /* Set name in ad-file */
227         ad_setname(&ad, utompath((char *)fname));
228         ad_flush(&ad);
229         ad_close_metadata(&ad);
230
231         chown(adname, st->st_uid, st->st_gid);
232         /* FIXME: should we inherit mode too here ? */
233 #if 0
234         chmod(adname, st->st_mode);
235 #endif
236     }
237     return 0;
238 }
239
240 /*
241   Check for .AppleDouble folder and .Parent, create if missing
242 */
243 static int check_addir(int volroot)
244 {
245     int addir_ok, adpar_ok;
246     struct stat st;
247     struct adouble ad;
248
249     /* Check for ad-dir */
250     if ( (addir_ok = access(ADv2_DIRNAME, F_OK)) != 0) {
251         if (errno != ENOENT) {
252             dbd_log(LOGSTD, "Access error in directory %s: %s", cwdbuf, strerror(errno));
253             return -1;
254         }
255         dbd_log(LOGSTD, "Missing %s for '%s'", ADv2_DIRNAME, cwdbuf);
256     }
257
258     /* Check for ".Parent" */
259     if ( (adpar_ok = access(volinfo->ad_path(".", ADFLAGS_DIR), F_OK)) != 0) {
260         if (errno != ENOENT) {
261             dbd_log(LOGSTD, "Access error on '%s/%s': %s",
262                     cwdbuf, volinfo->ad_path(".", ADFLAGS_DIR), strerror(errno));
263             return -1;
264         }
265         dbd_log(LOGSTD, "Missing .AppleDouble/.Parent for '%s'", cwdbuf);
266     }
267
268     /* Is one missing ? */
269     if ((addir_ok != 0) || (adpar_ok != 0)) {
270         /* Yes, but are we only scanning ? */
271         if (dbd_flags & DBD_FLAGS_SCAN) {
272             /* Yes:  missing .Parent is not a problem, but missing ad-dir
273                causes later checking of ad-files to fail. So we have to return appropiately */
274             if (addir_ok != 0)
275                 return -1;
276             else  /* (adpar_ok != 0) */
277                 return 0;
278         }
279
280         /* Create ad dir and set name */
281         ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
282
283         if (ad_open_metadata( ".", ADFLAGS_DIR, O_CREAT, &ad) != 0) {
284             dbd_log( LOGSTD, "Error creating AppleDouble dir in %s: %s", cwdbuf, strerror(errno));
285             return -1;
286         }
287
288         /* Get basename of cwd from cwdbuf */
289         char *mname = utompath(strrchr(cwdbuf, '/') + 1);
290
291         /* Update name in ad file */
292         ad_setname(&ad, mname);
293         ad_flush(&ad);
294         ad_close_metadata(&ad);
295
296         /* Inherit owner/group from "." to ".AppleDouble" and ".Parent" */
297         if ((stat(".", &st)) != 0) {
298             dbd_log( LOGSTD, "Couldnt stat %s: %s", cwdbuf, strerror(errno));
299             return -1;
300         }
301         chown(ADv2_DIRNAME, st.st_uid, st.st_gid);
302         chown(volinfo->ad_path(".", ADFLAGS_DIR), st.st_uid, st.st_gid);
303     }
304
305     return 0;
306 }
307
308 /* 
309   Check files and dirs inside .AppleDouble folder:
310   - remove orphaned files
311   - bail on dirs
312 */
313 static int read_addir(void)
314 {
315     DIR *dp;
316     struct dirent *ep;
317     struct stat st;
318     static char fname[MAXPATHLEN] = "../";
319
320     if ((chdir(ADv2_DIRNAME)) != 0) {
321         dbd_log(LOGSTD, "Couldn't chdir to '%s/%s': %s",
322                 cwdbuf, ADv2_DIRNAME, strerror(errno));
323         return -1;
324     }
325
326     if ((dp = opendir(".")) == NULL) {
327         dbd_log(LOGSTD, "Couldn't open the directory '%s/%s': %s",
328                 cwdbuf, ADv2_DIRNAME, strerror(errno));
329         return -1;
330     }
331
332     while ((ep = readdir(dp))) {
333         /* Check if its "." or ".." */
334         if (DIR_DOT_OR_DOTDOT(ep->d_name))
335             continue;
336         /* Skip ".Parent" */
337         if (STRCMP(ep->d_name, ==, ".Parent"))
338             continue;
339
340         if ((stat(ep->d_name, &st)) < 0) {
341             dbd_log( LOGSTD, "Lost file or dir while enumeratin dir '%s/%s/%s', probably removed: %s",
342                      cwdbuf, ADv2_DIRNAME, ep->d_name, strerror(errno));
343             continue;
344         }
345
346         /* Check for dirs */
347         if (S_ISDIR(st.st_mode)) {
348             dbd_log( LOGSTD, "Unexpected directory '%s' in AppleDouble dir '%s/%s'",
349                      ep->d_name, cwdbuf, ADv2_DIRNAME);
350             continue;
351         }
352
353         /* Check for data file */
354         strcpy(fname+3, ep->d_name);
355         if ((access( fname, F_OK)) != 0) {
356             if (errno != ENOENT) {
357                 dbd_log(LOGSTD, "Access error for file '%s/%s': %s",
358                         cwdbuf, ep->d_name, strerror(errno));
359                 continue;
360             }
361             /* Orphaned ad-file*/
362             dbd_log(LOGSTD, "Orphaned AppleDoube file '%s/%s/%s'",
363                     cwdbuf, ADv2_DIRNAME, ep->d_name);
364
365             if (dbd_flags & DBD_FLAGS_SCAN)
366                 /* Scan only requested, dont change anything */
367                 continue;;
368
369             if ((unlink(ep->d_name)) != 0) {
370                 dbd_log(LOGSTD, "Error unlinking orphaned AppleDoube file '%s/%s/%s'",
371                         cwdbuf, ADv2_DIRNAME, ep->d_name);
372
373             }
374         }
375     }
376
377     if ((chdir("..")) != 0) {
378         dbd_log(LOGSTD, "Couldn't chdir back to '%s' from AppleDouble dir: %s",
379                 cwdbuf, strerror(errno));
380         /* This really is EOT! */
381         exit(1);
382     }
383
384     closedir(dp);
385
386     return 0;
387 }
388
389 /* 
390    Check CNID for a file/dir, both from db and from ad-file.
391    For detailed specs see intro.
392 */
393 static cnid_t check_cnid(const char *name, cnid_t did, struct stat *st, int adfile_ok, int adflags)
394 {
395     int ret;
396     cnid_t db_cnid, ad_cnid;
397     struct adouble ad;
398
399     /* Get CNID from ad-file if volume is using AFPVOL_CACHE */
400     ad_cnid = 0;
401     if ( (volinfo->v_flags & AFPVOL_CACHE) && ADFILE_OK) {
402         ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
403         if (ad_open_metadata( name, adflags, O_RDWR, &ad) != 0) {
404             dbd_log( LOGSTD, "Error opening AppleDouble file for '%s/%s': %s", cwdbuf, name, strerror(errno));
405             return 0;
406         }
407
408         if (dbd_flags & DBD_FLAGS_FORCE) {
409             ad_cnid = ad_forcegetid(&ad);
410             /* This ensures the changed stamp is written */
411             ad_setid( &ad, st->st_dev, st->st_ino, ad_cnid, did, stamp);
412             ad_flush(&ad);
413         }
414         else
415             ad_cnid = ad_getid(&ad, st->st_dev, st->st_ino, did, stamp);            
416
417         if (ad_cnid == 0)
418             dbd_log( LOGSTD, "Incorrect CNID data in .AppleDouble data for '%s/%s' (bad stamp?)", cwdbuf, name);
419
420         ad_close_metadata(&ad);
421     }
422
423     /* Get CNID from database */
424
425     /* Prepare request data */
426     memset(&rqst, 0, sizeof(struct cnid_dbd_rqst));
427     memset(&rply, 0, sizeof(struct cnid_dbd_rply));
428     rqst.did = did;
429     if ( ! (volinfo->v_flags & AFPVOL_NODEV))
430         rqst.dev = st->st_dev;
431     rqst.ino = st->st_ino;
432     rqst.type = S_ISDIR(st->st_mode)?1:0;
433     rqst.name = (char *)name;
434     rqst.namelen = strlen(name);
435
436     /* Query the database */
437     ret = cmd_dbd_lookup(dbd, &rqst, &rply, (dbd_flags & DBD_FLAGS_SCAN) ? 1 : 0);
438     dbif_txn_close(dbd, ret);
439     if (rply.result == CNID_DBD_RES_OK) {
440         db_cnid = rply.cnid;
441     } else if (rply.result == CNID_DBD_RES_NOTFOUND) {
442         if ( ! (dbd_flags & DBD_FLAGS_FORCE))
443             dbd_log( LOGSTD, "No CNID for '%s/%s' in database", cwdbuf, name);
444         db_cnid = 0;
445     } else {
446         dbd_log( LOGSTD, "Fatal error resolving '%s/%s'", cwdbuf, name);
447         db_cnid = 0;
448     }
449
450     /* Compare results from both CNID searches */
451     if (ad_cnid && db_cnid && (ad_cnid == db_cnid)) {
452         /* Everything is fine */
453         return db_cnid;
454     } else if (ad_cnid && db_cnid && (ad_cnid != db_cnid)) {
455         /* Mismatch ? Delete both from db and re-add data from file */
456         dbd_log( LOGSTD, "CNID mismatch for '%s/%s', db: %u, ad-file: %u", cwdbuf, name, ntohl(db_cnid), ntohl(ad_cnid));
457         if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
458             rqst.cnid = db_cnid;
459             ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
460             dbif_txn_close(dbd, ret);
461
462             rqst.cnid = ad_cnid;
463             ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
464             dbif_txn_close(dbd, ret);
465
466             ret = dbd_rebuild_add(dbd, &rqst, &rply);
467             dbif_txn_close(dbd, ret);
468         }
469         return ad_cnid;
470     } else if (ad_cnid && (db_cnid == 0)) {
471         /* in ad-file but not in db */
472         if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
473             dbd_log( LOGDEBUG, "CNID rebuild add for '%s/%s', adding with CNID from ad-file: %u", cwdbuf, name, ntohl(ad_cnid));
474             rqst.cnid = ad_cnid;
475             ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
476             dbif_txn_close(dbd, ret);
477             ret = dbd_rebuild_add(dbd, &rqst, &rply);
478             dbif_txn_close(dbd, ret);
479         }
480         return ad_cnid;
481     } else if ((db_cnid == 0) && (ad_cnid == 0)) {
482         /* No CNID at all, we clearly have to allocate a fresh one... */
483         /* Note: the next test will use this new CNID too! */
484         if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
485             /* add to db */
486             ret = cmd_dbd_add(dbd, &rqst, &rply);
487             dbif_txn_close(dbd, ret);
488             db_cnid = rply.cnid;
489             dbd_log( LOGSTD, "New CNID for '%s/%s': %u", cwdbuf, name, ntohl(db_cnid));
490         }
491     }
492     
493     if ((ad_cnid == 0) && db_cnid) {
494         /* in db but zeroID in ad-file, write it to ad-file if AFPVOL_CACHE */
495         if ((volinfo->v_flags & AFPVOL_CACHE) && ADFILE_OK) {
496             if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
497                 dbd_log( LOGSTD, "Writing CNID data for '%s/%s' to AppleDouble file", cwdbuf, name, ntohl(db_cnid));
498                 ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
499                 if (ad_open_metadata( name, adflags, O_RDWR, &ad) != 0) {
500                     dbd_log( LOGSTD, "Error opening AppleDouble file for '%s/%s': %s", cwdbuf, name, strerror(errno));
501                     return 0;
502                 }
503                 ad_setid( &ad, st->st_dev, st->st_ino, db_cnid, did, stamp);
504                 ad_flush(&ad);
505                 ad_close_metadata(&ad);
506             }
507         }
508         return db_cnid;
509     }
510
511     return 0;
512 }
513
514 /*
515   This is called recursively for all dirs.
516   volroot=1 means we're in the volume root dir, 0 means we aren't.
517   We use this when checking for netatalk private folders like .AppleDB.
518   did is our parents CNID.
519 */
520 static int dbd_readdir(int volroot, cnid_t did)
521 {
522     int cwd, ret = 0, adflags, adfile_ok, addir_ok, encoding_ok;
523     cnid_t cnid;
524     const char *name;
525     DIR *dp;
526     struct dirent *ep;
527     static struct stat st;      /* Save some stack space */
528
529     /* Check again for .AppleDouble folder, check_adfile also checks/creates it */
530     if ((addir_ok = check_addir(volroot)) != 0)
531         if ( ! (dbd_flags & DBD_FLAGS_SCAN))
532             /* Fatal on rebuild run, continue if only scanning ! */
533             return -1;
534
535     /* Check AppleDouble files in AppleDouble folder, but only if it exists or could be created */
536     if (ADDIR_OK)
537         if ((read_addir()) != 0)
538             if ( ! (dbd_flags & DBD_FLAGS_SCAN))
539                 /* Fatal on rebuild run, continue if only scanning ! */
540                 return -1;
541
542     if ((dp = opendir (".")) == NULL) {
543         dbd_log(LOGSTD, "Couldn't open the directory: %s",strerror(errno));
544         return -1;
545     }
546
547     while ((ep = readdir (dp))) {
548         /* Check if we got a termination signal */
549         if (alarmed)
550             longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
551
552         /* Check if its "." or ".." */
553         if (DIR_DOT_OR_DOTDOT(ep->d_name))
554             continue;
555
556         /* Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop" */
557         if ((name = check_netatalk_dirs(ep->d_name)) != NULL) {
558             if (! volroot)
559                 dbd_log(LOGSTD, "Nested %s in %s", name, cwdbuf);
560             continue;
561         }
562
563         /* Skip .AppleDouble dir in this loop */
564         if (STRCMP(ep->d_name, == , ADv2_DIRNAME))
565             continue;
566
567         if ((ret = stat(ep->d_name, &st)) < 0) {
568             dbd_log( LOGSTD, "Lost file while reading dir '%s/%s', probably removed: %s", cwdbuf, ep->d_name, strerror(errno));
569             continue;
570         }
571         if (S_ISREG(st.st_mode))
572             adflags = 0;
573         else
574             adflags = ADFLAGS_DIR;
575
576         /**************************************************************************
577            Tests
578         **************************************************************************/
579
580         /* Check encoding */
581         if ( -1 == (encoding_ok = check_name_encoding(ep->d_name)) ) {
582             /* If its a file: skipp all other tests now ! */
583             /* For dirs we could try to get a CNID for it and recurse, but currently I prefer not to */
584             continue;
585         }
586
587         /* Check for appledouble file, create if missing, but only if we have addir */
588         adfile_ok = -1;
589         if (ADDIR_OK)
590             adfile_ok = check_adfile(ep->d_name, &st);
591
592         /* Check CNIDs */
593         cnid = check_cnid(ep->d_name, did, &st, adfile_ok, adflags);
594
595         /* Now add this object to our rebuild dbd */
596         if (cnid) {
597             rqst.cnid = rply.cnid;
598             dbd_rebuild_add(dbd_rebuild, &rqst, &rply);
599             if (rply.result != CNID_DBD_RES_OK) {
600                 dbd_log( LOGDEBUG, "Fatal error adding CNID: %u for '%s/%s' to in-memory rebuild-db",
601                          cnid, cwdbuf, ep->d_name);
602                 exit(EXIT_FAILURE);
603             }
604         }
605
606         /**************************************************************************
607           Recursion
608         **************************************************************************/
609         if (S_ISDIR(st.st_mode) && cnid) { /* If we have no cnid for it we cant recur */
610             strcat(cwdbuf, "/");
611             strcat(cwdbuf, ep->d_name);
612             dbd_log( LOGDEBUG, "Entering directory: %s", cwdbuf);
613             if (-1 == (cwd = open(".", O_RDONLY))) {
614                 dbd_log( LOGSTD, "Cant open directory '%s': %s", cwdbuf, strerror(errno));
615                 continue;
616             }
617             if (0 != chdir(ep->d_name)) {
618                 dbd_log( LOGSTD, "Cant chdir to directory '%s': %s", cwdbuf, strerror(errno));
619                 close(cwd);
620                 continue;
621             }
622
623             ret = dbd_readdir(0, cnid);
624
625             fchdir(cwd);
626             close(cwd);
627             *(strrchr(cwdbuf, '/')) = 0;
628             if (ret < 0)
629                 continue;
630         }
631     }
632
633     /*
634       Use results of previous checks
635     */
636
637     closedir(dp);
638     return ret;
639 }
640
641 static int scanvol(struct volinfo *vi, dbd_flags_t flags)
642 {
643     /* Dont scanvol on no-appledouble vols */
644     if (vi->v_flags & AFPVOL_NOADOUBLE) {
645         dbd_log( LOGSTD, "Volume without AppleDouble support: skipping volume scanning.");
646         return 0;
647     }
648
649     /* Make this stuff accessible from all funcs easily */
650     volinfo = vi;
651     dbd_flags = flags;
652
653     /* Run with umask 0 */
654     umask(0);
655
656     /* chdir to vol */
657     strcpy(cwdbuf, volinfo->v_path);
658     if (cwdbuf[strlen(cwdbuf) - 1] == '/')
659         cwdbuf[strlen(cwdbuf) - 1] = 0;
660     chdir(volinfo->v_path);
661
662     /* Start recursion */
663     if (dbd_readdir(1, htonl(2)) < 0)  /* 2 = volumeroot CNID */
664         return -1;
665
666     return 0;
667 }
668
669 /* 
670    Remove all CNIDs from dbd that are not in dbd_rebuild
671 */
672 void delete_orphaned_cnids(DBD *dbd, DBD *dbd_rebuild, dbd_flags_t flags)
673 {
674     int ret, deleted = 0;
675     cnid_t dbd_cnid = 0, rebuild_cnid = 0;
676     struct cnid_dbd_rqst rqst;
677     struct cnid_dbd_rply rply;
678
679     /* jump over rootinfo key */
680     if ( dbif_idwalk(dbd, &dbd_cnid, 0) != 1)
681         return;
682     if ( dbif_idwalk(dbd_rebuild, &rebuild_cnid, 0) != 1)
683         return;
684
685     /* Get first id from dbd_rebuild */
686     if ((dbif_idwalk(dbd_rebuild, &rebuild_cnid, 0)) == -1)
687         return;
688
689     /* Start main loop through dbd: get CNID from dbd */
690     while ((dbif_idwalk(dbd, &dbd_cnid, 0)) == 1) {
691
692         if (deleted > 50) {
693             deleted = 0;
694             if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0) {
695                 dbd_log(LOGSTD, "Error checkpointing!");
696                 goto cleanup;
697             }
698         }
699
700         /* This should be the normal case: CNID is in both dbs */
701         if (dbd_cnid == rebuild_cnid) {
702             /* Get next CNID from rebuild db */
703             if ((ret = dbif_idwalk(dbd_rebuild, &rebuild_cnid, 0)) == -1) {
704                 /* Some error */
705                 goto cleanup;
706             } else if (ret == 0) {
707                 /* end of rebuild_cnid, delete all remaining CNIDs from dbd */
708                 while ((dbif_idwalk(dbd, &dbd_cnid, 0)) == 1) {
709                     dbd_log(LOGSTD, "Orphaned CNID in database: %u", dbd_cnid);
710                     if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
711                         rqst.cnid = htonl(dbd_cnid);
712                         ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
713                         dbif_txn_close(dbd, ret);
714                         deleted++;
715                     }
716                 }
717                 return;
718             } else
719                 /* Normal case (ret=1): continue while loop */
720                 continue;
721         }
722
723         if (dbd_cnid < rebuild_cnid) {
724             /* CNID is orphaned -> delete */
725             dbd_log(LOGSTD, "Orphaned CNID in database: %u.", dbd_cnid);
726             if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
727                 rqst.cnid = htonl(dbd_cnid);
728                 ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
729                 dbif_txn_close(dbd, ret);
730                 deleted++;
731             }
732             continue;
733         }
734
735         if (dbd_cnid > rebuild_cnid) {
736             dbd_log(LOGSTD, "Ghost CNID: %u. This is fatal! Dumping rebuild db:\n", rebuild_cnid);
737             dbif_dump(dbd_rebuild, 0);
738             dbd_log(LOGSTD, "Send this dump and a `dbd -d ...` dump to the Netatalk Dev team!");
739             dbif_txn_close(dbd, ret);
740             dbif_idwalk(dbd, NULL, 1); /* Close cursor */
741             dbif_idwalk(dbd_rebuild, NULL, 1); /* Close cursor */
742             goto cleanup;
743         }
744     }
745
746 cleanup:
747     dbif_idwalk(dbd, NULL, 1); /* Close cursor */
748     dbif_idwalk(dbd_rebuild, NULL, 1); /* Close cursor */
749     return;
750 }
751
752 /*
753   Main func called from cmd_dbd.c
754 */
755 int cmd_dbd_scanvol(DBD *dbd_ref, struct volinfo *volinfo, dbd_flags_t flags)
756 {
757     int ret = 0;
758
759     /* Make it accessible for all funcs */
760     dbd = dbd_ref;
761
762     /* We only support unicode volumes ! */
763     if ( volinfo->v_volcharset != CH_UTF8) {
764         dbd_log( LOGSTD, "Not a Unicode volume: %s, %u != %u", volinfo->v_volcodepage, volinfo->v_volcharset, CH_UTF8);
765         return -1;
766     }
767
768     /* Get volume stamp */
769     dbd_getstamp(dbd, &rqst, &rply);
770     if (rply.result != CNID_DBD_RES_OK)
771         goto exit_cleanup;
772     memcpy(stamp, rply.name, CNID_DEV_LEN);
773
774     /* open/create rebuild dbd, copy rootinfo key */
775     if (NULL == (dbd_rebuild = dbif_init(NULL, NULL)))
776         return -1;
777     if (0 != (dbif_open(dbd_rebuild, NULL, 0)))
778         return -1;
779     if (0 != (dbif_copy_rootinfokey(dbd, dbd_rebuild)))
780         goto exit_cleanup;
781
782     if (setjmp(jmp) != 0)
783         goto exit_cleanup;      /* Got signal, jump from dbd_readdir */
784
785     /* scanvol */
786     if ( (scanvol(volinfo, flags)) != 0)
787         return -1;
788
789     /* We can only do this in exclusive mode, otherwise we might delete CNIDs added from
790        other clients in between our pass 1 and 2 */
791     if (flags & DBD_FLAGS_EXCL)
792         delete_orphaned_cnids(dbd, dbd_rebuild, flags);
793
794 exit_cleanup:
795     dbif_close(dbd_rebuild);
796     return ret;
797 }