/*
 * libhugetlbfs - Easy use of Linux hugepages
 * Copyright (C) 20015 Mike Kravetz, Oracle Corporation
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 */
#define _GNU_SOURCE

#include <stdio.h>
#include <linux/falloc.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <pthread.h>
#include <linux/falloc.h>

#include <hugetlbfs.h>

#include "hugetests.h"

#define P "fallocate-stress"
#define DESC \
	"* Stress test fallocate.  This test starts three threads.  Thread *\n"\
	"* one will continually punch/fill holes via falloc.  Thread two   *\n"\
	"* will continually fault in those same pages.  Thread three will  *\n"\
	"* continually mmap/munmap that page range.                        *"

#define min(a,b) (((a) < (b)) ? (a) : (b))

#define MAX_PAGES_TO_USE 100

static int htlb_fd;
static long max_hpages;
static long hpage_size;

#define FALLOCATE_ITERATIONS 100000
static void *thread_fallocate(void *arg)
{
	int i, err;
	long tpage;

	for (i=0; i < FALLOCATE_ITERATIONS; i++) {
		tpage = ((long long)random()) % (max_hpages);
		err = fallocate(htlb_fd,
				FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
				tpage * hpage_size, hpage_size);
		if (err)
			FAIL("fallocate(): %s", strerror(errno));
		err = fallocate(htlb_fd, 0, tpage * hpage_size, hpage_size);
		if (err)
			FAIL("fallocate(FALLOC_FL_PUNCH_HOLE): %s",
				strerror(errno));
	}

	return NULL;
}

static void *fault_mmap_addr = NULL;

static void thread_fault_cleanup(void *arg)
{
	if (fault_mmap_addr)
		munmap(fault_mmap_addr, max_hpages * hpage_size);
}

static void *thread_fault(void *arg)
{
	long tpage;
	char foo;
	struct timespec ts;

	fault_mmap_addr = mmap(NULL, max_hpages * hpage_size,
				PROT_READ | PROT_WRITE, MAP_SHARED,
				htlb_fd, 0);
	if (fault_mmap_addr == MAP_FAILED)
		FAIL("mmap(): %s", strerror(errno));

	pthread_cleanup_push(thread_fault_cleanup, NULL);

	ts.tv_sec = 0;
	ts.tv_nsec = 0;

	while (1) {
		tpage = ((long long)random()) % (max_hpages);

		foo = *((char *)(fault_mmap_addr + (tpage * hpage_size)));
		*((char *)(fault_mmap_addr + (tpage * hpage_size))) = foo;

		nanosleep(&ts, NULL);	/* thread cancellation point */
	}

	pthread_cleanup_pop(1);

	return NULL;
}

static void *mmap_munmap_addr = NULL;

static void thread_mmap_munmap_cleanup(void *arg)
{
	if (mmap_munmap_addr)
		munmap(mmap_munmap_addr, max_hpages * hpage_size);
}

static void *thread_mmap_munmap(void *arg)
{
	int err;
	struct timespec ts;

	pthread_cleanup_push(thread_mmap_munmap_cleanup, NULL);

	ts.tv_sec = 0;
	ts.tv_nsec = 0;

	while (1) {
		mmap_munmap_addr = mmap(NULL, max_hpages * hpage_size,
					PROT_READ | PROT_WRITE,
					MAP_SHARED, htlb_fd, 0);
		if (mmap_munmap_addr == MAP_FAILED)
			FAIL("mmap(): %s", strerror(errno));

		err = munmap(mmap_munmap_addr, max_hpages * hpage_size);
		if (err)
			FAIL("munmap(): %s", strerror(errno));
		mmap_munmap_addr = NULL;

		nanosleep(&ts, NULL);	/* thread cancellation point */
	}

	pthread_cleanup_pop(1);

	return NULL;
}

int main(int argc, char *argv[])
{
	long nr_hpages_free;
	int err;
	unsigned long free_before, free_after;
	unsigned long rsvd_before, rsvd_after;
	pthread_t falloc_th, fault_th, mmap_munmap_th;
	void *falloc_th_ret, *fault_th_ret, *mmap_munmap_th_ret;

	test_init(argc, argv);

	srandom((int)getpid() * time(NULL));
	hpage_size = check_hugepagesize();
	nr_hpages_free = get_huge_page_counter(hpage_size, HUGEPAGES_FREE);
	max_hpages = min(nr_hpages_free, MAX_PAGES_TO_USE);

	htlb_fd = hugetlbfs_unlinked_fd();
	if (htlb_fd < 0)
		FAIL("hugetlbfs_unlinked_fd()");

	free_before = get_huge_page_counter(hpage_size, HUGEPAGES_FREE);
	rsvd_before = get_huge_page_counter(hpage_size, HUGEPAGES_RSVD);

	/* First preallocate file with max_hpages pages */
	err = fallocate(htlb_fd, 0, 0, hpage_size * max_hpages);
	if (err) {
		if (errno == EOPNOTSUPP)
			IRRELEVANT();
		if (err)
			FAIL("fallocate(): %s", strerror(errno));
	}

	free_after = get_huge_page_counter(hpage_size, HUGEPAGES_FREE);
	if (free_before - free_after != max_hpages)
		FAIL("fallocate did not preallocate %ld huge pages\n",
							max_hpages);

	err = pthread_create(&falloc_th, NULL, thread_fallocate, NULL);
	if (err != 0)
		FAIL("pthread_create(): %s\n", strerror(errno));
	err = pthread_create(&fault_th, NULL, thread_fault, NULL);
	if (err != 0)
		FAIL("pthread_create(): %s\n", strerror(errno));
	err = pthread_create(&mmap_munmap_th, NULL, thread_mmap_munmap, NULL);
	if (err != 0)
		FAIL("pthread_create(): %s\n", strerror(errno));

	err = pthread_join(falloc_th, &falloc_th_ret);
	if (err != 0)
		FAIL("pthread_join(): %s\n", strerror(errno));
	if (falloc_th_ret)
		FAIL("thread_fallocate unexpected exit code\n");

	err = pthread_cancel(fault_th);
	if (err != 0)
		FAIL("pthread_cancel(): %s\n", strerror(errno));
	err = pthread_join(fault_th, &fault_th_ret);
	if (err != 0)
		FAIL("pthread_join(): %s\n", strerror(errno));

	err = pthread_cancel(mmap_munmap_th);
	if (err != 0)
		FAIL("pthread_cancel(): %s\n", strerror(errno));
	err = pthread_join(mmap_munmap_th, &mmap_munmap_th_ret);
	if (err != 0)
		FAIL("pthread_join(): %s\n", strerror(errno));

	if (close(htlb_fd))
		FAIL("close(): %s", strerror(errno));

	free_after = get_huge_page_counter(hpage_size, HUGEPAGES_FREE);
	rsvd_after = get_huge_page_counter(hpage_size, HUGEPAGES_RSVD);
	if (free_after != free_before || rsvd_after != rsvd_before)
		FAIL("free or reserve counts not correct after fallocate stress testing\n");

	PASS();
}
