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