// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2020 IBM.
 * All Rights Reserved.
 *
 * simple mmap write multithreaded test for testing enospc
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <assert.h>
#include <sys/mman.h>
#include <pthread.h>
#include <signal.h>

#define pr_debug(fmt, ...) do { \
    if (verbose) { \
        printf (fmt, ##__VA_ARGS__); \
    } \
} while (0)

struct thread_s {
	int id;
};

static float file_ratio[2] = {0.5, 0.5};
static unsigned long fsize = 0;
static char *base_dir = ".";
static char *ratio = "1";

static bool use_fallocate = false;
static bool verbose = false;

pthread_barrier_t bar;

void handle_sigbus(int sig)
{
	pr_debug("Enospc test failed with SIGBUS\n");
	exit(7);
}

void enospc_test(int id)
{
	int i;
	int fd;
	char fpath[255] = {0};
	char *addr;
	unsigned long size = 0;

	/*
	 * Comma separated values against -r option indicates that the file
	 * should be divided into two small files.
	 * The file_ratio specifies the proportion in which the file sizes must
	 * be divided into.
	 *
	 * Half of the files will be divided into size of the first ratio and the
	 * rest of the following ratio
	 */

	if (id % 2 == 0)
		size = fsize * file_ratio[0];
	else
		size = fsize * file_ratio[1];

	pthread_barrier_wait(&bar);

	sprintf(fpath, "%s/mmap-file-%d", base_dir, id);
	pr_debug("Test write phase starting file %s fsize %lu, id %d\n", fpath, size, id);

	signal(SIGBUS, handle_sigbus);

	fd = open(fpath, O_RDWR | O_CREAT, 0644);
	if (fd < 0) {
		pr_debug("Open failed\n");
		exit(1);
	}

	if (use_fallocate)
		assert(fallocate(fd, 0, 0, size) == 0);
	else
		assert(ftruncate(fd, size) == 0);

	addr = mmap(NULL, size, PROT_WRITE, MAP_SHARED, fd, 0);
	assert(addr != MAP_FAILED);

	for (i = 0; i < size; i++) {
		addr[i] = 0xAB;
	}

	assert(munmap(addr, size) != -1);
	close(fd);
	pr_debug("Test write phase completed...file %s, fsize %lu, id %d\n", fpath, size, id);
}

void *spawn_test_thread(void *arg)
{
	struct thread_s *thread_info = (struct thread_s *)arg;

	enospc_test(thread_info->id);
	return NULL;
}

void _run_test(int threads)
{
	int i;
	pthread_t tid[threads];

	pthread_barrier_init(&bar, NULL, threads+1);
	for (i = 0; i < threads; i++) {
		struct thread_s *thread_info = (struct thread_s *) malloc(sizeof(struct thread_s));
		thread_info->id = i;
		assert(pthread_create(&tid[i], NULL, spawn_test_thread, thread_info) == 0);
	}

	pthread_barrier_wait(&bar);

	for (i = 0; i < threads; i++) {
		assert(pthread_join(tid[i], NULL) == 0);
	}

	pthread_barrier_destroy(&bar);
	return;
}

static void usage(void)
{
	printf("\nUsage: t_enospc [options]\n\n"
	       " -t                    thread count\n"
	       " -s size               file size\n"
	       " -p path               set base path\n"
	       " -f fallocate          use fallocate instead of ftruncate\n"
	       " -v verbose            print debug information\n"
	       " -r ratio              ratio of file sizes, ',' separated values (def: 0.5,0.5)\n");
}

int main(int argc, char *argv[])
{
	int opt;
	int threads = 0;
	char *ratio_ptr;

	while ((opt = getopt(argc, argv, ":r:p:t:s:vf")) != -1) {
		switch(opt) {
		case 't':
			threads = atoi(optarg);
			pr_debug("Testing with (%d) threads\n", threads);
			break;
		case 's':
			fsize = atoi(optarg);
			pr_debug("size: %lu Bytes\n", fsize);
			break;
		case 'p':
			base_dir = optarg;
			break;
		case 'r':
			ratio = optarg;
			ratio_ptr = strtok(ratio, ",");
			if (ratio_ptr[0] == '1') {
				file_ratio[0] = 1;
				file_ratio[1] = 1;
				break;
			}
			if (ratio_ptr != NULL)
				file_ratio[0] = strtod(ratio_ptr, NULL);
			ratio_ptr = strtok(NULL, ",");
			if (ratio_ptr != NULL)
				file_ratio[1] = strtod(ratio_ptr, NULL);
			break;
		case 'f':
			use_fallocate = true;
			break;
		case 'v':
			verbose = true;
			break;
		case '?':
			usage();
			exit(1);
		}
	}

	/* Sanity test of parameters */
	assert(threads);
	assert(fsize);
	assert(file_ratio[0]);
	assert(file_ratio[1]);

	pr_debug("Testing with (%d) threads\n", threads);
	pr_debug("size: %lu Bytes\n", fsize);

	_run_test(threads);
	return 0;
}
