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;
75 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_SET
, &info
, 0);
77 T_ASSERT_EQ_INT(0, err
, "fsctl(DISK_CONDITIONER_IOC_SET)");
79 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_GET
, &info
, 0);
81 T_ASSERT_EQ_INT(0, err
, "fsctl(DISK_CONDITIONER_IOC_GET) after SET");
83 err
= memcmp(&info
, &expected_info
, sizeof(info
));
84 T_ASSERT_EQ_INT(0, err
, "fsctl.get is the info configured by fsctl.set");
87 T_DECL(fsctl_get_nonroot
,
88 "fsctl.get should not require root",
93 disk_conditioner_info info
;
96 // make sure we're not root
101 mount_path
= mktempmount();
104 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_GET
, &info
, 0);
106 T_ASSERT_EQ_INT(0, err
, "fsctl.get without root");
109 T_DECL(fsctl_set_nonroot
,
110 "fsctl.set should require root",
111 T_META_ASROOT(false))
115 disk_conditioner_info info
= {0};
116 disk_conditioner_info expected_info
= {0};
119 // make sure we're not root
120 if (0 == geteuid()) {
124 mount_path
= mktempmount();
127 // save original info
128 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_GET
, &expected_info
, 0);
130 T_ASSERT_EQ_INT(0, err
, "Get original DMC info");
133 info
.access_time_usec
= 10;
134 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_SET
, &info
, 0);
136 T_ASSERT_NE_INT(0, err
, "fsctl.set returns error without root");
138 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_GET
, &info
, 0);
140 T_ASSERT_EQ_INT(0, err
, "fsctl.get after nonroot fsctl.set");
142 err
= memcmp(&info
, &expected_info
, sizeof(info
));
143 T_ASSERT_EQ_INT(0, err
, "fsctl.set should not change info without root");
147 "Validate I/O delays when DMC is enabled")
152 uint64_t elapsed_nsec
, expected_nsec
;
153 disk_conditioner_info info
;
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
);
163 expected_nsec
= NSEC_PER_SEC
/ 2;
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
);
169 // measure delay after setting parameters
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);
177 T_ASSERT_EQ_INT(0, err
, "fsctl(DISK_CONDITIONER_IOC_SET) delay");
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
);
183 // measure delay after resetting parameters (should be none)
185 err
= fsctl(path
, DISK_CONDITIONER_IOC_SET
, &info
, 0);
187 T_ASSERT_EQ_INT(0, err
, "fsctl(DISK_CONDITIONER_IOC_SET) reset delay");
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
);
194 #else /* TEST_UNENTITLED */
196 #pragma mark Unentitled Tests
198 T_DECL(fsctl_get_unentitled
,
199 "fsctl.get should not require entitlement")
203 disk_conditioner_info info
;
206 mount_path
= mktempmount();
209 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_GET
, &info
, 0);
211 T_ASSERT_EQ_INT(0, err
, "fsctl.get without entitlement");
214 T_DECL(fsctl_set_unentitled
,
215 "fsctl.set should require entitlement")
219 disk_conditioner_info info
= {0};
220 disk_conditioner_info expected_info
= {0};
223 mount_path
= mktempmount();
226 // save original info
227 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_GET
, &expected_info
, 0);
229 T_ASSERT_EQ_INT(0, err
, "Get original DMC info");
232 info
.access_time_usec
= 10;
233 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_SET
, &info
, 0);
235 T_ASSERT_NE_INT(0, err
, "fsctl.set returns error without entitlement");
237 err
= fsctl(mount_path
, DISK_CONDITIONER_IOC_GET
, &info
, 0);
239 T_ASSERT_EQ_INT(0, err
, "fsctl.get after unentitled fsctl.set");
241 err
= memcmp(&info
, &expected_info
, sizeof(info
));
242 T_ASSERT_EQ_INT(0, err
, "fsctl.set should not change info without entitlement");
245 #endif /* TEST_UNENTITLED */
249 static char *mktempdir(void) {
250 char *path
= malloc(PATH_MAX
);
251 strcpy(path
, "/tmp/dmc.XXXXXXXX");
252 atexit_b(^{ free(path
); });
254 // create a temporary mount to run the fsctl on
256 T_ASSERT_NOTNULL(mkdtemp(path
), "Create temporary directory");
257 atexit_b(^{ remove(path
); });
263 * Return the path to a temporary mount
264 * with no usable filesystem but still
265 * can be configured by the disk conditioner
267 * Faster than creating a ram disk to test with
268 * when access to the filesystem is not necessary
270 static char *mktempmount(void) {
271 char *mount_path
= mktempdir();
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
); });
280 #ifndef TEST_UNENTITLED
283 * Wrapper around dt_launch_tool/dt_waitpid
284 * that works like libc:system()
286 static int system_legal(const char *command
) {
289 const char *argv
[] = {
296 int rc
= dt_launch_tool(&pid
, (char **)(void *)argv
, false, NULL
, NULL
);
300 if (!dt_waitpid(pid
, &exit_status
, NULL
, 30)) {
301 if (exit_status
!= 0) {
311 * Return the path to a temporary mount
312 * that contains a usable HFS+ filesystem
313 * mounted via a ram disk
315 static char *mkramdisk(void) {
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");
323 T_ASSERT_NOTNULL(mktemp(dev_disk_file
), "Create temporary file to store dev disk for ramdisk");
324 atexit_b(^{ remove(dev_disk_file
); });
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");
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
);
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");
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");
349 static uint64_t time_for_read(int fd
, const char *expected
) {
353 uint64_t start
, stop
;
355 bzero(buf
, sizeof(buf
));
356 lseek(fd
, 0, SEEK_SET
);
358 start
= dt_nanoseconds();
359 ret
= read(fd
, buf
, READSIZE
);
360 stop
= dt_nanoseconds();
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");
367 return (stop
- start
);
370 static void perf_setup(char **path
, int *fd
) {
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
); });
379 T_ASSERT_NOTNULL(mktemp(temp_path
), "Create temporary file");
380 atexit_b(^{ remove(temp_path
); });
382 temp_fd
= *fd
= open(temp_path
, O_RDWR
| O_CREAT
);
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);
388 #endif /* !TEST_UNENTITLED */