]>
Commit | Line | Data |
---|---|---|
927b7b56 A |
1 | // |
2 | // Copyright (c) 2019-2019 Apple Inc. All rights reserved. | |
3 | // | |
4 | // generate-compressed-image.c - Generates compressed disk images for | |
5 | // file-system type passed as argument. | |
6 | // | |
7 | ||
8 | #include <assert.h> | |
9 | #include <fcntl.h> | |
10 | #include <stdio.h> | |
11 | #include <spawn.h> | |
12 | #include <string.h> | |
13 | #include <stdlib.h> | |
14 | #include <stdbool.h> | |
15 | #include <sysexits.h> | |
16 | #include <err.h> | |
17 | #include <errno.h> | |
18 | #include <unistd.h> | |
19 | #include <zlib.h> | |
20 | ||
21 | // | |
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. | |
27 | // | |
28 | ||
29 | // | |
30 | // Name template for the temporary directory where we create the file-system | |
31 | // image before we can compress it. | |
32 | // | |
33 | #define GEN_COM_IMAGE_TMP_DIR "/tmp/generate_compressed_image.XXXXXXXX" | |
34 | ||
35 | // | |
36 | // Name of the temporary file-system image. | |
37 | // | |
38 | #define GEN_COM_IMAGE_TMP_FILENAME "img.sparseimage" | |
39 | ||
40 | // | |
41 | // Path to hdiutil utility inside build enviornment. | |
42 | // | |
43 | #define GEN_COM_IMAGE_HDIUTIL_PATH "/usr/bin/hdiutil" | |
44 | ||
45 | enum { | |
46 | ||
47 | // | |
48 | // Count of fixed number of argumets needed to call hdiutil, additional | |
49 | // options are passed by the caller. | |
50 | // | |
51 | FIXED_NR_ARGUMENTS = 4, | |
52 | ADDITIONAL_NR_ARGUMENTS = 10, | |
53 | BYTES_PER_LINE_MASK = 0xF, | |
54 | }; | |
55 | ||
56 | // | |
57 | // Valid file-system types that is supported by this program. | |
58 | // | |
59 | static bool | |
60 | is_fstype_valid(const char *fs_type) | |
61 | { | |
62 | int idx; | |
63 | ||
64 | const char *const VALID_FSTYPES[] = { | |
65 | "JHFS+", | |
66 | "APFS", | |
67 | "EXFAT", | |
68 | "FAT32", | |
69 | ||
70 | NULL | |
71 | }; | |
72 | ||
73 | for (idx = 0; VALID_FSTYPES[idx] != NULL; idx++) { | |
74 | if (strcmp(fs_type, VALID_FSTYPES[idx]) == 0) { | |
75 | return true; | |
76 | } | |
77 | } | |
78 | return false; | |
79 | } | |
80 | ||
81 | int | |
82 | main(int argc, char *argv[]) | |
83 | { | |
84 | pid_t pid, child_state_changed; | |
85 | z_stream c_stream; | |
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]); | |
94 | ||
95 | // | |
96 | // Disable stdout buffering. | |
97 | // | |
98 | setvbuf(stdout, NULL, _IONBF, 0); | |
99 | ||
100 | // | |
101 | // Validate that we have correct number of arguments passed (minimal, | |
102 | // this is only called from inside build internally). | |
103 | // | |
104 | if (argc != (ADDITIONAL_NR_ARGUMENTS + 1)) { | |
105 | ||
106 | err_usage: | |
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); | |
110 | return EXIT_FAILURE; | |
111 | } | |
112 | ||
113 | // | |
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. | |
117 | // | |
118 | if (!is_fstype_valid(argv[6])) { | |
119 | fprintf(stderr, "Unknown file-system type %s\n", argv[6]); | |
120 | goto err_usage; | |
121 | } | |
122 | fs_type = argv[6]; | |
123 | if (!strcmp(argv[6], "JHFS+")) { | |
124 | fs_type = "JHFS"; | |
125 | } | |
126 | ||
127 | // | |
128 | // First we create a temporary directory to host our newly created | |
129 | // disk image in the build environment. | |
130 | // | |
131 | tmp_dir = mkdtemp(tmp_dir_name_template); | |
132 | if (!tmp_dir) | |
133 | err(EX_NOINPUT, "mkdtemp failed"); | |
134 | ||
135 | // | |
136 | // Path where we want to keep our temporary disk image. | |
137 | // | |
138 | asprintf(&tmp_disk_image_path, "%s/"GEN_COM_IMAGE_TMP_FILENAME, | |
139 | tmp_dir); | |
140 | ||
141 | // | |
142 | // Set up the fixed command line parameters to be passed to the hdiutil | |
143 | // child process. | |
144 | // | |
145 | // - program name. | |
146 | // - create a new disk-image. | |
147 | // - path of disk-image file to create. | |
148 | // - silent mode. | |
149 | // | |
150 | args[0] = "hdiutil"; | |
151 | args[1] = "create"; | |
152 | args[2] = tmp_disk_image_path; | |
153 | args[3] = "-quiet"; | |
154 | ||
155 | // | |
156 | // Copy the additional arguments passed by the caller needed for | |
157 | // hdiutil. | |
158 | // | |
159 | for (idx = 1; idx < argc; ++idx) { | |
160 | args[idx + FIXED_NR_ARGUMENTS - 1] = argv[idx]; | |
161 | } | |
162 | args[idx + FIXED_NR_ARGUMENTS - 1] = NULL; | |
163 | ||
164 | // | |
165 | // Spawn the hdiutil as a child process and wait for its completion. | |
166 | // | |
167 | ret = posix_spawn(&pid, GEN_COM_IMAGE_HDIUTIL_PATH, NULL, NULL, | |
168 | args, NULL); | |
169 | if (ret) { | |
170 | errno = ret; | |
171 | err(EX_OSERR, "posix_spawn failed"); | |
172 | } | |
173 | ||
174 | // | |
175 | // Wait for the child process to finish. | |
176 | // | |
177 | do { | |
178 | errno = 0; | |
179 | child_state_changed = waitpid(pid, &status, 0); | |
180 | } while (child_state_changed == -1 && errno == EINTR); | |
181 | ||
182 | if (child_state_changed == -1) { | |
183 | err(EX_OSERR, "waitpid failed"); | |
184 | } | |
185 | if (!WIFEXITED(status) || WEXITSTATUS(status)) { | |
186 | fprintf(stderr, "hdiutil failed, status %d", status); | |
187 | exit(EXIT_FAILURE); | |
188 | } | |
189 | ||
190 | // | |
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. | |
194 | // | |
195 | fd = open(tmp_disk_image_path, O_RDONLY); | |
196 | if (fd == -1) { | |
197 | err(EX_NOINPUT, "open failed for file %s", | |
198 | tmp_disk_image_path); | |
199 | } | |
200 | ||
201 | // | |
202 | // Initialize the compressed stream of bytes we will write out. | |
203 | // | |
204 | c_stream = (z_stream) { | |
205 | .zalloc = Z_NULL, | |
206 | .zfree = Z_NULL, | |
207 | .opaque = Z_NULL, | |
208 | }; | |
209 | ||
210 | ret = deflateInit(&c_stream, Z_DEFAULT_COMPRESSION); | |
211 | if (ret != Z_OK) { | |
212 | err(EX_SOFTWARE, "deflateInit faile, ret %d", ret); | |
213 | } | |
214 | ||
215 | compressed_out_buf = malloc(chunk_size); | |
216 | if (!compressed_out_buf) { | |
217 | err(EX_OSERR, "malloc faliled for compressed_out_buf"); | |
218 | } | |
219 | ||
220 | uncompressed_in_buf = malloc(chunk_size); | |
221 | if (!uncompressed_in_buf) { | |
222 | err(EX_OSERR, "malloc faliled for uncompressed_in_buf"); | |
223 | } | |
224 | ||
225 | fprintf(stdout, "unsigned char %s_data[] = {", fs_type); | |
226 | ||
227 | offset = 0; | |
228 | flush = Z_NO_FLUSH; | |
229 | do { | |
230 | ssize_t bytes_read; | |
231 | ||
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); | |
237 | } | |
238 | ||
239 | // | |
240 | // Set the stream's input buffer and number of input bytes | |
241 | // available to be compressed. | |
242 | // | |
243 | c_stream.next_in = uncompressed_in_buf; | |
244 | c_stream.avail_in = bytes_read; | |
245 | ||
246 | // | |
247 | // If we have reached the end of the file, we have to flush the | |
248 | // compressed stream. | |
249 | // | |
250 | if (!bytes_read) { | |
251 | flush = Z_FINISH; | |
252 | } | |
253 | ||
254 | // | |
255 | // Run deflate() on input until output buffer is not full, | |
256 | // finish compression if all of source has been read in. | |
257 | // | |
258 | do { | |
259 | unsigned written; | |
260 | ||
261 | c_stream.avail_out = chunk_size; | |
262 | c_stream.next_out = compressed_out_buf; | |
263 | ||
264 | ret = deflate(&c_stream, flush); | |
265 | assert(ret != Z_STREAM_ERROR); | |
266 | ||
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]); | |
273 | ++offset; | |
274 | } | |
275 | ||
276 | } while (c_stream.avail_out == 0); | |
277 | ||
278 | // | |
279 | // All input should be used. | |
280 | // | |
281 | assert(c_stream.avail_in == 0); | |
282 | ||
283 | } while (flush != Z_FINISH); | |
284 | ||
285 | // | |
286 | // Stream will be complete. | |
287 | // | |
288 | assert(ret == Z_STREAM_END); | |
289 | (void)close(fd); | |
290 | ||
291 | // | |
292 | // stdout is line buffered by default and this should flush it. | |
293 | // | |
294 | fprintf(stdout, "\n};\n"); | |
295 | ||
296 | // | |
297 | // Clean up | |
298 | // | |
299 | (void)deflateEnd(&c_stream); | |
300 | unlink(tmp_disk_image_path); | |
301 | free(tmp_disk_image_path); | |
302 | rmdir(tmp_dir); | |
303 | } |