2 * Copyright (C) 2012 STRATO AG. All rights reserved.
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public
6 * License v2 as published by the Free Software Foundation.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
13 * You should have received a copy of the GNU General Public
14 * License along with this program; if not, write to the
15 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
16 * Boston, MA 021110-1307, USA.
20 #define _DEFAULT_SOURCE
21 #define _LARGEFILE64_SOURCE
33 #include <sys/types.h>
35 #include <sys/sysmacros.h>
37 #include <sys/mkdev.h>
40 #include <netinet/in.h>
53 #if __BYTE_ORDER == __LITTLE_ENDIAN
54 #define htonll(x) __bswap_64 (x)
60 /* TODO: add hardlink recognition */
61 /* TODO: add xattr/acl */
70 unsigned char out[16];
73 typedef int (*sum_file_data_t)(int fd, sum_t *dst);
77 char *checksum = NULL;
78 struct excludes *excludes;
97 const char flchar[] = "ugoamcdes";
100 int flags[NUM_FLAGS] = {1, 1, 1, 1, 1, 0, 1, 0, 0};
103 getln(char *buf, int size, FILE *fp)
108 p = fgets(buf, size, fp);
113 while(l > 0 && (p[l - 1] == '\n' || p[l - 1] == '\r'))
125 if (c >= 'A' && c <= 'Z') {
129 for (i = 0; flchar[i]; ++i) {
130 if (flchar[i] == c) {
131 flags[i] = is_upper ? 0 : 1;
135 fprintf(stderr, "unrecognized flag %c\n", c);
149 fprintf(stderr, "usage: fssum <options> <path>\n");
150 fprintf(stderr, " options:\n");
151 fprintf(stderr, " -f : write out a full manifest file\n");
152 fprintf(stderr, " -w <file> : send output to file\n");
153 fprintf(stderr, " -v : verbose mode (debugging only)\n");
155 " -r <file> : read checksum or manifest from file\n");
156 fprintf(stderr, " -[ugoamcde] : specify which fields to include in checksum calculation.\n");
157 fprintf(stderr, " u : include uid\n");
158 fprintf(stderr, " g : include gid\n");
159 fprintf(stderr, " o : include mode\n");
160 fprintf(stderr, " m : include mtime\n");
161 fprintf(stderr, " a : include atime\n");
162 fprintf(stderr, " c : include ctime\n");
163 fprintf(stderr, " d : include file data\n");
164 fprintf(stderr, " e : include open errors (aborts otherwise)\n");
165 fprintf(stderr, " s : include block structure (holes)\n");
166 fprintf(stderr, " -[UGOAMCDES]: exclude respective field from calculation\n");
167 fprintf(stderr, " -n : reset all flags\n");
168 fprintf(stderr, " -N : set all flags\n");
169 fprintf(stderr, " -x path : exclude path when building checksum (multiple ok)\n");
170 fprintf(stderr, " -h : this help\n\n");
171 fprintf(stderr, "The default field mask is ugoamCdES. If the checksum/manifest is read from a\n");
172 fprintf(stderr, "file, the mask is taken from there and the values given on the command line\n");
173 fprintf(stderr, "are ignored.\n");
177 static char buf[65536];
182 void *p = malloc(sz);
185 fprintf(stderr, "malloc failed\n");
201 MD5_Final(cs->out, &cs->md5);
205 sum_add(sum_t *cs, void *buf, int size)
207 MD5_Update(&cs->md5, buf, size);
211 sum_add_sum(sum_t *dst, sum_t *src)
213 sum_add(dst, src->out, sizeof(src->out));
217 sum_add_u64(sum_t *dst, uint64_t val)
219 uint64_t v = htonll(val);
220 sum_add(dst, &v, sizeof(v));
224 sum_add_time(sum_t *dst, time_t t)
230 sum_to_string(sum_t *dst)
233 char *s = alloc(CS_SIZE * 2 + 1);
235 for (i = 0; i < CS_SIZE; ++i)
236 sprintf(s + i * 2, "%02x", dst->out[i]);
242 sum_file_data_permissive(int fd, sum_t *dst)
250 pos = lseek(fd, 0, SEEK_CUR);
251 if (pos == (off_t)-1)
252 return errno == ENXIO ? 0 : -2;
256 pos = lseek(fd, pos, SEEK_DATA);
257 if (pos == (off_t)-1) {
258 if (errno == ENXIO) {
260 pos = lseek(fd, 0, SEEK_END);
261 if (pos != (off_t)-1)
268 ret = read(fd, buf, sizeof(buf));
269 assert(ret); /* eof found by lseek */
272 if (old < pos) /* hole */
274 for (i = 0; i < ret; ++i) {
275 for (old = i; buf[i] == 0 && i < ret; ++i)
277 if (old < i) /* code like a hole */
284 "adding %llu zeros to sum\n",
285 (unsigned long long)zeros);
287 sum_add_u64(dst, zeros);
290 for (old = i; buf[i] != 0 && i < ret; ++i)
293 fprintf(stderr, "adding %u non-zeros to sum\n",
295 sum_add(dst, buf + old, i - old);
303 "adding %llu zeros to sum (finishing)\n",
304 (unsigned long long)zeros);
306 sum_add_u64(dst, zeros);
313 sum_file_data_strict(int fd, sum_t *dst)
318 pos = lseek(fd, 0, SEEK_CUR);
319 if (pos == (off_t)-1)
320 return errno == ENXIO ? 0 : -2;
323 pos = lseek(fd, pos, SEEK_DATA);
324 if (pos == (off_t)-1)
325 return errno == ENXIO ? 0 : -2;
326 ret = read(fd, buf, sizeof(buf));
327 assert(ret); /* eof found by lseek */
332 "adding to sum at file offset %llu, %d bytes\n",
333 (unsigned long long)pos, ret);
334 sum_add_u64(dst, (uint64_t)pos);
335 sum_add(dst, buf, ret);
343 char *out = alloc(strlen(in) * 3 + 1);
347 for (; *src; ++src) {
348 if (*src >= 32 && *src < 127 && *src != '\\') {
351 sprintf(dst, "\\%02x", (unsigned char)*src);
361 excess_file(const char *fn)
363 printf("only in local fs: %s\n", fn);
367 missing_file(const char *fn)
369 printf("only in remote fs: %s\n", fn);
373 pathcmp(const char *a, const char *b)
375 int len_a = strlen(a);
376 int len_b = strlen(b);
379 * as the containing directory is sent after the files, it has to
380 * come out bigger in the comparison.
382 if (len_a < len_b && a[len_a - 1] == '/' && strncmp(a, b, len_a) == 0)
384 if (len_a > len_b && b[len_b - 1] == '/' && strncmp(a, b, len_b) == 0)
391 check_match(char *fn, char *local_m, char *remote_m,
392 char *local_c, char *remote_c)
394 int match_m = !strcmp(local_m, remote_m);
395 int match_c = !strcmp(local_c, remote_c);
397 if (match_m && !match_c) {
398 printf("data mismatch in %s\n", fn);
399 } else if (!match_m && match_c) {
400 printf("metadata mismatch in %s\n", fn);
401 } else if (!match_m && !match_c) {
402 printf("metadata and data mismatch in %s\n", fn);
410 check_manifest(char *fn, char *m, char *c, int last_call)
421 cmp = pathcmp(prev_fn, fn);
425 } else if (cmp < 0) {
426 missing_file(prev_fn);
428 check_match(fn, m, prev_m, c, prev_c);
439 while ((l = getln(line, sizeof(line), in_fp))) {
440 rem_c = strrchr(l, ' ');
443 checksum = strdup(l);
448 fprintf(stderr, "malformed input\n");
452 rem_m = strrchr(l, ' ');
460 cmp = pathcmp(l, fn);
462 check_match(fn, m, rem_m, c, rem_c);
464 } else if (cmp > 0) {
467 prev_m = strdup(rem_m);
468 prev_c = strdup(rem_c);
478 namecmp(const void *aa, const void *bb)
480 char * const *a = aa;
481 char * const *b = bb;
483 return strcmp(*a, *b);
487 sum(int dirfd, int level, sum_t *dircs, char *path_prefix, char *path_in)
491 char **namelist = NULL;
498 sum_file_data_t sum_file_data = flags[FLAG_STRUCTURE] ?
499 sum_file_data_strict : sum_file_data_permissive;
501 d = fdopendir(dirfd);
506 while((de = readdir(d))) {
507 if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
509 if (entries == alloclen) {
511 namelist = realloc(namelist,
512 alloclen * sizeof(*namelist));
514 fprintf(stderr, "malloc failed\n");
518 namelist[entries] = strdup(de->d_name);
519 if (!namelist[entries]) {
520 fprintf(stderr, "malloc failed\n");
525 qsort(namelist, entries, sizeof(*namelist), namecmp);
526 for (i = 0; i < entries; ++i) {
534 path = alloc(strlen(path_in) + strlen(namelist[i]) + 3);
535 sprintf(path, "%s/%s", path_in, namelist[i]);
536 for (excl = 0; excl < n_excludes; ++excl) {
537 if (strncmp(excludes[excl].path, path,
538 excludes[excl].len) == 0)
547 ret = lstat64(namelist[i], &st);
549 fprintf(stderr, "stat failed for %s/%s: %s\n",
550 path_prefix, path, strerror(errno));
553 sum_add_u64(&meta, level);
554 sum_add(&meta, namelist[i], strlen(namelist[i]));
555 if (!S_ISDIR(st.st_mode))
556 sum_add_u64(&meta, st.st_nlink);
558 sum_add_u64(&meta, st.st_uid);
560 sum_add_u64(&meta, st.st_gid);
561 if (flags[FLAG_MODE])
562 sum_add_u64(&meta, st.st_mode);
563 if (flags[FLAG_ATIME])
564 sum_add_time(&meta, st.st_atime);
565 if (flags[FLAG_MTIME])
566 sum_add_time(&meta, st.st_mtime);
567 if (flags[FLAG_CTIME])
568 sum_add_time(&meta, st.st_ctime);
569 if (S_ISDIR(st.st_mode)) {
570 fd = openat(dirfd, namelist[i], 0);
571 if (fd == -1 && flags[FLAG_OPEN_ERROR]) {
572 sum_add_u64(&meta, errno);
573 } else if (fd == -1) {
574 fprintf(stderr, "open failed for %s/%s: %s\n",
575 path_prefix, path, strerror(errno));
578 sum(fd, level + 1, &cs, path_prefix, path);
581 } else if (S_ISREG(st.st_mode)) {
582 sum_add_u64(&meta, st.st_size);
583 if (flags[FLAG_DATA]) {
585 fprintf(stderr, "file %s\n",
587 fd = openat(dirfd, namelist[i], 0);
588 if (fd == -1 && flags[FLAG_OPEN_ERROR]) {
589 sum_add_u64(&meta, errno);
590 } else if (fd == -1) {
592 "open failed for %s/%s: %s\n",
598 ret = sum_file_data(fd, &cs);
610 } else if (S_ISLNK(st.st_mode)) {
611 ret = readlink(namelist[i], buf, sizeof(buf));
616 sum_add(&cs, buf, ret);
617 } else if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) {
618 sum_add_u64(&cs, major(st.st_rdev));
619 sum_add_u64(&cs, minor(st.st_rdev));
623 if (gen_manifest || in_manifest) {
628 if (S_ISDIR(st.st_mode))
631 m = sum_to_string(&meta);
632 c = sum_to_string(&cs);
635 fprintf(out_fp, "%s %s %s\n", fn, m, c);
637 check_manifest(fn, m, c, 0);
642 sum_add_sum(dircs, &cs);
643 sum_add_sum(dircs, &meta);
650 main(int argc, char *argv[])
658 char flagstring[sizeof(flchar)];
663 const char *allopts = "heEfuUgGoOaAmMcCdDsSnNw:r:vx:";
666 while ((c = getopt(argc, argv, allopts)) != EOF) {
693 for (i = 0; i < NUM_FLAGS; ++i)
697 for (i = 0; i < NUM_FLAGS; ++i)
701 out_fp = fopen(optarg, "w");
704 "failed to open output file: %s\n",
710 in_fp = fopen(optarg, "r");
713 "failed to open input file: %s\n",
720 excludes = realloc(excludes,
721 sizeof(*excludes) * n_excludes);
724 "failed to alloc exclude space\n");
727 excludes[n_excludes - 1].path = optarg;
738 if (optind + 1 != argc) {
739 fprintf(stderr, "missing path\n");
744 char *l = getln(line, sizeof(line), in_fp);
748 fprintf(stderr, "failed to read line from input\n");
751 if (strncmp(l, "Flags: ", 7) == 0) {
755 } else if ((p = strchr(l, ':'))) {
758 checksum = strdup(p);
760 fprintf(stderr, "invalid input file format\n");
764 fprintf(stderr, "warning: "
765 "command line flags ignored in -r mode\n");
767 strcpy(flagstring, flchar);
768 for (i = 0; i < NUM_FLAGS; ++i) {
770 flagstring[i] -= 'a' - 'A';
775 if (path[plen - 1] == '/') {
780 for (i = 0; i < n_excludes; ++i) {
781 if (strncmp(path, excludes[i].path, plen) != 0)
783 "warning: exclude %s outside of path %s\n",
784 excludes[i].path, path);
786 excludes[i].path += plen;
787 elen = strlen(excludes[i].path);
788 if (excludes[i].path[elen - 1] == '/')
790 excludes[i].path[elen] = '\0';
791 excludes[i].len = elen;
794 fd = open(path, O_RDONLY);
796 fprintf(stderr, "failed to open %s: %s\n", path,
802 fprintf(out_fp, "Flags: %s\n", flagstring);
805 sum(fd, 1, &cs, path, "");
810 check_manifest("", "", "", 1);
814 fprintf(stderr, "malformed input\n");
818 fprintf(out_fp, "%s:", flagstring);
820 fprintf(out_fp, "%s\n", sum_to_string(&cs));
822 if (strcmp(checksum, sum_to_string(&cs)) == 0) {