]> git.saurik.com Git - apple/xnu.git/blame - tools/tests/darwintests/disk_mount_conditioner.c
xnu-4570.1.46.tar.gz
[apple/xnu.git] / tools / tests / darwintests / disk_mount_conditioner.c
CommitLineData
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
13static char *mktempdir(void);
14static char *mktempmount(void);
15
16#ifndef TEST_UNENTITLED
17static int system_legal(const char *command);
18static char *mkramdisk(void);
19static uint64_t time_for_read(int fd, const char *expected);
20static void perf_setup(char **path, int *fd);
21
22#define READSIZE 1024L
23#endif /* !TEST_UNENTITLED */
24
25T_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
33T_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
56T_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
87T_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
109T_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
146T_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
198T_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
214T_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
249static 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 */
270static 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 */
286static 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 */
315static 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
349static 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
370static 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 */