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