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, ¶ms->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, ¶ms->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, ¶ms->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, ¶ms->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, ¶ms->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, ×); 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