/* See LICENSE file for copyright and license details. */ #include "common.h" union str_or_off { const char *s; size_t off; }; struct listing { union str_or_off algorithm; union str_or_off filename; /* first char determines mode */ union str_or_off checksum; }; static int listingcmp(const void *av, const void *bv) { const struct listing *a = av, *b = bv; int r = strcmp(a->filename.s, b->filename.s); if (r) return r; return strcmp(a->algorithm.s, b->algorithm.s); } static int parse_listing(struct listing **listingp, size_t *listing_lenp, size_t *listing_sizep, struct buffer *buffer, const char *fname, int warn_improper_format, enum format format) { size_t lineno = 1U; char end = (format & WITH_NUL) ? '\0' : '\n'; for (; buffer->procoff < buffer->offset; lineno++) { for (; buffer->procoff < buffer->offset; buffer->procoff++) if (buffer->buf[buffer->procoff] == end) break; if (buffer->procoff == buffer->offset) { weprintf("last line in \"%s\" is not terminated", fname); if (!warn_improper_format) return -1; if (buffer->offset == buffer->size) buffer->buf = erealloc(buffer->buf, buffer->size += 1U); } buffer->buf[buffer->procoff++] = '\0'; if (*listing_lenp == *listing_sizep) *listingp = ereallocarray(*listingp, *listing_sizep += 512U, sizeof(**listingp)); (*listingp)[*listing_lenp].algorithm.off = buffer->ready; if (format & WITH_ALGOSTR) { for (; buffer->buf[buffer->ready] != ':'; buffer->ready++) if (isspace(buffer->buf[buffer->ready]) || !buffer->buf[buffer->ready]) goto badline; buffer->buf[buffer->ready++] = '\0'; } (*listingp)[*listing_lenp].checksum.off = buffer->ready; for (; isxdigit(buffer->buf[buffer->ready]); buffer->ready++) buffer->buf[buffer->ready] = (char)tolower(buffer->buf[buffer->ready]); if (buffer->buf[buffer->ready] != ' ') goto badline; buffer->buf[buffer->ready++] = '\0'; if (buffer->buf[buffer->ready] != ' ' && buffer->buf[buffer->ready] != '*') goto badline; if (buffer->buf[buffer->ready] == '*') buffer->buf[buffer->ready] = ' '; /* binary is insignificant */ (*listingp)[*listing_lenp].filename.off = buffer->ready; if (!buffer->buf[(*listingp)[*listing_lenp].algorithm.off] || !buffer->buf[(*listingp)[*listing_lenp].checksum.off] || !buffer->buf[(*listingp)[*listing_lenp].filename.off + 1U]) goto badline; *listing_lenp += 1U; nextline: buffer->ready = buffer->procoff; } return 0; badline: weprintf("line %zu in \"%s\" is improperly formatted", lineno, fname); if (warn_improper_format) goto nextline; return -1; } static int get_listing(char **files, struct listing **listingp, size_t *listing_lenp, size_t *listing_sizep, struct buffer *buffer, struct algorithm *algorithms, size_t nalgorithms, enum format format, int warn_improper_format) { const char *fname; int r, fd, is_new_fd; size_t i; format &= (enum format)~FORMAT_MASK; format |= LOWERCASE_HEX; buffer->procoff = 0; for (; *files; files++) { fd = openfile(*files, &is_new_fd, &fname); if (fd < 0) return -1; while (!(r = feedbuffer(fd, buffer, fname))); if (r < 0) goto fail; if (is_new_fd) { close(fd); is_new_fd = 0; } if (parse_listing(listingp, listing_lenp, listing_sizep, buffer, fname, warn_improper_format, format)) goto fail; } for (i = 0; i < *listing_lenp; i++) { (*listingp)[i].algorithm.s = &buffer->buf[(*listingp)[i].algorithm.off]; (*listingp)[i].filename.s = &buffer->buf[(*listingp)[i].filename.off]; (*listingp)[i].checksum.s = &buffer->buf[(*listingp)[i].checksum.off]; } if (!(format & WITH_ALGOSTR)) { if (nalgorithms != 1U) abort(); for (i = 0; i < *listing_lenp; i++) (*listingp)[i].algorithm.s = algorithms[0].algostr; } return 0; fail: if (is_new_fd) close(fd); return -1; } static int compare(const char *hash, size_t hash_len, const struct listing *listing, size_t nlisting, int *truncated_out) { int truncated, ok = 0; size_t len; *truncated_out = 0; for (; nlisting--; listing++) { len = strlen(listing->checksum.s); truncated = len < hash_len; len = MIN(hash_len, len); if (!strncmp(hash, listing->checksum.s, len)) { ok = 1; if (!truncated) return 1; } } *truncated_out = 1; return ok; } static int check_file(struct listing *listing, size_t nlisting, size_t nthreads, struct global_data *global, struct barrier_group *group, size_t *group_size) { size_t i, j, n, orig_count, nthreads_cur, nthreads_now; int cmp, truncated; memset(global->algorithms, 0, nlisting * sizeof(*global->algorithms)); global->algorithms[0].algostr = listing[0].algorithm.s; global->algorithms[0].result = NULL; global->nalgorithms = 1U; for (i = 1U; i < nlisting; i++) { if (!strcmp(listing[i].algorithm.s, listing[i - 1U].algorithm.s)) { listing[i].algorithm.s = listing[i - 1U].algorithm.s; } else { global->algorithms[global->nalgorithms].algostr = listing[i].algorithm.s; global->algorithms[global->nalgorithms].result = NULL; global->nalgorithms++; } } nthreads_now = nthreads ? nthreads : getautonthreads(); nthreads_cur = MAX(MIN(global->nalgorithms, nthreads_now), 1U); if (nthreads_cur > *group_size) { if (*group_size) killbarriergroup(group, global); createbarriergroup(group, nthreads_cur, global); } orig_count = global->nalgorithms; if (calculate(global->file, group, global)) { printf("%s: %s\n", global->file, errno == ENOENT ? "Missing" : "Error"); return errno == ENOENT ? 1 : 2; } for (i = 0, j = 0; i < global->nalgorithms; i++, j += n) { n = 1U; for (; n < j - nlisting; n++) if (listing[j].algorithm.s != listing[j + n].algorithm.s) break; cmp = strcmp(global->algorithms[0].algostr, listing[j].algorithm.s); if (cmp < 0) abort(); else if (cmp > 0) continue; if (compare(global->algorithms[0].result, global->algorithms[0].result_length, &listing[j], n, &truncated)) { printf("%s: OK%s\n", global->file, truncated ? " (calculated hash was shorted the listed checksum)" : ""); return 0; } } if (global->nalgorithms == orig_count) { printf("%s: Fail\n", global->file); return 1; } else if (global->nalgorithms) { printf("%s: Fail (some hash functions were not supported)\n", global->file); return 2; } else { printf("%s: Fail (no hash function was supported)\n", global->file); return 2; } } int verify_checksums(char **files, struct algorithm *algorithms, size_t nalgorithms, size_t nthreads, enum format format, int warn_improper_format, int hexinput) { struct listing *listing = NULL; size_t listing_len = 0; size_t listing_size = 0; struct buffer listbuffer = {0}; struct buffer filebuffer = {0}; size_t i, first, n; int r, ret = 0; struct global_data global; size_t algorithms_size = 0; struct barrier_group group; size_t group_size = 0; if (get_listing(files, &listing, &listing_len, &listing_size, &listbuffer, algorithms, nalgorithms, format, warn_improper_format)) goto fail; qsort(listing, listing_len, sizeof(*listing), &listingcmp); global.algorithms = NULL; global.buffer = &filebuffer; for (i = 0; i < listing_len;) { first = i++; while (i < listing_len && !strcmp(listing[i].filename.s, listing[first].filename.s)) i++; n = i - first; if (n > algorithms_size) { algorithms_size = n; global.algorithms = ereallocarray(global.algorithms, n, sizeof(*global.algorithms)); } global.file = &listing[i].filename.s[1]; global.hexinput = (hexinput || listing[i].filename.s[0] == '#'); r = check_file(&listing[i], n, nthreads, &global, &group, &group_size); if (r < 0) goto fail; ret = MAX(r, ret); } out: if (group_size) killbarriergroup(&group, &global); free(listing); free(listbuffer.buf); free(filebuffer.buf); return ret; fail: ret = 2; goto out; }