]> git.saurik.com Git - apple/hfs.git/blob - tests/disk-image.m
e7bb56c1847dd1be4c884114d37ca5976e1daa5f
[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_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