Apply Christian Heim's patch to deprecate the Gentoo initstyle
[util-vserver.git] / src / vhashify.c
1 // $Id$    --*- c -*--
2
3 // Copyright (C) 2005 Enrico Scholz <enrico.scholz@informatik.tu-chemnitz.de>
4 //  
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; version 2 of the License.
8 //  
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //  
14 // You should have received a copy of the GNU General Public License
15 // along with this program; if not, write to the Free Software
16 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17
18
19 #ifdef HAVE_CONFIG_H
20 #  include <config.h>
21 #endif
22
23 #include "vhashify.h"
24 #include "util.h"
25
26 #include "lib/internal.h"
27 #include "lib_internal/matchlist.h"
28 #include "lib_internal/unify.h"
29 #include "ensc_vector/vector.h"
30
31 #include <beecrypt/beecrypt.h>
32
33 #include <setjmp.h>
34 #include <unistd.h>
35 #include <getopt.h>
36 #include <string.h>
37 #include <assert.h>
38 #include <stdlib.h>
39 #include <fcntl.h>
40 #include <dirent.h>
41 #include <errno.h>
42 #include <signal.h>
43 #include <limits.h>
44 #include <sys/mman.h>
45 #include <sys/stat.h>
46
47 #define ENSC_WRAPPERS_STDLIB    1
48 #define ENSC_WRAPPERS_UNISTD    1
49 #define ENSC_WRAPPERS_FCNTL     1
50 #define ENSC_WRAPPERS_DIRENT    1
51 #define ENSC_WRAPPERS_IO        1
52 #include <wrappers.h>
53
54
55 #define HASH_BLOCKSIZE          0x10000000u
56 #define HASH_MINSIZE            0x10
57 #define HASH_MAXBITS            256             // we have to take care about
58                                                 // max filename-length...
59
60 #if HASH_MINSIZE<=0
61 #  error HASH_MINSIZE must be not '0'
62 #endif
63
64
65 #define CMD_HELP                0x8000
66 #define CMD_VERSION             0x8001
67
68 #define CMD_DESTINATION         0x1000
69 #define CMD_INSECURE            0x1001
70 #define CMD_SLEDGE              0x1002
71 #define CMD_MANUALLY            0x1003
72 #define CMD_REFRESH             0x1004
73
74 struct option const
75 CMDLINE_OPTIONS[] = {
76   { "help",         no_argument,        0, CMD_HELP },
77   { "version",      no_argument,        0, CMD_VERSION },
78   { "destination",  required_argument,  0, CMD_DESTINATION },
79   { "insecure",     no_argument,        0, CMD_INSECURE },
80   { "sledgehammer", no_argument,        0, CMD_SLEDGE },
81   { "manually",     no_argument,        0, CMD_MANUALLY },
82   { "refresh",      no_argument,        0, CMD_REFRESH },
83   { "dry-run",      no_argument,        0, 'n' },
84   { "verbose",      no_argument,        0, 'v' },
85   { 0,0,0,0 }
86 };
87
88   // hash digest grouped by 2 digits + hash-collision counter + 2* '/' + NULL
89 typedef char                    HashPath[HASH_MAXBITS/4 + (HASH_MAXBITS/4/2) +
90                                          sizeof(unsigned int)*2 + 3];
91
92 struct HashDirConfiguration
93 {
94     hashFunction const                          *method;
95     enum { hshALL=0, hshSTART = 1, hshMIDDLE=2,
96            hshEND = 4, hshINVALID = -1 }        blocks;
97     size_t                                      blocksize;
98 };
99
100 struct WalkdownInfo
101 {
102     PathInfo                    state;
103     struct MatchList            dst_list;
104     struct HashDirConfiguration hash_conf;
105     HashDirCollection           hash_dirs;
106     size_t                      hash_dirs_max_size;
107
108     hashFunctionContext         hash_context;
109 };
110
111 int                             wrapper_exit_code = 1;
112 struct Arguments const          *global_args;
113 static struct SkipReason        skip_reason;
114
115 struct WalkdownInfo             global_info = {
116   .hash_conf = { .method     = 0,
117                  .blocks     = hshALL,
118                  .blocksize  = 0x10000 }
119 };
120
121 #include "vhashify-init.hc"
122
123 int Global_getVerbosity() {
124   return global_args->verbosity;
125 }
126
127 int Global_doRenew() {
128   return true;
129 }
130
131 int Global_isVserverRunning() {
132     // TODO
133   return global_args->insecure<2;
134 }
135
136 static void
137 showHelp(char const *cmd)
138 {
139   WRITE_MSG(1, "Usage:\n  ");
140   WRITE_STR(1, cmd);
141   WRITE_MSG(1,
142             " [-nv] [--refresh] <vserver>\n    or\n  ");
143   WRITE_STR(1, cmd);
144   WRITE_MSG(1,
145             " --manually [-nv] [--] <hashdir> <path> <excludelist>\n\n"
146             "  --manually      ...  hashify generic paths; excludelists must be generated\n"
147             "                       manually\n"
148             "  --refresh       ...  hashify already hashified files also\n"
149             "  -n              ...  do not modify anything; just show what there will be\n"
150             "                       done (in combination with '-v')\n"
151             "  -v              ...  verbose mode\n"
152             "Please report bugs to " PACKAGE_BUGREPORT "\n");
153
154   exit(0);
155 }
156
157 static void
158 showVersion()
159 {
160   WRITE_MSG(1,
161             "vhashify " VERSION " -- hashifies vservers and/or directories\n"
162             "This program is part of " PACKAGE_STRING "\n\n"
163             "Copyright (C) 2005 Enrico Scholz\n"
164             VERSION_COPYRIGHT_DISCLAIMER);
165   exit(0);
166 }
167
168 int
169 HashDirInfo_compareDevice(void const *lhs_v, void const *rhs_v)
170 {
171   struct HashDirInfo const * const      lhs = lhs_v;
172   dev_t const * const                   rhs = rhs_v;
173
174   assert(lhs!=0 && rhs!=0);
175   return lhs->device - *rhs;
176 }
177
178 PathInfo const *
179 HashDirInfo_findDevice(HashDirCollection const *coll, dev_t dev)
180 {
181   struct HashDirInfo const      *res;
182
183   res = Vector_searchSelfOrg_const(coll, &dev,
184                                    HashDirInfo_compareDevice, vecSHIFT_ONCE);
185
186   if (res!=0) return &res->path;
187   else        return 0;
188 }
189
190 #include "vserver-visitdir.hc"
191
192 static bool
193 checkFstat(PathInfo const * const basename,
194            struct stat * const st)
195 {
196   assert(basename->d[0] != '/');
197
198     // local file does not exist... strange
199     // TODO: message
200   skip_reason.r = rsFSTAT;
201   if (lstat(basename->d, st)==-1) return false;
202
203     // this is a directory and succeeds everytime
204   if (S_ISDIR(st->st_mode))
205     return true;
206
207     // ignore symlinks
208   skip_reason.r = rsSYMLINK;
209   if (S_ISLNK(st->st_mode))       return false;
210
211     // ignore special files
212   skip_reason.r = rsSPECIAL;
213   if (!S_ISREG(st->st_mode) &&
214       !S_ISDIR(st->st_mode))      return false;
215   
216     // ignore small files
217   skip_reason.r = rsTOOSMALL;
218   if (st->st_size < HASH_MINSIZE) return false;
219   
220   switch (Unify_isIUnlinkable(basename->d)) {
221     case unifyUNSUPPORTED       :  skip_reason.r = rsUNSUPPORTED; return false;
222     case unifyBUSY              :
223         // do an implicit refresh on busy files when there are no active links
224       if (st->st_nlink>1 && !global_args->do_refresh) {
225           // TODO: message
226         skip_reason.r = rsUNIFIED;
227         return false;
228       }
229       break;
230     default                     :  break;
231   }
232
233   return true;
234 }
235
236 static sigjmp_buf               bus_error_restore;
237 static volatile sig_atomic_t    bus_error;
238
239 static void
240 handlerSIGBUS(int UNUSED num)
241 {
242   bus_error = 1;
243   siglongjmp(bus_error_restore, 1);
244 }
245
246 static bool
247 convertDigest(HashPath d_path)
248 {
249   static char const             HEX_DIGIT[] = "0123456789abcdef";
250   hashFunctionContext * const   h_ctx    = &global_info.hash_context;
251   size_t                        d_size   = h_ctx->algo->digestsize;
252     
253   unsigned char                 digest[d_size];
254   size_t                        out = 0;
255
256   if (hashFunctionContextDigest(h_ctx, digest)==-1)
257     return false;
258   
259   for (size_t in=0;
260        out+1<sizeof(HashPath)-(sizeof(unsigned int)*2 + 2) && in<d_size;
261        ++in) {
262     if ((in+254)%(in<=2 ? 1 : 256) == 0 && in>0)
263       d_path[out++]='/';
264     d_path[out++]  = HEX_DIGIT[digest[in] >>    4];
265     d_path[out++]  = HEX_DIGIT[digest[in] &  0x0f];
266   }
267   d_path[out++] = '\0';
268   
269   return true;
270 }
271
272 #ifndef ENSC_TESTSUITE
273 static bool
274 addStatHash(hashFunctionContext *h_ctx, struct stat const * const st)
275 {
276 #define DECL_ATTR(X)    __typeof__(st->st_##X)  X
277 #define SET_ATTR(X)     .X = st->st_##X
278   
279   struct __attribute__((__packed__)) {
280     DECL_ATTR(mode);
281     DECL_ATTR(uid);
282     DECL_ATTR(gid);
283     DECL_ATTR(rdev);
284     DECL_ATTR(size);
285     DECL_ATTR(mtime);
286   }             tmp = {
287     SET_ATTR(mode),
288     SET_ATTR(uid),
289     SET_ATTR(gid),
290     SET_ATTR(rdev),
291     SET_ATTR(size),
292     SET_ATTR(mtime)
293   };
294
295 #undef SET_ATTR
296 #undef DECL_ATTR
297
298   
299   return hashFunctionContextUpdate(h_ctx, (void *)&tmp, sizeof tmp)!=-1;
300 }
301 #else
302 static bool
303 addStatHash(hashFunctionContext UNUSED *h_ctx, struct stat const UNUSED * const st)
304 {
305   return true;
306 }
307 #endif
308   
309 static bool
310 calculateHashFromFD(int fd, HashPath d_path, struct stat const * const st)
311 {
312   hashFunctionContext * const   h_ctx    = &global_info.hash_context;
313   void const * volatile         buf      = 0;
314   loff_t volatile               buf_size = 0;
315   bool   volatile               res      = false;
316
317
318   if (hashFunctionContextReset(h_ctx)==-1 ||
319       !addStatHash(h_ctx, st))
320     return false;
321
322   bus_error = 0;
323   if (sigsetjmp(bus_error_restore,1)==0) {
324     loff_t                      offset   = 0;
325     off_t                       size     = st->st_size;
326
327     while (offset < size) {
328       buf_size = size-offset;
329       if (buf_size>HASH_BLOCKSIZE) buf_size = HASH_BLOCKSIZE;
330
331       if ((buf=mmap(0, buf_size, PROT_READ, MAP_SHARED, fd, offset))==0) {
332         perror("mmap(<hash>)");
333         goto out;
334       }
335
336       offset += buf_size;
337       madvise(const_cast(void *)(buf), buf_size, MADV_SEQUENTIAL);      // ignore error...
338
339       if (hashFunctionContextUpdate(h_ctx, buf, buf_size)==-1) goto out;
340
341       munmap(const_cast(void *)(buf), buf_size);
342       buf = 0;
343     }
344
345     res = convertDigest(d_path);
346   }
347
348   out:
349   if (buf!=0) munmap(const_cast(void *)(buf), buf_size);
350   return res;
351 }
352
353 static bool
354 calculateHash(PathInfo const *filename, HashPath d_path, struct stat const * const st)
355 {
356   int           fd  = open(filename->d, O_NOFOLLOW|O_NONBLOCK|O_RDONLY|O_NOCTTY);
357   struct stat   fst;
358   bool          res = false;
359
360   do {
361     if (fd==-1) {
362       int       old_errno = errno;
363       WRITE_MSG(2, "Failed to open '");
364       WRITE_STR(2, filename->d);
365       errno = old_errno;
366       perror("'");
367       break;;
368     }
369   
370     if (fstat(fd, &fst)==-1 ||
371         fst.st_dev!=st->st_dev || fst.st_ino!=st->st_ino) {
372       WRITE_MSG(2, "An unexpected event occured while stating '");
373       WRITE_STR(2, filename->d);
374       WRITE_MSG(2, "'.\n");
375       break;
376     }
377
378     if (!calculateHashFromFD(fd, d_path, st)) {
379       WRITE_MSG(2, "Failed to calculate hash for '");
380       WRITE_STR(2, filename->d);
381       WRITE_MSG(2, "'.\n");
382       break;
383     }
384
385     res = true;
386   } while (false);
387   
388   if (fd!=-1) close(fd);
389   return res;
390 }
391
392 static enum { mkdirFAIL, mkdirSUCCESS, mkdirSKIP }
393 mkdirSingle(char const *path, char *end_ptr, int good_err)
394 {
395   *end_ptr = '\0';
396   if (mkdir(path, 0700)!=-1 || errno==EEXIST) {
397     *end_ptr = '/';
398     return mkdirSUCCESS;
399   }
400   else if (errno==good_err) {
401     *end_ptr = '/';
402     return mkdirSKIP;
403   }
404   else {
405     int         old_errno = errno;
406     WRITE_MSG(2, "mkdir('");
407     WRITE_STR(2, path);
408     errno = old_errno;
409     perror("')");
410     return mkdirFAIL;
411   }
412 }
413
414 static char *
415 rstrchr(char *str, char c)
416 {
417   while (*str!=c) --str;
418   return str;
419 }
420
421 static bool
422 mkdirRecursive(char const *path)
423 {
424   if (path[0]!='/')      return false; // only absolute paths
425
426   char                  buf[strlen(path)+1];
427   char *                ptr = buf + sizeof(buf) - 2;
428
429   strcpy(buf, path);
430
431   while (ptr>buf && (ptr = rstrchr(ptr, '/'))!=0) {
432     switch (mkdirSingle(buf, ptr, ENOENT)) {
433       case mkdirSUCCESS         :  break;
434       case mkdirSKIP            :  --ptr; continue;
435       case mkdirFAIL            :  return false;
436     }
437
438     break;      // implied by mkdirSUCCESS
439   }
440
441   assert(ptr!=0);
442   ++ptr;
443
444   while ((ptr=strchr(ptr, '/'))!=0) {
445     switch (mkdirSingle(buf, ptr, 0)) {
446       case mkdirSKIP            :
447       case mkdirFAIL            :  return false;
448       case mkdirSUCCESS         :  ++ptr; continue;
449     }
450   }
451
452   return true;
453 }
454
455 static bool
456 resolveCollisions(char *result, PathInfo const *root, HashPath d_path,
457                   struct stat *st, struct stat *hash_st)
458 {
459   strcpy(result, root->d);      // 'root' ends on '/' already (see initHashList())
460   strcat(result, d_path);
461   
462   char                  *ptr = result + strlen(result);
463   unsigned int          idx  = 0;
464   char                  buf[sizeof(int)*2 + 1];
465   size_t                len;
466
467   *ptr                 = '-';
468   ptr[sizeof(int)*2+1] = '\0';
469
470   for (;; ++idx) {
471     len = utilvserver_fmt_xuint(buf, idx);
472     memset(ptr+1, '0', sizeof(int)*2 - len);
473     memcpy(ptr+1 + sizeof(int)*2 - len, buf, len);
474
475     if (lstat(result, hash_st)==-1) {
476       if (global_args->dry_run && errno!=ENOENT) {
477         int             old_errno = errno;
478         WRITE_MSG(2, "lstat('");
479         WRITE_STR(2, buf);
480         errno = old_errno;
481         perror("')");
482         return false;
483       }
484     }
485     else if (Unify_isUnified(st, hash_st)) {
486       skip_reason.r = rsUNIFIED;
487       return false;
488     }
489     else if (!Unify_isUnifyable(st, hash_st))
490       continue;         // continue with next number*****
491     else
492       break;            // ok, we finish here
493
494     if (!global_args->dry_run) {
495       *ptr = '\0';
496       if (!mkdirRecursive(result))
497         return false;
498       *ptr = '-';
499
500       int               fd = open(result, O_NOFOLLOW|O_EXCL|O_CREAT|O_WRONLY, 0200);
501
502       if (fd==-1) {
503         int             old_errno = errno;
504         WRITE_MSG(2, "open('");
505         WRITE_STR(2, buf);
506         errno = old_errno;
507         perror("')");
508         return false;
509       }
510
511       close(fd);
512     }
513
514       // HACK: avoid an additional lstat on the resulting hash-file
515     hash_st->st_size = 0;
516     break;
517   }
518
519   return true;
520 }
521
522 static char const *
523 checkDirEntry(PathInfo const *path, PathInfo const *basename,
524               bool *is_dir,
525               struct stat *st, struct stat *hash_st,
526               char *result_buf)
527 {
528     //printf("checkDirEntry(%s, %s, %u)\n", path->d, d_path, is_dir);
529
530   struct WalkdownInfo const * const     info       = &global_info;
531
532   // Check if it is in the exclude/include list of the destination vserver and
533   // abort when it is not matching an allowed entry
534   skip_reason.r      = rsEXCL;
535   if (MatchList_compare(&info->dst_list, path->d)!=stINCLUDE) return 0;
536
537   if (checkFstat(basename, st)) {
538     PathInfo const      *hash_root_path;
539     HashPath            d_path;
540     
541     *is_dir = S_ISDIR(st->st_mode);
542
543     if (!*is_dir &&
544         !((skip_reason.r = rsWRONGDEV,
545            (hash_root_path = HashDirInfo_findDevice(&info->hash_dirs, st->st_dev))!=0) &&
546           (skip_reason.r = rsGENERAL,
547            calculateHash(basename, d_path, st)) &&
548           resolveCollisions(result_buf, hash_root_path, d_path, st, hash_st)))
549       return 0;
550
551     return result_buf;
552   }
553
554   return 0;
555 }
556
557 static void
558 printSkipReason()
559 {
560   WRITE_MSG(1, " (");
561   switch (skip_reason.r) {
562     case rsDOTFILE      :  WRITE_MSG(1, "dotfile"); break;
563     case rsEXCL         :  WRITE_MSG(1, "excluded"); break;
564     case rsTOOSMALL     :  WRITE_MSG(1, "too small"); break;
565     case rsUNSUPPORTED  :  WRITE_MSG(1, "operation not supported"); break;
566     case rsFSTAT        :  WRITE_MSG(1, "fstat error"); break;
567     case rsSYMLINK      :  WRITE_MSG(1, "symlink"); break;
568     case rsUNIFIED      :  WRITE_MSG(1, "already unified"); break;
569     case rsSPECIAL      :  WRITE_MSG(1, "non regular file"); break;
570     case rsWRONGDEV     :  WRITE_MSG(1, "no matching device"); break;
571     case rsGENERAL      :  WRITE_MSG(1, "general error"); break;
572     default             :  assert(false); abort();
573   }
574   WRITE_MSG(1, ")");
575 }
576
577 static bool
578 doit(char const *src, char const *dst,
579      struct stat const *src_st, struct stat const *dst_st,
580      PathInfo const *path)
581 {
582   if (global_args->dry_run || Global_getVerbosity()>=2) {
583     WRITE_MSG(1, "unifying   '");
584     Vwrite(1, path->d, path->l);
585     WRITE_MSG(1, "'");
586     
587     if (Global_getVerbosity()>=4) {
588       WRITE_MSG(1, " (to '");
589       WRITE_STR(1, dst);
590       WRITE_MSG(1, "')");
591     }
592
593     WRITE_MSG(1, "\n");
594   }
595
596     // abort here in dry-run mode
597   if (global_args->dry_run) return true;
598
599   if (dst_st->st_size==0) {
600       // file was not unified yet
601     
602     if (Global_isVserverRunning()) {
603       (void)unlink(dst);
604       if (Unify_copy (src, src_st, dst) &&
605           // the mixed 'dst' and 'src_st' params are intentionally...
606           Unify_unify(dst, src_st, src, false))
607         return true;
608     }
609     else if (Unify_unify(src, src_st, dst, true))
610       return true;
611
612     (void)unlink(dst);  // cleanup in error-case
613   }
614     // there exists already a reference-file
615   else if (Unify_unify(dst, dst_st, src, false))
616     return true;
617
618   return false;
619 }
620
621 static uint64_t
622 visitDirEntry(struct dirent const *ent)
623 {
624   uint64_t                      res      = 0;
625   char const *                  dirname  = ent->d_name;
626   PathInfo                      path     = global_info.state;
627   PathInfo                      tmp_path = {
628     .d = dirname,
629     .l = strlen(dirname)
630   };
631   char                          path_buf[ENSC_PI_APPSZ(path, tmp_path)];
632   char const                    *match = 0;
633
634   
635   PathInfo_append(&path, &tmp_path, path_buf);
636
637   bool                          is_dotfile    = isDotfile(dirname);
638   bool                          is_dir;
639   struct stat                   src_stat = { .st_mode=0 };
640   struct stat                   hash_stat;
641   char                          tmpbuf[global_info.hash_dirs_max_size +
642                                        sizeof(HashPath) + 2];
643   
644   skip_reason.r = rsDOTFILE;
645
646   if (is_dotfile ||
647       (match=checkDirEntry(&path, &tmp_path,
648                            &is_dir, &src_stat, &hash_stat,
649                            tmpbuf))==0) {
650
651     bool        is_link = !is_dotfile && S_ISLNK(src_stat.st_mode);
652
653     if (Global_getVerbosity()>=1 &&
654         (Global_getVerbosity()>=3 || skip_reason.r!=rsUNIFIED) &&
655         ((!is_dotfile && !is_link) ||
656          (Global_getVerbosity()>=6 && is_dotfile) ||
657          (Global_getVerbosity()>=6 && is_link)) ) {
658       WRITE_MSG(1, "  skipping '");
659       Vwrite(1, path.d, path.l);
660       WRITE_MSG(1, "'");
661       if (Global_getVerbosity()>=2) printSkipReason();
662       WRITE_MSG(1, "\n");
663     }
664
665     return 0;
666   }
667
668   if (is_dir) {
669     res = visitDir(dirname, &src_stat);
670   }
671   else if (doit(dirname, match, &src_stat, &hash_stat, &path))
672     res = 1;
673   else {
674       // TODO: message
675     res = 0;
676   }
677
678   return res;
679     
680 }
681
682 int main(int argc, char *argv[])
683 {
684   struct Arguments      args = {
685     .mode               =  mdVSERVER,
686     .hash_dir           =  0,
687     .verbosity          =  0,
688     .insecure           =  0,
689     .dry_run            =  false,
690     .do_refresh         =  false,
691   };
692
693   Vector_init(&global_info.hash_dirs, sizeof(struct HashDirInfo));
694
695   global_args = &args;
696   while (1) {
697     int         c = getopt_long(argc, argv, "+nv",
698                                 CMDLINE_OPTIONS, 0);
699     if (c==-1) break;
700
701     switch (c) {
702       case CMD_HELP             :  showHelp(argv[0]);
703       case CMD_VERSION          :  showVersion();
704       case CMD_DESTINATION      :  args.hash_dir    = optarg; break;
705       case CMD_MANUALLY         :  args.mode        = mdMANUALLY; break;
706       case CMD_INSECURE         :  args.insecure    = 1;    break;
707       case CMD_SLEDGE           :  args.insecure    = 2;    break;
708       case CMD_REFRESH          :  args.do_refresh  = true; break;
709       case 'n'                  :  args.dry_run     = true; break;
710       case 'v'                  :  ++args.verbosity; break;
711       default           :
712         WRITE_MSG(2, "Try '");
713         WRITE_STR(2, argv[0]);
714         WRITE_MSG(2, " --help\" for more information.\n");
715         return EXIT_FAILURE;
716         break;
717     }
718   }
719
720   if (argc==optind) {
721     WRITE_MSG(2, "No directory/vserver given\n");
722     return EXIT_FAILURE;
723   }
724
725   if (args.hash_dir==0 && args.mode==mdMANUALLY) {
726     WRITE_MSG(2, "'--manually' requires '--destination'\n");
727     return EXIT_FAILURE;
728   }
729
730   switch (args.mode) {
731     case mdMANUALLY     :  initModeManually(&args, argc-optind, argv+optind); break;
732     case mdVSERVER      :  initModeVserver (&args, argc-optind, argv+optind); break;
733     default             :  assert(false); return EXIT_FAILURE;
734   };
735
736   if (hashFunctionContextInit(&global_info.hash_context,
737                               global_info.hash_conf.method)==-1) {
738     WRITE_MSG(2, "Failed to initialize hash-context\n");
739     return EXIT_FAILURE;
740   }
741
742   if (Global_getVerbosity()>=1)
743     WRITE_MSG(1, "Starting to traverse directories...\n");
744
745   signal(SIGBUS, handlerSIGBUS);
746   
747   Echdir(global_info.dst_list.root.d);
748   visitDir("/", 0);
749
750 #ifndef NDEBUG
751   MatchList_destroy(&global_info.dst_list);
752   freeHashList(&global_info.hash_dirs);
753   hashFunctionContextFree(&global_info.hash_context);
754 #endif
755
756   return EXIT_SUCCESS;
757 }