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