1 /* xcrun -sdk iphoneos.internal clang -ldarwintest -o freeable_vnodes freeable_vnodes.c -g -Weverything */
2
3 #include <darwintest.h>
4 #include <darwintest_utils.h>
5
6 #include <unistd.h>
7 #include <stdlib.h>
8 #include <stdio.h>
9 #include <signal.h>
10 #include <fcntl.h>
11 #include <string.h>
12 #include <sys/sysctl.h>
13 #include <sys/mman.h>
14 #include <sys/stat.h>
15 #include <sys/socket.h>
16 #include <TargetConditionals.h>
17
18 static pid_t vnoder_pid = -1;
19 static uint32_t maxvnodes = 0;
20 T_GLOBAL_META(T_META_NAMESPACE("xnu.vfs"));
21
22 #if TARGET_OS_IOS
23
24 static char *dlpaths[] = {
25 "/System/Library/Assistant/FlowDelegatePlugins/HomeAutomationFlowDelegatePlugin.bundle/HomeAutomationFlowDelegatePlugin",
26 "/System/Library/NanoPreferenceBundles/Applications/NanoPassbookBridgeSettings.bundle/NanoPassbookBridgeSettings",
27 "/System/Library/Assistant/FlowDelegatePlugins/MessagesFlowDelegatePlugin.bundle/MessagesFlowDelegatePlugin",
28 "/System/Library/NanoPreferenceBundles/Discover/HealthAndFitnessPlugin.bundle/HealthAndFitnessPlugin",
29 "/System/Library/NanoPreferenceBundles/SetupBundles/DepthCompanionSetup.bundle/DepthCompanionSetup",
30 "/System/Library/PreferenceBundles/DigitalSeparationSettings.bundle/DigitalSeparationSettings",
31 "/System/Library/Assistant/FlowDelegatePlugins/NotebookFlowPlugin.bundle/NotebookFlowPlugin",
32 "/System/Library/NanoPreferenceBundles/Discover/UserGuidePlugin.bundle/UserGuidePlugin",
33 "/System/Library/PreferenceBundles/NotificationsSettings.bundle/NotificationsSettings",
34 "/System/Library/PreferenceBundles/AssistantSettings.bundle/AssistantSettings",
35 "/System/Library/PreferenceBundles/SettingsCellular.bundle/SettingsCellular",
36 "/System/Library/PreferenceBundles/FocusSettings.bundle/FocusSettings",
37 "/private/preboot/Cryptexes/App/System/Library/PreferenceBundles/MobileSafariSettings.bundle/MobileSafariSettings",
38 NULL
39 };
40
41 #elif TARGET_OS_WATCH
42
43 static char *dlpaths[] = {
44 "/System/Library/Assistant/FlowDelegatePlugins/HomeAutomationFlowDelegatePlugin.bundle/HomeAutomationFlowDelegatePlugin",
45 "/System/Library/Assistant/FlowDelegatePlugins/MessagesFlowDelegatePlugin.bundle/MessagesFlowDelegatePlugin",
46 "/System/Library/PreferenceBundles/NanoAccessibilitySettings.bundle/NanoAccessibilitySettings",
47 "/System/Library/Assistant/FlowDelegatePlugins/NotebookFlowPlugin.bundle/NotebookFlowPlugin",
48 NULL
49 };
50
51 #elif TARGET_OS_OSX
52
53 static char *dlpaths[] = {
54 "/System/Library/Assistant/FlowDelegatePlugins/HomeAutomationFlowDelegatePlugin.bundle/Contents/MacOS/HomeAutomationFlowDelegatePlugin",
55 "/System/Library/Assistant/FlowDelegatePlugins/MessagesFlowDelegatePlugin.bundle/Contents/MacOS/MessagesFlowDelegatePlugin",
56 "/System/Library/Assistant/FlowDelegatePlugins/NotebookFlowPlugin.bundle/Contents/MacOS/NotebookFlowPlugin",
57 NULL
58 };
59
60 #else
61
62 static char *dlpaths[] = {
63 NULL
64 };
65
66 #endif
67
68 static uint32_t
get_sysctl_int(char * sysctl_name)69 get_sysctl_int(char *sysctl_name)
70 {
71 uint32_t max_vnodes = 0;
72 size_t length = sizeof(max_vnodes);
73 int return_code = 0;
74
75 return_code = sysctlbyname(sysctl_name, &max_vnodes, &length, NULL, 0);
76 T_QUIET;
77 T_ASSERT_POSIX_SUCCESS(return_code, "sysctl call should return 0");
78 return max_vnodes;
79 }
80
81 #define MINVNODES_FOR_TEST 200
82 #define MAXVNODES_FOR_TEST 18000
83
84 static void
run_vnoder(void)85 run_vnoder(void)
86 {
87 void *buffer;
88 uint32_t i = 0;
89 uint32_t current_num = 0;
90 uint32_t num_files = maxvnodes - MINVNODES_FOR_TEST;
91 int fd = -1;
92 int dir_fd = -1;
93 char dirpath[PATH_MAX];
94 char filepath[NAME_MAX];
95
96 T_WITH_ERRNO;
97
98 /* Create a temporary working directory */
99 sprintf(dirpath, "%s/tmp-mmap-bomb-dir.%d", dt_tmpdir(), getpid());
100 T_QUIET;
101 T_ASSERT_POSIX_SUCCESS(mkdir(dirpath, S_IRWXU | S_IRWXG ), NULL);
102
103 T_LOG("Created test dir %s\n", dirpath);
104
105 T_QUIET;
106 T_ASSERT_POSIX_SUCCESS(dir_fd = open(dirpath, O_RDONLY), NULL);
107
108 /* use mmap to exhaust vnode pool */
109 uint32_t log_interval = (num_files / 4);
110 uint32_t next_test_log_number = log_interval;
111 T_LOG("trying to map %u files, progress every %u files\n", num_files, log_interval);
112 /*
113 * This loop can take an ideterminate amount of time on different devices since the
114 * it is based on file creation (which is rate limited by the SEP) and the number we
115 * will attempt to create. At some point it should be replaced by walking a large hierarchy
116 * (like /System) and mapping those files instead of creating files.
117 */
118 for (i = 0; i < num_files; ++i) {
119 if (i == next_test_log_number) {
120 T_LOG("created and mapped %u files so far\n", next_test_log_number);
121 next_test_log_number += log_interval;
122 current_num = get_sysctl_int("vfs.vnstats.num_vnodes");
123 if (current_num >= (maxvnodes + 5000)) {
124 T_LOG("numvnodes is >= maxvnodes + 5000 (%u), done with creation and mmap loop\n",
125 current_num);
126 break;
127 }
128 }
129
130 sprintf(filepath, "file-%d", i);
131
132 T_QUIET;
133 T_ASSERT_POSIX_SUCCESS(fd = openat(dir_fd, filepath, O_CREAT | O_RDWR, 0666), NULL);
134
135 T_QUIET;
136 T_ASSERT_POSIX_SUCCESS(ftruncate(fd, (off_t)PAGE_SIZE), NULL);
137
138 T_QUIET;
139 T_ASSERT_POSIX_SUCCESS(unlinkat(dir_fd, filepath, 0), NULL);
140
141 T_QUIET;
142 T_ASSERT_POSIX_SUCCESS(buffer = mmap(NULL, PAGE_SIZE, PROT_READ, MAP_FILE | MAP_NOCACHE | MAP_PRIVATE, fd, 0), NULL);
143
144 T_QUIET;
145 T_ASSERT_POSIX_SUCCESS(close(fd), NULL);
146 }
147 T_LOG("created and mapped %u files\n", i);
148
149 T_QUIET;
150 T_ASSERT_POSIX_SUCCESS(close(dir_fd), NULL);
151
152 T_QUIET;
153 T_ASSERT_POSIX_SUCCESS(rmdir(dirpath), NULL);
154 }
155
156 static void
cleanup(void)157 cleanup(void)
158 {
159 if (vnoder_pid > 0) {
160 T_LOG("Killing vnoder pid in cleanup: %d", vnoder_pid);
161 kill(vnoder_pid, SIGKILL);
162 }
163 }
164
165
166 T_DECL(vnode_max_increase,
167 "Consume vnodes to cause the max vnodes available to increase",
168 T_META_REQUIRES_SYSCTL_EQ("vfs.vnstats.vn_dealloc_level", 1),
169 T_META_RADAR_COMPONENT_NAME("xnu"),
170 T_META_RADAR_COMPONENT_VERSION("VFS"))
171 {
172 int sock_fd[2];
173 char buf[10];
174 uint32_t initial_num = 0, current_num = 0;
175 uint32_t deallocateable_vnodes = 0;
176 uint32_t deallocateable_busy_vnodes = 0;
177 uint32_t vnode_delta = 100;
178 uint32_t timeout = 10;
179 uint32_t i = 0;
180
181 T_ATEND(cleanup);
182 T_WITH_ERRNO;
183
184 T_SETUPBEGIN;
185 /*
186 * We use this to handshake certain actions between this process and its
187 * child.
188 */
189 T_QUIET;
190 T_ASSERT_POSIX_SUCCESS(socketpair(AF_UNIX, SOCK_STREAM, 0, sock_fd),
191 NULL);
192
193 maxvnodes = get_sysctl_int("kern.maxvnodes");
194 T_LOG("max vnodes: %d", maxvnodes);
195 if (maxvnodes > MAXVNODES_FOR_TEST) {
196 T_SKIP("maxvnodes can't be more than %d for test", MAXVNODES_FOR_TEST);
197 } else if (maxvnodes <= MINVNODES_FOR_TEST) {
198 T_SKIP("maxvnodes can't be less than %d for test", MINVNODES_FOR_TEST);
199 }
200
201 initial_num = get_sysctl_int("vfs.vnstats.num_vnodes");
202 T_LOG("Initial num vnodes: %d", initial_num);
203
204 T_SETUPEND;
205
206 T_QUIET;
207 T_ASSERT_POSIX_SUCCESS(vnoder_pid = fork(), NULL);
208
209 if (vnoder_pid == 0) { /* child */
210 T_QUIET;
211 T_ASSERT_POSIX_SUCCESS(close(sock_fd[0]), NULL);
212
213 run_vnoder();
214
215 /* now let parent know we're done with creating all the vnodes */
216 T_QUIET;
217 T_ASSERT_POSIX_SUCCESS(write(sock_fd[1], "done", sizeof("done")), NULL);
218
219 /* wait for parent to set us to send us a signal */
220 T_QUIET;
221 T_ASSERT_POSIX_SUCCESS(read(sock_fd[1], buf, sizeof(buf)), NULL);
222
223 pause();
224
225 exit(0);
226 }
227
228 T_QUIET;
229 T_ASSERT_POSIX_SUCCESS(close(sock_fd[1]), NULL);
230
231 /* wait for child to run and run up the vnodes */
232 T_QUIET;
233 T_ASSERT_POSIX_SUCCESS(read(sock_fd[0], buf, sizeof(buf)), NULL);
234
235 int num_getpaths = 0;
236 for (i = 0; i < 5 && dlpaths[0] != NULL; i++) {
237 for (int j = 0; dlpaths[j] != NULL; j++) {
238 int dlpath_fd;
239 char path[256] = {0};
240 struct stat sb;
241
242 if (stat(dlpaths[j], &sb) != -1) {
243 T_QUIET;
244 T_ASSERT_POSIX_SUCCESS(dlpath_fd = open(dlpaths[j], O_RDONLY), NULL);
245 T_QUIET;
246 T_ASSERT_POSIX_SUCCESS(fcntl(dlpath_fd, F_GETPATH, &path), "path is %s, iteration number is %d and path number is %d", dlpaths[j], i + 1, j + 1);
247 T_QUIET;
248 T_ASSERT_POSIX_SUCCESS(close(dlpath_fd), NULL);
249 num_getpaths++;
250 }
251 }
252 }
253 T_LOG("Num getpaths done = %d", num_getpaths);
254
255 current_num = get_sysctl_int("vfs.vnstats.num_vnodes");
256 T_LOG("num vnodes after vnoder: %d", current_num);
257
258 T_QUIET;
259 T_ASSERT_GT(current_num, maxvnodes,
260 "vnode maximum should increase under vnode presssure");
261
262 T_LOG("Killing vnoder (pid %d) to free up held vnodes", vnoder_pid);
263 T_QUIET;
264 T_ASSERT_POSIX_SUCCESS(write(sock_fd[0], "done", sizeof("done")), NULL);
265 kill(vnoder_pid, SIGKILL);
266 vnoder_pid = -1;
267
268 T_LOG("Waiting up to %ds for vnodes to be deallocated", timeout);
269 for (i = 0; i < timeout; i++) {
270 sleep(1);
271 deallocateable_vnodes = get_sysctl_int(
272 "vfs.vnstats.num_deallocable_vnodes");
273 deallocateable_busy_vnodes = get_sysctl_int(
274 "vfs.vnstats.num_deallocable_busy_vnodes");
275
276 T_LOG("deallocateable_vnodes after %d second%s : %d",
277 i + 1, (i == 0) ? "" : "s", deallocateable_vnodes);
278 T_LOG("deallocateable_busy_vnodes after %d second%s : %d",
279 i + 1, (i == 0) ? "" : "s", deallocateable_busy_vnodes);
280
281 if (deallocateable_vnodes < vnode_delta) {
282 break;
283 }
284 /* This can happen because we don't fetch atomically */
285 if (deallocateable_busy_vnodes > deallocateable_vnodes) {
286 deallocateable_busy_vnodes = deallocateable_vnodes;
287 }
288
289 if ((i == (timeout - 1)) &&
290 ((deallocateable_vnodes - deallocateable_busy_vnodes) < vnode_delta)) {
291 break;
292 }
293 }
294
295 T_QUIET;
296 T_ASSERT_NE(i, timeout, "Deallocateable vnodes should drop in under %ds",
297 timeout);
298
299 current_num = get_sysctl_int("vfs.vnstats.num_vnodes");
300 T_LOG("num vnodes after killing vnoder: %d", current_num);
301
302 T_QUIET;
303 T_ASSERT_LE(current_num, maxvnodes + deallocateable_busy_vnodes + vnode_delta,
304 "vnode maximum should be within %d of the initial maximum",
305 vnode_delta);
306 }
307