aboutsummaryrefslogblamecommitdiffstats
path: root/check.c
blob: d014d653160812e998460751d0e0d36e21ea7fde (plain) (tree)

























































































































































































































































































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