126fa459cSmrg/* Copyright 2014 Google Inc. All Rights Reserved.
226fa459cSmrg
326fa459cSmrg   Distributed under MIT license.
426fa459cSmrg   See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
526fa459cSmrg*/
626fa459cSmrg
726fa459cSmrg/* Command line interface for Brotli library. */
826fa459cSmrg
926fa459cSmrg/* Mute strerror/strcpy warnings. */
1026fa459cSmrg#if !defined(_CRT_SECURE_NO_WARNINGS)
1126fa459cSmrg#define _CRT_SECURE_NO_WARNINGS
1226fa459cSmrg#endif
1326fa459cSmrg
1426fa459cSmrg#include <errno.h>
1526fa459cSmrg#include <fcntl.h>
1626fa459cSmrg#include <stdio.h>
1726fa459cSmrg#include <stdlib.h>
1826fa459cSmrg#include <string.h>
1926fa459cSmrg#include <sys/stat.h>
2026fa459cSmrg#include <sys/types.h>
2126fa459cSmrg#include <time.h>
2226fa459cSmrg
2326fa459cSmrg#include "../common/constants.h"
2426fa459cSmrg#include "../common/version.h"
2526fa459cSmrg#include <brotli/decode.h>
2626fa459cSmrg#include <brotli/encode.h>
2726fa459cSmrg
2826fa459cSmrg#if !defined(_WIN32)
2926fa459cSmrg#include <unistd.h>
3026fa459cSmrg#include <utime.h>
3126fa459cSmrg#define MAKE_BINARY(FILENO) (FILENO)
3226fa459cSmrg#else
3326fa459cSmrg#include <io.h>
3426fa459cSmrg#include <share.h>
3526fa459cSmrg#include <sys/utime.h>
3626fa459cSmrg
3726fa459cSmrg#define MAKE_BINARY(FILENO) (_setmode((FILENO), _O_BINARY), (FILENO))
3826fa459cSmrg
3926fa459cSmrg#if !defined(__MINGW32__)
4026fa459cSmrg#define STDIN_FILENO _fileno(stdin)
4126fa459cSmrg#define STDOUT_FILENO _fileno(stdout)
4226fa459cSmrg#define S_IRUSR S_IREAD
4326fa459cSmrg#define S_IWUSR S_IWRITE
4426fa459cSmrg#endif
4526fa459cSmrg
4626fa459cSmrg#define fdopen _fdopen
4726fa459cSmrg#define isatty _isatty
4826fa459cSmrg#define unlink _unlink
4926fa459cSmrg#define utimbuf _utimbuf
5026fa459cSmrg#define utime _utime
5126fa459cSmrg
5226fa459cSmrg#define fopen ms_fopen
5326fa459cSmrg#define open ms_open
5426fa459cSmrg
5526fa459cSmrg#define chmod(F, P) (0)
5626fa459cSmrg#define chown(F, O, G) (0)
5726fa459cSmrg
5826fa459cSmrg#if defined(_MSC_VER) && (_MSC_VER >= 1400)
5926fa459cSmrg#define fseek _fseeki64
6026fa459cSmrg#define ftell _ftelli64
6126fa459cSmrg#endif
6226fa459cSmrg
6326fa459cSmrgstatic FILE* ms_fopen(const char* filename, const char* mode) {
6426fa459cSmrg  FILE* result = 0;
6526fa459cSmrg  fopen_s(&result, filename, mode);
6626fa459cSmrg  return result;
6726fa459cSmrg}
6826fa459cSmrg
6926fa459cSmrgstatic int ms_open(const char* filename, int oflag, int pmode) {
7026fa459cSmrg  int result = -1;
7126fa459cSmrg  _sopen_s(&result, filename, oflag | O_BINARY, _SH_DENYNO, pmode);
7226fa459cSmrg  return result;
7326fa459cSmrg}
7426fa459cSmrg#endif  /* WIN32 */
7526fa459cSmrg
7626fa459cSmrgtypedef enum {
7726fa459cSmrg  COMMAND_COMPRESS,
7826fa459cSmrg  COMMAND_DECOMPRESS,
7926fa459cSmrg  COMMAND_HELP,
8026fa459cSmrg  COMMAND_INVALID,
8126fa459cSmrg  COMMAND_TEST_INTEGRITY,
8226fa459cSmrg  COMMAND_NOOP,
8326fa459cSmrg  COMMAND_VERSION
8426fa459cSmrg} Command;
8526fa459cSmrg
8626fa459cSmrg#define DEFAULT_LGWIN 24
8726fa459cSmrg#define DEFAULT_SUFFIX ".br"
8826fa459cSmrg#define MAX_OPTIONS 20
8926fa459cSmrg
9026fa459cSmrgtypedef struct {
9126fa459cSmrg  /* Parameters */
9226fa459cSmrg  int quality;
9326fa459cSmrg  int lgwin;
9426fa459cSmrg  int verbosity;
9526fa459cSmrg  BROTLI_BOOL force_overwrite;
9626fa459cSmrg  BROTLI_BOOL junk_source;
9726fa459cSmrg  BROTLI_BOOL copy_stat;
9826fa459cSmrg  BROTLI_BOOL write_to_stdout;
9926fa459cSmrg  BROTLI_BOOL test_integrity;
10026fa459cSmrg  BROTLI_BOOL decompress;
10126fa459cSmrg  BROTLI_BOOL large_window;
10226fa459cSmrg  const char* output_path;
10326fa459cSmrg  const char* suffix;
10426fa459cSmrg  int not_input_indices[MAX_OPTIONS];
10526fa459cSmrg  size_t longest_path_len;
10626fa459cSmrg  size_t input_count;
10726fa459cSmrg
10826fa459cSmrg  /* Inner state */
10926fa459cSmrg  int argc;
11026fa459cSmrg  char** argv;
11126fa459cSmrg  char* modified_path;  /* Storage for path with appended / cut suffix */
11226fa459cSmrg  int iterator;
11326fa459cSmrg  int ignore;
11426fa459cSmrg  BROTLI_BOOL iterator_error;
11526fa459cSmrg  uint8_t* buffer;
11626fa459cSmrg  uint8_t* input;
11726fa459cSmrg  uint8_t* output;
11826fa459cSmrg  const char* current_input_path;
11926fa459cSmrg  const char* current_output_path;
12026fa459cSmrg  int64_t input_file_length;  /* -1, if impossible to calculate */
12126fa459cSmrg  FILE* fin;
12226fa459cSmrg  FILE* fout;
12326fa459cSmrg
12426fa459cSmrg  /* I/O buffers */
12526fa459cSmrg  size_t available_in;
12626fa459cSmrg  const uint8_t* next_in;
12726fa459cSmrg  size_t available_out;
12826fa459cSmrg  uint8_t* next_out;
12926fa459cSmrg
13026fa459cSmrg  /* Reporting */
13126fa459cSmrg  /* size_t would be large enough,
13226fa459cSmrg     until 4GiB+ files are compressed / decompressed on 32-bit CPUs. */
13326fa459cSmrg  size_t total_in;
13426fa459cSmrg  size_t total_out;
13526fa459cSmrg} Context;
13626fa459cSmrg
13726fa459cSmrg/* Parse up to 5 decimal digits. */
13826fa459cSmrgstatic BROTLI_BOOL ParseInt(const char* s, int low, int high, int* result) {
13926fa459cSmrg  int value = 0;
14026fa459cSmrg  int i;
14126fa459cSmrg  for (i = 0; i < 5; ++i) {
14226fa459cSmrg    char c = s[i];
14326fa459cSmrg    if (c == 0) break;
14426fa459cSmrg    if (s[i] < '0' || s[i] > '9') return BROTLI_FALSE;
14526fa459cSmrg    value = (10 * value) + (c - '0');
14626fa459cSmrg  }
14726fa459cSmrg  if (i == 0) return BROTLI_FALSE;
14826fa459cSmrg  if (i > 1 && s[0] == '0') return BROTLI_FALSE;
14926fa459cSmrg  if (s[i] != 0) return BROTLI_FALSE;
15026fa459cSmrg  if (value < low || value > high) return BROTLI_FALSE;
15126fa459cSmrg  *result = value;
15226fa459cSmrg  return BROTLI_TRUE;
15326fa459cSmrg}
15426fa459cSmrg
15526fa459cSmrg/* Returns "base file name" or its tail, if it contains '/' or '\'. */
15626fa459cSmrgstatic const char* FileName(const char* path) {
15726fa459cSmrg  const char* separator_position = strrchr(path, '/');
15826fa459cSmrg  if (separator_position) path = separator_position + 1;
15926fa459cSmrg  separator_position = strrchr(path, '\\');
16026fa459cSmrg  if (separator_position) path = separator_position + 1;
16126fa459cSmrg  return path;
16226fa459cSmrg}
16326fa459cSmrg
16426fa459cSmrg/* Detect if the program name is a special alias that infers a command type. */
16526fa459cSmrgstatic Command ParseAlias(const char* name) {
16626fa459cSmrg  /* TODO: cast name to lower case? */
16726fa459cSmrg  const char* unbrotli = "unbrotli";
16826fa459cSmrg  size_t unbrotli_len = strlen(unbrotli);
16926fa459cSmrg  name = FileName(name);
17026fa459cSmrg  /* Partial comparison. On Windows there could be ".exe" suffix. */
17126fa459cSmrg  if (strncmp(name, unbrotli, unbrotli_len) == 0) {
17226fa459cSmrg    char terminator = name[unbrotli_len];
17326fa459cSmrg    if (terminator == 0 || terminator == '.') return COMMAND_DECOMPRESS;
17426fa459cSmrg  }
17526fa459cSmrg  return COMMAND_COMPRESS;
17626fa459cSmrg}
17726fa459cSmrg
17826fa459cSmrgstatic Command ParseParams(Context* params) {
17926fa459cSmrg  int argc = params->argc;
18026fa459cSmrg  char** argv = params->argv;
18126fa459cSmrg  int i;
18226fa459cSmrg  int next_option_index = 0;
18326fa459cSmrg  size_t input_count = 0;
18426fa459cSmrg  size_t longest_path_len = 1;
18526fa459cSmrg  BROTLI_BOOL command_set = BROTLI_FALSE;
18626fa459cSmrg  BROTLI_BOOL quality_set = BROTLI_FALSE;
18726fa459cSmrg  BROTLI_BOOL output_set = BROTLI_FALSE;
18826fa459cSmrg  BROTLI_BOOL keep_set = BROTLI_FALSE;
18926fa459cSmrg  BROTLI_BOOL lgwin_set = BROTLI_FALSE;
19026fa459cSmrg  BROTLI_BOOL suffix_set = BROTLI_FALSE;
19126fa459cSmrg  BROTLI_BOOL after_dash_dash = BROTLI_FALSE;
19226fa459cSmrg  Command command = ParseAlias(argv[0]);
19326fa459cSmrg
19426fa459cSmrg  for (i = 1; i < argc; ++i) {
19526fa459cSmrg    const char* arg = argv[i];
19626fa459cSmrg    /* C99 5.1.2.2.1: "members argv[0] through argv[argc-1] inclusive shall
19726fa459cSmrg       contain pointers to strings"; NULL and 0-length are not forbidden. */
19826fa459cSmrg    size_t arg_len = arg ? strlen(arg) : 0;
19926fa459cSmrg
20026fa459cSmrg    if (arg_len == 0) {
20126fa459cSmrg      params->not_input_indices[next_option_index++] = i;
20226fa459cSmrg      continue;
20326fa459cSmrg    }
20426fa459cSmrg
20526fa459cSmrg    /* Too many options. The expected longest option list is:
20626fa459cSmrg       "-q 0 -w 10 -o f -D d -S b -d -f -k -n -v --", i.e. 16 items in total.
20726fa459cSmrg       This check is an additional guard that is never triggered, but provides
20826fa459cSmrg       a guard for future changes. */
20926fa459cSmrg    if (next_option_index > (MAX_OPTIONS - 2)) {
21026fa459cSmrg      fprintf(stderr, "too many options passed\n");
21126fa459cSmrg      return COMMAND_INVALID;
21226fa459cSmrg    }
21326fa459cSmrg
21426fa459cSmrg    /* Input file entry. */
21526fa459cSmrg    if (after_dash_dash || arg[0] != '-' || arg_len == 1) {
21626fa459cSmrg      input_count++;
21726fa459cSmrg      if (longest_path_len < arg_len) longest_path_len = arg_len;
21826fa459cSmrg      continue;
21926fa459cSmrg    }
22026fa459cSmrg
22126fa459cSmrg    /* Not a file entry. */
22226fa459cSmrg    params->not_input_indices[next_option_index++] = i;
22326fa459cSmrg
22426fa459cSmrg    /* '--' entry stop parsing arguments. */
22526fa459cSmrg    if (arg_len == 2 && arg[1] == '-') {
22626fa459cSmrg      after_dash_dash = BROTLI_TRUE;
22726fa459cSmrg      continue;
22826fa459cSmrg    }
22926fa459cSmrg
23026fa459cSmrg    /* Simple / coalesced options. */
23126fa459cSmrg    if (arg[1] != '-') {
23226fa459cSmrg      size_t j;
23326fa459cSmrg      for (j = 1; j < arg_len; ++j) {
23426fa459cSmrg        char c = arg[j];
23526fa459cSmrg        if (c >= '0' && c <= '9') {
23626fa459cSmrg          if (quality_set) {
23726fa459cSmrg            fprintf(stderr, "quality already set\n");
23826fa459cSmrg            return COMMAND_INVALID;
23926fa459cSmrg          }
24026fa459cSmrg          quality_set = BROTLI_TRUE;
24126fa459cSmrg          params->quality = c - '0';
24226fa459cSmrg          continue;
24326fa459cSmrg        } else if (c == 'c') {
24426fa459cSmrg          if (output_set) {
24526fa459cSmrg            fprintf(stderr, "write to standard output already set\n");
24626fa459cSmrg            return COMMAND_INVALID;
24726fa459cSmrg          }
24826fa459cSmrg          output_set = BROTLI_TRUE;
24926fa459cSmrg          params->write_to_stdout = BROTLI_TRUE;
25026fa459cSmrg          continue;
25126fa459cSmrg        } else if (c == 'd') {
25226fa459cSmrg          if (command_set) {
25326fa459cSmrg            fprintf(stderr, "command already set when parsing -d\n");
25426fa459cSmrg            return COMMAND_INVALID;
25526fa459cSmrg          }
25626fa459cSmrg          command_set = BROTLI_TRUE;
25726fa459cSmrg          command = COMMAND_DECOMPRESS;
25826fa459cSmrg          continue;
25926fa459cSmrg        } else if (c == 'f') {
26026fa459cSmrg          if (params->force_overwrite) {
26126fa459cSmrg            fprintf(stderr, "force output overwrite already set\n");
26226fa459cSmrg            return COMMAND_INVALID;
26326fa459cSmrg          }
26426fa459cSmrg          params->force_overwrite = BROTLI_TRUE;
26526fa459cSmrg          continue;
26626fa459cSmrg        } else if (c == 'h') {
26726fa459cSmrg          /* Don't parse further. */
26826fa459cSmrg          return COMMAND_HELP;
26926fa459cSmrg        } else if (c == 'j' || c == 'k') {
27026fa459cSmrg          if (keep_set) {
27126fa459cSmrg            fprintf(stderr, "argument --rm / -j or --keep / -k already set\n");
27226fa459cSmrg            return COMMAND_INVALID;
27326fa459cSmrg          }
27426fa459cSmrg          keep_set = BROTLI_TRUE;
27526fa459cSmrg          params->junk_source = TO_BROTLI_BOOL(c == 'j');
27626fa459cSmrg          continue;
27726fa459cSmrg        } else if (c == 'n') {
27826fa459cSmrg          if (!params->copy_stat) {
27926fa459cSmrg            fprintf(stderr, "argument --no-copy-stat / -n already set\n");
28026fa459cSmrg            return COMMAND_INVALID;
28126fa459cSmrg          }
28226fa459cSmrg          params->copy_stat = BROTLI_FALSE;
28326fa459cSmrg          continue;
28426fa459cSmrg        } else if (c == 't') {
28526fa459cSmrg          if (command_set) {
28626fa459cSmrg            fprintf(stderr, "command already set when parsing -t\n");
28726fa459cSmrg            return COMMAND_INVALID;
28826fa459cSmrg          }
28926fa459cSmrg          command_set = BROTLI_TRUE;
29026fa459cSmrg          command = COMMAND_TEST_INTEGRITY;
29126fa459cSmrg          continue;
29226fa459cSmrg        } else if (c == 'v') {
29326fa459cSmrg          if (params->verbosity > 0) {
29426fa459cSmrg            fprintf(stderr, "argument --verbose / -v already set\n");
29526fa459cSmrg            return COMMAND_INVALID;
29626fa459cSmrg          }
29726fa459cSmrg          params->verbosity = 1;
29826fa459cSmrg          continue;
29926fa459cSmrg        } else if (c == 'V') {
30026fa459cSmrg          /* Don't parse further. */
30126fa459cSmrg          return COMMAND_VERSION;
30226fa459cSmrg        } else if (c == 'Z') {
30326fa459cSmrg          if (quality_set) {
30426fa459cSmrg            fprintf(stderr, "quality already set\n");
30526fa459cSmrg            return COMMAND_INVALID;
30626fa459cSmrg          }
30726fa459cSmrg          quality_set = BROTLI_TRUE;
30826fa459cSmrg          params->quality = 11;
30926fa459cSmrg          continue;
31026fa459cSmrg        }
31126fa459cSmrg        /* o/q/w/D/S with parameter is expected */
31226fa459cSmrg        if (c != 'o' && c != 'q' && c != 'w' && c != 'D' && c != 'S') {
31326fa459cSmrg          fprintf(stderr, "invalid argument -%c\n", c);
31426fa459cSmrg          return COMMAND_INVALID;
31526fa459cSmrg        }
31626fa459cSmrg        if (j + 1 != arg_len) {
31726fa459cSmrg          fprintf(stderr, "expected parameter for argument -%c\n", c);
31826fa459cSmrg          return COMMAND_INVALID;
31926fa459cSmrg        }
32026fa459cSmrg        i++;
32126fa459cSmrg        if (i == argc || !argv[i] || argv[i][0] == 0) {
32226fa459cSmrg          fprintf(stderr, "expected parameter for argument -%c\n", c);
32326fa459cSmrg          return COMMAND_INVALID;
32426fa459cSmrg        }
32526fa459cSmrg        params->not_input_indices[next_option_index++] = i;
32626fa459cSmrg        if (c == 'o') {
32726fa459cSmrg          if (output_set) {
32826fa459cSmrg            fprintf(stderr, "write to standard output already set (-o)\n");
32926fa459cSmrg            return COMMAND_INVALID;
33026fa459cSmrg          }
33126fa459cSmrg          params->output_path = argv[i];
33226fa459cSmrg        } else if (c == 'q') {
33326fa459cSmrg          if (quality_set) {
33426fa459cSmrg            fprintf(stderr, "quality already set\n");
33526fa459cSmrg            return COMMAND_INVALID;
33626fa459cSmrg          }
33726fa459cSmrg          quality_set = ParseInt(argv[i], BROTLI_MIN_QUALITY,
33826fa459cSmrg                                 BROTLI_MAX_QUALITY, &params->quality);
33926fa459cSmrg          if (!quality_set) {
34026fa459cSmrg            fprintf(stderr, "error parsing quality value [%s]\n", argv[i]);
34126fa459cSmrg            return COMMAND_INVALID;
34226fa459cSmrg          }
34326fa459cSmrg        } else if (c == 'w') {
34426fa459cSmrg          if (lgwin_set) {
34526fa459cSmrg            fprintf(stderr, "lgwin parameter already set\n");
34626fa459cSmrg            return COMMAND_INVALID;
34726fa459cSmrg          }
34826fa459cSmrg          lgwin_set = ParseInt(argv[i], 0,
34926fa459cSmrg                               BROTLI_MAX_WINDOW_BITS, &params->lgwin);
35026fa459cSmrg          if (!lgwin_set) {
35126fa459cSmrg            fprintf(stderr, "error parsing lgwin value [%s]\n", argv[i]);
35226fa459cSmrg            return COMMAND_INVALID;
35326fa459cSmrg          }
35426fa459cSmrg          if (params->lgwin != 0 && params->lgwin < BROTLI_MIN_WINDOW_BITS) {
35526fa459cSmrg            fprintf(stderr,
35626fa459cSmrg                    "lgwin parameter (%d) smaller than the minimum (%d)\n",
35726fa459cSmrg                    params->lgwin, BROTLI_MIN_WINDOW_BITS);
35826fa459cSmrg            return COMMAND_INVALID;
35926fa459cSmrg          }
36026fa459cSmrg        } else if (c == 'S') {
36126fa459cSmrg          if (suffix_set) {
36226fa459cSmrg            fprintf(stderr, "suffix already set\n");
36326fa459cSmrg            return COMMAND_INVALID;
36426fa459cSmrg          }
36526fa459cSmrg          suffix_set = BROTLI_TRUE;
36626fa459cSmrg          params->suffix = argv[i];
36726fa459cSmrg        }
36826fa459cSmrg      }
36926fa459cSmrg    } else {  /* Double-dash. */
37026fa459cSmrg      arg = &arg[2];
37126fa459cSmrg      if (strcmp("best", arg) == 0) {
37226fa459cSmrg        if (quality_set) {
37326fa459cSmrg          fprintf(stderr, "quality already set\n");
37426fa459cSmrg          return COMMAND_INVALID;
37526fa459cSmrg        }
37626fa459cSmrg        quality_set = BROTLI_TRUE;
37726fa459cSmrg        params->quality = 11;
37826fa459cSmrg      } else if (strcmp("decompress", arg) == 0) {
37926fa459cSmrg        if (command_set) {
38026fa459cSmrg          fprintf(stderr, "command already set when parsing --decompress\n");
38126fa459cSmrg          return COMMAND_INVALID;
38226fa459cSmrg        }
38326fa459cSmrg        command_set = BROTLI_TRUE;
38426fa459cSmrg        command = COMMAND_DECOMPRESS;
38526fa459cSmrg      } else if (strcmp("force", arg) == 0) {
38626fa459cSmrg        if (params->force_overwrite) {
38726fa459cSmrg          fprintf(stderr, "force output overwrite already set\n");
38826fa459cSmrg          return COMMAND_INVALID;
38926fa459cSmrg        }
39026fa459cSmrg        params->force_overwrite = BROTLI_TRUE;
39126fa459cSmrg      } else if (strcmp("help", arg) == 0) {
39226fa459cSmrg        /* Don't parse further. */
39326fa459cSmrg        return COMMAND_HELP;
39426fa459cSmrg      } else if (strcmp("keep", arg) == 0) {
39526fa459cSmrg        if (keep_set) {
39626fa459cSmrg          fprintf(stderr, "argument --rm / -j or --keep / -k already set\n");
39726fa459cSmrg          return COMMAND_INVALID;
39826fa459cSmrg        }
39926fa459cSmrg        keep_set = BROTLI_TRUE;
40026fa459cSmrg        params->junk_source = BROTLI_FALSE;
40126fa459cSmrg      } else if (strcmp("no-copy-stat", arg) == 0) {
40226fa459cSmrg        if (!params->copy_stat) {
40326fa459cSmrg          fprintf(stderr, "argument --no-copy-stat / -n already set\n");
40426fa459cSmrg          return COMMAND_INVALID;
40526fa459cSmrg        }
40626fa459cSmrg        params->copy_stat = BROTLI_FALSE;
40726fa459cSmrg      } else if (strcmp("rm", arg) == 0) {
40826fa459cSmrg        if (keep_set) {
40926fa459cSmrg          fprintf(stderr, "argument --rm / -j or --keep / -k already set\n");
41026fa459cSmrg          return COMMAND_INVALID;
41126fa459cSmrg        }
41226fa459cSmrg        keep_set = BROTLI_TRUE;
41326fa459cSmrg        params->junk_source = BROTLI_TRUE;
41426fa459cSmrg      } else if (strcmp("stdout", arg) == 0) {
41526fa459cSmrg        if (output_set) {
41626fa459cSmrg          fprintf(stderr, "write to standard output already set\n");
41726fa459cSmrg          return COMMAND_INVALID;
41826fa459cSmrg        }
41926fa459cSmrg        output_set = BROTLI_TRUE;
42026fa459cSmrg        params->write_to_stdout = BROTLI_TRUE;
42126fa459cSmrg      } else if (strcmp("test", arg) == 0) {
42226fa459cSmrg        if (command_set) {
42326fa459cSmrg          fprintf(stderr, "command already set when parsing --test\n");
42426fa459cSmrg          return COMMAND_INVALID;
42526fa459cSmrg        }
42626fa459cSmrg        command_set = BROTLI_TRUE;
42726fa459cSmrg        command = COMMAND_TEST_INTEGRITY;
42826fa459cSmrg      } else if (strcmp("verbose", arg) == 0) {
42926fa459cSmrg        if (params->verbosity > 0) {
43026fa459cSmrg          fprintf(stderr, "argument --verbose / -v already set\n");
43126fa459cSmrg          return COMMAND_INVALID;
43226fa459cSmrg        }
43326fa459cSmrg        params->verbosity = 1;
43426fa459cSmrg      } else if (strcmp("version", arg) == 0) {
43526fa459cSmrg        /* Don't parse further. */
43626fa459cSmrg        return COMMAND_VERSION;
43726fa459cSmrg      } else {
43826fa459cSmrg        /* key=value */
43926fa459cSmrg        const char* value = strrchr(arg, '=');
44026fa459cSmrg        size_t key_len;
44126fa459cSmrg        if (!value || value[1] == 0) {
44226fa459cSmrg          fprintf(stderr, "must pass the parameter as --%s=value\n", arg);
44326fa459cSmrg          return COMMAND_INVALID;
44426fa459cSmrg        }
44526fa459cSmrg        key_len = (size_t)(value - arg);
44626fa459cSmrg        value++;
44726fa459cSmrg        if (strncmp("lgwin", arg, key_len) == 0) {
44826fa459cSmrg          if (lgwin_set) {
44926fa459cSmrg            fprintf(stderr, "lgwin parameter already set\n");
45026fa459cSmrg            return COMMAND_INVALID;
45126fa459cSmrg          }
45226fa459cSmrg          lgwin_set = ParseInt(value, 0,
45326fa459cSmrg                               BROTLI_MAX_WINDOW_BITS, &params->lgwin);
45426fa459cSmrg          if (!lgwin_set) {
45526fa459cSmrg            fprintf(stderr, "error parsing lgwin value [%s]\n", value);
45626fa459cSmrg            return COMMAND_INVALID;
45726fa459cSmrg          }
45826fa459cSmrg          if (params->lgwin != 0 && params->lgwin < BROTLI_MIN_WINDOW_BITS) {
45926fa459cSmrg            fprintf(stderr,
46026fa459cSmrg                    "lgwin parameter (%d) smaller than the minimum (%d)\n",
46126fa459cSmrg                    params->lgwin, BROTLI_MIN_WINDOW_BITS);
46226fa459cSmrg            return COMMAND_INVALID;
46326fa459cSmrg          }
46426fa459cSmrg        } else if (strncmp("large_window", arg, key_len) == 0) {
46526fa459cSmrg          /* This option is intentionally not mentioned in help. */
46626fa459cSmrg          if (lgwin_set) {
46726fa459cSmrg            fprintf(stderr, "lgwin parameter already set\n");
46826fa459cSmrg            return COMMAND_INVALID;
46926fa459cSmrg          }
47026fa459cSmrg          lgwin_set = ParseInt(value, 0,
47126fa459cSmrg                               BROTLI_LARGE_MAX_WINDOW_BITS, &params->lgwin);
47226fa459cSmrg          if (!lgwin_set) {
47326fa459cSmrg            fprintf(stderr, "error parsing lgwin value [%s]\n", value);
47426fa459cSmrg            return COMMAND_INVALID;
47526fa459cSmrg          }
47626fa459cSmrg          if (params->lgwin != 0 && params->lgwin < BROTLI_MIN_WINDOW_BITS) {
47726fa459cSmrg            fprintf(stderr,
47826fa459cSmrg                    "lgwin parameter (%d) smaller than the minimum (%d)\n",
47926fa459cSmrg                    params->lgwin, BROTLI_MIN_WINDOW_BITS);
48026fa459cSmrg            return COMMAND_INVALID;
48126fa459cSmrg          }
48226fa459cSmrg        } else if (strncmp("output", arg, key_len) == 0) {
48326fa459cSmrg          if (output_set) {
48426fa459cSmrg            fprintf(stderr,
48526fa459cSmrg                    "write to standard output already set (--output)\n");
48626fa459cSmrg            return COMMAND_INVALID;
48726fa459cSmrg          }
48826fa459cSmrg          params->output_path = value;
48926fa459cSmrg        } else if (strncmp("quality", arg, key_len) == 0) {
49026fa459cSmrg          if (quality_set) {
49126fa459cSmrg            fprintf(stderr, "quality already set\n");
49226fa459cSmrg            return COMMAND_INVALID;
49326fa459cSmrg          }
49426fa459cSmrg          quality_set = ParseInt(value, BROTLI_MIN_QUALITY,
49526fa459cSmrg                                 BROTLI_MAX_QUALITY, &params->quality);
49626fa459cSmrg          if (!quality_set) {
49726fa459cSmrg            fprintf(stderr, "error parsing quality value [%s]\n", value);
49826fa459cSmrg            return COMMAND_INVALID;
49926fa459cSmrg          }
50026fa459cSmrg        } else if (strncmp("suffix", arg, key_len) == 0) {
50126fa459cSmrg          if (suffix_set) {
50226fa459cSmrg            fprintf(stderr, "suffix already set\n");
50326fa459cSmrg            return COMMAND_INVALID;
50426fa459cSmrg          }
50526fa459cSmrg          suffix_set = BROTLI_TRUE;
50626fa459cSmrg          params->suffix = value;
50726fa459cSmrg        } else {
50826fa459cSmrg          fprintf(stderr, "invalid parameter: [%s]\n", arg);
50926fa459cSmrg          return COMMAND_INVALID;
51026fa459cSmrg        }
51126fa459cSmrg      }
51226fa459cSmrg    }
51326fa459cSmrg  }
51426fa459cSmrg
51526fa459cSmrg  params->input_count = input_count;
51626fa459cSmrg  params->longest_path_len = longest_path_len;
51726fa459cSmrg  params->decompress = (command == COMMAND_DECOMPRESS);
51826fa459cSmrg  params->test_integrity = (command == COMMAND_TEST_INTEGRITY);
51926fa459cSmrg
52026fa459cSmrg  if (input_count > 1 && output_set) return COMMAND_INVALID;
52126fa459cSmrg  if (params->test_integrity) {
52226fa459cSmrg    if (params->output_path) return COMMAND_INVALID;
52326fa459cSmrg    if (params->write_to_stdout) return COMMAND_INVALID;
52426fa459cSmrg  }
52526fa459cSmrg  if (strchr(params->suffix, '/') || strchr(params->suffix, '\\')) {
52626fa459cSmrg    return COMMAND_INVALID;
52726fa459cSmrg  }
52826fa459cSmrg
52926fa459cSmrg  return command;
53026fa459cSmrg}
53126fa459cSmrg
53226fa459cSmrgstatic void PrintVersion(void) {
53326fa459cSmrg  int major = BROTLI_VERSION >> 24;
53426fa459cSmrg  int minor = (BROTLI_VERSION >> 12) & 0xFFF;
53526fa459cSmrg  int patch = BROTLI_VERSION & 0xFFF;
53626fa459cSmrg  fprintf(stdout, "brotli %d.%d.%d\n", major, minor, patch);
53726fa459cSmrg}
53826fa459cSmrg
53926fa459cSmrgstatic void PrintHelp(const char* name, BROTLI_BOOL error) {
54026fa459cSmrg  FILE* media = error ? stderr : stdout;
54126fa459cSmrg  /* String is cut to pieces with length less than 509, to conform C90 spec. */
54226fa459cSmrg  fprintf(media,
54326fa459cSmrg"Usage: %s [OPTION]... [FILE]...\n",
54426fa459cSmrg          name);
54526fa459cSmrg  fprintf(media,
54626fa459cSmrg"Options:\n"
54726fa459cSmrg"  -#                          compression level (0-9)\n"
54826fa459cSmrg"  -c, --stdout                write on standard output\n"
54926fa459cSmrg"  -d, --decompress            decompress\n"
55026fa459cSmrg"  -f, --force                 force output file overwrite\n"
55126fa459cSmrg"  -h, --help                  display this help and exit\n");
55226fa459cSmrg  fprintf(media,
55326fa459cSmrg"  -j, --rm                    remove source file(s)\n"
55426fa459cSmrg"  -k, --keep                  keep source file(s) (default)\n"
55526fa459cSmrg"  -n, --no-copy-stat          do not copy source file(s) attributes\n"
55626fa459cSmrg"  -o FILE, --output=FILE      output file (only if 1 input file)\n");
55726fa459cSmrg  fprintf(media,
55826fa459cSmrg"  -q NUM, --quality=NUM       compression level (%d-%d)\n",
55926fa459cSmrg          BROTLI_MIN_QUALITY, BROTLI_MAX_QUALITY);
56026fa459cSmrg  fprintf(media,
56126fa459cSmrg"  -t, --test                  test compressed file integrity\n"
56226fa459cSmrg"  -v, --verbose               verbose mode\n");
56326fa459cSmrg  fprintf(media,
56426fa459cSmrg"  -w NUM, --lgwin=NUM         set LZ77 window size (0, %d-%d)\n"
56526fa459cSmrg"                              window size = 2**NUM - 16\n"
56626fa459cSmrg"                              0 lets compressor choose the optimal value\n",
56726fa459cSmrg          BROTLI_MIN_WINDOW_BITS, BROTLI_MAX_WINDOW_BITS);
56826fa459cSmrg  fprintf(media,
56926fa459cSmrg"  --large_window=NUM          use incompatible large-window brotli\n"
57026fa459cSmrg"                              bitstream with window size (0, %d-%d)\n"
57126fa459cSmrg"                              WARNING: this format is not compatible\n"
57226fa459cSmrg"                              with brotli RFC 7932 and may not be\n"
57326fa459cSmrg"                              decodable with regular brotli decoders\n",
57426fa459cSmrg          BROTLI_MIN_WINDOW_BITS, BROTLI_LARGE_MAX_WINDOW_BITS);
57526fa459cSmrg  fprintf(media,
57626fa459cSmrg"  -S SUF, --suffix=SUF        output file suffix (default:'%s')\n",
57726fa459cSmrg          DEFAULT_SUFFIX);
57826fa459cSmrg  fprintf(media,
57926fa459cSmrg"  -V, --version               display version and exit\n"
58026fa459cSmrg"  -Z, --best                  use best compression level (11) (default)\n"
58126fa459cSmrg"Simple options could be coalesced, i.e. '-9kf' is equivalent to '-9 -k -f'.\n"
58226fa459cSmrg"With no FILE, or when FILE is -, read standard input.\n"
58326fa459cSmrg"All arguments after '--' are treated as files.\n");
58426fa459cSmrg}
58526fa459cSmrg
58626fa459cSmrgstatic const char* PrintablePath(const char* path) {
58726fa459cSmrg  return path ? path : "con";
58826fa459cSmrg}
58926fa459cSmrg
59026fa459cSmrgstatic BROTLI_BOOL OpenInputFile(const char* input_path, FILE** f) {
59126fa459cSmrg  *f = NULL;
59226fa459cSmrg  if (!input_path) {
59326fa459cSmrg    *f = fdopen(MAKE_BINARY(STDIN_FILENO), "rb");
59426fa459cSmrg    return BROTLI_TRUE;
59526fa459cSmrg  }
59626fa459cSmrg  *f = fopen(input_path, "rb");
59726fa459cSmrg  if (!*f) {
59826fa459cSmrg    fprintf(stderr, "failed to open input file [%s]: %s\n",
59926fa459cSmrg            PrintablePath(input_path), strerror(errno));
60026fa459cSmrg    return BROTLI_FALSE;
60126fa459cSmrg  }
60226fa459cSmrg  return BROTLI_TRUE;
60326fa459cSmrg}
60426fa459cSmrg
60526fa459cSmrgstatic BROTLI_BOOL OpenOutputFile(const char* output_path, FILE** f,
60626fa459cSmrg                                  BROTLI_BOOL force) {
60726fa459cSmrg  int fd;
60826fa459cSmrg  *f = NULL;
60926fa459cSmrg  if (!output_path) {
61026fa459cSmrg    *f = fdopen(MAKE_BINARY(STDOUT_FILENO), "wb");
61126fa459cSmrg    return BROTLI_TRUE;
61226fa459cSmrg  }
61326fa459cSmrg  fd = open(output_path, O_CREAT | (force ? 0 : O_EXCL) | O_WRONLY | O_TRUNC,
61426fa459cSmrg            S_IRUSR | S_IWUSR);
61526fa459cSmrg  if (fd < 0) {
61626fa459cSmrg    fprintf(stderr, "failed to open output file [%s]: %s\n",
61726fa459cSmrg            PrintablePath(output_path), strerror(errno));
61826fa459cSmrg    return BROTLI_FALSE;
61926fa459cSmrg  }
62026fa459cSmrg  *f = fdopen(fd, "wb");
62126fa459cSmrg  if (!*f) {
62226fa459cSmrg    fprintf(stderr, "failed to open output file [%s]: %s\n",
62326fa459cSmrg            PrintablePath(output_path), strerror(errno));
62426fa459cSmrg    return BROTLI_FALSE;
62526fa459cSmrg  }
62626fa459cSmrg  return BROTLI_TRUE;
62726fa459cSmrg}
62826fa459cSmrg
62926fa459cSmrgstatic int64_t FileSize(const char* path) {
63026fa459cSmrg  FILE* f = fopen(path, "rb");
63126fa459cSmrg  int64_t retval;
63226fa459cSmrg  if (f == NULL) {
63326fa459cSmrg    return -1;
63426fa459cSmrg  }
63526fa459cSmrg  if (fseek(f, 0L, SEEK_END) != 0) {
63626fa459cSmrg    fclose(f);
63726fa459cSmrg    return -1;
63826fa459cSmrg  }
63926fa459cSmrg  retval = ftell(f);
64026fa459cSmrg  if (fclose(f) != 0) {
64126fa459cSmrg    return -1;
64226fa459cSmrg  }
64326fa459cSmrg  return retval;
64426fa459cSmrg}
64526fa459cSmrg
64626fa459cSmrg/* Copy file times and permissions.
64726fa459cSmrg   TODO: this is a "best effort" implementation; honest cross-platform
64826fa459cSmrg   fully featured implementation is way too hacky; add more hacks by request. */
64926fa459cSmrgstatic void CopyStat(const char* input_path, const char* output_path) {
65026fa459cSmrg  struct stat statbuf;
65126fa459cSmrg  struct utimbuf times;
65226fa459cSmrg  int res;
65326fa459cSmrg  if (input_path == 0 || output_path == 0) {
65426fa459cSmrg    return;
65526fa459cSmrg  }
65626fa459cSmrg  if (stat(input_path, &statbuf) != 0) {
65726fa459cSmrg    return;
65826fa459cSmrg  }
65926fa459cSmrg  times.actime = statbuf.st_atime;
66026fa459cSmrg  times.modtime = statbuf.st_mtime;
66126fa459cSmrg  utime(output_path, &times);
66226fa459cSmrg  res = chmod(output_path, statbuf.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));
66326fa459cSmrg  if (res != 0) {
66426fa459cSmrg    fprintf(stderr, "setting access bits failed for [%s]: %s\n",
66526fa459cSmrg            PrintablePath(output_path), strerror(errno));
66626fa459cSmrg  }
66726fa459cSmrg  res = chown(output_path, (uid_t)-1, statbuf.st_gid);
66826fa459cSmrg  if (res != 0) {
66926fa459cSmrg    fprintf(stderr, "setting group failed for [%s]: %s\n",
67026fa459cSmrg            PrintablePath(output_path), strerror(errno));
67126fa459cSmrg  }
67226fa459cSmrg  res = chown(output_path, statbuf.st_uid, (gid_t)-1);
67326fa459cSmrg  if (res != 0) {
67426fa459cSmrg    fprintf(stderr, "setting user failed for [%s]: %s\n",
67526fa459cSmrg            PrintablePath(output_path), strerror(errno));
67626fa459cSmrg  }
67726fa459cSmrg}
67826fa459cSmrg
67926fa459cSmrgstatic BROTLI_BOOL NextFile(Context* context) {
68026fa459cSmrg  const char* arg;
68126fa459cSmrg  size_t arg_len;
68226fa459cSmrg
68326fa459cSmrg  /* Iterator points to last used arg; increment to search for the next one. */
68426fa459cSmrg  context->iterator++;
68526fa459cSmrg
68626fa459cSmrg  context->input_file_length = -1;
68726fa459cSmrg
68826fa459cSmrg  /* No input path; read from console. */
68926fa459cSmrg  if (context->input_count == 0) {
69026fa459cSmrg    if (context->iterator > 1) return BROTLI_FALSE;
69126fa459cSmrg    context->current_input_path = NULL;
69226fa459cSmrg    /* Either write to the specified path, or to console. */
69326fa459cSmrg    context->current_output_path = context->output_path;
69426fa459cSmrg    return BROTLI_TRUE;
69526fa459cSmrg  }
69626fa459cSmrg
69726fa459cSmrg  /* Skip option arguments. */
69826fa459cSmrg  while (context->iterator == context->not_input_indices[context->ignore]) {
69926fa459cSmrg    context->iterator++;
70026fa459cSmrg    context->ignore++;
70126fa459cSmrg  }
70226fa459cSmrg
70326fa459cSmrg  /* All args are scanned already. */
70426fa459cSmrg  if (context->iterator >= context->argc) return BROTLI_FALSE;
70526fa459cSmrg
70626fa459cSmrg  /* Iterator now points to the input file name. */
70726fa459cSmrg  arg = context->argv[context->iterator];
70826fa459cSmrg  arg_len = strlen(arg);
70926fa459cSmrg  /* Read from console. */
71026fa459cSmrg  if (arg_len == 1 && arg[0] == '-') {
71126fa459cSmrg    context->current_input_path = NULL;
71226fa459cSmrg    context->current_output_path = context->output_path;
71326fa459cSmrg    return BROTLI_TRUE;
71426fa459cSmrg  }
71526fa459cSmrg
71626fa459cSmrg  context->current_input_path = arg;
71726fa459cSmrg  context->input_file_length = FileSize(arg);
71826fa459cSmrg  context->current_output_path = context->output_path;
71926fa459cSmrg
72026fa459cSmrg  if (context->output_path) return BROTLI_TRUE;
72126fa459cSmrg  if (context->write_to_stdout) return BROTLI_TRUE;
72226fa459cSmrg
72326fa459cSmrg  strcpy(context->modified_path, arg);
72426fa459cSmrg  context->current_output_path = context->modified_path;
72526fa459cSmrg  /* If output is not specified, input path suffix should match. */
72626fa459cSmrg  if (context->decompress) {
72726fa459cSmrg    size_t suffix_len = strlen(context->suffix);
72826fa459cSmrg    char* name = (char*)FileName(context->modified_path);
72926fa459cSmrg    char* name_suffix;
73026fa459cSmrg    size_t name_len = strlen(name);
73126fa459cSmrg    if (name_len < suffix_len + 1) {
73226fa459cSmrg      fprintf(stderr, "empty output file name for [%s] input file\n",
73326fa459cSmrg              PrintablePath(arg));
73426fa459cSmrg      context->iterator_error = BROTLI_TRUE;
73526fa459cSmrg      return BROTLI_FALSE;
73626fa459cSmrg    }
73726fa459cSmrg    name_suffix = name + name_len - suffix_len;
73826fa459cSmrg    if (strcmp(context->suffix, name_suffix) != 0) {
73926fa459cSmrg      fprintf(stderr, "input file [%s] suffix mismatch\n",
74026fa459cSmrg              PrintablePath(arg));
74126fa459cSmrg      context->iterator_error = BROTLI_TRUE;
74226fa459cSmrg      return BROTLI_FALSE;
74326fa459cSmrg    }
74426fa459cSmrg    name_suffix[0] = 0;
74526fa459cSmrg    return BROTLI_TRUE;
74626fa459cSmrg  } else {
74726fa459cSmrg    strcpy(context->modified_path + arg_len, context->suffix);
74826fa459cSmrg    return BROTLI_TRUE;
74926fa459cSmrg  }
75026fa459cSmrg}
75126fa459cSmrg
75226fa459cSmrgstatic BROTLI_BOOL OpenFiles(Context* context) {
75326fa459cSmrg  BROTLI_BOOL is_ok = OpenInputFile(context->current_input_path, &context->fin);
75426fa459cSmrg  if (!context->test_integrity && is_ok) {
75526fa459cSmrg    is_ok = OpenOutputFile(
75626fa459cSmrg        context->current_output_path, &context->fout, context->force_overwrite);
75726fa459cSmrg  }
75826fa459cSmrg  return is_ok;
75926fa459cSmrg}
76026fa459cSmrg
76126fa459cSmrgstatic BROTLI_BOOL CloseFiles(Context* context, BROTLI_BOOL success) {
76226fa459cSmrg  BROTLI_BOOL is_ok = BROTLI_TRUE;
76326fa459cSmrg  if (!context->test_integrity && context->fout) {
76426fa459cSmrg    if (!success && context->current_output_path) {
76526fa459cSmrg      unlink(context->current_output_path);
76626fa459cSmrg    }
76726fa459cSmrg    if (fclose(context->fout) != 0) {
76826fa459cSmrg      if (success) {
76926fa459cSmrg        fprintf(stderr, "fclose failed [%s]: %s\n",
77026fa459cSmrg                PrintablePath(context->current_output_path), strerror(errno));
77126fa459cSmrg      }
77226fa459cSmrg      is_ok = BROTLI_FALSE;
77326fa459cSmrg    }
77426fa459cSmrg
77526fa459cSmrg    /* TOCTOU violation, but otherwise it is impossible to set file times. */
77626fa459cSmrg    if (success && is_ok && context->copy_stat) {
77726fa459cSmrg      CopyStat(context->current_input_path, context->current_output_path);
77826fa459cSmrg    }
77926fa459cSmrg  }
78026fa459cSmrg
78126fa459cSmrg  if (context->fin) {
78226fa459cSmrg    if (fclose(context->fin) != 0) {
78326fa459cSmrg      if (is_ok) {
78426fa459cSmrg        fprintf(stderr, "fclose failed [%s]: %s\n",
78526fa459cSmrg                PrintablePath(context->current_input_path), strerror(errno));
78626fa459cSmrg      }
78726fa459cSmrg      is_ok = BROTLI_FALSE;
78826fa459cSmrg    }
78926fa459cSmrg  }
79026fa459cSmrg  if (success && context->junk_source && context->current_input_path) {
79126fa459cSmrg    unlink(context->current_input_path);
79226fa459cSmrg  }
79326fa459cSmrg
79426fa459cSmrg  context->fin = NULL;
79526fa459cSmrg  context->fout = NULL;
79626fa459cSmrg
79726fa459cSmrg  return is_ok;
79826fa459cSmrg}
79926fa459cSmrg
80026fa459cSmrgstatic const size_t kFileBufferSize = 1 << 19;
80126fa459cSmrg
80226fa459cSmrgstatic void InitializeBuffers(Context* context) {
80326fa459cSmrg  context->available_in = 0;
80426fa459cSmrg  context->next_in = NULL;
80526fa459cSmrg  context->available_out = kFileBufferSize;
80626fa459cSmrg  context->next_out = context->output;
80726fa459cSmrg  context->total_in = 0;
80826fa459cSmrg  context->total_out = 0;
80926fa459cSmrg}
81026fa459cSmrg
81126fa459cSmrg/* This method might give the false-negative result.
81226fa459cSmrg   However, after an empty / incomplete read it should tell the truth. */
81326fa459cSmrgstatic BROTLI_BOOL HasMoreInput(Context* context) {
81426fa459cSmrg  return feof(context->fin) ? BROTLI_FALSE : BROTLI_TRUE;
81526fa459cSmrg}
81626fa459cSmrg
81726fa459cSmrgstatic BROTLI_BOOL ProvideInput(Context* context) {
81826fa459cSmrg  context->available_in =
81926fa459cSmrg      fread(context->input, 1, kFileBufferSize, context->fin);
82026fa459cSmrg  context->total_in += context->available_in;
82126fa459cSmrg  context->next_in = context->input;
82226fa459cSmrg  if (ferror(context->fin)) {
82326fa459cSmrg    fprintf(stderr, "failed to read input [%s]: %s\n",
82426fa459cSmrg            PrintablePath(context->current_input_path), strerror(errno));
82526fa459cSmrg    return BROTLI_FALSE;
82626fa459cSmrg  }
82726fa459cSmrg  return BROTLI_TRUE;
82826fa459cSmrg}
82926fa459cSmrg
83026fa459cSmrg/* Internal: should be used only in Provide-/Flush-Output. */
83126fa459cSmrgstatic BROTLI_BOOL WriteOutput(Context* context) {
83226fa459cSmrg  size_t out_size = (size_t)(context->next_out - context->output);
83326fa459cSmrg  context->total_out += out_size;
83426fa459cSmrg  if (out_size == 0) return BROTLI_TRUE;
83526fa459cSmrg  if (context->test_integrity) return BROTLI_TRUE;
83626fa459cSmrg
83726fa459cSmrg  fwrite(context->output, 1, out_size, context->fout);
83826fa459cSmrg  if (ferror(context->fout)) {
83926fa459cSmrg    fprintf(stderr, "failed to write output [%s]: %s\n",
84026fa459cSmrg            PrintablePath(context->current_output_path), strerror(errno));
84126fa459cSmrg    return BROTLI_FALSE;
84226fa459cSmrg  }
84326fa459cSmrg  return BROTLI_TRUE;
84426fa459cSmrg}
84526fa459cSmrg
84626fa459cSmrgstatic BROTLI_BOOL ProvideOutput(Context* context) {
84726fa459cSmrg  if (!WriteOutput(context)) return BROTLI_FALSE;
84826fa459cSmrg  context->available_out = kFileBufferSize;
84926fa459cSmrg  context->next_out = context->output;
85026fa459cSmrg  return BROTLI_TRUE;
85126fa459cSmrg}
85226fa459cSmrg
85326fa459cSmrgstatic BROTLI_BOOL FlushOutput(Context* context) {
85426fa459cSmrg  if (!WriteOutput(context)) return BROTLI_FALSE;
85526fa459cSmrg  context->available_out = 0;
85626fa459cSmrg  return BROTLI_TRUE;
85726fa459cSmrg}
85826fa459cSmrg
85926fa459cSmrgstatic void PrintBytes(size_t value) {
86026fa459cSmrg  if (value < 1024) {
86126fa459cSmrg    fprintf(stderr, "%d B", (int)value);
86226fa459cSmrg  } else if (value < 1048576) {
86326fa459cSmrg    fprintf(stderr, "%0.3f KiB", (double)value / 1024.0);
86426fa459cSmrg  } else if (value < 1073741824) {
86526fa459cSmrg    fprintf(stderr, "%0.3f MiB", (double)value / 1048576.0);
86626fa459cSmrg  } else {
86726fa459cSmrg    fprintf(stderr, "%0.3f GiB", (double)value / 1073741824.0);
86826fa459cSmrg  }
86926fa459cSmrg}
87026fa459cSmrg
87126fa459cSmrgstatic void PrintFileProcessingProgress(Context* context) {
87226fa459cSmrg  fprintf(stderr, "[%s]: ", PrintablePath(context->current_input_path));
87326fa459cSmrg  PrintBytes(context->total_in);
87426fa459cSmrg  fprintf(stderr, " -> ");
87526fa459cSmrg  PrintBytes(context->total_out);
87626fa459cSmrg}
87726fa459cSmrg
87826fa459cSmrgstatic BROTLI_BOOL DecompressFile(Context* context, BrotliDecoderState* s) {
87926fa459cSmrg  BrotliDecoderResult result = BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT;
88026fa459cSmrg  InitializeBuffers(context);
88126fa459cSmrg  for (;;) {
88226fa459cSmrg    if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) {
88326fa459cSmrg      if (!HasMoreInput(context)) {
88426fa459cSmrg        fprintf(stderr, "corrupt input [%s]\n",
88526fa459cSmrg                PrintablePath(context->current_input_path));
88626fa459cSmrg        return BROTLI_FALSE;
88726fa459cSmrg      }
88826fa459cSmrg      if (!ProvideInput(context)) return BROTLI_FALSE;
88926fa459cSmrg    } else if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {
89026fa459cSmrg      if (!ProvideOutput(context)) return BROTLI_FALSE;
89126fa459cSmrg    } else if (result == BROTLI_DECODER_RESULT_SUCCESS) {
89226fa459cSmrg      if (!FlushOutput(context)) return BROTLI_FALSE;
89326fa459cSmrg      int has_more_input =
89426fa459cSmrg          (context->available_in != 0) || (fgetc(context->fin) != EOF);
89526fa459cSmrg      if (has_more_input) {
89626fa459cSmrg        fprintf(stderr, "corrupt input [%s]\n",
89726fa459cSmrg                PrintablePath(context->current_input_path));
89826fa459cSmrg        return BROTLI_FALSE;
89926fa459cSmrg      }
90026fa459cSmrg      if (context->verbosity > 0) {
90126fa459cSmrg        fprintf(stderr, "Decompressed ");
90226fa459cSmrg        PrintFileProcessingProgress(context);
90326fa459cSmrg        fprintf(stderr, "\n");
90426fa459cSmrg      }
90526fa459cSmrg      return BROTLI_TRUE;
90626fa459cSmrg    } else {
90726fa459cSmrg      fprintf(stderr, "corrupt input [%s]\n",
90826fa459cSmrg              PrintablePath(context->current_input_path));
90926fa459cSmrg      return BROTLI_FALSE;
91026fa459cSmrg    }
91126fa459cSmrg
91226fa459cSmrg    result = BrotliDecoderDecompressStream(s, &context->available_in,
91326fa459cSmrg        &context->next_in, &context->available_out, &context->next_out, 0);
91426fa459cSmrg  }
91526fa459cSmrg}
91626fa459cSmrg
91726fa459cSmrgstatic BROTLI_BOOL DecompressFiles(Context* context) {
91826fa459cSmrg  while (NextFile(context)) {
91926fa459cSmrg    BROTLI_BOOL is_ok = BROTLI_TRUE;
92026fa459cSmrg    BrotliDecoderState* s = BrotliDecoderCreateInstance(NULL, NULL, NULL);
92126fa459cSmrg    if (!s) {
92226fa459cSmrg      fprintf(stderr, "out of memory\n");
92326fa459cSmrg      return BROTLI_FALSE;
92426fa459cSmrg    }
92526fa459cSmrg    /* This allows decoding "large-window" streams. Though it creates
92626fa459cSmrg       fragmentation (new builds decode streams that old builds don't),
92726fa459cSmrg       it is better from used experience perspective. */
92826fa459cSmrg    BrotliDecoderSetParameter(s, BROTLI_DECODER_PARAM_LARGE_WINDOW, 1u);
92926fa459cSmrg    is_ok = OpenFiles(context);
93026fa459cSmrg    if (is_ok && !context->current_input_path &&
93126fa459cSmrg        !context->force_overwrite && isatty(STDIN_FILENO)) {
93226fa459cSmrg      fprintf(stderr, "Use -h help. Use -f to force input from a terminal.\n");
93326fa459cSmrg      is_ok = BROTLI_FALSE;
93426fa459cSmrg    }
93526fa459cSmrg    if (is_ok) is_ok = DecompressFile(context, s);
93626fa459cSmrg    BrotliDecoderDestroyInstance(s);
93726fa459cSmrg    if (!CloseFiles(context, is_ok)) is_ok = BROTLI_FALSE;
93826fa459cSmrg    if (!is_ok) return BROTLI_FALSE;
93926fa459cSmrg  }
94026fa459cSmrg  return BROTLI_TRUE;
94126fa459cSmrg}
94226fa459cSmrg
94326fa459cSmrgstatic BROTLI_BOOL CompressFile(Context* context, BrotliEncoderState* s) {
94426fa459cSmrg  BROTLI_BOOL is_eof = BROTLI_FALSE;
94526fa459cSmrg  InitializeBuffers(context);
94626fa459cSmrg  for (;;) {
94726fa459cSmrg    if (context->available_in == 0 && !is_eof) {
94826fa459cSmrg      if (!ProvideInput(context)) return BROTLI_FALSE;
94926fa459cSmrg      is_eof = !HasMoreInput(context);
95026fa459cSmrg    }
95126fa459cSmrg
95226fa459cSmrg    if (!BrotliEncoderCompressStream(s,
95326fa459cSmrg        is_eof ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS,
95426fa459cSmrg        &context->available_in, &context->next_in,
95526fa459cSmrg        &context->available_out, &context->next_out, NULL)) {
95626fa459cSmrg      /* Should detect OOM? */
95726fa459cSmrg      fprintf(stderr, "failed to compress data [%s]\n",
95826fa459cSmrg              PrintablePath(context->current_input_path));
95926fa459cSmrg      return BROTLI_FALSE;
96026fa459cSmrg    }
96126fa459cSmrg
96226fa459cSmrg    if (context->available_out == 0) {
96326fa459cSmrg      if (!ProvideOutput(context)) return BROTLI_FALSE;
96426fa459cSmrg    }
96526fa459cSmrg
96626fa459cSmrg    if (BrotliEncoderIsFinished(s)) {
96726fa459cSmrg      if (!FlushOutput(context)) return BROTLI_FALSE;
96826fa459cSmrg      if (context->verbosity > 0) {
96926fa459cSmrg        fprintf(stderr, "Compressed ");
97026fa459cSmrg        PrintFileProcessingProgress(context);
97126fa459cSmrg        fprintf(stderr, "\n");
97226fa459cSmrg      }
97326fa459cSmrg      return BROTLI_TRUE;
97426fa459cSmrg    }
97526fa459cSmrg  }
97626fa459cSmrg}
97726fa459cSmrg
97826fa459cSmrgstatic BROTLI_BOOL CompressFiles(Context* context) {
97926fa459cSmrg  while (NextFile(context)) {
98026fa459cSmrg    BROTLI_BOOL is_ok = BROTLI_TRUE;
98126fa459cSmrg    BrotliEncoderState* s = BrotliEncoderCreateInstance(NULL, NULL, NULL);
98226fa459cSmrg    if (!s) {
98326fa459cSmrg      fprintf(stderr, "out of memory\n");
98426fa459cSmrg      return BROTLI_FALSE;
98526fa459cSmrg    }
98626fa459cSmrg    BrotliEncoderSetParameter(s,
98726fa459cSmrg        BROTLI_PARAM_QUALITY, (uint32_t)context->quality);
98826fa459cSmrg    if (context->lgwin > 0) {
98926fa459cSmrg      /* Specified by user. */
99026fa459cSmrg      /* Do not enable "large-window" extension, if not required. */
99126fa459cSmrg      if (context->lgwin > BROTLI_MAX_WINDOW_BITS) {
99226fa459cSmrg        BrotliEncoderSetParameter(s, BROTLI_PARAM_LARGE_WINDOW, 1u);
99326fa459cSmrg      }
99426fa459cSmrg      BrotliEncoderSetParameter(s,
99526fa459cSmrg          BROTLI_PARAM_LGWIN, (uint32_t)context->lgwin);
99626fa459cSmrg    } else {
99726fa459cSmrg      /* 0, or not specified by user; could be chosen by compressor. */
99826fa459cSmrg      uint32_t lgwin = DEFAULT_LGWIN;
99926fa459cSmrg      /* Use file size to limit lgwin. */
100026fa459cSmrg      if (context->input_file_length >= 0) {
100126fa459cSmrg        lgwin = BROTLI_MIN_WINDOW_BITS;
100226fa459cSmrg        while (BROTLI_MAX_BACKWARD_LIMIT(lgwin) <
100326fa459cSmrg               (uint64_t)context->input_file_length) {
100426fa459cSmrg          lgwin++;
100526fa459cSmrg          if (lgwin == BROTLI_MAX_WINDOW_BITS) break;
100626fa459cSmrg        }
100726fa459cSmrg      }
100826fa459cSmrg      BrotliEncoderSetParameter(s, BROTLI_PARAM_LGWIN, lgwin);
100926fa459cSmrg    }
101026fa459cSmrg    if (context->input_file_length > 0) {
101126fa459cSmrg      uint32_t size_hint = context->input_file_length < (1 << 30) ?
101226fa459cSmrg          (uint32_t)context->input_file_length : (1u << 30);
101326fa459cSmrg      BrotliEncoderSetParameter(s, BROTLI_PARAM_SIZE_HINT, size_hint);
101426fa459cSmrg    }
101526fa459cSmrg    is_ok = OpenFiles(context);
101626fa459cSmrg    if (is_ok && !context->current_output_path &&
101726fa459cSmrg        !context->force_overwrite && isatty(STDOUT_FILENO)) {
101826fa459cSmrg      fprintf(stderr, "Use -h help. Use -f to force output to a terminal.\n");
101926fa459cSmrg      is_ok = BROTLI_FALSE;
102026fa459cSmrg    }
102126fa459cSmrg    if (is_ok) is_ok = CompressFile(context, s);
102226fa459cSmrg    BrotliEncoderDestroyInstance(s);
102326fa459cSmrg    if (!CloseFiles(context, is_ok)) is_ok = BROTLI_FALSE;
102426fa459cSmrg    if (!is_ok) return BROTLI_FALSE;
102526fa459cSmrg  }
102626fa459cSmrg  return BROTLI_TRUE;
102726fa459cSmrg}
102826fa459cSmrg
102926fa459cSmrgint main(int argc, char** argv) {
103026fa459cSmrg  Command command;
103126fa459cSmrg  Context context;
103226fa459cSmrg  BROTLI_BOOL is_ok = BROTLI_TRUE;
103326fa459cSmrg  int i;
103426fa459cSmrg
103526fa459cSmrg  context.quality = 11;
103626fa459cSmrg  context.lgwin = -1;
103726fa459cSmrg  context.verbosity = 0;
103826fa459cSmrg  context.force_overwrite = BROTLI_FALSE;
103926fa459cSmrg  context.junk_source = BROTLI_FALSE;
104026fa459cSmrg  context.copy_stat = BROTLI_TRUE;
104126fa459cSmrg  context.test_integrity = BROTLI_FALSE;
104226fa459cSmrg  context.write_to_stdout = BROTLI_FALSE;
104326fa459cSmrg  context.decompress = BROTLI_FALSE;
104426fa459cSmrg  context.large_window = BROTLI_FALSE;
104526fa459cSmrg  context.output_path = NULL;
104626fa459cSmrg  context.suffix = DEFAULT_SUFFIX;
104726fa459cSmrg  for (i = 0; i < MAX_OPTIONS; ++i) context.not_input_indices[i] = 0;
104826fa459cSmrg  context.longest_path_len = 1;
104926fa459cSmrg  context.input_count = 0;
105026fa459cSmrg
105126fa459cSmrg  context.argc = argc;
105226fa459cSmrg  context.argv = argv;
105326fa459cSmrg  context.modified_path = NULL;
105426fa459cSmrg  context.iterator = 0;
105526fa459cSmrg  context.ignore = 0;
105626fa459cSmrg  context.iterator_error = BROTLI_FALSE;
105726fa459cSmrg  context.buffer = NULL;
105826fa459cSmrg  context.current_input_path = NULL;
105926fa459cSmrg  context.current_output_path = NULL;
106026fa459cSmrg  context.fin = NULL;
106126fa459cSmrg  context.fout = NULL;
106226fa459cSmrg
106326fa459cSmrg  command = ParseParams(&context);
106426fa459cSmrg
106526fa459cSmrg  if (command == COMMAND_COMPRESS || command == COMMAND_DECOMPRESS ||
106626fa459cSmrg      command == COMMAND_TEST_INTEGRITY) {
106726fa459cSmrg    if (is_ok) {
106826fa459cSmrg      size_t modified_path_len =
106926fa459cSmrg          context.longest_path_len + strlen(context.suffix) + 1;
107026fa459cSmrg      context.modified_path = (char*)malloc(modified_path_len);
107126fa459cSmrg      context.buffer = (uint8_t*)malloc(kFileBufferSize * 2);
107226fa459cSmrg      if (!context.modified_path || !context.buffer) {
107326fa459cSmrg        fprintf(stderr, "out of memory\n");
107426fa459cSmrg        is_ok = BROTLI_FALSE;
107526fa459cSmrg      } else {
107626fa459cSmrg        context.input = context.buffer;
107726fa459cSmrg        context.output = context.buffer + kFileBufferSize;
107826fa459cSmrg      }
107926fa459cSmrg    }
108026fa459cSmrg  }
108126fa459cSmrg
108226fa459cSmrg  if (!is_ok) command = COMMAND_NOOP;
108326fa459cSmrg
108426fa459cSmrg  switch (command) {
108526fa459cSmrg    case COMMAND_NOOP:
108626fa459cSmrg      break;
108726fa459cSmrg
108826fa459cSmrg    case COMMAND_VERSION:
108926fa459cSmrg      PrintVersion();
109026fa459cSmrg      break;
109126fa459cSmrg
109226fa459cSmrg    case COMMAND_COMPRESS:
109326fa459cSmrg      is_ok = CompressFiles(&context);
109426fa459cSmrg      break;
109526fa459cSmrg
109626fa459cSmrg    case COMMAND_DECOMPRESS:
109726fa459cSmrg    case COMMAND_TEST_INTEGRITY:
109826fa459cSmrg      is_ok = DecompressFiles(&context);
109926fa459cSmrg      break;
110026fa459cSmrg
110126fa459cSmrg    case COMMAND_HELP:
110226fa459cSmrg    case COMMAND_INVALID:
110326fa459cSmrg    default:
110426fa459cSmrg      is_ok = (command == COMMAND_HELP);
110526fa459cSmrg      PrintHelp(FileName(argv[0]), is_ok);
110626fa459cSmrg      break;
110726fa459cSmrg  }
110826fa459cSmrg
110926fa459cSmrg  if (context.iterator_error) is_ok = BROTLI_FALSE;
111026fa459cSmrg
111126fa459cSmrg  free(context.modified_path);
111226fa459cSmrg  free(context.buffer);
111326fa459cSmrg
111426fa459cSmrg  if (!is_ok) exit(1);
111526fa459cSmrg  return 0;
111626fa459cSmrg}
1117