xref: /illumos-gate/usr/src/test/os-tests/tests/vfs/statvfs.c (revision 4763305e3243687c189d755d737d52205b2614ed)
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
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
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
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
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
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
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
185 statvfs_open_negfd(const fstatvfs_test_t *test)
186 {
187 	return (-1);
188 }
189 
190 static int
191 statvfs_open_bigfd(const fstatvfs_test_t *test)
192 {
193 	return (0x7777);
194 }
195 
196 static int
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
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
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
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
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