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