/*
 * Copyright (c) International Business Machines  Corp., 2001
 * Copyright (c) 2014 Fujitsu Ltd.
 * Author: Xiaoguang Wang <wangxg.fnst@cn.fujitsu.com>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it would be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

/*
 * Description:
 * Verify that,
 *  1. msync() fails with -1 return value and sets errno to EBUSY
 *     if MS_INVALIDATE was specified in flags, and a memory lock
 *     exists for the specified address range.
 *  2. msync() fails with -1 return value and sets errno to EINVAL
 *     if addr is not a multiple of PAGESIZE; or any bit other than
 *     MS_ASYNC | MS_INVALIDATE | MS_SYNC is set in flags; or both
 *     MS_SYNC and MS_ASYNC are set in flags.
 *  3. msync() fails with -1 return value and sets errno to ENOMEM
 *     if the indicated memory (or part of it) was not mapped.
 */

#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <pwd.h>
#include <sys/resource.h>

#include "test.h"
#include "safe_macros.h"

#define INV_SYNC	-1
#define TEMPFILE	"msync_file"
#define BUF_SIZE	256

static void setup(void);
static void cleanup(void);

static int fd;
static char *addr1;
static char *addr2;
static char *addr3;

#if !defined(UCLINUX)
static char *addr4;
#endif

static size_t page_sz;

static struct test_case_t {
	char **addr;
	int flags;
	int exp_errno;
} test_cases[] = {
	{ &addr1, MS_INVALIDATE, EBUSY },
	{ &addr1, MS_ASYNC | MS_SYNC, EINVAL },
	{ &addr1, INV_SYNC, EINVAL },
	{ &addr2, MS_SYNC, EINVAL },
	{ &addr3, MS_SYNC, EINVAL },
#if !defined(UCLINUX)
	{ &addr4, MS_SYNC, ENOMEM },
#endif
};

static void msync_verify(struct test_case_t *tc);

char *TCID = "msync03";
int TST_TOTAL = ARRAY_SIZE(test_cases);

int main(int ac, char **av)
{
	int i, lc;

	tst_parse_opts(ac, av, NULL, NULL);

	setup();

	for (lc = 0; TEST_LOOPING(lc); lc++) {
		tst_count = 0;

		for (i = 0; i < TST_TOTAL; i++)
			msync_verify(&test_cases[i]);
	}

	cleanup();
	tst_exit();
}

static void setup(void)
{
	size_t nwrite = 0;
	char write_buf[BUF_SIZE];
	struct rlimit rl;

	tst_sig(NOFORK, DEF_HANDLER, cleanup);

	tst_tmpdir();

	TEST_PAUSE;

	page_sz = (size_t)sysconf(_SC_PAGESIZE);

	fd = SAFE_OPEN(cleanup, TEMPFILE, O_RDWR | O_CREAT, 0666);

	memset(write_buf, 'a', BUF_SIZE);
	while (nwrite < page_sz) {
		SAFE_WRITE(cleanup, 1, fd, write_buf, BUF_SIZE);
		nwrite += BUF_SIZE;
	}

	addr1 = SAFE_MMAP(cleanup, 0, page_sz, PROT_READ | PROT_WRITE,
			  MAP_SHARED | MAP_LOCKED, fd, 0);

	/* addr2 is not a multiple of PAGESIZE */
	addr2 = addr1 + 1;

	/* addr3 is outside the address space of the process */
	if (getrlimit(RLIMIT_DATA, &rl) < 0)
		tst_brkm(TBROK | TERRNO, NULL, "getrlimit failed");
	addr3 = (char *)rl.rlim_max;

#if !defined(UCLINUX)
	/* memory pointed to by addr4 was not mapped */
	addr4 = get_high_address();
#endif
}

static void msync_verify(struct test_case_t *tc)
{
	TEST(msync(*(tc->addr), page_sz, tc->flags));
	if (TEST_RETURN != -1) {
		tst_resm(TFAIL, "msync succeeded unexpectedly");
		return;
	}

	if (TEST_ERRNO == tc->exp_errno) {
		tst_resm(TPASS | TTERRNO, "msync failed as expected");
	} else {
		tst_resm(TFAIL | TTERRNO,
			 "msync failed unexpectedly; expected: "
			 "%d - %s", tc->exp_errno,
			 strerror(tc->exp_errno));
	}
}

static void cleanup(void)
{
	if (addr1 && munmap(addr1, page_sz) < 0)
		tst_resm(TWARN | TERRNO, "munmap() failed");

	if (fd > 0 && close(fd) < 0)
		tst_resm(TWARN | TERRNO, "close() failed");

	tst_rmdir();
}
