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, &params->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, &params->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, &params->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, &params->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, &params->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, &times);
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