2 // Copyright (c) 2019-2019 Apple Inc. All rights reserved.
4 // generate-compressed-image.c - Generates compressed disk images for
5 // file-system type passed as argument.
22 // Rationale: We don't have `hdiutil` in iOS runtime and thus cannot generate
23 // disk-images for test inside iOS. We assume that the build enviornment has
24 // `hdiutil` and thus generate custom file-system images (passed as argument)
25 // during build, compress it using deflate and finally use the compressed byte
26 // stream to generate files which can then be be used from iOS runtime.
30 // Name template for the temporary directory where we create the file-system
31 // image before we can compress it.
33 #define GEN_COM_IMAGE_TMP_DIR "/tmp/generate_compressed_image.XXXXXXXX"
36 // Name of the temporary file-system image.
38 #define GEN_COM_IMAGE_TMP_FILENAME "img.sparseimage"
41 // Path to hdiutil utility inside build enviornment.
43 #define GEN_COM_IMAGE_HDIUTIL_PATH "/usr/bin/hdiutil"
48 // Count of fixed number of argumets needed to call hdiutil, additional
49 // options are passed by the caller.
51 FIXED_NR_ARGUMENTS
= 4,
52 ADDITIONAL_NR_ARGUMENTS
= 10,
53 BYTES_PER_LINE_MASK
= 0xF,
57 // Valid file-system types that is supported by this program.
60 is_fstype_valid(const char *fs_type
)
64 const char *const VALID_FSTYPES
[] = {
73 for (idx
= 0; VALID_FSTYPES
[idx
] != NULL
; idx
++) {
74 if (strcmp(fs_type
, VALID_FSTYPES
[idx
]) == 0) {
82 main(int argc
, char *argv
[])
84 pid_t pid
, child_state_changed
;
86 int fd
, idx
, ret
, status
, flush
, offset
;
87 char *tmp_dir
, *tmp_disk_image_path
, *fs_type
;
88 unsigned char *compressed_out_buf
, *uncompressed_in_buf
;
89 const size_t chunk_size
= (1ULL << 20);
90 char *args
[argc
+ FIXED_NR_ARGUMENTS
];
91 char tmp_dir_name_template
[] = GEN_COM_IMAGE_TMP_DIR
;
92 const char *progname
= (progname
= strrchr(argv
[0], '/')) ?
93 progname
+=1 : (progname
= argv
[0]);
96 // Disable stdout buffering.
98 setvbuf(stdout
, NULL
, _IONBF
, 0);
101 // Validate that we have correct number of arguments passed (minimal,
102 // this is only called from inside build internally).
104 if (argc
!= (ADDITIONAL_NR_ARGUMENTS
+ 1)) {
107 fprintf(stderr
, "Usage: %s -size [size-arg] -type "
108 "[type-arg] -fs [APFS|JHFS+|EXFAT|FAT32] "
109 "-uid [uid-arg] -gid [gid-arg]\n", progname
);
114 // Just to simplify this program and to avoid parsing input aruments
115 // we assume that the arguments are passed in order and 7th argument
116 // has the file-system type. We confirm this now.
118 if (!is_fstype_valid(argv
[6])) {
119 fprintf(stderr
, "Unknown file-system type %s\n", argv
[6]);
123 if (!strcmp(argv
[6], "JHFS+")) {
128 // First we create a temporary directory to host our newly created
129 // disk image in the build environment.
131 tmp_dir
= mkdtemp(tmp_dir_name_template
);
133 err(EX_NOINPUT
, "mkdtemp failed");
136 // Path where we want to keep our temporary disk image.
138 asprintf(&tmp_disk_image_path
, "%s/"GEN_COM_IMAGE_TMP_FILENAME
,
142 // Set up the fixed command line parameters to be passed to the hdiutil
146 // - create a new disk-image.
147 // - path of disk-image file to create.
152 args
[2] = tmp_disk_image_path
;
156 // Copy the additional arguments passed by the caller needed for
159 for (idx
= 1; idx
< argc
; ++idx
) {
160 args
[idx
+ FIXED_NR_ARGUMENTS
- 1] = argv
[idx
];
162 args
[idx
+ FIXED_NR_ARGUMENTS
- 1] = NULL
;
165 // Spawn the hdiutil as a child process and wait for its completion.
167 ret
= posix_spawn(&pid
, GEN_COM_IMAGE_HDIUTIL_PATH
, NULL
, NULL
,
171 err(EX_OSERR
, "posix_spawn failed");
175 // Wait for the child process to finish.
179 child_state_changed
= waitpid(pid
, &status
, 0);
180 } while (child_state_changed
== -1 && errno
== EINTR
);
182 if (child_state_changed
== -1) {
183 err(EX_OSERR
, "waitpid failed");
185 if (!WIFEXITED(status
) || WEXITSTATUS(status
)) {
186 fprintf(stderr
, "hdiutil failed, status %d", status
);
191 // We have successfully create the disk image, now we have to
192 // open this disk image, read and finally write out a compess
193 // stream of this disk image to a data file.
195 fd
= open(tmp_disk_image_path
, O_RDONLY
);
197 err(EX_NOINPUT
, "open failed for file %s",
198 tmp_disk_image_path
);
202 // Initialize the compressed stream of bytes we will write out.
204 c_stream
= (z_stream
) {
210 ret
= deflateInit(&c_stream
, Z_DEFAULT_COMPRESSION
);
212 err(EX_SOFTWARE
, "deflateInit faile, ret %d", ret
);
215 compressed_out_buf
= malloc(chunk_size
);
216 if (!compressed_out_buf
) {
217 err(EX_OSERR
, "malloc faliled for compressed_out_buf");
220 uncompressed_in_buf
= malloc(chunk_size
);
221 if (!uncompressed_in_buf
) {
222 err(EX_OSERR
, "malloc faliled for uncompressed_in_buf");
225 fprintf(stdout
, "unsigned char %s_data[] = {", fs_type
);
232 bytes_read
= read(fd
, uncompressed_in_buf
, chunk_size
);
233 if (bytes_read
== -1) {
234 (void)deflateEnd(&c_stream
);
235 err(EX_OSERR
, "read failed for file %s\n",
236 tmp_disk_image_path
);
240 // Set the stream's input buffer and number of input bytes
241 // available to be compressed.
243 c_stream
.next_in
= uncompressed_in_buf
;
244 c_stream
.avail_in
= bytes_read
;
247 // If we have reached the end of the file, we have to flush the
248 // compressed stream.
255 // Run deflate() on input until output buffer is not full,
256 // finish compression if all of source has been read in.
261 c_stream
.avail_out
= chunk_size
;
262 c_stream
.next_out
= compressed_out_buf
;
264 ret
= deflate(&c_stream
, flush
);
265 assert(ret
!= Z_STREAM_ERROR
);
267 written
= chunk_size
- c_stream
.avail_out
;
268 for (idx
= 0; idx
< written
; ++idx
) {
269 if (!(offset
& BYTES_PER_LINE_MASK
))
270 fprintf(stdout
, "\n ");
271 fprintf(stdout
, "0x%02x, ",
272 compressed_out_buf
[idx
]);
276 } while (c_stream
.avail_out
== 0);
279 // All input should be used.
281 assert(c_stream
.avail_in
== 0);
283 } while (flush
!= Z_FINISH
);
286 // Stream will be complete.
288 assert(ret
== Z_STREAM_END
);
292 // stdout is line buffered by default and this should flush it.
294 fprintf(stdout
, "\n};\n");
299 (void)deflateEnd(&c_stream
);
300 unlink(tmp_disk_image_path
);
301 free(tmp_disk_image_path
);