// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (C) 2011  Red Hat, Inc.
 * Copyright (C) 2021 Xie Ziyao <xieziyao@huawei.com>
 */

/*\
 * [Description]
 *
 * Test ru_maxrss behaviors in struct rusage.
 *
 * This test program is backported from upstream commit: 1f10206cf8e9, which
 * fills ru_maxrss value in struct rusage according to rss hiwater mark. To
 * make sure this feature works correctly, a series of tests are executed in
 * this program.
 */

#include <stdlib.h>
#include <stdio.h>

#include "tst_test.h"
#include "getrusage03.h"

#define TESTBIN "getrusage03_child"

static struct rusage ru;
static long maxrss_init;

static const char *const resource[] = {
	TESTBIN,
	NULL,
};

static void inherit_fork1(void)
{
	SAFE_GETRUSAGE(RUSAGE_SELF, &ru);
	maxrss_init = ru.ru_maxrss;

	if (!SAFE_FORK()) {
		SAFE_GETRUSAGE(RUSAGE_SELF, &ru);

		if (is_in_delta(maxrss_init - ru.ru_maxrss))
			tst_res(TPASS, "initial.self ~= child.self");
		else
			tst_res(TFAIL, "child.self = %li, expected %li",
				ru.ru_maxrss, maxrss_init);
		exit(0);
	}
	tst_reap_children();
}

static void inherit_fork2(void)
{
	SAFE_GETRUSAGE(RUSAGE_CHILDREN, &ru);

	if (is_in_delta(ru.ru_maxrss - 102400))
		tst_res(TPASS, "initial.children ~= 100MB");
	else
		tst_res(TFAIL, "initial.children = %li, expected %i",
			ru.ru_maxrss, 102400);

	if (!SAFE_FORK()) {
		SAFE_GETRUSAGE(RUSAGE_CHILDREN, &ru);

		if (!ru.ru_maxrss)
			tst_res(TPASS, "child.children == 0");
		else
			tst_res(TFAIL, "child.children = %li, expected %i",
				ru.ru_maxrss, 0);
		exit(0);
	}
	tst_reap_children();
}

static void grandchild_maxrss(void)
{
	if (!SAFE_FORK())
		SAFE_EXECLP("getrusage03_child", "getrusage03_child",
			    "grand_consume", "300", NULL);
	tst_reap_children();
	SAFE_GETRUSAGE(RUSAGE_CHILDREN, &ru);

	if (is_in_delta(ru.ru_maxrss - 307200))
		tst_res(TPASS, "child.children ~= 300MB");
	else
		tst_res(TFAIL, "child.children = %li, expected %i",
			ru.ru_maxrss, 307200);
}

static void zombie(void)
{
	SAFE_GETRUSAGE(RUSAGE_CHILDREN, &ru);
	maxrss_init = ru.ru_maxrss;

	pid_t pid = SAFE_FORK();

	if (!pid)
		SAFE_EXECLP("getrusage03_child", "getrusage03_child",
			    "consume", "400", NULL);

	TST_PROCESS_STATE_WAIT(pid, 'Z', 0);
	SAFE_GETRUSAGE(RUSAGE_CHILDREN, &ru);
	if (is_in_delta(ru.ru_maxrss - maxrss_init))
		tst_res(TPASS, "initial.children ~= pre_wait.children");
	else
		tst_res(TFAIL, "pre_wait.children = %li, expected %li",
			ru.ru_maxrss, maxrss_init);

	tst_reap_children();
	SAFE_GETRUSAGE(RUSAGE_CHILDREN, &ru);
	if (is_in_delta(ru.ru_maxrss - 409600))
		tst_res(TPASS, "post_wait.children ~= 400MB");
	else
		tst_res(TFAIL, "post_wait.children = %li, expected %i",
			ru.ru_maxrss, 409600);
}

static void sig_ign(void)
{
	SAFE_SIGNAL(SIGCHLD, SIG_IGN);
	SAFE_GETRUSAGE(RUSAGE_CHILDREN, &ru);
	maxrss_init = ru.ru_maxrss;

	pid_t pid = SAFE_FORK();

	if (!pid)
		SAFE_EXECLP("getrusage03_child", "getrusage03_child",
			    "consume", "500", NULL);

	TST_PROCESS_EXIT_WAIT(pid, 0);
	SAFE_GETRUSAGE(RUSAGE_CHILDREN, &ru);
	if (is_in_delta(ru.ru_maxrss - maxrss_init))
		tst_res(TPASS, "initial.children ~= after_zombie.children");
	else
		tst_res(TFAIL, "after_zombie.children = %li, expected %li",
			ru.ru_maxrss, maxrss_init);

	SAFE_SIGNAL(SIGCHLD, SIG_DFL);
}

static void inherit_exec(void)
{
	if (!SAFE_FORK()) {
		char str_maxrss_self[BUFSIZ], str_maxrss_child[BUFSIZ];

		SAFE_GETRUSAGE(RUSAGE_SELF, &ru);
		sprintf(str_maxrss_self, "%ld", ru.ru_maxrss);
		SAFE_GETRUSAGE(RUSAGE_CHILDREN, &ru);
		sprintf(str_maxrss_child, "%ld", ru.ru_maxrss);

		SAFE_EXECLP("getrusage03_child", "getrusage03_child",
			    "compare", str_maxrss_self, str_maxrss_child, NULL);
	}
	tst_reap_children();
}

void (*testfunc_list[])(void) = {
	inherit_fork1, inherit_fork2, grandchild_maxrss,
	zombie, sig_ign, inherit_exec
};

static void run(unsigned int i)
{
	if (!SAFE_FORK()) {
		if (!SAFE_FORK()) {
			consume_mb(100);
			exit(0);
		}

		SAFE_WAIT(NULL);

		testfunc_list[i]();
	}
}

static struct tst_test test = {
	.forks_child = 1,
	.child_needs_reinit = 1,
	.resource_files = resource,
	.min_mem_avail = 512,
	.tags = (const struct tst_tag[]) {
		{"linux-git", "1f10206cf8e9"},
		{}
	},
	.test = run,
	.tcnt = ARRAY_SIZE(testfunc_list),
	.caps = (struct tst_cap []) {
		TST_CAP(TST_CAP_REQ, CAP_IPC_LOCK),
		{}
	},
};
