1 /*
2 * This file and its contents are supplied under the terms of the
3 * Common Development and Distribution License ("CDDL"), version 1.0.
4 * You may only use this file in accordance with the terms of version
5 * 1.0 of the CDDL.
6 *
7 * A full copy of the text of the CDDL should have accompanied this
8 * source. A copy of the CDDL is also available via the Internet at
9 * http://www.illumos.org/license/CDDL.
10 */
11
12 /*
13 * Copyright 2024 Oxide Computer Company
14 */
15
16 /*
17 * Basic tests for statvfs and fstatvfs. In particular we want to verify the
18 * following:
19 *
20 * - We can generate basic statvfs(2) errors like ENOENT, ENOTDIR, and EFAULT.
21 * - We can generate basic fstatvfs(2) errors like EBADF and EFAULT.
22 * - statvfs and fstatvfs work on basic file systems like /, ctfs, bootfs,
23 * objfs, procfs, tmpfs, etc. Additional paths will be allowed on the command
24 * line for this.
25 * - fstatvfs works on sockets and devices, but not pipes
26 */
27
28 #include <stdlib.h>
29 #include <sys/types.h>
30 #include <sys/statvfs.h>
31 #include <stdbool.h>
32 #include <err.h>
33 #include <string.h>
34 #include <errno.h>
35 #include <unistd.h>
36 #include <sys/mman.h>
37 #include <sys/debug.h>
38 #include <sys/sysmacros.h>
39 #include <fcntl.h>
40 #include <sys/socket.h>
41 #include <port.h>
42 #include <door.h>
43
44 static bool
statvfs_fail(const char * path,int exp,struct statvfs * svp)45 statvfs_fail(const char *path, int exp, struct statvfs *svp)
46 {
47 struct statvfs st;
48
49 if (svp == NULL) {
50 svp = &st;
51 }
52
53 if (statvfs(path, svp) == 0) {
54 warnx("TEST FAILED: statvfs on %s passed, but expected %s",
55 path, strerrorname_np(exp));
56 return (false);
57 }
58
59 if (errno != exp) {
60 warnx("TEST FAILED: statvfs on %s returned wrong errno: "
61 "expected %s, found %s", path, strerrorname_np(exp),
62 strerrorname_np(errno));
63 return (false);
64 }
65
66 (void) printf("TEST PASSED: statvfs on %s correctly returned %s\n",
67 path, strerrorname_np(exp));
68 return (true);
69 }
70
71 static bool
statvfs_pass(const char * path,const char * fs)72 statvfs_pass(const char *path, const char *fs)
73 {
74 struct statvfs sv;
75
76 if (statvfs(path, &sv) != 0) {
77 warnx("TEST FAILED: statvfs on %s failed with %s, but "
78 "expected success", path, strerrorname_np(errno));
79 return (false);
80 }
81
82 (void) printf("TEST PASSED: statvfs on %s worked\n", path);
83 if (fs == NULL) {
84 return (true);
85 }
86
87 if (strcmp(sv.f_basetype, fs) != 0) {
88 warnx("TEST FAILED: statvfs on %s has wrong fs: expected %s, "
89 "found %s", path, fs, sv.f_basetype);
90 return (false);
91 }
92
93 (void) printf("TEST PASSED: statvfs on %s correctly indicated fs %s\n",
94 path, fs);
95 return (true);
96 }
97
98 typedef struct {
99 const char *sp_path;
100 const char *sp_fs;
101 int sp_ret;
102 } statvfs_pass_t;
103
104 static const statvfs_pass_t statvfs_passes[] = {
105 { "/", NULL },
106 { "/usr/lib/libc.so.1", NULL },
107 { "/var/run", "tmpfs" },
108 { "/etc/svc/volatile", "tmpfs" },
109 { "/system/boot", "bootfs" },
110 { "/system/contract", "ctfs" },
111 { "/system/object", "objfs" },
112 { "/dev/fd", "fd" },
113 { "/etc/mnttab", "mntfs" },
114 { "/dev/net", "dev" },
115 /* This is a symlink in the GZ to /devices */
116 { "/dev/zero", "devfs" },
117 { "/devices/pseudo", "devfs" },
118 { "/etc/dfs/sharetab", "sharefs" },
119 { "/proc/self/psinfo", "proc" },
120 { "/var/run/name_service_door", "namefs" }
121 };
122
123 typedef struct fstatvfs_test {
124 int (*ft_open)(const struct fstatvfs_test *);
125 const char *ft_path;
126 const char *ft_fs;
127 int ft_ret;
128 } fstatvfs_test_t;
129
130 static int
statvfs_open_file(const fstatvfs_test_t * test)131 statvfs_open_file(const fstatvfs_test_t *test)
132 {
133 int fd = open(test->ft_path, O_RDONLY);
134 if (fd < 0) {
135 err(EXIT_FAILURE, "TEST FAILED: failed to open file %s",
136 test->ft_path);
137 }
138
139 return (fd);
140 }
141
142 static int
statvfs_open_socket(const fstatvfs_test_t * test)143 statvfs_open_socket(const fstatvfs_test_t *test)
144 {
145 struct sockaddr_in in;
146 int fd = socket(PF_INET, SOCK_STREAM, 0);
147 if (fd < 0) {
148 err(EXIT_FAILURE, "TEST FAILED: failed to create basic "
149 "socket");
150 }
151
152 (void) memset(&in, 0, sizeof (in));
153 if (bind(fd, (struct sockaddr *)&in, sizeof (in)) != 0) {
154 err(EXIT_FAILURE, "TEST FAILED: failed to bind socket");
155 }
156
157 return (fd);
158 }
159
160 static int
statvfs_open_uds(const fstatvfs_test_t * test)161 statvfs_open_uds(const fstatvfs_test_t *test)
162 {
163 int fd = socket(PF_UNIX, SOCK_STREAM, 0);
164 if (fd < 0) {
165 err(EXIT_FAILURE, "TEST FAILED: failed to create UDS");
166 }
167
168 return (fd);
169 }
170
171 static int
statvfs_open_pipe(const fstatvfs_test_t * test)172 statvfs_open_pipe(const fstatvfs_test_t *test)
173 {
174 int fds[2];
175
176 if (pipe(fds) != 0) {
177 err(EXIT_FAILURE, "TEST FAILED: failed to create pipe");
178 }
179
180 VERIFY0(close(fds[1]));
181 return (fds[0]);
182 }
183
184 static int
statvfs_open_negfd(const fstatvfs_test_t * test)185 statvfs_open_negfd(const fstatvfs_test_t *test)
186 {
187 return (-1);
188 }
189
190 static int
statvfs_open_bigfd(const fstatvfs_test_t * test)191 statvfs_open_bigfd(const fstatvfs_test_t *test)
192 {
193 return (0x7777);
194 }
195
196 static int
statvfs_open_portfs(const fstatvfs_test_t * test)197 statvfs_open_portfs(const fstatvfs_test_t *test)
198 {
199 int fd = port_create();
200 if (fd < 0) {
201 err(EXIT_FAILURE, "TEST FAILED: failed to create event port");
202 }
203
204 return (fd);
205 }
206
207 static void
statvfs_close_door(void * cookie,char * arg,size_t size,door_desc_t * dp,uint_t ndesc)208 statvfs_close_door(void *cookie, char *arg, size_t size, door_desc_t *dp,
209 uint_t ndesc)
210 {
211 (void) door_return(NULL, 0, NULL, 0);
212 }
213
214 static int
statvfs_open_door(const fstatvfs_test_t * test)215 statvfs_open_door(const fstatvfs_test_t *test)
216 {
217 int fd = door_create(statvfs_close_door, NULL, 0);
218 if (fd < 0) {
219 err(EXIT_FAILURE, "TEST FAILED: failed to create door");
220 }
221 return (fd);
222 }
223
224 static const fstatvfs_test_t fstatvfs_tests[] = {
225 { statvfs_open_socket, "localhost socket", "sockfs", 0 },
226 { statvfs_open_uds, "UDS socket", "sockfs", 0 },
227 { statvfs_open_pipe, "pipe", NULL, ENOSYS },
228 { statvfs_open_file, "/dev/tcp", NULL, ENOSYS },
229 { statvfs_open_negfd, "bad fd (-1)", NULL, EBADF },
230 { statvfs_open_negfd, "bad fd (-1)", NULL, EBADF },
231 { statvfs_open_bigfd, "bad fd (0x7777)", NULL, EBADF },
232 { statvfs_open_portfs, "event port", NULL, ENOSYS },
233 { statvfs_open_door, "door server", NULL, ENOSYS }
234 };
235
236 static bool
fstatvfs_test(const fstatvfs_test_t * test)237 fstatvfs_test(const fstatvfs_test_t *test)
238 {
239 struct statvfs sv;
240 int ret, fd, e;
241
242 /*
243 * Some tests will specifically use a bad fd value trying to get EBADF.
244 * In those cases don't try to close the fd again.
245 */
246 fd = test->ft_open(test);
247 ret = fstatvfs(fd, &sv);
248 e = errno;
249 if (test->ft_ret != EBADF) {
250 VERIFY0(close(fd));
251 }
252
253 if (ret != 0) {
254 if (test->ft_ret == 0) {
255 warnx("TEST FAILED: fstatvfs on %s failed with %s, but "
256 "expected success", test->ft_path,
257 strerrorname_np(errno));
258 return (false);
259 }
260
261 if (e != test->ft_ret) {
262 warnx("TEST FAILED: fstatvfs on %s returned wrong "
263 "errno: expected %s, found %s", test->ft_path,
264 strerrorname_np(test->ft_ret), strerrorname_np(e));
265 return (false);
266 }
267
268 (void) printf("TEST PASSED: fstatvfs on %s correctly failed "
269 "with %s\n", test->ft_path, strerrorname_np(test->ft_ret));
270 return (true);
271 }
272
273 if (test->ft_ret != 0) {
274 warnx("TEST FAILED: fstatvfs on %s passed, but expected %s",
275 test->ft_path, strerrorname_np(test->ft_ret));
276 return (false);
277 }
278
279 (void) printf("TEST PASSED: fstatvfs on %s worked\n", test->ft_path);
280 if (test->ft_fs == NULL) {
281 return (true);
282 }
283
284 if (strcmp(sv.f_basetype, test->ft_fs) != 0) {
285 warnx("TEST FAILED: fstatvfs on %s has wrong fs: expected %s, "
286 "found %s", test->ft_path, test->ft_fs, sv.f_basetype);
287 return (false);
288 }
289
290 (void) printf("TEST PASSED: fstatvfs on %s correctly indicated fs %s\n",
291 test->ft_path, test->ft_fs);
292 return (true);
293 }
294
295 int
main(void)296 main(void)
297 {
298 int ret = EXIT_SUCCESS;
299 void *unmap;
300 long page;
301
302 page = sysconf(_SC_PAGESIZE);
303 VERIFY3S(page, >=, sizeof (struct statvfs));
304 unmap = mmap(NULL, page, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0);
305 if (unmap == MAP_FAILED) {
306 err(EXIT_FAILURE, "INTERNAL TEST FAILURE: failed to mmap our "
307 "empty page");
308 }
309
310 if (!statvfs_fail("/elbe12th!", ENOENT, NULL)) {
311 ret = EXIT_FAILURE;
312 }
313
314 if (!statvfs_fail("/usr/sbin/dtrace/wait", ENOTDIR, NULL)) {
315 ret = EXIT_FAILURE;
316 }
317
318 if (!statvfs_fail("/", EFAULT, unmap)) {
319 ret = EXIT_FAILURE;
320 }
321
322 /*
323 * Each passing statvfs test should be a passing fstatvfs test as well.
324 */
325 for (size_t i = 0; i < ARRAY_SIZE(statvfs_passes); i++) {
326 fstatvfs_test_t ft;
327
328 if (!statvfs_pass(statvfs_passes[i].sp_path,
329 statvfs_passes[i].sp_fs)) {
330 ret = EXIT_FAILURE;
331 }
332
333 ft.ft_open = statvfs_open_file;
334 ft.ft_path = statvfs_passes[i].sp_path;
335 ft.ft_fs = statvfs_passes[i].sp_fs;
336 ft.ft_ret = 0;
337
338 if (!fstatvfs_test(&ft)) {
339 ret = EXIT_FAILURE;
340 }
341 }
342
343 for (size_t i = 0; i < ARRAY_SIZE(fstatvfs_tests); i++) {
344 if (!fstatvfs_test(&fstatvfs_tests[i])) {
345 ret = EXIT_FAILURE;
346 }
347 }
348
349 if (ret == EXIT_SUCCESS) {
350 (void) printf("All tests completed successfully\n");
351 }
352 return (ret);
353 }
354