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