+++ /dev/null
-/* Copyright 2014 Google Inc. All Rights Reserved.\r
-\r
- Distributed under MIT license.\r
- See file LICENSE for detail or copy at https://opensource.org/licenses/MIT\r
-*/\r
-\r
-/* Command line interface for Brotli library. */\r
-\r
-#include <errno.h>\r
-#include <fcntl.h>\r
-#include <stdio.h>\r
-#include <stdlib.h>\r
-#include <string.h>\r
-#include <sys/stat.h>\r
-#include <sys/types.h>\r
-#include <time.h>\r
-\r
-#include "../common/constants.h"\r
-#include "../common/version.h"\r
-#include <brotli/decode.h>\r
-#include <brotli/encode.h>\r
-\r
-#if !defined(_WIN32)\r
-#include <unistd.h>\r
-#include <utime.h>\r
-#define MAKE_BINARY(FILENO) (FILENO)\r
-#else\r
-#include <io.h>\r
-#include <share.h>\r
-#include <sys/utime.h>\r
-\r
-#define MAKE_BINARY(FILENO) (_setmode((FILENO), _O_BINARY), (FILENO))\r
-\r
-#if !defined(__MINGW32__)\r
-#define STDIN_FILENO _fileno(stdin)\r
-#define STDOUT_FILENO _fileno(stdout)\r
-#define S_IRUSR S_IREAD\r
-#define S_IWUSR S_IWRITE\r
-#endif\r
-\r
-#define fdopen _fdopen\r
-#define isatty _isatty\r
-#define unlink _unlink\r
-#define utimbuf _utimbuf\r
-#define utime _utime\r
-\r
-#define fopen ms_fopen\r
-#define open ms_open\r
-\r
-#define chmod(F, P) (0)\r
-#define chown(F, O, G) (0)\r
-\r
-#if defined(_MSC_VER) && (_MSC_VER >= 1400)\r
-#define fseek _fseeki64\r
-#define ftell _ftelli64\r
-#endif\r
-\r
-static FILE* ms_fopen(const char* filename, const char* mode) {\r
- FILE* result = 0;\r
- fopen_s(&result, filename, mode);\r
- return result;\r
-}\r
-\r
-static int ms_open(const char* filename, int oflag, int pmode) {\r
- int result = -1;\r
- _sopen_s(&result, filename, oflag | O_BINARY, _SH_DENYNO, pmode);\r
- return result;\r
-}\r
-#endif /* WIN32 */\r
-\r
-typedef enum {\r
- COMMAND_COMPRESS,\r
- COMMAND_DECOMPRESS,\r
- COMMAND_HELP,\r
- COMMAND_INVALID,\r
- COMMAND_TEST_INTEGRITY,\r
- COMMAND_NOOP,\r
- COMMAND_VERSION\r
-} Command;\r
-\r
-#define DEFAULT_LGWIN 24\r
-#define DEFAULT_SUFFIX ".br"\r
-#define MAX_OPTIONS 20\r
-#define DECODE_HEADER_SIZE 0x10\r
-#define GAP_MEM_BLOCK 0x1000\r
-\r
-typedef struct {\r
- /* Parameters */\r
- int gmem;\r
- int quality;\r
- int lgwin;\r
- BROTLI_BOOL force_overwrite;\r
- BROTLI_BOOL junk_source;\r
- BROTLI_BOOL copy_stat;\r
- BROTLI_BOOL verbose;\r
- BROTLI_BOOL write_to_stdout;\r
- BROTLI_BOOL test_integrity;\r
- BROTLI_BOOL decompress;\r
- BROTLI_BOOL large_window;\r
- const char* output_path;\r
- const char* suffix;\r
- int not_input_indices[MAX_OPTIONS];\r
- size_t longest_path_len;\r
- size_t input_count;\r
-\r
- /* Inner state */\r
- int argc;\r
- char** argv;\r
- char* modified_path; /* Storage for path with appended / cut suffix */\r
- int iterator;\r
- int ignore;\r
- BROTLI_BOOL iterator_error;\r
- uint8_t* buffer;\r
- uint8_t* input;\r
- uint8_t* output;\r
- const char* current_input_path;\r
- const char* current_output_path;\r
- int64_t input_file_length; /* -1, if impossible to calculate */\r
- FILE* fin;\r
- FILE* fout;\r
-\r
- /* I/O buffers */\r
- size_t available_in;\r
- const uint8_t* next_in;\r
- size_t available_out;\r
- uint8_t* next_out;\r
-} Context;\r
-\r
-/* Parse up to 5 decimal digits. */\r
-static BROTLI_BOOL ParseInt(const char* s, int low, int high, int* result) {\r
- int value = 0;\r
- int i;\r
- for (i = 0; i < 5; ++i) {\r
- char c = s[i];\r
- if (c == 0) break;\r
- if (s[i] < '0' || s[i] > '9') return BROTLI_FALSE;\r
- value = (10 * value) + (c - '0');\r
- }\r
- if (i == 0) return BROTLI_FALSE;\r
- if (i > 1 && s[0] == '0') return BROTLI_FALSE;\r
- if (s[i] != 0) return BROTLI_FALSE;\r
- if (value < low || value > high) return BROTLI_FALSE;\r
- *result = value;\r
- return BROTLI_TRUE;\r
-}\r
-\r
-/* Returns "base file name" or its tail, if it contains '/' or '\'. */\r
-static const char* FileName(const char* path) {\r
- const char* separator_position = strrchr(path, '/');\r
- if (separator_position) path = separator_position + 1;\r
- separator_position = strrchr(path, '\\');\r
- if (separator_position) path = separator_position + 1;\r
- return path;\r
-}\r
-\r
-/* Detect if the program name is a special alias that infers a command type. */\r
-static Command ParseAlias(const char* name) {\r
- /* TODO: cast name to lower case? */\r
- const char* unbrotli = "unbrotli";\r
- size_t unbrotli_len = strlen(unbrotli);\r
- name = FileName(name);\r
- /* Partial comparison. On Windows there could be ".exe" suffix. */\r
- if (strncmp(name, unbrotli, unbrotli_len) == 0) {\r
- char terminator = name[unbrotli_len];\r
- if (terminator == 0 || terminator == '.') return COMMAND_DECOMPRESS;\r
- }\r
- return COMMAND_COMPRESS;\r
-}\r
-\r
-static Command ParseParams(Context* params) {\r
- int argc = params->argc;\r
- char** argv = params->argv;\r
- int i;\r
- int next_option_index = 0;\r
- size_t input_count = 0;\r
- size_t longest_path_len = 1;\r
- BROTLI_BOOL command_set = BROTLI_FALSE;\r
- BROTLI_BOOL gmem_set = BROTLI_FALSE;\r
- BROTLI_BOOL quality_set = BROTLI_FALSE;\r
- BROTLI_BOOL output_set = BROTLI_FALSE;\r
- BROTLI_BOOL keep_set = BROTLI_FALSE;\r
- BROTLI_BOOL lgwin_set = BROTLI_FALSE;\r
- BROTLI_BOOL suffix_set = BROTLI_FALSE;\r
- BROTLI_BOOL after_dash_dash = BROTLI_FALSE;\r
- Command command = ParseAlias(argv[0]);\r
-\r
- for (i = 1; i < argc; ++i) {\r
- const char* arg = argv[i];\r
- /* C99 5.1.2.2.1: "members argv[0] through argv[argc-1] inclusive shall\r
- contain pointers to strings"; NULL and 0-length are not forbidden. */\r
- size_t arg_len = arg ? strlen(arg) : 0;\r
-\r
- if (arg_len == 0) {\r
- params->not_input_indices[next_option_index++] = i;\r
- continue;\r
- }\r
-\r
- /* Too many options. The expected longest option list is:\r
- "-q 0 -w 10 -o f -D d -S b -d -f -k -n -v --", i.e. 16 items in total.\r
- This check is an additional guard that is never triggered, but provides\r
- a guard for future changes. */\r
- if (next_option_index > (MAX_OPTIONS - 2)) {\r
- fprintf(stderr, "too many options passed\n");\r
- return COMMAND_INVALID;\r
- }\r
-\r
- /* Input file entry. */\r
- if (after_dash_dash || arg[0] != '-' || arg_len == 1) {\r
- input_count++;\r
- if (longest_path_len < arg_len) longest_path_len = arg_len;\r
- continue;\r
- }\r
-\r
- /* Not a file entry. */\r
- params->not_input_indices[next_option_index++] = i;\r
-\r
- /* '--' entry stop parsing arguments. */\r
- if (arg_len == 2 && arg[1] == '-') {\r
- after_dash_dash = BROTLI_TRUE;\r
- continue;\r
- }\r
-\r
- /* Simple / coalesced options. */\r
- if (arg[1] != '-') {\r
- size_t j;\r
- for (j = 1; j < arg_len; ++j) {\r
- char c = arg[j];\r
- if (c >= '0' && c <= '9') {\r
- if (quality_set) {\r
- fprintf(stderr, "quality already set\n");\r
- return COMMAND_INVALID;\r
- }\r
- quality_set = BROTLI_TRUE;\r
- params->quality = c - '0';\r
- continue;\r
- } else if (c == 'c') {\r
- if (output_set) {\r
- fprintf(stderr, "write to standard output already set\n");\r
- return COMMAND_INVALID;\r
- }\r
- output_set = BROTLI_TRUE;\r
- params->write_to_stdout = BROTLI_TRUE;\r
- continue;\r
- } else if (c == 'd') {\r
- if (command_set) {\r
- fprintf(stderr, "command already set when parsing -d\n");\r
- return COMMAND_INVALID;\r
- }\r
- command_set = BROTLI_TRUE;\r
- command = COMMAND_DECOMPRESS;\r
- continue;\r
- } else if (c == 'f') {\r
- if (params->force_overwrite) {\r
- fprintf(stderr, "force output overwrite already set\n");\r
- return COMMAND_INVALID;\r
- }\r
- params->force_overwrite = BROTLI_TRUE;\r
- continue;\r
- } else if (c == 'h') {\r
- /* Don't parse further. */\r
- return COMMAND_HELP;\r
- } else if (c == 'j' || c == 'k') {\r
- if (keep_set) {\r
- fprintf(stderr, "argument --rm / -j or --keep / -n already set\n");\r
- return COMMAND_INVALID;\r
- }\r
- keep_set = BROTLI_TRUE;\r
- params->junk_source = TO_BROTLI_BOOL(c == 'j');\r
- continue;\r
- } else if (c == 'n') {\r
- if (!params->copy_stat) {\r
- fprintf(stderr, "argument --no-copy-stat / -n already set\n");\r
- return COMMAND_INVALID;\r
- }\r
- params->copy_stat = BROTLI_FALSE;\r
- continue;\r
- } else if (c == 't') {\r
- if (command_set) {\r
- fprintf(stderr, "command already set when parsing -t\n");\r
- return COMMAND_INVALID;\r
- }\r
- command_set = BROTLI_TRUE;\r
- command = COMMAND_TEST_INTEGRITY;\r
- continue;\r
- } else if (c == 'v') {\r
- if (params->verbose) {\r
- fprintf(stderr, "argument --verbose / -v already set\n");\r
- return COMMAND_INVALID;\r
- }\r
- params->verbose = BROTLI_TRUE;\r
- continue;\r
- } else if (c == 'V') {\r
- /* Don't parse further. */\r
- return COMMAND_VERSION;\r
- } else if (c == 'Z') {\r
- if (quality_set) {\r
- fprintf(stderr, "quality already set\n");\r
- return COMMAND_INVALID;\r
- }\r
- quality_set = BROTLI_TRUE;\r
- params->quality = 11;\r
- continue;\r
- }\r
- /* o/q/w/D/S with parameter is expected */\r
- if (c != 'o' && c != 'q' && c != 'w' && c != 'D' && c != 'S') {\r
- fprintf(stderr, "invalid argument -%c\n", c);\r
- return COMMAND_INVALID;\r
- }\r
- if (j + 1 != arg_len) {\r
- fprintf(stderr, "expected parameter for argument -%c\n", c);\r
- return COMMAND_INVALID;\r
- }\r
- i++;\r
- if (i == argc || !argv[i] || argv[i][0] == 0) {\r
- fprintf(stderr, "expected parameter for argument -%c\n", c);\r
- return COMMAND_INVALID;\r
- }\r
- params->not_input_indices[next_option_index++] = i;\r
- if (c == 'o') {\r
- if (output_set) {\r
- fprintf(stderr, "write to standard output already set (-o)\n");\r
- return COMMAND_INVALID;\r
- }\r
- params->output_path = argv[i];\r
- } else if (c == 'g') {\r
- gmem_set = ParseInt(argv[i], 1, 0x10, ¶ms->gmem);\r
- if (!gmem_set) {\r
- fprintf(stderr, "error parsing gmem value [%s]\n", argv[i]);\r
- return COMMAND_INVALID;\r
- }\r
- } else if (c == 'q') {\r
- if (quality_set) {\r
- fprintf(stderr, "quality already set\n");\r
- return COMMAND_INVALID;\r
- }\r
- quality_set = ParseInt(argv[i], BROTLI_MIN_QUALITY,\r
- BROTLI_MAX_QUALITY, ¶ms->quality);\r
- if (!quality_set) {\r
- fprintf(stderr, "error parsing quality value [%s]\n", argv[i]);\r
- return COMMAND_INVALID;\r
- }\r
- } else if (c == 'w') {\r
- if (lgwin_set) {\r
- fprintf(stderr, "lgwin parameter already set\n");\r
- return COMMAND_INVALID;\r
- }\r
- lgwin_set = ParseInt(argv[i], 0,\r
- BROTLI_MAX_WINDOW_BITS, ¶ms->lgwin);\r
- if (!lgwin_set) {\r
- fprintf(stderr, "error parsing lgwin value [%s]\n", argv[i]);\r
- return COMMAND_INVALID;\r
- }\r
- if (params->lgwin != 0 && params->lgwin < BROTLI_MIN_WINDOW_BITS) {\r
- fprintf(stderr,\r
- "lgwin parameter (%d) smaller than the minimum (%d)\n",\r
- params->lgwin, BROTLI_MIN_WINDOW_BITS);\r
- return COMMAND_INVALID;\r
- }\r
- } else if (c == 'S') {\r
- if (suffix_set) {\r
- fprintf(stderr, "suffix already set\n");\r
- return COMMAND_INVALID;\r
- }\r
- suffix_set = BROTLI_TRUE;\r
- params->suffix = argv[i];\r
- }\r
- }\r
- } else { /* Double-dash. */\r
- arg = &arg[2];\r
- if (strcmp("best", arg) == 0) {\r
- if (quality_set) {\r
- fprintf(stderr, "quality already set\n");\r
- return COMMAND_INVALID;\r
- }\r
- quality_set = BROTLI_TRUE;\r
- params->quality = 11;\r
- } else if (strcmp("decompress", arg) == 0) {\r
- if (command_set) {\r
- fprintf(stderr, "command already set when parsing --decompress\n");\r
- return COMMAND_INVALID;\r
- }\r
- command_set = BROTLI_TRUE;\r
- command = COMMAND_DECOMPRESS;\r
- } else if (strcmp("force", arg) == 0) {\r
- if (params->force_overwrite) {\r
- fprintf(stderr, "force output overwrite already set\n");\r
- return COMMAND_INVALID;\r
- }\r
- params->force_overwrite = BROTLI_TRUE;\r
- } else if (strcmp("help", arg) == 0) {\r
- /* Don't parse further. */\r
- return COMMAND_HELP;\r
- } else if (strcmp("keep", arg) == 0) {\r
- if (keep_set) {\r
- fprintf(stderr, "argument --rm / -j or --keep / -n already set\n");\r
- return COMMAND_INVALID;\r
- }\r
- keep_set = BROTLI_TRUE;\r
- params->junk_source = BROTLI_FALSE;\r
- } else if (strcmp("no-copy-stat", arg) == 0) {\r
- if (!params->copy_stat) {\r
- fprintf(stderr, "argument --no-copy-stat / -n already set\n");\r
- return COMMAND_INVALID;\r
- }\r
- params->copy_stat = BROTLI_FALSE;\r
- } else if (strcmp("rm", arg) == 0) {\r
- if (keep_set) {\r
- fprintf(stderr, "argument --rm / -j or --keep / -n already set\n");\r
- return COMMAND_INVALID;\r
- }\r
- keep_set = BROTLI_TRUE;\r
- params->junk_source = BROTLI_TRUE;\r
- } else if (strcmp("stdout", arg) == 0) {\r
- if (output_set) {\r
- fprintf(stderr, "write to standard output already set\n");\r
- return COMMAND_INVALID;\r
- }\r
- output_set = BROTLI_TRUE;\r
- params->write_to_stdout = BROTLI_TRUE;\r
- } else if (strcmp("test", arg) == 0) {\r
- if (command_set) {\r
- fprintf(stderr, "command already set when parsing --test\n");\r
- return COMMAND_INVALID;\r
- }\r
- command_set = BROTLI_TRUE;\r
- command = COMMAND_TEST_INTEGRITY;\r
- } else if (strcmp("verbose", arg) == 0) {\r
- if (params->verbose) {\r
- fprintf(stderr, "argument --verbose / -v already set\n");\r
- return COMMAND_INVALID;\r
- }\r
- params->verbose = BROTLI_TRUE;\r
- } else if (strcmp("version", arg) == 0) {\r
- /* Don't parse further. */\r
- return COMMAND_VERSION;\r
- } else {\r
- /* key=value */\r
- const char* value = strrchr(arg, '=');\r
- size_t key_len;\r
- if (!value || value[1] == 0) {\r
- fprintf(stderr, "must pass the parameter as --%s=value\n", arg);\r
- return COMMAND_INVALID;\r
- }\r
- key_len = (size_t)(value - arg);\r
- value++;\r
- if (strncmp("lgwin", arg, key_len) == 0) {\r
- if (lgwin_set) {\r
- fprintf(stderr, "lgwin parameter already set\n");\r
- return COMMAND_INVALID;\r
- }\r
- lgwin_set = ParseInt(value, 0,\r
- BROTLI_MAX_WINDOW_BITS, ¶ms->lgwin);\r
- if (!lgwin_set) {\r
- fprintf(stderr, "error parsing lgwin value [%s]\n", value);\r
- return COMMAND_INVALID;\r
- }\r
- if (params->lgwin != 0 && params->lgwin < BROTLI_MIN_WINDOW_BITS) {\r
- fprintf(stderr,\r
- "lgwin parameter (%d) smaller than the minimum (%d)\n",\r
- params->lgwin, BROTLI_MIN_WINDOW_BITS);\r
- return COMMAND_INVALID;\r
- }\r
- } else if (strncmp("large_window", arg, key_len) == 0) {\r
- /* This option is intentionally not mentioned in help. */\r
- if (lgwin_set) {\r
- fprintf(stderr, "lgwin parameter already set\n");\r
- return COMMAND_INVALID;\r
- }\r
- lgwin_set = ParseInt(value, 0,\r
- BROTLI_LARGE_MAX_WINDOW_BITS, ¶ms->lgwin);\r
- if (!lgwin_set) {\r
- fprintf(stderr, "error parsing lgwin value [%s]\n", value);\r
- return COMMAND_INVALID;\r
- }\r
- if (params->lgwin != 0 && params->lgwin < BROTLI_MIN_WINDOW_BITS) {\r
- fprintf(stderr,\r
- "lgwin parameter (%d) smaller than the minimum (%d)\n",\r
- params->lgwin, BROTLI_MIN_WINDOW_BITS);\r
- return COMMAND_INVALID;\r
- }\r
- } else if (strncmp("output", arg, key_len) == 0) {\r
- if (output_set) {\r
- fprintf(stderr,\r
- "write to standard output already set (--output)\n");\r
- return COMMAND_INVALID;\r
- }\r
- params->output_path = value;\r
- } else if (strncmp("gap", arg, key_len) == 0) {\r
- gmem_set = ParseInt(value, 1, 0x10, ¶ms->gmem);\r
- if (!gmem_set) {\r
- fprintf(stderr, "error parsing gmem value [%s]\n", value);\r
- return COMMAND_INVALID;\r
- }\r
- } else if (strncmp("quality", arg, key_len) == 0) {\r
- if (quality_set) {\r
- fprintf(stderr, "quality already set\n");\r
- return COMMAND_INVALID;\r
- }\r
- quality_set = ParseInt(value, BROTLI_MIN_QUALITY,\r
- BROTLI_MAX_QUALITY, ¶ms->quality);\r
- if (!quality_set) {\r
- fprintf(stderr, "error parsing quality value [%s]\n", value);\r
- return COMMAND_INVALID;\r
- }\r
- } else if (strncmp("suffix", arg, key_len) == 0) {\r
- if (suffix_set) {\r
- fprintf(stderr, "suffix already set\n");\r
- return COMMAND_INVALID;\r
- }\r
- suffix_set = BROTLI_TRUE;\r
- params->suffix = value;\r
- } else {\r
- fprintf(stderr, "invalid parameter: [%s]\n", arg);\r
- return COMMAND_INVALID;\r
- }\r
- }\r
- }\r
- }\r
-\r
- params->input_count = input_count;\r
- params->longest_path_len = longest_path_len;\r
- params->decompress = (command == COMMAND_DECOMPRESS);\r
- params->test_integrity = (command == COMMAND_TEST_INTEGRITY);\r
-\r
- if (input_count > 1 && output_set) return COMMAND_INVALID;\r
- if (params->test_integrity) {\r
- if (params->output_path) return COMMAND_INVALID;\r
- if (params->write_to_stdout) return COMMAND_INVALID;\r
- }\r
- if (strchr(params->suffix, '/') || strchr(params->suffix, '\\')) {\r
- return COMMAND_INVALID;\r
- }\r
-\r
- return command;\r
-}\r
-\r
-static void PrintVersion(void) {\r
- int major = BROTLI_VERSION >> 24;\r
- int minor = (BROTLI_VERSION >> 12) & 0xFFF;\r
- int patch = BROTLI_VERSION & 0xFFF;\r
- fprintf(stdout, "brotli %d.%d.%d\n", major, minor, patch);\r
-}\r
-\r
-static void PrintHelp(const char* name, BROTLI_BOOL error) {\r
- FILE* media = error ? stderr : stdout;\r
- /* String is cut to pieces with length less than 509, to conform C90 spec. */\r
- fprintf(media,\r
-"Usage: %s [OPTION]... [FILE]...\n",\r
- name);\r
- fprintf(media,\r
-"Options:\n"\r
-" -# compression level (0-9)\n"\r
-" -c, --stdout write on standard output\n"\r
-" -d, --decompress decompress\n"\r
-" -f, --force force output file overwrite\n"\r
-" -h, --help display this help and exit\n");\r
- fprintf(media,\r
-" -j, --rm remove source file(s)\n"\r
-" -k, --keep keep source file(s) (default)\n"\r
-" -n, --no-copy-stat do not copy source file(s) attributes\n"\r
-" -o FILE, --output=FILE output file (only if 1 input file)\n");\r
- fprintf(media,\r
-" -g NUM, --gap=NUM scratch memory gap level (1-16)\n");\r
- fprintf(media,\r
-" -q NUM, --quality=NUM compression level (%d-%d)\n",\r
- BROTLI_MIN_QUALITY, BROTLI_MAX_QUALITY);\r
- fprintf(media,\r
-" -t, --test test compressed file integrity\n"\r
-" -v, --verbose verbose mode\n");\r
- fprintf(media,\r
-" -w NUM, --lgwin=NUM set LZ77 window size (0, %d-%d)\n",\r
- BROTLI_MIN_WINDOW_BITS, BROTLI_MAX_WINDOW_BITS);\r
- fprintf(media,\r
-" window size = 2**NUM - 16\n"\r
-" 0 lets compressor choose the optimal value\n");\r
- fprintf(media,\r
-" -S SUF, --suffix=SUF output file suffix (default:'%s')\n",\r
- DEFAULT_SUFFIX);\r
- fprintf(media,\r
-" -V, --version display version and exit\n"\r
-" -Z, --best use best compression level (11) (default)\n"\r
-"Simple options could be coalesced, i.e. '-9kf' is equivalent to '-9 -k -f'.\n"\r
-"With no FILE, or when FILE is -, read standard input.\n"\r
-"All arguments after '--' are treated as files.\n");\r
-}\r
-\r
-static const char* PrintablePath(const char* path) {\r
- return path ? path : "con";\r
-}\r
-\r
-static BROTLI_BOOL OpenInputFile(const char* input_path, FILE** f) {\r
- *f = NULL;\r
- if (!input_path) {\r
- *f = fdopen(MAKE_BINARY(STDIN_FILENO), "rb");\r
- return BROTLI_TRUE;\r
- }\r
- *f = fopen(input_path, "rb");\r
- if (!*f) {\r
- fprintf(stderr, "failed to open input file [%s]: %s\n",\r
- PrintablePath(input_path), strerror(errno));\r
- return BROTLI_FALSE;\r
- }\r
- return BROTLI_TRUE;\r
-}\r
-\r
-static BROTLI_BOOL OpenOutputFile(const char* output_path, FILE** f,\r
- BROTLI_BOOL force) {\r
- int fd;\r
- *f = NULL;\r
- if (!output_path) {\r
- *f = fdopen(MAKE_BINARY(STDOUT_FILENO), "wb");\r
- return BROTLI_TRUE;\r
- }\r
- fd = open(output_path, O_CREAT | (force ? 0 : O_EXCL) | O_WRONLY | O_TRUNC,\r
- S_IRUSR | S_IWUSR);\r
- if (fd < 0) {\r
- fd = open(output_path, (force ? 0 : O_EXCL) | O_WRONLY | O_TRUNC,\r
- S_IRUSR | S_IWUSR);\r
- if (fd < 0) {\r
- fprintf(stderr, "failed to open output file [%s]: %s\n",\r
- PrintablePath(output_path), strerror(errno));\r
- return BROTLI_FALSE;\r
- }\r
- }\r
-\r
- *f = fdopen(fd, "wb");\r
- if (!*f) {\r
- fprintf(stderr, "failed to open output file [%s]: %s\n",\r
- PrintablePath(output_path), strerror(errno));\r
- return BROTLI_FALSE;\r
- }\r
- return BROTLI_TRUE;\r
-}\r
-\r
-static int64_t FileSize(const char* path) {\r
- FILE* f = fopen(path, "rb");\r
- int64_t retval;\r
- if (f == NULL) {\r
- return -1;\r
- }\r
- if (fseek(f, 0L, SEEK_END) != 0) {\r
- fclose(f);\r
- return -1;\r
- }\r
- retval = ftell(f);\r
- if (fclose(f) != 0) {\r
- return -1;\r
- }\r
- return retval;\r
-}\r
-\r
-/* Copy file times and permissions.\r
- TODO: this is a "best effort" implementation; honest cross-platform\r
- fully featured implementation is way too hacky; add more hacks by request. */\r
-static void CopyStat(const char* input_path, const char* output_path) {\r
- struct stat statbuf;\r
- struct utimbuf times;\r
- int res;\r
- if (input_path == 0 || output_path == 0) {\r
- return;\r
- }\r
- if (stat(input_path, &statbuf) != 0) {\r
- return;\r
- }\r
- times.actime = statbuf.st_atime;\r
- times.modtime = statbuf.st_mtime;\r
- utime(output_path, ×);\r
- res = chmod(output_path, statbuf.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));\r
- if (res != 0) {\r
- fprintf(stderr, "setting access bits failed for [%s]: %s\n",\r
- PrintablePath(output_path), strerror(errno));\r
- }\r
- res = chown(output_path, (uid_t)-1, statbuf.st_gid);\r
- if (res != 0) {\r
- fprintf(stderr, "setting group failed for [%s]: %s\n",\r
- PrintablePath(output_path), strerror(errno));\r
- }\r
- res = chown(output_path, statbuf.st_uid, (gid_t)-1);\r
- if (res != 0) {\r
- fprintf(stderr, "setting user failed for [%s]: %s\n",\r
- PrintablePath(output_path), strerror(errno));\r
- }\r
-}\r
-\r
-int64_t input_file_length = 0;\r
-\r
-static BROTLI_BOOL NextFile(Context* context) {\r
- const char* arg;\r
- size_t arg_len;\r
-\r
- /* Iterator points to last used arg; increment to search for the next one. */\r
- context->iterator++;\r
-\r
- context->input_file_length = -1;\r
-\r
- /* No input path; read from console. */\r
- if (context->input_count == 0) {\r
- if (context->iterator > 1) return BROTLI_FALSE;\r
- context->current_input_path = NULL;\r
- /* Either write to the specified path, or to console. */\r
- context->current_output_path = context->output_path;\r
- return BROTLI_TRUE;\r
- }\r
-\r
- /* Skip option arguments. */\r
- while (context->iterator == context->not_input_indices[context->ignore]) {\r
- context->iterator++;\r
- context->ignore++;\r
- }\r
-\r
- /* All args are scanned already. */\r
- if (context->iterator >= context->argc) return BROTLI_FALSE;\r
-\r
- /* Iterator now points to the input file name. */\r
- arg = context->argv[context->iterator];\r
- arg_len = strlen(arg);\r
- /* Read from console. */\r
- if (arg_len == 1 && arg[0] == '-') {\r
- context->current_input_path = NULL;\r
- context->current_output_path = context->output_path;\r
- return BROTLI_TRUE;\r
- }\r
-\r
- if (context->current_input_path == NULL) {\r
- context->current_input_path = arg;\r
- }\r
- context->input_file_length = FileSize(context->current_input_path);\r
- context->current_output_path = context->output_path;\r
- if (!context->decompress) {\r
- input_file_length += context->input_file_length;\r
- }\r
-\r
- if (context->output_path) return BROTLI_TRUE;\r
- if (context->write_to_stdout) return BROTLI_TRUE;\r
-\r
- strcpy(context->modified_path, arg);\r
- context->current_output_path = context->modified_path;\r
- /* If output is not specified, input path suffix should match. */\r
- if (context->decompress) {\r
- size_t suffix_len = strlen(context->suffix);\r
- char* name = (char*)FileName(context->modified_path);\r
- char* name_suffix;\r
- size_t name_len = strlen(name);\r
- if (name_len < suffix_len + 1) {\r
- fprintf(stderr, "empty output file name for [%s] input file\n",\r
- PrintablePath(arg));\r
- context->iterator_error = BROTLI_TRUE;\r
- return BROTLI_FALSE;\r
- }\r
- name_suffix = name + name_len - suffix_len;\r
- if (strcmp(context->suffix, name_suffix) != 0) {\r
- fprintf(stderr, "input file [%s] suffix mismatch\n",\r
- PrintablePath(arg));\r
- context->iterator_error = BROTLI_TRUE;\r
- return BROTLI_FALSE;\r
- }\r
- name_suffix[0] = 0;\r
- return BROTLI_TRUE;\r
- } else {\r
- strcpy(context->modified_path + arg_len, context->suffix);\r
- return BROTLI_TRUE;\r
- }\r
-}\r
-\r
-static BROTLI_BOOL OpenFiles(Context* context) {\r
- BROTLI_BOOL is_ok = OpenInputFile(context->current_input_path, &context->fin);\r
- if (context->decompress) {\r
- //\r
- // skip the decoder data header\r
- //\r
- fseek(context->fin, DECODE_HEADER_SIZE, SEEK_SET);\r
- }\r
- if (!context->test_integrity && is_ok && context->fout == NULL) {\r
- is_ok = OpenOutputFile(\r
- context->current_output_path, &context->fout, context->force_overwrite);\r
- }\r
- if (!context->decompress) {\r
- //\r
- // append the decoder data header\r
- //\r
- fseek(context->fout, DECODE_HEADER_SIZE, SEEK_SET);\r
- }\r
- return is_ok;\r
-}\r
-\r
-static BROTLI_BOOL CloseFiles(Context* context, BROTLI_BOOL success) {\r
- BROTLI_BOOL is_ok = BROTLI_TRUE;\r
- if (!context->test_integrity && context->fout) {\r
- if (!success && context->current_output_path) {\r
- unlink(context->current_output_path);\r
- }\r
- if (fclose(context->fout) != 0) {\r
- if (success) {\r
- fprintf(stderr, "fclose failed [%s]: %s\n",\r
- PrintablePath(context->current_output_path), strerror(errno));\r
- }\r
- is_ok = BROTLI_FALSE;\r
- }\r
-\r
- /* TOCTOU violation, but otherwise it is impossible to set file times. */\r
- if (success && is_ok && context->copy_stat) {\r
- CopyStat(context->current_input_path, context->current_output_path);\r
- }\r
- }\r
-\r
- if (context->fin) {\r
- if (fclose(context->fin) != 0) {\r
- if (is_ok) {\r
- fprintf(stderr, "fclose failed [%s]: %s\n",\r
- PrintablePath(context->current_input_path), strerror(errno));\r
- }\r
- is_ok = BROTLI_FALSE;\r
- }\r
- }\r
- if (success && context->junk_source && context->current_input_path) {\r
- unlink(context->current_input_path);\r
- }\r
-\r
- context->fin = NULL;\r
- context->fout = NULL;\r
-\r
- return is_ok;\r
-}\r
-\r
-static const size_t kFileBufferSize = 1 << 16;\r
-\r
-static void InitializeBuffers(Context* context) {\r
- context->available_in = 0;\r
- context->next_in = NULL;\r
- context->available_out = kFileBufferSize;\r
- context->next_out = context->output;\r
-}\r
-\r
-static BROTLI_BOOL HasMoreInput(Context* context) {\r
- return feof(context->fin) ? BROTLI_FALSE : BROTLI_TRUE;\r
-}\r
-\r
-static BROTLI_BOOL ProvideInput(Context* context) {\r
- context->available_in =\r
- fread(context->input, 1, kFileBufferSize, context->fin);\r
- context->next_in = context->input;\r
- if (ferror(context->fin)) {\r
- fprintf(stderr, "failed to read input [%s]: %s\n",\r
- PrintablePath(context->current_input_path), strerror(errno));\r
- return BROTLI_FALSE;\r
- }\r
- return BROTLI_TRUE;\r
-}\r
-\r
-/* Internal: should be used only in Provide-/Flush-Output. */\r
-static BROTLI_BOOL WriteOutput(Context* context) {\r
- size_t out_size = (size_t)(context->next_out - context->output);\r
- if (out_size == 0) return BROTLI_TRUE;\r
- if (context->test_integrity) return BROTLI_TRUE;\r
-\r
- fwrite(context->output, 1, out_size, context->fout);\r
- if (ferror(context->fout)) {\r
- fprintf(stderr, "failed to write output [%s]: %s\n",\r
- PrintablePath(context->current_output_path), strerror(errno));\r
- return BROTLI_FALSE;\r
- }\r
- return BROTLI_TRUE;\r
-}\r
-\r
-static BROTLI_BOOL ProvideOutput(Context* context) {\r
- if (!WriteOutput(context)) return BROTLI_FALSE;\r
- context->available_out = kFileBufferSize;\r
- context->next_out = context->output;\r
- return BROTLI_TRUE;\r
-}\r
-\r
-static BROTLI_BOOL FlushOutput(Context* context) {\r
- if (!WriteOutput(context)) return BROTLI_FALSE;\r
- context->available_out = 0;\r
- return BROTLI_TRUE;\r
-}\r
-\r
-static BROTLI_BOOL DecompressFile(Context* context, BrotliDecoderState* s) {\r
- BrotliDecoderResult result = BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT;\r
- InitializeBuffers(context);\r
- for (;;) {\r
- if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) {\r
- if (!HasMoreInput(context)) {\r
- fprintf(stderr, "corrupt input [%s]\n",\r
- PrintablePath(context->current_input_path));\r
- return BROTLI_FALSE;\r
- }\r
- if (!ProvideInput(context)) return BROTLI_FALSE;\r
- } else if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) {\r
- if (!ProvideOutput(context)) return BROTLI_FALSE;\r
- } else if (result == BROTLI_DECODER_RESULT_SUCCESS) {\r
- if (!FlushOutput(context)) return BROTLI_FALSE;\r
- if (context->available_in != 0 || HasMoreInput(context)) {\r
- fprintf(stderr, "corrupt input [%s]\n",\r
- PrintablePath(context->current_input_path));\r
- return BROTLI_FALSE;\r
- }\r
- return BROTLI_TRUE;\r
- } else {\r
- fprintf(stderr, "corrupt input [%s]\n",\r
- PrintablePath(context->current_input_path));\r
- return BROTLI_FALSE;\r
- }\r
-\r
- result = BrotliDecoderDecompressStream(s, &context->available_in,\r
- &context->next_in, &context->available_out, &context->next_out, 0);\r
- }\r
-}\r
-\r
-/* Default brotli_alloc_func */\r
-void* BrotliAllocFunc(void* opaque, size_t size) {\r
- *(size_t *)opaque = *(size_t *) opaque + size; \r
- return malloc(size);\r
-}\r
-\r
-/* Default brotli_free_func */\r
-void BrotliFreeFunc(void* opaque, void* address) {\r
- free(address);\r
-}\r
-\r
-size_t scratch_buffer_size = 0;\r
-\r
-static BROTLI_BOOL DecompressFiles(Context* context) {\r
- while (NextFile(context)) {\r
- BROTLI_BOOL is_ok = BROTLI_TRUE;\r
- BrotliDecoderState* s = BrotliDecoderCreateInstance(BrotliAllocFunc, BrotliFreeFunc, &scratch_buffer_size);\r
- if (!s) {\r
- fprintf(stderr, "out of memory\n");\r
- return BROTLI_FALSE;\r
- }\r
- /* This allows decoding "large-window" streams. Though it creates\r
- fragmentation (new builds decode streams that old builds don't),\r
- it is better from used experience perspective. */\r
- BrotliDecoderSetParameter(s, BROTLI_DECODER_PARAM_LARGE_WINDOW, 1u);\r
- is_ok = OpenFiles(context);\r
- if (is_ok && !context->current_input_path &&\r
- !context->force_overwrite && isatty(STDIN_FILENO)) {\r
- fprintf(stderr, "Use -h help. Use -f to force input from a terminal.\n");\r
- is_ok = BROTLI_FALSE;\r
- }\r
- if (is_ok) is_ok = DecompressFile(context, s);\r
- BrotliDecoderDestroyInstance(s);\r
- if (!CloseFiles(context, is_ok)) is_ok = BROTLI_FALSE;\r
- if (!is_ok) return BROTLI_FALSE;\r
- }\r
- return BROTLI_TRUE;\r
-}\r
-\r
-static BROTLI_BOOL CompressFile(Context* context, BrotliEncoderState* s) {\r
- BROTLI_BOOL is_eof = BROTLI_FALSE;\r
- InitializeBuffers(context);\r
- for (;;) {\r
- if (context->available_in == 0 && !is_eof) {\r
- if (!ProvideInput(context)) return BROTLI_FALSE;\r
- is_eof = !HasMoreInput(context);\r
- }\r
-\r
- if (!BrotliEncoderCompressStream(s,\r
- is_eof ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS,\r
- &context->available_in, &context->next_in,\r
- &context->available_out, &context->next_out, NULL)) {\r
- /* Should detect OOM? */\r
- fprintf(stderr, "failed to compress data [%s]\n",\r
- PrintablePath(context->current_input_path));\r
- return BROTLI_FALSE;\r
- }\r
-\r
- if (context->available_out == 0) {\r
- if (!ProvideOutput(context)) return BROTLI_FALSE;\r
- }\r
-\r
- if (BrotliEncoderIsFinished(s)) {\r
- return FlushOutput(context);\r
- }\r
- }\r
-}\r
-\r
-static BROTLI_BOOL CompressFiles(Context* context) {\r
- while (NextFile(context)) {\r
- BROTLI_BOOL is_ok = BROTLI_TRUE;\r
- BrotliEncoderState* s = BrotliEncoderCreateInstance(NULL, NULL, NULL);\r
- if (!s) {\r
- fprintf(stderr, "out of memory\n");\r
- return BROTLI_FALSE;\r
- }\r
- BrotliEncoderSetParameter(s,\r
- BROTLI_PARAM_QUALITY, (uint32_t)context->quality);\r
- if (context->lgwin > 0) {\r
- /* Specified by user. */\r
- /* Do not enable "large-window" extension, if not required. */\r
- if (context->lgwin > BROTLI_MAX_WINDOW_BITS) {\r
- BrotliEncoderSetParameter(s, BROTLI_PARAM_LARGE_WINDOW, 1u);\r
- }\r
- BrotliEncoderSetParameter(s,\r
- BROTLI_PARAM_LGWIN, (uint32_t)context->lgwin);\r
- } else {\r
- /* 0, or not specified by user; could be chosen by compressor. */\r
- uint32_t lgwin = DEFAULT_LGWIN;\r
- /* Use file size to limit lgwin. */\r
- if (context->input_file_length >= 0) {\r
- int32_t size = 1 << BROTLI_MIN_WINDOW_BITS;\r
- lgwin = BROTLI_MIN_WINDOW_BITS;\r
- while (size < context->input_file_length) {\r
- size <<= 1;\r
- lgwin++;\r
- if (lgwin == BROTLI_MAX_WINDOW_BITS) break;\r
- }\r
- }\r
- BrotliEncoderSetParameter(s, BROTLI_PARAM_LGWIN, lgwin);\r
- }\r
- if (context->input_file_length > 0) {\r
- uint32_t size_hint = context->input_file_length < (1 << 30) ?\r
- (uint32_t)context->input_file_length : (1u << 30);\r
- BrotliEncoderSetParameter(s, BROTLI_PARAM_SIZE_HINT, size_hint);\r
- }\r
- is_ok = OpenFiles(context);\r
- if (is_ok && !context->current_output_path &&\r
- !context->force_overwrite && isatty(STDOUT_FILENO)) {\r
- fprintf(stderr, "Use -h help. Use -f to force output to a terminal.\n");\r
- is_ok = BROTLI_FALSE;\r
- }\r
- if (is_ok) is_ok = CompressFile(context, s);\r
- BrotliEncoderDestroyInstance(s);\r
- if (!CloseFiles(context, is_ok)) is_ok = BROTLI_FALSE;\r
- if (!is_ok) return BROTLI_FALSE;\r
- }\r
- return BROTLI_TRUE;\r
-}\r
-\r
-int main(int argc, char** argv) {\r
- Command command;\r
- Context context;\r
- Context context_dec;\r
- BROTLI_BOOL is_ok = BROTLI_TRUE;\r
- int i;\r
-\r
- context.quality = 11;\r
- context.lgwin = -1;\r
- context.gmem = 1;\r
- context.force_overwrite = BROTLI_FALSE;\r
- context.junk_source = BROTLI_FALSE;\r
- context.copy_stat = BROTLI_TRUE;\r
- context.test_integrity = BROTLI_FALSE;\r
- context.verbose = BROTLI_FALSE;\r
- context.write_to_stdout = BROTLI_FALSE;\r
- context.decompress = BROTLI_FALSE;\r
- context.large_window = BROTLI_FALSE;\r
- context.output_path = NULL;\r
- context.suffix = DEFAULT_SUFFIX;\r
- for (i = 0; i < MAX_OPTIONS; ++i) context.not_input_indices[i] = 0;\r
- context.longest_path_len = 1;\r
- context.input_count = 0;\r
-\r
- context.argc = argc;\r
- context.argv = argv;\r
- context.modified_path = NULL;\r
- context.iterator = 0;\r
- context.ignore = 0;\r
- context.iterator_error = BROTLI_FALSE;\r
- context.buffer = NULL;\r
- context.current_input_path = NULL;\r
- context.current_output_path = NULL;\r
- context.fin = NULL;\r
- context.fout = NULL;\r
-\r
- command = ParseParams(&context);\r
-\r
- if (command == COMMAND_COMPRESS || command == COMMAND_DECOMPRESS ||\r
- command == COMMAND_TEST_INTEGRITY) {\r
- if (is_ok) {\r
- size_t modified_path_len =\r
- context.longest_path_len + strlen(context.suffix) + 1;\r
- context.modified_path = (char*)malloc(modified_path_len);\r
- context.buffer = (uint8_t*)malloc(kFileBufferSize * 2);\r
- if (!context.modified_path || !context.buffer) {\r
- fprintf(stderr, "out of memory\n");\r
- is_ok = BROTLI_FALSE;\r
- } else {\r
- context.input = context.buffer;\r
- context.output = context.buffer + kFileBufferSize;\r
- }\r
- }\r
- }\r
-\r
- if (!is_ok) command = COMMAND_NOOP;\r
-\r
- switch (command) {\r
- case COMMAND_NOOP:\r
- break;\r
-\r
- case COMMAND_VERSION:\r
- PrintVersion();\r
- break;\r
-\r
- case COMMAND_COMPRESS:\r
- memcpy (&context_dec, &context, sizeof (context));\r
- is_ok = CompressFiles(&context);\r
- if (!is_ok) {\r
- break;\r
- }\r
- context_dec.decompress = BROTLI_TRUE;\r
- context_dec.input_count = 1;\r
- context_dec.current_input_path = context_dec.output_path;\r
- context_dec.fout = tmpfile ();\r
- is_ok = DecompressFiles(&context_dec);\r
- if (!is_ok) {\r
- break;\r
- }\r
- //\r
- // fill decoder header\r
- //\r
- context_dec.fout = fopen(context_dec.output_path, "rb+"); /* open output_path file and add in head info */\r
- fwrite(&input_file_length, 1, sizeof(int64_t), context_dec.fout);\r
- scratch_buffer_size += context.gmem * GAP_MEM_BLOCK; /* there is a memory gap between IA32 and X64 environment*/\r
- scratch_buffer_size += kFileBufferSize * 2;\r
- fwrite(&scratch_buffer_size, 1, sizeof(int64_t), context_dec.fout);\r
- if (fclose(context_dec.fout) != 0) {\r
- fprintf(stderr, "failed to update ouptut file: %s\n", context_dec.output_path);\r
- is_ok = 0;\r
- }\r
- break;\r
-\r
- case COMMAND_DECOMPRESS:\r
- case COMMAND_TEST_INTEGRITY:\r
- is_ok = DecompressFiles(&context);\r
- break;\r
-\r
- case COMMAND_HELP:\r
- case COMMAND_INVALID:\r
- default:\r
- is_ok = (command == COMMAND_HELP);\r
- PrintHelp(FileName(argv[0]), is_ok);\r
- break;\r
- }\r
-\r
- if (context.iterator_error) is_ok = BROTLI_FALSE;\r
-\r
- free(context.modified_path);\r
- free(context.buffer);\r
-\r
- if (!is_ok) exit(1);\r
- return 0;\r
-}\r