/* 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;
}