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