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 22
     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   const char* output_path;
     97   const char* suffix;
     98   int not_input_indices[MAX_OPTIONS];
     99   size_t longest_path_len;
    100   size_t input_count;
    101 
    102   /* Inner state */
    103   int argc;
    104   char** argv;
    105   char* modified_path;  /* Storage for path with appended / cut suffix */
    106   int iterator;
    107   int ignore;
    108   BROTLI_BOOL iterator_error;
    109   uint8_t* buffer;
    110   uint8_t* input;
    111   uint8_t* output;
    112   const char* current_input_path;
    113   const char* current_output_path;
    114   FILE* fin;
    115   FILE* fout;
    116 } Context;
    117 
    118 /* Parse up to 5 decimal digits. */
    119 static BROTLI_BOOL ParseInt(const char* s, int low, int high, int* result) {
    120   int value = 0;
    121   int i;
    122   for (i = 0; i < 5; ++i) {
    123     char c = s[i];
    124     if (c == 0) break;
    125     if (s[i] < '0' || s[i] > '9') return BROTLI_FALSE;
    126     value = (10 * value) + (c - '0');
    127   }
    128   if (i == 0) return BROTLI_FALSE;
    129   if (i > 1 && s[0] == '0') return BROTLI_FALSE;
    130   if (s[i] != 0) return BROTLI_FALSE;
    131   if (value < low || value > high) return BROTLI_FALSE;
    132   *result = value;
    133   return BROTLI_TRUE;
    134 }
    135 
    136 /* Returns "base file name" or its tail, if it contains '/' or '\'. */
    137 static const char* FileName(const char* path) {
    138   const char* separator_position = strrchr(path, '/');
    139   if (separator_position) path = separator_position + 1;
    140   separator_position = strrchr(path, '\\');
    141   if (separator_position) path = separator_position + 1;
    142   return path;
    143 }
    144 
    145 /* Detect if the program name is a special alias that infers a command type. */
    146 static Command ParseAlias(const char* name) {
    147   /* TODO: cast name to lower case? */
    148   const char* unbrotli = "unbrotli";
    149   size_t unbrotli_len = strlen(unbrotli);
    150   name = FileName(name);
    151   /* Partial comparison. On Windows there could be ".exe" suffix. */
    152   if (strncmp(name, unbrotli, unbrotli_len) == 0) {
    153     char terminator = name[unbrotli_len];
    154     if (terminator == 0 || terminator == '.') return COMMAND_DECOMPRESS;
    155   }
    156   return COMMAND_COMPRESS;
    157 }
    158 
    159 static Command ParseParams(Context* params) {
    160   int argc = params->argc;
    161   char** argv = params->argv;
    162   int i;
    163   int next_option_index = 0;
    164   size_t input_count = 0;
    165   size_t longest_path_len = 1;
    166   BROTLI_BOOL command_set = BROTLI_FALSE;
    167   BROTLI_BOOL quality_set = BROTLI_FALSE;
    168   BROTLI_BOOL output_set = BROTLI_FALSE;
    169   BROTLI_BOOL keep_set = BROTLI_FALSE;
    170   BROTLI_BOOL lgwin_set = BROTLI_FALSE;
    171   BROTLI_BOOL suffix_set = BROTLI_FALSE;
    172   BROTLI_BOOL after_dash_dash = BROTLI_FALSE;
    173   Command command = ParseAlias(argv[0]);
    174 
    175   for (i = 1; i < argc; ++i) {
    176     const char* arg = argv[i];
    177     /* C99 5.1.2.2.1: "members argv[0] through argv[argc-1] inclusive shall
    178        contain pointers to strings"; NULL and 0-length are not forbidden. */
    179     size_t arg_len = arg ? strlen(arg) : 0;
    180 
    181     if (arg_len == 0) {
    182       params->not_input_indices[next_option_index++] = i;
    183       continue;
    184     }
    185 
    186     /* Too many options. The expected longest option list is:
    187        "-q 0 -w 10 -o f -D d -S b -d -f -k -n -v --", i.e. 16 items in total.
    188        This check is an additinal guard that is never triggered, but provides an
    189        additional guard for future changes. */
    190     if (next_option_index > (MAX_OPTIONS - 2)) {
    191       return COMMAND_INVALID;
    192     }
    193 
    194     /* Input file entry. */
    195     if (after_dash_dash || arg[0] != '-' || arg_len == 1) {
    196       input_count++;
    197       if (longest_path_len < arg_len) longest_path_len = arg_len;
    198       continue;
    199     }
    200 
    201     /* Not a file entry. */
    202     params->not_input_indices[next_option_index++] = i;
    203 
    204     /* '--' entry stop parsing arguments. */
    205     if (arg_len == 2 && arg[1] == '-') {
    206       after_dash_dash = BROTLI_TRUE;
    207       continue;
    208     }
    209 
    210     /* Simple / coalesced options. */
    211     if (arg[1] != '-') {
    212       size_t j;
    213       for (j = 1; j < arg_len; ++j) {
    214         char c = arg[j];
    215         if (c >= '0' && c <= '9') {
    216           if (quality_set) return COMMAND_INVALID;
    217           quality_set = BROTLI_TRUE;
    218           params->quality = c - '0';
    219           continue;
    220         } else if (c == 'c') {
    221           if (output_set) return COMMAND_INVALID;
    222           output_set = BROTLI_TRUE;
    223           params->write_to_stdout = BROTLI_TRUE;
    224           continue;
    225         } else if (c == 'd') {
    226           if (command_set) return COMMAND_INVALID;
    227           command_set = BROTLI_TRUE;
    228           command = COMMAND_DECOMPRESS;
    229           continue;
    230         } else if (c == 'f') {
    231           if (params->force_overwrite) return COMMAND_INVALID;
    232           params->force_overwrite = BROTLI_TRUE;
    233           continue;
    234         } else if (c == 'h') {
    235           /* Don't parse further. */
    236           return COMMAND_HELP;
    237         } else if (c == 'j' || c == 'k') {
    238           if (keep_set) return COMMAND_INVALID;
    239           keep_set = BROTLI_TRUE;
    240           params->junk_source = TO_BROTLI_BOOL(c == 'j');
    241           continue;
    242         } else if (c == 'n') {
    243           if (!params->copy_stat) return COMMAND_INVALID;
    244           params->copy_stat = BROTLI_FALSE;
    245           continue;
    246         } else if (c == 't') {
    247           if (command_set) return COMMAND_INVALID;
    248           command_set = BROTLI_TRUE;
    249           command = COMMAND_TEST_INTEGRITY;
    250           continue;
    251         } else if (c == 'v') {
    252           if (params->verbose) return COMMAND_INVALID;
    253           params->verbose = BROTLI_TRUE;
    254           continue;
    255         } else if (c == 'V') {
    256           /* Don't parse further. */
    257           return COMMAND_VERSION;
    258         } else if (c == 'Z') {
    259           if (quality_set) return COMMAND_INVALID;
    260           quality_set = BROTLI_TRUE;
    261           params->quality = 11;
    262           continue;
    263         }
    264         /* o/q/w/D/S with parameter is expected */
    265         if (c != 'o' && c != 'q' && c != 'w' && c != 'D' && c != 'S') {
    266           return COMMAND_INVALID;
    267         }
    268         if (j + 1 != arg_len) return COMMAND_INVALID;
    269         i++;
    270         if (i == argc || !argv[i] || argv[i][0] == 0) return COMMAND_INVALID;
    271         params->not_input_indices[next_option_index++] = i;
    272         if (c == 'o') {
    273           if (output_set) return COMMAND_INVALID;
    274           params->output_path = argv[i];
    275         } else if (c == 'q') {
    276           if (quality_set) return COMMAND_INVALID;
    277           quality_set = ParseInt(argv[i], BROTLI_MIN_QUALITY,
    278                                  BROTLI_MAX_QUALITY, &params->quality);
    279           if (!quality_set) return COMMAND_INVALID;
    280         } else if (c == 'w') {
    281           if (lgwin_set) return COMMAND_INVALID;
    282           lgwin_set = ParseInt(argv[i], 0,
    283                                BROTLI_MAX_WINDOW_BITS, &params->lgwin);
    284           if (!lgwin_set) return COMMAND_INVALID;
    285           if (params->lgwin != 0 && params->lgwin < BROTLI_MIN_WINDOW_BITS) {
    286             return COMMAND_INVALID;
    287           }
    288         } else if (c == 'S') {
    289           if (suffix_set) return COMMAND_INVALID;
    290           suffix_set = BROTLI_TRUE;
    291           params->suffix = argv[i];
    292         }
    293       }
    294     } else {  /* Double-dash. */
    295       arg = &arg[2];
    296       if (strcmp("best", arg) == 0) {
    297         if (quality_set) return COMMAND_INVALID;
    298         quality_set = BROTLI_TRUE;
    299         params->quality = 11;
    300       } else if (strcmp("decompress", arg) == 0) {
    301         if (command_set) return COMMAND_INVALID;
    302         command_set = BROTLI_TRUE;
    303         command = COMMAND_DECOMPRESS;
    304       } else if (strcmp("force", arg) == 0) {
    305         if (params->force_overwrite) return COMMAND_INVALID;
    306         params->force_overwrite = BROTLI_TRUE;
    307       } else if (strcmp("help", arg) == 0) {
    308         /* Don't parse further. */
    309         return COMMAND_HELP;
    310       } else if (strcmp("keep", arg) == 0) {
    311         if (keep_set) return COMMAND_INVALID;
    312         keep_set = BROTLI_TRUE;
    313         params->junk_source = BROTLI_FALSE;
    314       } else if (strcmp("no-copy-stat", arg) == 0) {
    315         if (!params->copy_stat) return COMMAND_INVALID;
    316         params->copy_stat = BROTLI_FALSE;
    317       } else if (strcmp("rm", arg) == 0) {
    318         if (keep_set) return COMMAND_INVALID;
    319         keep_set = BROTLI_TRUE;
    320         params->junk_source = BROTLI_TRUE;
    321       } else if (strcmp("stdout", arg) == 0) {
    322         if (output_set) return COMMAND_INVALID;
    323         output_set = BROTLI_TRUE;
    324         params->write_to_stdout = BROTLI_TRUE;
    325       } else if (strcmp("test", arg) == 0) {
    326         if (command_set) return COMMAND_INVALID;
    327         command_set = BROTLI_TRUE;
    328         command = COMMAND_TEST_INTEGRITY;
    329       } else if (strcmp("verbose", arg) == 0) {
    330         if (params->verbose) return COMMAND_INVALID;
    331         params->verbose = BROTLI_TRUE;
    332       } else if (strcmp("version", arg) == 0) {
    333         /* Don't parse further. */
    334         return COMMAND_VERSION;
    335       } else {
    336         /* key=value */
    337         const char* value = strrchr(arg, '=');
    338         size_t key_len;
    339         if (!value || value[1] == 0) return COMMAND_INVALID;
    340         key_len = (size_t)(value - arg);
    341         value++;
    342         if (strncmp("lgwin", arg, key_len) == 0) {
    343           if (lgwin_set) return COMMAND_INVALID;
    344           lgwin_set = ParseInt(value, 0,
    345                                BROTLI_MAX_WINDOW_BITS, &params->lgwin);
    346           if (!lgwin_set) return COMMAND_INVALID;
    347           if (params->lgwin != 0 && params->lgwin < BROTLI_MIN_WINDOW_BITS) {
    348             return COMMAND_INVALID;
    349           }
    350         } else if (strncmp("output", arg, key_len) == 0) {
    351           if (output_set) return COMMAND_INVALID;
    352           params->output_path = value;
    353         } else if (strncmp("quality", arg, key_len) == 0) {
    354           if (quality_set) return COMMAND_INVALID;
    355           quality_set = ParseInt(value, BROTLI_MIN_QUALITY,
    356                                  BROTLI_MAX_QUALITY, &params->quality);
    357           if (!quality_set) return COMMAND_INVALID;
    358         } else if (strncmp("suffix", arg, key_len) == 0) {
    359           if (suffix_set) return COMMAND_INVALID;
    360           suffix_set = BROTLI_TRUE;
    361           params->suffix = value;
    362         } else {
    363           return COMMAND_INVALID;
    364         }
    365       }
    366     }
    367   }
    368 
    369   params->input_count = input_count;
    370   params->longest_path_len = longest_path_len;
    371   params->decompress = (command == COMMAND_DECOMPRESS);
    372   params->test_integrity = (command == COMMAND_TEST_INTEGRITY);
    373 
    374   if (input_count > 1 && output_set) return COMMAND_INVALID;
    375   if (params->test_integrity) {
    376     if (params->output_path) return COMMAND_INVALID;
    377     if (params->write_to_stdout) return COMMAND_INVALID;
    378   }
    379   if (strchr(params->suffix, '/') || strchr(params->suffix, '\\')) {
    380     return COMMAND_INVALID;
    381   }
    382 
    383   return command;
    384 }
    385 
    386 static void PrintVersion(void) {
    387   int major = BROTLI_VERSION >> 24;
    388   int minor = (BROTLI_VERSION >> 12) & 0xFFF;
    389   int patch = BROTLI_VERSION & 0xFFF;
    390   fprintf(stdout, "brotli %d.%d.%d\n", major, minor, patch);
    391 }
    392 
    393 static void PrintHelp(const char* name) {
    394   /* String is cut to pieces with length less than 509, to conform C90 spec. */
    395   fprintf(stdout,
    396 "Usage: %s [OPTION]... [FILE]...\n",
    397           name);
    398   fprintf(stdout,
    399 "Options:\n"
    400 "  -#                          compression level (0-9)\n"
    401 "  -c, --stdout                write on standard output\n"
    402 "  -d, --decompress            decompress\n"
    403 "  -f, --force                 force output file overwrite\n"
    404 "  -h, --help                  display this help and exit\n");
    405   fprintf(stdout,
    406 "  -j, --rm                    remove source file(s)\n"
    407 "  -k, --keep                  keep source file(s) (default)\n"
    408 "  -n, --no-copy-stat          do not copy source file(s) attributes\n"
    409 "  -o FILE, --output=FILE      output file (only if 1 input file)\n");
    410   fprintf(stdout,
    411 "  -q NUM, --quality=NUM       compression level (%d-%d)\n",
    412           BROTLI_MIN_QUALITY, BROTLI_MAX_QUALITY);
    413   fprintf(stdout,
    414 "  -t, --test                  test compressed file integrity\n"
    415 "  -v, --verbose               verbose mode\n");
    416   fprintf(stdout,
    417 "  -w NUM, --lgwin=NUM         set LZ77 window size (0, %d-%d) (default:%d)\n",
    418           BROTLI_MIN_WINDOW_BITS, BROTLI_MAX_WINDOW_BITS, DEFAULT_LGWIN);
    419   fprintf(stdout,
    420 "                              window size = 2**NUM - 16\n"
    421 "                              0 lets compressor choose the optimal value\n");
    422   fprintf(stdout,
    423 "  -S SUF, --suffix=SUF        output file suffix (default:'%s')\n",
    424           DEFAULT_SUFFIX);
    425   fprintf(stdout,
    426 "  -V, --version               display version and exit\n"
    427 "  -Z, --best                  use best compression level (11) (default)\n"
    428 "Simple options could be coalesced, i.e. '-9kf' is equivalent to '-9 -k -f'.\n"
    429 "With no FILE, or when FILE is -, read standard input.\n"
    430 "All arguments after '--' are treated as files.\n");
    431 }
    432 
    433 static const char* PrintablePath(const char* path) {
    434   return path ? path : "con";
    435 }
    436 
    437 static BROTLI_BOOL OpenInputFile(const char* input_path, FILE** f) {
    438   *f = NULL;
    439   if (!input_path) {
    440     *f = fdopen(MAKE_BINARY(STDIN_FILENO), "rb");
    441     return BROTLI_TRUE;
    442   }
    443   *f = fopen(input_path, "rb");
    444   if (!*f) {
    445     fprintf(stderr, "failed to open input file [%s]: %s\n",
    446             PrintablePath(input_path), strerror(errno));
    447     return BROTLI_FALSE;
    448   }
    449   return BROTLI_TRUE;
    450 }
    451 
    452 static BROTLI_BOOL OpenOutputFile(const char* output_path, FILE** f,
    453                                   BROTLI_BOOL force) {
    454   int fd;
    455   *f = NULL;
    456   if (!output_path) {
    457     *f = fdopen(MAKE_BINARY(STDOUT_FILENO), "wb");
    458     return BROTLI_TRUE;
    459   }
    460   fd = open(output_path, O_CREAT | (force ? 0 : O_EXCL) | O_WRONLY | O_TRUNC,
    461             S_IRUSR | S_IWUSR);
    462   if (fd < 0) {
    463     fprintf(stderr, "failed to open output file [%s]: %s\n",
    464             PrintablePath(output_path), strerror(errno));
    465     return BROTLI_FALSE;
    466   }
    467   *f = fdopen(fd, "wb");
    468   if (!*f) {
    469     fprintf(stderr, "failed to open output file [%s]: %s\n",
    470             PrintablePath(output_path), strerror(errno));
    471     return BROTLI_FALSE;
    472   }
    473   return BROTLI_TRUE;
    474 }
    475 
    476 /* Copy file times and permissions.
    477    TODO: this is a "best effort" implementation; honest cross-platform
    478    fully featured implementation is way too hacky; add more hacks by request. */
    479 static void CopyStat(const char* input_path, const char* output_path) {
    480   struct stat statbuf;
    481   struct utimbuf times;
    482   int res;
    483   if (input_path == 0 || output_path == 0) {
    484     return;
    485   }
    486   if (stat(input_path, &statbuf) != 0) {
    487     return;
    488   }
    489   times.actime = statbuf.st_atime;
    490   times.modtime = statbuf.st_mtime;
    491   utime(output_path, &times);
    492   res = chmod(output_path, statbuf.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));
    493   if (res != 0) {
    494     fprintf(stderr, "setting access bits failed for [%s]: %s\n",
    495             PrintablePath(output_path), strerror(errno));
    496   }
    497   res = chown(output_path, (uid_t)-1, statbuf.st_gid);
    498   if (res != 0) {
    499     fprintf(stderr, "setting group failed for [%s]: %s\n",
    500             PrintablePath(output_path), strerror(errno));
    501   }
    502   res = chown(output_path, statbuf.st_uid, (gid_t)-1);
    503   if (res != 0) {
    504     fprintf(stderr, "setting user failed for [%s]: %s\n",
    505             PrintablePath(output_path), strerror(errno));
    506   }
    507 }
    508 
    509 static BROTLI_BOOL NextFile(Context* context) {
    510   const char* arg;
    511   size_t arg_len;
    512 
    513   /* Iterator points to last used arg; increment to search for the next one. */
    514   context->iterator++;
    515 
    516   /* No input path; read from console. */
    517   if (context->input_count == 0) {
    518     if (context->iterator > 1) return BROTLI_FALSE;
    519     context->current_input_path = NULL;
    520     /* Either write to the specified path, or to console. */
    521     context->current_output_path = context->output_path;
    522     return BROTLI_TRUE;
    523   }
    524 
    525   /* Skip option arguments. */
    526   while (context->iterator == context->not_input_indices[context->ignore]) {
    527     context->iterator++;
    528     context->ignore++;
    529   }
    530 
    531   /* All args are scanned already. */
    532   if (context->iterator >= context->argc) return BROTLI_FALSE;
    533 
    534   /* Iterator now points to the input file name. */
    535   arg = context->argv[context->iterator];
    536   arg_len = strlen(arg);
    537   /* Read from console. */
    538   if (arg_len == 1 && arg[0] == '-') {
    539     context->current_input_path = NULL;
    540     context->current_output_path = context->output_path;
    541     return BROTLI_TRUE;
    542   }
    543 
    544   context->current_input_path = arg;
    545   context->current_output_path = context->output_path;
    546 
    547   if (context->output_path) return BROTLI_TRUE;
    548   if (context->write_to_stdout) return BROTLI_TRUE;
    549 
    550   strcpy(context->modified_path, arg);
    551   context->current_output_path = context->modified_path;
    552   /* If output is not specified, input path suffix should match. */
    553   if (context->decompress) {
    554     size_t suffix_len = strlen(context->suffix);
    555     char* name = (char*)FileName(context->modified_path);
    556     char* name_suffix;
    557     size_t name_len = strlen(name);
    558     if (name_len < suffix_len + 1) {
    559       fprintf(stderr, "empty output file name for [%s] input file\n",
    560               PrintablePath(arg));
    561       context->iterator_error = BROTLI_TRUE;
    562       return BROTLI_FALSE;
    563     }
    564     name_suffix = name + name_len - suffix_len;
    565     if (strcmp(context->suffix, name_suffix) != 0) {
    566       fprintf(stderr, "input file [%s] suffix mismatch\n",
    567               PrintablePath(arg));
    568       context->iterator_error = BROTLI_TRUE;
    569       return BROTLI_FALSE;
    570     }
    571     name_suffix[0] = 0;
    572     return BROTLI_TRUE;
    573   } else {
    574     strcpy(context->modified_path + arg_len, context->suffix);
    575     return BROTLI_TRUE;
    576   }
    577 }
    578 
    579 static BROTLI_BOOL OpenFiles(Context* context) {
    580   BROTLI_BOOL is_ok = OpenInputFile(context->current_input_path, &context->fin);
    581   if (!context->test_integrity && is_ok) {
    582     is_ok = OpenOutputFile(
    583         context->current_output_path, &context->fout, context->force_overwrite);
    584   }
    585   return is_ok;
    586 }
    587 
    588 static BROTLI_BOOL CloseFiles(Context* context, BROTLI_BOOL success) {
    589   BROTLI_BOOL is_ok = BROTLI_TRUE;
    590   if (!context->test_integrity && context->fout) {
    591     if (!success && context->current_output_path) {
    592       unlink(context->current_output_path);
    593     }
    594     if (fclose(context->fout) != 0) {
    595       if (success) {
    596         fprintf(stderr, "fclose failed [%s]: %s\n",
    597                 PrintablePath(context->current_output_path), strerror(errno));
    598       }
    599       is_ok = BROTLI_FALSE;
    600     }
    601 
    602     /* TOCTOU violation, but otherwise it is impossible to set file times. */
    603     if (success && is_ok && context->copy_stat) {
    604       CopyStat(context->current_input_path, context->current_output_path);
    605     }
    606   }
    607 
    608   if (context->fin) {
    609     if (fclose(context->fin) != 0) {
    610       if (is_ok) {
    611         fprintf(stderr, "fclose failed [%s]: %s\n",
    612                 PrintablePath(context->current_input_path), strerror(errno));
    613       }
    614       is_ok = BROTLI_FALSE;
    615     }
    616   }
    617   if (success && context->junk_source && context->current_input_path) {
    618     unlink(context->current_input_path);
    619   }
    620 
    621   context->fin = NULL;
    622   context->fout = NULL;
    623 
    624   return is_ok;
    625 }
    626 
    627 static const size_t kFileBufferSize = 1 << 16;
    628 
    629 static BROTLI_BOOL DecompressFile(Context* context, BrotliDecoderState* s) {
    630   size_t available_in = 0;
    631   const uint8_t* next_in = NULL;
    632   size_t available_out = kFileBufferSize;
    633   uint8_t* next_out = context->output;
    634   BrotliDecoderResult result = BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT;
    635   for (;;) {
    636     if (next_out != context->output) {
    637       if (!context->test_integrity) {
    638         size_t out_size = (size_t)(next_out - context->output);
    639         fwrite(context->output, 1, out_size, context->fout);
    640         if (ferror(context->fout)) {
    641           fprintf(stderr, "failed to write output [%s]: %s\n",
    642                   PrintablePath(context->current_output_path), strerror(errno));
    643           return BROTLI_FALSE;
    644         }
    645       }
    646       available_out = kFileBufferSize;
    647       next_out = context->output;
    648     }
    649 
    650     if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) {
    651       if (feof(context->fin)) {
    652         fprintf(stderr, "corrupt input [%s]\n",
    653                 PrintablePath(context->current_input_path));
    654         return BROTLI_FALSE;
    655       }
    656       available_in = fread(context->input, 1, kFileBufferSize, context->fin);
    657       next_in = context->input;
    658       if (ferror(context->fin)) {
    659         fprintf(stderr, "failed to read input [%s]: %s\n",
    660                 PrintablePath(context->current_input_path), strerror(errno));
    661       return BROTLI_FALSE;
    662       }
    663     } else if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
    664       /* Nothing to do - output is already written. */
    665     } else if (result == BROTLI_DECODER_RESULT_SUCCESS) {
    666       if (available_in != 0 || !feof(context->fin)) {
    667         fprintf(stderr, "corrupt input [%s]\n",
    668                 PrintablePath(context->current_input_path));
    669         return BROTLI_FALSE;
    670       }
    671       return BROTLI_TRUE;
    672     } else {
    673       fprintf(stderr, "corrupt input [%s]\n",
    674               PrintablePath(context->current_input_path));
    675       return BROTLI_FALSE;
    676     }
    677 
    678     result = BrotliDecoderDecompressStream(
    679         s, &available_in, &next_in, &available_out, &next_out, 0);
    680   }
    681 }
    682 
    683 static BROTLI_BOOL DecompressFiles(Context* context) {
    684   while (NextFile(context)) {
    685     BROTLI_BOOL is_ok = BROTLI_TRUE;
    686     BrotliDecoderState* s = BrotliDecoderCreateInstance(NULL, NULL, NULL);
    687     if (!s) {
    688       fprintf(stderr, "out of memory\n");
    689       return BROTLI_FALSE;
    690     }
    691     is_ok = OpenFiles(context);
    692     if (is_ok && !context->current_input_path &&
    693         !context->force_overwrite && isatty(STDIN_FILENO)) {
    694       fprintf(stderr, "Use -h help. Use -f to force input from a terminal.\n");
    695       is_ok = BROTLI_FALSE;
    696     }
    697     if (is_ok) is_ok = DecompressFile(context, s);
    698     BrotliDecoderDestroyInstance(s);
    699     if (!CloseFiles(context, is_ok)) is_ok = BROTLI_FALSE;
    700     if (!is_ok) return BROTLI_FALSE;
    701   }
    702   return BROTLI_TRUE;
    703 }
    704 
    705 static BROTLI_BOOL CompressFile(Context* context, BrotliEncoderState* s) {
    706   size_t available_in = 0;
    707   const uint8_t* next_in = NULL;
    708   size_t available_out = kFileBufferSize;
    709   uint8_t* next_out = context->output;
    710   BROTLI_BOOL is_eof = BROTLI_FALSE;
    711 
    712   for (;;) {
    713     if (available_in == 0 && !is_eof) {
    714       available_in = fread(context->input, 1, kFileBufferSize, context->fin);
    715       next_in = context->input;
    716       if (ferror(context->fin)) {
    717         fprintf(stderr, "failed to read input [%s]: %s\n",
    718                 PrintablePath(context->current_input_path), strerror(errno));
    719         return BROTLI_FALSE;
    720       }
    721       is_eof = feof(context->fin) ? BROTLI_TRUE : BROTLI_FALSE;
    722     }
    723 
    724     if (!BrotliEncoderCompressStream(s,
    725         is_eof ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS,
    726         &available_in, &next_in, &available_out, &next_out, NULL)) {
    727       /* Should detect OOM? */
    728       fprintf(stderr, "failed to compress data [%s]\n",
    729               PrintablePath(context->current_input_path));
    730       return BROTLI_FALSE;
    731     }
    732 
    733     if (available_out != kFileBufferSize) {
    734       size_t out_size = kFileBufferSize - available_out;
    735       fwrite(context->output, 1, out_size, context->fout);
    736       if (ferror(context->fout)) {
    737         fprintf(stderr, "failed to write output [%s]: %s\n",
    738                 PrintablePath(context->current_output_path), strerror(errno));
    739         return BROTLI_FALSE;
    740       }
    741       available_out = kFileBufferSize;
    742       next_out = context->output;
    743     }
    744 
    745     if (BrotliEncoderIsFinished(s)) return BROTLI_TRUE;
    746   }
    747 }
    748 
    749 static BROTLI_BOOL CompressFiles(Context* context) {
    750   while (NextFile(context)) {
    751     BROTLI_BOOL is_ok = BROTLI_TRUE;
    752     BrotliEncoderState* s = BrotliEncoderCreateInstance(NULL, NULL, NULL);
    753     if (!s) {
    754       fprintf(stderr, "out of memory\n");
    755       return BROTLI_FALSE;
    756     }
    757     BrotliEncoderSetParameter(s,
    758         BROTLI_PARAM_QUALITY, (uint32_t)context->quality);
    759     BrotliEncoderSetParameter(s,
    760         BROTLI_PARAM_LGWIN, (uint32_t)context->lgwin);
    761     is_ok = OpenFiles(context);
    762     if (is_ok && !context->current_output_path &&
    763         !context->force_overwrite && isatty(STDOUT_FILENO)) {
    764       fprintf(stderr, "Use -h help. Use -f to force output to a terminal.\n");
    765       is_ok = BROTLI_FALSE;
    766     }
    767     if (is_ok) is_ok = CompressFile(context, s);
    768     BrotliEncoderDestroyInstance(s);
    769     if (!CloseFiles(context, is_ok)) is_ok = BROTLI_FALSE;
    770     if (!is_ok) return BROTLI_FALSE;
    771   }
    772   return BROTLI_TRUE;
    773 }
    774 
    775 int main(int argc, char** argv) {
    776   Command command;
    777   Context context;
    778   BROTLI_BOOL is_ok = BROTLI_TRUE;
    779   int i;
    780 
    781   context.quality = 11;
    782   context.lgwin = DEFAULT_LGWIN;
    783   context.force_overwrite = BROTLI_FALSE;
    784   context.junk_source = BROTLI_FALSE;
    785   context.copy_stat = BROTLI_TRUE;
    786   context.test_integrity = BROTLI_FALSE;
    787   context.verbose = BROTLI_FALSE;
    788   context.write_to_stdout = BROTLI_FALSE;
    789   context.decompress = BROTLI_FALSE;
    790   context.output_path = NULL;
    791   context.suffix = DEFAULT_SUFFIX;
    792   for (i = 0; i < MAX_OPTIONS; ++i) context.not_input_indices[i] = 0;
    793   context.longest_path_len = 1;
    794   context.input_count = 0;
    795 
    796   context.argc = argc;
    797   context.argv = argv;
    798   context.modified_path = NULL;
    799   context.iterator = 0;
    800   context.ignore = 0;
    801   context.iterator_error = BROTLI_FALSE;
    802   context.buffer = NULL;
    803   context.current_input_path = NULL;
    804   context.current_output_path = NULL;
    805   context.fin = NULL;
    806   context.fout = NULL;
    807 
    808   command = ParseParams(&context);
    809 
    810   if (command == COMMAND_COMPRESS || command == COMMAND_DECOMPRESS ||
    811       command == COMMAND_TEST_INTEGRITY) {
    812     if (is_ok) {
    813       size_t modified_path_len =
    814           context.longest_path_len + strlen(context.suffix) + 1;
    815       context.modified_path = (char*)malloc(modified_path_len);
    816       context.buffer = (uint8_t*)malloc(kFileBufferSize * 2);
    817       if (!context.modified_path || !context.buffer) {
    818         fprintf(stderr, "out of memory\n");
    819         is_ok = BROTLI_FALSE;
    820       } else {
    821         context.input = context.buffer;
    822         context.output = context.buffer + kFileBufferSize;
    823       }
    824     }
    825   }
    826 
    827   if (!is_ok) command = COMMAND_NOOP;
    828 
    829   switch (command) {
    830     case COMMAND_NOOP:
    831       break;
    832 
    833     case COMMAND_VERSION:
    834       PrintVersion();
    835       break;
    836 
    837     case COMMAND_COMPRESS:
    838       is_ok = CompressFiles(&context);
    839       break;
    840 
    841     case COMMAND_DECOMPRESS:
    842     case COMMAND_TEST_INTEGRITY:
    843       is_ok = DecompressFiles(&context);
    844       break;
    845 
    846     case COMMAND_HELP:
    847     case COMMAND_INVALID:
    848     default:
    849       PrintHelp(FileName(argv[0]));
    850       is_ok = (command == COMMAND_HELP);
    851       break;
    852   }
    853 
    854   if (context.iterator_error) is_ok = BROTLI_FALSE;
    855 
    856   free(context.modified_path);
    857   free(context.buffer);
    858 
    859   if (!is_ok) exit(1);
    860   return 0;
    861 }
    862