xref: /xnu-8792.41.9/tests/vfs/freeable_vnodes.c (revision 5c2921b07a2480ab43ec66f5b9e41cb872bc554f)
1 #include <darwintest.h>
2 #include <darwintest_utils.h>
3 
4 #include <unistd.h>
5 #include <stdlib.h>
6 #include <stdio.h>
7 #include <signal.h>
8 #include <fcntl.h>
9 #include <sys/sysctl.h>
10 #include <sys/mman.h>
11 #include <sys/stat.h>
12 #include <sys/socket.h>
13 
14 static pid_t vnoder_pid = -1;
15 static uint32_t maxvnodes = 0;
16 T_GLOBAL_META(T_META_NAMESPACE("xnu.vfs"));
17 
18 static uint32_t
get_sysctl_int(char * sysctl_name)19 get_sysctl_int(char *sysctl_name)
20 {
21 	uint32_t max_vnodes = 0;
22 	size_t length = sizeof(max_vnodes);
23 	int return_code = 0;
24 
25 	return_code = sysctlbyname(sysctl_name, &max_vnodes, &length, NULL, 0);
26 	T_QUIET;
27 	T_ASSERT_POSIX_SUCCESS(return_code, "sysctl call should return 0");
28 	return max_vnodes;
29 }
30 
31 #define MINVNODES_FOR_TEST 200
32 #define MAXVNODES_FOR_TEST 18000
33 
34 static void
run_vnoder(void)35 run_vnoder(void)
36 {
37 	void *buffer;
38 	uint32_t i = 0;
39 	uint32_t current_num = 0;
40 	uint32_t num_files =  maxvnodes - MINVNODES_FOR_TEST;
41 	int fd = -1;
42 	int dir_fd = -1;
43 	char dirpath[PATH_MAX];
44 	char filepath[NAME_MAX];
45 
46 	T_WITH_ERRNO;
47 
48 	/* Create a temporary working directory */
49 	sprintf(dirpath, "%s/tmp-mmap-bomb-dir.%d", dt_tmpdir(), getpid());
50 	T_QUIET;
51 	T_ASSERT_POSIX_SUCCESS(mkdir(dirpath, S_IRWXU | S_IRWXG ), NULL);
52 
53 	T_LOG("Created test dir %s\n", dirpath);
54 
55 	T_QUIET;
56 	T_ASSERT_POSIX_SUCCESS(dir_fd = open(dirpath, O_RDONLY), NULL);
57 
58 	/* use mmap to exhaust vnode pool */
59 	uint32_t log_interval = (num_files / 4);
60 	uint32_t next_test_log_number = log_interval;
61 	T_LOG("trying to map %u files, progress every %u files\n", num_files, log_interval);
62 	/*
63 	 * This loop can take an ideterminate amount of time on different devices since the
64 	 * it is based on file creation (which is rate limited by the SEP) and the number we
65 	 * will attempt to create. At some point it should be replaced by walking a large hierarchy
66 	 * (like /System) and mapping those files instead of creating files.
67 	 */
68 	for (i = 0; i < num_files; ++i) {
69 		if (i == next_test_log_number) {
70 			T_LOG("created and mapped %u files so far\n", next_test_log_number);
71 			next_test_log_number += log_interval;
72 			current_num = get_sysctl_int("vfs.vnstats.num_vnodes");
73 			if (current_num >= (maxvnodes + 5000)) {
74 				T_LOG("numvnodes is >= maxvnodes + 5000 (%u), done with creation and mmap loop\n",
75 				    current_num);
76 				break;
77 			}
78 		}
79 
80 		sprintf(filepath, "file-%d", i);
81 
82 		T_QUIET;
83 		T_ASSERT_POSIX_SUCCESS(fd = openat(dir_fd, filepath, O_CREAT | O_RDWR, 0666), NULL);
84 
85 		T_QUIET;
86 		T_ASSERT_POSIX_SUCCESS(ftruncate(fd, (off_t)PAGE_SIZE), NULL);
87 
88 		T_QUIET;
89 		T_ASSERT_POSIX_SUCCESS(unlinkat(dir_fd, filepath, 0), NULL);
90 
91 		T_QUIET;
92 		T_ASSERT_POSIX_SUCCESS(buffer = mmap(NULL, PAGE_SIZE, PROT_READ, MAP_FILE | MAP_NOCACHE | MAP_PRIVATE, fd, 0), NULL);
93 
94 		T_QUIET;
95 		T_ASSERT_POSIX_SUCCESS(close(fd), NULL);
96 	}
97 	T_LOG("created and mapped %u files\n", i);
98 
99 	T_QUIET;
100 	T_ASSERT_POSIX_SUCCESS(close(dir_fd), NULL);
101 
102 	T_QUIET;
103 	T_ASSERT_POSIX_SUCCESS(rmdir(dirpath), NULL);
104 }
105 
106 static void
cleanup(void)107 cleanup(void)
108 {
109 	if (vnoder_pid > 0) {
110 		T_LOG("Killing vnoder pid in cleanup: %d", vnoder_pid);
111 		kill(vnoder_pid, SIGKILL);
112 	}
113 }
114 
115 
116 T_DECL(vnode_max_increase,
117     "Consume vnodes to cause the max vnodes available to increase",
118     T_META_REQUIRES_SYSCTL_EQ("vfs.vnstats.vn_dealloc_level", 1),
119     T_META_RADAR_COMPONENT_NAME("xnu"),
120     T_META_RADAR_COMPONENT_VERSION("VFS"))
121 {
122 	int sock_fd[2];
123 	char buf[10];
124 	uint32_t initial_num = 0, current_num = 0;
125 	uint32_t deallocateable_vnodes = 0;
126 	uint32_t deallocateable_busy_vnodes = 0;
127 	uint32_t vnode_delta = 100;
128 	uint32_t timeout = 10;
129 	uint32_t i = 0;
130 
131 	T_ATEND(cleanup);
132 	T_WITH_ERRNO;
133 
134 	T_SETUPBEGIN;
135 	/*
136 	 * We use this to handshake certain actions between this process and its
137 	 * child.
138 	 */
139 	T_QUIET;
140 	T_ASSERT_POSIX_SUCCESS(socketpair(AF_UNIX, SOCK_STREAM, 0, sock_fd),
141 	    NULL);
142 
143 	maxvnodes = get_sysctl_int("kern.maxvnodes");
144 	T_LOG("max vnodes: %d", maxvnodes);
145 	if (maxvnodes > MAXVNODES_FOR_TEST) {
146 		T_SKIP("maxvnodes can't be more than %d for test", MAXVNODES_FOR_TEST);
147 	} else if (maxvnodes <= MINVNODES_FOR_TEST) {
148 		T_SKIP("maxvnodes can't be less than %d for test", MINVNODES_FOR_TEST);
149 	}
150 
151 	initial_num = get_sysctl_int("vfs.vnstats.num_vnodes");
152 	T_LOG("Initial num vnodes: %d", initial_num);
153 
154 	T_SETUPEND;
155 
156 	T_QUIET;
157 	T_ASSERT_POSIX_SUCCESS(vnoder_pid = fork(), NULL);
158 
159 	if (vnoder_pid == 0) { /* child */
160 		T_QUIET;
161 		T_ASSERT_POSIX_SUCCESS(close(sock_fd[0]), NULL);
162 
163 		run_vnoder();
164 
165 		/* now let parent know we're done with creating all the vnodes */
166 		T_QUIET;
167 		T_ASSERT_POSIX_SUCCESS(write(sock_fd[1], "done", sizeof("done")), NULL);
168 
169 		/* wait for parent to set us to send us a signal */
170 		T_QUIET;
171 		T_ASSERT_POSIX_SUCCESS(read(sock_fd[1], buf, sizeof(buf)), NULL);
172 
173 		pause();
174 
175 		exit(0);
176 	}
177 
178 	T_QUIET;
179 	T_ASSERT_POSIX_SUCCESS(close(sock_fd[1]), NULL);
180 
181 	/* wait for child to run and run up the vnodes */
182 	T_QUIET;
183 	T_ASSERT_POSIX_SUCCESS(read(sock_fd[0], buf, sizeof(buf)), NULL);
184 
185 	current_num = get_sysctl_int("vfs.vnstats.num_vnodes");
186 	T_LOG("num vnodes after vnoder: %d", current_num);
187 
188 	T_QUIET;
189 	T_ASSERT_GT(current_num, maxvnodes,
190 	    "vnode maximum should increase under vnode presssure");
191 
192 	T_LOG("Killing vnoder (pid %d) to free up held vnodes", vnoder_pid);
193 	T_QUIET;
194 	T_ASSERT_POSIX_SUCCESS(write(sock_fd[0], "done", sizeof("done")), NULL);
195 	kill(vnoder_pid, SIGKILL);
196 	vnoder_pid = -1;
197 
198 	T_LOG("Waiting up to %ds for vnodes to be deallocated", timeout);
199 	for (i = 0; i < timeout; i++) {
200 		sleep(1);
201 		deallocateable_vnodes = get_sysctl_int(
202 			"vfs.vnstats.num_deallocable_vnodes");
203 		deallocateable_busy_vnodes = get_sysctl_int(
204 			"vfs.vnstats.num_deallocable_busy_vnodes");
205 
206 		T_LOG("deallocateable_vnodes after %d second%s : %d",
207 		    i + 1, (i == 0) ? "" : "s", deallocateable_vnodes);
208 		T_LOG("deallocateable_busy_vnodes after %d second%s : %d",
209 		    i + 1, (i == 0) ? "" : "s", deallocateable_busy_vnodes);
210 
211 		if (deallocateable_vnodes < vnode_delta) {
212 			break;
213 		}
214 		/* This can happen because we don't fetch atomically */
215 		if (deallocateable_busy_vnodes > deallocateable_vnodes) {
216 			deallocateable_busy_vnodes = deallocateable_vnodes;
217 		}
218 
219 		if ((i == (timeout - 1)) &&
220 		    ((deallocateable_vnodes - deallocateable_busy_vnodes) < vnode_delta)) {
221 			break;
222 		}
223 	}
224 
225 	T_QUIET;
226 	T_ASSERT_NE(i, timeout, "Deallocateable vnodes should drop in under %ds",
227 	    timeout);
228 
229 	current_num = get_sysctl_int("vfs.vnstats.num_vnodes");
230 	T_LOG("num vnodes after killing vnoder: %d", current_num);
231 
232 	T_QUIET;
233 	T_ASSERT_LE(current_num, maxvnodes + deallocateable_busy_vnodes + vnode_delta,
234 	    "vnode maximum should be within %d of the initial maximum",
235 	    vnode_delta);
236 }
237