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 * This provides a basic wrapper for tests around the syncfs(3C) operation. As
18 * it's difficult to inject I/O failures, we specifically test the following:
19 *
20 * - Verify that an invalid fd will result in EBADF
21 * - Use file systems that we know will never support syncfs() and get ENOSYS.
22 * For this we use bootfs, objfs, sockets, and related.
23 * - Attempt to find something that we know will support syncfs() and try to
24 * use it. This last one is the trickiest, we rely on the fact that we know
25 * /var/run will be a tmpfs, but allow additional paths to be specified on
26 * the command line to try.
27 */
28
29 #include <stdlib.h>
30 #include <err.h>
31 #include <unistd.h>
32 #include <stdbool.h>
33 #include <errno.h>
34 #include <string.h>
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <fcntl.h>
38 #include <sys/socket.h>
39 #include <sys/sysmacros.h>
40 #include <sys/debug.h>
41 #include <limits.h>
42 #include <port.h>
43
44 typedef struct syncfs_enosys {
45 int (*se_open)(const struct syncfs_enosys *);
46 const char *se_path;
47 } syncfs_enosys_t;
48
49 static int
syncfs_open_file(const syncfs_enosys_t * test)50 syncfs_open_file(const syncfs_enosys_t *test)
51 {
52 int fd = open(test->se_path, O_RDONLY);
53 if (fd < 0) {
54 err(EXIT_FAILURE, "TEST FAILED: failed to open file %s",
55 test->se_path);
56 }
57
58 return (fd);
59 }
60
61 static int
syncfs_open_socket(const syncfs_enosys_t * test)62 syncfs_open_socket(const syncfs_enosys_t *test)
63 {
64 struct sockaddr_in in;
65 int fd = socket(PF_INET, SOCK_STREAM, 0);
66 if (fd < 0) {
67 err(EXIT_FAILURE, "TEST FAILED: failed to create basic "
68 "socket");
69 }
70
71 (void) memset(&in, 0, sizeof (in));
72 if (bind(fd, (struct sockaddr *)&in, sizeof (in)) != 0) {
73 err(EXIT_FAILURE, "TEST FAILED: failed to bind socket");
74 }
75
76 return (fd);
77 }
78
79 static int
syncfs_open_uds(const syncfs_enosys_t * test)80 syncfs_open_uds(const syncfs_enosys_t *test)
81 {
82 int fd = socket(PF_UNIX, SOCK_STREAM, 0);
83 if (fd < 0) {
84 err(EXIT_FAILURE, "TEST FAILED: failed to create UDS");
85 }
86
87 return (fd);
88 }
89
90 static int
syncfs_open_pipe(const syncfs_enosys_t * test)91 syncfs_open_pipe(const syncfs_enosys_t *test)
92 {
93 int fds[2];
94
95 if (pipe(fds) != 0) {
96 err(EXIT_FAILURE, "TEST FAILED: failed to create pipe");
97 }
98
99 VERIFY0(close(fds[1]));
100 return (fds[0]);
101 }
102
103 static int
syncfs_open_port(const syncfs_enosys_t * test)104 syncfs_open_port(const syncfs_enosys_t *test)
105 {
106 int fd = port_create();
107 if (fd < 0) {
108 err(EXIT_FAILURE, "TEST FAILED: failed to create event port");
109 }
110
111 return (fd);
112 }
113
114 static const syncfs_enosys_t syncfs_enosys[] = {
115 { syncfs_open_file, "/system/boot" },
116 { syncfs_open_file, "/system/object" },
117 { syncfs_open_file, "/proc/self/psinfo" },
118 { syncfs_open_file, "/dev/tcp" },
119 { syncfs_open_file, "/dev/null" },
120 { syncfs_open_file, "/dev/net" },
121 { syncfs_open_file, "/etc/dfs/sharetab" },
122 { syncfs_open_socket, "localhost socket" },
123 { syncfs_open_uds, "UDS socket" },
124 { syncfs_open_pipe, "pipe" },
125 { syncfs_open_file, "/var/run/name_service_door" },
126 { syncfs_open_port, "event port" },
127 };
128
129 static const int syncfs_badfs[] = { -1, STDERR_FILENO + 1, INT_MAX - 1,
130 0x7777, -0x7777 };
131
132 static bool
syncfs_fail(const char * desc,int fd,int exp_err)133 syncfs_fail(const char *desc, int fd, int exp_err)
134 {
135 int ret = syncfs(fd);
136 if (ret != -1) {
137 warnx("TEST FAILED: %s: syncfs succeeded, but expected "
138 "failure", desc);
139 return (false);
140 }
141
142 if (errno != exp_err) {
143 warnx("TEST FAILED: %s: syncfs returned %s, expected %s",
144 desc, strerrorname_np(ret), strerrorname_np(exp_err));
145 return (false);
146 }
147
148 (void) printf("TEST PASSED: %s\n", desc);
149 return (true);
150 }
151
152 static bool
syncfs_pass(const char * path)153 syncfs_pass(const char *path)
154 {
155 bool ret = true;
156 int fd = open(path, O_RDONLY);
157 if (fd < 0) {
158 err(EXIT_FAILURE, "failed to open %s", path);
159 }
160
161 if (syncfs(fd) != 0) {
162 warnx("TEST FAILED: syncfs failed with %s on %s",
163 strerrorname_np(errno), path);
164 ret = false;
165 } else {
166 (void) printf("TEST PASSED: syncfs returned 0 on %s\n", path);
167 }
168
169 VERIFY0(close(fd));
170 return (ret);
171 }
172
173 int
main(int argc,char * argv[])174 main(int argc, char *argv[])
175 {
176 int ret = EXIT_SUCCESS;
177
178 closefrom(STDERR_FILENO + 1);
179
180 for (size_t i = 0; i < ARRAY_SIZE(syncfs_badfs); i++) {
181 char msg[PATH_MAX];
182 (void) snprintf(msg, sizeof (msg), "Invalid file descriptor "
183 "returns EBADF (%zu)", i);
184 if (!syncfs_fail(msg, syncfs_badfs[i], EBADF)) {
185 ret = EXIT_FAILURE;
186 }
187 }
188
189 for (size_t i = 0; i < ARRAY_SIZE(syncfs_enosys); i++) {
190 char msg[PATH_MAX];
191 int fd = syncfs_enosys[i].se_open(&syncfs_enosys[i]);
192 (void) snprintf(msg, sizeof (msg), "Unsupported fs returns "
193 "ENOSYS: %s", syncfs_enosys[i].se_path);
194 if (!syncfs_fail(msg, fd, ENOSYS)) {
195 ret = EXIT_FAILURE;
196 }
197
198 VERIFY0(close(fd));
199 }
200
201 if (!syncfs_pass("/var/run")) {
202 ret = EXIT_FAILURE;
203 }
204
205 for (int i = 1; i < argc; i++) {
206 if (!syncfs_pass(argv[i])) {
207 ret = EXIT_FAILURE;
208 }
209 }
210
211 if (ret == EXIT_SUCCESS) {
212 (void) printf("All tests completed successfully\n");
213 }
214
215 return (ret);
216 }
217