]> arthur.barton.de Git - netatalk.git/blob - etc/cnid_dbd/cmd_dbd_scanvol.c
Stamp was not updated in force mode
[netatalk.git] / etc / cnid_dbd / cmd_dbd_scanvol.c
1 /*
2   $Id: cmd_dbd_scanvol.c,v 1.5 2009-05-23 06:28:27 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         if (ad_cnid == 0)
417             dbd_log( LOGSTD, "Incorrect CNID data in .AppleDouble data for '%s/%s' (bad stamp?)", cwdbuf, name);
418
419         ad_close_metadata(&ad);
420     }
421
422     /* Get CNID from database */
423
424     /* Prepare request data */
425     memset(&rqst, 0, sizeof(struct cnid_dbd_rqst));
426     memset(&rply, 0, sizeof(struct cnid_dbd_rply));
427     rqst.did = did;
428     if ( ! (volinfo->v_flags & AFPVOL_NODEV))
429         rqst.dev = st->st_dev;
430     rqst.ino = st->st_ino;
431     rqst.type = S_ISDIR(st->st_mode)?1:0;
432     rqst.name = (char *)name;
433     rqst.namelen = strlen(name);
434
435     /* Query the database */
436     ret = cmd_dbd_lookup(dbd, &rqst, &rply, (dbd_flags & DBD_FLAGS_SCAN) ? 1 : 0);
437     dbif_txn_close(dbd, ret);
438     if (rply.result == CNID_DBD_RES_OK) {
439         db_cnid = rply.cnid;
440     } else if (rply.result == CNID_DBD_RES_NOTFOUND) {
441         dbd_log( LOGSTD, "No CNID for '%s/%s' in database", cwdbuf, name);
442         db_cnid = 0;
443     } else {
444         dbd_log( LOGSTD, "Fatal error resolving '%s/%s'", cwdbuf, name);
445         db_cnid = 0;
446     }
447
448     /* Compare results from both CNID searches */
449     if (ad_cnid && db_cnid && (ad_cnid == db_cnid)) {
450         /* Everything is fine */
451         return db_cnid;
452     } else if (ad_cnid && db_cnid && (ad_cnid != db_cnid)) {
453         /* Mismatch ? Delete both from db and re-add data from file */
454         dbd_log( LOGSTD, "CNID mismatch for '%s/%s', db: %u, ad-file: %u", cwdbuf, name, ntohl(db_cnid), ntohl(ad_cnid));
455         if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
456             rqst.cnid = db_cnid;
457             ret = dbd_delete(dbd, &rqst, &rply);
458             dbif_txn_close(dbd, ret);
459
460             rqst.cnid = ad_cnid;
461             ret = dbd_delete(dbd, &rqst, &rply);
462             dbif_txn_close(dbd, ret);
463
464             ret = dbd_rebuild_add(dbd, &rqst, &rply);
465             dbif_txn_close(dbd, ret);
466         }
467         return ad_cnid;
468     } else if (ad_cnid && (db_cnid == 0)) {
469         /* in ad-file but not in db */
470         if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
471             dbd_log( LOGSTD, "CNID rebuild add for '%s/%s', adding with CNID from ad-file: %u", cwdbuf, name, ntohl(ad_cnid));
472             rqst.cnid = ad_cnid;
473             ret = dbd_delete(dbd, &rqst, &rply);
474             dbif_txn_close(dbd, ret);
475             ret = dbd_rebuild_add(dbd, &rqst, &rply);
476             dbif_txn_close(dbd, ret);
477         }
478         return ad_cnid;
479     } else if ((db_cnid == 0) && (ad_cnid == 0)) {
480         /* No CNID at all, we clearly have to allocate a fresh one... */
481         /* Note: the next test will use this new CNID too! */
482         if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
483             /* add to db */
484             ret = cmd_dbd_add(dbd, &rqst, &rply);
485             dbif_txn_close(dbd, ret);
486             db_cnid = rply.cnid;
487             dbd_log( LOGSTD, "New CNID for '%s/%s': %u", cwdbuf, name, ntohl(db_cnid));
488         }
489     }
490     
491     if ((ad_cnid == 0) && db_cnid) {
492         /* in db but zeroID in ad-file, write it to ad-file if AFPVOL_CACHE */
493         if ((volinfo->v_flags & AFPVOL_CACHE) && ADFILE_OK) {
494             if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
495                 dbd_log( LOGSTD, "Writing CNID data for '%s/%s' to AppleDouble file", cwdbuf, name, ntohl(db_cnid));
496                 ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
497                 if (ad_open_metadata( name, adflags, O_RDWR, &ad) != 0) {
498                     dbd_log( LOGSTD, "Error opening AppleDouble file for '%s/%s': %s", cwdbuf, name, strerror(errno));
499                     return 0;
500                 }
501                 ad_setid( &ad, st->st_dev, st->st_ino, db_cnid, did, stamp);
502                 ad_flush(&ad);
503                 ad_close_metadata(&ad);
504             }
505         }
506         return db_cnid;
507     }
508
509     return 0;
510 }
511
512 /*
513   This is called recursively for all dirs.
514   volroot=1 means we're in the volume root dir, 0 means we aren't.
515   We use this when checking for netatalk private folders like .AppleDB.
516   did is our parents CNID.
517 */
518 static int dbd_readdir(int volroot, cnid_t did)
519 {
520     int cwd, ret = 0, adflags, adfile_ok, addir_ok, encoding_ok;
521     cnid_t cnid;
522     const char *name;
523     DIR *dp;
524     struct dirent *ep;
525     static struct stat st;      /* Save some stack space */
526
527     /* Check again for .AppleDouble folder, check_adfile also checks/creates it */
528     if ((addir_ok = check_addir(volroot)) != 0)
529         if ( ! (dbd_flags & DBD_FLAGS_SCAN))
530             /* Fatal on rebuild run, continue if only scanning ! */
531             return -1;
532
533     /* Check AppleDouble files in AppleDouble folder, but only if it exists or could be created */
534     if (ADDIR_OK)
535         if ((read_addir()) != 0)
536             if ( ! (dbd_flags & DBD_FLAGS_SCAN))
537                 /* Fatal on rebuild run, continue if only scanning ! */
538                 return -1;
539
540     if ((dp = opendir (".")) == NULL) {
541         dbd_log(LOGSTD, "Couldn't open the directory: %s",strerror(errno));
542         return -1;
543     }
544
545     while ((ep = readdir (dp))) {
546         /* Check if we got a termination signal */
547         if (alarmed)
548             longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
549
550         /* Check if its "." or ".." */
551         if (DIR_DOT_OR_DOTDOT(ep->d_name))
552             continue;
553
554         /* Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop" */
555         if ((name = check_netatalk_dirs(ep->d_name)) != NULL) {
556             if (! volroot)
557                 dbd_log(LOGSTD, "Nested %s in %s", name, cwdbuf);
558             continue;
559         }
560
561         /* Skip .AppleDouble dir in this loop */
562         if (STRCMP(ep->d_name, == , ADv2_DIRNAME))
563             continue;
564
565         if ((ret = stat(ep->d_name, &st)) < 0) {
566             dbd_log( LOGSTD, "Lost file while reading dir '%s/%s', probably removed: %s", cwdbuf, ep->d_name, strerror(errno));
567             continue;
568         }
569         if (S_ISREG(st.st_mode))
570             adflags = 0;
571         else
572             adflags = ADFLAGS_DIR;
573
574         /**************************************************************************
575            Tests
576         **************************************************************************/
577
578         /* Check encoding */
579         if ( -1 == (encoding_ok = check_name_encoding(ep->d_name)) ) {
580             /* If its a file: skipp all other tests now ! */
581             /* For dirs we could try to get a CNID for it and recurse, but currently I prefer not to */
582             continue;
583         }
584
585         /* Check for appledouble file, create if missing, but only if we have addir */
586         adfile_ok = -1;
587         if (ADDIR_OK)
588             adfile_ok = check_adfile(ep->d_name, &st);
589
590         /* Check CNIDs */
591         cnid = check_cnid(ep->d_name, did, &st, adfile_ok, adflags);
592
593         /* Now add this object to our rebuild dbd */
594         if (cnid) {
595             rqst.cnid = rply.cnid;
596             dbd_rebuild_add(dbd_rebuild, &rqst, &rply);
597             if (rply.result != CNID_DBD_RES_OK) {
598                 dbd_log( LOGDEBUG, "Fatal error adding CNID: %u for '%s/%s' to in-memory rebuild-db",
599                          cnid, cwdbuf, ep->d_name);
600                 exit(EXIT_FAILURE);
601             }
602         }
603
604         /**************************************************************************
605           Recursion
606         **************************************************************************/
607         if (S_ISDIR(st.st_mode) && cnid) { /* If we have no cnid for it we cant recur */
608             strcat(cwdbuf, "/");
609             strcat(cwdbuf, ep->d_name);
610             dbd_log( LOGDEBUG, "Entering directory: %s", cwdbuf);
611             if (-1 == (cwd = open(".", O_RDONLY))) {
612                 dbd_log( LOGSTD, "Cant open directory '%s': %s", cwdbuf, strerror(errno));
613                 continue;
614             }
615             if (0 != chdir(ep->d_name)) {
616                 dbd_log( LOGSTD, "Cant chdir to directory '%s': %s", cwdbuf, strerror(errno));
617                 close(cwd);
618                 continue;
619             }
620
621             ret = dbd_readdir(0, cnid);
622
623             fchdir(cwd);
624             close(cwd);
625             *(strrchr(cwdbuf, '/')) = 0;
626             if (ret < 0)
627                 continue;
628         }
629     }
630
631     /*
632       Use results of previous checks
633     */
634
635     closedir(dp);
636     return ret;
637 }
638
639 static int scanvol(struct volinfo *vi, dbd_flags_t flags)
640 {
641     /* Dont scanvol on no-appledouble vols */
642     if (vi->v_flags & AFPVOL_NOADOUBLE) {
643         dbd_log( LOGSTD, "Volume without AppleDouble support: skipping volume scanning.");
644         return 0;
645     }
646
647     /* Make this stuff accessible from all funcs easily */
648     volinfo = vi;
649     dbd_flags = flags;
650
651     /* Run with umask 0 */
652     umask(0);
653
654     /* chdir to vol */
655     strcpy(cwdbuf, volinfo->v_path);
656     if (cwdbuf[strlen(cwdbuf) - 1] == '/')
657         cwdbuf[strlen(cwdbuf) - 1] = 0;
658     chdir(volinfo->v_path);
659
660     /* Start recursion */
661     if (dbd_readdir(1, htonl(2)) < 0)  /* 2 = volumeroot CNID */
662         return -1;
663
664     return 0;
665 }
666
667 /* 
668    Remove all CNIDs from dbd that are not in dbd_rebuild
669 */
670 void delete_orphaned_cnids(DBD *dbd, DBD *dbd_rebuild, dbd_flags_t flags)
671 {
672     int ret, deleted = 0;
673     cnid_t dbd_cnid = 0, rebuild_cnid = 0;
674     struct cnid_dbd_rqst rqst;
675     struct cnid_dbd_rply rply;
676
677     /* jump over rootinfo key */
678     if ( dbif_idwalk(dbd, &dbd_cnid, 0) != 1)
679         return;
680     if ( dbif_idwalk(dbd_rebuild, &rebuild_cnid, 0) != 1)
681         return;
682
683     /* Get first id from dbd_rebuild */
684     if ((dbif_idwalk(dbd_rebuild, &rebuild_cnid, 0)) == -1)
685         return;
686
687     /* Start main loop through dbd: get CNID from dbd */
688     while ((dbif_idwalk(dbd, &dbd_cnid, 0)) == 1) {
689
690         if (deleted > 50) {
691             deleted = 0;
692             if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0) {
693                 dbd_log(LOGSTD, "Error checkpointing!");
694                 goto cleanup;
695             }
696         }
697
698         /* This should be the normal case: CNID is in both dbs */
699         if (dbd_cnid == rebuild_cnid) {
700             /* Get next CNID from rebuild db */
701             if ((ret = dbif_idwalk(dbd_rebuild, &rebuild_cnid, 0)) == -1) {
702                 /* Some error */
703                 goto cleanup;
704             } else if (ret == 0) {
705                 /* end of rebuild_cnid, delete all remaining CNIDs from dbd */
706                 while ((dbif_idwalk(dbd, &dbd_cnid, 0)) == 1) {
707                     dbd_log(LOGSTD, "Orphaned CNID in database: %u", dbd_cnid);
708                     if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
709                         rqst.cnid = htonl(dbd_cnid);
710                         ret = dbd_delete(dbd, &rqst, &rply);
711                         dbif_txn_close(dbd, ret);
712                         deleted++;
713                     }
714                 }
715                 return;
716             } else
717                 /* Normal case (ret=1): continue while loop */
718                 continue;
719         }
720
721         if (dbd_cnid < rebuild_cnid) {
722             /* CNID is orphaned -> delete */
723             dbd_log(LOGSTD, "Orphaned CNID in database: %u.", dbd_cnid);
724             if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
725                 rqst.cnid = htonl(dbd_cnid);
726                 ret = dbd_delete(dbd, &rqst, &rply);
727                 dbif_txn_close(dbd, ret);
728                 deleted++;
729             }
730             continue;
731         }
732
733         if (dbd_cnid > rebuild_cnid) {
734             dbd_log(LOGSTD, "Ghost CNID: %u. This is fatal! Dumping rebuild db:\n", rebuild_cnid);
735             dbif_dump(dbd_rebuild, 0);
736             dbd_log(LOGSTD, "Send this dump and a `dbd -d ...` dump to the Netatalk Dev team!");
737             dbif_txn_close(dbd, ret);
738             dbif_idwalk(dbd, NULL, 1); /* Close cursor */
739             dbif_idwalk(dbd_rebuild, NULL, 1); /* Close cursor */
740             goto cleanup;
741         }
742     }
743
744 cleanup:
745     dbif_idwalk(dbd, NULL, 1); /* Close cursor */
746     dbif_idwalk(dbd_rebuild, NULL, 1); /* Close cursor */
747     return;
748 }
749
750 /*
751   Main func called from cmd_dbd.c
752 */
753 int cmd_dbd_scanvol(DBD *dbd_ref, struct volinfo *volinfo, dbd_flags_t flags)
754 {
755     int ret = 0;
756
757     /* Make it accessible for all funcs */
758     dbd = dbd_ref;
759
760     /* We only support unicode volumes ! */
761     if ( volinfo->v_volcharset != CH_UTF8) {
762         dbd_log( LOGSTD, "Not a Unicode volume: %s, %u != %u", volinfo->v_volcodepage, volinfo->v_volcharset, CH_UTF8);
763         return -1;
764     }
765
766     /* Get volume stamp */
767     dbd_getstamp(dbd, &rqst, &rply);
768     if (rply.result != CNID_DBD_RES_OK)
769         goto exit_cleanup;
770     memcpy(stamp, rply.name, CNID_DEV_LEN);
771
772     /* open/create rebuild dbd, copy rootinfo key */
773     if (NULL == (dbd_rebuild = dbif_init(NULL, NULL)))
774         return -1;
775     if (0 != (dbif_open(dbd_rebuild, NULL, 0)))
776         return -1;
777     if (0 != (dbif_copy_rootinfokey(dbd, dbd_rebuild)))
778         goto exit_cleanup;
779
780     if (setjmp(jmp) != 0)
781         goto exit_cleanup;      /* Got signal, jump from dbd_readdir */
782
783     /* scanvol */
784     if ( (scanvol(volinfo, flags)) != 0)
785         return -1;
786
787     /* We can only do this in excluse mode, otherwise we might delete CNIDs added from
788        other clients in between our pass 1 and 2 */
789     if (flags & DBD_FLAGS_EXCL)
790         delete_orphaned_cnids(dbd, dbd_rebuild, flags);
791
792 exit_cleanup:
793     dbif_close(dbd_rebuild);
794     return ret;
795 }