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