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 22 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 const char* output_path; 97 const char* suffix; 98 int not_input_indices[MAX_OPTIONS]; 99 size_t longest_path_len; 100 size_t input_count; 101 102 /* Inner state */ 103 int argc; 104 char** argv; 105 char* modified_path; /* Storage for path with appended / cut suffix */ 106 int iterator; 107 int ignore; 108 BROTLI_BOOL iterator_error; 109 uint8_t* buffer; 110 uint8_t* input; 111 uint8_t* output; 112 const char* current_input_path; 113 const char* current_output_path; 114 FILE* fin; 115 FILE* fout; 116 } Context; 117 118 /* Parse up to 5 decimal digits. */ 119 static BROTLI_BOOL ParseInt(const char* s, int low, int high, int* result) { 120 int value = 0; 121 int i; 122 for (i = 0; i < 5; ++i) { 123 char c = s[i]; 124 if (c == 0) break; 125 if (s[i] < '0' || s[i] > '9') return BROTLI_FALSE; 126 value = (10 * value) + (c - '0'); 127 } 128 if (i == 0) return BROTLI_FALSE; 129 if (i > 1 && s[0] == '0') return BROTLI_FALSE; 130 if (s[i] != 0) return BROTLI_FALSE; 131 if (value < low || value > high) return BROTLI_FALSE; 132 *result = value; 133 return BROTLI_TRUE; 134 } 135 136 /* Returns "base file name" or its tail, if it contains '/' or '\'. */ 137 static const char* FileName(const char* path) { 138 const char* separator_position = strrchr(path, '/'); 139 if (separator_position) path = separator_position + 1; 140 separator_position = strrchr(path, '\\'); 141 if (separator_position) path = separator_position + 1; 142 return path; 143 } 144 145 /* Detect if the program name is a special alias that infers a command type. */ 146 static Command ParseAlias(const char* name) { 147 /* TODO: cast name to lower case? */ 148 const char* unbrotli = "unbrotli"; 149 size_t unbrotli_len = strlen(unbrotli); 150 name = FileName(name); 151 /* Partial comparison. On Windows there could be ".exe" suffix. */ 152 if (strncmp(name, unbrotli, unbrotli_len) == 0) { 153 char terminator = name[unbrotli_len]; 154 if (terminator == 0 || terminator == '.') return COMMAND_DECOMPRESS; 155 } 156 return COMMAND_COMPRESS; 157 } 158 159 static Command ParseParams(Context* params) { 160 int argc = params->argc; 161 char** argv = params->argv; 162 int i; 163 int next_option_index = 0; 164 size_t input_count = 0; 165 size_t longest_path_len = 1; 166 BROTLI_BOOL command_set = BROTLI_FALSE; 167 BROTLI_BOOL quality_set = BROTLI_FALSE; 168 BROTLI_BOOL output_set = BROTLI_FALSE; 169 BROTLI_BOOL keep_set = BROTLI_FALSE; 170 BROTLI_BOOL lgwin_set = BROTLI_FALSE; 171 BROTLI_BOOL suffix_set = BROTLI_FALSE; 172 BROTLI_BOOL after_dash_dash = BROTLI_FALSE; 173 Command command = ParseAlias(argv[0]); 174 175 for (i = 1; i < argc; ++i) { 176 const char* arg = argv[i]; 177 /* C99 5.1.2.2.1: "members argv[0] through argv[argc-1] inclusive shall 178 contain pointers to strings"; NULL and 0-length are not forbidden. */ 179 size_t arg_len = arg ? strlen(arg) : 0; 180 181 if (arg_len == 0) { 182 params->not_input_indices[next_option_index++] = i; 183 continue; 184 } 185 186 /* Too many options. The expected longest option list is: 187 "-q 0 -w 10 -o f -D d -S b -d -f -k -n -v --", i.e. 16 items in total. 188 This check is an additinal guard that is never triggered, but provides an 189 additional guard for future changes. */ 190 if (next_option_index > (MAX_OPTIONS - 2)) { 191 return COMMAND_INVALID; 192 } 193 194 /* Input file entry. */ 195 if (after_dash_dash || arg[0] != '-' || arg_len == 1) { 196 input_count++; 197 if (longest_path_len < arg_len) longest_path_len = arg_len; 198 continue; 199 } 200 201 /* Not a file entry. */ 202 params->not_input_indices[next_option_index++] = i; 203 204 /* '--' entry stop parsing arguments. */ 205 if (arg_len == 2 && arg[1] == '-') { 206 after_dash_dash = BROTLI_TRUE; 207 continue; 208 } 209 210 /* Simple / coalesced options. */ 211 if (arg[1] != '-') { 212 size_t j; 213 for (j = 1; j < arg_len; ++j) { 214 char c = arg[j]; 215 if (c >= '0' && c <= '9') { 216 if (quality_set) return COMMAND_INVALID; 217 quality_set = BROTLI_TRUE; 218 params->quality = c - '0'; 219 continue; 220 } else if (c == 'c') { 221 if (output_set) return COMMAND_INVALID; 222 output_set = BROTLI_TRUE; 223 params->write_to_stdout = BROTLI_TRUE; 224 continue; 225 } else if (c == 'd') { 226 if (command_set) return COMMAND_INVALID; 227 command_set = BROTLI_TRUE; 228 command = COMMAND_DECOMPRESS; 229 continue; 230 } else if (c == 'f') { 231 if (params->force_overwrite) return COMMAND_INVALID; 232 params->force_overwrite = BROTLI_TRUE; 233 continue; 234 } else if (c == 'h') { 235 /* Don't parse further. */ 236 return COMMAND_HELP; 237 } else if (c == 'j' || c == 'k') { 238 if (keep_set) return COMMAND_INVALID; 239 keep_set = BROTLI_TRUE; 240 params->junk_source = TO_BROTLI_BOOL(c == 'j'); 241 continue; 242 } else if (c == 'n') { 243 if (!params->copy_stat) return COMMAND_INVALID; 244 params->copy_stat = BROTLI_FALSE; 245 continue; 246 } else if (c == 't') { 247 if (command_set) return COMMAND_INVALID; 248 command_set = BROTLI_TRUE; 249 command = COMMAND_TEST_INTEGRITY; 250 continue; 251 } else if (c == 'v') { 252 if (params->verbose) return COMMAND_INVALID; 253 params->verbose = BROTLI_TRUE; 254 continue; 255 } else if (c == 'V') { 256 /* Don't parse further. */ 257 return COMMAND_VERSION; 258 } else if (c == 'Z') { 259 if (quality_set) return COMMAND_INVALID; 260 quality_set = BROTLI_TRUE; 261 params->quality = 11; 262 continue; 263 } 264 /* o/q/w/D/S with parameter is expected */ 265 if (c != 'o' && c != 'q' && c != 'w' && c != 'D' && c != 'S') { 266 return COMMAND_INVALID; 267 } 268 if (j + 1 != arg_len) return COMMAND_INVALID; 269 i++; 270 if (i == argc || !argv[i] || argv[i][0] == 0) return COMMAND_INVALID; 271 params->not_input_indices[next_option_index++] = i; 272 if (c == 'o') { 273 if (output_set) return COMMAND_INVALID; 274 params->output_path = argv[i]; 275 } else if (c == 'q') { 276 if (quality_set) return COMMAND_INVALID; 277 quality_set = ParseInt(argv[i], BROTLI_MIN_QUALITY, 278 BROTLI_MAX_QUALITY, ¶ms->quality); 279 if (!quality_set) return COMMAND_INVALID; 280 } else if (c == 'w') { 281 if (lgwin_set) return COMMAND_INVALID; 282 lgwin_set = ParseInt(argv[i], 0, 283 BROTLI_MAX_WINDOW_BITS, ¶ms->lgwin); 284 if (!lgwin_set) return COMMAND_INVALID; 285 if (params->lgwin != 0 && params->lgwin < BROTLI_MIN_WINDOW_BITS) { 286 return COMMAND_INVALID; 287 } 288 } else if (c == 'S') { 289 if (suffix_set) return COMMAND_INVALID; 290 suffix_set = BROTLI_TRUE; 291 params->suffix = argv[i]; 292 } 293 } 294 } else { /* Double-dash. */ 295 arg = &arg[2]; 296 if (strcmp("best", arg) == 0) { 297 if (quality_set) return COMMAND_INVALID; 298 quality_set = BROTLI_TRUE; 299 params->quality = 11; 300 } else if (strcmp("decompress", arg) == 0) { 301 if (command_set) return COMMAND_INVALID; 302 command_set = BROTLI_TRUE; 303 command = COMMAND_DECOMPRESS; 304 } else if (strcmp("force", arg) == 0) { 305 if (params->force_overwrite) return COMMAND_INVALID; 306 params->force_overwrite = BROTLI_TRUE; 307 } else if (strcmp("help", arg) == 0) { 308 /* Don't parse further. */ 309 return COMMAND_HELP; 310 } else if (strcmp("keep", arg) == 0) { 311 if (keep_set) return COMMAND_INVALID; 312 keep_set = BROTLI_TRUE; 313 params->junk_source = BROTLI_FALSE; 314 } else if (strcmp("no-copy-stat", arg) == 0) { 315 if (!params->copy_stat) return COMMAND_INVALID; 316 params->copy_stat = BROTLI_FALSE; 317 } else if (strcmp("rm", arg) == 0) { 318 if (keep_set) return COMMAND_INVALID; 319 keep_set = BROTLI_TRUE; 320 params->junk_source = BROTLI_TRUE; 321 } else if (strcmp("stdout", arg) == 0) { 322 if (output_set) return COMMAND_INVALID; 323 output_set = BROTLI_TRUE; 324 params->write_to_stdout = BROTLI_TRUE; 325 } else if (strcmp("test", arg) == 0) { 326 if (command_set) return COMMAND_INVALID; 327 command_set = BROTLI_TRUE; 328 command = COMMAND_TEST_INTEGRITY; 329 } else if (strcmp("verbose", arg) == 0) { 330 if (params->verbose) return COMMAND_INVALID; 331 params->verbose = BROTLI_TRUE; 332 } else if (strcmp("version", arg) == 0) { 333 /* Don't parse further. */ 334 return COMMAND_VERSION; 335 } else { 336 /* key=value */ 337 const char* value = strrchr(arg, '='); 338 size_t key_len; 339 if (!value || value[1] == 0) return COMMAND_INVALID; 340 key_len = (size_t)(value - arg); 341 value++; 342 if (strncmp("lgwin", arg, key_len) == 0) { 343 if (lgwin_set) return COMMAND_INVALID; 344 lgwin_set = ParseInt(value, 0, 345 BROTLI_MAX_WINDOW_BITS, ¶ms->lgwin); 346 if (!lgwin_set) return COMMAND_INVALID; 347 if (params->lgwin != 0 && params->lgwin < BROTLI_MIN_WINDOW_BITS) { 348 return COMMAND_INVALID; 349 } 350 } else if (strncmp("output", arg, key_len) == 0) { 351 if (output_set) return COMMAND_INVALID; 352 params->output_path = value; 353 } else if (strncmp("quality", arg, key_len) == 0) { 354 if (quality_set) return COMMAND_INVALID; 355 quality_set = ParseInt(value, BROTLI_MIN_QUALITY, 356 BROTLI_MAX_QUALITY, ¶ms->quality); 357 if (!quality_set) return COMMAND_INVALID; 358 } else if (strncmp("suffix", arg, key_len) == 0) { 359 if (suffix_set) return COMMAND_INVALID; 360 suffix_set = BROTLI_TRUE; 361 params->suffix = value; 362 } else { 363 return COMMAND_INVALID; 364 } 365 } 366 } 367 } 368 369 params->input_count = input_count; 370 params->longest_path_len = longest_path_len; 371 params->decompress = (command == COMMAND_DECOMPRESS); 372 params->test_integrity = (command == COMMAND_TEST_INTEGRITY); 373 374 if (input_count > 1 && output_set) return COMMAND_INVALID; 375 if (params->test_integrity) { 376 if (params->output_path) return COMMAND_INVALID; 377 if (params->write_to_stdout) return COMMAND_INVALID; 378 } 379 if (strchr(params->suffix, '/') || strchr(params->suffix, '\\')) { 380 return COMMAND_INVALID; 381 } 382 383 return command; 384 } 385 386 static void PrintVersion(void) { 387 int major = BROTLI_VERSION >> 24; 388 int minor = (BROTLI_VERSION >> 12) & 0xFFF; 389 int patch = BROTLI_VERSION & 0xFFF; 390 fprintf(stdout, "brotli %d.%d.%d\n", major, minor, patch); 391 } 392 393 static void PrintHelp(const char* name) { 394 /* String is cut to pieces with length less than 509, to conform C90 spec. */ 395 fprintf(stdout, 396 "Usage: %s [OPTION]... [FILE]...\n", 397 name); 398 fprintf(stdout, 399 "Options:\n" 400 " -# compression level (0-9)\n" 401 " -c, --stdout write on standard output\n" 402 " -d, --decompress decompress\n" 403 " -f, --force force output file overwrite\n" 404 " -h, --help display this help and exit\n"); 405 fprintf(stdout, 406 " -j, --rm remove source file(s)\n" 407 " -k, --keep keep source file(s) (default)\n" 408 " -n, --no-copy-stat do not copy source file(s) attributes\n" 409 " -o FILE, --output=FILE output file (only if 1 input file)\n"); 410 fprintf(stdout, 411 " -q NUM, --quality=NUM compression level (%d-%d)\n", 412 BROTLI_MIN_QUALITY, BROTLI_MAX_QUALITY); 413 fprintf(stdout, 414 " -t, --test test compressed file integrity\n" 415 " -v, --verbose verbose mode\n"); 416 fprintf(stdout, 417 " -w NUM, --lgwin=NUM set LZ77 window size (0, %d-%d) (default:%d)\n", 418 BROTLI_MIN_WINDOW_BITS, BROTLI_MAX_WINDOW_BITS, DEFAULT_LGWIN); 419 fprintf(stdout, 420 " window size = 2**NUM - 16\n" 421 " 0 lets compressor choose the optimal value\n"); 422 fprintf(stdout, 423 " -S SUF, --suffix=SUF output file suffix (default:'%s')\n", 424 DEFAULT_SUFFIX); 425 fprintf(stdout, 426 " -V, --version display version and exit\n" 427 " -Z, --best use best compression level (11) (default)\n" 428 "Simple options could be coalesced, i.e. '-9kf' is equivalent to '-9 -k -f'.\n" 429 "With no FILE, or when FILE is -, read standard input.\n" 430 "All arguments after '--' are treated as files.\n"); 431 } 432 433 static const char* PrintablePath(const char* path) { 434 return path ? path : "con"; 435 } 436 437 static BROTLI_BOOL OpenInputFile(const char* input_path, FILE** f) { 438 *f = NULL; 439 if (!input_path) { 440 *f = fdopen(MAKE_BINARY(STDIN_FILENO), "rb"); 441 return BROTLI_TRUE; 442 } 443 *f = fopen(input_path, "rb"); 444 if (!*f) { 445 fprintf(stderr, "failed to open input file [%s]: %s\n", 446 PrintablePath(input_path), strerror(errno)); 447 return BROTLI_FALSE; 448 } 449 return BROTLI_TRUE; 450 } 451 452 static BROTLI_BOOL OpenOutputFile(const char* output_path, FILE** f, 453 BROTLI_BOOL force) { 454 int fd; 455 *f = NULL; 456 if (!output_path) { 457 *f = fdopen(MAKE_BINARY(STDOUT_FILENO), "wb"); 458 return BROTLI_TRUE; 459 } 460 fd = open(output_path, O_CREAT | (force ? 0 : O_EXCL) | O_WRONLY | O_TRUNC, 461 S_IRUSR | S_IWUSR); 462 if (fd < 0) { 463 fprintf(stderr, "failed to open output file [%s]: %s\n", 464 PrintablePath(output_path), strerror(errno)); 465 return BROTLI_FALSE; 466 } 467 *f = fdopen(fd, "wb"); 468 if (!*f) { 469 fprintf(stderr, "failed to open output file [%s]: %s\n", 470 PrintablePath(output_path), strerror(errno)); 471 return BROTLI_FALSE; 472 } 473 return BROTLI_TRUE; 474 } 475 476 /* Copy file times and permissions. 477 TODO: this is a "best effort" implementation; honest cross-platform 478 fully featured implementation is way too hacky; add more hacks by request. */ 479 static void CopyStat(const char* input_path, const char* output_path) { 480 struct stat statbuf; 481 struct utimbuf times; 482 int res; 483 if (input_path == 0 || output_path == 0) { 484 return; 485 } 486 if (stat(input_path, &statbuf) != 0) { 487 return; 488 } 489 times.actime = statbuf.st_atime; 490 times.modtime = statbuf.st_mtime; 491 utime(output_path, ×); 492 res = chmod(output_path, statbuf.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)); 493 if (res != 0) { 494 fprintf(stderr, "setting access bits failed for [%s]: %s\n", 495 PrintablePath(output_path), strerror(errno)); 496 } 497 res = chown(output_path, (uid_t)-1, statbuf.st_gid); 498 if (res != 0) { 499 fprintf(stderr, "setting group failed for [%s]: %s\n", 500 PrintablePath(output_path), strerror(errno)); 501 } 502 res = chown(output_path, statbuf.st_uid, (gid_t)-1); 503 if (res != 0) { 504 fprintf(stderr, "setting user failed for [%s]: %s\n", 505 PrintablePath(output_path), strerror(errno)); 506 } 507 } 508 509 static BROTLI_BOOL NextFile(Context* context) { 510 const char* arg; 511 size_t arg_len; 512 513 /* Iterator points to last used arg; increment to search for the next one. */ 514 context->iterator++; 515 516 /* No input path; read from console. */ 517 if (context->input_count == 0) { 518 if (context->iterator > 1) return BROTLI_FALSE; 519 context->current_input_path = NULL; 520 /* Either write to the specified path, or to console. */ 521 context->current_output_path = context->output_path; 522 return BROTLI_TRUE; 523 } 524 525 /* Skip option arguments. */ 526 while (context->iterator == context->not_input_indices[context->ignore]) { 527 context->iterator++; 528 context->ignore++; 529 } 530 531 /* All args are scanned already. */ 532 if (context->iterator >= context->argc) return BROTLI_FALSE; 533 534 /* Iterator now points to the input file name. */ 535 arg = context->argv[context->iterator]; 536 arg_len = strlen(arg); 537 /* Read from console. */ 538 if (arg_len == 1 && arg[0] == '-') { 539 context->current_input_path = NULL; 540 context->current_output_path = context->output_path; 541 return BROTLI_TRUE; 542 } 543 544 context->current_input_path = arg; 545 context->current_output_path = context->output_path; 546 547 if (context->output_path) return BROTLI_TRUE; 548 if (context->write_to_stdout) return BROTLI_TRUE; 549 550 strcpy(context->modified_path, arg); 551 context->current_output_path = context->modified_path; 552 /* If output is not specified, input path suffix should match. */ 553 if (context->decompress) { 554 size_t suffix_len = strlen(context->suffix); 555 char* name = (char*)FileName(context->modified_path); 556 char* name_suffix; 557 size_t name_len = strlen(name); 558 if (name_len < suffix_len + 1) { 559 fprintf(stderr, "empty output file name for [%s] input file\n", 560 PrintablePath(arg)); 561 context->iterator_error = BROTLI_TRUE; 562 return BROTLI_FALSE; 563 } 564 name_suffix = name + name_len - suffix_len; 565 if (strcmp(context->suffix, name_suffix) != 0) { 566 fprintf(stderr, "input file [%s] suffix mismatch\n", 567 PrintablePath(arg)); 568 context->iterator_error = BROTLI_TRUE; 569 return BROTLI_FALSE; 570 } 571 name_suffix[0] = 0; 572 return BROTLI_TRUE; 573 } else { 574 strcpy(context->modified_path + arg_len, context->suffix); 575 return BROTLI_TRUE; 576 } 577 } 578 579 static BROTLI_BOOL OpenFiles(Context* context) { 580 BROTLI_BOOL is_ok = OpenInputFile(context->current_input_path, &context->fin); 581 if (!context->test_integrity && is_ok) { 582 is_ok = OpenOutputFile( 583 context->current_output_path, &context->fout, context->force_overwrite); 584 } 585 return is_ok; 586 } 587 588 static BROTLI_BOOL CloseFiles(Context* context, BROTLI_BOOL success) { 589 BROTLI_BOOL is_ok = BROTLI_TRUE; 590 if (!context->test_integrity && context->fout) { 591 if (!success && context->current_output_path) { 592 unlink(context->current_output_path); 593 } 594 if (fclose(context->fout) != 0) { 595 if (success) { 596 fprintf(stderr, "fclose failed [%s]: %s\n", 597 PrintablePath(context->current_output_path), strerror(errno)); 598 } 599 is_ok = BROTLI_FALSE; 600 } 601 602 /* TOCTOU violation, but otherwise it is impossible to set file times. */ 603 if (success && is_ok && context->copy_stat) { 604 CopyStat(context->current_input_path, context->current_output_path); 605 } 606 } 607 608 if (context->fin) { 609 if (fclose(context->fin) != 0) { 610 if (is_ok) { 611 fprintf(stderr, "fclose failed [%s]: %s\n", 612 PrintablePath(context->current_input_path), strerror(errno)); 613 } 614 is_ok = BROTLI_FALSE; 615 } 616 } 617 if (success && context->junk_source && context->current_input_path) { 618 unlink(context->current_input_path); 619 } 620 621 context->fin = NULL; 622 context->fout = NULL; 623 624 return is_ok; 625 } 626 627 static const size_t kFileBufferSize = 1 << 16; 628 629 static BROTLI_BOOL DecompressFile(Context* context, BrotliDecoderState* s) { 630 size_t available_in = 0; 631 const uint8_t* next_in = NULL; 632 size_t available_out = kFileBufferSize; 633 uint8_t* next_out = context->output; 634 BrotliDecoderResult result = BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT; 635 for (;;) { 636 if (next_out != context->output) { 637 if (!context->test_integrity) { 638 size_t out_size = (size_t)(next_out - context->output); 639 fwrite(context->output, 1, out_size, context->fout); 640 if (ferror(context->fout)) { 641 fprintf(stderr, "failed to write output [%s]: %s\n", 642 PrintablePath(context->current_output_path), strerror(errno)); 643 return BROTLI_FALSE; 644 } 645 } 646 available_out = kFileBufferSize; 647 next_out = context->output; 648 } 649 650 if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) { 651 if (feof(context->fin)) { 652 fprintf(stderr, "corrupt input [%s]\n", 653 PrintablePath(context->current_input_path)); 654 return BROTLI_FALSE; 655 } 656 available_in = fread(context->input, 1, kFileBufferSize, context->fin); 657 next_in = context->input; 658 if (ferror(context->fin)) { 659 fprintf(stderr, "failed to read input [%s]: %s\n", 660 PrintablePath(context->current_input_path), strerror(errno)); 661 return BROTLI_FALSE; 662 } 663 } else if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) { 664 /* Nothing to do - output is already written. */ 665 } else if (result == BROTLI_DECODER_RESULT_SUCCESS) { 666 if (available_in != 0 || !feof(context->fin)) { 667 fprintf(stderr, "corrupt input [%s]\n", 668 PrintablePath(context->current_input_path)); 669 return BROTLI_FALSE; 670 } 671 return BROTLI_TRUE; 672 } else { 673 fprintf(stderr, "corrupt input [%s]\n", 674 PrintablePath(context->current_input_path)); 675 return BROTLI_FALSE; 676 } 677 678 result = BrotliDecoderDecompressStream( 679 s, &available_in, &next_in, &available_out, &next_out, 0); 680 } 681 } 682 683 static BROTLI_BOOL DecompressFiles(Context* context) { 684 while (NextFile(context)) { 685 BROTLI_BOOL is_ok = BROTLI_TRUE; 686 BrotliDecoderState* s = BrotliDecoderCreateInstance(NULL, NULL, NULL); 687 if (!s) { 688 fprintf(stderr, "out of memory\n"); 689 return BROTLI_FALSE; 690 } 691 is_ok = OpenFiles(context); 692 if (is_ok && !context->current_input_path && 693 !context->force_overwrite && isatty(STDIN_FILENO)) { 694 fprintf(stderr, "Use -h help. Use -f to force input from a terminal.\n"); 695 is_ok = BROTLI_FALSE; 696 } 697 if (is_ok) is_ok = DecompressFile(context, s); 698 BrotliDecoderDestroyInstance(s); 699 if (!CloseFiles(context, is_ok)) is_ok = BROTLI_FALSE; 700 if (!is_ok) return BROTLI_FALSE; 701 } 702 return BROTLI_TRUE; 703 } 704 705 static BROTLI_BOOL CompressFile(Context* context, BrotliEncoderState* s) { 706 size_t available_in = 0; 707 const uint8_t* next_in = NULL; 708 size_t available_out = kFileBufferSize; 709 uint8_t* next_out = context->output; 710 BROTLI_BOOL is_eof = BROTLI_FALSE; 711 712 for (;;) { 713 if (available_in == 0 && !is_eof) { 714 available_in = fread(context->input, 1, kFileBufferSize, context->fin); 715 next_in = context->input; 716 if (ferror(context->fin)) { 717 fprintf(stderr, "failed to read input [%s]: %s\n", 718 PrintablePath(context->current_input_path), strerror(errno)); 719 return BROTLI_FALSE; 720 } 721 is_eof = feof(context->fin) ? BROTLI_TRUE : BROTLI_FALSE; 722 } 723 724 if (!BrotliEncoderCompressStream(s, 725 is_eof ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS, 726 &available_in, &next_in, &available_out, &next_out, NULL)) { 727 /* Should detect OOM? */ 728 fprintf(stderr, "failed to compress data [%s]\n", 729 PrintablePath(context->current_input_path)); 730 return BROTLI_FALSE; 731 } 732 733 if (available_out != kFileBufferSize) { 734 size_t out_size = kFileBufferSize - available_out; 735 fwrite(context->output, 1, out_size, context->fout); 736 if (ferror(context->fout)) { 737 fprintf(stderr, "failed to write output [%s]: %s\n", 738 PrintablePath(context->current_output_path), strerror(errno)); 739 return BROTLI_FALSE; 740 } 741 available_out = kFileBufferSize; 742 next_out = context->output; 743 } 744 745 if (BrotliEncoderIsFinished(s)) return BROTLI_TRUE; 746 } 747 } 748 749 static BROTLI_BOOL CompressFiles(Context* context) { 750 while (NextFile(context)) { 751 BROTLI_BOOL is_ok = BROTLI_TRUE; 752 BrotliEncoderState* s = BrotliEncoderCreateInstance(NULL, NULL, NULL); 753 if (!s) { 754 fprintf(stderr, "out of memory\n"); 755 return BROTLI_FALSE; 756 } 757 BrotliEncoderSetParameter(s, 758 BROTLI_PARAM_QUALITY, (uint32_t)context->quality); 759 BrotliEncoderSetParameter(s, 760 BROTLI_PARAM_LGWIN, (uint32_t)context->lgwin); 761 is_ok = OpenFiles(context); 762 if (is_ok && !context->current_output_path && 763 !context->force_overwrite && isatty(STDOUT_FILENO)) { 764 fprintf(stderr, "Use -h help. Use -f to force output to a terminal.\n"); 765 is_ok = BROTLI_FALSE; 766 } 767 if (is_ok) is_ok = CompressFile(context, s); 768 BrotliEncoderDestroyInstance(s); 769 if (!CloseFiles(context, is_ok)) is_ok = BROTLI_FALSE; 770 if (!is_ok) return BROTLI_FALSE; 771 } 772 return BROTLI_TRUE; 773 } 774 775 int main(int argc, char** argv) { 776 Command command; 777 Context context; 778 BROTLI_BOOL is_ok = BROTLI_TRUE; 779 int i; 780 781 context.quality = 11; 782 context.lgwin = DEFAULT_LGWIN; 783 context.force_overwrite = BROTLI_FALSE; 784 context.junk_source = BROTLI_FALSE; 785 context.copy_stat = BROTLI_TRUE; 786 context.test_integrity = BROTLI_FALSE; 787 context.verbose = BROTLI_FALSE; 788 context.write_to_stdout = BROTLI_FALSE; 789 context.decompress = BROTLI_FALSE; 790 context.output_path = NULL; 791 context.suffix = DEFAULT_SUFFIX; 792 for (i = 0; i < MAX_OPTIONS; ++i) context.not_input_indices[i] = 0; 793 context.longest_path_len = 1; 794 context.input_count = 0; 795 796 context.argc = argc; 797 context.argv = argv; 798 context.modified_path = NULL; 799 context.iterator = 0; 800 context.ignore = 0; 801 context.iterator_error = BROTLI_FALSE; 802 context.buffer = NULL; 803 context.current_input_path = NULL; 804 context.current_output_path = NULL; 805 context.fin = NULL; 806 context.fout = NULL; 807 808 command = ParseParams(&context); 809 810 if (command == COMMAND_COMPRESS || command == COMMAND_DECOMPRESS || 811 command == COMMAND_TEST_INTEGRITY) { 812 if (is_ok) { 813 size_t modified_path_len = 814 context.longest_path_len + strlen(context.suffix) + 1; 815 context.modified_path = (char*)malloc(modified_path_len); 816 context.buffer = (uint8_t*)malloc(kFileBufferSize * 2); 817 if (!context.modified_path || !context.buffer) { 818 fprintf(stderr, "out of memory\n"); 819 is_ok = BROTLI_FALSE; 820 } else { 821 context.input = context.buffer; 822 context.output = context.buffer + kFileBufferSize; 823 } 824 } 825 } 826 827 if (!is_ok) command = COMMAND_NOOP; 828 829 switch (command) { 830 case COMMAND_NOOP: 831 break; 832 833 case COMMAND_VERSION: 834 PrintVersion(); 835 break; 836 837 case COMMAND_COMPRESS: 838 is_ok = CompressFiles(&context); 839 break; 840 841 case COMMAND_DECOMPRESS: 842 case COMMAND_TEST_INTEGRITY: 843 is_ok = DecompressFiles(&context); 844 break; 845 846 case COMMAND_HELP: 847 case COMMAND_INVALID: 848 default: 849 PrintHelp(FileName(argv[0])); 850 is_ok = (command == COMMAND_HELP); 851 break; 852 } 853 854 if (context.iterator_error) is_ok = BROTLI_FALSE; 855 856 free(context.modified_path); 857 free(context.buffer); 858 859 if (!is_ok) exit(1); 860 return 0; 861 } 862