]> git.saurik.com Git - apple/hfs.git/blob - tests/generate-compressed-image.c
hfs-556.41.1.tar.gz
[apple/hfs.git] / tests / generate-compressed-image.c
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 }