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"),
30 #pragma mark Entitled Tests
32 #ifndef TEST_UNENTITLED
33 T_DECL(fsctl_get_uninitialized
,
34 "Initial fsctl.get should return zeros",
39 disk_conditioner_info info
= {0};
40 disk_conditioner_info expected_info
= {0};
43 mount_path
= mktempmount();
48 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_GET
, &info
, 0);
50 T_ASSERT_EQ_INT(0, err
, "fsctl(DISK_CONDITIONER_IOC_GET)");
52 err
= memcmp(&info
, &expected_info
, sizeof(info
));
53 T_ASSERT_EQ_INT(0, err
, "initial DMC info is zeroed");
57 "fsctl.set should succeed and fsctl.get should verify")
61 disk_conditioner_info info
= {0};
62 disk_conditioner_info expected_info
= {0};
65 mount_path
= mktempmount();
69 info
.access_time_usec
= 10;
70 info
.read_throughput_mbps
= 40;
71 info
.write_throughput_mbps
= 40;
73 info
.ioqueue_depth
= 8;
80 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_SET
, &info
, 0);
82 T_ASSERT_EQ_INT(0, err
, "fsctl(DISK_CONDITIONER_IOC_SET)");
84 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_GET
, &info
, 0);
86 T_ASSERT_EQ_INT(0, err
, "fsctl(DISK_CONDITIONER_IOC_GET) after SET");
88 err
= memcmp(&info
, &expected_info
, sizeof(info
));
89 T_ASSERT_EQ_INT(0, err
, "fsctl.get is the info configured by fsctl.set");
93 verify_mount_fallback_values(const char *mount_path
, disk_conditioner_info
*info
)
96 disk_conditioner_info newinfo
= {0};
98 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_SET
, info
, 0);
100 T_ASSERT_EQ_INT(0, err
, "fsctl(DISK_CONDITIONER_IOC_SET)");
102 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_GET
, &newinfo
, 0);
104 T_ASSERT_EQ_INT(0, err
, "fsctl(DISK_CONDITIONER_IOC_GET) after SET");
106 // without querying the drive for the expected values, the best we can do is
107 // assert that they are not zero (impossible) or less than UINT32_MAX (unlikely)
108 T_ASSERT_GT(newinfo
.ioqueue_depth
, 0u, "ioqueue_depth is the value from the mount");
109 T_ASSERT_GT(newinfo
.maxreadcnt
, 0u, "maxreadcnt is value from the mount");
110 T_ASSERT_GT(newinfo
.maxwritecnt
, 0u, "maxwritecnt is value from the mount");
111 T_ASSERT_GT(newinfo
.segreadcnt
, 0u, "segreadcnt is value from the mount");
112 T_ASSERT_GT(newinfo
.segwritecnt
, 0u, "segwritecnt is value from the mount");
113 T_ASSERT_LT(newinfo
.ioqueue_depth
, UINT32_MAX
, "ioqueue_depth is the value from the mount");
114 T_ASSERT_LT(newinfo
.maxreadcnt
, UINT32_MAX
, "maxreadcnt is value from the mount");
115 T_ASSERT_LT(newinfo
.maxwritecnt
, UINT32_MAX
, "maxwritecnt is value from the mount");
116 T_ASSERT_LT(newinfo
.segreadcnt
, UINT32_MAX
, "segreadcnt is value from the mount");
117 T_ASSERT_LT(newinfo
.segwritecnt
, UINT32_MAX
, "segwritecnt is value from the mount");
120 T_DECL(fsctl_set_zero
,
121 "fsctl.set zero values should fall back to original mount settings")
124 disk_conditioner_info info
= {0};
127 mount_path
= mktempmount();
130 /* everything else is 0 */
134 verify_mount_fallback_values(mount_path
, &info
);
137 T_DECL(fsctl_set_out_of_bounds
,
138 "fsctl.set out-of-bounds values should fall back to original mount settings")
141 disk_conditioner_info info
;
144 mount_path
= mktempmount();
146 memset(&info
, UINT32_MAX
, sizeof(info
));
148 info
.access_time_usec
= 0;
149 info
.read_throughput_mbps
= 0;
150 info
.write_throughput_mbps
= 0;
151 /* everything else is UINT32_MAX */
155 verify_mount_fallback_values(mount_path
, &info
);
158 T_DECL(fsctl_restore_mount_fields
,
159 "fsctl.set should restore fields on mount_t that it temporarily overrides")
163 disk_conditioner_info info
;
164 disk_conditioner_info mount_fields
;
167 mount_path
= mktempmount();
170 /* first set out-of-bounds values to retrieve the original mount_t fields */
171 memset(&info
, UINT32_MAX
, sizeof(info
));
173 info
.access_time_usec
= 0;
174 info
.read_throughput_mbps
= 0;
175 info
.write_throughput_mbps
= 0;
176 /* everything else is UINT32_MAX */
177 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_SET
, &info
, 0);
179 T_ASSERT_EQ_INT(0, err
, "fsctl(DISK_CONDITIONER_IOC_SET)");
181 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_GET
, &mount_fields
, 0);
183 T_ASSERT_EQ_INT(0, err
, "fsctl(DISK_CONDITIONER_IOC_GET)");
185 /* now turn off the disk conditioner which should restore fields on the mount_t */
186 memset(&info
, 1, sizeof(info
));
188 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_SET
, &info
, 0);
190 T_ASSERT_EQ_INT(0, err
, "fsctl(DISK_CONDITIONER_IOC_SET)");
192 /* and finally set out-of-bounds values again to retrieve the new mount_t fields which should not have changed */
193 memset(&info
, UINT32_MAX
, sizeof(info
));
195 info
.access_time_usec
= 0;
196 info
.read_throughput_mbps
= 0;
197 info
.write_throughput_mbps
= 0;
198 /* everything else is UINT32_MAX */
199 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_SET
, &info
, 0);
201 T_ASSERT_EQ_INT(0, err
, "fsctl(DISK_CONDITIONER_IOC_SET)");
203 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_GET
, &info
, 0);
205 T_ASSERT_EQ_INT(0, err
, "fsctl(DISK_CONDITIONER_IOC_GET)");
207 T_ASSERT_EQ(info
.maxreadcnt
, mount_fields
.maxreadcnt
, "mount_t maxreadcnt restored");
208 T_ASSERT_EQ(info
.maxwritecnt
, mount_fields
.maxwritecnt
, "mount_t maxwritecnt restored");
209 T_ASSERT_EQ(info
.segreadcnt
, mount_fields
.segreadcnt
, "mount_t segreadcnt restored");
210 T_ASSERT_EQ(info
.segwritecnt
, mount_fields
.segwritecnt
, "mount_t segwritecnt restored");
211 T_ASSERT_EQ(info
.ioqueue_depth
, mount_fields
.ioqueue_depth
, "mount_t ioqueue_depth restored");
214 T_DECL(fsctl_get_nonroot
,
215 "fsctl.get should not require root",
216 T_META_ASROOT(false))
220 disk_conditioner_info info
;
223 // make sure we're not root
224 if (0 == geteuid()) {
228 mount_path
= mktempmount();
231 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_GET
, &info
, 0);
233 T_ASSERT_EQ_INT(0, err
, "fsctl.get without root");
236 T_DECL(fsctl_set_nonroot
,
237 "fsctl.set should require root",
238 T_META_ASROOT(false))
242 disk_conditioner_info info
= {0};
243 disk_conditioner_info expected_info
= {0};
246 // make sure we're not root
247 if (0 == geteuid()) {
251 mount_path
= mktempmount();
254 // save original info
255 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_GET
, &expected_info
, 0);
257 T_ASSERT_EQ_INT(0, err
, "Get original DMC info");
260 info
.access_time_usec
= 10;
261 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_SET
, &info
, 0);
263 T_ASSERT_NE_INT(0, err
, "fsctl.set returns error without root");
265 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_GET
, &info
, 0);
267 T_ASSERT_EQ_INT(0, err
, "fsctl.get after nonroot fsctl.set");
269 err
= memcmp(&info
, &expected_info
, sizeof(info
));
270 T_ASSERT_EQ_INT(0, err
, "fsctl.set should not change info without root");
274 "Validate I/O delays when DMC is enabled")
279 uint64_t elapsed_nsec
, expected_nsec
;
280 disk_conditioner_info info
= {0};
284 perf_setup(&path
, &fd
);
285 memset(buf
, 0xFF, sizeof(buf
));
286 T_ASSERT_EQ_LONG((long)sizeof(buf
), write(fd
, buf
, sizeof(buf
)), "write random data to temp file");
287 fcntl(fd
, F_FULLFSYNC
);
290 expected_nsec
= NSEC_PER_SEC
/ 2;
292 // measure delay before setting parameters (should be none)
293 elapsed_nsec
= time_for_read(fd
, buf
);
294 T_ASSERT_LT_ULLONG(elapsed_nsec
, expected_nsec
, "DMC disabled read(%ld) from %s is reasonably fast", READSIZE
, path
);
296 // measure delay after setting parameters
298 info
.access_time_usec
= expected_nsec
/ NSEC_PER_USEC
;
299 info
.read_throughput_mbps
= 40;
300 info
.write_throughput_mbps
= 40;
301 info
.is_ssd
= 1; // is_ssd will ensure we get constant access_time delays rather than scaled
302 err
= fsctl(path
, DISK_CONDITIONER_IOC_SET
, &info
, 0);
304 T_ASSERT_EQ_INT(0, err
, "fsctl(DISK_CONDITIONER_IOC_SET) delay");
306 elapsed_nsec
= time_for_read(fd
, buf
);
307 T_ASSERT_GT_ULLONG(elapsed_nsec
, expected_nsec
, "DMC enabled read(%ld) from %s is at least the expected delay", READSIZE
, path
);
308 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
);
310 // measure delay after resetting parameters (should be none)
312 err
= fsctl(path
, DISK_CONDITIONER_IOC_SET
, &info
, 0);
314 T_ASSERT_EQ_INT(0, err
, "fsctl(DISK_CONDITIONER_IOC_SET) reset delay");
316 usleep(USEC_PER_SEC
/ 2); // might still be other I/O inflight
317 elapsed_nsec
= time_for_read(fd
, buf
);
318 T_ASSERT_LT_ULLONG(elapsed_nsec
, expected_nsec
, "After disabling DMC read(%ld) from %s is reasonably fast", READSIZE
, path
);
321 #else /* TEST_UNENTITLED */
323 #pragma mark Unentitled Tests
325 T_DECL(fsctl_get_unentitled
,
326 "fsctl.get should not require entitlement")
330 disk_conditioner_info info
;
333 mount_path
= mktempmount();
336 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_GET
, &info
, 0);
338 T_ASSERT_EQ_INT(0, err
, "fsctl.get without entitlement");
341 T_DECL(fsctl_set_unentitled
,
342 "fsctl.set should require entitlement")
346 disk_conditioner_info info
= {0};
347 disk_conditioner_info expected_info
= {0};
350 mount_path
= mktempmount();
353 // save original info
354 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_GET
, &expected_info
, 0);
356 T_ASSERT_EQ_INT(0, err
, "Get original DMC info");
359 info
.access_time_usec
= 10;
360 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_SET
, &info
, 0);
362 T_ASSERT_NE_INT(0, err
, "fsctl.set returns error without entitlement");
364 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_GET
, &info
, 0);
366 T_ASSERT_EQ_INT(0, err
, "fsctl.get after unentitled fsctl.set");
368 err
= memcmp(&info
, &expected_info
, sizeof(info
));
369 T_ASSERT_EQ_INT(0, err
, "fsctl.set should not change info without entitlement");
372 #endif /* TEST_UNENTITLED */
379 char *path
= malloc(PATH_MAX
);
380 strcpy(path
, "/tmp/dmc.XXXXXXXX");
381 atexit_b(^{ free(path
); });
383 // create a temporary mount to run the fsctl on
385 T_ASSERT_NOTNULL(mkdtemp(path
), "Create temporary directory");
386 atexit_b(^{ remove(path
); });
392 * Return the path to a temporary mount
393 * with no usable filesystem but still
394 * can be configured by the disk conditioner
396 * Faster than creating a ram disk to test with
397 * when access to the filesystem is not necessary
402 char *mount_path
= mktempdir();
405 T_ASSERT_EQ_INT(0, mount("devfs", mount_path
, MNT_RDONLY
, NULL
), "Create temporary devfs mount");
406 atexit_b(^{ unmount(mount_path
, MNT_FORCE
); });
411 #ifndef TEST_UNENTITLED
414 * Wrapper around dt_launch_tool/dt_waitpid
415 * that works like libc:system()
418 system_legal(const char *command
)
422 const char *argv
[] = {
429 int rc
= dt_launch_tool(&pid
, (char **)(void *)argv
, false, NULL
, NULL
);
433 if (!dt_waitpid(pid
, &exit_status
, NULL
, 30)) {
434 if (exit_status
!= 0) {
444 * Return the path to a temporary mount
445 * that contains a usable HFS+ filesystem
446 * mounted via a ram disk
452 char *mount_path
= mktempdir();
453 char *dev_disk_file
= malloc(256);
454 atexit_b(^{ free(dev_disk_file
); });
455 strcpy(dev_disk_file
, "/tmp/dmc.ramdisk.XXXXXXXX");
458 T_ASSERT_NOTNULL(mktemp(dev_disk_file
), "Create temporary file to store dev disk for ramdisk");
459 atexit_b(^{ remove(dev_disk_file
); });
461 // create the RAM disk device
462 snprintf(cmd
, sizeof(cmd
), "hdik -nomount ram://10000 > %s", dev_disk_file
);
463 T_ASSERT_EQ_INT(0, system_legal(cmd
), "Create ramdisk");
466 char eject_cmd
[1024];
467 unmount(mount_path
, MNT_FORCE
);
468 snprintf(eject_cmd
, sizeof(eject_cmd
), "hdik -e `cat %s`", dev_disk_file
);
469 system_legal(eject_cmd
);
470 remove(dev_disk_file
);
473 // initialize as an HFS volume
474 snprintf(cmd
, sizeof(cmd
), "newfs_hfs `cat %s`", dev_disk_file
);
475 T_ASSERT_EQ_INT(0, system_legal(cmd
), "Initialize ramdisk as HFS");
478 snprintf(cmd
, sizeof(cmd
), "mount -t hfs `cat %s` %s", dev_disk_file
, mount_path
);
479 T_ASSERT_EQ_INT(0, system_legal(cmd
), "Mount ramdisk");
485 time_for_read(int fd
, const char *expected
)
490 uint64_t start
, stop
;
492 bzero(buf
, sizeof(buf
));
493 lseek(fd
, 0, SEEK_SET
);
495 start
= dt_nanoseconds();
496 ret
= read(fd
, buf
, READSIZE
);
497 stop
= dt_nanoseconds();
499 T_ASSERT_GE_LONG(ret
, 0L, "read from temporary file");
500 T_ASSERT_EQ_LONG(ret
, READSIZE
, "read %ld bytes from temporary file", READSIZE
);
501 err
= memcmp(buf
, expected
, sizeof(buf
));
502 T_ASSERT_EQ_INT(0, err
, "read expected contents from temporary file");
508 perf_setup(char **path
, int *fd
)
513 char *mount_path
= mkramdisk();
514 temp_path
= *path
= malloc(PATH_MAX
);
515 snprintf(temp_path
, PATH_MAX
, "%s/dmc.XXXXXXXX", mount_path
);
516 atexit_b(^{ free(temp_path
); });
518 T_ASSERT_NOTNULL(mktemp(temp_path
), "Create temporary file");
519 atexit_b(^{ remove(temp_path
); });
521 temp_fd
= *fd
= open(temp_path
, O_RDWR
| O_CREAT
);
523 T_ASSERT_GE_INT(temp_fd
, 0, "Open temporary file for read/write");
524 atexit_b(^{ close(temp_fd
); });
525 fcntl(temp_fd
, F_NOCACHE
, 1);
527 #endif /* !TEST_UNENTITLED */