162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright 2022 Google LLC
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci#define _GNU_SOURCE
662306a36Sopenharmony_ci#include <errno.h>
762306a36Sopenharmony_ci#include <stdbool.h>
862306a36Sopenharmony_ci#include <stdio.h>
962306a36Sopenharmony_ci#include <stdlib.h>
1062306a36Sopenharmony_ci#include <sys/syscall.h>
1162306a36Sopenharmony_ci#include <sys/wait.h>
1262306a36Sopenharmony_ci#include <unistd.h>
1362306a36Sopenharmony_ci#include <asm-generic/unistd.h>
1462306a36Sopenharmony_ci#include "vm_util.h"
1562306a36Sopenharmony_ci#include "../kselftest.h"
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#define MB(x) (x << 20)
1862306a36Sopenharmony_ci#define MAX_SIZE_MB 1024
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_cistatic int alloc_noexit(unsigned long nr_pages, int pipefd)
2162306a36Sopenharmony_ci{
2262306a36Sopenharmony_ci	int ppid = getppid();
2362306a36Sopenharmony_ci	int timeout = 10; /* 10sec timeout to get killed */
2462306a36Sopenharmony_ci	unsigned long i;
2562306a36Sopenharmony_ci	char *buf;
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci	buf = (char *)mmap(NULL, nr_pages * psize(), PROT_READ | PROT_WRITE,
2862306a36Sopenharmony_ci			   MAP_PRIVATE | MAP_ANON, 0, 0);
2962306a36Sopenharmony_ci	if (buf == MAP_FAILED) {
3062306a36Sopenharmony_ci		perror("mmap failed, halting the test");
3162306a36Sopenharmony_ci		return KSFT_FAIL;
3262306a36Sopenharmony_ci	}
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci	for (i = 0; i < nr_pages; i++)
3562306a36Sopenharmony_ci		*((unsigned long *)(buf + (i * psize()))) = i;
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	/* Signal the parent that the child is ready */
3862306a36Sopenharmony_ci	if (write(pipefd, "", 1) < 0) {
3962306a36Sopenharmony_ci		perror("write");
4062306a36Sopenharmony_ci		return KSFT_FAIL;
4162306a36Sopenharmony_ci	}
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	/* Wait to be killed (when reparenting happens) */
4462306a36Sopenharmony_ci	while (getppid() == ppid && timeout > 0) {
4562306a36Sopenharmony_ci		sleep(1);
4662306a36Sopenharmony_ci		timeout--;
4762306a36Sopenharmony_ci	}
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	munmap(buf, nr_pages * psize());
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci	return (timeout > 0) ? KSFT_PASS : KSFT_FAIL;
5262306a36Sopenharmony_ci}
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci/* The process_mrelease calls in this test are expected to fail */
5562306a36Sopenharmony_cistatic void run_negative_tests(int pidfd)
5662306a36Sopenharmony_ci{
5762306a36Sopenharmony_ci	int res;
5862306a36Sopenharmony_ci	/* Test invalid flags. Expect to fail with EINVAL error code. */
5962306a36Sopenharmony_ci	if (!syscall(__NR_process_mrelease, pidfd, (unsigned int)-1) ||
6062306a36Sopenharmony_ci			errno != EINVAL) {
6162306a36Sopenharmony_ci		res = (errno == ENOSYS ? KSFT_SKIP : KSFT_FAIL);
6262306a36Sopenharmony_ci		perror("process_mrelease with wrong flags");
6362306a36Sopenharmony_ci		exit(res);
6462306a36Sopenharmony_ci	}
6562306a36Sopenharmony_ci	/*
6662306a36Sopenharmony_ci	 * Test reaping while process is alive with no pending SIGKILL.
6762306a36Sopenharmony_ci	 * Expect to fail with EINVAL error code.
6862306a36Sopenharmony_ci	 */
6962306a36Sopenharmony_ci	if (!syscall(__NR_process_mrelease, pidfd, 0) || errno != EINVAL) {
7062306a36Sopenharmony_ci		res = (errno == ENOSYS ? KSFT_SKIP : KSFT_FAIL);
7162306a36Sopenharmony_ci		perror("process_mrelease on a live process");
7262306a36Sopenharmony_ci		exit(res);
7362306a36Sopenharmony_ci	}
7462306a36Sopenharmony_ci}
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_cistatic int child_main(int pipefd[], size_t size)
7762306a36Sopenharmony_ci{
7862306a36Sopenharmony_ci	int res;
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	/* Allocate and fault-in memory and wait to be killed */
8162306a36Sopenharmony_ci	close(pipefd[0]);
8262306a36Sopenharmony_ci	res = alloc_noexit(MB(size) / psize(), pipefd[1]);
8362306a36Sopenharmony_ci	close(pipefd[1]);
8462306a36Sopenharmony_ci	return res;
8562306a36Sopenharmony_ci}
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ciint main(void)
8862306a36Sopenharmony_ci{
8962306a36Sopenharmony_ci	int pipefd[2], pidfd;
9062306a36Sopenharmony_ci	bool success, retry;
9162306a36Sopenharmony_ci	size_t size;
9262306a36Sopenharmony_ci	pid_t pid;
9362306a36Sopenharmony_ci	char byte;
9462306a36Sopenharmony_ci	int res;
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	/* Test a wrong pidfd */
9762306a36Sopenharmony_ci	if (!syscall(__NR_process_mrelease, -1, 0) || errno != EBADF) {
9862306a36Sopenharmony_ci		res = (errno == ENOSYS ? KSFT_SKIP : KSFT_FAIL);
9962306a36Sopenharmony_ci		perror("process_mrelease with wrong pidfd");
10062306a36Sopenharmony_ci		exit(res);
10162306a36Sopenharmony_ci	}
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ci	/* Start the test with 1MB child memory allocation */
10462306a36Sopenharmony_ci	size = 1;
10562306a36Sopenharmony_ciretry:
10662306a36Sopenharmony_ci	/*
10762306a36Sopenharmony_ci	 * Pipe for the child to signal when it's done allocating
10862306a36Sopenharmony_ci	 * memory
10962306a36Sopenharmony_ci	 */
11062306a36Sopenharmony_ci	if (pipe(pipefd)) {
11162306a36Sopenharmony_ci		perror("pipe");
11262306a36Sopenharmony_ci		exit(KSFT_FAIL);
11362306a36Sopenharmony_ci	}
11462306a36Sopenharmony_ci	pid = fork();
11562306a36Sopenharmony_ci	if (pid < 0) {
11662306a36Sopenharmony_ci		perror("fork");
11762306a36Sopenharmony_ci		close(pipefd[0]);
11862306a36Sopenharmony_ci		close(pipefd[1]);
11962306a36Sopenharmony_ci		exit(KSFT_FAIL);
12062306a36Sopenharmony_ci	}
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	if (pid == 0) {
12362306a36Sopenharmony_ci		/* Child main routine */
12462306a36Sopenharmony_ci		res = child_main(pipefd, size);
12562306a36Sopenharmony_ci		exit(res);
12662306a36Sopenharmony_ci	}
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	/*
12962306a36Sopenharmony_ci	 * Parent main routine:
13062306a36Sopenharmony_ci	 * Wait for the child to finish allocations, then kill and reap
13162306a36Sopenharmony_ci	 */
13262306a36Sopenharmony_ci	close(pipefd[1]);
13362306a36Sopenharmony_ci	/* Block until the child is ready */
13462306a36Sopenharmony_ci	res = read(pipefd[0], &byte, 1);
13562306a36Sopenharmony_ci	close(pipefd[0]);
13662306a36Sopenharmony_ci	if (res < 0) {
13762306a36Sopenharmony_ci		perror("read");
13862306a36Sopenharmony_ci		if (!kill(pid, SIGKILL))
13962306a36Sopenharmony_ci			waitpid(pid, NULL, 0);
14062306a36Sopenharmony_ci		exit(KSFT_FAIL);
14162306a36Sopenharmony_ci	}
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	pidfd = syscall(__NR_pidfd_open, pid, 0);
14462306a36Sopenharmony_ci	if (pidfd < 0) {
14562306a36Sopenharmony_ci		perror("pidfd_open");
14662306a36Sopenharmony_ci		if (!kill(pid, SIGKILL))
14762306a36Sopenharmony_ci			waitpid(pid, NULL, 0);
14862306a36Sopenharmony_ci		exit(KSFT_FAIL);
14962306a36Sopenharmony_ci	}
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	/* Run negative tests which require a live child */
15262306a36Sopenharmony_ci	run_negative_tests(pidfd);
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	if (kill(pid, SIGKILL)) {
15562306a36Sopenharmony_ci		res = (errno == ENOSYS ? KSFT_SKIP : KSFT_FAIL);
15662306a36Sopenharmony_ci		perror("kill");
15762306a36Sopenharmony_ci		exit(res);
15862306a36Sopenharmony_ci	}
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	success = (syscall(__NR_process_mrelease, pidfd, 0) == 0);
16162306a36Sopenharmony_ci	if (!success) {
16262306a36Sopenharmony_ci		/*
16362306a36Sopenharmony_ci		 * If we failed to reap because the child exited too soon,
16462306a36Sopenharmony_ci		 * before we could call process_mrelease. Double child's memory
16562306a36Sopenharmony_ci		 * which causes it to spend more time on cleanup and increases
16662306a36Sopenharmony_ci		 * our chances of reaping its memory before it exits.
16762306a36Sopenharmony_ci		 * Retry until we succeed or reach MAX_SIZE_MB.
16862306a36Sopenharmony_ci		 */
16962306a36Sopenharmony_ci		if (errno == ESRCH) {
17062306a36Sopenharmony_ci			retry = (size <= MAX_SIZE_MB);
17162306a36Sopenharmony_ci		} else {
17262306a36Sopenharmony_ci			res = (errno == ENOSYS ? KSFT_SKIP : KSFT_FAIL);
17362306a36Sopenharmony_ci			perror("process_mrelease");
17462306a36Sopenharmony_ci			waitpid(pid, NULL, 0);
17562306a36Sopenharmony_ci			exit(res);
17662306a36Sopenharmony_ci		}
17762306a36Sopenharmony_ci	}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	/* Cleanup to prevent zombies */
18062306a36Sopenharmony_ci	if (waitpid(pid, NULL, 0) < 0) {
18162306a36Sopenharmony_ci		perror("waitpid");
18262306a36Sopenharmony_ci		exit(KSFT_FAIL);
18362306a36Sopenharmony_ci	}
18462306a36Sopenharmony_ci	close(pidfd);
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	if (!success) {
18762306a36Sopenharmony_ci		if (retry) {
18862306a36Sopenharmony_ci			size *= 2;
18962306a36Sopenharmony_ci			goto retry;
19062306a36Sopenharmony_ci		}
19162306a36Sopenharmony_ci		printf("All process_mrelease attempts failed!\n");
19262306a36Sopenharmony_ci		exit(KSFT_FAIL);
19362306a36Sopenharmony_ci	}
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	printf("Success reaping a child with %zuMB of memory allocations\n",
19662306a36Sopenharmony_ci	       size);
19762306a36Sopenharmony_ci	return KSFT_PASS;
19862306a36Sopenharmony_ci}
199