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 	T_META_RUN_CONCURRENTLY(true)
29 	);
30 
31 #pragma mark Entitled Tests
32 
33 #ifndef TEST_UNENTITLED
34 T_DECL(fsctl_get_uninitialized,
35     "Initial fsctl.get should return zeros",
36     T_META_ASROOT(false), T_META_TAG_VM_PREFERRED)
37 {
38 	int err;
39 	char *mount_path;
40 	disk_conditioner_info info = {0};
41 	disk_conditioner_info expected_info = {0};
42 
43 	T_SETUPBEGIN;
44 	mount_path = mktempmount();
45 	T_SETUPEND;
46 
47 	info.enabled = true;
48 	info.is_ssd = true;
49 	err = fsctl(mount_path, DISK_CONDITIONER_IOC_GET, &info, 0);
50 	T_WITH_ERRNO;
51 	T_ASSERT_EQ_INT(0, err, "fsctl(DISK_CONDITIONER_IOC_GET)");
52 
53 	err = memcmp(&info, &expected_info, sizeof(info));
54 	T_ASSERT_EQ_INT(0, err, "initial DMC info is zeroed");
55 }
56 
57 T_DECL(fsctl_set,
58     "fsctl.set should succeed and fsctl.get should verify", T_META_TAG_VM_PREFERRED)
59 {
60 	int err;
61 	char *mount_path;
62 	disk_conditioner_info info = {0};
63 	disk_conditioner_info expected_info = {0};
64 
65 	T_SETUPBEGIN;
66 	mount_path = mktempmount();
67 	T_SETUPEND;
68 
69 	info.enabled = 1;
70 	info.access_time_usec = 10;
71 	info.read_throughput_mbps = 40;
72 	info.write_throughput_mbps = 40;
73 	info.is_ssd = 0;
74 	info.ioqueue_depth = 8;
75 	info.maxreadcnt = 8;
76 	info.maxwritecnt = 8;
77 	info.segreadcnt = 8;
78 	info.segwritecnt = 8;
79 	expected_info = info;
80 
81 	err = fsctl(mount_path, DISK_CONDITIONER_IOC_SET, &info, 0);
82 	T_WITH_ERRNO;
83 	T_ASSERT_EQ_INT(0, err, "fsctl(DISK_CONDITIONER_IOC_SET)");
84 
85 	err = fsctl(mount_path, DISK_CONDITIONER_IOC_GET, &info, 0);
86 	T_WITH_ERRNO;
87 	T_ASSERT_EQ_INT(0, err, "fsctl(DISK_CONDITIONER_IOC_GET) after SET");
88 
89 	err = memcmp(&info, &expected_info, sizeof(info));
90 	T_ASSERT_EQ_INT(0, err, "fsctl.get is the info configured by fsctl.set");
91 }
92 
93 static void
verify_mount_fallback_values(const char * mount_path,disk_conditioner_info * info)94 verify_mount_fallback_values(const char *mount_path, disk_conditioner_info *info)
95 {
96 	int err;
97 	disk_conditioner_info newinfo = {0};
98 
99 	err = fsctl(mount_path, DISK_CONDITIONER_IOC_SET, info, 0);
100 	T_WITH_ERRNO;
101 	T_ASSERT_EQ_INT(0, err, "fsctl(DISK_CONDITIONER_IOC_SET)");
102 
103 	err = fsctl(mount_path, DISK_CONDITIONER_IOC_GET, &newinfo, 0);
104 	T_WITH_ERRNO;
105 	T_ASSERT_EQ_INT(0, err, "fsctl(DISK_CONDITIONER_IOC_GET) after SET");
106 
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");
119 }
120 
121 T_DECL(fsctl_set_zero,
122     "fsctl.set zero values should fall back to original mount settings", T_META_TAG_VM_PREFERRED)
123 {
124 	char *mount_path;
125 	disk_conditioner_info info = {0};
126 
127 	T_SETUPBEGIN;
128 	mount_path = mktempmount();
129 
130 	info.enabled = 1;
131 	/* everything else is 0 */
132 
133 	T_SETUPEND;
134 
135 	verify_mount_fallback_values(mount_path, &info);
136 }
137 
138 T_DECL(fsctl_set_out_of_bounds,
139     "fsctl.set out-of-bounds values should fall back to original mount settings", T_META_TAG_VM_PREFERRED)
140 {
141 	char *mount_path;
142 	disk_conditioner_info info;
143 
144 	T_SETUPBEGIN;
145 	mount_path = mktempmount();
146 
147 	memset(&info, UINT32_MAX, sizeof(info));
148 	info.enabled = 1;
149 	info.access_time_usec = 0;
150 	info.read_throughput_mbps = 0;
151 	info.write_throughput_mbps = 0;
152 	/* everything else is UINT32_MAX */
153 
154 	T_SETUPEND;
155 
156 	verify_mount_fallback_values(mount_path, &info);
157 }
158 
159 T_DECL(fsctl_restore_mount_fields,
160     "fsctl.set should restore fields on mount_t that it temporarily overrides", T_META_TAG_VM_PREFERRED)
161 {
162 	int err;
163 	char *mount_path;
164 	disk_conditioner_info info;
165 	disk_conditioner_info mount_fields;
166 
167 	T_SETUPBEGIN;
168 	mount_path = mktempmount();
169 	T_SETUPEND;
170 
171 	/* first set out-of-bounds values to retrieve the original mount_t fields */
172 	memset(&info, UINT32_MAX, sizeof(info));
173 	info.enabled = 1;
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);
179 	T_WITH_ERRNO;
180 	T_ASSERT_EQ_INT(0, err, "fsctl(DISK_CONDITIONER_IOC_SET)");
181 
182 	err = fsctl(mount_path, DISK_CONDITIONER_IOC_GET, &mount_fields, 0);
183 	T_WITH_ERRNO;
184 	T_ASSERT_EQ_INT(0, err, "fsctl(DISK_CONDITIONER_IOC_GET)");
185 
186 	/* now turn off the disk conditioner which should restore fields on the mount_t */
187 	memset(&info, 1, sizeof(info));
188 	info.enabled = 0;
189 	err = fsctl(mount_path, DISK_CONDITIONER_IOC_SET, &info, 0);
190 	T_WITH_ERRNO;
191 	T_ASSERT_EQ_INT(0, err, "fsctl(DISK_CONDITIONER_IOC_SET)");
192 
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));
195 	info.enabled = 0;
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);
201 	T_WITH_ERRNO;
202 	T_ASSERT_EQ_INT(0, err, "fsctl(DISK_CONDITIONER_IOC_SET)");
203 
204 	err = fsctl(mount_path, DISK_CONDITIONER_IOC_GET, &info, 0);
205 	T_WITH_ERRNO;
206 	T_ASSERT_EQ_INT(0, err, "fsctl(DISK_CONDITIONER_IOC_GET)");
207 
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");
213 }
214 
215 T_DECL(fsctl_get_nonroot,
216     "fsctl.get should not require root",
217     T_META_ASROOT(false), T_META_TAG_VM_PREFERRED)
218 {
219 	int err;
220 	char *mount_path;
221 	disk_conditioner_info info;
222 
223 	T_SETUPBEGIN;
224 	// make sure we're not root
225 	if (0 == geteuid()) {
226 		seteuid(5000);
227 	}
228 
229 	mount_path = mktempmount();
230 	T_SETUPEND;
231 
232 	err = fsctl(mount_path, DISK_CONDITIONER_IOC_GET, &info, 0);
233 	T_WITH_ERRNO;
234 	T_ASSERT_EQ_INT(0, err, "fsctl.get without root");
235 }
236 
237 T_DECL(fsctl_set_nonroot,
238     "fsctl.set should require root",
239     T_META_ASROOT(false), T_META_TAG_VM_PREFERRED)
240 {
241 	int err;
242 	char *mount_path;
243 	disk_conditioner_info info = {0};
244 	disk_conditioner_info expected_info = {0};
245 
246 	T_SETUPBEGIN;
247 	// make sure we're not root
248 	if (0 == geteuid()) {
249 		seteuid(5000);
250 	}
251 
252 	mount_path = mktempmount();
253 	T_SETUPEND;
254 
255 	// save original info
256 	err = fsctl(mount_path, DISK_CONDITIONER_IOC_GET, &expected_info, 0);
257 	T_WITH_ERRNO;
258 	T_ASSERT_EQ_INT(0, err, "Get original DMC info");
259 
260 	info.enabled = 1;
261 	info.access_time_usec = 10;
262 	err = fsctl(mount_path, DISK_CONDITIONER_IOC_SET, &info, 0);
263 	T_WITH_ERRNO;
264 	T_ASSERT_NE_INT(0, err, "fsctl.set returns error without root");
265 
266 	err = fsctl(mount_path, DISK_CONDITIONER_IOC_GET, &info, 0);
267 	T_WITH_ERRNO;
268 	T_ASSERT_EQ_INT(0, err, "fsctl.get after nonroot fsctl.set");
269 
270 	err = memcmp(&info, &expected_info, sizeof(info));
271 	T_ASSERT_EQ_INT(0, err, "fsctl.set should not change info without root");
272 }
273 
274 T_DECL(fsctl_delays,
275     "Validate I/O delays when DMC is enabled",
276     T_META_RUN_CONCURRENTLY(false), T_META_TAG_VM_PREFERRED)
277 {
278 	char *path;
279 	int fd;
280 	int err;
281 	uint64_t elapsed_nsec, expected_nsec;
282 	disk_conditioner_info info = {0};
283 	char buf[READSIZE];
284 
285 	T_SETUPBEGIN;
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);
290 	T_SETUPEND;
291 
292 	expected_nsec = NSEC_PER_SEC / 2;
293 
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);
297 
298 	// measure delay after setting parameters
299 	info.enabled = 1;
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);
305 	T_WITH_ERRNO;
306 	T_ASSERT_EQ_INT(0, err, "fsctl(DISK_CONDITIONER_IOC_SET) delay");
307 
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);
311 
312 	// measure delay after resetting parameters (should be none)
313 	info.enabled = 0;
314 	err = fsctl(path, DISK_CONDITIONER_IOC_SET, &info, 0);
315 	T_WITH_ERRNO;
316 	T_ASSERT_EQ_INT(0, err, "fsctl(DISK_CONDITIONER_IOC_SET) reset delay");
317 
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);
321 }
322 
323 #else /* TEST_UNENTITLED */
324 
325 #pragma mark Unentitled Tests
326 
327 T_DECL(fsctl_get_unentitled,
328     "fsctl.get should not require entitlement", T_META_TAG_VM_PREFERRED)
329 {
330 	int err;
331 	char *mount_path;
332 	disk_conditioner_info info;
333 
334 	T_SETUPBEGIN;
335 	mount_path = mktempmount();
336 	T_SETUPEND;
337 
338 	err = fsctl(mount_path, DISK_CONDITIONER_IOC_GET, &info, 0);
339 	T_WITH_ERRNO;
340 	T_ASSERT_EQ_INT(0, err, "fsctl.get without entitlement");
341 }
342 
343 T_DECL(fsctl_set_unentitled,
344     "fsctl.set should require entitlement", T_META_TAG_VM_PREFERRED)
345 {
346 	int err;
347 	char *mount_path;
348 	disk_conditioner_info info = {0};
349 	disk_conditioner_info expected_info = {0};
350 
351 	T_SETUPBEGIN;
352 	mount_path = mktempmount();
353 	T_SETUPEND;
354 
355 	// save original info
356 	err = fsctl(mount_path, DISK_CONDITIONER_IOC_GET, &expected_info, 0);
357 	T_WITH_ERRNO;
358 	T_ASSERT_EQ_INT(0, err, "Get original DMC info");
359 
360 	info.enabled = 1;
361 	info.access_time_usec = 10;
362 	err = fsctl(mount_path, DISK_CONDITIONER_IOC_SET, &info, 0);
363 	T_WITH_ERRNO;
364 	T_ASSERT_NE_INT(0, err, "fsctl.set returns error without entitlement");
365 
366 	err = fsctl(mount_path, DISK_CONDITIONER_IOC_GET, &info, 0);
367 	T_WITH_ERRNO;
368 	T_ASSERT_EQ_INT(0, err, "fsctl.get after unentitled fsctl.set");
369 
370 	err = memcmp(&info, &expected_info, sizeof(info));
371 	T_ASSERT_EQ_INT(0, err, "fsctl.set should not change info without entitlement");
372 }
373 
374 #endif /* TEST_UNENTITLED */
375 
376 #pragma mark Helpers
377 
378 static char *
mktempdir(void)379 mktempdir(void)
380 {
381 	char *path = malloc(PATH_MAX);
382 	strcpy(path, "/tmp/dmc.XXXXXXXX");
383 	atexit_b(^{ free(path); });
384 
385 	// create a temporary mount to run the fsctl on
386 	T_WITH_ERRNO;
387 	T_ASSERT_NOTNULL(mkdtemp(path), "Create temporary directory");
388 	atexit_b(^{ remove(path); });
389 
390 	return path;
391 }
392 
393 /*
394  * Return the path to a temporary mount
395  * with no usable filesystem but still
396  * can be configured by the disk conditioner
397  *
398  * Faster than creating a ram disk to test with
399  * when access to the filesystem is not necessary
400  */
401 static char *
mktempmount(void)402 mktempmount(void)
403 {
404 	char *mount_path = mktempdir();
405 
406 	T_WITH_ERRNO;
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); });
409 
410 	return mount_path;
411 }
412 
413 #ifndef TEST_UNENTITLED
414 
415 /*
416  * Wrapper around dt_launch_tool/dt_waitpid
417  * that works like libc:system()
418  */
419 static int
system_legal(const char * command)420 system_legal(const char *command)
421 {
422 	pid_t pid = -1;
423 	int exit_status = 0;
424 	const char *argv[] = {
425 		_PATH_BSHELL,
426 		"-c",
427 		command,
428 		NULL
429 	};
430 
431 	int rc = dt_launch_tool(&pid, (char **)(void *)argv, false, NULL, NULL);
432 	if (rc != 0) {
433 		return -1;
434 	}
435 	if (!dt_waitpid(pid, &exit_status, NULL, 30)) {
436 		if (exit_status != 0) {
437 			return exit_status;
438 		}
439 		return -1;
440 	}
441 
442 	return exit_status;
443 }
444 
445 /*
446  * Return the path to a temporary mount
447  * that contains a usable HFS+ filesystem
448  * mounted via a ram disk
449  */
450 static char *
mkramdisk(void)451 mkramdisk(void)
452 {
453 	char cmd[1024];
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");
458 
459 	T_WITH_ERRNO;
460 	T_ASSERT_NOTNULL(mktemp(dev_disk_file), "Create temporary file to store dev disk for ramdisk");
461 	atexit_b(^{ remove(dev_disk_file); });
462 
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");
466 
467 	atexit_b(^{
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);
473 	});
474 
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");
478 
479 	// mount it
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");
482 
483 	return mount_path;
484 }
485 
486 static uint64_t
time_for_read(int fd,const char * expected)487 time_for_read(int fd, const char *expected)
488 {
489 	int err;
490 	ssize_t ret;
491 	char buf[READSIZE];
492 	uint64_t start, stop;
493 
494 	bzero(buf, sizeof(buf));
495 	lseek(fd, 0, SEEK_SET);
496 
497 	start = dt_nanoseconds();
498 	ret = read(fd, buf, READSIZE);
499 	stop = dt_nanoseconds();
500 
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");
505 
506 	return stop - start;
507 }
508 
509 static void
perf_setup(char ** path,int * fd)510 perf_setup(char **path, int *fd)
511 {
512 	int temp_fd;
513 	char *temp_path;
514 
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); });
519 
520 	T_ASSERT_NOTNULL(mktemp(temp_path), "Create temporary file");
521 	atexit_b(^{ remove(temp_path); });
522 
523 	temp_fd = *fd = open(temp_path, O_RDWR | O_CREAT);
524 	T_WITH_ERRNO;
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);
528 }
529 #endif /* !TEST_UNENTITLED */
530