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