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