]>
Commit | Line | Data |
---|---|---|
558d2836 A |
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 | ||
927b7b56 A |
26 | #define RETRY_MAX 3 |
27 | ||
de8ee011 | 28 | #if (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR) |
558d2836 A |
29 | |
30 | #include "dmg.dat" | |
31 | ||
32 | bool disk_image_cleanup(disk_image_t *di) | |
33 | { | |
34 | pid_t pid; | |
ec99dd30 | 35 | bool result = false; |
927b7b56 A |
36 | int eject_retry; |
37 | int status; | |
558d2836 A |
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 | ||
558d2836 A |
47 | waitpid(pid, &status, 0); |
48 | ||
49 | char *detach_args[] | |
50 | = { "hdik", "-e", (char *)di->disk, NULL }; | |
927b7b56 | 51 | |
558d2836 A |
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 | ||
927b7b56 A |
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 | } | |
558d2836 A |
65 | |
66 | posix_spawn_file_actions_destroy(&facts); | |
67 | ||
927b7b56 | 68 | assert(eject_retry != RETRY_MAX); |
558d2836 A |
69 | |
70 | struct stat sb; | |
71 | ||
927b7b56 | 72 | if (stat(di->disk, &sb) == -1 && errno == ENOENT) { |
558d2836 | 73 | unlink(di->path); |
ec99dd30 A |
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); | |
558d2836 | 80 | } |
ec99dd30 A |
81 | |
82 | return result; | |
558d2836 A |
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; | |
927b7b56 | 129 | zs.avail_out = (uInt)buf_size; |
558d2836 A |
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 | ||
de8ee011 A |
191 | // Ensure we have a mount point |
192 | assert(opts->mount_point); | |
193 | ||
558d2836 A |
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 | ||
558d2836 A |
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; | |
ec99dd30 A |
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 | }); | |
558d2836 A |
245 | } else { |
246 | disk_image_opts_t opts = { | |
247 | .mount_point = SHARED_MOUNT | |
248 | }; | |
249 | di = disk_image_create(SHARED_PATH, &opts); | |
ec99dd30 A |
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. | |
558d2836 A |
253 | } |
254 | ||
255 | return di; | |
256 | } | |
257 | ||
de8ee011 | 258 | #else // !(TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR) |
558d2836 A |
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; | |
ec99dd30 | 266 | bool result = false; |
927b7b56 A |
267 | int eject_retry; |
268 | int status; | |
558d2836 A |
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 | ||
927b7b56 A |
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 | } | |
558d2836 A |
284 | |
285 | posix_spawn_file_actions_destroy(&facts); | |
286 | ||
927b7b56 | 287 | assert(eject_retry != RETRY_MAX); |
558d2836 A |
288 | |
289 | struct stat sb; | |
290 | ||
927b7b56 | 291 | if (stat(di->disk, &sb) == -1 && errno == ENOENT) { |
558d2836 A |
292 | if (unlink(di->path) && errno == EACCES && !seteuid(0)) |
293 | unlink(di->path); | |
ec99dd30 A |
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); | |
558d2836 | 301 | } |
ec99dd30 A |
302 | |
303 | return result; | |
558d2836 A |
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; | |
ec99dd30 A |
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 | }); | |
558d2836 A |
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); | |
ec99dd30 A |
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. | |
558d2836 A |
510 | } |
511 | ||
512 | return di; | |
513 | } | |
514 | ||
de8ee011 | 515 | #endif // (TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR) |