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