/*
 * Copyright (C) 2013-2017  Red Hat, Inc.
 *
 * This program is free software;  you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 */

/*
 * The case is designed to test new sysfs boolean knob
 * /sys/kernel/mm/ksm/merge_across_nodes, which was introduced by
 * commit 90bd6fd31c8097ee (ksm: allow trees per NUMA node).
 * when merge_across_nodes is set to zero only pages from the same
 * node are merged, otherwise pages from all nodes can be merged
 * together.
 */

#include "config.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <limits.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <limits.h>

#include "mem.h"
#include "numa_helper.h"

#ifdef HAVE_NUMA_V2
#include <numaif.h>

static int run = -1;
static int sleep_millisecs = -1;
static int merge_across_nodes = -1;
static unsigned long nr_pages;

static char *n_opt;
static struct tst_option ksm_options[] = {
	{"n:", &n_opt,  "-n x    Allocate x pages memory per node"},
	{NULL, NULL, NULL}
};

static const char * const save_restore[] = {
	"?/sys/kernel/mm/ksm/max_page_sharing",
	NULL,
};

static void test_ksm(void)
{
	if (n_opt)
		nr_pages = SAFE_STRTOUL(n_opt, 0, ULONG_MAX);
	else
		nr_pages = 100;

	test_ksm_merge_across_nodes(nr_pages);
}

static void setup(void)
{
	if (access(PATH_KSM "merge_across_nodes", F_OK) == -1)
		tst_brk(TCONF, "no merge_across_nodes sysfs knob");

	if (!is_numa(NULL, NH_MEMS, 2))
		tst_brk(TCONF, "The case needs a NUMA system.");

	/* save the current value */
	SAFE_FILE_SCANF(PATH_KSM "run", "%d", &run);
	SAFE_FILE_SCANF(PATH_KSM "merge_across_nodes",
			"%d", &merge_across_nodes);
	SAFE_FILE_SCANF(PATH_KSM "sleep_millisecs",
			"%d", &sleep_millisecs);
}

static void cleanup(void)
{
	if (merge_across_nodes != -1) {
		FILE_PRINTF(PATH_KSM "merge_across_nodes",
			    "%d", merge_across_nodes);
	}

	if (sleep_millisecs != -1)
		FILE_PRINTF(PATH_KSM "sleep_millisecs", "%d", sleep_millisecs);

	if (run != -1)
		FILE_PRINTF(PATH_KSM "run", "%d", run);
}

static struct tst_test test = {
	.needs_root = 1,
	.options = ksm_options,
	.setup = setup,
	.cleanup = cleanup,
	.save_restore = save_restore,
	.test_all = test_ksm,
};

#else
	TST_TEST_TCONF(NUMA_ERROR_MSG);
#endif