xref: /linux/tools/testing/selftests/mm/mrelease_test.c (revision 71dfa617ea9f18e4585fe78364217cd32b1fc382)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Copyright 2022 Google LLC
4  */
5 #define _GNU_SOURCE
6 #include <errno.h>
7 #include <stdbool.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <sys/syscall.h>
11 #include <sys/wait.h>
12 #include <unistd.h>
13 #include <asm-generic/unistd.h>
14 #include "vm_util.h"
15 #include "../kselftest.h"
16 
17 #define MB(x) (x << 20)
18 #define MAX_SIZE_MB 1024
19 
20 static int alloc_noexit(unsigned long nr_pages, int pipefd)
21 {
22 	int ppid = getppid();
23 	int timeout = 10; /* 10sec timeout to get killed */
24 	unsigned long i;
25 	char *buf;
26 
27 	buf = (char *)mmap(NULL, nr_pages * psize(), PROT_READ | PROT_WRITE,
28 			   MAP_PRIVATE | MAP_ANON, 0, 0);
29 	if (buf == MAP_FAILED)
30 		ksft_exit_fail_msg("mmap failed, halting the test: %s\n", strerror(errno));
31 
32 	for (i = 0; i < nr_pages; i++)
33 		*((unsigned long *)(buf + (i * psize()))) = i;
34 
35 	/* Signal the parent that the child is ready */
36 	if (write(pipefd, "", 1) < 0)
37 		ksft_exit_fail_msg("write: %s\n", strerror(errno));
38 
39 	/* Wait to be killed (when reparenting happens) */
40 	while (getppid() == ppid && timeout > 0) {
41 		sleep(1);
42 		timeout--;
43 	}
44 
45 	munmap(buf, nr_pages * psize());
46 
47 	return (timeout > 0) ? KSFT_PASS : KSFT_FAIL;
48 }
49 
50 /* The process_mrelease calls in this test are expected to fail */
51 static void run_negative_tests(int pidfd)
52 {
53 	/* Test invalid flags. Expect to fail with EINVAL error code. */
54 	if (!syscall(__NR_process_mrelease, pidfd, (unsigned int)-1) ||
55 			errno != EINVAL) {
56 		ksft_exit_fail_msg("process_mrelease with wrong flags: %s\n", strerror(errno));
57 	}
58 	/*
59 	 * Test reaping while process is alive with no pending SIGKILL.
60 	 * Expect to fail with EINVAL error code.
61 	 */
62 	if (!syscall(__NR_process_mrelease, pidfd, 0) || errno != EINVAL)
63 		ksft_exit_fail_msg("process_mrelease on a live process: %s\n", strerror(errno));
64 }
65 
66 static int child_main(int pipefd[], size_t size)
67 {
68 	int res;
69 
70 	/* Allocate and fault-in memory and wait to be killed */
71 	close(pipefd[0]);
72 	res = alloc_noexit(MB(size) / psize(), pipefd[1]);
73 	close(pipefd[1]);
74 	return res;
75 }
76 
77 int main(void)
78 {
79 	int pipefd[2], pidfd;
80 	bool success, retry;
81 	size_t size;
82 	pid_t pid;
83 	char byte;
84 	int res;
85 
86 	ksft_print_header();
87 	ksft_set_plan(1);
88 
89 	/* Test a wrong pidfd */
90 	if (!syscall(__NR_process_mrelease, -1, 0) || errno != EBADF) {
91 		if (errno == ENOSYS) {
92 			ksft_test_result_skip("process_mrelease not implemented\n");
93 			ksft_finished();
94 		} else {
95 			ksft_exit_fail_msg("process_mrelease with wrong pidfd: %s",
96 					   strerror(errno));
97 		}
98 	}
99 
100 	/* Start the test with 1MB child memory allocation */
101 	size = 1;
102 retry:
103 	/*
104 	 * Pipe for the child to signal when it's done allocating
105 	 * memory
106 	 */
107 	if (pipe(pipefd))
108 		ksft_exit_fail_msg("pipe: %s\n", strerror(errno));
109 
110 	pid = fork();
111 	if (pid < 0) {
112 		close(pipefd[0]);
113 		close(pipefd[1]);
114 		ksft_exit_fail_msg("fork: %s\n", strerror(errno));
115 	}
116 
117 	if (pid == 0) {
118 		/* Child main routine */
119 		res = child_main(pipefd, size);
120 		exit(res);
121 	}
122 
123 	/*
124 	 * Parent main routine:
125 	 * Wait for the child to finish allocations, then kill and reap
126 	 */
127 	close(pipefd[1]);
128 	/* Block until the child is ready */
129 	res = read(pipefd[0], &byte, 1);
130 	close(pipefd[0]);
131 	if (res < 0) {
132 		if (!kill(pid, SIGKILL))
133 			waitpid(pid, NULL, 0);
134 		ksft_exit_fail_msg("read: %s\n", strerror(errno));
135 	}
136 
137 	pidfd = syscall(__NR_pidfd_open, pid, 0);
138 	if (pidfd < 0) {
139 		if (!kill(pid, SIGKILL))
140 			waitpid(pid, NULL, 0);
141 		ksft_exit_fail_msg("pidfd_open: %s\n", strerror(errno));
142 	}
143 
144 	/* Run negative tests which require a live child */
145 	run_negative_tests(pidfd);
146 
147 	if (kill(pid, SIGKILL))
148 		ksft_exit_fail_msg("kill: %s\n", strerror(errno));
149 
150 	success = (syscall(__NR_process_mrelease, pidfd, 0) == 0);
151 	if (!success) {
152 		/*
153 		 * If we failed to reap because the child exited too soon,
154 		 * before we could call process_mrelease. Double child's memory
155 		 * which causes it to spend more time on cleanup and increases
156 		 * our chances of reaping its memory before it exits.
157 		 * Retry until we succeed or reach MAX_SIZE_MB.
158 		 */
159 		if (errno == ESRCH) {
160 			retry = (size <= MAX_SIZE_MB);
161 		} else {
162 			waitpid(pid, NULL, 0);
163 			ksft_exit_fail_msg("process_mrelease: %s\n", strerror(errno));
164 		}
165 	}
166 
167 	/* Cleanup to prevent zombies */
168 	if (waitpid(pid, NULL, 0) < 0)
169 		ksft_exit_fail_msg("waitpid: %s\n", strerror(errno));
170 
171 	close(pidfd);
172 
173 	if (!success) {
174 		if (retry) {
175 			size *= 2;
176 			goto retry;
177 		}
178 		ksft_exit_fail_msg("All process_mrelease attempts failed!\n");
179 	}
180 
181 	ksft_test_result_pass("Success reaping a child with %zuMB of memory allocations\n",
182 			      size);
183 	ksft_finished();
184 }
185