]> arthur.barton.de Git - netatalk.git/blob - etc/cnid_dbd/cmd_dbd_scanvol.c
be0aaf9c44883b34457323088b6a5e2c63a23e40
[netatalk.git] / etc / cnid_dbd / cmd_dbd_scanvol.c
1 /*
2   $Id: cmd_dbd_scanvol.c,v 1.20 2010-02-15 13:58:38 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
31 #include <atalk/adouble.h>
32 #include <atalk/unicode.h>
33 #include <atalk/volinfo.h>
34 #include <atalk/cnid_dbd_private.h>
35 #include <atalk/volume.h>
36 #include <atalk/ea.h>
37 #include <atalk/util.h>
38
39 #include "cmd_dbd.h"
40 #include "dbif.h"
41 #include "db_param.h"
42 #include "dbd.h"
43
44 /* Some defines to ease code parsing */
45 #define ADDIR_OK (addir_ok == 0)
46 #define ADFILE_OK (adfile_ok == 0)
47
48 /* These must be accessible for cmd_dbd_* funcs */
49 struct volinfo        *volinfo;
50 char                  cwdbuf[MAXPATHLEN+1];
51
52 /* Some static vars */
53 static DBD            *dbd;
54 static DBD            *dbd_rebuild;
55 static dbd_flags_t    dbd_flags;
56 static char           stamp[CNID_DEV_LEN];
57 static char           *netatalk_dirs[] = {
58     ".AppleDB",
59     ".AppleDesktop",
60     NULL
61 };
62 static struct cnid_dbd_rqst rqst;
63 static struct cnid_dbd_rply rply;
64 static jmp_buf jmp;
65 static struct vol volume; /* fake it for ea_open */
66 static char pname[MAXPATHLEN] = "../";
67
68 /*
69   Taken form afpd/desktop.c
70 */
71 static char *utompath(char *upath)
72 {
73     static char  mpath[ MAXPATHLEN + 2]; /* for convert_charset dest_len parameter +2 */
74     char         *m, *u;
75     uint16_t     flags = CONV_IGNORE | CONV_UNESCAPEHEX;
76     size_t       outlen;
77
78     if (!upath)
79         return NULL;
80
81     m = mpath;
82     u = upath;
83     outlen = strlen(upath);
84
85     if ((volinfo->v_casefold & AFPVOL_UTOMUPPER))
86         flags |= CONV_TOUPPER;
87     else if ((volinfo->v_casefold & AFPVOL_UTOMLOWER))
88         flags |= CONV_TOLOWER;
89
90     if ((volinfo->v_flags & AFPVOL_EILSEQ)) {
91         flags |= CONV__EILSEQ;
92     }
93
94     /* convert charsets */
95     if ((size_t)-1 == ( outlen = convert_charset(volinfo->v_volcharset,
96                                                  CH_UTF8_MAC,
97                                                  volinfo->v_maccharset,
98                                                  u, outlen, mpath, MAXPATHLEN, &flags)) ) {
99         dbd_log( LOGSTD, "Conversion from %s to %s for %s failed.",
100                  volinfo->v_volcodepage, volinfo->v_maccodepage, u);
101         return NULL;
102     }
103
104     return(m);
105 }
106
107 /*
108   Taken form afpd/desktop.c
109 */
110 static char *mtoupath(char *mpath)
111 {
112     static char  upath[ MAXPATHLEN + 2]; /* for convert_charset dest_len parameter +2 */
113     char    *m, *u;
114     size_t       inplen;
115     size_t       outlen;
116     u_int16_t    flags = 0;
117
118     if (!mpath)
119         return NULL;
120
121     if ( *mpath == '\0' ) {
122         return( "." );
123     }
124
125     /* set conversion flags */
126     if (!(volinfo->v_flags & AFPVOL_NOHEX))
127         flags |= CONV_ESCAPEHEX;
128     if (!(volinfo->v_flags & AFPVOL_USEDOTS))
129         flags |= CONV_ESCAPEDOTS;
130
131     if ((volinfo->v_casefold & AFPVOL_MTOUUPPER))
132         flags |= CONV_TOUPPER;
133     else if ((volinfo->v_casefold & AFPVOL_MTOULOWER))
134         flags |= CONV_TOLOWER;
135
136     if ((volinfo->v_flags & AFPVOL_EILSEQ)) {
137         flags |= CONV__EILSEQ;
138     }
139
140     m = mpath;
141     u = upath;
142
143     inplen = strlen(m);
144     outlen = MAXPATHLEN;
145
146     if ((size_t)-1 == (outlen = convert_charset(CH_UTF8_MAC,
147                                                 volinfo->v_volcharset,
148                                                 volinfo->v_maccharset,
149                                                 m, inplen, u, outlen, &flags)) ) {
150         dbd_log( LOGSTD, "conversion from UTF8-MAC to %s for %s failed.",
151                  volinfo->v_volcodepage, mpath);
152         return NULL;
153     }
154
155     return( upath );
156 }
157
158 #if 0
159 /* 
160    Check if "name" is pointing to
161    a) an object inside the current volume (return 0)
162    b) an object outside the current volume (return 1)
163    Then stats the pointed to object and if it is a dir ors ADFLAGS_DIR to *adflags
164    Return -1 on any serious error.
165  */
166 static int check_symlink(const char *name, int *adflags)
167 {
168     int cwd;
169     ssize_t len;
170     char pathbuf[MAXPATHLEN + 1];
171     char *sep;
172     struct stat st;
173
174     if ((len = readlink(name, pathbuf, MAXPATHLEN)) == -1) {
175         dbd_log(LOGSTD, "Error reading link info for '%s/%s': %s", 
176                 cwdbuf, name, strerror(errno));
177         return -1;
178     }
179     pathbuf[len] = 0;
180
181     if ((stat(pathbuf, &st)) != 0) {
182         dbd_log(LOGSTD, "stat error '%s': %s", pathbuf, strerror(errno));
183     }
184
185     /* Remember cwd */
186     if ((cwd = open(".", O_RDONLY)) < 0) {
187         dbd_log(LOGSTD, "error opening cwd '%s': %s", cwdbuf, strerror(errno));
188         return -1;
189     }
190
191     if (S_ISDIR(st.st_mode)) {
192         *adflags |= ADFLAGS_DIR;
193     } else { /* file */
194         /* get basename from path */
195         if ((sep = strrchr(pathbuf, '/')) == NULL)
196             /* just a file at the same level */
197             return 0;
198         sep = 0; /* pathbuf now contains the directory*/
199     }
200
201     /* fchdir() to pathbuf so we can easily get its path with getcwd() */
202     if ((chdir(pathbuf)) != 0) {
203         dbd_log(LOGSTD, "Cant chdir to '%s': %s", pathbuf, strerror(errno));
204         return -1;
205     }
206
207     if ((getcwd(pathbuf, MAXPATHLEN)) == NULL) {
208         dbd_log(LOGSTD, "Cant get symlink'ed dir '%s/%s': %s", cwdbuf, pathbuf, strerror(errno));
209         if ((fchdir(cwd)) != 0)
210             /* We're foobared */
211             longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
212         return -1;
213     }
214
215     if ((fchdir(cwd)) != 0)
216         /* We're foobared */
217         longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
218
219     /*
220       We now have the symlink target dir as absoulte path in pathbuf
221       and can compare it with the currents volume path
222     */
223     int i = 0;
224     while (volinfo->v_path[i]) {
225         if ((pathbuf[i] == 0) || (volinfo->v_path[i] != pathbuf[i])) {
226             dbd_log( LOGDEBUG, "extra-share symlink '%s/%s', following", cwdbuf, name);
227             return 1;
228         }
229         i++;
230     }
231
232     dbd_log( LOGDEBUG, "intra-share symlink '%s/%s', not following", cwdbuf, name);
233     return 0;
234 }
235 #endif
236
237 /*
238   Check for wrong encoding e.g. "." at the beginning is not CAP encoded (:2e) although volume is default !AFPVOL_USEDOTS.
239   We do it by roundtripiping from volcharset to UTF8-MAC and back and then compare the result.
240 */
241 static int check_name_encoding(char *uname)
242 {
243     char *roundtripped;
244
245     roundtripped = mtoupath(utompath(uname));
246     if (!roundtripped) {
247         dbd_log( LOGSTD, "Error checking encoding for '%s/%s'", cwdbuf, uname);
248         return -1;
249     }
250
251     if ( STRCMP(uname, !=, roundtripped)) {
252         dbd_log( LOGSTD, "Bad encoding for '%s/%s'", cwdbuf, uname);
253         return -1;
254     }
255
256     return 0;
257 }
258
259 /*
260   Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop"
261   Returns pointer to name or NULL.
262 */
263 static const char *check_netatalk_dirs(const char *name)
264 {
265     int c;
266
267     for (c=0; netatalk_dirs[c]; c++) {
268         if ((strcmp(name, netatalk_dirs[c])) == 0)
269             return netatalk_dirs[c];
270     }
271     return NULL;
272 }
273
274 /*
275   Check for .AppleDouble file, create if missing
276 */
277 static int check_adfile(const char *fname, const struct stat *st)
278 {
279     int ret, adflags;
280     struct adouble ad;
281     char *adname;
282
283     if (S_ISREG(st->st_mode))
284         adflags = 0;
285     else
286         adflags = ADFLAGS_DIR;
287
288     adname = volinfo->ad_path(fname, adflags);
289
290     if ((ret = access( adname, F_OK)) != 0) {
291         if (errno != ENOENT) {
292             dbd_log(LOGSTD, "Access error for ad-file '%s/%s': %s",
293                     cwdbuf, adname, strerror(errno));
294             return -1;
295         }
296         /* Missing. Log and create it */
297         dbd_log(LOGSTD, "Missing AppleDouble file '%s/%s'", cwdbuf, adname);
298
299         if (dbd_flags & DBD_FLAGS_SCAN)
300             /* Scan only requested, dont change anything */
301             return -1;
302
303         /* Create ad file */
304         ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
305
306         if ((ret = ad_open_metadata( fname, adflags, O_CREAT, &ad)) != 0) {
307             dbd_log( LOGSTD, "Error creating AppleDouble file '%s/%s': %s",
308                      cwdbuf, adname, strerror(errno));
309
310             return -1;
311         }
312
313         /* Set name in ad-file */
314         ad_setname(&ad, utompath((char *)fname));
315         ad_flush(&ad);
316         ad_close_metadata(&ad);
317
318         chown(adname, st->st_uid, st->st_gid);
319         /* FIXME: should we inherit mode too here ? */
320 #if 0
321         chmod(adname, st->st_mode);
322 #endif
323     } else {
324         ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
325         if (ad_open_metadata( fname, adflags, O_RDONLY, &ad) != 0) {
326             dbd_log( LOGSTD, "Error opening AppleDouble file for '%s/%s'", cwdbuf, fname);
327             return -1;
328         }
329         ad_close_metadata(&ad);
330     }
331     return 0;
332 }
333
334 /* 
335    Remove all files with file::EA* from adouble dir
336 */
337 static void remove_eafiles(const char *name, struct ea *ea)
338 {
339     DIR *dp = NULL;
340     struct dirent *ep;
341     char eaname[MAXPATHLEN];
342
343     strlcpy(eaname, name, sizeof(eaname));
344     if (strlcat(eaname, "::EA", sizeof(eaname)) >= sizeof(eaname)) {
345         dbd_log(LOGSTD, "name too long: '%s/%s/%s'", cwdbuf, ADv2_DIRNAME, name);
346         return;
347     }
348
349     if ((chdir(ADv2_DIRNAME)) != 0) {
350         dbd_log(LOGSTD, "Couldn't chdir to '%s/%s': %s",
351                 cwdbuf, ADv2_DIRNAME, strerror(errno));
352         return;
353     }
354
355     if ((dp = opendir(".")) == NULL) {
356         dbd_log(LOGSTD, "Couldn't open the directory '%s/%s': %s",
357                 cwdbuf, ADv2_DIRNAME, strerror(errno));
358         return;
359     }
360
361     while ((ep = readdir(dp))) {
362         if (strstr(ep->d_name, eaname) != NULL) {
363             dbd_log(LOGSTD, "Removing EA file: '%s/%s/%s'",
364                     cwdbuf, ADv2_DIRNAME, ep->d_name);
365             if ((unlink(ep->d_name)) != 0) {
366                 dbd_log(LOGSTD, "Error unlinking EA file '%s/%s/%s': %s",
367                         cwdbuf, ADv2_DIRNAME, ep->d_name, strerror(errno));
368             }
369         } /* if */
370     } /* while */
371
372     if (dp)
373         closedir(dp);
374
375 }
376
377 /*
378   Check Extended Attributes files
379 */
380 static int check_eafiles(const char *fname)
381 {
382     unsigned int  count = 0;
383     int ret = 0, remove;
384     struct ea ea;
385     struct stat st;
386     char *eaname;
387
388     if ((ret = ea_open(&volume, fname, EA_RDWR, &ea)) != 0) {
389         if (errno == ENOENT)
390             return 0;
391         dbd_log(LOGSTD, "Error calling ea_open for file: %s/%s, removing EA files",
392                 cwdbuf, fname);
393         if ( ! (dbd_flags & DBD_FLAGS_SCAN))
394             remove_eafiles(fname, &ea);
395         return -1;
396     }
397
398     /* Check all EAs */
399     while (count < ea.ea_count) {        
400         dbd_log(LOGDEBUG, "EA: %s", (*ea.ea_entries)[count].ea_name);
401         remove = 0;
402
403         eaname = ea_path(&ea, (*ea.ea_entries)[count].ea_name, 0);
404
405         if (lstat(eaname, &st) != 0) {
406             if (errno == ENOENT)
407                 dbd_log(LOGSTD, "Missing EA: %s/%s", cwdbuf, eaname);
408             else
409                 dbd_log(LOGSTD, "Bogus EA: %s/%s", cwdbuf, eaname);
410             remove = 1;
411         } else if (st.st_size != (*ea.ea_entries)[count].ea_size) {
412             dbd_log(LOGSTD, "Bogus EA: %s/%s, removing it...", cwdbuf, eaname);
413             remove = 1;
414             if ((unlink(eaname)) != 0)
415                 dbd_log(LOGSTD, "Error removing EA file '%s/%s': %s",
416                         cwdbuf, eaname, strerror(errno));
417         }
418
419         if (remove) {
420             /* Be CAREFUL here! This should do what ea_delentry does. ea_close relies on it !*/
421             free((*ea.ea_entries)[count].ea_name);
422             (*ea.ea_entries)[count].ea_name = NULL;
423         }
424
425         count++;
426     } /* while */
427
428     ea_close(&ea);
429     return ret;
430 }
431
432 /*
433   Check for .AppleDouble folder and .Parent, create if missing
434 */
435 static int check_addir(int volroot)
436 {
437     int addir_ok, adpar_ok;
438     struct stat st;
439     struct adouble ad;
440     char *mname = NULL;
441
442     /* Check for ad-dir */
443     if ( (addir_ok = access(ADv2_DIRNAME, F_OK)) != 0) {
444         if (errno != ENOENT) {
445             dbd_log(LOGSTD, "Access error in directory %s: %s", cwdbuf, strerror(errno));
446             return -1;
447         }
448         dbd_log(LOGSTD, "Missing %s for '%s'", ADv2_DIRNAME, cwdbuf);
449     }
450
451     /* Check for ".Parent" */
452     if ( (adpar_ok = access(volinfo->ad_path(".", ADFLAGS_DIR), F_OK)) != 0) {
453         if (errno != ENOENT) {
454             dbd_log(LOGSTD, "Access error on '%s/%s': %s",
455                     cwdbuf, volinfo->ad_path(".", ADFLAGS_DIR), strerror(errno));
456             return -1;
457         }
458         dbd_log(LOGSTD, "Missing .AppleDouble/.Parent for '%s'", cwdbuf);
459     }
460
461     /* Is one missing ? */
462     if ((addir_ok != 0) || (adpar_ok != 0)) {
463         /* Yes, but are we only scanning ? */
464         if (dbd_flags & DBD_FLAGS_SCAN) {
465             /* Yes:  missing .Parent is not a problem, but missing ad-dir
466                causes later checking of ad-files to fail. So we have to return appropiately */
467             if (addir_ok != 0)
468                 return -1;
469             else  /* (adpar_ok != 0) */
470                 return 0;
471         }
472
473         /* Create ad dir and set name */
474         ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
475
476         if (ad_open_metadata( ".", ADFLAGS_DIR, O_CREAT, &ad) != 0) {
477             dbd_log( LOGSTD, "Error creating AppleDouble dir in %s: %s", cwdbuf, strerror(errno));
478             return -1;
479         }
480
481         /* Get basename of cwd from cwdbuf */
482         mname = utompath(strrchr(cwdbuf, '/') + 1);
483
484         /* Update name in ad file */
485         ad_setname(&ad, mname);
486         ad_flush(&ad);
487         ad_close_metadata(&ad);
488
489         /* Inherit owner/group from "." to ".AppleDouble" and ".Parent" */
490         if ((lstat(".", &st)) != 0) {
491             dbd_log( LOGSTD, "Couldnt stat %s: %s", cwdbuf, strerror(errno));
492             return -1;
493         }
494         chown(ADv2_DIRNAME, st.st_uid, st.st_gid);
495         chown(volinfo->ad_path(".", ADFLAGS_DIR), st.st_uid, st.st_gid);
496     }
497
498     return 0;
499 }
500
501 /*
502   Check if file cotains "::EA" and if it does check if its correspondig data fork exists.
503   Returns:
504   0 = name is not an EA file
505   1 = name is an EA file and no problem was found
506   -1 = name is an EA file and data fork is gone
507  */
508 static int check_eafile_in_adouble(const char *name)
509 {
510     int ret = 0;
511     char *namep, *namedup = NULL;
512
513     /* Check if this is an AFPVOL_EA_AD vol */
514     if (volinfo->v_vfs_ea == AFPVOL_EA_AD) {
515         /* Does the filename contain "::EA" ? */
516         namedup = strdup(name);
517         if ((namep = strstr(namedup, "::EA")) == NULL) {
518             ret = 0;
519             goto ea_check_done;
520         } else {
521             /* File contains "::EA" so it's an EA file. Check for data file  */
522
523             /* Get string before "::EA" from EA filename */
524             namep[0] = 0;
525             strlcpy(pname + 3, namedup, sizeof(pname)); /* Prepends "../" */
526
527             if ((access( pname, F_OK)) == 0) {
528                 ret = 1;
529                 goto ea_check_done;
530             } else {
531                 ret = -1;
532                 if (errno != ENOENT) {
533                     dbd_log(LOGSTD, "Access error for file '%s/%s': %s",
534                             cwdbuf, name, strerror(errno));
535                     goto ea_check_done;
536                 }
537
538                 /* Orphaned EA file*/
539                 dbd_log(LOGSTD, "Orphaned Extended Attribute file '%s/%s/%s'",
540                         cwdbuf, ADv2_DIRNAME, name);
541
542                 if (dbd_flags & DBD_FLAGS_SCAN)
543                     /* Scan only requested, dont change anything */
544                     goto ea_check_done;
545
546                 if ((unlink(name)) != 0) {
547                     dbd_log(LOGSTD, "Error unlinking orphaned Extended Attribute file '%s/%s/%s'",
548                             cwdbuf, ADv2_DIRNAME, name);
549                 }
550             } /* if (access) */
551         } /* if strstr */
552     } /* if AFPVOL_EA_AD */
553
554 ea_check_done:
555     if (namedup)
556         free(namedup);
557
558     return ret;
559 }
560
561 /*
562   Check files and dirs inside .AppleDouble folder:
563   - remove orphaned files
564   - bail on dirs
565 */
566 static int read_addir(void)
567 {
568     DIR *dp;
569     struct dirent *ep;
570     struct stat st;
571
572     if ((chdir(ADv2_DIRNAME)) != 0) {
573         dbd_log(LOGSTD, "Couldn't chdir to '%s/%s': %s",
574                 cwdbuf, ADv2_DIRNAME, strerror(errno));
575         return -1;
576     }
577
578     if ((dp = opendir(".")) == NULL) {
579         dbd_log(LOGSTD, "Couldn't open the directory '%s/%s': %s",
580                 cwdbuf, ADv2_DIRNAME, strerror(errno));
581         return -1;
582     }
583
584     while ((ep = readdir(dp))) {
585         /* Check if its "." or ".." */
586         if (DIR_DOT_OR_DOTDOT(ep->d_name))
587             continue;
588         /* Skip ".Parent" */
589         if (STRCMP(ep->d_name, ==, ".Parent"))
590             continue;
591
592         if ((lstat(ep->d_name, &st)) < 0) {
593             dbd_log( LOGSTD, "Lost file or dir while enumeratin dir '%s/%s/%s', probably removed: %s",
594                      cwdbuf, ADv2_DIRNAME, ep->d_name, strerror(errno));
595             continue;
596         }
597
598         /* Check for dirs */
599         if (S_ISDIR(st.st_mode)) {
600             dbd_log( LOGSTD, "Unexpected directory '%s' in AppleDouble dir '%s/%s'",
601                      ep->d_name, cwdbuf, ADv2_DIRNAME);
602             continue;
603         }
604
605         /* Check if for orphaned and corrupt Extended Attributes file */
606         if (check_eafile_in_adouble(ep->d_name) != 0)
607             continue;
608
609         /* Check for data file */
610         strcpy(pname + 3, ep->d_name);
611         if ((access( pname, F_OK)) != 0) {
612             if (errno != ENOENT) {
613                 dbd_log(LOGSTD, "Access error for file '%s/%s': %s",
614                         cwdbuf, pname, strerror(errno));
615                 continue;
616             }
617             /* Orphaned ad-file*/
618             dbd_log(LOGSTD, "Orphaned AppleDoube file '%s/%s/%s'",
619                     cwdbuf, ADv2_DIRNAME, ep->d_name);
620
621             if (dbd_flags & DBD_FLAGS_SCAN)
622                 /* Scan only requested, dont change anything */
623                 continue;;
624
625             if ((unlink(ep->d_name)) != 0) {
626                 dbd_log(LOGSTD, "Error unlinking orphaned AppleDoube file '%s/%s/%s'",
627                         cwdbuf, ADv2_DIRNAME, ep->d_name);
628
629             }
630         }
631     }
632
633     if ((chdir("..")) != 0) {
634         dbd_log(LOGSTD, "Couldn't chdir back to '%s' from AppleDouble dir: %s",
635                 cwdbuf, strerror(errno));
636         /* This really is EOT! */
637         longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
638     }
639
640     closedir(dp);
641
642     return 0;
643 }
644
645 /*
646   Check CNID for a file/dir, both from db and from ad-file.
647   For detailed specs see intro.
648 */
649 static cnid_t check_cnid(const char *name, cnid_t did, struct stat *st, int adfile_ok, int adflags)
650 {
651     int ret;
652     cnid_t db_cnid, ad_cnid;
653     struct adouble ad;
654
655     /* Get CNID from ad-file if volume is using AFPVOL_CACHE */
656     ad_cnid = 0;
657     if ( (volinfo->v_flags & AFPVOL_CACHE) && ADFILE_OK) {
658         ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
659         if (ad_open_metadata( name, adflags, O_RDWR, &ad) != 0) {
660             dbd_log( LOGSTD, "Error opening AppleDouble file for '%s/%s': %s", cwdbuf, name, strerror(errno));
661             return 0;
662         }
663
664         if (dbd_flags & DBD_FLAGS_FORCE) {
665             ad_cnid = ad_forcegetid(&ad);
666             /* This ensures the changed stamp is written */
667             ad_setid( &ad, st->st_dev, st->st_ino, ad_cnid, did, stamp);
668             ad_flush(&ad);
669         }
670         else
671             ad_cnid = ad_getid(&ad, st->st_dev, st->st_ino, did, stamp);
672
673         if (ad_cnid == 0)
674             dbd_log( LOGSTD, "Incorrect CNID data in .AppleDouble data for '%s/%s' (bad stamp?)", cwdbuf, name);
675         else
676             dbd_log( LOGDEBUG, "CNID from .AppleDouble file for '%s/%s': %u", cwdbuf, name, ntohl(ad_cnid));
677
678         ad_close_metadata(&ad);
679     }
680
681     /* Get CNID from database */
682
683     /* Prepare request data */
684     memset(&rqst, 0, sizeof(struct cnid_dbd_rqst));
685     memset(&rply, 0, sizeof(struct cnid_dbd_rply));
686     rqst.did = did;
687     rqst.cnid = ad_cnid;
688     if ( ! (volinfo->v_flags & AFPVOL_NODEV))
689         rqst.dev = st->st_dev;
690     rqst.ino = st->st_ino;
691     rqst.type = S_ISDIR(st->st_mode)?1:0;
692     rqst.name = (char *)name;
693     rqst.namelen = strlen(name);
694
695     /* Query the database */
696     ret = dbd_lookup(dbd, &rqst, &rply, (dbd_flags & DBD_FLAGS_SCAN) ? 1 : 0);
697     dbif_txn_close(dbd, ret);
698     if (rply.result == CNID_DBD_RES_OK) {
699         db_cnid = rply.cnid;
700     } else if (rply.result == CNID_DBD_RES_NOTFOUND) {
701         if ( ! (dbd_flags & DBD_FLAGS_FORCE))
702             dbd_log( LOGSTD, "No CNID for '%s/%s' in database", cwdbuf, name);
703         db_cnid = 0;
704     } else {
705         dbd_log( LOGSTD, "Fatal error resolving '%s/%s'", cwdbuf, name);
706         db_cnid = 0;
707     }
708
709     /* Compare results from both CNID searches */
710     if (ad_cnid && db_cnid && (ad_cnid == db_cnid)) {
711         /* Everything is fine */
712         return db_cnid;
713     } else if (ad_cnid && db_cnid && (ad_cnid != db_cnid)) {
714         /* Mismatch ? Delete both from db and re-add data from file */
715         dbd_log( LOGSTD, "CNID mismatch for '%s/%s', db: %u, ad-file: %u", cwdbuf, name, ntohl(db_cnid), ntohl(ad_cnid));
716         if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
717             rqst.cnid = db_cnid;
718             ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
719             dbif_txn_close(dbd, ret);
720
721             rqst.cnid = ad_cnid;
722             ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
723             dbif_txn_close(dbd, ret);
724
725             ret = dbd_rebuild_add(dbd, &rqst, &rply);
726             dbif_txn_close(dbd, ret);
727         }
728         return ad_cnid;
729     } else if (ad_cnid && (db_cnid == 0)) {
730         /* in ad-file but not in db */
731         if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
732             dbd_log( LOGDEBUG, "CNID rebuild add for '%s/%s', adding with CNID from ad-file: %u", cwdbuf, name, ntohl(ad_cnid));
733             rqst.cnid = ad_cnid;
734             ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
735             dbif_txn_close(dbd, ret);
736             ret = dbd_rebuild_add(dbd, &rqst, &rply);
737             dbif_txn_close(dbd, ret);
738         }
739         return ad_cnid;
740     } else if ((db_cnid == 0) && (ad_cnid == 0)) {
741         /* No CNID at all, we clearly have to allocate a fresh one... */
742         /* Note: the next test will use this new CNID too! */
743         if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
744             /* add to db */
745             ret = dbd_add(dbd, &rqst, &rply, 1);
746             dbif_txn_close(dbd, ret);
747             db_cnid = rply.cnid;
748             dbd_log( LOGSTD, "New CNID for '%s/%s': %u", cwdbuf, name, ntohl(db_cnid));
749         }
750     }
751
752     if ((ad_cnid == 0) && db_cnid) {
753         /* in db but zeroID in ad-file, write it to ad-file if AFPVOL_CACHE */
754         if ((volinfo->v_flags & AFPVOL_CACHE) && ADFILE_OK) {
755             if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
756                 dbd_log( LOGSTD, "Writing CNID data for '%s/%s' to AppleDouble file", cwdbuf, name, ntohl(db_cnid));
757                 ad_init(&ad, volinfo->v_adouble, volinfo->v_ad_options);
758                 if (ad_open_metadata( name, adflags, O_RDWR, &ad) != 0) {
759                     dbd_log( LOGSTD, "Error opening AppleDouble file for '%s/%s': %s", cwdbuf, name, strerror(errno));
760                     return 0;
761                 }
762                 ad_setid( &ad, st->st_dev, st->st_ino, db_cnid, did, stamp);
763                 ad_flush(&ad);
764                 ad_close_metadata(&ad);
765             }
766         }
767         return db_cnid;
768     }
769
770     return 0;
771 }
772
773 /*
774   This is called recursively for all dirs.
775   volroot=1 means we're in the volume root dir, 0 means we aren't.
776   We use this when checking for netatalk private folders like .AppleDB.
777   did is our parents CNID.
778 */
779 static int dbd_readdir(int volroot, cnid_t did)
780 {
781     int cwd, ret = 0, adflags, adfile_ok, addir_ok, encoding_ok;
782     cnid_t cnid = 0;
783     const char *name;
784     DIR *dp;
785     struct dirent *ep;
786     static struct stat st;      /* Save some stack space */
787
788     /* Check again for .AppleDouble folder, check_adfile also checks/creates it */
789     if ((addir_ok = check_addir(volroot)) != 0)
790         if ( ! (dbd_flags & DBD_FLAGS_SCAN))
791             /* Fatal on rebuild run, continue if only scanning ! */
792             return -1;
793
794     /* Check AppleDouble files in AppleDouble folder, but only if it exists or could be created */
795     if (ADDIR_OK)
796         if ((read_addir()) != 0)
797             if ( ! (dbd_flags & DBD_FLAGS_SCAN))
798                 /* Fatal on rebuild run, continue if only scanning ! */
799                 return -1;
800
801     if ((dp = opendir (".")) == NULL) {
802         dbd_log(LOGSTD, "Couldn't open the directory: %s",strerror(errno));
803         return -1;
804     }
805
806     while ((ep = readdir (dp))) {
807         /* Check if we got a termination signal */
808         if (alarmed)
809             longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
810
811         /* Check if its "." or ".." */
812         if (DIR_DOT_OR_DOTDOT(ep->d_name))
813             continue;
814
815         /* Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop" */
816         if ((name = check_netatalk_dirs(ep->d_name)) != NULL) {
817             if (! volroot)
818                 dbd_log(LOGSTD, "Nested %s in %s", name, cwdbuf);
819             continue;
820         }
821
822         /* Skip .AppleDouble dir in this loop */
823         if (STRCMP(ep->d_name, == , ADv2_DIRNAME))
824             continue;
825
826         if ((ret = lstat(ep->d_name, &st)) < 0) {
827             dbd_log( LOGSTD, "Lost file while reading dir '%s/%s', probably removed: %s", cwdbuf, ep->d_name, strerror(errno));
828             continue;
829         }
830         
831         switch (st.st_mode & S_IFMT) {
832         case S_IFREG:
833             adflags = 0;
834             break;
835         case S_IFDIR:
836             adflags = ADFLAGS_DIR;
837             break;
838         case S_IFLNK:
839             dbd_log(LOGDEBUG, "Ignoring symlink %s/%s", cwdbuf, ep->d_name);
840 #if 0
841             ret = check_symlink(ep->d_name, &adflags);
842             if (ret == 1)
843                 break;
844             if (ret == -1)
845                 dbd_log(LOGSTD, "Error checking symlink %s/%s", cwdbuf, ep->d_name);
846 #endif
847             continue;
848         default:
849             dbd_log(LOGSTD, "Bad filetype: %s/%s", cwdbuf, ep->d_name);
850             if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
851                 if ((unlink(ep->d_name)) != 0) {
852                     dbd_log(LOGSTD, "Error removing: %s/%s: %s", cwdbuf, ep->d_name, strerror(errno));
853                 }
854             }
855             continue;
856         }
857
858         /**************************************************************************
859            Tests
860         **************************************************************************/
861
862         /* Check encoding */
863         if ( -1 == (encoding_ok = check_name_encoding(ep->d_name)) ) {
864             /* If its a file: skipp all other tests now ! */
865             /* For dirs we could try to get a CNID for it and recurse, but currently I prefer not to */
866             continue;
867         }
868
869         /* Check for appledouble file, create if missing, but only if we have addir */
870         adfile_ok = -1;
871         if (ADDIR_OK)
872             adfile_ok = check_adfile(ep->d_name, &st);
873
874         if ( ! nocniddb) {
875             /* Check CNIDs */
876             cnid = check_cnid(ep->d_name, did, &st, adfile_ok, adflags);
877
878             /* Now add this object to our rebuild dbd */
879             if (cnid) {
880                 rqst.cnid = rply.cnid;
881                 dbd_rebuild_add(dbd_rebuild, &rqst, &rply);
882                 if (rply.result != CNID_DBD_RES_OK) {
883                     dbd_log( LOGDEBUG, "Fatal error adding CNID: %u for '%s/%s' to in-memory rebuild-db",
884                              cnid, cwdbuf, ep->d_name);
885                     longjmp(jmp, 1); /* this jumps back to cmd_dbd_scanvol() */
886                 }
887             }
888         }
889
890         /* Check EA files */
891         if (volinfo->v_vfs_ea == AFPVOL_EA_AD)
892             check_eafiles(ep->d_name);
893
894         /**************************************************************************
895           Recursion
896         **************************************************************************/
897         if (S_ISDIR(st.st_mode) && (cnid || nocniddb)) { /* If we have no cnid for it we cant recur */
898             strcat(cwdbuf, "/");
899             strcat(cwdbuf, ep->d_name);
900             dbd_log( LOGDEBUG, "Entering directory: %s", cwdbuf);
901             if (-1 == (cwd = open(".", O_RDONLY))) {
902                 dbd_log( LOGSTD, "Cant open directory '%s': %s", cwdbuf, strerror(errno));
903                 continue;
904             }
905             if (0 != chdir(ep->d_name)) {
906                 dbd_log( LOGSTD, "Cant chdir to directory '%s': %s", cwdbuf, strerror(errno));
907                 close(cwd);
908                 continue;
909             }
910
911             ret = dbd_readdir(0, cnid);
912
913             fchdir(cwd);
914             close(cwd);
915             *(strrchr(cwdbuf, '/')) = 0;
916             if (ret < 0)
917                 continue;
918         }
919     }
920
921     /*
922       Use results of previous checks
923     */
924
925     closedir(dp);
926     return ret;
927 }
928
929 static int scanvol(struct volinfo *vi, dbd_flags_t flags)
930 {
931     /* Dont scanvol on no-appledouble vols */
932     if (vi->v_flags & AFPVOL_NOADOUBLE) {
933         dbd_log( LOGSTD, "Volume without AppleDouble support: skipping volume scanning.");
934         return 0;
935     }
936
937     /* Make this stuff accessible from all funcs easily */
938     volinfo = vi;
939     dbd_flags = flags;
940
941     /* Init a fake struct vol with just enough so we can call ea_open and friends */
942     volume.v_adouble = AD_VERSION2;
943     volume.v_vfs_ea = volinfo->v_vfs_ea;
944     initvol_vfs(&volume);
945
946     /* Run with umask 0 */
947     umask(0);
948
949     /* Remove trailing slash from volume, chdir to vol */
950     if (volinfo->v_path[strlen(volinfo->v_path) - 1] == '/')
951         volinfo->v_path[strlen(volinfo->v_path) - 1] = 0;
952     strcpy(cwdbuf, volinfo->v_path);
953     chdir(volinfo->v_path);
954
955     /* Start recursion */
956     if (dbd_readdir(1, htonl(2)) < 0)  /* 2 = volumeroot CNID */
957         return -1;
958
959     return 0;
960 }
961
962 /*
963   Remove all CNIDs from dbd that are not in dbd_rebuild
964 */
965 static void delete_orphaned_cnids(DBD *dbd, DBD *dbd_rebuild, dbd_flags_t flags)
966 {
967     int ret, deleted = 0;
968     cnid_t dbd_cnid = 0, rebuild_cnid = 0;
969     struct cnid_dbd_rqst rqst;
970     struct cnid_dbd_rply rply;
971
972     /* jump over rootinfo key */
973     if ( dbif_idwalk(dbd, &dbd_cnid, 0) != 1)
974         return;
975     if ( dbif_idwalk(dbd_rebuild, &rebuild_cnid, 0) != 1)
976         return;
977
978     /* Get first id from dbd_rebuild */
979     if ((dbif_idwalk(dbd_rebuild, &rebuild_cnid, 0)) == -1)
980         return;
981
982     /* Start main loop through dbd: get CNID from dbd */
983     while ((dbif_idwalk(dbd, &dbd_cnid, 0)) == 1) {
984
985         if (deleted > 50) {
986             deleted = 0;
987             if (dbif_txn_checkpoint(dbd, 0, 0, 0) < 0) {
988                 dbd_log(LOGSTD, "Error checkpointing!");
989                 goto cleanup;
990             }
991         }
992
993         /* This should be the normal case: CNID is in both dbs */
994         if (dbd_cnid == rebuild_cnid) {
995             /* Get next CNID from rebuild db */
996             if ((ret = dbif_idwalk(dbd_rebuild, &rebuild_cnid, 0)) == -1) {
997                 /* Some error */
998                 goto cleanup;
999             } else if (ret == 0) {
1000                 /* end of rebuild_cnid, delete all remaining CNIDs from dbd */
1001                 while ((dbif_idwalk(dbd, &dbd_cnid, 0)) == 1) {
1002                     dbd_log(LOGSTD, "Orphaned CNID in database: %u", dbd_cnid);
1003                     if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
1004                         rqst.cnid = htonl(dbd_cnid);
1005                         ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
1006                         dbif_txn_close(dbd, ret);
1007                         deleted++;
1008                     }
1009                 }
1010                 return;
1011             } else
1012                 /* Normal case (ret=1): continue while loop */
1013                 continue;
1014         }
1015
1016         if (dbd_cnid < rebuild_cnid) {
1017             /* CNID is orphaned -> delete */
1018             dbd_log(LOGSTD, "Orphaned CNID in database: %u.", dbd_cnid);
1019             if ( ! (dbd_flags & DBD_FLAGS_SCAN)) {
1020                 rqst.cnid = htonl(dbd_cnid);
1021                 ret = dbd_delete(dbd, &rqst, &rply, DBIF_CNID);
1022                 dbif_txn_close(dbd, ret);
1023                 deleted++;
1024             }
1025             continue;
1026         }
1027
1028         if (dbd_cnid > rebuild_cnid) {
1029             dbd_log(LOGSTD, "Ghost CNID: %u. This is fatal! Dumping rebuild db:\n", rebuild_cnid);
1030             dbif_dump(dbd_rebuild, 0);
1031             dbd_log(LOGSTD, "Send this dump and a `dbd -d ...` dump to the Netatalk Dev team!");
1032             dbif_txn_close(dbd, ret);
1033             dbif_idwalk(dbd, NULL, 1); /* Close cursor */
1034             dbif_idwalk(dbd_rebuild, NULL, 1); /* Close cursor */
1035             goto cleanup;
1036         }
1037     }
1038
1039 cleanup:
1040     dbif_idwalk(dbd, NULL, 1); /* Close cursor */
1041     dbif_idwalk(dbd_rebuild, NULL, 1); /* Close cursor */
1042     return;
1043 }
1044
1045 /*
1046   Main func called from cmd_dbd.c
1047 */
1048 int cmd_dbd_scanvol(DBD *dbd_ref, struct volinfo *volinfo, dbd_flags_t flags)
1049 {
1050     int ret = 0;
1051     struct db_param db_param = { 0 };
1052
1053     /* Set cachesize for in-memory rebuild db */
1054     db_param.cachesize = 128 * 1024 * 1024; /* 128 MB */
1055
1056     /* Make it accessible for all funcs */
1057     dbd = dbd_ref;
1058
1059     /* We only support unicode volumes ! */
1060     if ( volinfo->v_volcharset != CH_UTF8) {
1061         dbd_log( LOGSTD, "Not a Unicode volume: %s, %u != %u", volinfo->v_volcodepage, volinfo->v_volcharset, CH_UTF8);
1062         return -1;
1063     }
1064
1065     if (! nocniddb) {
1066         /* Get volume stamp */
1067         dbd_getstamp(dbd, &rqst, &rply);
1068         if (rply.result != CNID_DBD_RES_OK)
1069             goto exit_cleanup;
1070         memcpy(stamp, rply.name, CNID_DEV_LEN);
1071
1072         /* open/create rebuild dbd, copy rootinfo key */
1073         if (NULL == (dbd_rebuild = dbif_init(NULL, NULL)))
1074             return -1;
1075         if (0 != (dbif_open(dbd_rebuild, &db_param, 0)))
1076             return -1;
1077         if (0 != (dbif_copy_rootinfokey(dbd, dbd_rebuild)))
1078             goto exit_cleanup;
1079     }
1080
1081     if (setjmp(jmp) != 0)
1082         goto exit_cleanup;      /* Got signal, jump from dbd_readdir */
1083
1084     /* scanvol */
1085     if ( (scanvol(volinfo, flags)) != 0)
1086         return -1;
1087
1088     if (! nocniddb) {
1089         /* We can only do this in exclusive mode, otherwise we might delete CNIDs added from
1090            other clients in between our pass 1 and 2 */
1091         if (flags & DBD_FLAGS_EXCL)
1092             delete_orphaned_cnids(dbd, dbd_rebuild, flags);
1093     }
1094
1095 exit_cleanup:
1096     if (! nocniddb)
1097         dbif_close(dbd_rebuild);
1098     return ret;
1099 }