fsx/fsstress: round blocksize properly
[xfstests-dev.git] / src / fssum.c
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Copyright (C) 2012 STRATO AG.  All rights reserved.
4  */
5 #ifdef __linux__
6 #define _BSD_SOURCE
7 #define _DEFAULT_SOURCE
8 #define _LARGEFILE64_SOURCE
9 #ifndef _GNU_SOURCE
10 #define _GNU_SOURCE
11 #endif
12 #endif
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <unistd.h>
16 #include <string.h>
17 #include <fcntl.h>
18 #include <dirent.h>
19 #include <errno.h>
20 #include <sys/types.h>
21 #include <sys/stat.h>
22 #include <sys/sysmacros.h>
23 #include <sys/xattr.h>
24 #ifdef __SOLARIS__
25 #include <sys/mkdev.h>
26 #endif
27 #include "md5.h"
28 #include <netinet/in.h>
29 #include <inttypes.h>
30 #include <assert.h>
31 #include <endian.h>
32
33 #define CS_SIZE 16
34 #define CHUNKS  128
35
36 #ifdef __linux__
37 #ifndef SEEK_DATA
38 #define SEEK_DATA 3
39 #define SEEK_HOLE 4
40 #endif
41 #endif
42
43 /* TODO: add hardlink recognition */
44
45 struct excludes {
46         char *path;
47         int len;
48 };
49
50 typedef struct _sum {
51         MD5_CTX         md5;
52         unsigned char   out[16];
53 } sum_t;
54
55 typedef int (*sum_file_data_t)(int fd, sum_t *dst);
56
57 int gen_manifest = 0;
58 int in_manifest = 0;
59 char *checksum = NULL;
60 struct excludes *excludes;
61 int n_excludes = 0;
62 int verbose = 0;
63 FILE *out_fp;
64 FILE *in_fp;
65
66 enum _flags {
67         FLAG_UID,
68         FLAG_GID,
69         FLAG_MODE,
70         FLAG_ATIME,
71         FLAG_MTIME,
72         FLAG_CTIME,
73         FLAG_DATA,
74         FLAG_XATTRS,
75         FLAG_OPEN_ERROR,
76         FLAG_STRUCTURE,
77         NUM_FLAGS
78 };
79
80 const char flchar[] = "ugoamcdtes";
81 char line[65536];
82
83 int flags[NUM_FLAGS] = {1, 1, 1, 1, 1, 0, 1, 1, 0, 0};
84
85 char *
86 getln(char *buf, int size, FILE *fp)
87 {
88         char *p;
89         int l;
90
91         p = fgets(buf, size, fp);
92         if (!p)
93                 return NULL;
94
95         l = strlen(p);
96         while(l > 0  && (p[l - 1] == '\n' || p[l - 1] == '\r'))
97                 p[--l] = 0;
98
99         return p;
100 }
101
102 void
103 parse_flag(int c)
104 {
105         int i;
106         int is_upper = 0;
107
108         if (c >= 'A' && c <= 'Z') {
109                 is_upper = 1;
110                 c += 'a' - 'A';
111         }
112         for (i = 0; flchar[i]; ++i) {
113                 if (flchar[i] == c) {
114                         flags[i] = is_upper ? 0 : 1;
115                         return;
116                 }
117         }
118         fprintf(stderr, "unrecognized flag %c\n", c);
119         exit(-1);
120 }
121
122 void
123 parse_flags(char *p)
124 {
125         while (*p)
126                 parse_flag(*p++);
127 }
128
129 void
130 usage(void)
131 {
132         fprintf(stderr, "usage: fssum <options> <path>\n");
133         fprintf(stderr, "  options:\n");
134         fprintf(stderr, "    -f           : write out a full manifest file\n");
135         fprintf(stderr, "    -w <file>    : send output to file\n");
136         fprintf(stderr, "    -v           : verbose mode (debugging only)\n");
137         fprintf(stderr, "    -r <file>    : read checksum or manifest from file\n");
138         fprintf(stderr, "    -[ugoamcdtes]: specify which fields to include in checksum calculation.\n");
139         fprintf(stderr, "         u       : include uid\n");
140         fprintf(stderr, "         g       : include gid\n");
141         fprintf(stderr, "         o       : include mode\n");
142         fprintf(stderr, "         m       : include mtime\n");
143         fprintf(stderr, "         a       : include atime\n");
144         fprintf(stderr, "         c       : include ctime\n");
145         fprintf(stderr, "         d       : include file data\n");
146         fprintf(stderr, "         t       : include xattrs\n");
147         fprintf(stderr, "         e       : include open errors (aborts otherwise)\n");
148         fprintf(stderr, "         s       : include block structure (holes)\n");
149         fprintf(stderr, "    -[UGOAMCDTES]: exclude respective field from calculation\n");
150         fprintf(stderr, "    -n           : reset all flags\n");
151         fprintf(stderr, "    -N           : set all flags\n");
152         fprintf(stderr, "    -x path      : exclude path when building checksum (multiple ok)\n");
153         fprintf(stderr, "    -h           : this help\n\n");
154         fprintf(stderr, "The default field mask is ugoamCdtES. If the checksum/manifest is read from a\n");
155         fprintf(stderr, "file, the mask is taken from there and the values given on the command line\n");
156         fprintf(stderr, "are ignored.\n");
157         exit(-1);
158 }
159
160 static char buf[65536];
161
162 void *
163 alloc(size_t sz)
164 {
165         void *p = malloc(sz);
166
167         if (!p) {
168                 fprintf(stderr, "malloc failed\n");
169                 exit(-1);
170         }
171
172         return p;
173 }
174
175 void
176 sum_init(sum_t *cs)
177 {
178         MD5_Init(&cs->md5);
179 }
180
181 void
182 sum_fini(sum_t *cs)
183 {
184         MD5_Final(cs->out, &cs->md5);
185 }
186
187 void
188 sum_add(sum_t *cs, void *buf, int size)
189 {
190         MD5_Update(&cs->md5, buf, size);
191 }
192
193 void
194 sum_add_sum(sum_t *dst, sum_t *src)
195 {
196         sum_add(dst, src->out, sizeof(src->out));
197 }
198
199 void
200 sum_add_u64(sum_t *dst, uint64_t val)
201 {
202         uint64_t v = htobe64(val);
203         sum_add(dst, &v, sizeof(v));
204 }
205
206 void
207 sum_add_time(sum_t *dst, time_t t)
208 {
209         sum_add_u64(dst, t);
210 }
211
212 char *
213 sum_to_string(sum_t *dst)
214 {
215         int i;
216         char *s = alloc(CS_SIZE * 2 + 1);
217
218         for (i = 0; i < CS_SIZE; ++i)
219                 sprintf(s + i * 2, "%02x", dst->out[i]);
220
221         return s;
222 }
223
224 int
225 namecmp(const void *aa, const void *bb)
226 {
227         char * const *a = aa;
228         char * const *b = bb;
229
230         return strcmp(*a, *b);
231 }
232
233 int
234 sum_xattrs(int fd, sum_t *dst)
235 {
236         ssize_t buflen;
237         ssize_t len;
238         char *buf;
239         char *p;
240         char **names = NULL;
241         int num_xattrs = 0;
242         int ret = 0;
243         int i;
244
245         buflen = flistxattr(fd, NULL, 0);
246         if (buflen < 0)
247                 return -errno;
248         /* no xattrs exist */
249         if (buflen == 0)
250                 return 0;
251
252         buf = malloc(buflen);
253         if (!buf)
254                 return -ENOMEM;
255
256         buflen = flistxattr(fd, buf, buflen);
257         if (buflen < 0) {
258                 ret = -errno;
259                 goto out;
260         }
261
262         /*
263          * Keep the list of xattrs sorted, because the order in which they are
264          * listed is filesystem dependent, so we want to get the same checksum
265          * on different filesystems.
266          */
267
268         p = buf;
269         len = buflen;
270         while (len > 0) {
271                 int keylen;
272
273                 keylen = strlen(p) + 1; /* +1 for NULL terminator */
274                 len -= keylen;
275                 p += keylen;
276                 num_xattrs++;
277         }
278
279         names = malloc(sizeof(char *) * num_xattrs);
280         if (!names) {
281                 ret = -ENOMEM;
282                 goto out;
283         }
284
285         p = buf;
286         for (i = 0; i < num_xattrs; i++) {
287                 names[i] = p;
288                 p += strlen(p) + 1; /* +1 for NULL terminator */
289         }
290
291         qsort(names, num_xattrs, sizeof(char *), namecmp);
292
293         for (i = 0; i < num_xattrs; i++) {
294                 len = fgetxattr(fd, names[i], NULL, 0);
295                 if (len < 0) {
296                         ret = -errno;
297                         goto out;
298                 }
299                 sum_add(dst, names[i], strlen(names[i]));
300                 /* no value */
301                 if (len == 0)
302                         continue;
303                 p = malloc(len);
304                 if (!p) {
305                         ret = -ENOMEM;
306                         goto out;
307                 }
308                 len = fgetxattr(fd, names[i], p, len);
309                 if (len < 0) {
310                         ret = -errno;
311                         free(p);
312                         goto out;
313                 }
314                 sum_add(dst, p, len);
315                 free(p);
316         }
317 out:
318         free(buf);
319         free(names);
320
321         return ret;
322 }
323
324 int
325 sum_file_data_permissive(int fd, sum_t *dst)
326 {
327         int ret;
328
329         while (1) {
330                 ret = read(fd, buf, sizeof(buf));
331                 if (ret < 0)
332                         return -errno;
333                 sum_add(dst, buf, ret);
334                 if (ret < sizeof(buf))
335                         break;
336         }
337         return 0;
338 }
339
340 int
341 sum_file_data_strict(int fd, sum_t *dst)
342 {
343         int ret;
344         off_t pos;
345
346         pos = lseek(fd, 0, SEEK_CUR);
347         if (pos == (off_t)-1)
348                 return errno == ENXIO ? 0 : -2;
349
350         while (1) {
351                 pos = lseek(fd, pos, SEEK_DATA);
352                 if (pos == (off_t)-1)
353                         return errno == ENXIO ? 0 : -2;
354                 ret = read(fd, buf, sizeof(buf));
355                 assert(ret); /* eof found by lseek */
356                 if (ret <= 0)
357                         return ret;
358                 if (verbose >= 2)
359                         fprintf(stderr,
360                                 "adding to sum at file offset %llu, %d bytes\n",
361                                 (unsigned long long)pos, ret);
362                 sum_add_u64(dst, (uint64_t)pos);
363                 sum_add(dst, buf, ret);
364                 pos += ret;
365         }
366 }
367
368 char *
369 escape(char *in)
370 {
371         char *out = alloc(strlen(in) * 3 + 1);
372         char *src = in;
373         char *dst = out;
374
375         for (; *src; ++src) {
376                 if (*src >= 32 && *src < 127 && *src != '\\') {
377                         *dst++ = *src;
378                 } else {
379                         sprintf(dst, "\\%02x", (unsigned char)*src);
380                         dst += 3;
381                 }
382         }
383         *dst = 0;
384
385         return out;
386 }
387
388 void
389 excess_file(const char *fn)
390 {
391         printf("only in local fs: %s\n", fn);
392 }
393
394 void
395 missing_file(const char *fn)
396 {
397         printf("only in remote fs: %s\n", fn);
398 }
399
400 int
401 pathcmp(const char *a, const char *b)
402 {
403         int len_a = strlen(a);
404         int len_b = strlen(b);
405
406         /*
407          * as the containing directory is sent after the files, it has to
408          * come out bigger in the comparison.
409          */
410         if (len_a < len_b && a[len_a - 1] == '/' && strncmp(a, b, len_a) == 0)
411                 return 1;
412         if (len_a > len_b && b[len_b - 1] == '/' && strncmp(a, b, len_b) == 0)
413                 return -1;
414
415         return strcmp(a, b);
416 }
417
418 void
419 check_match(char *fn, char *local_m, char *remote_m,
420             char *local_c, char *remote_c)
421 {
422         int match_m = !strcmp(local_m, remote_m);
423         int match_c = !strcmp(local_c, remote_c);
424
425         if (match_m && !match_c) {
426                 printf("data mismatch in %s\n", fn);
427         } else if (!match_m && match_c) {
428                 printf("metadata mismatch in %s\n", fn);
429         } else if (!match_m && !match_c) {
430                 printf("metadata and data mismatch in %s\n", fn);
431         }
432 }
433
434 char *prev_fn;
435 char *prev_m;
436 char *prev_c;
437 void
438 check_manifest(char *fn, char *m, char *c, int last_call)
439 {
440         char *rem_m;
441         char *rem_c;
442         char *l;
443         int cmp;
444
445         if (prev_fn) {
446                 if (last_call)
447                         cmp = -1;
448                 else
449                         cmp = pathcmp(prev_fn, fn);
450                 if (cmp > 0) {
451                         excess_file(fn);
452                         return;
453                 } else if (cmp < 0) {
454                         missing_file(prev_fn);
455                 } else {
456                         check_match(fn, m, prev_m, c, prev_c);
457                 }
458                 free(prev_fn);
459                 free(prev_m);
460                 free(prev_c);
461                 prev_fn = NULL;
462                 prev_m = NULL;
463                 prev_c = NULL;
464                 if (cmp == 0)
465                         return;
466         }
467         while ((l = getln(line, sizeof(line), in_fp))) {
468                 rem_c = strrchr(l, ' ');
469                 if (!rem_c) {
470                         /* final cs */
471                         checksum = strdup(l);
472                         break;
473                 }
474                 if (rem_c == l) {
475 malformed:
476                         fprintf(stderr, "malformed input\n");
477                         exit(-1);
478                 }
479                 *rem_c++ = 0;
480                 rem_m = strrchr(l, ' ');
481                 if (!rem_m)
482                         goto malformed;
483                 *rem_m++ = 0;
484
485                 if (last_call)
486                         cmp = -1;
487                 else
488                         cmp = pathcmp(l, fn);
489                 if (cmp == 0) {
490                         check_match(fn, m, rem_m, c, rem_c);
491                         return;
492                 } else if (cmp > 0) {
493                         excess_file(fn);
494                         prev_fn = strdup(l);
495                         prev_m = strdup(rem_m);
496                         prev_c = strdup(rem_c); 
497                         return;
498                 }
499                 missing_file(l);
500         }
501         if (!last_call)
502                 excess_file(fn);
503 }
504
505 void
506 sum(int dirfd, int level, sum_t *dircs, char *path_prefix, char *path_in)
507 {
508         DIR *d;
509         struct dirent *de;
510         char **namelist = NULL;
511         int alloclen = 0;
512         int entries = 0;
513         int i;
514         int ret;
515         int fd;
516         int excl;
517         sum_file_data_t sum_file_data = flags[FLAG_STRUCTURE] ?
518                         sum_file_data_strict : sum_file_data_permissive;
519         struct stat64 dir_st;
520
521         if (fstat64(dirfd, &dir_st)) {
522                 perror("fstat");
523                 exit(-1);
524         }
525
526         d = fdopendir(dirfd);
527         if (!d) {
528                 perror("opendir");
529                 exit(-1);
530         }
531         while((de = readdir(d))) {
532                 if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
533                         continue;
534                 if (entries == alloclen) {
535                         alloclen += CHUNKS;
536                         namelist = realloc(namelist,
537                                            alloclen * sizeof(*namelist));
538                         if (!namelist) {
539                                 fprintf(stderr, "malloc failed\n");
540                                 exit(-1);
541                         }
542                 }
543                 namelist[entries] = strdup(de->d_name);
544                 if (!namelist[entries]) {
545                         fprintf(stderr, "malloc failed\n");
546                         exit(-1);
547                 }
548                 ++entries;
549         }
550         qsort(namelist, entries, sizeof(*namelist), namecmp);
551         for (i = 0; i < entries; ++i) {
552                 struct stat64 st;
553                 sum_t cs;
554                 sum_t meta;
555                 char *path;
556
557                 sum_init(&cs);
558                 sum_init(&meta);
559                 path = alloc(strlen(path_in) + strlen(namelist[i]) + 3);
560                 sprintf(path, "%s/%s", path_in, namelist[i]);
561                 for (excl = 0; excl < n_excludes; ++excl) {
562                         if (strncmp(excludes[excl].path, path,
563                             excludes[excl].len) == 0)
564                                 goto next;
565                 }
566
567                 ret = fchdir(dirfd);
568                 if (ret == -1) {
569                         perror("fchdir");
570                         exit(-1);
571                 }
572                 ret = lstat64(namelist[i], &st);
573                 if (ret) {
574                         fprintf(stderr, "stat failed for %s/%s: %s\n",
575                                 path_prefix, path, strerror(errno));
576                         exit(-1);
577                 }
578
579                 /* We are crossing into a different subvol, skip this subtree. */
580                 if (st.st_dev != dir_st.st_dev)
581                         goto next;
582
583                 sum_add_u64(&meta, level);
584                 sum_add(&meta, namelist[i], strlen(namelist[i]));
585                 if (!S_ISDIR(st.st_mode))
586                         sum_add_u64(&meta, st.st_nlink);
587                 if (flags[FLAG_UID])
588                         sum_add_u64(&meta, st.st_uid);
589                 if (flags[FLAG_GID])
590                         sum_add_u64(&meta, st.st_gid);
591                 if (flags[FLAG_MODE])
592                         sum_add_u64(&meta, st.st_mode);
593                 if (flags[FLAG_ATIME])
594                         sum_add_time(&meta, st.st_atime);
595                 if (flags[FLAG_MTIME])
596                         sum_add_time(&meta, st.st_mtime);
597                 if (flags[FLAG_CTIME])
598                         sum_add_time(&meta, st.st_ctime);
599                 if (flags[FLAG_XATTRS] &&
600                     (S_ISDIR(st.st_mode) || S_ISREG(st.st_mode))) {
601                         fd = openat(dirfd, namelist[i], 0);
602                         if (fd == -1 && flags[FLAG_OPEN_ERROR]) {
603                                 sum_add_u64(&meta, errno);
604                         } else if (fd == -1) {
605                                 fprintf(stderr, "open failed for %s/%s: %s\n",
606                                         path_prefix, path, strerror(errno));
607                                 exit(-1);
608                         } else {
609                                 ret = sum_xattrs(fd, &meta);
610                                 close(fd);
611                                 if (ret < 0) {
612                                         fprintf(stderr,
613                                                 "failed to read xattrs from "
614                                                 "%s/%s: %s\n",
615                                                 path_prefix, path,
616                                                 strerror(-ret));
617                                         exit(-1);
618                                 }
619                         }
620                 }
621                 if (S_ISDIR(st.st_mode)) {
622                         fd = openat(dirfd, namelist[i], 0);
623                         if (fd == -1 && flags[FLAG_OPEN_ERROR]) {
624                                 sum_add_u64(&meta, errno);
625                         } else if (fd == -1) {
626                                 fprintf(stderr, "open failed for %s/%s: %s\n",
627                                         path_prefix, path, strerror(errno));
628                                 exit(-1);
629                         } else {
630                                 sum(fd, level + 1, &cs, path_prefix, path);
631                                 close(fd);
632                         }
633                 } else if (S_ISREG(st.st_mode)) {
634                         sum_add_u64(&meta, st.st_size);
635                         if (flags[FLAG_DATA]) {
636                                 if (verbose)
637                                         fprintf(stderr, "file %s\n",
638                                                 namelist[i]);
639                                 fd = openat(dirfd, namelist[i], 0);
640                                 if (fd == -1 && flags[FLAG_OPEN_ERROR]) {
641                                         sum_add_u64(&meta, errno);
642                                 } else if (fd == -1) {
643                                         fprintf(stderr,
644                                                 "open failed for %s/%s: %s\n",
645                                                 path_prefix, path,
646                                                 strerror(errno));
647                                         exit(-1);
648                                 }
649                                 if (fd != -1) {
650                                         ret = sum_file_data(fd, &cs);
651                                         if (ret < 0) {
652                                                 fprintf(stderr,
653                                                         "read failed for "
654                                                         "%s/%s: %s\n",
655                                                         path_prefix, path,
656                                                         strerror(errno));
657                                                 exit(-1);
658                                         }
659                                         close(fd);
660                                 }
661                         }
662                 } else if (S_ISLNK(st.st_mode)) {
663                         ret = readlink(namelist[i], buf, sizeof(buf));
664                         if (ret == -1) {
665                                 perror("readlink");
666                                 exit(-1);
667                         }
668                         sum_add(&cs, buf, ret);
669                 } else if (S_ISCHR(st.st_mode) || S_ISBLK(st.st_mode)) {
670                         sum_add_u64(&cs, major(st.st_rdev));
671                         sum_add_u64(&cs, minor(st.st_rdev));
672                 }
673                 sum_fini(&cs);
674                 sum_fini(&meta);
675                 if (gen_manifest || in_manifest) {
676                         char *fn;
677                         char *m;
678                         char *c;
679
680                         if (S_ISDIR(st.st_mode))
681                                 strcat(path, "/");
682                         fn = escape(path);
683                         m = sum_to_string(&meta);
684                         c = sum_to_string(&cs);
685
686                         if (gen_manifest)
687                                 fprintf(out_fp, "%s %s %s\n", fn, m, c);
688                         if (in_manifest)
689                                 check_manifest(fn, m, c, 0);
690                         free(c);
691                         free(m);
692                         free(fn);
693                 }
694                 sum_add_sum(dircs, &cs);
695                 sum_add_sum(dircs, &meta);
696 next:
697                 free(path);
698         }
699 }
700
701 int
702 main(int argc, char *argv[])
703 {
704         extern char *optarg;
705         extern int optind;
706         int     c;
707         char *path;
708         int fd;
709         sum_t cs;
710         char flagstring[sizeof(flchar)];
711         int i;
712         int plen;
713         int elen;
714         int n_flags = 0;
715         const char *allopts = "heEfuUgGoOaAmMcCdDtTsSnNw:r:vx:";
716
717         out_fp = stdout;
718         while ((c = getopt(argc, argv, allopts)) != EOF) {
719                 switch(c) {
720                 case 'f':
721                         gen_manifest = 1;
722                         break;
723                 case 'u':
724                 case 'U':
725                 case 'g':
726                 case 'G':
727                 case 'o':
728                 case 'O':
729                 case 'a':
730                 case 'A':
731                 case 'm':
732                 case 'M':
733                 case 'c':
734                 case 'C':
735                 case 'd':
736                 case 'D':
737                 case 'T':
738                 case 't':
739                 case 'e':
740                 case 'E':
741                 case 's':
742                 case 'S':
743                         ++n_flags;
744                         parse_flag(c);
745                         break;
746                 case 'n':
747                         for (i = 0; i < NUM_FLAGS; ++i)
748                                 flags[i] = 0;
749                         break;
750                 case 'N':
751                         for (i = 0; i < NUM_FLAGS; ++i)
752                                 flags[i] = 1;
753                         break;
754                 case 'w':
755                         out_fp = fopen(optarg, "w");
756                         if (!out_fp) {
757                                 fprintf(stderr,
758                                         "failed to open output file: %s\n",
759                                         strerror(errno));
760                                 exit(-1);
761                         }
762                         break;
763                 case 'r':
764                         in_fp = fopen(optarg, "r");
765                         if (!in_fp) {
766                                 fprintf(stderr,
767                                         "failed to open input file: %s\n",
768                                         strerror(errno));
769                                 exit(-1);
770                         }
771                         break;
772                 case 'x':
773                         ++n_excludes;
774                         excludes = realloc(excludes,
775                                            sizeof(*excludes) * n_excludes);
776                         if (!excludes) {
777                                 fprintf(stderr,
778                                         "failed to alloc exclude space\n");
779                                 exit(-1);
780                         }
781                         excludes[n_excludes - 1].path = optarg;
782                         break;
783                 case 'v':
784                         ++verbose;
785                         break;
786                 case 'h':
787                 case '?':
788                         usage();
789                 }
790         }
791
792         if (optind + 1 != argc) {
793                 fprintf(stderr, "missing path\n");
794                 usage();
795         }
796
797         if (in_fp) {
798                 char *l = getln(line, sizeof(line), in_fp);
799                 char *p;
800
801                 if (l == NULL) {
802                         fprintf(stderr, "failed to read line from input\n");
803                         exit(-1);
804                 }
805                 if (strncmp(l, "Flags: ", 7) == 0) {
806                         l += 7;
807                         in_manifest = 1;
808                         parse_flags(l);
809                 } else if ((p = strchr(l, ':'))) {
810                         *p++ = 0;
811                         parse_flags(l);
812                         checksum = strdup(p);
813                 } else {
814                         fprintf(stderr, "invalid input file format\n");
815                         exit(-1);
816                 }
817                 if (n_flags)
818                         fprintf(stderr, "warning: "
819                                 "command line flags ignored in -r mode\n");
820         }
821         strcpy(flagstring, flchar);
822         for (i = 0; i < NUM_FLAGS; ++i) {
823                 if (flags[i] == 0)
824                         flagstring[i] -= 'a' - 'A';
825         }
826
827         path = argv[optind];
828         plen = strlen(path);
829         if (path[plen - 1] == '/') {
830                 --plen;
831                 path[plen] = '\0';
832         }
833
834         for (i = 0; i < n_excludes; ++i) {
835                 if (strncmp(path, excludes[i].path, plen) != 0)
836                         fprintf(stderr,
837                                 "warning: exclude %s outside of path %s\n",
838                                 excludes[i].path, path);
839                 else
840                         excludes[i].path += plen;
841                 elen = strlen(excludes[i].path);
842                 if (excludes[i].path[elen - 1] == '/')
843                         --elen;
844                 excludes[i].path[elen] = '\0';
845                 excludes[i].len = elen;
846         }
847
848         fd = open(path, O_RDONLY);
849         if (fd == -1) {
850                 fprintf(stderr, "failed to open %s: %s\n", path,
851                         strerror(errno));
852                 exit(-1);
853         }
854
855         if (gen_manifest)
856                 fprintf(out_fp, "Flags: %s\n", flagstring);
857
858         sum_init(&cs);
859         sum(fd, 1, &cs, path, "");
860         sum_fini(&cs);
861
862         close(fd);
863         if (in_manifest)
864                 check_manifest("", "", "", 1);
865
866         if (!checksum) {
867                 if (in_manifest) {
868                         fprintf(stderr, "malformed input\n");
869                         exit(-1);
870                 }
871                 if (!gen_manifest)
872                         fprintf(out_fp, "%s:", flagstring);
873
874                 fprintf(out_fp, "%s\n", sum_to_string(&cs));
875         } else {
876                 if (strcmp(checksum, sum_to_string(&cs)) == 0) {
877                         printf("OK\n");
878                         exit(0);
879                 } else {
880                         printf("FAIL\n");
881                         exit(1);
882                 }
883         }
884
885         exit(0);
886 }