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