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