#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/mman.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int HPS;
char hugetlbfsdir[256];
#ifndef UTILS
#include "utils.h"
#endif
#define errmsg(x) fprintf(stderr, x), exit(1)

/*
 * Function    : write_hugepage(char *addr, int nr_hugepage, char *avoid_addr)
 * Parameters  :
 *     addr            head address of hugepage range
 *     nr_hugepage     hugepage number from head address
 *     avoid_addr      the address which avoid to be operated
 */
void write_hugepage(char *addr, int nr_hugepage, char *avoid_addr)
{
	int i, j;
	for (i = 0; i < nr_hugepage; i++) {
		if ((addr + i * HPS) == avoid_addr)
			continue;
		for (j = 0; j < HPS; j++) {
			*(addr + i * HPS + j) = (char)('a' + ((i + j) % 26));
		}
	}
}

/*
 * Function    : read_hugepage(char *addr, int nr_hugepage, char *avoid_addr)
 * Parameters  :
 *     addr            head address of hugepage range
 *     nr_hugepage     hugepage number from head address
 *     avoid_addr      the address which avoid to be operated
 *
 * return      :
 *     0               OK
 *     -1              if buffer content differs from the expected ones
 */
int read_hugepage(char *addr, int nr_hugepage, char *avoid_addr)
{
	int i, j;
	int ret = 0;

	for (i = 0; i < nr_hugepage; i++) {
		if ((addr + i * HPS) == avoid_addr)
			continue;
		for (j = 0; j < HPS; j++) {
			if (*(addr + i * HPS + j) != (char)('a' + ((i + j) % 26))) {
				printf("Mismatch at %d\n", i + j);
				ret = -1;
				break;
			}
		}
	}
	return ret;
}

int hugetlbfs_root(char *dir)
{
	int found = 0;
	FILE *f = fopen("/proc/mounts", "r");
	if (!f) err("open /proc/mounts");
	char *line = NULL;
	size_t linelen = 0;
	char dummy[100];
	while (getline(&line, &linelen, f) > 0) {
		if (sscanf(line, "none %s hugetlbfs %[^ ]",
			   dir, dummy) >= 2) {
			found = 1;
			break;
		}
	}
	free(line);
	fclose(f);
	if (!found)
		printf("cannot find hugetlbfs directory in /proc/mounts\n");
	return found;
}

/* Assume there is only one types of hugepage size for now. */
int gethugepagesize(void)
{
	int hpagesize = 0;
	struct dirent *dent;
	DIR *dir;
	dir = opendir("/sys/kernel/mm/hugepages");
	if (!dir) err("open /sys/kernel/mm/hugepages");
	while ((dent = readdir(dir)) != NULL)
		if (sscanf(dent->d_name, "hugepages-%dkB", &hpagesize) >= 1)
			break;
	closedir(dir);
	return hpagesize * 1024;
}

void *alloc_shm_hugepage(int *key, int size)
{
	void *addr;
	int shmid;
	if ((shmid = shmget(*key, size,
			    SHM_HUGETLB | IPC_CREAT | SHM_R | SHM_W)) < 0) {
		perror("shmget");
		return NULL;
	}
	addr = shmat(shmid, (void *)0x0UL, 0);
	if (addr == (char *)-1) {
		perror("Shared memory attach failure");
		shmctl(shmid, IPC_RMID, NULL);
		return NULL;
	}
	*key = shmid;
	return addr;
}

void *alloc_anonymous_hugepage(int size, int private)
{
	void *addr;
	int mapflag = MAP_ANONYMOUS | 0x40000; /* MAP_HUGETLB */
	if (private)
		mapflag |= MAP_PRIVATE;
	else
		mapflag |= MAP_SHARED;
	if ((addr = mmap(0, size,
			 PROT_READ|PROT_WRITE, mapflag, -1, 0)) == MAP_FAILED) {
		perror("mmap");
		return NULL;
	}
	return addr;
}

void *alloc_filebacked_hugepage(char *filepath, int size, int private, int *fd)
{
	int mapflag = MAP_SHARED;
	void *addr;
	if (private)
		mapflag = MAP_PRIVATE;
	if ((*fd = open(filepath, O_CREAT | O_RDWR, 0777)) < 0) {
		perror("open");
		return NULL;
	}
	if ((addr = mmap(0, size,
			 PROT_READ|PROT_WRITE, mapflag, *fd, 0)) == MAP_FAILED) {
		perror("mmap");
		unlink(filepath);
		return NULL;
	}
	return addr;
}

int free_shm_hugepage(int key, void *addr)
{
	if (shmdt((const void *)addr) != 0) {
		perror("Detach failure");
		shmctl(key, IPC_RMID, NULL);
		return -1;
	}
	shmctl(key, IPC_RMID, NULL);
	return 0;
}

int free_anonymous_hugepage(void *addr, int size)
{
	int ret = 0;
	if (munmap(addr, size)) {
		perror("munmap");
		ret = -1;
	}
	return ret;
}

int free_filebacked_hugepage(void *addr, int size, int fd, char *filepath)
{
	int ret = 0;
	if (munmap(addr, size)) {
		perror("munmap");
		ret = -1;
	}
	if (close(fd)) {
		perror("close");
		ret = -1;
	}
	if (filepath) {
		if (unlink(filepath)) {
			perror("unlink");
			ret = -1;
		}
	} else {
		fprintf(stderr, "Filepath not specified.\n");
		ret = -1;
	}
	return ret;
}
