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