]> git.saurik.com Git - apple/hfs.git/blob - tests/disk-image.m
hfs-556.41.1.tar.gz
[apple/hfs.git] / tests / disk-image.m
1 //
2 // disk-image.m
3 // hfs
4 //
5 // Created by Chris Suter on 8/12/15.
6 //
7 //
8
9 #include <unistd.h>
10 #include <spawn.h>
11 #include <sys/stat.h>
12 #include <sys/param.h>
13 #include <sys/mount.h>
14 #include <zlib.h>
15 #include <stdlib.h>
16 #include <fcntl.h>
17 #include <stdbool.h>
18
19 #include <Foundation/Foundation.h>
20 #include <TargetConditionals.h>
21
22 #include "disk-image.h"
23 #include "test-utils.h"
24 #include "systemx.h"
25
26 #define RETRY_MAX 3
27
28 #if (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR)
29
30 #include "dmg.dat"
31
32 bool disk_image_cleanup(disk_image_t *di)
33 {
34 pid_t pid;
35 bool result = false;
36 int eject_retry;
37 int status;
38
39 // We need to be root
40 assert(seteuid(0) == 0);
41
42 char *umount_args[]
43 = { "umount", "-f", (char *)di->mount_point, NULL };
44
45 assert_no_err(posix_spawn(&pid, "/sbin/umount", NULL, NULL, umount_args, NULL));
46
47 waitpid(pid, &status, 0);
48
49 char *detach_args[]
50 = { "hdik", "-e", (char *)di->disk, NULL };
51
52 posix_spawn_file_actions_t facts;
53 posix_spawn_file_actions_init(&facts);
54 posix_spawn_file_actions_addopen(&facts, STDOUT_FILENO, "/dev/null", O_APPEND, 0);
55 posix_spawn_file_actions_addopen(&facts, STDERR_FILENO, "/dev/null", O_APPEND, 0);
56
57 for (eject_retry = 0; eject_retry < RETRY_MAX; ++eject_retry) {
58 if (!posix_spawn(&pid, "/usr/sbin/hdik", &facts, NULL, detach_args, NULL)) {
59 waitpid(pid, &status, 0);
60 if (WIFEXITED(status) && !WEXITSTATUS(status))
61 break;
62 }
63 sleep(1);
64 }
65
66 posix_spawn_file_actions_destroy(&facts);
67
68 assert(eject_retry != RETRY_MAX);
69
70 struct stat sb;
71
72 if (stat(di->disk, &sb) == -1 && errno == ENOENT) {
73 unlink(di->path);
74 result = true;
75 // We are the last user of di, so free it.
76 free(di->mount_point);
77 free(di->disk);
78 free(di->path);
79 free(di);
80 }
81
82 return result;
83 }
84
85 void *zalloc(__unused void *opaque, uInt items, uInt size)
86 {
87 return malloc(items * size);
88 }
89
90 void zfree(__unused void *opaque, void *ptr)
91 {
92 free(ptr);
93 }
94
95 disk_image_t *disk_image_create(const char *path, disk_image_opts_t *opts)
96 {
97 disk_image_t *di;
98
99 di = calloc(1, sizeof(disk_image_t));
100 assert(di);
101
102 // We need to be root
103 uid_t uid_old;
104 if ((uid_old = geteuid()) != 0) {
105 assert_no_err(seteuid(0));
106 }
107
108 // Extract the image
109 int fd = open(path, O_RDWR | O_TRUNC | O_CREAT, 0666);
110
111 z_stream zs = {
112 .zalloc = zalloc,
113 .zfree = zfree,
114 };
115
116 inflateInit(&zs);
117
118 size_t buf_size = 1024 * 1024;
119 void *out_buf = malloc(buf_size);
120 assert(out_buf);
121
122 zs.next_in = data;
123 zs.avail_in = sizeof(data);
124
125 int ret;
126
127 do {
128 zs.next_out = out_buf;
129 zs.avail_out = (uInt)buf_size;
130
131 ret = inflate(&zs, 0);
132
133 size_t todo = buf_size - zs.avail_out;
134
135 assert(write(fd, out_buf, todo) == (ssize_t)todo);
136 } while (ret == Z_OK);
137
138 assert(ret == Z_STREAM_END);
139
140 di->path = strdup(path);
141
142 // Attach it
143 pid_t pid;
144 char *attach_args[4] = { "hdik", "-nomount", (char *)di->path, NULL };
145 int fds[2];
146
147 assert_no_err(pipe(fds));
148
149 posix_spawn_file_actions_t actions;
150 posix_spawn_file_actions_init(&actions);
151 posix_spawn_file_actions_adddup2(&actions, fds[1], STDOUT_FILENO);
152
153 assert_no_err(posix_spawn(&pid, "/usr/sbin/hdik", &actions, NULL, attach_args, NULL));
154
155 posix_spawn_file_actions_destroy(&actions);
156
157 close(fds[1]);
158
159 char *line, *slice = NULL;
160 size_t lnsz = 64;
161 FILE *fp = fdopen(fds[0], "r");
162
163 line = malloc(lnsz);
164 assert(line);
165
166 while (getline(&line, &lnsz, fp) != -1) {
167 char *first, *second;
168
169 first = strtok(line, " ");
170 assert(first);
171
172 second = strtok(NULL, " ");
173 assert(second);
174
175 if (strstr(second, "GUID"))
176 di->disk = strdup(first);
177
178 // The output of hdik gets truncated, so just search for the leading part of the UUID
179 else if (strstr(second, "48465300-0000-11AA"))
180 slice = strdup(first);
181 }
182
183 int status;
184 assert_with_errno(ignore_eintr(waitpid(pid, &status, 0), -1) == pid);
185 assert(WIFEXITED(status) && !WEXITSTATUS(status));
186
187 assert(di->disk && slice);
188 free(line);
189 fclose(fp);
190
191 // Ensure we have a mount point
192 assert(opts->mount_point);
193
194 // Mount it
195 char *mkdir_args[4] = { "mkdir", "-p", (char *)opts->mount_point, NULL };
196 assert_no_err(posix_spawn(&pid, "/bin/mkdir", NULL, NULL, mkdir_args, NULL));
197
198 assert_with_errno(ignore_eintr(waitpid(pid, &status, 0), -1) == pid);
199 assert(WIFEXITED(status) && !WEXITSTATUS(status));
200
201 posix_spawn_file_actions_t facts;
202 posix_spawn_file_actions_init(&facts);
203 posix_spawn_file_actions_addopen(&facts, STDOUT_FILENO, "/dev/null", O_APPEND, 0);
204 posix_spawn_file_actions_addopen(&facts, STDERR_FILENO, "/dev/null", O_APPEND, 0);
205
206 char *mount_args[4] = { "mount", slice, (char *)opts->mount_point, NULL };
207 assert_no_err(posix_spawn(&pid, "/sbin/mount_hfs", &facts, NULL, mount_args, NULL));
208
209 posix_spawn_file_actions_destroy(&facts);
210 free(slice);
211
212 assert_with_errno(ignore_eintr(waitpid(pid, &status, 0), -1) == pid);
213 assert(WIFEXITED(status) && !WEXITSTATUS(status));
214
215 di->mount_point = strdup(opts->mount_point);
216
217 if (strcmp(path, SHARED_PATH)) { // Don't register a cleanup for the shared image
218 test_cleanup(^ bool {
219 return disk_image_cleanup(di);
220 });
221 }
222
223 assert_no_err(seteuid(uid_old));
224
225 return di;
226 }
227
228 disk_image_t *disk_image_get(void)
229 {
230 disk_image_t *di;
231 struct statfs sfs;
232
233 if (statfs(SHARED_MOUNT, &sfs) == 0) {
234 di = calloc(1, sizeof(*di));
235 di->mount_point = SHARED_MOUNT;
236 di->disk = strdup(sfs.f_mntfromname);
237 di->path = SHARED_PATH;
238
239 // Make sure the di struct is freed when tests are complete.
240 test_cleanup(^ bool {
241 free(di->disk);
242 free(di);
243 return true;
244 });
245 } else {
246 disk_image_opts_t opts = {
247 .mount_point = SHARED_MOUNT
248 };
249 di = disk_image_create(SHARED_PATH, &opts);
250 // Per the contract of disk_image_create(),
251 // di will be freed when disk_image_cleanup() is called,
252 // so don't free it here.
253 }
254
255 return di;
256 }
257
258 #else // !(TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR)
259
260 bool disk_image_cleanup(disk_image_t *di)
261 {
262 char *detach_args[]
263 = { "hdiutil", "detach", (char *)di->disk, "-force", NULL };
264
265 pid_t pid;
266 bool result = false;
267 int eject_retry;
268 int status;
269
270 posix_spawn_file_actions_t facts;
271 posix_spawn_file_actions_init(&facts);
272 posix_spawn_file_actions_addopen(&facts, STDOUT_FILENO, "/dev/null", O_APPEND, 0);
273 posix_spawn_file_actions_addopen(&facts, STDERR_FILENO, "/dev/null", O_APPEND, 0);
274
275 for (eject_retry = 0; eject_retry < RETRY_MAX; ++eject_retry) {
276 if (!posix_spawn(&pid, "/usr/bin/hdiutil", &facts, NULL, detach_args, NULL)) {
277 waitpid(pid, &status, 0);
278 if (WIFEXITED(status) && !WEXITSTATUS(status)) {
279 break;
280 }
281 }
282 sleep(1);
283 }
284
285 posix_spawn_file_actions_destroy(&facts);
286
287 assert(eject_retry != RETRY_MAX);
288
289 struct stat sb;
290
291 if (stat(di->disk, &sb) == -1 && errno == ENOENT) {
292 if (unlink(di->path) && errno == EACCES && !seteuid(0))
293 unlink(di->path);
294 result = true;
295
296 // We are the last user of di, so free it.
297 free(di->mount_point);
298 free(di->disk);
299 free(di->path);
300 free(di);
301 }
302
303 return result;
304 }
305
306 disk_image_t *disk_image_create(const char *path, disk_image_opts_t *opts)
307 {
308 pid_t pid;
309 char sz[32];
310 sprintf(sz, "%llu", opts->size);
311
312 if (opts->mount_point) {
313 assert(!systemx("/bin/mkdir", SYSTEMX_QUIET, "-p", opts->mount_point, NULL));
314 }
315
316 // Start with the basic args
317 char *args[64] = { "hdiutil", "create", (char *)path, "-size", sz, "-ov" };
318
319 if (opts && opts->partition_type) {
320 args[6] = "-partitionType";
321 args[7] = (char *)opts->partition_type;
322 args[8] = NULL;
323
324 posix_spawn_file_actions_t facts;
325 posix_spawn_file_actions_init(&facts);
326 posix_spawn_file_actions_addopen(&facts, STDOUT_FILENO, "/dev/null", O_APPEND, 0);
327
328 assert_no_err(posix_spawn(&pid, "/usr/bin/hdiutil", &facts, NULL,
329 args, NULL));
330
331 posix_spawn_file_actions_destroy(&facts);
332
333 int status;
334 assert_with_errno(ignore_eintr(waitpid(pid, &status, 0), -1));
335
336 assert(WIFEXITED(status) && !WEXITSTATUS(status));
337
338 args[1] = "attach";
339 // args[2] == path
340 args[3] = "-nomount";
341 args[4] = "-plist";
342 args[5] = NULL;
343 } else if (opts && opts->enable_owners) {
344 args[6] = "-fs";
345 args[7] = "HFS+J";
346 args[8] = NULL;
347
348 posix_spawn_file_actions_t facts;
349 posix_spawn_file_actions_init(&facts);
350 posix_spawn_file_actions_addopen(&facts, STDOUT_FILENO, "/dev/null", O_APPEND, 0);
351
352 assert_no_err(posix_spawn(&pid, "/usr/bin/hdiutil", &facts, NULL,
353 args, NULL));
354
355 posix_spawn_file_actions_destroy(&facts);
356
357 int status;
358 assert_with_errno(ignore_eintr(waitpid(pid, &status, 0), -1));
359
360 assert(WIFEXITED(status) && !WEXITSTATUS(status));
361
362 args[1] = "attach";
363 // args[2] == path
364 args[3] = "-plist";
365 args[4] = "-owners";
366 args[5] = "on";
367 if (opts->mount_point) {
368 args[6] = "-mountpoint";
369 args[7] = (char *)opts->mount_point;
370 args[8] = NULL;
371 }
372 else
373 args[6] = NULL;
374 } else {
375 args[6] = "-fs";
376 args[7] = "HFS+J";
377 args[8] = NULL;
378
379 posix_spawn_file_actions_t facts;
380 posix_spawn_file_actions_init(&facts);
381 posix_spawn_file_actions_addopen(&facts, STDOUT_FILENO, "/dev/null", O_APPEND, 0);
382
383 assert_no_err(posix_spawn(&pid, "/usr/bin/hdiutil", &facts, NULL,
384 args, NULL));
385
386 posix_spawn_file_actions_destroy(&facts);
387
388 int status;
389 assert_with_errno(ignore_eintr(waitpid(pid, &status, 0), -1));
390
391 assert(WIFEXITED(status) && !WEXITSTATUS(status));
392
393 args[1] = "attach";
394 // args[2] == path
395 args[3] = "-plist";
396 if (opts->mount_point) {
397 args[4] = "-mountpoint";
398 args[5] = (char *)opts->mount_point;
399 args[6] = NULL;
400 }
401 else
402 args[4] = NULL;
403 }
404
405 int fds[2];
406 assert_no_err(pipe(fds));
407
408 posix_spawn_file_actions_t actions;
409 posix_spawn_file_actions_init(&actions);
410 posix_spawn_file_actions_adddup2(&actions, fds[1], STDOUT_FILENO);
411
412 assert_no_err(posix_spawn(&pid, "/usr/bin/hdiutil", &actions, NULL, args, NULL));
413
414 posix_spawn_file_actions_destroy(&actions);
415
416 close(fds[1]);
417
418 char buffer[4096];
419 size_t amt = 0;
420
421 for (;;) {
422 ssize_t res = read(fds[0], buffer + amt, 4096 - amt);
423
424 if (!res)
425 break;
426
427 if (res == -1 && errno == EINTR)
428 continue;
429
430 assert_with_errno(res > 0);
431
432 amt += res;
433
434 assert(amt < 4096);
435 }
436
437 disk_image_t *di = calloc(1, sizeof(*di));
438
439 di->path = strdup(path);
440
441 @autoreleasepool {
442 NSDictionary *results
443 = [NSPropertyListSerialization propertyListWithData:
444 [NSData dataWithBytesNoCopy:buffer
445 length:amt
446 freeWhenDone:NO]
447 options:0
448 format:NULL
449 error:NULL];
450
451 for (NSDictionary *entity in results[@"system-entities"]) {
452 if (opts && opts->partition_type) {
453 if (!strcmp([entity[@"unmapped-content-hint"] UTF8String],
454 opts->partition_type)
455 || !strcmp([entity[@"content-hint"] UTF8String],
456 opts->partition_type)) {
457 di->disk = strdup([entity[@"dev-entry"] fileSystemRepresentation]);
458 break;
459 }
460 } else if ([entity[@"content-hint"] isEqualToString:@"Apple_HFS"]) {
461 di->mount_point = strdup([entity[@"mount-point"] fileSystemRepresentation]);
462 di->disk = strdup([entity[@"dev-entry"] fileSystemRepresentation]);
463 break;
464 }
465 }
466 }
467
468 int status;
469 assert_with_errno(ignore_eintr(waitpid(pid, &status, 0), -1) == pid);
470 assert(WIFEXITED(status) && !WEXITSTATUS(status));
471
472 assert(di->disk);
473
474 if (strcmp(path, SHARED_PATH)) { // Don't register a cleanup for the shared image
475 test_cleanup(^ bool {
476 return disk_image_cleanup(di);
477 });
478 }
479
480 return di;
481 }
482
483 disk_image_t *disk_image_get(void)
484 {
485 disk_image_t *di;
486 struct statfs sfs;
487
488 if (statfs(SHARED_MOUNT, &sfs) == 0) {
489 di = calloc(1, sizeof(*di));
490
491 di->mount_point = SHARED_MOUNT;
492 di->disk = strdup(sfs.f_mntfromname);
493 di->path = SHARED_PATH;
494
495 // Make sure the di struct is freed when tests are complete.
496 test_cleanup(^ bool {
497 free(di->disk);
498 free(di);
499 return true;
500 });
501 } else {
502 disk_image_opts_t opts = {
503 .size = 4 GB,
504 .mount_point = SHARED_MOUNT
505 };
506 di = disk_image_create(SHARED_PATH, &opts);
507 // Per the contract of disk_image_create(),
508 // di will be freed when disk_image_cleanup() is called,
509 // so don't free it here.
510 }
511
512 return di;
513 }
514
515 #endif // (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR)