Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/tests/sys/kern/fdgrowtable_test.c
39536 views
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/param.h>
29
#include <sys/filedesc.h>
30
#include <sys/queue.h>
31
#include <sys/sysctl.h>
32
#include <sys/user.h>
33
#include <sys/wait.h>
34
35
#include <atf-c.h>
36
#include <fcntl.h>
37
#include <signal.h>
38
#include <stdio.h>
39
#include <stdlib.h>
40
#include <string.h>
41
#include <unistd.h>
42
43
/* linked libraries */
44
#include <kvm.h>
45
#include <libutil.h>
46
#include <libprocstat.h>
47
#include <pthread.h>
48
49
/* test-case macro */
50
#define AFILE "afile"
51
52
/*
53
* The following macros, struct freetable, struct fdescenttbl0
54
* and struct filedesc0 are copied from sys/kern/kern_descrip.c
55
*/
56
#define NDFILE 20
57
#define NDSLOTSIZE sizeof(NDSLOTTYPE)
58
#define NDENTRIES (NDSLOTSIZE * __CHAR_BIT)
59
#define NDSLOT(x) ((x) / NDENTRIES)
60
#define NDBIT(x) ((NDSLOTTYPE)1 << ((x) % NDENTRIES))
61
#define NDSLOTS(x) (((x) + NDENTRIES - 1) / NDENTRIES)
62
63
struct freetable {
64
struct fdescenttbl *ft_table;
65
SLIST_ENTRY(freetable) ft_next;
66
};
67
68
struct fdescenttbl0 {
69
int fdt_nfiles;
70
struct filedescent fdt_ofiles[NDFILE];
71
};
72
73
struct filedesc0 {
74
struct filedesc fd_fd;
75
SLIST_HEAD(, freetable) fd_free;
76
struct fdescenttbl0 fd_dfiles;
77
NDSLOTTYPE fd_dmap[NDSLOTS(NDFILE)];
78
};
79
80
static void
81
openfiles(int n)
82
{
83
int i, fd;
84
85
ATF_REQUIRE((fd = open(AFILE, O_CREAT, 0644)) != -1);
86
close(fd);
87
for (i = 0; i < n; i++)
88
ATF_REQUIRE((fd = open(AFILE, O_RDONLY, 0644)) != -1);
89
}
90
91
/*
92
* Get a count of the old file descriptor tables on the freelist.
93
*/
94
static int
95
old_tables(kvm_t *kd, struct kinfo_proc *kp)
96
{
97
struct filedesc0 fdp0;
98
struct freetable *ft, tft;
99
int counter;
100
101
counter = 0;
102
103
ATF_REQUIRE(kvm_read(kd, (unsigned long) kp->ki_fd, &fdp0, sizeof(fdp0)) > 0);
104
105
SLIST_FOREACH(ft, &fdp0.fd_free, ft_next) {
106
ATF_REQUIRE(kvm_read(kd, (unsigned long) ft, &tft, sizeof(tft)) > 0 );
107
ft = &tft;
108
counter++;
109
}
110
111
return (counter);
112
}
113
114
/*
115
* The returning struct kinfo_proc stores kernel addresses that will be
116
* used by kvm_read to retrieve information for the current process.
117
*/
118
static struct kinfo_proc *
119
read_kinfo(kvm_t *kd)
120
{
121
struct kinfo_proc *kp;
122
int procs_found;
123
124
ATF_REQUIRE((kp = kvm_getprocs(kd, KERN_PROC_PID, (int) getpid(), &procs_found)) != NULL);
125
ATF_REQUIRE(procs_found == 1);
126
127
return (kp);
128
}
129
130
/*
131
* Test a single threaded process that doesn't have a shared
132
* file descriptor table. The old tables should be freed.
133
*/
134
ATF_TC(free_oldtables);
135
ATF_TC_HEAD(free_oldtables, tc)
136
{
137
atf_tc_set_md_var(tc, "require.user", "root");
138
}
139
140
ATF_TC_BODY(free_oldtables, tc)
141
{
142
kvm_t *kd;
143
struct kinfo_proc *kp;
144
145
ATF_REQUIRE((kd = kvm_open(NULL, NULL, NULL, O_RDONLY, NULL)) != NULL);
146
openfiles(128);
147
kp = read_kinfo(kd);
148
ATF_CHECK(old_tables(kd,kp) == 0);
149
}
150
151
static _Noreturn void *
152
exec_thread(void *args)
153
{
154
for (;;)
155
sleep(1);
156
}
157
158
/*
159
* Test a process with two threads that doesn't have a shared file
160
* descriptor table. The old tables should not be freed.
161
*/
162
ATF_TC(oldtables_shared_via_threads);
163
ATF_TC_HEAD(oldtables_shared_via_threads, tc)
164
{
165
atf_tc_set_md_var(tc, "require.user", "root");
166
}
167
168
ATF_TC_BODY(oldtables_shared_via_threads, tc)
169
{
170
pid_t child;
171
kvm_t *kd;
172
struct kinfo_proc *kp;
173
pthread_t thread;
174
175
if ((child = rfork(RFPROC | RFCFDG)) > 0) {
176
pid_t wpid;
177
int status;
178
179
wpid = waitpid(child, &status, 0);
180
ATF_REQUIRE(wpid == child);
181
ATF_REQUIRE(WIFEXITED(status));
182
ATF_REQUIRE(WEXITSTATUS(status) == EXIT_SUCCESS);
183
return;
184
}
185
186
#define REQUIRE(expression) do { \
187
if (!(expression)) \
188
exit(EXIT_FAILURE); \
189
} while (0)
190
191
REQUIRE(child == 0);
192
193
REQUIRE((kd = kvm_open(NULL, NULL, NULL, O_RDONLY, NULL)) != NULL);
194
REQUIRE(pthread_create(&thread, NULL, exec_thread, NULL) == 0);
195
196
openfiles(128);
197
198
kp = read_kinfo(kd);
199
REQUIRE(kp->ki_numthreads > 1);
200
REQUIRE(old_tables(kd,kp) > 1);
201
202
REQUIRE(pthread_cancel(thread) == 0);
203
REQUIRE(pthread_join(thread, NULL) == 0);
204
#undef REQUIRE
205
206
exit(EXIT_SUCCESS);
207
}
208
209
/*
210
* Get the reference count of a file descriptor table.
211
*/
212
static int
213
filedesc_refcnt(kvm_t *kd, struct kinfo_proc *kp)
214
{
215
struct filedesc fdp;
216
217
ATF_REQUIRE(kvm_read(kd, (unsigned long) kp->ki_fd, &fdp, sizeof(fdp)) > 0);
218
219
return (fdp.fd_refcnt);
220
}
221
222
/*
223
* Test a single threaded process that shares a file descriptor
224
* table with another process. The old tables should not be freed.
225
*/
226
ATF_TC(oldtables_shared_via_process);
227
ATF_TC_HEAD(oldtables_shared_via_process, tc)
228
{
229
atf_tc_set_md_var(tc, "require.user", "root");
230
}
231
232
ATF_TC_BODY(oldtables_shared_via_process, tc)
233
{
234
kvm_t *kd;
235
struct kinfo_proc *kp;
236
int status;
237
pid_t child, wpid;
238
239
ATF_REQUIRE((kd = kvm_open(NULL, NULL, NULL, O_RDONLY, NULL)) != NULL);
240
241
/* share the file descriptor table */
242
ATF_REQUIRE((child = rfork(RFPROC)) != -1);
243
244
if (child == 0) {
245
openfiles(128);
246
raise(SIGSTOP);
247
exit(127);
248
}
249
250
/* let parent process open some files too */
251
openfiles(128);
252
253
/* get current status of child */
254
wpid = waitpid(child, &status, WUNTRACED);
255
ATF_REQUIRE(wpid == child);
256
257
/* child should be stopped */
258
ATF_REQUIRE(WIFSTOPPED(status));
259
260
/*
261
* We want to read kernel data
262
* before the child exits
263
* otherwise we'll lose a reference count
264
* to the file descriptor table
265
*/
266
kp = read_kinfo(kd);
267
268
ATF_CHECK(filedesc_refcnt(kd,kp) > 1);
269
ATF_CHECK(old_tables(kd,kp) > 1);
270
271
kill(child, SIGCONT);
272
273
/* child should have exited */
274
wpid = waitpid(child, &status, 0);
275
ATF_REQUIRE(wpid == child);
276
ATF_REQUIRE(WIFEXITED(status));
277
ATF_REQUIRE(WEXITSTATUS(status) == 127);
278
}
279
280
ATF_TP_ADD_TCS(tp)
281
{
282
ATF_TP_ADD_TC(tp, free_oldtables);
283
ATF_TP_ADD_TC(tp, oldtables_shared_via_threads);
284
ATF_TP_ADD_TC(tp, oldtables_shared_via_process);
285
return (atf_no_error());
286
}
287
288