/*
 * libhugetlbfs - Easy use of Linux hugepages
 * Copyright (C) 2005-2006 David Gibson & Adam Litke, IBM 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 _LARGEFILE64_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>
#include <assert.h>

#include <hugetlbfs.h>

#include "hugetests.h"

#define MAPS_BUF_SZ 4096

static unsigned long find_last_mapped(void)
{
	FILE *f;
	char line[MAPS_BUF_SZ];
	char *tmp;
	unsigned long start, end, off, ino;
	int ret;

	f = fopen("/proc/self/maps", "r");
	if (!f) {
		ERROR("Failed to open /proc/self/maps: %s\n", strerror(errno));
		return -1;
	}

	do {
		tmp = fgets(line, MAPS_BUF_SZ, f);
	} while (tmp);
	fclose(f);

	verbose_printf("Last map: %s", line);
	ret = sscanf(line, "%lx-%lx %*s %lx %*s %ld %*s", &start, &end, &off, &ino);
	if (ret == EOF)
		FAIL("Couldn't parse /proc/self/maps line: %s: %s\n", line,
							strerror(errno));
	if (ret != 4)
		FAIL("Couldn't parse /proc/self/maps line: %s\n", line);

	verbose_printf("Last map at 0x%lx-0x%lx\n", start, end);
	return end;
}

#define ALIGN_DOWN(x,a) ((x) & ~((a) - 1))

static unsigned long find_task_size(void)
{
	unsigned long low, high; /* PFNs */
	void *p;

	low = find_last_mapped();
	if (!low || ((low % getpagesize()) != 0))
		FAIL("Bogus stack end address, 0x%lx!?", low);
	low = low / getpagesize();

	/* This sum should get us (2^(wordsize) - 2 pages) */
	high = (unsigned long)(-2 * getpagesize()) / getpagesize();

	verbose_printf("Binary searching for task size PFNs 0x%lx..0x%lx\n",
		       low, high);

	while (high > low + 1) {
		unsigned long pfn = (low + high) / 2;
		unsigned long addr = pfn * getpagesize();

		assert((pfn >= low) && (pfn <= high));

		p = mmap64((void *)addr, getpagesize(), PROT_READ,
			   MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0);
		if (p == MAP_FAILED) {
			verbose_printf("Map failed at 0x%lx (%s)\n",
				       addr, strerror(errno));
			high = pfn;
		} else {
			verbose_printf("Map succeeded at 0x%lx\n", addr);
			munmap(p, getpagesize());
			low = pfn;
		}
	}

	return low * getpagesize();
}

int main(int argc, char *argv[])
{
	long hpage_size;
	int fd;
	void *p;
	unsigned long task_size;
	unsigned long straddle_addr;

	test_init(argc, argv);

	task_size = find_task_size();

	verbose_printf("TASK_SIZE = 0x%lx\n", task_size);

	hpage_size = check_hugepagesize();

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

	straddle_addr = task_size - hpage_size;
	straddle_addr = ALIGN(straddle_addr, hpage_size);

	/* We first try to get the mapping without MAP_FIXED */
	verbose_printf("Mapping without MAP_FIXED at %lx...", straddle_addr);
	errno = 0;
	p = mmap((void *)straddle_addr, 2*hpage_size, PROT_READ|PROT_WRITE,
		 MAP_SHARED, fd, 0);
	verbose_printf("%s\n", strerror(errno));
	if (p == (void *)straddle_addr)
		FAIL("Apparently suceeded in mapping across TASK_SIZE boundary");

	verbose_printf("Mapping with MAP_FIXED at %lx...", straddle_addr);
	errno = 0;
	p = mmap((void *)straddle_addr, 2*hpage_size, PROT_READ|PROT_WRITE,
		 MAP_SHARED|MAP_FIXED, fd, 0);
	verbose_printf("%s\n", strerror(errno));
	if (p != MAP_FAILED)
		FAIL("Apparently suceeded in mapping across TASK_SIZE boundary");

	PASS();
}
