]>
Commit | Line | Data |
---|---|---|
5ba3f43e A |
1 | #ifdef T_NAMESPACE |
2 | #undef T_NAMESPACE | |
3 | #endif | |
4 | #include <darwintest.h> | |
5 | #include <darwintest_utils.h> | |
6 | ||
7 | #include <stdlib.h> | |
8 | #include <unistd.h> | |
9 | #include <fcntl.h> | |
10 | #include <System/sys/fsctl.h> | |
11 | #include <paths.h> | |
12 | ||
13 | static char *mktempdir(void); | |
14 | static char *mktempmount(void); | |
15 | ||
16 | #ifndef TEST_UNENTITLED | |
17 | static int system_legal(const char *command); | |
18 | static char *mkramdisk(void); | |
19 | static uint64_t time_for_read(int fd, const char *expected); | |
20 | static void perf_setup(char **path, int *fd); | |
21 | ||
22 | #define READSIZE 1024L | |
23 | #endif /* !TEST_UNENTITLED */ | |
24 | ||
25 | T_GLOBAL_META( | |
26 | T_META_NAMESPACE("xnu.vfs.dmc"), | |
27 | T_META_ASROOT(true) | |
28 | ); | |
29 | ||
30 | #pragma mark Entitled Tests | |
31 | ||
32 | #ifndef TEST_UNENTITLED | |
33 | T_DECL(fsctl_get_uninitialized, | |
34 | "Initial fsctl.get should return zeros", | |
35 | T_META_ASROOT(false)) | |
36 | { | |
37 | int err; | |
38 | char *mount_path; | |
39 | disk_conditioner_info info = {0}; | |
40 | disk_conditioner_info expected_info = {0}; | |
41 | ||
42 | T_SETUPBEGIN; | |
43 | mount_path = mktempmount(); | |
44 | T_SETUPEND; | |
45 | ||
46 | info.enabled = true; | |
47 | info.is_ssd = true; | |
48 | err = fsctl(mount_path, DISK_CONDITIONER_IOC_GET, &info, 0); | |
49 | T_WITH_ERRNO; | |
50 | T_ASSERT_EQ_INT(0, err, "fsctl(DISK_CONDITIONER_IOC_GET)"); | |
51 | ||
52 | err = memcmp(&info, &expected_info, sizeof(info)); | |
53 | T_ASSERT_EQ_INT(0, err, "initial DMC info is zeroed"); | |
54 | } | |
55 | ||
56 | T_DECL(fsctl_set, | |
57 | "fsctl.set should succeed and fsctl.get should verify") | |
58 | { | |
59 | int err; | |
60 | char *mount_path; | |
61 | disk_conditioner_info info = {0}; | |
62 | disk_conditioner_info expected_info = {0}; | |
63 | ||
64 | T_SETUPBEGIN; | |
65 | mount_path = mktempmount(); | |
66 | T_SETUPEND; | |
67 | ||
68 | info.enabled = 1; | |
69 | info.access_time_usec = 10; | |
70 | info.read_throughput_mbps = 40; | |
71 | info.write_throughput_mbps = 40; | |
72 | info.is_ssd = 0; | |
73 | expected_info = info; | |
74 | ||
75 | err = fsctl(mount_path, DISK_CONDITIONER_IOC_SET, &info, 0); | |
76 | T_WITH_ERRNO; | |
77 | T_ASSERT_EQ_INT(0, err, "fsctl(DISK_CONDITIONER_IOC_SET)"); | |
78 | ||
79 | err = fsctl(mount_path, DISK_CONDITIONER_IOC_GET, &info, 0); | |
80 | T_WITH_ERRNO; | |
81 | T_ASSERT_EQ_INT(0, err, "fsctl(DISK_CONDITIONER_IOC_GET) after SET"); | |
82 | ||
83 | err = memcmp(&info, &expected_info, sizeof(info)); | |
84 | T_ASSERT_EQ_INT(0, err, "fsctl.get is the info configured by fsctl.set"); | |
85 | } | |
86 | ||
87 | T_DECL(fsctl_get_nonroot, | |
88 | "fsctl.get should not require root", | |
89 | T_META_ASROOT(false)) | |
90 | { | |
91 | int err; | |
92 | char *mount_path; | |
93 | disk_conditioner_info info; | |
94 | ||
95 | T_SETUPBEGIN; | |
96 | // make sure we're not root | |
97 | if (0 == geteuid()) { | |
98 | seteuid(5000); | |
99 | } | |
100 | ||
101 | mount_path = mktempmount(); | |
102 | T_SETUPEND; | |
103 | ||
104 | err = fsctl(mount_path, DISK_CONDITIONER_IOC_GET, &info, 0); | |
105 | T_WITH_ERRNO; | |
106 | T_ASSERT_EQ_INT(0, err, "fsctl.get without root"); | |
107 | } | |
108 | ||
109 | T_DECL(fsctl_set_nonroot, | |
110 | "fsctl.set should require root", | |
111 | T_META_ASROOT(false)) | |
112 | { | |
113 | int err; | |
114 | char *mount_path; | |
115 | disk_conditioner_info info = {0}; | |
116 | disk_conditioner_info expected_info = {0}; | |
117 | ||
118 | T_SETUPBEGIN; | |
119 | // make sure we're not root | |
120 | if (0 == geteuid()) { | |
121 | seteuid(5000); | |
122 | } | |
123 | ||
124 | mount_path = mktempmount(); | |
125 | T_SETUPEND; | |
126 | ||
127 | // save original info | |
128 | err = fsctl(mount_path, DISK_CONDITIONER_IOC_GET, &expected_info, 0); | |
129 | T_WITH_ERRNO; | |
130 | T_ASSERT_EQ_INT(0, err, "Get original DMC info"); | |
131 | ||
132 | info.enabled = 1; | |
133 | info.access_time_usec = 10; | |
134 | err = fsctl(mount_path, DISK_CONDITIONER_IOC_SET, &info, 0); | |
135 | T_WITH_ERRNO; | |
136 | T_ASSERT_NE_INT(0, err, "fsctl.set returns error without root"); | |
137 | ||
138 | err = fsctl(mount_path, DISK_CONDITIONER_IOC_GET, &info, 0); | |
139 | T_WITH_ERRNO; | |
140 | T_ASSERT_EQ_INT(0, err, "fsctl.get after nonroot fsctl.set"); | |
141 | ||
142 | err = memcmp(&info, &expected_info, sizeof(info)); | |
143 | T_ASSERT_EQ_INT(0, err, "fsctl.set should not change info without root"); | |
144 | } | |
145 | ||
146 | T_DECL(fsctl_delays, | |
147 | "Validate I/O delays when DMC is enabled") | |
148 | { | |
149 | char *path; | |
150 | int fd; | |
151 | int err; | |
152 | uint64_t elapsed_nsec, expected_nsec; | |
153 | disk_conditioner_info info; | |
154 | char buf[READSIZE]; | |
155 | ||
156 | T_SETUPBEGIN; | |
157 | perf_setup(&path, &fd); | |
158 | memset(buf, 0xFF, sizeof(buf)); | |
159 | T_ASSERT_EQ_LONG((long)sizeof(buf), write(fd, buf, sizeof(buf)), "write random data to temp file"); | |
160 | fcntl(fd, F_FULLFSYNC); | |
161 | T_SETUPEND; | |
162 | ||
163 | expected_nsec = NSEC_PER_SEC / 2; | |
164 | ||
165 | // measure delay before setting parameters (should be none) | |
166 | elapsed_nsec = time_for_read(fd, buf); | |
167 | T_ASSERT_LT_ULLONG(elapsed_nsec, expected_nsec, "DMC disabled read(%ld) from %s is reasonably fast", READSIZE, path); | |
168 | ||
169 | // measure delay after setting parameters | |
170 | info.enabled = 1; | |
171 | info.access_time_usec = expected_nsec / NSEC_PER_USEC; | |
172 | info.read_throughput_mbps = 40; | |
173 | info.write_throughput_mbps = 40; | |
174 | info.is_ssd = 1; // is_ssd will ensure we get constant access_time delays rather than scaled | |
175 | err = fsctl(path, DISK_CONDITIONER_IOC_SET, &info, 0); | |
176 | T_WITH_ERRNO; | |
177 | T_ASSERT_EQ_INT(0, err, "fsctl(DISK_CONDITIONER_IOC_SET) delay"); | |
178 | ||
179 | elapsed_nsec = time_for_read(fd, buf); | |
180 | T_ASSERT_GT_ULLONG(elapsed_nsec, expected_nsec, "DMC enabled read(%ld) from %s is at least the expected delay", READSIZE, path); | |
181 | T_ASSERT_LT_ULLONG(elapsed_nsec, 2 * expected_nsec, "DMC enabled read(%ld) from %s is no more than twice the expected delay", READSIZE, path); | |
182 | ||
183 | // measure delay after resetting parameters (should be none) | |
184 | info.enabled = 0; | |
185 | err = fsctl(path, DISK_CONDITIONER_IOC_SET, &info, 0); | |
186 | T_WITH_ERRNO; | |
187 | T_ASSERT_EQ_INT(0, err, "fsctl(DISK_CONDITIONER_IOC_SET) reset delay"); | |
188 | ||
189 | usleep(USEC_PER_SEC / 2); // might still be other I/O inflight | |
190 | elapsed_nsec = time_for_read(fd, buf); | |
191 | T_ASSERT_LT_ULLONG(elapsed_nsec, expected_nsec, "After disabling DMC read(%ld) from %s is reasonably fast", READSIZE, path); | |
192 | } | |
193 | ||
194 | #else /* TEST_UNENTITLED */ | |
195 | ||
196 | #pragma mark Unentitled Tests | |
197 | ||
198 | T_DECL(fsctl_get_unentitled, | |
199 | "fsctl.get should not require entitlement") | |
200 | { | |
201 | int err; | |
202 | char *mount_path; | |
203 | disk_conditioner_info info; | |
204 | ||
205 | T_SETUPBEGIN; | |
206 | mount_path = mktempmount(); | |
207 | T_SETUPEND; | |
208 | ||
209 | err = fsctl(mount_path, DISK_CONDITIONER_IOC_GET, &info, 0); | |
210 | T_WITH_ERRNO; | |
211 | T_ASSERT_EQ_INT(0, err, "fsctl.get without entitlement"); | |
212 | } | |
213 | ||
214 | T_DECL(fsctl_set_unentitled, | |
215 | "fsctl.set should require entitlement") | |
216 | { | |
217 | int err; | |
218 | char *mount_path; | |
219 | disk_conditioner_info info = {0}; | |
220 | disk_conditioner_info expected_info = {0}; | |
221 | ||
222 | T_SETUPBEGIN; | |
223 | mount_path = mktempmount(); | |
224 | T_SETUPEND; | |
225 | ||
226 | // save original info | |
227 | err = fsctl(mount_path, DISK_CONDITIONER_IOC_GET, &expected_info, 0); | |
228 | T_WITH_ERRNO; | |
229 | T_ASSERT_EQ_INT(0, err, "Get original DMC info"); | |
230 | ||
231 | info.enabled = 1; | |
232 | info.access_time_usec = 10; | |
233 | err = fsctl(mount_path, DISK_CONDITIONER_IOC_SET, &info, 0); | |
234 | T_WITH_ERRNO; | |
235 | T_ASSERT_NE_INT(0, err, "fsctl.set returns error without entitlement"); | |
236 | ||
237 | err = fsctl(mount_path, DISK_CONDITIONER_IOC_GET, &info, 0); | |
238 | T_WITH_ERRNO; | |
239 | T_ASSERT_EQ_INT(0, err, "fsctl.get after unentitled fsctl.set"); | |
240 | ||
241 | err = memcmp(&info, &expected_info, sizeof(info)); | |
242 | T_ASSERT_EQ_INT(0, err, "fsctl.set should not change info without entitlement"); | |
243 | } | |
244 | ||
245 | #endif /* TEST_UNENTITLED */ | |
246 | ||
247 | #pragma mark Helpers | |
248 | ||
249 | static char *mktempdir(void) { | |
250 | char *path = malloc(PATH_MAX); | |
251 | strcpy(path, "/tmp/dmc.XXXXXXXX"); | |
252 | atexit_b(^{ free(path); }); | |
253 | ||
254 | // create a temporary mount to run the fsctl on | |
255 | T_WITH_ERRNO; | |
256 | T_ASSERT_NOTNULL(mkdtemp(path), "Create temporary directory"); | |
257 | atexit_b(^{ remove(path); }); | |
258 | ||
259 | return path; | |
260 | } | |
261 | ||
262 | /* | |
263 | * Return the path to a temporary mount | |
264 | * with no usable filesystem but still | |
265 | * can be configured by the disk conditioner | |
266 | * | |
267 | * Faster than creating a ram disk to test with | |
268 | * when access to the filesystem is not necessary | |
269 | */ | |
270 | static char *mktempmount(void) { | |
271 | char *mount_path = mktempdir(); | |
272 | ||
273 | T_WITH_ERRNO; | |
274 | T_ASSERT_EQ_INT(0, mount("devfs", mount_path, MNT_RDONLY, NULL), "Create temporary devfs mount"); | |
275 | atexit_b(^{ unmount(mount_path, MNT_FORCE); }); | |
276 | ||
277 | return mount_path; | |
278 | } | |
279 | ||
280 | #ifndef TEST_UNENTITLED | |
281 | ||
282 | /* | |
283 | * Wrapper around dt_launch_tool/dt_waitpid | |
284 | * that works like libc:system() | |
285 | */ | |
286 | static int system_legal(const char *command) { | |
287 | pid_t pid = -1; | |
288 | int exit_status = 0; | |
289 | const char *argv[] = { | |
290 | _PATH_BSHELL, | |
291 | "-c", | |
292 | command, | |
293 | NULL | |
294 | }; | |
295 | ||
296 | int rc = dt_launch_tool(&pid, (char **)(void *)argv, false, NULL, NULL); | |
297 | if (rc != 0) { | |
298 | return -1; | |
299 | } | |
300 | if (!dt_waitpid(pid, &exit_status, NULL, 30)) { | |
301 | if (exit_status != 0) { | |
302 | return exit_status; | |
303 | } | |
304 | return -1; | |
305 | } | |
306 | ||
307 | return exit_status; | |
308 | } | |
309 | ||
310 | /* | |
311 | * Return the path to a temporary mount | |
312 | * that contains a usable HFS+ filesystem | |
313 | * mounted via a ram disk | |
314 | */ | |
315 | static char *mkramdisk(void) { | |
316 | char cmd[1024]; | |
317 | char *mount_path = mktempdir(); | |
318 | char *dev_disk_file = malloc(256); | |
319 | atexit_b(^{ free(dev_disk_file); }); | |
320 | strcpy(dev_disk_file, "/tmp/dmc.ramdisk.XXXXXXXX"); | |
321 | ||
322 | T_WITH_ERRNO; | |
323 | T_ASSERT_NOTNULL(mktemp(dev_disk_file), "Create temporary file to store dev disk for ramdisk"); | |
324 | atexit_b(^{ remove(dev_disk_file); }); | |
325 | ||
326 | // create the RAM disk device | |
327 | snprintf(cmd, sizeof(cmd), "hdik -nomount ram://10000 > %s", dev_disk_file); | |
328 | T_ASSERT_EQ_INT(0, system_legal(cmd), "Create ramdisk"); | |
329 | ||
330 | atexit_b(^{ | |
331 | char eject_cmd[1024]; | |
332 | unmount(mount_path, MNT_FORCE); | |
333 | snprintf(eject_cmd, sizeof(eject_cmd), "hdik -e `cat %s`", dev_disk_file); | |
334 | system_legal(eject_cmd); | |
335 | remove(dev_disk_file); | |
336 | }); | |
337 | ||
338 | // initialize as an HFS volume | |
339 | snprintf(cmd, sizeof(cmd), "newfs_hfs `cat %s`", dev_disk_file); | |
340 | T_ASSERT_EQ_INT(0, system_legal(cmd), "Initialize ramdisk as HFS"); | |
341 | ||
342 | // mount it | |
343 | snprintf(cmd, sizeof(cmd), "mount -t hfs `cat %s` %s", dev_disk_file, mount_path); | |
344 | T_ASSERT_EQ_INT(0, system_legal(cmd), "Mount ramdisk"); | |
345 | ||
346 | return mount_path; | |
347 | } | |
348 | ||
349 | static uint64_t time_for_read(int fd, const char *expected) { | |
350 | int err; | |
351 | ssize_t ret; | |
352 | char buf[READSIZE]; | |
353 | uint64_t start, stop; | |
354 | ||
355 | bzero(buf, sizeof(buf)); | |
356 | lseek(fd, 0, SEEK_SET); | |
357 | ||
358 | start = dt_nanoseconds(); | |
359 | ret = read(fd, buf, READSIZE); | |
360 | stop = dt_nanoseconds(); | |
361 | ||
362 | T_ASSERT_GE_LONG(ret, 0L, "read from temporary file"); | |
363 | T_ASSERT_EQ_LONG(ret, READSIZE, "read %ld bytes from temporary file", READSIZE); | |
364 | err = memcmp(buf, expected, sizeof(buf)); | |
365 | T_ASSERT_EQ_INT(0, err, "read expected contents from temporary file"); | |
366 | ||
367 | return (stop - start); | |
368 | } | |
369 | ||
370 | static void perf_setup(char **path, int *fd) { | |
371 | int temp_fd; | |
372 | char *temp_path; | |
373 | ||
374 | char *mount_path = mkramdisk(); | |
375 | temp_path = *path = malloc(PATH_MAX); | |
376 | snprintf(temp_path, PATH_MAX, "%s/dmc.XXXXXXXX", mount_path); | |
377 | atexit_b(^{ free(temp_path); }); | |
378 | ||
379 | T_ASSERT_NOTNULL(mktemp(temp_path), "Create temporary file"); | |
380 | atexit_b(^{ remove(temp_path); }); | |
381 | ||
382 | temp_fd = *fd = open(temp_path, O_RDWR | O_CREAT); | |
383 | T_WITH_ERRNO; | |
384 | T_ASSERT_GE_INT(temp_fd, 0, "Open temporary file for read/write"); | |
385 | atexit_b(^{ close(temp_fd); }); | |
386 | fcntl(temp_fd, F_NOCACHE, 1); | |
387 | } | |
388 | #endif /* !TEST_UNENTITLED */ |