xref: /linux/tools/testing/selftests/mm/mrelease_test.c (revision 2241f81c91f211b512bd2c3a26a4a74258d0e008)
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 		perror("mmap failed, halting the test");
31 		return KSFT_FAIL;
32 	}
33 
34 	for (i = 0; i < nr_pages; i++)
35 		*((unsigned long *)(buf + (i * psize()))) = i;
36 
37 	/* Signal the parent that the child is ready */
38 	if (write(pipefd, "", 1) < 0) {
39 		perror("write");
40 		return KSFT_FAIL;
41 	}
42 
43 	/* Wait to be killed (when reparenting happens) */
44 	while (getppid() == ppid && timeout > 0) {
45 		sleep(1);
46 		timeout--;
47 	}
48 
49 	munmap(buf, nr_pages * psize());
50 
51 	return (timeout > 0) ? KSFT_PASS : KSFT_FAIL;
52 }
53 
54 /* The process_mrelease calls in this test are expected to fail */
55 static void run_negative_tests(int pidfd)
56 {
57 	int res;
58 	/* Test invalid flags. Expect to fail with EINVAL error code. */
59 	if (!syscall(__NR_process_mrelease, pidfd, (unsigned int)-1) ||
60 			errno != EINVAL) {
61 		res = (errno == ENOSYS ? KSFT_SKIP : KSFT_FAIL);
62 		perror("process_mrelease with wrong flags");
63 		exit(res);
64 	}
65 	/*
66 	 * Test reaping while process is alive with no pending SIGKILL.
67 	 * Expect to fail with EINVAL error code.
68 	 */
69 	if (!syscall(__NR_process_mrelease, pidfd, 0) || errno != EINVAL) {
70 		res = (errno == ENOSYS ? KSFT_SKIP : KSFT_FAIL);
71 		perror("process_mrelease on a live process");
72 		exit(res);
73 	}
74 }
75 
76 static int child_main(int pipefd[], size_t size)
77 {
78 	int res;
79 
80 	/* Allocate and fault-in memory and wait to be killed */
81 	close(pipefd[0]);
82 	res = alloc_noexit(MB(size) / psize(), pipefd[1]);
83 	close(pipefd[1]);
84 	return res;
85 }
86 
87 int main(void)
88 {
89 	int pipefd[2], pidfd;
90 	bool success, retry;
91 	size_t size;
92 	pid_t pid;
93 	char byte;
94 	int res;
95 
96 	/* Test a wrong pidfd */
97 	if (!syscall(__NR_process_mrelease, -1, 0) || errno != EBADF) {
98 		res = (errno == ENOSYS ? KSFT_SKIP : KSFT_FAIL);
99 		perror("process_mrelease with wrong pidfd");
100 		exit(res);
101 	}
102 
103 	/* Start the test with 1MB child memory allocation */
104 	size = 1;
105 retry:
106 	/*
107 	 * Pipe for the child to signal when it's done allocating
108 	 * memory
109 	 */
110 	if (pipe(pipefd)) {
111 		perror("pipe");
112 		exit(KSFT_FAIL);
113 	}
114 	pid = fork();
115 	if (pid < 0) {
116 		perror("fork");
117 		close(pipefd[0]);
118 		close(pipefd[1]);
119 		exit(KSFT_FAIL);
120 	}
121 
122 	if (pid == 0) {
123 		/* Child main routine */
124 		res = child_main(pipefd, size);
125 		exit(res);
126 	}
127 
128 	/*
129 	 * Parent main routine:
130 	 * Wait for the child to finish allocations, then kill and reap
131 	 */
132 	close(pipefd[1]);
133 	/* Block until the child is ready */
134 	res = read(pipefd[0], &byte, 1);
135 	close(pipefd[0]);
136 	if (res < 0) {
137 		perror("read");
138 		if (!kill(pid, SIGKILL))
139 			waitpid(pid, NULL, 0);
140 		exit(KSFT_FAIL);
141 	}
142 
143 	pidfd = syscall(__NR_pidfd_open, pid, 0);
144 	if (pidfd < 0) {
145 		perror("pidfd_open");
146 		if (!kill(pid, SIGKILL))
147 			waitpid(pid, NULL, 0);
148 		exit(KSFT_FAIL);
149 	}
150 
151 	/* Run negative tests which require a live child */
152 	run_negative_tests(pidfd);
153 
154 	if (kill(pid, SIGKILL)) {
155 		res = (errno == ENOSYS ? KSFT_SKIP : KSFT_FAIL);
156 		perror("kill");
157 		exit(res);
158 	}
159 
160 	success = (syscall(__NR_process_mrelease, pidfd, 0) == 0);
161 	if (!success) {
162 		/*
163 		 * If we failed to reap because the child exited too soon,
164 		 * before we could call process_mrelease. Double child's memory
165 		 * which causes it to spend more time on cleanup and increases
166 		 * our chances of reaping its memory before it exits.
167 		 * Retry until we succeed or reach MAX_SIZE_MB.
168 		 */
169 		if (errno == ESRCH) {
170 			retry = (size <= MAX_SIZE_MB);
171 		} else {
172 			res = (errno == ENOSYS ? KSFT_SKIP : KSFT_FAIL);
173 			perror("process_mrelease");
174 			waitpid(pid, NULL, 0);
175 			exit(res);
176 		}
177 	}
178 
179 	/* Cleanup to prevent zombies */
180 	if (waitpid(pid, NULL, 0) < 0) {
181 		perror("waitpid");
182 		exit(KSFT_FAIL);
183 	}
184 	close(pidfd);
185 
186 	if (!success) {
187 		if (retry) {
188 			size *= 2;
189 			goto retry;
190 		}
191 		printf("All process_mrelease attempts failed!\n");
192 		exit(KSFT_FAIL);
193 	}
194 
195 	printf("Success reaping a child with %zuMB of memory allocations\n",
196 	       size);
197 	return KSFT_PASS;
198 }
199