3 // Copyright (C) 2005 Enrico Scholz <enrico.scholz@informatik.tu-chemnitz.de>
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.
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.
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.
26 #include "lib/internal.h"
27 #include "lib_internal/matchlist.h"
28 #include "lib_internal/unify.h"
29 #include "ensc_vector/vector.h"
32 #include <beecrypt/beecrypt.h>
46 #define ENSC_WRAPPERS_STDLIB 1
47 #define ENSC_WRAPPERS_UNISTD 1
48 #define ENSC_WRAPPERS_FCNTL 1
49 #define ENSC_WRAPPERS_DIRENT 1
53 #define HASH_BLOCKSIZE 0x10000000
54 #define HASH_MINSIZE 0x10
58 #define CMD_HELP 0x8000
59 #define CMD_VERSION 0x8001
61 #define CMD_DESTINATION 0x1000
62 #define CMD_INSECURE 0x1001
63 #define CMD_SLEDGE 0x1002
64 #define CMD_MANUALLY 0x1003
68 { "help", no_argument, 0, CMD_HELP },
69 { "version", no_argument, 0, CMD_VERSION },
70 { "destination", required_argument, 0, CMD_DESTINATION },
71 { "insecure", no_argument, 0, CMD_INSECURE },
72 { "sledgehammer", no_argument, 0, CMD_SLEDGE },
73 { "manually", no_argument, 0, CMD_MANUALLY },
77 // SHA1, grouped by 4 digits + hash-collision counter + 2* '/' + NULL
78 typedef char HashPath[160/4 + (160/4/4) + sizeof(unsigned int)*2 + 3];
83 struct MatchList dst_list;
84 HashDirCollection hash_dirs;
85 size_t hash_dirs_max_size;
87 hashFunctionContext hash_context;
90 int wrapper_exit_code = 1;
91 struct Arguments const *global_args;
92 struct WalkdownInfo global_info;
93 static struct SkipReason skip_reason;
95 #include "vhashify-init.hc"
97 int Global_getVerbosity() {
98 return global_args->verbosity;
101 int Global_doRenew() {
106 showHelp(char const *cmd)
108 WRITE_MSG(1, "Usage:\n ");
111 " [-Rnv] <vserver>\n or\n ");
114 " --manually [-Rnvx] [--] <hashdir> <path> <excludelist>\n\n"
115 " --manually ... hashify generic paths; excludelists must be generated\n"
117 " -R ... revert operation; dehashify files\n"
118 " -n ... do not modify anything; just show what there will be\n"
119 " done (in combination with '-v')\n"
120 " -v ... verbose mode\n"
121 "Please report bugs to " PACKAGE_BUGREPORT "\n");
130 "vhashify " VERSION " -- hashifies vservers and/or directories\n"
131 "This program is part of " PACKAGE_STRING "\n\n"
132 "Copyright (C) 2005 Enrico Scholz\n"
133 VERSION_COPYRIGHT_DISCLAIMER);
138 HashDirInfo_compareDevice(void const *lhs_v, void const *rhs_v)
140 struct HashDirInfo const * const lhs = lhs_v;
141 dev_t const * const rhs = rhs_v;
143 assert(lhs!=0 && rhs!=0);
144 return lhs->device - *rhs;
148 HashDirInfo_findDevice(HashDirCollection const *coll, dev_t dev)
150 struct HashDirInfo const *res;
152 res = Vector_searchSelfOrg_const(coll, &dev,
153 HashDirInfo_compareDevice, vecSHIFT_ONCE);
155 if (res!=0) return &res->path;
159 #include "vserver-visitdir.hc"
162 checkFstat(PathInfo const * const basename,
163 struct stat * const st)
165 assert(basename->d[0] != '/');
167 // local file does not exist... strange
169 skip_reason.r = rsFSTAT;
170 if (lstat(basename->d, st)==-1) return false;
172 // this is a directory and succeeds everytime
173 if (S_ISDIR(st->st_mode))
177 skip_reason.r = rsSYMLINK;
178 if (S_ISLNK(st->st_mode)) return false;
180 // ignore special files
181 skip_reason.r = rsSPECIAL;
182 if (!S_ISREG(st->st_mode) &&
183 !S_ISDIR(st->st_mode)) return false;
185 // ignore small files
186 skip_reason.r = rsTOOSMALL;
187 if (st->st_size < HASH_MINSIZE) return false;
189 skip_reason.r = rsUNIFIED;
190 if ((!global_args->do_revert && !(st->st_nlink==1 || Unify_isIUnlinkable(basename->d))) ||
191 ( global_args->do_revert && Unify_isIUnlinkable(basename->d)))
197 static jmp_buf bus_error_restore;
198 static volatile sig_atomic_t bus_error;
201 handlerSIGBUS(int UNUSED num)
204 longjmp(bus_error_restore, 1);
208 convertDigest(HashPath d_path)
210 static char const HEX_DIGIT[] = "0123456789abcdef";
211 hashFunctionContext * const h_ctx = &global_info.hash_context;
212 size_t d_size = h_ctx->algo->digestsize;
214 unsigned char digest[d_size];
217 if (hashFunctionContextDigest(h_ctx, digest)==-1)
221 out+1<sizeof(HashPath)-(sizeof(unsigned int)*2 + 2) && in<d_size;
223 if (in%2 == 0 && in>0) d_path[out++]='/';
224 d_path[out++] = HEX_DIGIT[digest[in] >> 4];
225 d_path[out++] = HEX_DIGIT[digest[in] & 0x0f];
227 d_path[out++] = '\0';
233 addStatHash(hashFunctionContext *h_ctx, struct stat const * const st)
235 #define DECL_ATTR(X) __typeof__(st->st_##X) X
236 #define SET_ATTR(X) .X = st->st_##X
238 struct __attribute__((__packed__)) {
254 return hashFunctionContextUpdate(h_ctx, (void *)&tmp, sizeof tmp)!=-1;
258 calculateHashFromFD(int fd, HashPath d_path, struct stat const * const st)
260 hashFunctionContext * const h_ctx = &global_info.hash_context;
264 off_t size = st->st_size;
268 if (hashFunctionContextReset(h_ctx)==-1 ||
269 !addStatHash(h_ctx, st))
273 if (setjmp(bus_error_restore)!=0) goto out;
275 while (offset < size) {
276 size_t real_size = size-offset;
277 cur_size = real_size;
278 //cur_size = (real_size + PAGESIZE-1)/PAGESIZE * PAGESIZE;
279 if (cur_size>HASH_BLOCKSIZE) cur_size = HASH_BLOCKSIZE;
281 buf = mmap(0, cur_size, PROT_READ, MAP_SHARED, fd, offset);
284 madvise(buf, cur_size, MADV_SEQUENTIAL); // ignore error...
286 if (buf==0) goto out;
287 if (hashFunctionContextUpdate(h_ctx, buf, real_size)==-1) goto out;
289 munmap(buf, cur_size);
293 if (!convertDigest(d_path)) goto out;
298 if (buf!=0) munmap(buf, cur_size);
303 calculateHash(PathInfo const *filename, HashPath d_path, struct stat const * const st)
305 int fd = open(filename->d, O_NOFOLLOW|O_NONBLOCK|O_RDONLY|O_NOCTTY);
311 int old_errno = errno;
312 WRITE_MSG(2, "Failed to open '");
313 WRITE_STR(2, filename->d);
319 if (fstat(fd, &fst)==-1 ||
320 fst.st_dev!=st->st_dev || fst.st_ino!=st->st_ino) {
321 WRITE_MSG(2, "An unexpected event occured while stating '");
322 WRITE_STR(2, filename->d);
323 WRITE_MSG(2, "'.\n");
327 if (!calculateHashFromFD(fd, d_path, st)) {
328 WRITE_MSG(2, "Failed to calculate hash for '");
329 WRITE_STR(2, filename->d);
330 WRITE_MSG(2, "'.\n");
337 if (fd!=-1) close(fd);
342 mkdirRecursive(char const *path)
346 if (path[0]!='/') return false; // only absolute paths
347 if (lstat(path,&st)!=-1) return true;
349 char buf[strlen(path)+1];
354 while ((ptr = strchr(ptr, '/'))!=0) {
356 if (mkdir(buf, 0700)==-1 && errno!=EEXIST) {
357 int old_errno = errno;
358 WRITE_MSG(2, "mkdir('");
372 resolveCollisions(char *result, PathInfo const *root, HashPath d_path, struct stat *st)
374 strcpy(result, root->d); // 'root' ends on '/' already (see initHashList())
375 strcat(result, d_path);
377 char *ptr = result + strlen(result);
378 unsigned int idx = 0;
379 char buf[sizeof(int)*2 + 1];
384 ptr[sizeof(int)*2] = '\0';
386 if (!mkdirRecursive(result))
390 len = utilvserver_fmt_xuint(buf, idx);
391 memset(ptr, '0', sizeof(int)*2 - len);
392 memcpy(ptr + sizeof(int)*2 - len, buf, len);
395 if (lstat(result, &new_st)==-1) {
397 int old_errno = errno;
398 WRITE_MSG(2, "lstat('");
405 else if (!Unify_isUnifyable(st, &new_st))
406 continue; // continue with next number
408 break; // ok, we finish here
410 int fd = open(result, O_NOFOLLOW|O_EXCL|O_CREAT|O_WRONLY, 0200);
413 int old_errno = errno;
414 WRITE_MSG(2, "open('");
429 checkDirEntry(PathInfo const *path, PathInfo const *basename,
430 bool *is_dir, struct stat *st, char *result_buf)
432 //printf("checkDirEntry(%s, %s, %u)\n", path->d, d_path, is_dir);
434 struct WalkdownInfo const * const info = &global_info;
436 // Check if it is in the exclude/include list of the destination vserver and
437 // abort when it is not matching an allowed entry
438 skip_reason.r = rsEXCL;
439 if (MatchList_compare(&info->dst_list, path->d)!=stINCLUDE) return 0;
441 if (checkFstat(basename, st)) {
442 PathInfo const *hash_root_path;
445 *is_dir = S_ISDIR(st->st_mode);
448 !((hash_root_path = HashDirInfo_findDevice(&info->hash_dirs, st->st_dev))!=0 &&
449 calculateHash(basename, d_path, st) &&
450 resolveCollisions(result_buf, hash_root_path, d_path, st)))
460 visitDirEntry(struct dirent const *ent)
463 char const * dirname = ent->d_name;
464 PathInfo path = global_info.state;
465 PathInfo tmp_path = {
469 char path_buf[ENSC_PI_APPSZ(path, tmp_path)];
470 char const *match = 0;
473 PathInfo_append(&path, &tmp_path, path_buf);
475 bool is_dotfile = isDotfile(dirname);
477 struct stat src_stat;
478 char tmpbuf[global_info.hash_dirs_max_size +
479 sizeof(HashPath) + 2];
481 skip_reason.r = rsDOTFILE;
484 (match=checkDirEntry(&path, &tmp_path,
485 &is_dir, &src_stat, tmpbuf))==0) {
491 res = visitDir(dirname, &src_stat);
494 printf("%s <- %s\n", match, path.d);
502 int main(int argc, char *argv[])
504 struct Arguments args = {
511 Vector_init(&global_info.hash_dirs, sizeof(struct HashDirInfo));
513 if (hashFunctionContextInit(&global_info.hash_context,
514 hashFunctionDefault())==-1)
520 int c = getopt_long(argc, argv, "",
525 case CMD_HELP : showHelp(argv[0]);
526 case CMD_VERSION : showVersion();
527 case CMD_DESTINATION : args.hash_dir = optarg; break;
528 case CMD_MANUALLY : args.mode = mdMANUALLY; break;
529 case CMD_INSECURE : args.insecure = 1; break;
530 case CMD_SLEDGE : args.insecure = 2; break;
531 case 'v' : ++args.verbosity; break;
533 WRITE_MSG(2, "Try '");
534 WRITE_STR(2, argv[0]);
535 WRITE_MSG(2, " --help\" for more information.\n");
542 WRITE_MSG(2, "No directory/vserver given\n");
546 if (args.hash_dir==0 && args.mode==mdMANUALLY) {
547 WRITE_MSG(2, "'--manually' requires '--destination'\n");
552 case mdMANUALLY : initModeManually(&args, argc-optind, argv+optind); break;
553 case mdVSERVER : initModeVserver (&args, argc-optind, argv+optind); break;
554 default : assert(false); return EXIT_FAILURE;
557 if (Global_getVerbosity()>=1)
558 WRITE_MSG(1, "Starting to traverse directories...\n");
560 signal(SIGBUS, handlerSIGBUS);
562 Echdir(global_info.dst_list.root.d);