]> git.proxmox.com Git - mirror_edk2.git/blob - BaseTools/Source/C/BrotliCompress/tools/brotli.c
b0246e1ecf864ab6a5cb5626d2997928ab2f276c
[mirror_edk2.git] / BaseTools / Source / C / BrotliCompress / tools / brotli.c
1 /* Copyright 2014 Google Inc. All Rights Reserved.
2
3 Distributed under MIT license.
4 See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
5 */
6
7 /* Command line interface for Brotli library. */
8
9 #include <errno.h>
10 #include <fcntl.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <sys/stat.h>
15 #include <sys/types.h>
16 #include <time.h>
17
18 #include "../common/constants.h"
19 #include "../common/version.h"
20 #include <brotli/decode.h>
21 #include <brotli/encode.h>
22
23 #if !defined(_WIN32)
24 #include <unistd.h>
25 #include <utime.h>
26 #define MAKE_BINARY(FILENO) (FILENO)
27 #else
28 #include <io.h>
29 #include <share.h>
30 #include <sys/utime.h>
31
32 #define MAKE_BINARY(FILENO) (_setmode((FILENO), _O_BINARY), (FILENO))
33
34 #if !defined(__MINGW32__)
35 #define STDIN_FILENO _fileno(stdin)
36 #define STDOUT_FILENO _fileno(stdout)
37 #define S_IRUSR S_IREAD
38 #define S_IWUSR S_IWRITE
39 #endif
40
41 #define fdopen _fdopen
42 #define isatty _isatty
43 #define unlink _unlink
44 #define utimbuf _utimbuf
45 #define utime _utime
46
47 #define fopen ms_fopen
48 #define open ms_open
49
50 #define chmod(F, P) (0)
51 #define chown(F, O, G) (0)
52
53 #if defined(_MSC_VER) && (_MSC_VER >= 1400)
54 #define fseek _fseeki64
55 #define ftell _ftelli64
56 #endif
57
58 static FILE* ms_fopen(const char* filename, const char* mode) {
59 FILE* result = 0;
60 fopen_s(&result, filename, mode);
61 return result;
62 }
63
64 static int ms_open(const char* filename, int oflag, int pmode) {
65 int result = -1;
66 _sopen_s(&result, filename, oflag | O_BINARY, _SH_DENYNO, pmode);
67 return result;
68 }
69 #endif /* WIN32 */
70
71 typedef enum {
72 COMMAND_COMPRESS,
73 COMMAND_DECOMPRESS,
74 COMMAND_HELP,
75 COMMAND_INVALID,
76 COMMAND_TEST_INTEGRITY,
77 COMMAND_NOOP,
78 COMMAND_VERSION
79 } Command;
80
81 #define DEFAULT_LGWIN 24
82 #define DEFAULT_SUFFIX ".br"
83 #define MAX_OPTIONS 20
84 #define DECODE_HEADER_SIZE 0x10
85 #define GAP_MEM_BLOCK 0x1000
86
87 typedef struct {
88 /* Parameters */
89 int gmem;
90 int quality;
91 int lgwin;
92 BROTLI_BOOL force_overwrite;
93 BROTLI_BOOL junk_source;
94 BROTLI_BOOL copy_stat;
95 BROTLI_BOOL verbose;
96 BROTLI_BOOL write_to_stdout;
97 BROTLI_BOOL test_integrity;
98 BROTLI_BOOL decompress;
99 BROTLI_BOOL large_window;
100 const char* output_path;
101 const char* suffix;
102 int not_input_indices[MAX_OPTIONS];
103 size_t longest_path_len;
104 size_t input_count;
105
106 /* Inner state */
107 int argc;
108 char** argv;
109 char* modified_path; /* Storage for path with appended / cut suffix */
110 int iterator;
111 int ignore;
112 BROTLI_BOOL iterator_error;
113 uint8_t* buffer;
114 uint8_t* input;
115 uint8_t* output;
116 const char* current_input_path;
117 const char* current_output_path;
118 int64_t input_file_length; /* -1, if impossible to calculate */
119 FILE* fin;
120 FILE* fout;
121
122 /* I/O buffers */
123 size_t available_in;
124 const uint8_t* next_in;
125 size_t available_out;
126 uint8_t* next_out;
127 } Context;
128
129 /* Parse up to 5 decimal digits. */
130 static BROTLI_BOOL ParseInt(const char* s, int low, int high, int* result) {
131 int value = 0;
132 int i;
133 for (i = 0; i < 5; ++i) {
134 char c = s[i];
135 if (c == 0) break;
136 if (s[i] < '0' || s[i] > '9') return BROTLI_FALSE;
137 value = (10 * value) + (c - '0');
138 }
139 if (i == 0) return BROTLI_FALSE;
140 if (i > 1 && s[0] == '0') return BROTLI_FALSE;
141 if (s[i] != 0) return BROTLI_FALSE;
142 if (value < low || value > high) return BROTLI_FALSE;
143 *result = value;
144 return BROTLI_TRUE;
145 }
146
147 /* Returns "base file name" or its tail, if it contains '/' or '\'. */
148 static const char* FileName(const char* path) {
149 const char* separator_position = strrchr(path, '/');
150 if (separator_position) path = separator_position + 1;
151 separator_position = strrchr(path, '\\');
152 if (separator_position) path = separator_position + 1;
153 return path;
154 }
155
156 /* Detect if the program name is a special alias that infers a command type. */
157 static Command ParseAlias(const char* name) {
158 /* TODO: cast name to lower case? */
159 const char* unbrotli = "unbrotli";
160 size_t unbrotli_len = strlen(unbrotli);
161 name = FileName(name);
162 /* Partial comparison. On Windows there could be ".exe" suffix. */
163 if (strncmp(name, unbrotli, unbrotli_len) == 0) {
164 char terminator = name[unbrotli_len];
165 if (terminator == 0 || terminator == '.') return COMMAND_DECOMPRESS;
166 }
167 return COMMAND_COMPRESS;
168 }
169
170 static Command ParseParams(Context* params) {
171 int argc = params->argc;
172 char** argv = params->argv;
173 int i;
174 int next_option_index = 0;
175 size_t input_count = 0;
176 size_t longest_path_len = 1;
177 BROTLI_BOOL command_set = BROTLI_FALSE;
178 BROTLI_BOOL gmem_set = BROTLI_FALSE;
179 BROTLI_BOOL quality_set = BROTLI_FALSE;
180 BROTLI_BOOL output_set = BROTLI_FALSE;
181 BROTLI_BOOL keep_set = BROTLI_FALSE;
182 BROTLI_BOOL lgwin_set = BROTLI_FALSE;
183 BROTLI_BOOL suffix_set = BROTLI_FALSE;
184 BROTLI_BOOL after_dash_dash = BROTLI_FALSE;
185 Command command = ParseAlias(argv[0]);
186
187 for (i = 1; i < argc; ++i) {
188 const char* arg = argv[i];
189 /* C99 5.1.2.2.1: "members argv[0] through argv[argc-1] inclusive shall
190 contain pointers to strings"; NULL and 0-length are not forbidden. */
191 size_t arg_len = arg ? strlen(arg) : 0;
192
193 if (arg_len == 0) {
194 params->not_input_indices[next_option_index++] = i;
195 continue;
196 }
197
198 /* Too many options. The expected longest option list is:
199 "-q 0 -w 10 -o f -D d -S b -d -f -k -n -v --", i.e. 16 items in total.
200 This check is an additional guard that is never triggered, but provides
201 a guard for future changes. */
202 if (next_option_index > (MAX_OPTIONS - 2)) {
203 fprintf(stderr, "too many options passed\n");
204 return COMMAND_INVALID;
205 }
206
207 /* Input file entry. */
208 if (after_dash_dash || arg[0] != '-' || arg_len == 1) {
209 input_count++;
210 if (longest_path_len < arg_len) longest_path_len = arg_len;
211 continue;
212 }
213
214 /* Not a file entry. */
215 params->not_input_indices[next_option_index++] = i;
216
217 /* '--' entry stop parsing arguments. */
218 if (arg_len == 2 && arg[1] == '-') {
219 after_dash_dash = BROTLI_TRUE;
220 continue;
221 }
222
223 /* Simple / coalesced options. */
224 if (arg[1] != '-') {
225 size_t j;
226 for (j = 1; j < arg_len; ++j) {
227 char c = arg[j];
228 if (c >= '0' && c <= '9') {
229 if (quality_set) {
230 fprintf(stderr, "quality already set\n");
231 return COMMAND_INVALID;
232 }
233 quality_set = BROTLI_TRUE;
234 params->quality = c - '0';
235 continue;
236 } else if (c == 'c') {
237 if (output_set) {
238 fprintf(stderr, "write to standard output already set\n");
239 return COMMAND_INVALID;
240 }
241 output_set = BROTLI_TRUE;
242 params->write_to_stdout = BROTLI_TRUE;
243 continue;
244 } else if (c == 'd') {
245 if (command_set) {
246 fprintf(stderr, "command already set when parsing -d\n");
247 return COMMAND_INVALID;
248 }
249 command_set = BROTLI_TRUE;
250 command = COMMAND_DECOMPRESS;
251 continue;
252 } else if (c == 'f') {
253 if (params->force_overwrite) {
254 fprintf(stderr, "force output overwrite already set\n");
255 return COMMAND_INVALID;
256 }
257 params->force_overwrite = BROTLI_TRUE;
258 continue;
259 } else if (c == 'h') {
260 /* Don't parse further. */
261 return COMMAND_HELP;
262 } else if (c == 'j' || c == 'k') {
263 if (keep_set) {
264 fprintf(stderr, "argument --rm / -j or --keep / -n already set\n");
265 return COMMAND_INVALID;
266 }
267 keep_set = BROTLI_TRUE;
268 params->junk_source = TO_BROTLI_BOOL(c == 'j');
269 continue;
270 } else if (c == 'n') {
271 if (!params->copy_stat) {
272 fprintf(stderr, "argument --no-copy-stat / -n already set\n");
273 return COMMAND_INVALID;
274 }
275 params->copy_stat = BROTLI_FALSE;
276 continue;
277 } else if (c == 't') {
278 if (command_set) {
279 fprintf(stderr, "command already set when parsing -t\n");
280 return COMMAND_INVALID;
281 }
282 command_set = BROTLI_TRUE;
283 command = COMMAND_TEST_INTEGRITY;
284 continue;
285 } else if (c == 'v') {
286 if (params->verbose) {
287 fprintf(stderr, "argument --verbose / -v already set\n");
288 return COMMAND_INVALID;
289 }
290 params->verbose = BROTLI_TRUE;
291 continue;
292 } else if (c == 'V') {
293 /* Don't parse further. */
294 return COMMAND_VERSION;
295 } else if (c == 'Z') {
296 if (quality_set) {
297 fprintf(stderr, "quality already set\n");
298 return COMMAND_INVALID;
299 }
300 quality_set = BROTLI_TRUE;
301 params->quality = 11;
302 continue;
303 }
304 /* o/q/w/D/S with parameter is expected */
305 if (c != 'o' && c != 'q' && c != 'w' && c != 'D' && c != 'S') {
306 fprintf(stderr, "invalid argument -%c\n", c);
307 return COMMAND_INVALID;
308 }
309 if (j + 1 != arg_len) {
310 fprintf(stderr, "expected parameter for argument -%c\n", c);
311 return COMMAND_INVALID;
312 }
313 i++;
314 if (i == argc || !argv[i] || argv[i][0] == 0) {
315 fprintf(stderr, "expected parameter for argument -%c\n", c);
316 return COMMAND_INVALID;
317 }
318 params->not_input_indices[next_option_index++] = i;
319 if (c == 'o') {
320 if (output_set) {
321 fprintf(stderr, "write to standard output already set (-o)\n");
322 return COMMAND_INVALID;
323 }
324 params->output_path = argv[i];
325 } else if (c == 'g') {
326 gmem_set = ParseInt(argv[i], 1, 0x10, &params->gmem);
327 if (!gmem_set) {
328 fprintf(stderr, "error parsing gmem value [%s]\n", argv[i]);
329 return COMMAND_INVALID;
330 }
331 } else if (c == 'q') {
332 if (quality_set) {
333 fprintf(stderr, "quality already set\n");
334 return COMMAND_INVALID;
335 }
336 quality_set = ParseInt(argv[i], BROTLI_MIN_QUALITY,
337 BROTLI_MAX_QUALITY, &params->quality);
338 if (!quality_set) {
339 fprintf(stderr, "error parsing quality value [%s]\n", argv[i]);
340 return COMMAND_INVALID;
341 }
342 } else if (c == 'w') {
343 if (lgwin_set) {
344 fprintf(stderr, "lgwin parameter already set\n");
345 return COMMAND_INVALID;
346 }
347 lgwin_set = ParseInt(argv[i], 0,
348 BROTLI_MAX_WINDOW_BITS, &params->lgwin);
349 if (!lgwin_set) {
350 fprintf(stderr, "error parsing lgwin value [%s]\n", argv[i]);
351 return COMMAND_INVALID;
352 }
353 if (params->lgwin != 0 && params->lgwin < BROTLI_MIN_WINDOW_BITS) {
354 fprintf(stderr,
355 "lgwin parameter (%d) smaller than the minimum (%d)\n",
356 params->lgwin, BROTLI_MIN_WINDOW_BITS);
357 return COMMAND_INVALID;
358 }
359 } else if (c == 'S') {
360 if (suffix_set) {
361 fprintf(stderr, "suffix already set\n");
362 return COMMAND_INVALID;
363 }
364 suffix_set = BROTLI_TRUE;
365 params->suffix = argv[i];
366 }
367 }
368 } else { /* Double-dash. */
369 arg = &arg[2];
370 if (strcmp("best", arg) == 0) {
371 if (quality_set) {
372 fprintf(stderr, "quality already set\n");
373 return COMMAND_INVALID;
374 }
375 quality_set = BROTLI_TRUE;
376 params->quality = 11;
377 } else if (strcmp("decompress", arg) == 0) {
378 if (command_set) {
379 fprintf(stderr, "command already set when parsing --decompress\n");
380 return COMMAND_INVALID;
381 }
382 command_set = BROTLI_TRUE;
383 command = COMMAND_DECOMPRESS;
384 } else if (strcmp("force", arg) == 0) {
385 if (params->force_overwrite) {
386 fprintf(stderr, "force output overwrite already set\n");
387 return COMMAND_INVALID;
388 }
389 params->force_overwrite = BROTLI_TRUE;
390 } else if (strcmp("help", arg) == 0) {
391 /* Don't parse further. */
392 return COMMAND_HELP;
393 } else if (strcmp("keep", arg) == 0) {
394 if (keep_set) {
395 fprintf(stderr, "argument --rm / -j or --keep / -n already set\n");
396 return COMMAND_INVALID;
397 }
398 keep_set = BROTLI_TRUE;
399 params->junk_source = BROTLI_FALSE;
400 } else if (strcmp("no-copy-stat", arg) == 0) {
401 if (!params->copy_stat) {
402 fprintf(stderr, "argument --no-copy-stat / -n already set\n");
403 return COMMAND_INVALID;
404 }
405 params->copy_stat = BROTLI_FALSE;
406 } else if (strcmp("rm", arg) == 0) {
407 if (keep_set) {
408 fprintf(stderr, "argument --rm / -j or --keep / -n already set\n");
409 return COMMAND_INVALID;
410 }
411 keep_set = BROTLI_TRUE;
412 params->junk_source = BROTLI_TRUE;
413 } else if (strcmp("stdout", arg) == 0) {
414 if (output_set) {
415 fprintf(stderr, "write to standard output already set\n");
416 return COMMAND_INVALID;
417 }
418 output_set = BROTLI_TRUE;
419 params->write_to_stdout = BROTLI_TRUE;
420 } else if (strcmp("test", arg) == 0) {
421 if (command_set) {
422 fprintf(stderr, "command already set when parsing --test\n");
423 return COMMAND_INVALID;
424 }
425 command_set = BROTLI_TRUE;
426 command = COMMAND_TEST_INTEGRITY;
427 } else if (strcmp("verbose", arg) == 0) {
428 if (params->verbose) {
429 fprintf(stderr, "argument --verbose / -v already set\n");
430 return COMMAND_INVALID;
431 }
432 params->verbose = BROTLI_TRUE;
433 } else if (strcmp("version", arg) == 0) {
434 /* Don't parse further. */
435 return COMMAND_VERSION;
436 } else {
437 /* key=value */
438 const char* value = strrchr(arg, '=');
439 size_t key_len;
440 if (!value || value[1] == 0) {
441 fprintf(stderr, "must pass the parameter as --%s=value\n", arg);
442 return COMMAND_INVALID;
443 }
444 key_len = (size_t)(value - arg);
445 value++;
446 if (strncmp("lgwin", arg, key_len) == 0) {
447 if (lgwin_set) {
448 fprintf(stderr, "lgwin parameter already set\n");
449 return COMMAND_INVALID;
450 }
451 lgwin_set = ParseInt(value, 0,
452 BROTLI_MAX_WINDOW_BITS, &params->lgwin);
453 if (!lgwin_set) {
454 fprintf(stderr, "error parsing lgwin value [%s]\n", value);
455 return COMMAND_INVALID;
456 }
457 if (params->lgwin != 0 && params->lgwin < BROTLI_MIN_WINDOW_BITS) {
458 fprintf(stderr,
459 "lgwin parameter (%d) smaller than the minimum (%d)\n",
460 params->lgwin, BROTLI_MIN_WINDOW_BITS);
461 return COMMAND_INVALID;
462 }
463 } else if (strncmp("large_window", arg, key_len) == 0) {
464 /* This option is intentionally not mentioned in help. */
465 if (lgwin_set) {
466 fprintf(stderr, "lgwin parameter already set\n");
467 return COMMAND_INVALID;
468 }
469 lgwin_set = ParseInt(value, 0,
470 BROTLI_LARGE_MAX_WINDOW_BITS, &params->lgwin);
471 if (!lgwin_set) {
472 fprintf(stderr, "error parsing lgwin value [%s]\n", value);
473 return COMMAND_INVALID;
474 }
475 if (params->lgwin != 0 && params->lgwin < BROTLI_MIN_WINDOW_BITS) {
476 fprintf(stderr,
477 "lgwin parameter (%d) smaller than the minimum (%d)\n",
478 params->lgwin, BROTLI_MIN_WINDOW_BITS);
479 return COMMAND_INVALID;
480 }
481 } else if (strncmp("output", arg, key_len) == 0) {
482 if (output_set) {
483 fprintf(stderr,
484 "write to standard output already set (--output)\n");
485 return COMMAND_INVALID;
486 }
487 params->output_path = value;
488 } else if (strncmp("gap", arg, key_len) == 0) {
489 gmem_set = ParseInt(value, 1, 0x10, &params->gmem);
490 if (!gmem_set) {
491 fprintf(stderr, "error parsing gmem value [%s]\n", value);
492 return COMMAND_INVALID;
493 }
494 } else if (strncmp("quality", arg, key_len) == 0) {
495 if (quality_set) {
496 fprintf(stderr, "quality already set\n");
497 return COMMAND_INVALID;
498 }
499 quality_set = ParseInt(value, BROTLI_MIN_QUALITY,
500 BROTLI_MAX_QUALITY, &params->quality);
501 if (!quality_set) {
502 fprintf(stderr, "error parsing quality value [%s]\n", value);
503 return COMMAND_INVALID;
504 }
505 } else if (strncmp("suffix", arg, key_len) == 0) {
506 if (suffix_set) {
507 fprintf(stderr, "suffix already set\n");
508 return COMMAND_INVALID;
509 }
510 suffix_set = BROTLI_TRUE;
511 params->suffix = value;
512 } else {
513 fprintf(stderr, "invalid parameter: [%s]\n", arg);
514 return COMMAND_INVALID;
515 }
516 }
517 }
518 }
519
520 params->input_count = input_count;
521 params->longest_path_len = longest_path_len;
522 params->decompress = (command == COMMAND_DECOMPRESS);
523 params->test_integrity = (command == COMMAND_TEST_INTEGRITY);
524
525 if (input_count > 1 && output_set) return COMMAND_INVALID;
526 if (params->test_integrity) {
527 if (params->output_path) return COMMAND_INVALID;
528 if (params->write_to_stdout) return COMMAND_INVALID;
529 }
530 if (strchr(params->suffix, '/') || strchr(params->suffix, '\\')) {
531 return COMMAND_INVALID;
532 }
533
534 return command;
535 }
536
537 static void PrintVersion(void) {
538 int major = BROTLI_VERSION >> 24;
539 int minor = (BROTLI_VERSION >> 12) & 0xFFF;
540 int patch = BROTLI_VERSION & 0xFFF;
541 fprintf(stdout, "brotli %d.%d.%d\n", major, minor, patch);
542 }
543
544 static void PrintHelp(const char* name, BROTLI_BOOL error) {
545 FILE* media = error ? stderr : stdout;
546 /* String is cut to pieces with length less than 509, to conform C90 spec. */
547 fprintf(media,
548 "Usage: %s [OPTION]... [FILE]...\n",
549 name);
550 fprintf(media,
551 "Options:\n"
552 " -# compression level (0-9)\n"
553 " -c, --stdout write on standard output\n"
554 " -d, --decompress decompress\n"
555 " -f, --force force output file overwrite\n"
556 " -h, --help display this help and exit\n");
557 fprintf(media,
558 " -j, --rm remove source file(s)\n"
559 " -k, --keep keep source file(s) (default)\n"
560 " -n, --no-copy-stat do not copy source file(s) attributes\n"
561 " -o FILE, --output=FILE output file (only if 1 input file)\n");
562 fprintf(media,
563 " -g NUM, --gap=NUM scratch memory gap level (1-16)\n");
564 fprintf(media,
565 " -q NUM, --quality=NUM compression level (%d-%d)\n",
566 BROTLI_MIN_QUALITY, BROTLI_MAX_QUALITY);
567 fprintf(media,
568 " -t, --test test compressed file integrity\n"
569 " -v, --verbose verbose mode\n");
570 fprintf(media,
571 " -w NUM, --lgwin=NUM set LZ77 window size (0, %d-%d)\n",
572 BROTLI_MIN_WINDOW_BITS, BROTLI_MAX_WINDOW_BITS);
573 fprintf(media,
574 " window size = 2**NUM - 16\n"
575 " 0 lets compressor choose the optimal value\n");
576 fprintf(media,
577 " -S SUF, --suffix=SUF output file suffix (default:'%s')\n",
578 DEFAULT_SUFFIX);
579 fprintf(media,
580 " -V, --version display version and exit\n"
581 " -Z, --best use best compression level (11) (default)\n"
582 "Simple options could be coalesced, i.e. '-9kf' is equivalent to '-9 -k -f'.\n"
583 "With no FILE, or when FILE is -, read standard input.\n"
584 "All arguments after '--' are treated as files.\n");
585 }
586
587 static const char* PrintablePath(const char* path) {
588 return path ? path : "con";
589 }
590
591 static BROTLI_BOOL OpenInputFile(const char* input_path, FILE** f) {
592 *f = NULL;
593 if (!input_path) {
594 *f = fdopen(MAKE_BINARY(STDIN_FILENO), "rb");
595 return BROTLI_TRUE;
596 }
597 *f = fopen(input_path, "rb");
598 if (!*f) {
599 fprintf(stderr, "failed to open input file [%s]: %s\n",
600 PrintablePath(input_path), strerror(errno));
601 return BROTLI_FALSE;
602 }
603 return BROTLI_TRUE;
604 }
605
606 static BROTLI_BOOL OpenOutputFile(const char* output_path, FILE** f,
607 BROTLI_BOOL force) {
608 int fd;
609 *f = NULL;
610 if (!output_path) {
611 *f = fdopen(MAKE_BINARY(STDOUT_FILENO), "wb");
612 return BROTLI_TRUE;
613 }
614 fd = open(output_path, O_CREAT | (force ? 0 : O_EXCL) | O_WRONLY | O_TRUNC,
615 S_IRUSR | S_IWUSR);
616 if (fd < 0) {
617 fd = open(output_path, (force ? 0 : O_EXCL) | O_WRONLY | O_TRUNC,
618 S_IRUSR | S_IWUSR);
619 if (fd < 0) {
620 fprintf(stderr, "failed to open output file [%s]: %s\n",
621 PrintablePath(output_path), strerror(errno));
622 return BROTLI_FALSE;
623 }
624 }
625
626 *f = fdopen(fd, "wb");
627 if (!*f) {
628 fprintf(stderr, "failed to open output file [%s]: %s\n",
629 PrintablePath(output_path), strerror(errno));
630 return BROTLI_FALSE;
631 }
632 return BROTLI_TRUE;
633 }
634
635 static int64_t FileSize(const char* path) {
636 FILE* f = fopen(path, "rb");
637 int64_t retval;
638 if (f == NULL) {
639 return -1;
640 }
641 if (fseek(f, 0L, SEEK_END) != 0) {
642 fclose(f);
643 return -1;
644 }
645 retval = ftell(f);
646 if (fclose(f) != 0) {
647 return -1;
648 }
649 return retval;
650 }
651
652 /* Copy file times and permissions.
653 TODO: this is a "best effort" implementation; honest cross-platform
654 fully featured implementation is way too hacky; add more hacks by request. */
655 static void CopyStat(const char* input_path, const char* output_path) {
656 struct stat statbuf;
657 struct utimbuf times;
658 int res;
659 if (input_path == 0 || output_path == 0) {
660 return;
661 }
662 if (stat(input_path, &statbuf) != 0) {
663 return;
664 }
665 times.actime = statbuf.st_atime;
666 times.modtime = statbuf.st_mtime;
667 utime(output_path, &times);
668 res = chmod(output_path, statbuf.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));
669 if (res != 0) {
670 fprintf(stderr, "setting access bits failed for [%s]: %s\n",
671 PrintablePath(output_path), strerror(errno));
672 }
673 res = chown(output_path, (uid_t)-1, statbuf.st_gid);
674 if (res != 0) {
675 fprintf(stderr, "setting group failed for [%s]: %s\n",
676 PrintablePath(output_path), strerror(errno));
677 }
678 res = chown(output_path, statbuf.st_uid, (gid_t)-1);
679 if (res != 0) {
680 fprintf(stderr, "setting user failed for [%s]: %s\n",
681 PrintablePath(output_path), strerror(errno));
682 }
683 }
684
685 int64_t input_file_length = 0;
686
687 static BROTLI_BOOL NextFile(Context* context) {
688 const char* arg;
689 size_t arg_len;
690
691 /* Iterator points to last used arg; increment to search for the next one. */
692 context->iterator++;
693
694 context->input_file_length = -1;
695
696 /* No input path; read from console. */
697 if (context->input_count == 0) {
698 if (context->iterator > 1) return BROTLI_FALSE;
699 context->current_input_path = NULL;
700 /* Either write to the specified path, or to console. */
701 context->current_output_path = context->output_path;
702 return BROTLI_TRUE;
703 }
704
705 /* Skip option arguments. */
706 while (context->iterator == context->not_input_indices[context->ignore]) {
707 context->iterator++;
708 context->ignore++;
709 }
710
711 /* All args are scanned already. */
712 if (context->iterator >= context->argc) return BROTLI_FALSE;
713
714 /* Iterator now points to the input file name. */
715 arg = context->argv[context->iterator];
716 arg_len = strlen(arg);
717 /* Read from console. */
718 if (arg_len == 1 && arg[0] == '-') {
719 context->current_input_path = NULL;
720 context->current_output_path = context->output_path;
721 return BROTLI_TRUE;
722 }
723
724 if (context->current_input_path == NULL) {
725 context->current_input_path = arg;
726 }
727 context->input_file_length = FileSize(context->current_input_path);
728 context->current_output_path = context->output_path;
729 if (!context->decompress) {
730 input_file_length += context->input_file_length;
731 }
732
733 if (context->output_path) return BROTLI_TRUE;
734 if (context->write_to_stdout) return BROTLI_TRUE;
735
736 strcpy(context->modified_path, arg);
737 context->current_output_path = context->modified_path;
738 /* If output is not specified, input path suffix should match. */
739 if (context->decompress) {
740 size_t suffix_len = strlen(context->suffix);
741 char* name = (char*)FileName(context->modified_path);
742 char* name_suffix;
743 size_t name_len = strlen(name);
744 if (name_len < suffix_len + 1) {
745 fprintf(stderr, "empty output file name for [%s] input file\n",
746 PrintablePath(arg));
747 context->iterator_error = BROTLI_TRUE;
748 return BROTLI_FALSE;
749 }
750 name_suffix = name + name_len - suffix_len;
751 if (strcmp(context->suffix, name_suffix) != 0) {
752 fprintf(stderr, "input file [%s] suffix mismatch\n",
753 PrintablePath(arg));
754 context->iterator_error = BROTLI_TRUE;
755 return BROTLI_FALSE;
756 }
757 name_suffix[0] = 0;
758 return BROTLI_TRUE;
759 } else {
760 strcpy(context->modified_path + arg_len, context->suffix);
761 return BROTLI_TRUE;
762 }
763 }
764
765 static BROTLI_BOOL OpenFiles(Context* context) {
766 BROTLI_BOOL is_ok = OpenInputFile(context->current_input_path, &context->fin);
767 if (context->decompress) {
768 //
769 // skip the decoder data header
770 //
771 fseek(context->fin, DECODE_HEADER_SIZE, SEEK_SET);
772 }
773 if (!context->test_integrity && is_ok && context->fout == NULL) {
774 is_ok = OpenOutputFile(
775 context->current_output_path, &context->fout, context->force_overwrite);
776 }
777 if (!context->decompress) {
778 //
779 // append the decoder data header
780 //
781 fseek(context->fout, DECODE_HEADER_SIZE, SEEK_SET);
782 }
783 return is_ok;
784 }
785
786 static BROTLI_BOOL CloseFiles(Context* context, BROTLI_BOOL success) {
787 BROTLI_BOOL is_ok = BROTLI_TRUE;
788 if (!context->test_integrity && context->fout) {
789 if (!success && context->current_output_path) {
790 unlink(context->current_output_path);
791 }
792 if (fclose(context->fout) != 0) {
793 if (success) {
794 fprintf(stderr, "fclose failed [%s]: %s\n",
795 PrintablePath(context->current_output_path), strerror(errno));
796 }
797 is_ok = BROTLI_FALSE;
798 }
799
800 /* TOCTOU violation, but otherwise it is impossible to set file times. */
801 if (success && is_ok && context->copy_stat) {
802 CopyStat(context->current_input_path, context->current_output_path);
803 }
804 }
805
806 if (context->fin) {
807 if (fclose(context->fin) != 0) {
808 if (is_ok) {
809 fprintf(stderr, "fclose failed [%s]: %s\n",
810 PrintablePath(context->current_input_path), strerror(errno));
811 }
812 is_ok = BROTLI_FALSE;
813 }
814 }
815 if (success && context->junk_source && context->current_input_path) {
816 unlink(context->current_input_path);
817 }
818
819 context->fin = NULL;
820 context->fout = NULL;
821
822 return is_ok;
823 }
824
825 static const size_t kFileBufferSize = 1 << 16;
826
827 static void InitializeBuffers(Context* context) {
828 context->available_in = 0;
829 context->next_in = NULL;
830 context->available_out = kFileBufferSize;
831 context->next_out = context->output;
832 }
833
834 static BROTLI_BOOL HasMoreInput(Context* context) {
835 return feof(context->fin) ? BROTLI_FALSE : BROTLI_TRUE;
836 }
837
838 static BROTLI_BOOL ProvideInput(Context* context) {
839 context->available_in =
840 fread(context->input, 1, kFileBufferSize, context->fin);
841 context->next_in = context->input;
842 if (ferror(context->fin)) {
843 fprintf(stderr, "failed to read input [%s]: %s\n",
844 PrintablePath(context->current_input_path), strerror(errno));
845 return BROTLI_FALSE;
846 }
847 return BROTLI_TRUE;
848 }
849
850 /* Internal: should be used only in Provide-/Flush-Output. */
851 static BROTLI_BOOL WriteOutput(Context* context) {
852 size_t out_size = (size_t)(context->next_out - context->output);
853 if (out_size == 0) return BROTLI_TRUE;
854 if (context->test_integrity) return BROTLI_TRUE;
855
856 fwrite(context->output, 1, out_size, context->fout);
857 if (ferror(context->fout)) {
858 fprintf(stderr, "failed to write output [%s]: %s\n",
859 PrintablePath(context->current_output_path), strerror(errno));
860 return BROTLI_FALSE;
861 }
862 return BROTLI_TRUE;
863 }
864
865 static BROTLI_BOOL ProvideOutput(Context* context) {
866 if (!WriteOutput(context)) return BROTLI_FALSE;
867 context->available_out = kFileBufferSize;
868 context->next_out = context->output;
869 return BROTLI_TRUE;
870 }
871
872 static BROTLI_BOOL FlushOutput(Context* context) {
873 if (!WriteOutput(context)) return BROTLI_FALSE;
874 context->available_out = 0;
875 return BROTLI_TRUE;
876 }
877
878 static BROTLI_BOOL DecompressFile(Context* context, BrotliDecoderState* s) {
879 BrotliDecoderResult result = BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT;
880 InitializeBuffers(context);
881 for (;;) {
882 if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) {
883 if (!HasMoreInput(context)) {
884 fprintf(stderr, "corrupt input [%s]\n",
885 PrintablePath(context->current_input_path));
886 return BROTLI_FALSE;
887 }
888 if (!ProvideInput(context)) return BROTLI_FALSE;
889 } else if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
890 if (!ProvideOutput(context)) return BROTLI_FALSE;
891 } else if (result == BROTLI_DECODER_RESULT_SUCCESS) {
892 if (!FlushOutput(context)) return BROTLI_FALSE;
893 if (context->available_in != 0 || HasMoreInput(context)) {
894 fprintf(stderr, "corrupt input [%s]\n",
895 PrintablePath(context->current_input_path));
896 return BROTLI_FALSE;
897 }
898 return BROTLI_TRUE;
899 } else {
900 fprintf(stderr, "corrupt input [%s]\n",
901 PrintablePath(context->current_input_path));
902 return BROTLI_FALSE;
903 }
904
905 result = BrotliDecoderDecompressStream(s, &context->available_in,
906 &context->next_in, &context->available_out, &context->next_out, 0);
907 }
908 }
909
910 /* Default brotli_alloc_func */
911 void* BrotliAllocFunc(void* opaque, size_t size) {
912 *(size_t *)opaque = *(size_t *) opaque + size;
913 return malloc(size);
914 }
915
916 /* Default brotli_free_func */
917 void BrotliFreeFunc(void* opaque, void* address) {
918 free(address);
919 }
920
921 size_t scratch_buffer_size = 0;
922
923 static BROTLI_BOOL DecompressFiles(Context* context) {
924 while (NextFile(context)) {
925 BROTLI_BOOL is_ok = BROTLI_TRUE;
926 BrotliDecoderState* s = BrotliDecoderCreateInstance(BrotliAllocFunc, BrotliFreeFunc, &scratch_buffer_size);
927 if (!s) {
928 fprintf(stderr, "out of memory\n");
929 return BROTLI_FALSE;
930 }
931 /* This allows decoding "large-window" streams. Though it creates
932 fragmentation (new builds decode streams that old builds don't),
933 it is better from used experience perspective. */
934 BrotliDecoderSetParameter(s, BROTLI_DECODER_PARAM_LARGE_WINDOW, 1u);
935 is_ok = OpenFiles(context);
936 if (is_ok && !context->current_input_path &&
937 !context->force_overwrite && isatty(STDIN_FILENO)) {
938 fprintf(stderr, "Use -h help. Use -f to force input from a terminal.\n");
939 is_ok = BROTLI_FALSE;
940 }
941 if (is_ok) is_ok = DecompressFile(context, s);
942 BrotliDecoderDestroyInstance(s);
943 if (!CloseFiles(context, is_ok)) is_ok = BROTLI_FALSE;
944 if (!is_ok) return BROTLI_FALSE;
945 }
946 return BROTLI_TRUE;
947 }
948
949 static BROTLI_BOOL CompressFile(Context* context, BrotliEncoderState* s) {
950 BROTLI_BOOL is_eof = BROTLI_FALSE;
951 InitializeBuffers(context);
952 for (;;) {
953 if (context->available_in == 0 && !is_eof) {
954 if (!ProvideInput(context)) return BROTLI_FALSE;
955 is_eof = !HasMoreInput(context);
956 }
957
958 if (!BrotliEncoderCompressStream(s,
959 is_eof ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS,
960 &context->available_in, &context->next_in,
961 &context->available_out, &context->next_out, NULL)) {
962 /* Should detect OOM? */
963 fprintf(stderr, "failed to compress data [%s]\n",
964 PrintablePath(context->current_input_path));
965 return BROTLI_FALSE;
966 }
967
968 if (context->available_out == 0) {
969 if (!ProvideOutput(context)) return BROTLI_FALSE;
970 }
971
972 if (BrotliEncoderIsFinished(s)) {
973 return FlushOutput(context);
974 }
975 }
976 }
977
978 static BROTLI_BOOL CompressFiles(Context* context) {
979 while (NextFile(context)) {
980 BROTLI_BOOL is_ok = BROTLI_TRUE;
981 BrotliEncoderState* s = BrotliEncoderCreateInstance(NULL, NULL, NULL);
982 if (!s) {
983 fprintf(stderr, "out of memory\n");
984 return BROTLI_FALSE;
985 }
986 BrotliEncoderSetParameter(s,
987 BROTLI_PARAM_QUALITY, (uint32_t)context->quality);
988 if (context->lgwin > 0) {
989 /* Specified by user. */
990 /* Do not enable "large-window" extension, if not required. */
991 if (context->lgwin > BROTLI_MAX_WINDOW_BITS) {
992 BrotliEncoderSetParameter(s, BROTLI_PARAM_LARGE_WINDOW, 1u);
993 }
994 BrotliEncoderSetParameter(s,
995 BROTLI_PARAM_LGWIN, (uint32_t)context->lgwin);
996 } else {
997 /* 0, or not specified by user; could be chosen by compressor. */
998 uint32_t lgwin = DEFAULT_LGWIN;
999 /* Use file size to limit lgwin. */
1000 if (context->input_file_length >= 0) {
1001 int32_t size = 1 << BROTLI_MIN_WINDOW_BITS;
1002 lgwin = BROTLI_MIN_WINDOW_BITS;
1003 while (size < context->input_file_length) {
1004 size <<= 1;
1005 lgwin++;
1006 if (lgwin == BROTLI_MAX_WINDOW_BITS) break;
1007 }
1008 }
1009 BrotliEncoderSetParameter(s, BROTLI_PARAM_LGWIN, lgwin);
1010 }
1011 if (context->input_file_length > 0) {
1012 uint32_t size_hint = context->input_file_length < (1 << 30) ?
1013 (uint32_t)context->input_file_length : (1u << 30);
1014 BrotliEncoderSetParameter(s, BROTLI_PARAM_SIZE_HINT, size_hint);
1015 }
1016 is_ok = OpenFiles(context);
1017 if (is_ok && !context->current_output_path &&
1018 !context->force_overwrite && isatty(STDOUT_FILENO)) {
1019 fprintf(stderr, "Use -h help. Use -f to force output to a terminal.\n");
1020 is_ok = BROTLI_FALSE;
1021 }
1022 if (is_ok) is_ok = CompressFile(context, s);
1023 BrotliEncoderDestroyInstance(s);
1024 if (!CloseFiles(context, is_ok)) is_ok = BROTLI_FALSE;
1025 if (!is_ok) return BROTLI_FALSE;
1026 }
1027 return BROTLI_TRUE;
1028 }
1029
1030 int main(int argc, char** argv) {
1031 Command command;
1032 Context context;
1033 Context context_dec;
1034 BROTLI_BOOL is_ok = BROTLI_TRUE;
1035 int i;
1036
1037 context.quality = 11;
1038 context.lgwin = -1;
1039 context.gmem = 1;
1040 context.force_overwrite = BROTLI_FALSE;
1041 context.junk_source = BROTLI_FALSE;
1042 context.copy_stat = BROTLI_TRUE;
1043 context.test_integrity = BROTLI_FALSE;
1044 context.verbose = BROTLI_FALSE;
1045 context.write_to_stdout = BROTLI_FALSE;
1046 context.decompress = BROTLI_FALSE;
1047 context.large_window = BROTLI_FALSE;
1048 context.output_path = NULL;
1049 context.suffix = DEFAULT_SUFFIX;
1050 for (i = 0; i < MAX_OPTIONS; ++i) context.not_input_indices[i] = 0;
1051 context.longest_path_len = 1;
1052 context.input_count = 0;
1053
1054 context.argc = argc;
1055 context.argv = argv;
1056 context.modified_path = NULL;
1057 context.iterator = 0;
1058 context.ignore = 0;
1059 context.iterator_error = BROTLI_FALSE;
1060 context.buffer = NULL;
1061 context.current_input_path = NULL;
1062 context.current_output_path = NULL;
1063 context.fin = NULL;
1064 context.fout = NULL;
1065
1066 command = ParseParams(&context);
1067
1068 if (command == COMMAND_COMPRESS || command == COMMAND_DECOMPRESS ||
1069 command == COMMAND_TEST_INTEGRITY) {
1070 if (is_ok) {
1071 size_t modified_path_len =
1072 context.longest_path_len + strlen(context.suffix) + 1;
1073 context.modified_path = (char*)malloc(modified_path_len);
1074 context.buffer = (uint8_t*)malloc(kFileBufferSize * 2);
1075 if (!context.modified_path || !context.buffer) {
1076 fprintf(stderr, "out of memory\n");
1077 is_ok = BROTLI_FALSE;
1078 } else {
1079 context.input = context.buffer;
1080 context.output = context.buffer + kFileBufferSize;
1081 }
1082 }
1083 }
1084
1085 if (!is_ok) command = COMMAND_NOOP;
1086
1087 switch (command) {
1088 case COMMAND_NOOP:
1089 break;
1090
1091 case COMMAND_VERSION:
1092 PrintVersion();
1093 break;
1094
1095 case COMMAND_COMPRESS:
1096 memcpy (&context_dec, &context, sizeof (context));
1097 is_ok = CompressFiles(&context);
1098 if (!is_ok) {
1099 break;
1100 }
1101 context_dec.decompress = BROTLI_TRUE;
1102 context_dec.input_count = 1;
1103 context_dec.current_input_path = context_dec.output_path;
1104 context_dec.fout = tmpfile ();
1105 is_ok = DecompressFiles(&context_dec);
1106 if (!is_ok) {
1107 break;
1108 }
1109 //
1110 // fill decoder header
1111 //
1112 context_dec.fout = fopen(context_dec.output_path, "rb+"); /* open output_path file and add in head info */
1113 fwrite(&input_file_length, 1, sizeof(int64_t), context_dec.fout);
1114 scratch_buffer_size += context.gmem * GAP_MEM_BLOCK; /* there is a memory gap between IA32 and X64 environment*/
1115 scratch_buffer_size += kFileBufferSize * 2;
1116 fwrite(&scratch_buffer_size, 1, sizeof(int64_t), context_dec.fout);
1117 if (fclose(context_dec.fout) != 0) {
1118 fprintf(stderr, "failed to update ouptut file: %s\n", context_dec.output_path);
1119 is_ok = 0;
1120 }
1121 break;
1122
1123 case COMMAND_DECOMPRESS:
1124 case COMMAND_TEST_INTEGRITY:
1125 is_ok = DecompressFiles(&context);
1126 break;
1127
1128 case COMMAND_HELP:
1129 case COMMAND_INVALID:
1130 default:
1131 is_ok = (command == COMMAND_HELP);
1132 PrintHelp(FileName(argv[0]), is_ok);
1133 break;
1134 }
1135
1136 if (context.iterator_error) is_ok = BROTLI_FALSE;
1137
1138 free(context.modified_path);
1139 free(context.buffer);
1140
1141 if (!is_ok) exit(1);
1142 return 0;
1143 }