/*
 * 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 <errno.h>
#include <signal.h>
#include <sys/mman.h>

#include <hugetlbfs.h>

#include "hugetests.h"

/*
 * Test rationale:
 *
 * At one stage, a misconversion of hugetlb_vmtruncate_list to a
 * prio_tree meant that on 32-bit machines, certain combinations of
 * mapping and truncations could truncate incorrect pages, or
 * overwrite pmds from other VMAs, triggering BUG_ON()s or other
 * wierdness.
 *
 * Test adapted to the libhugetlbfs framework from an example by
 * Kenneth Chen <kenneth.w.chen@intel.com>
 *
 * WARNING: The offsets and addresses used within are specifically
 * calculated to trigger the bug as it existed.  Don't mess with them
 * unless you *really* know what you're doing.
 *
 * The kernel bug in question was fixed with commit
 * 856fc29505556cf263f3dcda2533cf3766c14ab6.
 */
#define MAP_LENGTH	(4 * hpage_size)
#if defined(__s390__) && __WORDSIZE == 32
#define TRUNCATE_POINT 0x20000000UL
#else
#define TRUNCATE_POINT 0x60000000UL
#endif
#define HIGH_ADDR	0xa0000000UL
#define FOURGIG		((off64_t)0x100000000ULL)

int main(int argc, char *argv[])
{
	long hpage_size;
	int fd;
	char *p, *q;
	unsigned long i;
	int err;

	test_init(argc, argv);

	hpage_size = check_hugepagesize();

	if (hpage_size > TRUNCATE_POINT)
		CONFIG("Huge page size is too large");

	if (TRUNCATE_POINT % hpage_size)
		CONFIG("Truncation point is not aligned to huge page size");

	check_free_huge_pages(4);

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

	/* First mapping */
	p = mmap(0, MAP_LENGTH + TRUNCATE_POINT, PROT_READ | PROT_WRITE,
		 MAP_PRIVATE | MAP_NORESERVE, fd, 0);
	if (p == MAP_FAILED)
		FAIL("mmap() 1: %s", strerror(errno));

	munmap(p, 4*hpage_size + TRUNCATE_POINT);

	q = mmap((void *)HIGH_ADDR, MAP_LENGTH, PROT_READ | PROT_WRITE,
		 MAP_PRIVATE, fd, 0);
	if (q == MAP_FAILED)
		FAIL("mmap() 2: %s", strerror(errno));

	verbose_printf("High map at %p\n", q);

	for (i = 0; i < MAP_LENGTH; i += hpage_size)
		q[i] = 1;

	err = ftruncate(fd, TRUNCATE_POINT);
	if (err != 0)
		FAIL("ftruncate(): %s", strerror(errno));

	if (q[0] != 1)
		FAIL("data mismatch");

	PASS();
}
