• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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, &params->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, &params->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, &params->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, &params->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, &params->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, &times);
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