2 // Copyright (c) 2019-2019 Apple Inc. All rights reserved.
4 // test-lf-cs-plugin.c - Implements unit test for livefiles Apple_CoreStorage
9 #include "test-utils.h"
15 #include <sys/param.h>
17 #include <TargetConditionals.h>
20 // Enable this test on iOS and no other iOS-style platforms.
25 // Headers generated at build time. Containes compressed disk images for the
26 // corresponding file-system formats.
28 #include "JHFS+-dmg.dat"
29 #include "APFS-dmg.dat"
30 #include "EXFAT-dmg.dat"
31 #include "FAT32-dmg.dat"
33 TEST(lf_cs_plugin
, .run_as_root
= true)
35 #define LF_CS_PLUGIN_TEST_DMG "/tmp/lf_cs_plugin_test.sparseimage"
36 #define LF_CS_PLUGIN_PATH_TO_HDIK "/usr/sbin/hdik"
37 #define LF_CS_PLUGIN_INSTALL_PATH "/AppleInternal/CoreOS/tests/hfs/"
40 // The output of hdik gets truncated, so just search for the leading part of
41 // the UUID and partition scheme names.
43 #define LF_CSP_HFS_UUID "48465300-0000-11AA-AA11"
44 #define LF_CSP_APFS_UUID "7C3457EF-0000-11AA-AA11"
45 #define LF_CSP_EXFAT_PART_SCHEME "Windows_NTFS"
46 #define LF_CSP_FAT32_PART_SCHEME "DOS_FAT_32"
49 // Enums describing file-system formats.
59 // Local struct describing the disk image on disk.
62 char *path
; // Path to disk-image on disk.
63 char *disk
; // Path to dev node after disk-image is attached.
64 char *slice
; // Path to dev node slice after disk-image is attached.
65 } lf_csp_disk_image_t
;
68 // lf_csp_disk_image_cleanup - disattach disk image from the DiskImages driver
69 // and unlink the disk image file on disk.
72 lf_csp_disk_image_cleanup(lf_csp_disk_image_t
*di
)
75 posix_spawn_file_actions_t facts
;
80 char *detach_args
[] = { "hdik", "-e", (char *)di
->disk
, NULL
};
83 ret
= posix_spawn_file_actions_init(&facts
);
86 (void)posix_spawn_file_actions_addopen(&facts
, STDOUT_FILENO
,
87 "/dev/null", O_APPEND
, 0);
88 (void)posix_spawn_file_actions_addopen(&facts
, STDERR_FILENO
,
89 "/dev/null", O_APPEND
, 0);
92 assert_no_err(posix_spawn(&pid
, LF_CS_PLUGIN_PATH_TO_HDIK
, &facts
,
93 NULL
, detach_args
, NULL
));
96 posix_spawn_file_actions_destroy(&facts
);
99 assert_with_errno(ignore_eintr(waitpid(pid
, &status
, 0), -1) == pid
);
101 if (WIFEXITED(status
) &&
102 !WEXITSTATUS(status
) &&
103 (stat(di
->disk
, &sb
) == -1) &&
118 // lf_csp_disk_image_create - create a new disk image file on disk and attach
119 // disk images directly to the DiskImages driver.
121 // This routine reads the compressed file system image (generated during build)
122 // based on the passed file-system type parameter. The compressed image is
123 // decompressd and written to disk and then attached to directly to the
124 // DiskImages driver.
126 // Please NOTE: The created image is hooked up to the test cleanup list thus
127 // gets cleaned up automatically after the test, even on failures. Explicit
128 // cleanup is not needed.
130 static lf_csp_disk_image_t
*
131 lf_csp_disk_image_create(const char *path
, lf_csp_fstype_t fs_type
)
135 lf_csp_disk_image_t
*di
;
137 posix_spawn_file_actions_t actions
;
141 int ret
, fd
, fds
[2], status
;
142 void *uncompressed_out_buf
;
143 const size_t chunk_size
= (1ULL << 20);
144 char *attach_args
[4], *line
, *part_scheme
, *uuid_or_partid_str
;
147 // We need to ensure that we are root.
149 if ((uid_old
= geteuid()) != 0) {
150 assert_no_err(seteuid(0));
153 di
= calloc(1, sizeof(lf_csp_disk_image_t
));
157 // We need to extract the compressed image into the passed image
158 // file path, thus we open it for writing.
160 fd
= open(path
, O_RDWR
| O_TRUNC
| O_CREAT
, 0666);
161 assert_with_errno(fd
!= -1);
163 u_stream
= (z_stream
) {
169 ret
= inflateInit(&u_stream
);
171 assert_fail("inflateInit failed\n");
174 uncompressed_out_buf
= malloc(chunk_size
);
175 assert(uncompressed_out_buf
);
177 uuid_or_partid_str
= NULL
;
181 u_stream
.next_in
= JHFS_data
;
182 u_stream
.avail_in
= sizeof(JHFS_data
);
183 uuid_or_partid_str
= LF_CSP_HFS_UUID
;
184 part_scheme
= "GUID";
188 u_stream
.next_in
= APFS_data
;
189 u_stream
.avail_in
= sizeof(APFS_data
);
190 uuid_or_partid_str
= LF_CSP_APFS_UUID
;
191 part_scheme
= "GUID";
195 u_stream
.next_in
= FAT32_data
;
196 u_stream
.avail_in
= sizeof(FAT32_data
);
197 uuid_or_partid_str
= LF_CSP_FAT32_PART_SCHEME
;
198 part_scheme
= "FDisk";
202 u_stream
.next_in
= EXFAT_data
;
203 u_stream
.avail_in
= sizeof(EXFAT_data
);
204 uuid_or_partid_str
= LF_CSP_EXFAT_PART_SCHEME
;
205 part_scheme
= "FDisk";
209 assert_fail("passed unkown file-system type\n");
213 ssize_t bytes_to_write
;
215 u_stream
.next_out
= uncompressed_out_buf
;
216 u_stream
.avail_out
= chunk_size
;
218 ret
= inflate(&u_stream
, Z_NO_FLUSH
);
219 assert(ret
!= Z_STREAM_ERROR
);
221 bytes_to_write
= chunk_size
- u_stream
.avail_out
;
222 assert(write(fd
, uncompressed_out_buf
, bytes_to_write
) ==
223 (ssize_t
)bytes_to_write
);
224 } while (ret
== Z_OK
);
225 assert(ret
== Z_STREAM_END
);
226 (void)inflateEnd(&u_stream
);
229 // Update the disk image path.
231 di
->path
= strdup(path
);
234 // Attach the created disk image directly to the DiskImage driver.
236 attach_args
[0] = "hdik";
237 attach_args
[1] = "-nomount";
238 attach_args
[2] = (char *)di
->path
;
239 attach_args
[3] = NULL
;
241 assert_no_err(pipe(fds
));
242 ret
= posix_spawn_file_actions_init(&actions
);
244 assert_fail("Failed to init file actions, error %d\n", ret
);
247 ret
= posix_spawn_file_actions_adddup2(&actions
, fds
[1],
250 assert_fail("Failed to adddup file action, error %d\n",
254 assert_no_err(posix_spawn(&pid
, LF_CS_PLUGIN_PATH_TO_HDIK
, &actions
,
255 NULL
, attach_args
, NULL
));
257 (void)posix_spawn_file_actions_destroy(&actions
);
261 // Read the output from `hdik` and populate the disk image's dev node
262 // after it is attached to DiskImage driver.
270 fp
= fdopen(fds
[0], "r");
271 while (getline(&line
, &lnsz
, fp
) != -1) {
272 char *disk_path
, *uuid_or_partid
;
274 disk_path
= strtok(line
, " ");
277 uuid_or_partid
= strtok(NULL
, " ");
278 assert(uuid_or_partid
);
280 if (strstr(uuid_or_partid
, part_scheme
))
281 di
->disk
= strdup(disk_path
);
282 else if (strstr(uuid_or_partid
, uuid_or_partid_str
))
283 di
->slice
= strdup(disk_path
);
286 assert_with_errno(ignore_eintr(waitpid(pid
, &status
, 0), -1) == pid
);
287 assert(WIFEXITED(status
) && !WEXITSTATUS(status
));
289 assert(di
->disk
&& di
->slice
);
294 // Place this attached image in the cleanup list so that it can be
295 // disattached after the test is run.
297 test_cleanup(^ bool { return lf_csp_disk_image_cleanup(di
); });
300 // Restore back the old uid.
302 assert_no_err(seteuid(uid_old
));
307 run_lf_cs_plugin(__unused test_ctx_t
*ctx
)
309 lf_csp_disk_image_t
*di
;
312 assert(asprintf(&tester_path
, "%s/livefiles_cs_tester",
313 LF_CS_PLUGIN_INSTALL_PATH
) > 0);
316 // Call livefiles Apple_CoreStorage plugin with all our file-system
319 // Kindly NOTE: We don't explicitly free the disk-images after use
320 // because preparing the disk image with lf_csp_disk_image_create() we
321 // place the image in a cleanup list and they get cleaned up after
322 // test, regardless of the test failures.
324 // Test livefiles Apple_CoreStorage plugin with AFPS disk image.
326 di
= lf_csp_disk_image_create(LF_CS_PLUGIN_TEST_DMG
, APFS
);
327 assert(!systemx(tester_path
, "APFS", di
->slice
, NULL
));
330 // Test livefiles Apple_CoreStorage plugin with HFS+ disk image.
332 di
= lf_csp_disk_image_create(LF_CS_PLUGIN_TEST_DMG
, JHFS
);
333 assert(!systemx(tester_path
, "JHFS", di
->slice
, NULL
));
336 // Test livefiles Apple_CoreStorage plugin with EXFAT disk image.
338 di
= lf_csp_disk_image_create(LF_CS_PLUGIN_TEST_DMG
, EXFAT
);
339 assert(!systemx(tester_path
, "EXFAT", di
->slice
, NULL
));
342 // Test livefiles Apple_CoreStorage plugin with FAT32 disk image.
344 di
= lf_csp_disk_image_create(LF_CS_PLUGIN_TEST_DMG
, FAT32
);
345 assert(!systemx(tester_path
, "FAT32", di
->slice
, NULL
));
350 #endif /* TARGET_OS_IOS */