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