]> arthur.barton.de Git - netatalk.git/blob - etc/cnid_dbd/cmd_dbd_scanvol.c
Fixes a serious error in the way recovery was run on db_env opening.
[netatalk.git] / etc / cnid_dbd / cmd_dbd_scanvol.c
1 /* 
2    $Id: cmd_dbd_scanvol.c,v 1.1 2009-05-06 11:54:24 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 /* 
18    dbd specs and implementation progress
19    =====================================
20
21    St Type Check (Status of implementation progress, type: file/dir)
22    -- ---- -----
23    OK D    Make sure .AppleDouble dir exist, create if missing. Error creating
24            it is fatal as that shouldn't happen as root
25    OK F    Make sure ad file exists
26    OK F/D  Delete orphaned ad-files, log dirs in ad-dir
27    OK F/D  Check name encoding by roundtripping, log on error
28    .. F/D  try: read CNID from ad file (if cnid caching is on)
29            try: fetch CNID from database
30            on mismatch: keep CNID from file, update database
31            on no CNID id ad file: write CNID from database to ad file
32            on no CNID in database: add CNID from ad file to database
33            on no CNID at all: create one and store in both places
34 */
35
36 /* FIXME: set id */
37 #if 0
38         ad_setid( &ad, s_path->st.st_dev, s_path->st.st_ino, dir->d_did, did, vol->v_stamp);
39 #endif
40
41 #ifdef HAVE_CONFIG_H
42 #include "config.h"
43 #endif /* HAVE_CONFIG_H */
44
45 #include <unistd.h>
46 #include <stdlib.h>
47 #include <sys/types.h>
48 #include <sys/stat.h>
49 #include <dirent.h>
50 #include <fcntl.h>
51 #include <string.h>
52 #include <errno.h>
53
54 #include <atalk/adouble.h>
55 #include <atalk/unicode.h>
56 #include <atalk/volinfo.h>
57 #include "cmd_dbd.h" 
58 #include "dbif.h"
59 #include "db_param.h"
60
61 static DBD            *dbd_rebuild;
62 static struct volinfo *volinfo;
63 static dbd_flags_t    dbd_flags;
64 static char           cwdbuf[MAXPATHLEN+1];
65 static char           *netatalk_dirs[] = {
66     ".AppleDB",
67     ".AppleDesktop",
68     NULL
69 };
70
71 /* 
72    Taken form afpd/desktop.c
73 */
74 static char *utompath(char *upath)
75 {
76     static char  mpath[ MAXPATHLEN + 2]; /* for convert_charset dest_len parameter +2 */
77     char         *m, *u;
78     uint16_t     flags = CONV_IGNORE | CONV_UNESCAPEHEX;
79     size_t       outlen;
80
81     if (!upath)
82         return NULL;
83
84     m = mpath;
85     u = upath;
86     outlen = strlen(upath);
87
88     if ((volinfo->v_casefold & AFPVOL_UTOMUPPER))
89         flags |= CONV_TOUPPER;
90     else if ((volinfo->v_casefold & AFPVOL_UTOMLOWER))
91         flags |= CONV_TOLOWER;
92
93     if ((volinfo->v_flags & AFPVOL_EILSEQ)) {
94         flags |= CONV__EILSEQ;
95     }
96
97     /* convert charsets */
98     if ((size_t)-1 == ( outlen = convert_charset(volinfo->v_volcharset,
99                                                  CH_UTF8_MAC, 
100                                                  volinfo->v_maccharset,
101                                                  u, outlen, mpath, MAXPATHLEN, &flags)) ) { 
102         dbd_log( LOGSTD, "Conversion from %s to %s for %s failed.",
103                  volinfo->v_volcodepage, volinfo->v_maccodepage, u);
104         return NULL;
105     }
106
107     return(m);
108 }
109
110 /* 
111    Taken form afpd/desktop.c
112 */
113 static char *mtoupath(char *mpath)
114 {
115     static char  upath[ MAXPATHLEN + 2]; /* for convert_charset dest_len parameter +2 */
116     char        *m, *u;
117     size_t       inplen;
118     size_t       outlen;
119     u_int16_t    flags = 0;
120
121     if (!mpath)
122         return NULL;
123         
124     if ( *mpath == '\0' ) {
125         return( "." );
126     }
127
128     /* set conversion flags */
129     if (!(volinfo->v_flags & AFPVOL_NOHEX))
130         flags |= CONV_ESCAPEHEX;
131     if (!(volinfo->v_flags & AFPVOL_USEDOTS))
132         flags |= CONV_ESCAPEDOTS;
133
134     if ((volinfo->v_casefold & AFPVOL_MTOUUPPER))
135         flags |= CONV_TOUPPER;
136     else if ((volinfo->v_casefold & AFPVOL_MTOULOWER))
137         flags |= CONV_TOLOWER;
138
139     if ((volinfo->v_flags & AFPVOL_EILSEQ)) {
140         flags |= CONV__EILSEQ;
141     }
142
143     m = mpath;
144     u = upath;
145
146     inplen = strlen(m);
147     outlen = MAXPATHLEN;
148
149     if ((size_t)-1 == (outlen = convert_charset(CH_UTF8_MAC,
150                                                 volinfo->v_volcharset,
151                                                 volinfo->v_maccharset,
152                                                 m, inplen, u, outlen, &flags)) ) {
153         dbd_log( LOGSTD, "conversion from UTF8-MAC to %s for %s failed.",
154                  volinfo->v_volcodepage, mpath);
155             return NULL;
156     }
157
158     return( upath );
159 }
160
161 /* 
162    Check for wrong encoding e.g. "." at the beginning is not CAP encoded (:2e) although volume is default !AFPVOL_USEDOTS.
163    We do it by roundtripiping from volcharset to UTF8-MAC and back and then compare the result.
164 */
165 static int check_name_encoding(char *uname)
166 {
167     char *roundtripped;
168
169     roundtripped = mtoupath(utompath(uname));
170     if (!roundtripped) {
171         dbd_log( LOGSTD, "Error checking encoding for %s/%s", cwdbuf, uname);
172         return -1;
173     }
174
175     if ( STRCMP(uname, !=, roundtripped)) {
176         dbd_log( LOGSTD, "Bad encoding for %s/%s", cwdbuf, uname);
177         return -1;
178     }
179
180     return 0;
181 }
182
183 /*
184   Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop"
185   Returns pointer to name or NULL.
186 */
187 static const char *check_netatalk_dirs(const char *name)
188 {
189     int c;
190     
191     for (c=0; netatalk_dirs[c]; c++) {
192         if ((strcmp(name, netatalk_dirs[c])) == 0)
193             return netatalk_dirs[c];
194     }
195     return NULL;
196 }
197
198 /* 
199    Check for .AppleDouble file, create if missing
200 */
201 static int check_adfile(const char *fname, const struct stat *st)
202 {
203     int ret;
204     struct adouble ad;
205     char *adname;
206
207     adname = volinfo->ad_path(fname, 0);
208
209     if ((ret = access( adname, F_OK)) != 0) {
210         if (errno != ENOENT) {
211             dbd_log(LOGSTD, "Access error for ad-file '%s/%s': %s",
212                     cwdbuf, adname, strerror(errno));
213             return -1;
214         }
215         /* Missing. Log and create it */
216         dbd_log(LOGSTD, "Missing AppleDoube file '%s/%s'", cwdbuf, adname);
217
218         if (dbd_flags & DBD_FLAGS_SCAN)
219             /* Scan only requested, dont change anything */
220             return 0;
221
222         /* Create ad file */
223         ad_init(&ad, volinfo->v_adouble, volinfo->v_flags);
224         
225         if ((ret = ad_open_metadata( fname, 0, O_CREAT, &ad)) != 0) {
226             dbd_log( LOGSTD, "Error creating AppleDouble file '%s/%s': %s", 
227                      cwdbuf, adname, strerror(errno));
228             return -1;
229         }
230
231         /* Set name in ad-file */
232         ad_setname(&ad, utompath((char *)fname));
233         ad_flush(&ad);
234         ad_close_metadata(&ad);
235
236         chown(adname, st->st_uid, st->st_gid);
237         /* FIXME: should we inherit mode too here ? */
238 #if 0
239         chmod(adname, st->st_mode);
240 #endif
241     }
242     return 0;
243 }
244
245 /* 
246    Check for .AppleDouble folder, create if missing
247 */
248 static int check_addir(int volroot)
249 {
250     int ret;
251     struct stat st;
252     struct adouble ad;
253
254     if ((ret = access(ADv2_DIRNAME, F_OK)) != 0) {
255         if (errno != ENOENT) {
256             dbd_log(LOGSTD, "Access error in directory %s: %s", cwdbuf, strerror(errno));
257             return -1;
258         }
259         /* Missing. Log and create it */
260         dbd_log(LOGSTD, "Missing %s directory %s", ADv2_DIRNAME, cwdbuf);
261
262         if (dbd_flags & DBD_FLAGS_SCAN)
263             /* Scan only requested, dont change anything */
264             return 0;
265
266         /* Create ad dir and set name and id */
267         ad_init(&ad, volinfo->v_adouble, volinfo->v_flags);
268         
269         if ((ret = ad_open_metadata( ".", ADFLAGS_DIR, O_CREAT, &ad)) != 0) {
270             dbd_log( LOGSTD, "Error creating AppleDouble dir in %s: %s", cwdbuf, strerror(errno));
271             return -1;
272         }
273
274         /* Get basename of cwd from cwdbuf */
275         char *mname = utompath(strrchr(cwdbuf, '/') + 1);
276
277         /* Update name in ad file */
278         ad_setname(&ad, mname);
279         ad_flush(&ad);
280         ad_close_metadata(&ad);
281         
282         /* Inherit owner/group from "." to ".AppleDouble" and ".Parent" */
283         if ((stat(".", &st)) != 0) {
284             dbd_log( LOGSTD, "Couldnt stat %s: %s", cwdbuf, strerror(errno));
285             return -1;
286         }
287         chown(ADv2_DIRNAME, st.st_uid, st.st_gid);
288         chown(volinfo->ad_path(".", ADFLAGS_DIR), st.st_uid, st.st_gid);
289     }
290
291     return 0;
292 }
293
294 static int read_addir(void)
295 {
296     DIR *dp;
297     struct dirent *ep;
298     struct stat st;
299     static char fname[NAME_MAX] = "../";
300
301     if ((chdir(ADv2_DIRNAME)) != 0) {
302         dbd_log(LOGSTD, "Couldn't chdir to '%s/%s': %s",
303                 cwdbuf, ADv2_DIRNAME, strerror(errno));
304         return -1;
305     }
306
307     if ((dp = opendir(".")) == NULL) {
308         dbd_log(LOGSTD, "Couldn't open the directory '%s/%s': %s",
309                 cwdbuf, ADv2_DIRNAME, strerror(errno));
310         return -1;
311     }
312
313     while ((ep = readdir(dp))) {
314         /* Check if its "." or ".." */
315         if (DIR_DOT_OR_DOTDOT(ep->d_name))
316             continue;
317
318         if ((stat(ep->d_name, &st)) < 0) {
319             dbd_log( LOGSTD, "Lost file while reading dir '%s/%s/%s', probably removed: %s",
320                      cwdbuf, ADv2_DIRNAME, ep->d_name, strerror(errno));
321             continue;
322         }
323
324         /* Check for dirs */
325         if (S_ISDIR(st.st_mode)) {
326             dbd_log( LOGSTD, "Unexpected directory '%s' in AppleDouble dir '%s/%s'",
327                      ep->d_name, cwdbuf, ADv2_DIRNAME);
328             continue;
329         }
330
331         /* Skip ".Parent" */
332         if (STRCMP(ep->d_name, ==, ".Parent"))
333             continue;
334
335         /* Check for data file */
336         strcpy(fname+3, ep->d_name);
337         if ((access( fname, F_OK)) != 0) {
338             if (errno != ENOENT) {
339                 dbd_log(LOGSTD, "Access error for file '%s/%s': %s", 
340                         cwdbuf, ep->d_name, strerror(errno));
341                 continue;
342             }
343             /* Orphaned ad-file*/
344             dbd_log(LOGSTD, "Orphaned AppleDoube file '%s/%s/%s'",
345                     cwdbuf, ADv2_DIRNAME, ep->d_name);
346
347             if (dbd_flags & DBD_FLAGS_SCAN)
348                 /* Scan only requested, dont change anything */
349                 continue;;
350
351             if ((unlink(ep->d_name)) != 0) {
352                 dbd_log(LOGSTD, "Error unlinking orphaned AppleDoube file '%s/%s/%s'",
353                         cwdbuf, ADv2_DIRNAME, ep->d_name);
354
355             }
356         }
357     }
358
359     if ((chdir("..")) != 0) {
360         dbd_log(LOGSTD, "Couldn't chdir back to '%s' from AppleDouble dir: %s",
361                 cwdbuf, strerror(errno));
362         /* This really is EOT! */
363         exit(1);
364     }
365
366     closedir(dp);
367
368     return 0;
369 }
370
371 static int dbd_readdir(int volroot, cnid_t did)
372 {
373     int cwd, ret = 0, encoding_ok;
374     const char *name;
375     DIR *dp;
376     struct dirent *ep;
377     static struct stat st;      /* Save some stack space */
378
379     /* Check for .AppleDouble folder */
380     if ((check_addir(volroot)) != 0)
381         return -1;
382
383     /* Check AppleDouble files in AppleDouble folder */
384     if ((ret = read_addir()) != 0)
385         return -1;
386
387     if ((dp = opendir (".")) == NULL) {
388         dbd_log(LOGSTD, "Couldn't open the directory: %s",strerror(errno));
389         return -1;
390     }
391
392     while ((ep = readdir (dp))) {
393         /* Check if its "." or ".." */
394         if (DIR_DOT_OR_DOTDOT(ep->d_name))
395             continue;
396
397         /* Check for netatalk special folders e.g. ".AppleDB" or ".AppleDesktop" */
398         if ((name = check_netatalk_dirs(ep->d_name)) != NULL) {
399             if (! volroot)
400                 dbd_log(LOGSTD, "Nested %s in %s", name, cwdbuf);
401             continue;
402         }
403
404         /* Skip .AppleDouble dir in this loop */
405         if (STRCMP(ep->d_name, == , ADv2_DIRNAME))
406             continue;
407
408         if ((ret = stat(ep->d_name, &st)) < 0) {
409             dbd_log( LOGSTD, "Lost file while reading dir '%s/%s', probably removed: %s", cwdbuf, ep->d_name, strerror(errno));
410             continue;
411         }
412
413         /**************************************************************************
414            Tests
415         **************************************************************************/
416
417         /* Check encoding */
418         if ( -1 == (encoding_ok = check_name_encoding(ep->d_name)) ) {
419             /* If its a file: skipp all other tests now ! */
420             /* For dirs we skip tests too, but later */
421             if (S_ISREG(st.st_mode))
422                 continue;
423         }
424
425         /* Check for appledouble file, create if missing */
426         if (S_ISREG(st.st_mode)) {
427             if ( 0 != check_adfile(ep->d_name, &st))
428                 continue;
429         }
430         
431         /**************************************************************************
432           Recursion
433         **************************************************************************/
434         if (S_ISDIR(st.st_mode)) {
435             strcat(cwdbuf, "/");
436             strcat(cwdbuf, ep->d_name);
437             dbd_log( LOGDEBUG, "Entering directory: %s", cwdbuf);
438             if (-1 == (cwd = open(".", O_RDONLY))) {
439                 dbd_log( LOGSTD, "Cant open directory '%s': %s", cwdbuf, strerror(errno));
440                 continue;
441             }
442             if (0 != chdir(ep->d_name)) {
443                 dbd_log( LOGSTD, "Cant chdir to directory '%s': %s", cwdbuf, strerror(errno));
444                 close(cwd);
445                 continue;
446             }
447
448             ret = dbd_readdir(0, did);
449
450             fchdir(cwd);
451             close(cwd);
452             *(strrchr(cwdbuf, '/')) = 0;
453             if (ret < 0)
454                 continue;
455         }
456     }
457
458     /* 
459        Use results of previous checks
460     */
461
462 exit_cleanup:
463     closedir(dp);    
464     return ret;
465 }
466
467 static int scanvol(struct volinfo *vi, dbd_flags_t flags)
468 {
469     /* Dont scanvol on no-appledouble vols */
470     if (vi->v_flags & AFPVOL_NOADOUBLE) {
471         dbd_log( LOGSTD, "Volume without AppleDouble support: skipping volume scanning.");
472         return 0;
473     }
474
475     /* Make this stuff accessible from all funcs easily */
476     volinfo = vi;
477     dbd_flags = flags;
478
479     /* Run with umask 0 */
480     umask(0);
481
482     /* chdir to vol */
483     strcpy(cwdbuf, volinfo->v_path);
484     if (cwdbuf[strlen(cwdbuf) - 1] == '/')
485         cwdbuf[strlen(cwdbuf) - 1] = 0;
486     chdir(volinfo->v_path);
487
488     /* Start recursion */
489     if (dbd_readdir(1, 2) < 0)  /* 2 = volumeroot CNID */
490         return -1;
491
492     return 0;
493 }
494
495 int cmd_dbd_scanvol(DBD *dbd, struct volinfo *volinfo, dbd_flags_t flags)
496 {
497     int ret = 0;
498
499     /* We only support unicode volumes ! */
500     if ( volinfo->v_volcharset != CH_UTF8) {
501         dbd_log( LOGSTD, "Not a Unicode volume: %s, %u != %u", volinfo->v_volcodepage, volinfo->v_volcharset, CH_UTF8);
502         return -1;
503     }
504
505     /* open/create dbd, copy rootinfo key */
506     if (NULL == (dbd_rebuild = dbif_init(NULL)))
507         return -1;
508     if (0 != (dbif_open(dbd_rebuild, NULL, 0)))
509         return -1;
510     if (0 != (ret = dbif_copy_rootinfokey(dbd, dbd_rebuild)))
511         goto exit_cleanup;
512
513     dbd_log( LOGSTD, "dumping rebuilddb");
514     dbif_dump(dbd_rebuild, 0);
515
516     /* scanvol */
517     if ( (scanvol(volinfo, flags)) != 0)
518         return -1;
519
520 exit_cleanup:
521     dbif_close(dbd_rebuild);
522     return ret;
523 }