| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright (C) 2012 STRATO AG. All rights reserved. | |
| */ | |
| #ifdef __linux__ | |
| #define _BSD_SOURCE | |
| #define _DEFAULT_SOURCE | |
| #define _LARGEFILE64_SOURCE | |
| #ifndef _GNU_SOURCE | |
| #define _GNU_SOURCE | |
| #endif | |
| #endif | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <unistd.h> | |
| #include <string.h> | |
| #include <fcntl.h> | |
| #include <dirent.h> | |
| #include <errno.h> | |
| #include <sys/types.h> | |
| #include <sys/stat.h> | |
| #include <sys/sysmacros.h> | |
| #include <sys/xattr.h> | |
| #ifdef __SOLARIS__ | |
| #include <sys/mkdev.h> | |
| #endif | |
| #include "md5.h" | |
| #include <netinet/in.h> | |
| #include <inttypes.h> | |
| #include <assert.h> | |
| #include <endian.h> | |
| #define CS_SIZE 16 | |
| #define CHUNKS 128 | |
| #ifdef __linux__ | |
| #ifndef SEEK_DATA | |
| #define SEEK_DATA 3 | |
| #define SEEK_HOLE 4 | |
| #endif | |
| #endif | |
| /* TODO: add hardlink recognition */ | |
| struct excludes { | |
| char *path; | |
| int len; | |
| }; | |
| typedef struct _sum { | |
| MD5_CTX md5; | |
| unsigned char out[16]; | |
| } sum_t; | |
| typedef int (*sum_file_data_t)(int fd, sum_t *dst); | |
| int gen_manifest = 0; | |
| int in_manifest = 0; | |
| char *checksum = NULL; | |
| struct excludes *excludes; | |
| int n_excludes = 0; | |
| int verbose = 0; | |
| FILE *out_fp; | |
| FILE *in_fp; | |
| enum _flags { | |
| FLAG_UID, | |
| FLAG_GID, | |
| FLAG_MODE, | |
| FLAG_ATIME, | |
| FLAG_MTIME, | |
| FLAG_CTIME, | |
| FLAG_DATA, | |
| FLAG_XATTRS, | |
| FLAG_OPEN_ERROR, | |
| FLAG_STRUCTURE, | |
| NUM_FLAGS | |
| }; | |
| const char flchar[] = "ugoamcdtes"; | |
| char line[65536]; | |
| int flags[NUM_FLAGS] = {1, 1, 1, 1, 1, 0, 1, 1, 0, 0}; | |
| char * | |
| getln(char *buf, int size, FILE *fp) | |
| { | |
| char *p; | |
| int l; | |
| p = fgets(buf, size, fp); | |
| if (!p) | |
| return NULL; | |
| l = strlen(p); | |
| while(l > 0 && (p[l - 1] == '\n' || p[l - 1] == '\r')) | |
| p[--l] = 0; | |
| return p; | |
| } | |
| void | |
| parse_flag(int c) | |
| { | |
| int i; | |
| int is_upper = 0; | |
| if (c >= 'A' && c <= 'Z') { | |
| is_upper = 1; | |
| c += 'a' - 'A'; | |
| } | |
| for (i = 0; flchar[i]; ++i) { | |
| if (flchar[i] == c) { | |
| flags[i] = is_upper ? 0 : 1; | |
| return; | |
| } | |
| } | |
| fprintf(stderr, "unrecognized flag %c\n", c); | |
| exit(-1); | |
| } | |
| void | |
| parse_flags(char *p) | |
| { | |
| while (*p) | |
| parse_flag(*p++); | |
| } | |
| void | |
| usage(void) | |
| { | |
| fprintf(stderr, "usage: fssum <options> <path>\n"); | |
| fprintf(stderr, " options:\n"); | |
| fprintf(stderr, " -f : write out a full manifest file\n"); | |
| fprintf(stderr, " -w <file> : send output to file\n"); | |
| fprintf(stderr, " -v : verbose mode (debugging only)\n"); | |
| fprintf(stderr, " -r <file> : read checksum or manifest from file\n"); | |
| fprintf(stderr, " -[ugoamcdtes]: specify which fields to include in checksum calculation.\n"); | |
| fprintf(stderr, " u : include uid\n"); | |
| fprintf(stderr, " g : include gid\n"); | |
| fprintf(stderr, " o : include mode\n"); | |
| fprintf(stderr, " m : include mtime\n"); | |
| fprintf(stderr, " a : include atime\n"); | |
| fprintf(stderr, " c : include ctime\n"); | |
| fprintf(stderr, " d : include file data\n"); | |
| fprintf(stderr, " t : include xattrs\n"); | |
| fprintf(stderr, " e : include open errors (aborts otherwise)\n"); | |
| fprintf(stderr, " s : include block structure (holes)\n"); | |
| fprintf(stderr, " -[UGOAMCDTES]: exclude respective field from calculation\n"); | |
| fprintf(stderr, " -n : reset all flags\n"); | |
| fprintf(stderr, " -N : set all flags\n"); | |
| fprintf(stderr, " -x path : exclude path when building checksum (multiple ok)\n"); | |
| fprintf(stderr, " -h : this help\n\n"); | |
| fprintf(stderr, "The default field mask is ugoamCdtES. If the checksum/manifest is read from a\n"); | |
| fprintf(stderr, "file, the mask is taken from there and the values given on the command line\n"); | |
| fprintf(stderr, "are ignored.\n"); | |
| exit(-1); | |
| } | |
| static char buf[65536]; | |
| void * | |
| alloc(size_t sz) | |
| { | |
| void *p = malloc(sz); | |
| if (!p) { | |
| fprintf(stderr, "malloc failed\n"); | |
| exit(-1); | |
| } | |
| return p; | |
| } | |
| void | |
| sum_init(sum_t *cs) | |
| { | |
| MD5_Init(&cs->md5); | |
| } | |
| void | |
| sum_fini(sum_t *cs) | |
| { | |
| MD5_Final(cs->out, &cs->md5); | |
| } | |
| void | |
| sum_add(sum_t *cs, void *buf, int size) | |
| { | |
| MD5_Update(&cs->md5, buf, size); | |
| } | |
| void | |
| sum_add_sum(sum_t *dst, sum_t *src) | |
| { | |
| sum_add(dst, src->out, sizeof(src->out)); | |
| } | |
| void | |
| sum_add_u64(sum_t *dst, uint64_t val) | |
| { | |
| uint64_t v = htobe64(val); | |
| sum_add(dst, &v, sizeof(v)); | |
| } | |
| void | |
| sum_add_time(sum_t *dst, time_t t) | |
| { | |
| sum_add_u64(dst, t); | |
| } | |
| char * | |
| sum_to_string(sum_t *dst) | |
| { | |
| int i; | |
| char *s = alloc(CS_SIZE * 2 + 1); | |
| for (i = 0; i < CS_SIZE; ++i) | |
| sprintf(s + i * 2, "%02x", dst->out[i]); | |
| return s; | |
| } | |
| int | |
| namecmp(const void *aa, const void *bb) | |
| { | |
| char * const *a = aa; | |
| char * const *b = bb; | |
| return strcmp(*a, *b); | |
| } | |
| int | |
| sum_xattrs(int fd, sum_t *dst) | |
| { | |
| ssize_t buflen; | |
| ssize_t len; | |
| char *buf; | |
| char *p; | |
| char **names = NULL; | |
| int num_xattrs = 0; | |
| int ret = 0; | |
| int i; | |
| buflen = flistxattr(fd, NULL, 0); | |
| if (buflen < 0) | |
| return -errno; | |
| /* no xattrs exist */ | |
| if (buflen == 0) | |
| return 0; | |
| buf = malloc(buflen); | |
| if (!buf) | |
| return -ENOMEM; | |
| buflen = flistxattr(fd, buf, buflen); | |
| if (buflen < 0) { | |
| ret = -errno; | |
| goto out; | |
| } | |
| /* | |
| * Keep the list of xattrs sorted, because the order in which they are | |
| * listed is filesystem dependent, so we want to get the same checksum | |
| * on different filesystems. | |
| */ | |
| p = buf; | |
| len = buflen; | |
| while (len > 0) { | |
| int keylen; | |
| keylen = strlen(p) + 1; /* +1 for NULL terminator */ | |
| len -= keylen; | |
| p += keylen; | |
| num_xattrs++; | |
| } | |
| names = malloc(sizeof(char *) * num_xattrs); | |
| if (!names) { | |
| ret = -ENOMEM; | |
| goto out; | |
| } | |
| p = buf; | |
| for (i = 0; i < num_xattrs; i++) { | |
| names[i] = p; | |
| p += strlen(p) + 1; /* +1 for NULL terminator */ | |
| } | |
| qsort(names, num_xattrs, sizeof(char *), namecmp); | |
| for (i = 0; i < num_xattrs; i++) { | |
| len = fgetxattr(fd, names[i], NULL, 0); | |
| if (len < 0) { | |
| ret = -errno; | |
| goto out; | |
| } | |
| sum_add(dst, names[i], strlen(names[i])); | |
| /* no value */ | |
| if (len == 0) | |
| continue; | |
| p = malloc(len); | |
| if (!p) { | |
| ret = -ENOMEM; | |
| goto out; | |
| } | |
| len = fgetxattr(fd, names[i], p, len); | |
| if (len < 0) { | |
| ret = -errno; | |
| free(p); | |
| goto out; | |
| } | |
| sum_add(dst, p, len); | |
| free(p); | |
| } | |
| out: | |
| free(buf); | |
| free(names); | |
| return ret; | |
| } | |
| int | |
| sum_file_data_permissive(int fd, sum_t *dst) | |
| { | |
| int ret; | |
| while (1) { | |
| ret = read(fd, buf, sizeof(buf)); | |
| if (ret < 0) | |
| return -errno; | |
| sum_add(dst, buf, ret); | |
| if (ret < sizeof(buf)) | |
| break; | |
| } | |
| return 0; | |
| } | |
| int | |
| sum_file_data_strict(int fd, sum_t *dst) | |
| { | |
| int ret; | |
| off_t pos; | |
| pos = lseek(fd, 0, SEEK_CUR); | |
| if (pos == (off_t)-1) | |
| return errno == ENXIO ? 0 : -2; | |
| while (1) { | |
| pos = lseek(fd, pos, SEEK_DATA); | |
| if (pos == (off_t)-1) | |
| return errno == ENXIO ? 0 : -2; | |
| ret = read(fd, buf, sizeof(buf)); | |
| assert(ret); /* eof found by lseek */ | |
| if (ret <= 0) | |
| return ret; | |
| if (verbose >= 2) | |
| fprintf(stderr, | |
| "adding to sum at file offset %llu, %d bytes\n", | |
| (unsigned long long)pos, ret); | |
| sum_add_u64(dst, (uint64_t)pos); | |
| sum_add(dst, buf, ret); | |
| pos += ret; | |
| } | |
| } | |
| char * | |
| escape(char *in) | |
| { | |
| char *out = alloc(strlen(in) * 3 + 1); | |
| char *src = in; | |
| char *dst = out; | |
| for (; *src; ++src) { | |
| if (*src >= 32 && *src < 127 && *src != '\\') { | |
| *dst++ = *src; | |
| } else { | |
| sprintf(dst, "\\%02x", (unsigned char)*src); | |
| dst += 3; | |
| } | |
| } | |
| *dst = 0; | |
| return out; | |
| } | |
| void | |
| excess_file(const char *fn) | |
| { | |
| printf("only in local fs: %s\n", fn); | |
| } | |
| void | |
| missing_file(const char *fn) | |
| { | |
| printf("only in remote fs: %s\n", fn); | |
| } | |
| int | |
| pathcmp(const char *a, const char *b) | |
| { | |
| int len_a = strlen(a); | |
| int len_b = strlen(b); | |
| /* | |
| * as the containing directory is sent after the files, it has to | |
| * come out bigger in the comparison. | |
| */ | |
| if (len_a < len_b && a[len_a - 1] == '/' && strncmp(a, b, len_a) == 0) | |
| return 1; | |
| if (len_a > len_b && b[len_b - 1] == '/' && strncmp(a, b, len_b) == 0) | |
| return -1; | |
| return strcmp(a, b); | |
| } | |
| void | |
| check_match(char *fn, char *local_m, char *remote_m, | |
| char *local_c, char *remote_c) | |
| { | |
| int match_m = !strcmp(local_m, remote_m); | |
| int match_c = !strcmp(local_c, remote_c); | |
| if (match_m && !match_c) { | |
| printf("data mismatch in %s\n", fn); | |
| } else if (!match_m && match_c) { | |
| printf("metadata mismatch in %s\n", fn); | |
| } else if (!match_m && !match_c) { | |
| printf("metadata and data mismatch in %s\n", fn); | |
| } | |
| } | |
| char *prev_fn; | |
| char *prev_m; | |
| char *prev_c; | |
| void | |
| check_manifest(char *fn, char *m, char *c, int last_call) | |
| { | |
| char *rem_m; | |
| char *rem_c; | |
| char *l; | |
| int cmp; | |
| if (prev_fn) { | |
| if (last_call) | |
| cmp = -1; | |
| else | |
| cmp = pathcmp(prev_fn, fn); | |
| if (cmp > 0) { | |
| excess_file(fn); | |
| return; | |
| } else if (cmp < 0) { | |
| missing_file(prev_fn); | |
| } else { | |
| check_match(fn, m, prev_m, c, prev_c); | |
| } | |
| free(prev_fn); | |
| free(prev_m); | |
| free(prev_c); | |
| prev_fn = NULL; | |
| prev_m = NULL; | |
| prev_c = NULL; | |
| if (cmp == 0) | |
| return; | |
| } | |
| while ((l = getln(line, sizeof(line), in_fp))) { | |
| rem_c = strrchr(l, ' '); | |
| if (!rem_c) { | |
| /* final cs */ | |
| checksum = strdup(l); | |
| break; | |
| } | |
| if (rem_c == l) { | |
| malformed: | |
| fprintf(stderr, "malformed input\n"); | |
| exit(-1); | |
| } | |
| *rem_c++ = 0; | |
| rem_m = strrchr(l, ' '); | |
| if (!rem_m) | |
| goto malformed; | |
| *rem_m++ = 0; | |
| if (last_call) | |
| cmp = -1; | |
| else | |
| cmp = pathcmp(l, fn); | |
| if (cmp == 0) { | |
| check_match(fn, m, rem_m, c, rem_c); | |
| return; | |
| } else if (cmp > 0) { | |
| excess_file(fn); | |
| prev_fn = strdup(l); | |
| prev_m = strdup(rem_m); | |
| prev_c = strdup(rem_c); | |
| return; | |
| } | |
| missing_file(l); | |
| } | |
| if (!last_call) | |
| excess_file(fn); | |
| } | |
| void | |
| sum(int dirfd, int level, sum_t *dircs, char *path_prefix, char *path_in) | |
| { | |
| DIR *d; | |
| struct dirent *de; | |
| char **namelist = NULL; | |
| int alloclen = 0; | |
| int entries = 0; | |
| int i; | |
| int ret; | |
| int fd; | |
| int excl; | |
| sum_file_data_t sum_file_data = flags[FLAG_STRUCTURE] ? | |
| sum_file_data_strict : sum_file_data_permissive; | |
| struct stat64 dir_st; | |
| if (fstat64(dirfd, &dir_st)) { | |
| perror("fstat"); | |
| exit(-1); | |
| } | |
| d = fdopendir(dirfd); | |
| if (!d) { | |
| perror("opendir"); | |
| exit(-1); | |
| } | |
| while((de = readdir(d))) { | |
| if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) | |
| continue; | |
| if (entries == alloclen) { | |
| alloclen += CHUNKS; | |
| namelist = realloc(namelist, | |
| alloclen * sizeof(*namelist)); | |
| if (!namelist) { | |
| fprintf(stderr, "malloc failed\n"); | |
| exit(-1); | |
| } | |
| } | |
| namelist[entries] = strdup(de->d_name); | |
| if (!namelist[entries]) { | |
| fprintf(stderr, "malloc failed\n"); | |
| exit(-1); | |
| } | |
| ++entries; | |
| } | |
| qsort(namelist, entries, sizeof(*namelist), namecmp); | |
| for (i = 0; i < entries; ++i) { | |
| struct stat64 st; | |
| sum_t cs; | |
| sum_t meta; | |
| char *path; | |
| sum_init(&cs); | |
| sum_init(&meta); | |
| path = alloc(strlen(path_in) + strlen(namelist[i]) + 3); | |
| sprintf(path, "%s/%s", path_in, namelist[i]); | |
| for (excl = 0; excl < n_excludes; ++excl) { | |
| if (strncmp(excludes[excl].path, path, | |
| excludes[excl].len) == 0) | |
| goto next; | |
| } | |
| ret = fchdir(dirfd); | |
| if (ret == -1) { | |
| perror("fchdir"); | |
| exit(-1); | |
| } | |
| ret = lstat64(namelist[i], &st); | |
| if (ret) { | |
| fprintf(stderr, "stat failed for %s/%s: %s\n", | |
| path_prefix, path, strerror(errno)); | |
| exit(-1); | |
| } | |
| /* We are crossing into a different subvol, skip this subtree. */ | |
| if (st.st_dev != dir_st.st_dev) | |
| goto next; | |
| sum_add_u64(&meta, level); | |
| sum_add(&meta, namelist[i], strlen(namelist[i])); | |
| if (!S_ISDIR(st.st_mode)) | |
| sum_add_u64(&meta, st.st_nlink); | |
| if (flags[FLAG_UID]) | |
| sum_add_u64(&meta, st.st_uid); | |
| if (flags[FLAG_GID]) | |
| sum_add_u64(&meta, st.st_gid); | |
| if (flags[FLAG_MODE]) | |
| sum_add_u64(&meta, st.st_mode); | |
| if (flags[FLAG_ATIME]) | |
| sum_add_time(&meta, st.st_atime); | |
| if (flags[FLAG_MTIME]) | |
| sum_add_time(&meta, st.st_mtime); | |
| if (flags[FLAG_CTIME]) | |
| sum_add_time(&meta, st.st_ctime); | |
| if (flags[FLAG_XATTRS] && | |
| (S_ISDIR(st.st_mode) || S_ISREG(st.st_mode))) { | |
| fd = openat(dirfd, namelist[i], 0); | |
| if (fd == -1 && flags[FLAG_OPEN_ERROR]) { | |
| sum_add_u64(&meta, errno); | |
| } else if (fd == -1) { | |
| fprintf(stderr, "open failed for %s/%s: %s\n", | |
| path_prefix, path, strerror(errno)); | |
| exit(-1); | |
| } else { | |
| ret = sum_xattrs(fd, &meta); | |
| close(fd); | |
| if (ret < 0) { | |
| fprintf(stderr, | |
| "failed to read xattrs from " | |
| "%s/%s: %s\n", | |
| path_prefix, path, | |
| strerror(-ret)); | |
| exit(-1); | |
| } | |
| } | |
| } | |
| if (S_ISDIR(st.st_mode)) { | |
| fd = openat(dirfd, namelist[i], 0); | |
| if (fd == -1 && flags[FLAG_OPEN_ERROR]) { | |
| sum_add_u64(&meta, errno); | |
| } else if (fd == -1) { | |
| fprintf(stderr, "open failed for %s/%s: %s\n", | |
| path_prefix, path, strerror(errno)); | |
| exit(-1); | |
| } else { | |
| sum(fd, level + 1, &cs, path_prefix, path); | |
| close(fd); | |
| } | |
| } else if (S_ISREG(st.st_mode)) { | |
| sum_add_u64(&meta, st.st_size); | |
| if (flags[FLAG_DATA]) { | |
| if (verbose) | |
| fprintf(stderr, "file %s\n", | |
| namelist[i]); | |
| fd = openat(dirfd, namelist[i], 0); | |
| if (fd == -1 && flags[FLAG_OPEN_ERROR]) { | |
| sum_add_u64(&meta, errno); | |
| } else if (fd == -1) { | |
| fprintf(stderr, | |
| "open failed for %s/%s: %s\n", | |
| path_prefix, path, | |
| strerror(errno)); | |
| exit(-1); | |
| } | |
| if (fd != -1) { | |
| ret = sum_file_data(fd, &cs); | |
| if (ret < 0) { | |
| fprintf(stderr, | |
| "read failed for " | |
| "%s/%s: %s\n", | |
| path_prefix, path, | |
| strerror(errno)); | |
| exit(-1); | |
| } | |
| close(fd); | |
| } | |
| } | |
| } else if (S_ISLNK(st.st_mode)) { | |
| ret = readlink(namelist[i], buf, sizeof(buf)); | |
| if (ret == -1) { | |
| perror("readlink"); | |
| exit(-1); | |
| } | |
| sum_add(&cs, buf, ret); | |
| } else if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) { | |
| sum_add_u64(&cs, major(st.st_rdev)); | |
| sum_add_u64(&cs, minor(st.st_rdev)); | |
| } | |
| sum_fini(&cs); | |
| sum_fini(&meta); | |
| if (gen_manifest || in_manifest) { | |
| char *fn; | |
| char *m; | |
| char *c; | |
| if (S_ISDIR(st.st_mode)) | |
| strcat(path, "/"); | |
| fn = escape(path); | |
| m = sum_to_string(&meta); | |
| c = sum_to_string(&cs); | |
| if (gen_manifest) | |
| fprintf(out_fp, "%s %s %s\n", fn, m, c); | |
| if (in_manifest) | |
| check_manifest(fn, m, c, 0); | |
| free(c); | |
| free(m); | |
| free(fn); | |
| } | |
| sum_add_sum(dircs, &cs); | |
| sum_add_sum(dircs, &meta); | |
| next: | |
| free(path); | |
| } | |
| } | |
| int | |
| main(int argc, char *argv[]) | |
| { | |
| extern char *optarg; | |
| extern int optind; | |
| int c; | |
| char *path; | |
| int fd; | |
| sum_t cs; | |
| char flagstring[sizeof(flchar)]; | |
| int i; | |
| int plen; | |
| int elen; | |
| int n_flags = 0; | |
| const char *allopts = "heEfuUgGoOaAmMcCdDtTsSnNw:r:vx:"; | |
| out_fp = stdout; | |
| while ((c = getopt(argc, argv, allopts)) != EOF) { | |
| switch(c) { | |
| case 'f': | |
| gen_manifest = 1; | |
| break; | |
| case 'u': | |
| case 'U': | |
| case 'g': | |
| case 'G': | |
| case 'o': | |
| case 'O': | |
| case 'a': | |
| case 'A': | |
| case 'm': | |
| case 'M': | |
| case 'c': | |
| case 'C': | |
| case 'd': | |
| case 'D': | |
| case 'T': | |
| case 't': | |
| case 'e': | |
| case 'E': | |
| case 's': | |
| case 'S': | |
| ++n_flags; | |
| parse_flag(c); | |
| break; | |
| case 'n': | |
| for (i = 0; i < NUM_FLAGS; ++i) | |
| flags[i] = 0; | |
| break; | |
| case 'N': | |
| for (i = 0; i < NUM_FLAGS; ++i) | |
| flags[i] = 1; | |
| break; | |
| case 'w': | |
| out_fp = fopen(optarg, "w"); | |
| if (!out_fp) { | |
| fprintf(stderr, | |
| "failed to open output file: %s\n", | |
| strerror(errno)); | |
| exit(-1); | |
| } | |
| break; | |
| case 'r': | |
| in_fp = fopen(optarg, "r"); | |
| if (!in_fp) { | |
| fprintf(stderr, | |
| "failed to open input file: %s\n", | |
| strerror(errno)); | |
| exit(-1); | |
| } | |
| break; | |
| case 'x': | |
| ++n_excludes; | |
| excludes = realloc(excludes, | |
| sizeof(*excludes) * n_excludes); | |
| if (!excludes) { | |
| fprintf(stderr, | |
| "failed to alloc exclude space\n"); | |
| exit(-1); | |
| } | |
| excludes[n_excludes - 1].path = optarg; | |
| break; | |
| case 'v': | |
| ++verbose; | |
| break; | |
| case 'h': | |
| case '?': | |
| usage(); | |
| } | |
| } | |
| if (optind + 1 != argc) { | |
| fprintf(stderr, "missing path\n"); | |
| usage(); | |
| } | |
| if (in_fp) { | |
| char *l = getln(line, sizeof(line), in_fp); | |
| char *p; | |
| if (l == NULL) { | |
| fprintf(stderr, "failed to read line from input\n"); | |
| exit(-1); | |
| } | |
| if (strncmp(l, "Flags: ", 7) == 0) { | |
| l += 7; | |
| in_manifest = 1; | |
| parse_flags(l); | |
| } else if ((p = strchr(l, ':'))) { | |
| *p++ = 0; | |
| parse_flags(l); | |
| checksum = strdup(p); | |
| } else { | |
| fprintf(stderr, "invalid input file format\n"); | |
| exit(-1); | |
| } | |
| if (n_flags) | |
| fprintf(stderr, "warning: " | |
| "command line flags ignored in -r mode\n"); | |
| } | |
| strcpy(flagstring, flchar); | |
| for (i = 0; i < NUM_FLAGS; ++i) { | |
| if (flags[i] == 0) | |
| flagstring[i] -= 'a' - 'A'; | |
| } | |
| path = argv[optind]; | |
| plen = strlen(path); | |
| if (path[plen - 1] == '/') { | |
| --plen; | |
| path[plen] = '\0'; | |
| } | |
| for (i = 0; i < n_excludes; ++i) { | |
| if (strncmp(path, excludes[i].path, plen) != 0) | |
| fprintf(stderr, | |
| "warning: exclude %s outside of path %s\n", | |
| excludes[i].path, path); | |
| else | |
| excludes[i].path += plen; | |
| elen = strlen(excludes[i].path); | |
| if (excludes[i].path[elen - 1] == '/') | |
| --elen; | |
| excludes[i].path[elen] = '\0'; | |
| excludes[i].len = elen; | |
| } | |
| fd = open(path, O_RDONLY); | |
| if (fd == -1) { | |
| fprintf(stderr, "failed to open %s: %s\n", path, | |
| strerror(errno)); | |
| exit(-1); | |
| } | |
| if (gen_manifest) | |
| fprintf(out_fp, "Flags: %s\n", flagstring); | |
| sum_init(&cs); | |
| sum(fd, 1, &cs, path, ""); | |
| sum_fini(&cs); | |
| close(fd); | |
| if (in_manifest) | |
| check_manifest("", "", "", 1); | |
| if (!checksum) { | |
| if (in_manifest) { | |
| fprintf(stderr, "malformed input\n"); | |
| exit(-1); | |
| } | |
| if (!gen_manifest) | |
| fprintf(out_fp, "%s:", flagstring); | |
| fprintf(out_fp, "%s\n", sum_to_string(&cs)); | |
| } else { | |
| if (strcmp(checksum, sum_to_string(&cs)) == 0) { | |
| printf("OK\n"); | |
| exit(0); | |
| } else { | |
| printf("FAIL\n"); | |
| exit(1); | |
| } | |
| } | |
| exit(0); | |
| } |