4 #include <darwintest.h>
5 #include <darwintest_utils.h>
10 #include <System/sys/fsctl.h>
13 static char *mktempdir(void);
14 static char *mktempmount(void);
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
);
22 #define READSIZE 1024L
23 #endif /* !TEST_UNENTITLED */
26 T_META_NAMESPACE("xnu.vfs.dmc"),
28 T_META_RUN_CONCURRENTLY(true)
31 #pragma mark Entitled Tests
33 #ifndef TEST_UNENTITLED
34 T_DECL(fsctl_get_uninitialized
,
35 "Initial fsctl.get should return zeros",
40 disk_conditioner_info info
= {0};
41 disk_conditioner_info expected_info
= {0};
44 mount_path
= mktempmount();
49 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_GET
, &info
, 0);
51 T_ASSERT_EQ_INT(0, err
, "fsctl(DISK_CONDITIONER_IOC_GET)");
53 err
= memcmp(&info
, &expected_info
, sizeof(info
));
54 T_ASSERT_EQ_INT(0, err
, "initial DMC info is zeroed");
58 "fsctl.set should succeed and fsctl.get should verify")
62 disk_conditioner_info info
= {0};
63 disk_conditioner_info expected_info
= {0};
66 mount_path
= mktempmount();
70 info
.access_time_usec
= 10;
71 info
.read_throughput_mbps
= 40;
72 info
.write_throughput_mbps
= 40;
74 info
.ioqueue_depth
= 8;
81 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_SET
, &info
, 0);
83 T_ASSERT_EQ_INT(0, err
, "fsctl(DISK_CONDITIONER_IOC_SET)");
85 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_GET
, &info
, 0);
87 T_ASSERT_EQ_INT(0, err
, "fsctl(DISK_CONDITIONER_IOC_GET) after SET");
89 err
= memcmp(&info
, &expected_info
, sizeof(info
));
90 T_ASSERT_EQ_INT(0, err
, "fsctl.get is the info configured by fsctl.set");
94 verify_mount_fallback_values(const char *mount_path
, disk_conditioner_info
*info
)
97 disk_conditioner_info newinfo
= {0};
99 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_SET
, info
, 0);
101 T_ASSERT_EQ_INT(0, err
, "fsctl(DISK_CONDITIONER_IOC_SET)");
103 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_GET
, &newinfo
, 0);
105 T_ASSERT_EQ_INT(0, err
, "fsctl(DISK_CONDITIONER_IOC_GET) after SET");
107 // without querying the drive for the expected values, the best we can do is
108 // assert that they are not zero (impossible) or less than UINT32_MAX (unlikely)
109 T_ASSERT_GT(newinfo
.ioqueue_depth
, 0u, "ioqueue_depth is the value from the mount");
110 T_ASSERT_GT(newinfo
.maxreadcnt
, 0u, "maxreadcnt is value from the mount");
111 T_ASSERT_GT(newinfo
.maxwritecnt
, 0u, "maxwritecnt is value from the mount");
112 T_ASSERT_GT(newinfo
.segreadcnt
, 0u, "segreadcnt is value from the mount");
113 T_ASSERT_GT(newinfo
.segwritecnt
, 0u, "segwritecnt is value from the mount");
114 T_ASSERT_LT(newinfo
.ioqueue_depth
, UINT32_MAX
, "ioqueue_depth is the value from the mount");
115 T_ASSERT_LT(newinfo
.maxreadcnt
, UINT32_MAX
, "maxreadcnt is value from the mount");
116 T_ASSERT_LT(newinfo
.maxwritecnt
, UINT32_MAX
, "maxwritecnt is value from the mount");
117 T_ASSERT_LT(newinfo
.segreadcnt
, UINT32_MAX
, "segreadcnt is value from the mount");
118 T_ASSERT_LT(newinfo
.segwritecnt
, UINT32_MAX
, "segwritecnt is value from the mount");
121 T_DECL(fsctl_set_zero
,
122 "fsctl.set zero values should fall back to original mount settings")
125 disk_conditioner_info info
= {0};
128 mount_path
= mktempmount();
131 /* everything else is 0 */
135 verify_mount_fallback_values(mount_path
, &info
);
138 T_DECL(fsctl_set_out_of_bounds
,
139 "fsctl.set out-of-bounds values should fall back to original mount settings")
142 disk_conditioner_info info
;
145 mount_path
= mktempmount();
147 memset(&info
, UINT32_MAX
, sizeof(info
));
149 info
.access_time_usec
= 0;
150 info
.read_throughput_mbps
= 0;
151 info
.write_throughput_mbps
= 0;
152 /* everything else is UINT32_MAX */
156 verify_mount_fallback_values(mount_path
, &info
);
159 T_DECL(fsctl_restore_mount_fields
,
160 "fsctl.set should restore fields on mount_t that it temporarily overrides")
164 disk_conditioner_info info
;
165 disk_conditioner_info mount_fields
;
168 mount_path
= mktempmount();
171 /* first set out-of-bounds values to retrieve the original mount_t fields */
172 memset(&info
, UINT32_MAX
, sizeof(info
));
174 info
.access_time_usec
= 0;
175 info
.read_throughput_mbps
= 0;
176 info
.write_throughput_mbps
= 0;
177 /* everything else is UINT32_MAX */
178 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_SET
, &info
, 0);
180 T_ASSERT_EQ_INT(0, err
, "fsctl(DISK_CONDITIONER_IOC_SET)");
182 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_GET
, &mount_fields
, 0);
184 T_ASSERT_EQ_INT(0, err
, "fsctl(DISK_CONDITIONER_IOC_GET)");
186 /* now turn off the disk conditioner which should restore fields on the mount_t */
187 memset(&info
, 1, sizeof(info
));
189 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_SET
, &info
, 0);
191 T_ASSERT_EQ_INT(0, err
, "fsctl(DISK_CONDITIONER_IOC_SET)");
193 /* and finally set out-of-bounds values again to retrieve the new mount_t fields which should not have changed */
194 memset(&info
, UINT32_MAX
, sizeof(info
));
196 info
.access_time_usec
= 0;
197 info
.read_throughput_mbps
= 0;
198 info
.write_throughput_mbps
= 0;
199 /* everything else is UINT32_MAX */
200 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_SET
, &info
, 0);
202 T_ASSERT_EQ_INT(0, err
, "fsctl(DISK_CONDITIONER_IOC_SET)");
204 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_GET
, &info
, 0);
206 T_ASSERT_EQ_INT(0, err
, "fsctl(DISK_CONDITIONER_IOC_GET)");
208 T_ASSERT_EQ(info
.maxreadcnt
, mount_fields
.maxreadcnt
, "mount_t maxreadcnt restored");
209 T_ASSERT_EQ(info
.maxwritecnt
, mount_fields
.maxwritecnt
, "mount_t maxwritecnt restored");
210 T_ASSERT_EQ(info
.segreadcnt
, mount_fields
.segreadcnt
, "mount_t segreadcnt restored");
211 T_ASSERT_EQ(info
.segwritecnt
, mount_fields
.segwritecnt
, "mount_t segwritecnt restored");
212 T_ASSERT_EQ(info
.ioqueue_depth
, mount_fields
.ioqueue_depth
, "mount_t ioqueue_depth restored");
215 T_DECL(fsctl_get_nonroot
,
216 "fsctl.get should not require root",
217 T_META_ASROOT(false))
221 disk_conditioner_info info
;
224 // make sure we're not root
225 if (0 == geteuid()) {
229 mount_path
= mktempmount();
232 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_GET
, &info
, 0);
234 T_ASSERT_EQ_INT(0, err
, "fsctl.get without root");
237 T_DECL(fsctl_set_nonroot
,
238 "fsctl.set should require root",
239 T_META_ASROOT(false))
243 disk_conditioner_info info
= {0};
244 disk_conditioner_info expected_info
= {0};
247 // make sure we're not root
248 if (0 == geteuid()) {
252 mount_path
= mktempmount();
255 // save original info
256 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_GET
, &expected_info
, 0);
258 T_ASSERT_EQ_INT(0, err
, "Get original DMC info");
261 info
.access_time_usec
= 10;
262 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_SET
, &info
, 0);
264 T_ASSERT_NE_INT(0, err
, "fsctl.set returns error without root");
266 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_GET
, &info
, 0);
268 T_ASSERT_EQ_INT(0, err
, "fsctl.get after nonroot fsctl.set");
270 err
= memcmp(&info
, &expected_info
, sizeof(info
));
271 T_ASSERT_EQ_INT(0, err
, "fsctl.set should not change info without root");
275 "Validate I/O delays when DMC is enabled",
276 T_META_RUN_CONCURRENTLY(false))
281 uint64_t elapsed_nsec
, expected_nsec
;
282 disk_conditioner_info info
= {0};
286 perf_setup(&path
, &fd
);
287 memset(buf
, 0xFF, sizeof(buf
));
288 T_ASSERT_EQ_LONG((long)sizeof(buf
), write(fd
, buf
, sizeof(buf
)), "write random data to temp file");
289 fcntl(fd
, F_FULLFSYNC
);
292 expected_nsec
= NSEC_PER_SEC
/ 2;
294 // measure delay before setting parameters (should be none)
295 elapsed_nsec
= time_for_read(fd
, buf
);
296 T_ASSERT_LT_ULLONG(elapsed_nsec
, expected_nsec
, "DMC disabled read(%ld) from %s is reasonably fast", READSIZE
, path
);
298 // measure delay after setting parameters
300 info
.access_time_usec
= expected_nsec
/ NSEC_PER_USEC
;
301 info
.read_throughput_mbps
= 40;
302 info
.write_throughput_mbps
= 40;
303 info
.is_ssd
= 1; // is_ssd will ensure we get constant access_time delays rather than scaled
304 err
= fsctl(path
, DISK_CONDITIONER_IOC_SET
, &info
, 0);
306 T_ASSERT_EQ_INT(0, err
, "fsctl(DISK_CONDITIONER_IOC_SET) delay");
308 elapsed_nsec
= time_for_read(fd
, buf
);
309 T_ASSERT_GT_ULLONG(elapsed_nsec
, expected_nsec
, "DMC enabled read(%ld) from %s is at least the expected delay", READSIZE
, path
);
310 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
);
312 // measure delay after resetting parameters (should be none)
314 err
= fsctl(path
, DISK_CONDITIONER_IOC_SET
, &info
, 0);
316 T_ASSERT_EQ_INT(0, err
, "fsctl(DISK_CONDITIONER_IOC_SET) reset delay");
318 usleep(USEC_PER_SEC
/ 2); // might still be other I/O inflight
319 elapsed_nsec
= time_for_read(fd
, buf
);
320 T_ASSERT_LT_ULLONG(elapsed_nsec
, expected_nsec
, "After disabling DMC read(%ld) from %s is reasonably fast", READSIZE
, path
);
323 #else /* TEST_UNENTITLED */
325 #pragma mark Unentitled Tests
327 T_DECL(fsctl_get_unentitled
,
328 "fsctl.get should not require entitlement")
332 disk_conditioner_info info
;
335 mount_path
= mktempmount();
338 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_GET
, &info
, 0);
340 T_ASSERT_EQ_INT(0, err
, "fsctl.get without entitlement");
343 T_DECL(fsctl_set_unentitled
,
344 "fsctl.set should require entitlement")
348 disk_conditioner_info info
= {0};
349 disk_conditioner_info expected_info
= {0};
352 mount_path
= mktempmount();
355 // save original info
356 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_GET
, &expected_info
, 0);
358 T_ASSERT_EQ_INT(0, err
, "Get original DMC info");
361 info
.access_time_usec
= 10;
362 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_SET
, &info
, 0);
364 T_ASSERT_NE_INT(0, err
, "fsctl.set returns error without entitlement");
366 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_GET
, &info
, 0);
368 T_ASSERT_EQ_INT(0, err
, "fsctl.get after unentitled fsctl.set");
370 err
= memcmp(&info
, &expected_info
, sizeof(info
));
371 T_ASSERT_EQ_INT(0, err
, "fsctl.set should not change info without entitlement");
374 #endif /* TEST_UNENTITLED */
381 char *path
= malloc(PATH_MAX
);
382 strcpy(path
, "/tmp/dmc.XXXXXXXX");
383 atexit_b(^{ free(path
); });
385 // create a temporary mount to run the fsctl on
387 T_ASSERT_NOTNULL(mkdtemp(path
), "Create temporary directory");
388 atexit_b(^{ remove(path
); });
394 * Return the path to a temporary mount
395 * with no usable filesystem but still
396 * can be configured by the disk conditioner
398 * Faster than creating a ram disk to test with
399 * when access to the filesystem is not necessary
404 char *mount_path
= mktempdir();
407 T_ASSERT_EQ_INT(0, mount("devfs", mount_path
, MNT_RDONLY
, NULL
), "Create temporary devfs mount");
408 atexit_b(^{ unmount(mount_path
, MNT_FORCE
); });
413 #ifndef TEST_UNENTITLED
416 * Wrapper around dt_launch_tool/dt_waitpid
417 * that works like libc:system()
420 system_legal(const char *command
)
424 const char *argv
[] = {
431 int rc
= dt_launch_tool(&pid
, (char **)(void *)argv
, false, NULL
, NULL
);
435 if (!dt_waitpid(pid
, &exit_status
, NULL
, 30)) {
436 if (exit_status
!= 0) {
446 * Return the path to a temporary mount
447 * that contains a usable HFS+ filesystem
448 * mounted via a ram disk
454 char *mount_path
= mktempdir();
455 char *dev_disk_file
= malloc(256);
456 atexit_b(^{ free(dev_disk_file
); });
457 strcpy(dev_disk_file
, "/tmp/dmc.ramdisk.XXXXXXXX");
460 T_ASSERT_NOTNULL(mktemp(dev_disk_file
), "Create temporary file to store dev disk for ramdisk");
461 atexit_b(^{ remove(dev_disk_file
); });
463 // create the RAM disk device
464 snprintf(cmd
, sizeof(cmd
), "hdik -nomount ram://10000 > %s", dev_disk_file
);
465 T_ASSERT_EQ_INT(0, system_legal(cmd
), "Create ramdisk");
468 char eject_cmd
[1024];
469 unmount(mount_path
, MNT_FORCE
);
470 snprintf(eject_cmd
, sizeof(eject_cmd
), "hdik -e `cat %s`", dev_disk_file
);
471 system_legal(eject_cmd
);
472 remove(dev_disk_file
);
475 // initialize as an HFS volume
476 snprintf(cmd
, sizeof(cmd
), "newfs_hfs `cat %s`", dev_disk_file
);
477 T_ASSERT_EQ_INT(0, system_legal(cmd
), "Initialize ramdisk as HFS");
480 snprintf(cmd
, sizeof(cmd
), "mount -t hfs `cat %s` %s", dev_disk_file
, mount_path
);
481 T_ASSERT_EQ_INT(0, system_legal(cmd
), "Mount ramdisk");
487 time_for_read(int fd
, const char *expected
)
492 uint64_t start
, stop
;
494 bzero(buf
, sizeof(buf
));
495 lseek(fd
, 0, SEEK_SET
);
497 start
= dt_nanoseconds();
498 ret
= read(fd
, buf
, READSIZE
);
499 stop
= dt_nanoseconds();
501 T_ASSERT_GE_LONG(ret
, 0L, "read from temporary file");
502 T_ASSERT_EQ_LONG(ret
, READSIZE
, "read %ld bytes from temporary file", READSIZE
);
503 err
= memcmp(buf
, expected
, sizeof(buf
));
504 T_ASSERT_EQ_INT(0, err
, "read expected contents from temporary file");
510 perf_setup(char **path
, int *fd
)
515 char *mount_path
= mkramdisk();
516 temp_path
= *path
= malloc(PATH_MAX
);
517 snprintf(temp_path
, PATH_MAX
, "%s/dmc.XXXXXXXX", mount_path
);
518 atexit_b(^{ free(temp_path
); });
520 T_ASSERT_NOTNULL(mktemp(temp_path
), "Create temporary file");
521 atexit_b(^{ remove(temp_path
); });
523 temp_fd
= *fd
= open(temp_path
, O_RDWR
| O_CREAT
);
525 T_ASSERT_GE_INT(temp_fd
, 0, "Open temporary file for read/write");
526 atexit_b(^{ close(temp_fd
); });
527 fcntl(temp_fd
, F_NOCACHE
, 1);
529 #endif /* !TEST_UNENTITLED */