2 * Copyright (C) 2012 STRATO AG. All rights reserved.
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public
6 * License v2 as published by the Free Software Foundation.
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 * General Public License for more details.
13 * You should have received a copy of the GNU General Public
14 * License along with this program; if not, write to the
15 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
16 * Boston, MA 021110-1307, USA.
19 #include "kerncompat.h"
27 #include <sys/sysmacros.h>
32 #include "tests/sha.h"
42 /* TODO: add hardlink recognition */
43 /* TODO: add xattr/acl */
52 unsigned char out
[CS_SIZE
];
55 typedef int (*sum_file_data_t
)(int fd
, sum_t
*dst
);
59 char *checksum
= NULL
;
60 struct excludes
*excludes
;
79 const char flchar
[] = "ugoamcdes";
82 int flags
[NUM_FLAGS
] = {1, 1, 1, 1, 1, 0, 1, 0, 0};
85 getln(char *buf
, int size
, FILE *fp
)
90 p
= fgets(buf
, size
, fp
);
95 while(l
> 0 && (p
[l
- 1] == '\n' || p
[l
- 1] == '\r'))
107 if (c
>= 'A' && c
<= 'Z') {
111 for (i
= 0; flchar
[i
]; ++i
) {
112 if (flchar
[i
] == c
) {
113 flags
[i
] = is_upper
? 0 : 1;
117 fprintf(stderr
, "unrecognized flag %c\n", c
);
131 fprintf(stderr
, "usage: fssum <options> <path>\n");
132 fprintf(stderr
, " options:\n");
133 fprintf(stderr
, " -f : write out a full manifest file\n");
134 fprintf(stderr
, " -w <file> : send output to file\n");
135 fprintf(stderr
, " -v : verbose mode (debugging only)\n");
137 " -r <file> : read checksum or manifest from file\n");
138 fprintf(stderr
, " -[ugoamcde] : 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
, " e : include open errors (aborts otherwise)\n");
147 fprintf(stderr
, " s : include block structure (holes)\n");
148 fprintf(stderr
, " -[UGOAMCDES]: exclude respective field from calculation\n");
149 fprintf(stderr
, " -n : reset all flags\n");
150 fprintf(stderr
, " -N : set all flags\n");
151 fprintf(stderr
, " -x path : exclude path when building checksum (multiple ok)\n");
152 fprintf(stderr
, " -h : this help\n\n");
153 fprintf(stderr
, "The default field mask is ugoamCdES. If the checksum/manifest is read from a\n");
154 fprintf(stderr
, "file, the mask is taken from there and the values given on the command line\n");
155 fprintf(stderr
, "are ignored.\n");
159 static char buf
[65536];
164 void *p
= malloc(sz
);
167 fprintf(stderr
, "malloc failed\n");
177 SHA256Reset(&cs
->sha
);
183 SHA256Result(&cs
->sha
, cs
->out
);
187 sum_add(sum_t
*cs
, void *buf
, int size
)
189 SHA256Input(&cs
->sha
, buf
, size
);
193 sum_add_sum(sum_t
*dst
, sum_t
*src
)
195 sum_add(dst
, src
->out
, sizeof(src
->out
));
199 sum_add_u64(sum_t
*dst
, uint64_t val
)
201 uint64_t v
= cpu_to_le64(val
);
202 sum_add(dst
, &v
, sizeof(v
));
206 sum_add_time(sum_t
*dst
, time_t t
)
212 sum_to_string(sum_t
*dst
)
215 char *s
= alloc(CS_SIZE
* 2 + 1);
217 for (i
= 0; i
< CS_SIZE
; ++i
)
218 sprintf(s
+ i
* 2, "%02x", dst
->out
[i
]);
224 sum_file_data_permissive(int fd
, sum_t
*dst
)
232 pos
= lseek(fd
, 0, SEEK_CUR
);
233 if (pos
== (off_t
)-1)
234 return errno
== ENXIO
? 0 : -2;
238 pos
= lseek(fd
, pos
, SEEK_DATA
);
239 if (pos
== (off_t
)-1) {
240 if (errno
== ENXIO
) {
242 pos
= lseek(fd
, 0, SEEK_END
);
243 if (pos
!= (off_t
)-1)
250 ret
= read(fd
, buf
, sizeof(buf
));
251 assert(ret
); /* eof found by lseek */
254 if (old
< pos
) /* hole */
256 for (i
= 0; i
< ret
; ++i
) {
257 for (old
= i
; buf
[i
] == 0 && i
< ret
; ++i
)
259 if (old
< i
) /* code like a hole */
266 "adding %llu zeros to sum\n",
267 (unsigned long long)zeros
);
269 sum_add_u64(dst
, zeros
);
272 for (old
= i
; buf
[i
] != 0 && i
< ret
; ++i
)
275 fprintf(stderr
, "adding %u non-zeros to sum\n",
277 sum_add(dst
, buf
+ old
, i
- old
);
285 "adding %llu zeros to sum (finishing)\n",
286 (unsigned long long)zeros
);
288 sum_add_u64(dst
, zeros
);
295 sum_file_data_strict(int fd
, sum_t
*dst
)
300 pos
= lseek(fd
, 0, SEEK_CUR
);
301 if (pos
== (off_t
)-1)
302 return errno
== ENXIO
? 0 : -2;
305 pos
= lseek(fd
, pos
, SEEK_DATA
);
306 if (pos
== (off_t
)-1)
307 return errno
== ENXIO
? 0 : -2;
308 ret
= read(fd
, buf
, sizeof(buf
));
309 assert(ret
); /* eof found by lseek */
314 "adding to sum at file offset %llu, %d bytes\n",
315 (unsigned long long)pos
, ret
);
316 sum_add_u64(dst
, (uint64_t)pos
);
317 sum_add(dst
, buf
, ret
);
325 char *out
= alloc(strlen(in
) * 3 + 1);
329 for (; *src
; ++src
) {
330 if (*src
>= 32 && *src
< 127 && *src
!= '\\') {
333 sprintf(dst
, "\\%02x", (unsigned char)*src
);
343 excess_file(const char *fn
)
345 printf("only in local fs: %s\n", fn
);
349 missing_file(const char *fn
)
351 printf("only in remote fs: %s\n", fn
);
355 pathcmp(const char *a
, const char *b
)
357 int len_a
= strlen(a
);
358 int len_b
= strlen(b
);
361 * as the containing directory is sent after the files, it has to
362 * come out bigger in the comparison.
364 if (len_a
< len_b
&& a
[len_a
- 1] == '/' && strncmp(a
, b
, len_a
) == 0)
366 if (len_a
> len_b
&& b
[len_b
- 1] == '/' && strncmp(a
, b
, len_b
) == 0)
373 check_match(char *fn
, char *local_m
, char *remote_m
,
374 char *local_c
, char *remote_c
)
376 int match_m
= !strcmp(local_m
, remote_m
);
377 int match_c
= !strcmp(local_c
, remote_c
);
379 if (match_m
&& !match_c
) {
380 printf("data mismatch in %s\n", fn
);
381 } else if (!match_m
&& match_c
) {
382 printf("metadata mismatch in %s\n", fn
);
383 } else if (!match_m
&& !match_c
) {
384 printf("metadata and data mismatch in %s\n", fn
);
392 check_manifest(char *fn
, char *m
, char *c
, int last_call
)
403 cmp
= pathcmp(prev_fn
, fn
);
407 } else if (cmp
< 0) {
408 missing_file(prev_fn
);
410 check_match(fn
, m
, prev_m
, c
, prev_c
);
421 while ((l
= getln(line
, sizeof(line
), in_fp
))) {
422 rem_c
= strrchr(l
, ' ');
428 checksum
= strdup(l
);
433 fprintf(stderr
, "malformed input\n");
437 rem_m
= strrchr(l
, ' ');
445 cmp
= pathcmp(l
, fn
);
447 check_match(fn
, m
, rem_m
, c
, rem_c
);
449 } else if (cmp
> 0) {
452 prev_m
= strdup(rem_m
);
453 prev_c
= strdup(rem_c
);
463 namecmp(const void *aa
, const void *bb
)
465 char * const *a
= aa
;
466 char * const *b
= bb
;
468 return strcmp(*a
, *b
);
472 sum(int dirfd
, int level
, sum_t
*dircs
, char *path_prefix
, char *path_in
)
476 char **namelist
= NULL
;
483 sum_file_data_t sum_file_data
= flags
[FLAG_STRUCTURE
] ?
484 sum_file_data_strict
: sum_file_data_permissive
;
486 d
= fdopendir(dirfd
);
491 while((de
= readdir(d
))) {
492 if (!strcmp(de
->d_name
, ".") || !strcmp(de
->d_name
, ".."))
494 if (entries
== alloclen
) {
496 namelist
= realloc(namelist
,
497 alloclen
* sizeof(*namelist
));
499 fprintf(stderr
, "malloc failed\n");
503 namelist
[entries
] = strdup(de
->d_name
);
504 if (!namelist
[entries
]) {
505 fprintf(stderr
, "malloc failed\n");
511 qsort(namelist
, entries
, sizeof(*namelist
), namecmp
);
512 for (i
= 0; i
< entries
; ++i
) {
520 path
= alloc(strlen(path_in
) + strlen(namelist
[i
]) + 3);
521 sprintf(path
, "%s/%s", path_in
, namelist
[i
]);
522 for (excl
= 0; excl
< n_excludes
; ++excl
) {
523 if (strncmp(excludes
[excl
].path
, path
,
524 excludes
[excl
].len
) == 0)
533 ret
= lstat64(namelist
[i
], &st
);
535 fprintf(stderr
, "stat failed for %s/%s: %m\n",
539 sum_add_u64(&meta
, level
);
540 sum_add(&meta
, namelist
[i
], strlen(namelist
[i
]));
541 if (!S_ISDIR(st
.st_mode
))
542 sum_add_u64(&meta
, st
.st_nlink
);
544 sum_add_u64(&meta
, st
.st_uid
);
546 sum_add_u64(&meta
, st
.st_gid
);
547 if (flags
[FLAG_MODE
])
548 sum_add_u64(&meta
, st
.st_mode
);
549 if (flags
[FLAG_ATIME
])
550 sum_add_time(&meta
, st
.st_atime
);
551 if (flags
[FLAG_MTIME
])
552 sum_add_time(&meta
, st
.st_mtime
);
553 if (flags
[FLAG_CTIME
])
554 sum_add_time(&meta
, st
.st_ctime
);
555 if (S_ISDIR(st
.st_mode
)) {
556 fd
= openat(dirfd
, namelist
[i
], 0);
557 if (fd
== -1 && flags
[FLAG_OPEN_ERROR
]) {
558 sum_add_u64(&meta
, errno
);
559 } else if (fd
== -1) {
560 fprintf(stderr
, "open failed for %s/%s: %m\n",
564 sum(fd
, level
+ 1, &cs
, path_prefix
, path
);
567 } else if (S_ISREG(st
.st_mode
)) {
568 sum_add_u64(&meta
, st
.st_size
);
569 if (flags
[FLAG_DATA
]) {
571 fprintf(stderr
, "file %s\n",
573 fd
= openat(dirfd
, namelist
[i
], 0);
574 if (fd
== -1 && flags
[FLAG_OPEN_ERROR
]) {
575 sum_add_u64(&meta
, errno
);
576 } else if (fd
== -1) {
578 "open failed for %s/%s: %m\n",
583 ret
= sum_file_data(fd
, &cs
);
594 } else if (S_ISLNK(st
.st_mode
)) {
595 ret
= readlink(namelist
[i
], buf
, sizeof(buf
));
600 sum_add(&cs
, buf
, ret
);
601 } else if (S_ISCHR(st
.st_mode
) || S_ISBLK(st
.st_mode
)) {
602 sum_add_u64(&cs
, major(st
.st_rdev
));
603 sum_add_u64(&cs
, minor(st
.st_rdev
));
607 if (gen_manifest
|| in_manifest
) {
612 if (S_ISDIR(st
.st_mode
))
615 m
= sum_to_string(&meta
);
616 c
= sum_to_string(&cs
);
619 fprintf(out_fp
, "%s %s %s\n", fn
, m
, c
);
621 check_manifest(fn
, m
, c
, 0);
626 sum_add_sum(dircs
, &cs
);
627 sum_add_sum(dircs
, &meta
);
638 main(int argc
, char *argv
[])
647 char flagstring
[sizeof(flchar
)];
653 const char *allopts
= "heEfuUgGoOaAmMcCdDsSnNw:r:vx:";
656 while ((c
= getopt(argc
, argv
, allopts
)) != EOF
) {
683 for (i
= 0; i
< NUM_FLAGS
; ++i
)
687 for (i
= 0; i
< NUM_FLAGS
; ++i
)
691 out_fp
= fopen(optarg
, "w");
694 "failed to open output file: %m\n");
699 in_fp
= fopen(optarg
, "r");
702 "failed to open input file: %m\n");
708 excludes
= realloc(excludes
,
709 sizeof(*excludes
) * n_excludes
);
712 "failed to alloc exclude space\n");
715 excludes
[n_excludes
- 1].path
= optarg
;
726 if (optind
+ 1 != argc
) {
727 fprintf(stderr
, "missing path\n");
732 char *l
= getln(line
, sizeof(line
), in_fp
);
736 fprintf(stderr
, "failed to read line from input\n");
739 if (strncmp(l
, "Flags: ", 7) == 0) {
743 } else if ((p
= strchr(l
, ':'))) {
749 checksum
= strdup(p
);
751 fprintf(stderr
, "invalid input file format\n");
755 fprintf(stderr
, "warning: "
756 "command line flags ignored in -r mode\n");
758 strcpy(flagstring
, flchar
);
759 for (i
= 0; i
< NUM_FLAGS
; ++i
) {
761 flagstring
[i
] -= 'a' - 'A';
766 if (path
[plen
- 1] == '/') {
771 for (i
= 0; i
< n_excludes
; ++i
) {
772 if (strncmp(path
, excludes
[i
].path
, plen
) != 0)
774 "warning: exclude %s outside of path %s\n",
775 excludes
[i
].path
, path
);
777 excludes
[i
].path
+= plen
;
778 elen
= strlen(excludes
[i
].path
);
779 if (excludes
[i
].path
[elen
- 1] == '/')
781 excludes
[i
].path
[elen
] = '\0';
782 excludes
[i
].len
= elen
;
785 fd
= open(path
, O_RDONLY
);
787 fprintf(stderr
, "failed to open %s: %m\n", path
);
792 fprintf(out_fp
, "Flags: %s\n", flagstring
);
795 sum(fd
, 1, &cs
, path
, "");
800 check_manifest("", "", "", 1);
804 fprintf(stderr
, "malformed input\n");
808 fprintf(out_fp
, "%s:", flagstring
);
810 sumstring
= sum_to_string(&cs
);
811 fprintf(out_fp
, "%s\n", sumstring
);
814 sumstring
= sum_to_string(&cs
);
815 if (strcmp(checksum
, sumstring
) == 0) {
830 if (out_fp
!= stdout
)