/* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only */
/* Copyright (c) 2023 Brett Sheffield <bacs@librecast.net> */

#include "testdata.h"

#include <unistd.h>

static size_t getbufsize(void)
{
	int buflen = sysconf(_SC_PAGESIZE);
	return (size_t)(16 * buflen);
}

int test_random_bytes(void *ptr, size_t sz)
{
	FILE *fr;
	size_t byt = 0, bi;
	fr = fopen("/dev/urandom", "r");
	if (!fr) return -1;
	while (byt < sz) {
		bi = fread(ptr, 1, sz - byt, fr);
		if (bi > 0) {
			byt += bi;
			ptr = (char *)ptr + bi;
		}
	}
	fclose(fr);
	return 0;
}

int test_sparsify_fd(int fd, size_t sz)
{
	FILE *f;
	int fdup;
	fdup = dup(fd);
	f = fdopen(fdup, "r+");
	if (!f) return -1;
	fseek(f, sz - 1, SEEK_SET);
	fputc(0, f);
	fclose(f);
	return 0;
}

static int test_randomize_file(int fd, size_t sz)
{
	FILE *f, *fr;
	char buf[BUFSIZ];
	size_t byt = 0, bi;
	int fdup;
	fdup = dup(fd);
	f = fdopen(fdup, "w+");
	if (!f) return -1;
	fr = fopen("/dev/urandom", "r");
	if (!fr) goto exit_0;
	while (byt < sz) {
		bi = fread(buf, 1, MIN(sizeof buf, sz - byt), fr);
		if (bi > 0) {
			byt += fwrite(buf, 1, bi, f);
		}
	}
	fclose(fr);
exit_0:
	fclose(f);
	return 0;
}

int test_data_file(char *filename, size_t sz, int flags)
{
	int fd;
	if (flags & TEST_TMP) {
		if ((fd = mkstemp(filename)) == -1) {
			perror("mkstemp");
			return -1;
		}
	}
	else if ((fd = open(filename, flags)) == -1) {
		perror("open");
		return -1;
	}
	if (flags & TEST_RND) {
		if (test_randomize_file(fd, sz) == -1) {
			close(fd);
			return -1;
		}
	}
	else if (sz > 0) {
		/* size requested, but no data => sparse */
		if (test_sparsify_fd(fd, sz) == -1) {
			close(fd);
			return -1;
		}
	}
	return fd;
}

int test_random_meta(const char *path, int flags)
{
	int rc = 0;
	if (flags & TEST_OWN) {
		uid_t uid; gid_t gid;
		uid = arc4random_uniform(1024);
		gid = arc4random_uniform(1024);
		rc = chown(path, uid, gid);
		if (rc) return rc;
	}
	if (flags & TEST_MOD) {
		mode_t mode;
		mode = arc4random_uniform(0777);
		rc = chmod(path, mode);
	}
	return rc;
}

int test_createtesttree(const char *path, off_t max_filesz, int max_files, int max_dirs, int depth,
		int flags)
{
	char template[] = "test-XXXXXX";
	char filename[] = "test-XXXXXX";
	char cwd[PATH_MAX];
	char owd[PATH_MAX];
	size_t len;
	int fd, max, rc;
	if (!getcwd(owd, sizeof owd)) return -1;
	rc = chdir(path);
	if (!getcwd(cwd, sizeof cwd)) return -1;
	if (rc == -1) { perror(path); return -1; }
	len = strlen(template);
	max = arc4random_uniform(max_files);
	for (int i = 0; i < max; i++) {
		memcpy(filename, template, len);
		fd = mkstemp(filename);
		if (fd == -1) { perror("mkstemp"); break; }
		test_randomize_file(fd, arc4random_uniform(max_filesz));
		test_random_meta(filename, flags);
		close(fd);
	}
	max = arc4random_uniform(max_dirs);
	if (!max) max = 1;
	for (int i = 0; i < max; i++) {
		memcpy(filename, template, len);
		if (mkdtemp(filename)) {
			test_random_meta(filename, flags);
			if (depth > 0)
				test_createtesttree(filename, max_filesz, max_files, max_dirs, depth - 1, flags);
		}
	}
	return chdir(owd);
}

int test_createtestdir(char *testprog, char **dir, const char *srcdst)
{
	char template[] = ".tmp.XXXXXX";
	size_t buflen, sz;
	int rc;
	/* find length of required buffer and allocate */
	sz = strlen(testprog) - 5; /* strip .test */
	buflen = (size_t)snprintf(NULL, 0, "%.*s.%s%s", (int)sz, testprog, srcdst, template) + 1;
	*dir = malloc(buflen);
	if (!*dir) return -1;
	/* build template string for directory name */
	rc = snprintf(*dir, buflen, "%.*s.%s%s", (int)sz, testprog, srcdst, template);
	if (rc != (int)(buflen - 1)) goto err_free_dir;
	/* create temp directory */
	if (!mkdtemp(*dir)) goto err_free_dir;
	fprintf(stderr, "directory '%s' created\n", *dir);
	return 0;
err_free_dir:
	free(*dir);
	return -1;
}

int test_createtestdirs(char *testprog, char **src, char **dst)
{
	int rc;
	rc = test_createtestdir(testprog, src, "src");
	if (rc == -1) return -1;
	rc = test_createtestdir(testprog, dst, "dst");
	if (rc == 0) return 0;
	rc = errno;
	free(src);
	errno = rc;
	return -1;
}

int test_data_size(char *filename, size_t sz)
{
	struct stat sb;
	if (stat(filename, &sb) == -1) return -1;
	if ((sb.st_mode & S_IFMT) != S_IFREG) return -1;
	if ((size_t)sb.st_size != sz) return -1;
	return 0;
}

int test_hash_file(const char *filename, unsigned char *hash, size_t hashlen, char *buf, size_t buflen)
{
	hash_state state;
	FILE *f;
	size_t byt;
	int free_buf = (buf == NULL); /* was buffer provided ? */
	if (!buf) {
		buflen = getbufsize();
		buf = malloc(buflen);
		if (!buf) return -1;
	}
	f = fopen(filename, "r");
	if (!f) goto exit_0;
	hash_init(&state, NULL, 0, hashlen);
	while ((byt = fread(buf, 1, buflen, f))) {
		hash_update(&state, (unsigned char *)buf, byt);
	}
	hash_final(&state, hash, hashlen);
	fclose(f);
exit_0:
	if (free_buf) free(buf);
	return 0;
}

int test_file_match(const char *file1, const char *file2)
{
	unsigned char hash[2][HASHSIZE];
	char buf[BUFSIZ];
	memset(hash, 0, sizeof hash);
	test_hash_file(file1, hash[0], HASHSIZE, buf, sizeof buf);
	test_hash_file(file2, hash[1], HASHSIZE, buf, sizeof buf);
	fprintf(stderr, "%s: ", file1);
	hash_hex_debug(stderr, hash[0], HASHSIZE);
	fprintf(stderr, "%s: ", file2);
	hash_hex_debug(stderr, hash[1], HASHSIZE);
	return memcmp(hash[0], hash[1], HASHSIZE);
}

int test_file_scratch(const char *filename, long offset, size_t len)
{
	char *buf;
	FILE *f, *fr;
	size_t byt = 0, bi;
	int buflen = MIN(getpagesize(), (int)len);
	if (!len) return 0;
	f = fopen(filename, "r+");
	if (!f) return -1;
	fr = fopen("/dev/urandom", "r");
	if (!fr) goto exit_0;
	if (offset < 0) {
		if (fseek(f, offset, SEEK_END) == -1) goto exit_1;
	}
	else if (offset > 0) {
		if (fseek(f, offset, SEEK_SET) == -1) goto exit_1;
	}
	buf = malloc(buflen);
	if (!buf) goto exit_1;
	while (byt < len) {
		bi = fread(buf, 1, MIN((size_t)buflen, len - byt), fr);
		if (bi > 0) {
			byt += fwrite(buf, 1, bi, f);
		}
	}
	free(buf);
exit_1:
	fclose(fr);
exit_0:
	fclose(f);
	return 0;
}
