xref: /freebsd/tests/sys/kern/fdgrowtable_test.c (revision 55141f2c8991b2a6adbf30bb0fe3e6cbc303f06d)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2020 Rob Wing
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
16  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
21  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
22  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
23  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
24  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
25  * SUCH DAMAGE.
26  */
27 
28 #include <sys/cdefs.h>
29 #include <sys/param.h>
30 #include <sys/filedesc.h>
31 #include <sys/queue.h>
32 #include <sys/sysctl.h>
33 #include <sys/user.h>
34 #include <sys/wait.h>
35 
36 #include <atf-c.h>
37 #include <fcntl.h>
38 #include <signal.h>
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <unistd.h>
43 
44 /* linked libraries */
45 #include <kvm.h>
46 #include <libutil.h>
47 #include <libprocstat.h>
48 #include <pthread.h>
49 
50 /* test-case macro */
51 #define AFILE "afile"
52 
53 /*
54  * The following macros, struct freetable, struct fdescenttbl0
55  * and struct filedesc0 are copied from sys/kern/kern_descrip.c
56  */
57 #define NDFILE		20
58 #define NDSLOTSIZE	sizeof(NDSLOTTYPE)
59 #define	NDENTRIES	(NDSLOTSIZE * __CHAR_BIT)
60 #define NDSLOT(x)	((x) / NDENTRIES)
61 #define NDBIT(x)	((NDSLOTTYPE)1 << ((x) % NDENTRIES))
62 #define	NDSLOTS(x)	(((x) + NDENTRIES - 1) / NDENTRIES)
63 
64 struct freetable {
65 	struct fdescenttbl *ft_table;
66 	SLIST_ENTRY(freetable) ft_next;
67 };
68 
69 struct fdescenttbl0 {
70 	int	fdt_nfiles;
71 	struct	filedescent fdt_ofiles[NDFILE];
72 };
73 
74 struct filedesc0 {
75 	struct filedesc fd_fd;
76 	SLIST_HEAD(, freetable) fd_free;
77 	struct	fdescenttbl0 fd_dfiles;
78 	NDSLOTTYPE fd_dmap[NDSLOTS(NDFILE)];
79 };
80 
81 static void
82 openfiles(int n)
83 {
84 	int i, fd;
85 
86 	ATF_REQUIRE((fd = open(AFILE, O_CREAT, 0644)) != -1);
87 	close(fd);
88 	for (i = 0; i < n; i++)
89 		ATF_REQUIRE((fd = open(AFILE, O_RDONLY, 0644)) != -1);
90 }
91 
92 /*
93  * Get a count of the old file descriptor tables on the freelist.
94  */
95 static int
96 old_tables(kvm_t *kd, struct kinfo_proc *kp)
97 {
98 	struct filedesc0 fdp0;
99 	struct freetable *ft, tft;
100 	int counter;
101 
102 	counter = 0;
103 
104 	ATF_REQUIRE(kvm_read(kd, (unsigned long) kp->ki_fd, &fdp0, sizeof(fdp0)) > 0);
105 
106 	SLIST_FOREACH(ft, &fdp0.fd_free, ft_next) {
107 		ATF_REQUIRE(kvm_read(kd, (unsigned long) ft, &tft, sizeof(tft)) > 0 );
108 		ft = &tft;
109 		counter++;
110 	}
111 
112 	return (counter);
113 }
114 
115 /*
116  *  The returning struct kinfo_proc stores kernel addresses that will be
117  *  used by kvm_read to retrieve information for the current process.
118  */
119 static struct kinfo_proc *
120 read_kinfo(kvm_t *kd)
121 {
122 	struct kinfo_proc *kp;
123 	int procs_found;
124 
125 	ATF_REQUIRE((kp = kvm_getprocs(kd, KERN_PROC_PID, (int) getpid(), &procs_found)) != NULL);
126 	ATF_REQUIRE(procs_found == 1);
127 
128 	return (kp);
129 }
130 
131 /*
132  * Test a single threaded process that doesn't have a shared
133  * file descriptor table. The old tables should be freed.
134  */
135 ATF_TC(free_oldtables);
136 ATF_TC_HEAD(free_oldtables, tc)
137 {
138 	atf_tc_set_md_var(tc, "require.user", "root");
139 }
140 
141 ATF_TC_BODY(free_oldtables, tc)
142 {
143 	kvm_t *kd;
144 	struct kinfo_proc *kp;
145 
146 	ATF_REQUIRE((kd = kvm_open(NULL, NULL, NULL, O_RDONLY, NULL)) != NULL);
147 	openfiles(128);
148 	kp = read_kinfo(kd);
149 	ATF_CHECK(old_tables(kd,kp) == 0);
150 }
151 
152 static _Noreturn void *
153 exec_thread(void *args)
154 {
155 	for (;;)
156 		sleep(1);
157 }
158 
159 /*
160  * Test a process with two threads that doesn't have a shared file
161  * descriptor table. The old tables should not be freed.
162  */
163 ATF_TC(oldtables_shared_via_threads);
164 ATF_TC_HEAD(oldtables_shared_via_threads, tc)
165 {
166 	atf_tc_set_md_var(tc, "require.user", "root");
167 }
168 
169 ATF_TC_BODY(oldtables_shared_via_threads, tc)
170 {
171 	kvm_t *kd;
172 	struct kinfo_proc *kp;
173 	pthread_t thread;
174 
175 	ATF_REQUIRE((kd = kvm_open(NULL, NULL, NULL, O_RDONLY, NULL)) != NULL);
176 	ATF_REQUIRE(pthread_create(&thread, NULL, exec_thread, NULL) == 0);
177 
178 	openfiles(128);
179 
180 	kp = read_kinfo(kd);
181 	ATF_CHECK(kp->ki_numthreads > 1);
182 	ATF_CHECK(old_tables(kd,kp) > 1);
183 
184 	ATF_REQUIRE(pthread_cancel(thread) == 0);
185 	ATF_REQUIRE(pthread_join(thread, NULL) == 0);
186 }
187 
188 /*
189  * Get the reference count of a file descriptor table.
190  */
191 static int
192 filedesc_refcnt(kvm_t *kd, struct kinfo_proc *kp)
193 {
194 	struct filedesc fdp;
195 
196 	ATF_REQUIRE(kvm_read(kd, (unsigned long) kp->ki_fd, &fdp, sizeof(fdp)) > 0);
197 
198 	return (fdp.fd_refcnt);
199 }
200 
201 /*
202  * Test a single threaded process that shares a file descriptor
203  * table with another process. The old tables should not be freed.
204  */
205 ATF_TC(oldtables_shared_via_process);
206 ATF_TC_HEAD(oldtables_shared_via_process, tc)
207 {
208 	atf_tc_set_md_var(tc, "require.user", "root");
209 }
210 
211 ATF_TC_BODY(oldtables_shared_via_process, tc)
212 {
213 	kvm_t *kd;
214 	struct kinfo_proc *kp;
215 	int status;
216 	pid_t child, wpid;
217 
218 	ATF_REQUIRE((kd = kvm_open(NULL, NULL, NULL, O_RDONLY, NULL)) != NULL);
219 
220 	/* share the file descriptor table */
221 	ATF_REQUIRE((child = rfork(RFPROC)) != -1);
222 
223 	if (child == 0) {
224 		openfiles(128);
225 		raise(SIGSTOP);
226 		exit(127);
227 	}
228 
229 	/* let parent process open some files too */
230 	openfiles(128);
231 
232 	/* get current status of child */
233 	wpid = waitpid(child, &status, WUNTRACED);
234 	ATF_REQUIRE(wpid == child);
235 
236 	/* child should be stopped */
237 	ATF_REQUIRE(WIFSTOPPED(status));
238 
239 	/*
240 	 * We want to read kernel data
241 	 * before the child exits
242 	 * otherwise we'll lose a reference count
243 	 * to the file descriptor table
244 	 */
245 	if (child != 0) {
246 		kp = read_kinfo(kd);
247 
248 		ATF_CHECK(filedesc_refcnt(kd,kp) > 1);
249 		ATF_CHECK(old_tables(kd,kp) > 1);
250 
251 		kill(child, SIGCONT);
252 	}
253 
254 	/* child should have exited */
255 	wpid = waitpid(child, &status, 0);
256 	ATF_REQUIRE(wpid == child);
257 	ATF_REQUIRE(WIFEXITED(status));
258 	ATF_REQUIRE(WEXITSTATUS(status) == 127);
259 }
260 
261 ATF_TP_ADD_TCS(tp)
262 {
263 	ATF_TP_ADD_TC(tp, free_oldtables);
264 	ATF_TP_ADD_TC(tp, oldtables_shared_via_threads);
265 	ATF_TP_ADD_TC(tp, oldtables_shared_via_process);
266 	return (atf_no_error());
267 }
268