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