1 // SPDX-License-Identifier: GPL-2.0
3 * Copyright (C) 2012 STRATO AG. All rights reserved.
7 #define _DEFAULT_SOURCE
8 #define _LARGEFILE64_SOURCE
20 #include <sys/types.h>
22 #include <sys/sysmacros.h>
23 #include <sys/xattr.h>
25 #include <sys/mkdev.h>
28 #include <netinet/in.h>
43 /* TODO: add hardlink recognition */
56 unsigned char out[16];
59 typedef int (*sum_file_data_t)(int fd, sum_t *dst);
63 char *checksum = NULL;
64 struct excludes *excludes;
84 const char flchar[] = "ugoamcdtes";
87 int flags[NUM_FLAGS] = {1, 1, 1, 1, 1, 0, 1, 1, 0, 0};
90 getln(char *buf, int size, FILE *fp)
95 p = fgets(buf, size, fp);
100 while(l > 0 && (p[l - 1] == '\n' || p[l - 1] == '\r'))
112 if (c >= 'A' && c <= 'Z') {
116 for (i = 0; flchar[i]; ++i) {
117 if (flchar[i] == c) {
118 flags[i] = is_upper ? 0 : 1;
122 fprintf(stderr, "unrecognized flag %c\n", c);
136 fprintf(stderr, "usage: fssum <options> <path>\n");
137 fprintf(stderr, " options:\n");
138 fprintf(stderr, " -f : write out a full manifest file\n");
139 fprintf(stderr, " -w <file> : send output to file\n");
140 fprintf(stderr, " -v : verbose mode (debugging only)\n");
141 fprintf(stderr, " -r <file> : read checksum or manifest from file\n");
142 fprintf(stderr, " -[ugoamcdtes]: specify which fields to include in checksum calculation.\n");
143 fprintf(stderr, " u : include uid\n");
144 fprintf(stderr, " g : include gid\n");
145 fprintf(stderr, " o : include mode\n");
146 fprintf(stderr, " m : include mtime\n");
147 fprintf(stderr, " a : include atime\n");
148 fprintf(stderr, " c : include ctime\n");
149 fprintf(stderr, " d : include file data\n");
150 fprintf(stderr, " t : include xattrs\n");
151 fprintf(stderr, " e : include open errors (aborts otherwise)\n");
152 fprintf(stderr, " s : include block structure (holes)\n");
153 fprintf(stderr, " -[UGOAMCDTES]: exclude respective field from calculation\n");
154 fprintf(stderr, " -n : reset all flags\n");
155 fprintf(stderr, " -N : set all flags\n");
156 fprintf(stderr, " -x path : exclude path when building checksum (multiple ok)\n");
157 fprintf(stderr, " -h : this help\n\n");
158 fprintf(stderr, "The default field mask is ugoamCdtES. If the checksum/manifest is read from a\n");
159 fprintf(stderr, "file, the mask is taken from there and the values given on the command line\n");
160 fprintf(stderr, "are ignored.\n");
164 static char buf[65536];
169 void *p = malloc(sz);
172 fprintf(stderr, "malloc failed\n");
183 cs->ctx = EVP_MD_CTX_new();
185 fprintf(stderr, "evp md ctx allocation failed\n");
188 EVP_DigestInit(cs->ctx, EVP_md5());
198 EVP_DigestFinal(cs->ctx, cs->out, NULL);
199 EVP_MD_CTX_free(cs->ctx);
202 MD5_Final(cs->out, &cs->md5);
207 sum_add(sum_t *cs, void *buf, int size)
210 EVP_DigestUpdate(cs->ctx, buf, size);
212 MD5_Update(&cs->md5, buf, size);
217 sum_add_sum(sum_t *dst, sum_t *src)
219 sum_add(dst, src->out, sizeof(src->out));
223 sum_add_u64(sum_t *dst, uint64_t val)
225 uint64_t v = htobe64(val);
226 sum_add(dst, &v, sizeof(v));
230 sum_add_time(sum_t *dst, time_t t)
236 sum_to_string(sum_t *dst)
239 char *s = alloc(CS_SIZE * 2 + 1);
241 for (i = 0; i < CS_SIZE; ++i)
242 sprintf(s + i * 2, "%02x", dst->out[i]);
248 namecmp(const void *aa, const void *bb)
250 char * const *a = aa;
251 char * const *b = bb;
253 return strcmp(*a, *b);
257 sum_xattrs(int fd, sum_t *dst)
268 buflen = flistxattr(fd, NULL, 0);
271 /* no xattrs exist */
275 buf = malloc(buflen);
279 buflen = flistxattr(fd, buf, buflen);
286 * Keep the list of xattrs sorted, because the order in which they are
287 * listed is filesystem dependent, so we want to get the same checksum
288 * on different filesystems.
296 keylen = strlen(p) + 1; /* +1 for NULL terminator */
302 names = malloc(sizeof(char *) * num_xattrs);
309 for (i = 0; i < num_xattrs; i++) {
311 p += strlen(p) + 1; /* +1 for NULL terminator */
314 qsort(names, num_xattrs, sizeof(char *), namecmp);
316 for (i = 0; i < num_xattrs; i++) {
317 len = fgetxattr(fd, names[i], NULL, 0);
322 sum_add(dst, names[i], strlen(names[i]));
331 len = fgetxattr(fd, names[i], p, len);
337 sum_add(dst, p, len);
348 sum_file_data_permissive(int fd, sum_t *dst)
353 ret = read(fd, buf, sizeof(buf));
356 sum_add(dst, buf, ret);
357 if (ret < sizeof(buf))
364 sum_file_data_strict(int fd, sum_t *dst)
369 pos = lseek(fd, 0, SEEK_CUR);
370 if (pos == (off_t)-1)
371 return errno == ENXIO ? 0 : -2;
374 pos = lseek(fd, pos, SEEK_DATA);
375 if (pos == (off_t)-1)
376 return errno == ENXIO ? 0 : -2;
377 ret = read(fd, buf, sizeof(buf));
378 assert(ret); /* eof found by lseek */
383 "adding to sum at file offset %llu, %d bytes\n",
384 (unsigned long long)pos, ret);
385 sum_add_u64(dst, (uint64_t)pos);
386 sum_add(dst, buf, ret);
394 char *out = alloc(strlen(in) * 3 + 1);
398 for (; *src; ++src) {
399 if (*src >= 32 && *src < 127 && *src != '\\') {
402 sprintf(dst, "\\%02x", (unsigned char)*src);
412 excess_file(const char *fn)
414 printf("only in local fs: %s\n", fn);
418 missing_file(const char *fn)
420 printf("only in remote fs: %s\n", fn);
424 pathcmp(const char *a, const char *b)
426 int len_a = strlen(a);
427 int len_b = strlen(b);
430 * as the containing directory is sent after the files, it has to
431 * come out bigger in the comparison.
433 if (len_a < len_b && a[len_a - 1] == '/' && strncmp(a, b, len_a) == 0)
435 if (len_a > len_b && b[len_b - 1] == '/' && strncmp(a, b, len_b) == 0)
442 check_match(char *fn, char *local_m, char *remote_m,
443 char *local_c, char *remote_c)
445 int match_m = !strcmp(local_m, remote_m);
446 int match_c = !strcmp(local_c, remote_c);
448 if (match_m && !match_c) {
449 printf("data mismatch in %s\n", fn);
450 } else if (!match_m && match_c) {
451 printf("metadata mismatch in %s\n", fn);
452 } else if (!match_m && !match_c) {
453 printf("metadata and data mismatch in %s\n", fn);
461 check_manifest(char *fn, char *m, char *c, int last_call)
472 cmp = pathcmp(prev_fn, fn);
476 } else if (cmp < 0) {
477 missing_file(prev_fn);
479 check_match(fn, m, prev_m, c, prev_c);
490 while ((l = getln(line, sizeof(line), in_fp))) {
491 rem_c = strrchr(l, ' ');
494 checksum = strdup(l);
499 fprintf(stderr, "malformed input\n");
503 rem_m = strrchr(l, ' ');
511 cmp = pathcmp(l, fn);
513 check_match(fn, m, rem_m, c, rem_c);
515 } else if (cmp > 0) {
518 prev_m = strdup(rem_m);
519 prev_c = strdup(rem_c);
529 sum(int dirfd, int level, sum_t *dircs, char *path_prefix, char *path_in)
533 char **namelist = NULL;
540 sum_file_data_t sum_file_data = flags[FLAG_STRUCTURE] ?
541 sum_file_data_strict : sum_file_data_permissive;
542 struct stat64 dir_st;
544 if (fstat64(dirfd, &dir_st)) {
549 d = fdopendir(dirfd);
554 while((de = readdir(d))) {
555 if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
557 if (entries == alloclen) {
559 namelist = realloc(namelist,
560 alloclen * sizeof(*namelist));
562 fprintf(stderr, "malloc failed\n");
566 namelist[entries] = strdup(de->d_name);
567 if (!namelist[entries]) {
568 fprintf(stderr, "malloc failed\n");
573 qsort(namelist, entries, sizeof(*namelist), namecmp);
574 for (i = 0; i < entries; ++i) {
582 path = alloc(strlen(path_in) + strlen(namelist[i]) + 3);
583 sprintf(path, "%s/%s", path_in, namelist[i]);
584 for (excl = 0; excl < n_excludes; ++excl) {
585 if (strncmp(excludes[excl].path, path,
586 excludes[excl].len) == 0)
595 ret = lstat64(namelist[i], &st);
597 fprintf(stderr, "stat failed for %s/%s: %s\n",
598 path_prefix, path, strerror(errno));
602 /* We are crossing into a different subvol, skip this subtree. */
603 if (st.st_dev != dir_st.st_dev)
606 sum_add_u64(&meta, level);
607 sum_add(&meta, namelist[i], strlen(namelist[i]));
608 if (!S_ISDIR(st.st_mode))
609 sum_add_u64(&meta, st.st_nlink);
611 sum_add_u64(&meta, st.st_uid);
613 sum_add_u64(&meta, st.st_gid);
614 if (flags[FLAG_MODE])
615 sum_add_u64(&meta, st.st_mode);
616 if (flags[FLAG_ATIME])
617 sum_add_time(&meta, st.st_atime);
618 if (flags[FLAG_MTIME])
619 sum_add_time(&meta, st.st_mtime);
620 if (flags[FLAG_CTIME])
621 sum_add_time(&meta, st.st_ctime);
622 if (flags[FLAG_XATTRS] &&
623 (S_ISDIR(st.st_mode) || S_ISREG(st.st_mode))) {
624 fd = openat(dirfd, namelist[i], 0);
625 if (fd == -1 && flags[FLAG_OPEN_ERROR]) {
626 sum_add_u64(&meta, errno);
627 } else if (fd == -1) {
628 fprintf(stderr, "open failed for %s/%s: %s\n",
629 path_prefix, path, strerror(errno));
632 ret = sum_xattrs(fd, &meta);
636 "failed to read xattrs from "
644 if (S_ISDIR(st.st_mode)) {
645 fd = openat(dirfd, namelist[i], 0);
646 if (fd == -1 && flags[FLAG_OPEN_ERROR]) {
647 sum_add_u64(&meta, errno);
648 } else if (fd == -1) {
649 fprintf(stderr, "open failed for %s/%s: %s\n",
650 path_prefix, path, strerror(errno));
653 sum(fd, level + 1, &cs, path_prefix, path);
656 } else if (S_ISREG(st.st_mode)) {
657 sum_add_u64(&meta, st.st_size);
658 if (flags[FLAG_DATA]) {
660 fprintf(stderr, "file %s\n",
662 fd = openat(dirfd, namelist[i], 0);
663 if (fd == -1 && flags[FLAG_OPEN_ERROR]) {
664 sum_add_u64(&meta, errno);
665 } else if (fd == -1) {
667 "open failed for %s/%s: %s\n",
673 ret = sum_file_data(fd, &cs);
685 } else if (S_ISLNK(st.st_mode)) {
686 ret = readlink(namelist[i], buf, sizeof(buf));
691 sum_add(&cs, buf, ret);
692 } else if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) {
693 sum_add_u64(&cs, major(st.st_rdev));
694 sum_add_u64(&cs, minor(st.st_rdev));
698 if (gen_manifest || in_manifest) {
703 if (S_ISDIR(st.st_mode))
706 m = sum_to_string(&meta);
707 c = sum_to_string(&cs);
710 fprintf(out_fp, "%s %s %s\n", fn, m, c);
712 check_manifest(fn, m, c, 0);
717 sum_add_sum(dircs, &cs);
718 sum_add_sum(dircs, &meta);
725 main(int argc, char *argv[])
733 char flagstring[sizeof(flchar)];
738 const char *allopts = "heEfuUgGoOaAmMcCdDtTsSnNw:r:vx:";
741 while ((c = getopt(argc, argv, allopts)) != EOF) {
770 for (i = 0; i < NUM_FLAGS; ++i)
774 for (i = 0; i < NUM_FLAGS; ++i)
778 out_fp = fopen(optarg, "w");
781 "failed to open output file: %s\n",
787 in_fp = fopen(optarg, "r");
790 "failed to open input file: %s\n",
797 excludes = realloc(excludes,
798 sizeof(*excludes) * n_excludes);
801 "failed to alloc exclude space\n");
804 excludes[n_excludes - 1].path = optarg;
815 if (optind + 1 != argc) {
816 fprintf(stderr, "missing path\n");
821 char *l = getln(line, sizeof(line), in_fp);
825 fprintf(stderr, "failed to read line from input\n");
828 if (strncmp(l, "Flags: ", 7) == 0) {
832 } else if ((p = strchr(l, ':'))) {
835 checksum = strdup(p);
837 fprintf(stderr, "invalid input file format\n");
841 fprintf(stderr, "warning: "
842 "command line flags ignored in -r mode\n");
844 strcpy(flagstring, flchar);
845 for (i = 0; i < NUM_FLAGS; ++i) {
847 flagstring[i] -= 'a' - 'A';
852 if (path[plen - 1] == '/') {
857 for (i = 0; i < n_excludes; ++i) {
858 if (strncmp(path, excludes[i].path, plen) != 0)
860 "warning: exclude %s outside of path %s\n",
861 excludes[i].path, path);
863 excludes[i].path += plen;
864 elen = strlen(excludes[i].path);
865 if (excludes[i].path[elen - 1] == '/')
867 excludes[i].path[elen] = '\0';
868 excludes[i].len = elen;
871 fd = open(path, O_RDONLY);
873 fprintf(stderr, "failed to open %s: %s\n", path,
879 fprintf(out_fp, "Flags: %s\n", flagstring);
882 sum(fd, 1, &cs, path, "");
887 check_manifest("", "", "", 1);
891 fprintf(stderr, "malformed input\n");
895 fprintf(out_fp, "%s:", flagstring);
897 fprintf(out_fp, "%s\n", sum_to_string(&cs));
899 if (strcmp(checksum, sum_to_string(&cs)) == 0) {