]> arthur.barton.de Git - netatalk.git/blob - etc/cnid_dbd/cmd_dbd_scanvol.c
remove 'ISO C90 forbids mixed declarations and code' warning
[netatalk.git] / etc / cnid_dbd / cmd_dbd_scanvol.c
1 /*
2   $Id: cmd_dbd_scanvol.c,v 1.8 2009-09-14 02:56:19 didg 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     char *mname;
249     
250     /* Check for ad-dir */
251     if ( (addir_ok = access(ADv2_DIRNAME, F_OK)) != 0) {
252         if (errno != ENOENT) {
253             dbd_log(LOGSTD, "Access error in directory %s: %s", cwdbuf, strerror(errno));
254             return -1;
255         }
256         dbd_log(LOGSTD, "Missing %s for '%s'", ADv2_DIRNAME, cwdbuf);
257     }
258
259     /* Check for ".Parent" */
260     if ( (adpar_ok = access(volinfo->ad_path(".", ADFLAGS_DIR), F_OK)) != 0) {
261         if (errno != ENOENT) {
262             dbd_log(LOGSTD, "Access error on '%s/%s': %s",
263                     cwdbuf, volinfo->ad_path(".", ADFLAGS_DIR), strerror(errno));
264             return -1;
265         }
266         dbd_log(LOGSTD, "Missing .AppleDouble/.Parent for '%s'", cwdbuf);
267     }
268
269     /* Is one missing ? */
270     if ((addir_ok != 0) || (adpar_ok != 0)) {
271         /* Yes, but are we only scanning ? */
272         if (dbd_flags & DBD_FLAGS_SCAN) {
273             /* Yes:  missing .Parent is not a problem, but missing ad-dir
274                causes later checking of ad-files to fail. So we have to return appropiately */
275             if (addir_ok != 0)
276                 return -1;
277             else  /* (adpar_ok != 0) */
278                 return 0;
279         }
280
281         /* Create ad dir and set name */
282         ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
283
284         if (ad_open_metadata( ".", ADFLAGS_DIR, O_CREAT, &ad) != 0) {
285             dbd_log( LOGSTD, "Error creating AppleDouble dir in %s: %s", cwdbuf, strerror(errno));
286             return -1;
287         }
288
289         /* Get basename of cwd from cwdbuf */
290         utompath(strrchr(cwdbuf, '/') + 1);
291
292         /* Update name in ad file */
293         ad_setname(&ad, mname);
294         ad_flush(&ad);
295         ad_close_metadata(&ad);
296
297         /* Inherit owner/group from "." to ".AppleDouble" and ".Parent" */
298         if ((stat(".", &st)) != 0) {
299             dbd_log( LOGSTD, "Couldnt stat %s: %s", cwdbuf, strerror(errno));
300             return -1;
301         }
302         chown(ADv2_DIRNAME, st.st_uid, st.st_gid);
303         chown(volinfo->ad_path(".", ADFLAGS_DIR), st.st_uid, st.st_gid);
304     }
305
306     return 0;
307 }
308
309 /* 
310   Check files and dirs inside .AppleDouble folder:
311   - remove orphaned files
312   - bail on dirs
313 */
314 static int read_addir(void)
315 {
316     DIR *dp;
317     struct dirent *ep;
318     struct stat st;
319     static char fname[MAXPATHLEN] = "../";
320
321     if ((chdir(ADv2_DIRNAME)) != 0) {
322         dbd_log(LOGSTD, "Couldn't chdir to '%s/%s': %s",
323                 cwdbuf, ADv2_DIRNAME, strerror(errno));
324         return -1;
325     }
326
327     if ((dp = opendir(".")) == NULL) {
328         dbd_log(LOGSTD, "Couldn't open the directory '%s/%s': %s",
329                 cwdbuf, ADv2_DIRNAME, strerror(errno));
330         return -1;
331     }
332
333     while ((ep = readdir(dp))) {
334         /* Check if its "." or ".." */
335         if (DIR_DOT_OR_DOTDOT(ep->d_name))
336             continue;
337         /* Skip ".Parent" */
338         if (STRCMP(ep->d_name, ==, ".Parent"))
339             continue;
340
341         if ((stat(ep->d_name, &st)) < 0) {
342             dbd_log( LOGSTD, "Lost file or dir while enumeratin dir '%s/%s/%s', probably removed: %s",
343                      cwdbuf, ADv2_DIRNAME, ep->d_name, strerror(errno));
344             continue;
345         }
346
347         /* Check for dirs */
348         if (S_ISDIR(st.st_mode)) {
349             dbd_log( LOGSTD, "Unexpected directory '%s' in AppleDouble dir '%s/%s'",
350                      ep->d_name, cwdbuf, ADv2_DIRNAME);
351             continue;
352         }
353
354         /* Check for data file */
355         strcpy(fname+3, ep->d_name);
356         if ((access( fname, F_OK)) != 0) {
357             if (errno != ENOENT) {
358                 dbd_log(LOGSTD, "Access error for file '%s/%s': %s",
359                         cwdbuf, ep->d_name, strerror(errno));
360                 continue;
361             }
362             /* Orphaned ad-file*/
363             dbd_log(LOGSTD, "Orphaned AppleDoube file '%s/%s/%s'",
364                     cwdbuf, ADv2_DIRNAME, ep->d_name);
365
366             if (dbd_flags & DBD_FLAGS_SCAN)
367                 /* Scan only requested, dont change anything */
368                 continue;;
369
370             if ((unlink(ep->d_name)) != 0) {
371                 dbd_log(LOGSTD, "Error unlinking orphaned AppleDoube file '%s/%s/%s'",
372                         cwdbuf, ADv2_DIRNAME, ep->d_name);
373
374             }
375         }
376     }
377
378     if ((chdir("..")) != 0) {
379         dbd_log(LOGSTD, "Couldn't chdir back to '%s' from AppleDouble dir: %s",
380                 cwdbuf, strerror(errno));
381         /* This really is EOT! */
382         exit(1);
383     }
384
385     closedir(dp);
386
387     return 0;
388 }
389
390 /* 
391    Check CNID for a file/dir, both from db and from ad-file.
392    For detailed specs see intro.
393 */
394 static cnid_t check_cnid(const char *name, cnid_t did, struct stat *st, int adfile_ok, int adflags)
395 {
396     int ret;
397     cnid_t db_cnid, ad_cnid;
398     struct adouble ad;
399
400     /* Get CNID from ad-file if volume is using AFPVOL_CACHE */
401     ad_cnid = 0;
402     if ( (volinfo->v_flags & AFPVOL_CACHE) && ADFILE_OK) {
403         ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
404         if (ad_open_metadata( name, adflags, O_RDWR, &ad) != 0) {
405             dbd_log( LOGSTD, "Error opening AppleDouble file for '%s/%s': %s", cwdbuf, name, strerror(errno));
406             return 0;
407         }
408
409         if (dbd_flags & DBD_FLAGS_FORCE) {
410             ad_cnid = ad_forcegetid(&ad);
411             /* This ensures the changed stamp is written */
412             ad_setid( &ad, st->st_dev, st->st_ino, ad_cnid, did, stamp);
413             ad_flush(&ad);
414         }
415         else
416             ad_cnid = ad_getid(&ad, st->st_dev, st->st_ino, did, stamp);            
417
418         if (ad_cnid == 0)
419             dbd_log( LOGSTD, "Incorrect CNID data in .AppleDouble data for '%s/%s' (bad stamp?)", cwdbuf, name);
420
421         ad_close_metadata(&ad);
422     }
423
424     /* Get CNID from database */
425
426     /* Prepare request data */
427     memset(&rqst, 0, sizeof(struct cnid_dbd_rqst));
428     memset(&rply, 0, sizeof(struct cnid_dbd_rply));
429     rqst.did = did;
430     if ( ! (volinfo->v_flags & AFPVOL_NODEV))
431         rqst.dev = st->st_dev;
432     rqst.ino = st->st_ino;
433     rqst.type = S_ISDIR(st->st_mode)?1:0;
434     rqst.name = (char *)name;
435     rqst.namelen = strlen(name);
436
437     /* Query the database */
438     ret = cmd_dbd_lookup(dbd, &rqst, &rply, (dbd_flags & DBD_FLAGS_SCAN) ? 1 : 0);
439     dbif_txn_close(dbd, ret);
440     if (rply.result == CNID_DBD_RES_OK) {
441         db_cnid = rply.cnid;
442     } else if (rply.result == CNID_DBD_RES_NOTFOUND) {
443         if ( ! (dbd_flags & DBD_FLAGS_FORCE))
444             dbd_log( LOGSTD, "No CNID for '%s/%s' in database", cwdbuf, name);
445         db_cnid = 0;
446     } else {
447         dbd_log( LOGSTD, "Fatal error resolving '%s/%s'", cwdbuf, name);
448         db_cnid = 0;
449     }
450
451     /* Compare results from both CNID searches */
452     if (ad_cnid && db_cnid && (ad_cnid == db_cnid)) {
453         /* Everything is fine */
454         return db_cnid;
455     } else if (ad_cnid && db_cnid && (ad_cnid != db_cnid)) {
456         /* Mismatch ? Delete both from db and re-add data from file */
457         dbd_log( LOGSTD, "CNID mismatch for '%s/%s', db: %u, ad-file: %u", cwdbuf, name, ntohl(db_cnid), ntohl(ad_cnid));
458         if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
459             rqst.cnid = db_cnid;
460             ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
461             dbif_txn_close(dbd, ret);
462
463             rqst.cnid = ad_cnid;
464             ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
465             dbif_txn_close(dbd, ret);
466
467             ret = dbd_rebuild_add(dbd, &rqst, &rply);
468             dbif_txn_close(dbd, ret);
469         }
470         return ad_cnid;
471     } else if (ad_cnid && (db_cnid == 0)) {
472         /* in ad-file but not in db */
473         if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
474             dbd_log( LOGDEBUG, "CNID rebuild add for '%s/%s', adding with CNID from ad-file: %u", cwdbuf, name, ntohl(ad_cnid));
475             rqst.cnid = ad_cnid;
476             ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
477             dbif_txn_close(dbd, ret);
478             ret = dbd_rebuild_add(dbd, &rqst, &rply);
479             dbif_txn_close(dbd, ret);
480         }
481         return ad_cnid;
482     } else if ((db_cnid == 0) && (ad_cnid == 0)) {
483         /* No CNID at all, we clearly have to allocate a fresh one... */
484         /* Note: the next test will use this new CNID too! */
485         if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
486             /* add to db */
487             ret = cmd_dbd_add(dbd, &rqst, &rply);
488             dbif_txn_close(dbd, ret);
489             db_cnid = rply.cnid;
490             dbd_log( LOGSTD, "New CNID for '%s/%s': %u", cwdbuf, name, ntohl(db_cnid));
491         }
492     }
493     
494     if ((ad_cnid == 0) && db_cnid) {
495         /* in db but zeroID in ad-file, write it to ad-file if AFPVOL_CACHE */
496         if ((volinfo->v_flags & AFPVOL_CACHE) && ADFILE_OK) {
497             if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
498                 dbd_log( LOGSTD, "Writing CNID data for '%s/%s' to AppleDouble file", cwdbuf, name, ntohl(db_cnid));
499                 ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
500                 if (ad_open_metadata( name, adflags, O_RDWR, &ad) != 0) {
501                     dbd_log( LOGSTD, "Error opening AppleDouble file for '%s/%s': %s", cwdbuf, name, strerror(errno));
502                     return 0;
503                 }
504                 ad_setid( &ad, st->st_dev, st->st_ino, db_cnid, did, stamp);
505                 ad_flush(&ad);
506                 ad_close_metadata(&ad);
507             }
508         }
509         return db_cnid;
510     }
511
512     return 0;
513 }
514
515 /*
516   This is called recursively for all dirs.
517   volroot=1 means we're in the volume root dir, 0 means we aren't.
518   We use this when checking for netatalk private folders like .AppleDB.
519   did is our parents CNID.
520 */
521 static int dbd_readdir(int volroot, cnid_t did)
522 {
523     int cwd, ret = 0, adflags, adfile_ok, addir_ok, encoding_ok;
524     cnid_t cnid;
525     const char *name;
526     DIR *dp;
527     struct dirent *ep;
528     static struct stat st;      /* Save some stack space */
529
530     /* Check again for .AppleDouble folder, check_adfile also checks/creates it */
531     if ((addir_ok = check_addir(volroot)) != 0)
532         if ( ! (dbd_flags & DBD_FLAGS_SCAN))
533             /* Fatal on rebuild run, continue if only scanning ! */
534             return -1;
535
536     /* Check AppleDouble files in AppleDouble folder, but only if it exists or could be created */
537     if (ADDIR_OK)
538         if ((read_addir()) != 0)
539             if ( ! (dbd_flags & DBD_FLAGS_SCAN))
540                 /* Fatal on rebuild run, continue if only scanning ! */
541                 return -1;
542
543     if ((dp = opendir (".")) == NULL) {
544         dbd_log(LOGSTD, "Couldn't open the directory: %s",strerror(errno));
545         return -1;
546     }
547
548     while ((ep = readdir (dp))) {
549         /* Check if we got a termination signal */
550         if (alarmed)
551             longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
552
553         /* Check if its "." or ".." */
554         if (DIR_DOT_OR_DOTDOT(ep->d_name))
555             continue;
556
557         /* Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop" */
558         if ((name = check_netatalk_dirs(ep->d_name)) != NULL) {
559             if (! volroot)
560                 dbd_log(LOGSTD, "Nested %s in %s", name, cwdbuf);
561             continue;
562         }
563
564         /* Skip .AppleDouble dir in this loop */
565         if (STRCMP(ep->d_name, == , ADv2_DIRNAME))
566             continue;
567
568         if ((ret = stat(ep->d_name, &st)) < 0) {
569             dbd_log( LOGSTD, "Lost file while reading dir '%s/%s', probably removed: %s", cwdbuf, ep->d_name, strerror(errno));
570             continue;
571         }
572         if (S_ISREG(st.st_mode))
573             adflags = 0;
574         else
575             adflags = ADFLAGS_DIR;
576
577         /**************************************************************************
578            Tests
579         **************************************************************************/
580
581         /* Check encoding */
582         if ( -1 == (encoding_ok = check_name_encoding(ep->d_name)) ) {
583             /* If its a file: skipp all other tests now ! */
584             /* For dirs we could try to get a CNID for it and recurse, but currently I prefer not to */
585             continue;
586         }
587
588         /* Check for appledouble file, create if missing, but only if we have addir */
589         adfile_ok = -1;
590         if (ADDIR_OK)
591             adfile_ok = check_adfile(ep->d_name, &st);
592
593         /* Check CNIDs */
594         cnid = check_cnid(ep->d_name, did, &st, adfile_ok, adflags);
595
596         /* Now add this object to our rebuild dbd */
597         if (cnid) {
598             rqst.cnid = rply.cnid;
599             dbd_rebuild_add(dbd_rebuild, &rqst, &rply);
600             if (rply.result != CNID_DBD_RES_OK) {
601                 dbd_log( LOGDEBUG, "Fatal error adding CNID: %u for '%s/%s' to in-memory rebuild-db",
602                          cnid, cwdbuf, ep->d_name);
603                 exit(EXIT_FAILURE);
604             }
605         }
606
607         /**************************************************************************
608           Recursion
609         **************************************************************************/
610         if (S_ISDIR(st.st_mode) && cnid) { /* If we have no cnid for it we cant recur */
611             strcat(cwdbuf, "/");
612             strcat(cwdbuf, ep->d_name);
613             dbd_log( LOGDEBUG, "Entering directory: %s", cwdbuf);
614             if (-1 == (cwd = open(".", O_RDONLY))) {
615                 dbd_log( LOGSTD, "Cant open directory '%s': %s", cwdbuf, strerror(errno));
616                 continue;
617             }
618             if (0 != chdir(ep->d_name)) {
619                 dbd_log( LOGSTD, "Cant chdir to directory '%s': %s", cwdbuf, strerror(errno));
620                 close(cwd);
621                 continue;
622             }
623
624             ret = dbd_readdir(0, cnid);
625
626             fchdir(cwd);
627             close(cwd);
628             *(strrchr(cwdbuf, '/')) = 0;
629             if (ret < 0)
630                 continue;
631         }
632     }
633
634     /*
635       Use results of previous checks
636     */
637
638     closedir(dp);
639     return ret;
640 }
641
642 static int scanvol(struct volinfo *vi, dbd_flags_t flags)
643 {
644     /* Dont scanvol on no-appledouble vols */
645     if (vi->v_flags & AFPVOL_NOADOUBLE) {
646         dbd_log( LOGSTD, "Volume without AppleDouble support: skipping volume scanning.");
647         return 0;
648     }
649
650     /* Make this stuff accessible from all funcs easily */
651     volinfo = vi;
652     dbd_flags = flags;
653
654     /* Run with umask 0 */
655     umask(0);
656
657     /* chdir to vol */
658     strcpy(cwdbuf, volinfo->v_path);
659     if (cwdbuf[strlen(cwdbuf) - 1] == '/')
660         cwdbuf[strlen(cwdbuf) - 1] = 0;
661     chdir(volinfo->v_path);
662
663     /* Start recursion */
664     if (dbd_readdir(1, htonl(2)) < 0)  /* 2 = volumeroot CNID */
665         return -1;
666
667     return 0;
668 }
669
670 /* 
671    Remove all CNIDs from dbd that are not in dbd_rebuild
672 */
673 void delete_orphaned_cnids(DBD *dbd, DBD *dbd_rebuild, dbd_flags_t flags)
674 {
675     int ret, deleted = 0;
676     cnid_t dbd_cnid = 0, rebuild_cnid = 0;
677     struct cnid_dbd_rqst rqst;
678     struct cnid_dbd_rply rply;
679
680     /* jump over rootinfo key */
681     if ( dbif_idwalk(dbd, &dbd_cnid, 0) != 1)
682         return;
683     if ( dbif_idwalk(dbd_rebuild, &rebuild_cnid, 0) != 1)
684         return;
685
686     /* Get first id from dbd_rebuild */
687     if ((dbif_idwalk(dbd_rebuild, &rebuild_cnid, 0)) == -1)
688         return;
689
690     /* Start main loop through dbd: get CNID from dbd */
691     while ((dbif_idwalk(dbd, &dbd_cnid, 0)) == 1) {
692
693         if (deleted > 50) {
694             deleted = 0;
695             if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0) {
696                 dbd_log(LOGSTD, "Error checkpointing!");
697                 goto cleanup;
698             }
699         }
700
701         /* This should be the normal case: CNID is in both dbs */
702         if (dbd_cnid == rebuild_cnid) {
703             /* Get next CNID from rebuild db */
704             if ((ret = dbif_idwalk(dbd_rebuild, &rebuild_cnid, 0)) == -1) {
705                 /* Some error */
706                 goto cleanup;
707             } else if (ret == 0) {
708                 /* end of rebuild_cnid, delete all remaining CNIDs from dbd */
709                 while ((dbif_idwalk(dbd, &dbd_cnid, 0)) == 1) {
710                     dbd_log(LOGSTD, "Orphaned CNID in database: %u", dbd_cnid);
711                     if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
712                         rqst.cnid = htonl(dbd_cnid);
713                         ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
714                         dbif_txn_close(dbd, ret);
715                         deleted++;
716                     }
717                 }
718                 return;
719             } else
720                 /* Normal case (ret=1): continue while loop */
721                 continue;
722         }
723
724         if (dbd_cnid < rebuild_cnid) {
725             /* CNID is orphaned -> delete */
726             dbd_log(LOGSTD, "Orphaned CNID in database: %u.", dbd_cnid);
727             if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
728                 rqst.cnid = htonl(dbd_cnid);
729                 ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
730                 dbif_txn_close(dbd, ret);
731                 deleted++;
732             }
733             continue;
734         }
735
736         if (dbd_cnid > rebuild_cnid) {
737             dbd_log(LOGSTD, "Ghost CNID: %u. This is fatal! Dumping rebuild db:\n", rebuild_cnid);
738             dbif_dump(dbd_rebuild, 0);
739             dbd_log(LOGSTD, "Send this dump and a `dbd -d ...` dump to the Netatalk Dev team!");
740             dbif_txn_close(dbd, ret);
741             dbif_idwalk(dbd, NULL, 1); /* Close cursor */
742             dbif_idwalk(dbd_rebuild, NULL, 1); /* Close cursor */
743             goto cleanup;
744         }
745     }
746
747 cleanup:
748     dbif_idwalk(dbd, NULL, 1); /* Close cursor */
749     dbif_idwalk(dbd_rebuild, NULL, 1); /* Close cursor */
750     return;
751 }
752
753 /*
754   Main func called from cmd_dbd.c
755 */
756 int cmd_dbd_scanvol(DBD *dbd_ref, struct volinfo *volinfo, dbd_flags_t flags)
757 {
758     int ret = 0;
759
760     /* Make it accessible for all funcs */
761     dbd = dbd_ref;
762
763     /* We only support unicode volumes ! */
764     if ( volinfo->v_volcharset != CH_UTF8) {
765         dbd_log( LOGSTD, "Not a Unicode volume: %s, %u != %u", volinfo->v_volcodepage, volinfo->v_volcharset, CH_UTF8);
766         return -1;
767     }
768
769     /* Get volume stamp */
770     dbd_getstamp(dbd, &rqst, &rply);
771     if (rply.result != CNID_DBD_RES_OK)
772         goto exit_cleanup;
773     memcpy(stamp, rply.name, CNID_DEV_LEN);
774
775     /* open/create rebuild dbd, copy rootinfo key */
776     if (NULL == (dbd_rebuild = dbif_init(NULL, NULL)))
777         return -1;
778     if (0 != (dbif_open(dbd_rebuild, NULL, 0)))
779         return -1;
780     if (0 != (dbif_copy_rootinfokey(dbd, dbd_rebuild)))
781         goto exit_cleanup;
782
783     if (setjmp(jmp) != 0)
784         goto exit_cleanup;      /* Got signal, jump from dbd_readdir */
785
786     /* scanvol */
787     if ( (scanvol(volinfo, flags)) != 0)
788         return -1;
789
790     /* We can only do this in exclusive mode, otherwise we might delete CNIDs added from
791        other clients in between our pass 1 and 2 */
792     if (flags & DBD_FLAGS_EXCL)
793         delete_orphaned_cnids(dbd, dbd_rebuild, flags);
794
795 exit_cleanup:
796     dbif_close(dbd_rebuild);
797     return ret;
798 }