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 /* Example main() function for Brotli library. */ 8 9 #include <fcntl.h> 10 #include <stdio.h> 11 #include <stdlib.h> 12 #include <string.h> 13 #include <sys/stat.h> 14 #include <sys/types.h> 15 #include <time.h> 16 17 #include <brotli/decode.h> 18 #include <brotli/encode.h> 19 20 #if !defined(_WIN32) 21 #include <unistd.h> 22 #include <utime.h> 23 #else 24 #include <io.h> 25 #include <share.h> 26 #include <sys/utime.h> 27 28 #define MAKE_BINARY(FILENO) (_setmode((FILENO), _O_BINARY), (FILENO)) 29 30 #if !defined(__MINGW32__) 31 #define STDIN_FILENO MAKE_BINARY(_fileno(stdin)) 32 #define STDOUT_FILENO MAKE_BINARY(_fileno(stdout)) 33 #define S_IRUSR S_IREAD 34 #define S_IWUSR S_IWRITE 35 #endif 36 37 #define fdopen _fdopen 38 #define unlink _unlink 39 #define utimbuf _utimbuf 40 #define utime _utime 41 42 #define fopen ms_fopen 43 #define open ms_open 44 45 #define chmod(F, P) (0) 46 #define chown(F, O, G) (0) 47 48 #if defined(_MSC_VER) && (_MSC_VER >= 1400) 49 #define fseek _fseeki64 50 #define ftell _ftelli64 51 #endif 52 53 static FILE* ms_fopen(const char *filename, const char *mode) { 54 FILE* result = 0; 55 fopen_s(&result, filename, mode); 56 return result; 57 } 58 59 static int ms_open(const char *filename, int oflag, int pmode) { 60 int result = -1; 61 _sopen_s(&result, filename, oflag | O_BINARY, _SH_DENYNO, pmode); 62 return result; 63 } 64 #endif /* WIN32 */ 65 66 static int ParseQuality(const char* s, int* quality) { 67 if (s[0] >= '0' && s[0] <= '9') { 68 *quality = s[0] - '0'; 69 if (s[1] >= '0' && s[1] <= '9') { 70 *quality = *quality * 10 + s[1] - '0'; 71 return (s[2] == 0) ? 1 : 0; 72 } 73 return (s[1] == 0) ? 1 : 0; 74 } 75 return 0; 76 } 77 78 static void ParseArgv(int argc, char **argv, 79 char **input_path, 80 char **output_path, 81 char **dictionary_path, 82 int *force, 83 int *quality, 84 int *decompress, 85 int *repeat, 86 int *verbose, 87 int *lgwin, 88 int *copy_stat) { 89 int k; 90 *force = 0; 91 *input_path = 0; 92 *output_path = 0; 93 *repeat = 1; 94 *verbose = 0; 95 *lgwin = 22; 96 *copy_stat = 1; 97 { 98 size_t argv0_len = strlen(argv[0]); 99 *decompress = 100 argv0_len >= 5 && strcmp(&argv[0][argv0_len - 5], "unbro") == 0; 101 } 102 for (k = 1; k < argc; ++k) { 103 if (!strcmp("--force", argv[k]) || 104 !strcmp("-f", argv[k])) { 105 if (*force != 0) { 106 goto error; 107 } 108 *force = 1; 109 continue; 110 } else if (!strcmp("--decompress", argv[k]) || 111 !strcmp("--uncompress", argv[k]) || 112 !strcmp("-d", argv[k])) { 113 *decompress = 1; 114 continue; 115 } else if (!strcmp("--verbose", argv[k]) || 116 !strcmp("-v", argv[k])) { 117 if (*verbose != 0) { 118 goto error; 119 } 120 *verbose = 1; 121 continue; 122 } else if (!strcmp("--no-copy-stat", argv[k])) { 123 if (*copy_stat == 0) { 124 goto error; 125 } 126 *copy_stat = 0; 127 continue; 128 } 129 if (k < argc - 1) { 130 if (!strcmp("--input", argv[k]) || 131 !strcmp("--in", argv[k]) || 132 !strcmp("-i", argv[k])) { 133 if (*input_path != 0) { 134 goto error; 135 } 136 *input_path = argv[k + 1]; 137 ++k; 138 continue; 139 } else if (!strcmp("--output", argv[k]) || 140 !strcmp("--out", argv[k]) || 141 !strcmp("-o", argv[k])) { 142 if (*output_path != 0) { 143 goto error; 144 } 145 *output_path = argv[k + 1]; 146 ++k; 147 continue; 148 } else if (!strcmp("--custom-dictionary", argv[k])) { 149 if (*dictionary_path != 0) { 150 goto error; 151 } 152 *dictionary_path = argv[k + 1]; 153 ++k; 154 continue; 155 } else if (!strcmp("--quality", argv[k]) || 156 !strcmp("-q", argv[k])) { 157 if (!ParseQuality(argv[k + 1], quality)) { 158 goto error; 159 } 160 ++k; 161 continue; 162 } else if (!strcmp("--repeat", argv[k]) || 163 !strcmp("-r", argv[k])) { 164 if (!ParseQuality(argv[k + 1], repeat)) { 165 goto error; 166 } 167 ++k; 168 continue; 169 } else if (!strcmp("--window", argv[k]) || 170 !strcmp("-w", argv[k])) { 171 if (!ParseQuality(argv[k + 1], lgwin)) { 172 goto error; 173 } 174 if (*lgwin < 10 || *lgwin >= 25) { 175 goto error; 176 } 177 ++k; 178 continue; 179 } 180 } 181 goto error; 182 } 183 return; 184 error: 185 fprintf(stderr, 186 "Usage: %s [--force] [--quality n] [--decompress]" 187 " [--input filename] [--output filename] [--repeat iters]" 188 " [--verbose] [--window n] [--custom-dictionary filename]" 189 " [--no-copy-stat]\n", 190 argv[0]); 191 exit(1); 192 } 193 194 static FILE* OpenInputFile(const char* input_path) { 195 FILE* f; 196 if (input_path == 0) { 197 return fdopen(STDIN_FILENO, "rb"); 198 } 199 f = fopen(input_path, "rb"); 200 if (f == 0) { 201 perror("fopen"); 202 exit(1); 203 } 204 return f; 205 } 206 207 static FILE *OpenOutputFile(const char *output_path, const int force) { 208 int fd; 209 if (output_path == 0) { 210 return fdopen(STDOUT_FILENO, "wb"); 211 } 212 fd = open(output_path, O_CREAT | (force ? 0 : O_EXCL) | O_WRONLY | O_TRUNC, 213 S_IRUSR | S_IWUSR); 214 if (fd < 0) { 215 if (!force) { 216 struct stat statbuf; 217 if (stat(output_path, &statbuf) == 0) { 218 fprintf(stderr, "output file exists\n"); 219 exit(1); 220 } 221 } 222 perror("open"); 223 exit(1); 224 } 225 return fdopen(fd, "wb"); 226 } 227 228 static int64_t FileSize(const char *path) { 229 FILE *f = fopen(path, "rb"); 230 int64_t retval; 231 if (f == NULL) { 232 return -1; 233 } 234 if (fseek(f, 0L, SEEK_END) != 0) { 235 fclose(f); 236 return -1; 237 } 238 retval = ftell(f); 239 if (fclose(f) != 0) { 240 return -1; 241 } 242 return retval; 243 } 244 245 /* Copy file times and permissions. 246 TODO: this is a "best effort" implementation; honest cross-platform 247 fully featured implementation is way too hacky; add more hacks by request. */ 248 static void CopyStat(const char* input_path, const char* output_path) { 249 struct stat statbuf; 250 struct utimbuf times; 251 int res; 252 if (input_path == 0 || output_path == 0) { 253 return; 254 } 255 if (stat(input_path, &statbuf) != 0) { 256 return; 257 } 258 times.actime = statbuf.st_atime; 259 times.modtime = statbuf.st_mtime; 260 utime(output_path, ×); 261 res = chmod(output_path, statbuf.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)); 262 if (res != 0) 263 perror("chmod failed"); 264 res = chown(output_path, (uid_t)-1, statbuf.st_gid); 265 if (res != 0) 266 perror("chown failed"); 267 res = chown(output_path, statbuf.st_uid, (gid_t)-1); 268 if (res != 0) 269 perror("chown failed"); 270 } 271 272 /* Result ownersip is passed to caller. 273 |*dictionary_size| is set to resulting buffer size. */ 274 static uint8_t* ReadDictionary(const char* path, size_t* dictionary_size) { 275 static const int kMaxDictionarySize = (1 << 24) - 16; 276 FILE *f = fopen(path, "rb"); 277 int64_t file_size_64; 278 uint8_t* buffer; 279 size_t bytes_read; 280 281 if (f == NULL) { 282 perror("fopen"); 283 exit(1); 284 } 285 286 file_size_64 = FileSize(path); 287 if (file_size_64 == -1) { 288 fprintf(stderr, "could not get size of dictionary file"); 289 exit(1); 290 } 291 292 if (file_size_64 > kMaxDictionarySize) { 293 fprintf(stderr, "dictionary is larger than maximum allowed: %d\n", 294 kMaxDictionarySize); 295 exit(1); 296 } 297 *dictionary_size = (size_t)file_size_64; 298 299 buffer = (uint8_t*)malloc(*dictionary_size); 300 if (!buffer) { 301 fprintf(stderr, "could not read dictionary: out of memory\n"); 302 exit(1); 303 } 304 bytes_read = fread(buffer, sizeof(uint8_t), *dictionary_size, f); 305 if (bytes_read != *dictionary_size) { 306 fprintf(stderr, "could not read dictionary\n"); 307 exit(1); 308 } 309 fclose(f); 310 return buffer; 311 } 312 313 static const size_t kFileBufferSize = 65536; 314 315 static int Decompress(FILE* fin, FILE* fout, const char* dictionary_path) { 316 /* Dictionary should be kept during first rounds of decompression. */ 317 uint8_t* dictionary = NULL; 318 uint8_t* input; 319 uint8_t* output; 320 size_t available_in; 321 const uint8_t* next_in; 322 size_t available_out = kFileBufferSize; 323 uint8_t* next_out; 324 BrotliDecoderResult result = BROTLI_DECODER_RESULT_ERROR; 325 BrotliDecoderState* s = BrotliDecoderCreateInstance(NULL, NULL, NULL); 326 if (!s) { 327 fprintf(stderr, "out of memory\n"); 328 return 0; 329 } 330 if (dictionary_path != NULL) { 331 size_t dictionary_size = 0; 332 dictionary = ReadDictionary(dictionary_path, &dictionary_size); 333 BrotliDecoderSetCustomDictionary(s, dictionary_size, dictionary); 334 } 335 input = (uint8_t*)malloc(kFileBufferSize); 336 output = (uint8_t*)malloc(kFileBufferSize); 337 if (!input || !output) { 338 fprintf(stderr, "out of memory\n"); 339 goto end; 340 } 341 next_out = output; 342 result = BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT; 343 while (1) { 344 if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) { 345 if (feof(fin)) { 346 break; 347 } 348 available_in = fread(input, 1, kFileBufferSize, fin); 349 next_in = input; 350 if (ferror(fin)) { 351 break; 352 } 353 } else if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) { 354 fwrite(output, 1, kFileBufferSize, fout); 355 if (ferror(fout)) { 356 break; 357 } 358 available_out = kFileBufferSize; 359 next_out = output; 360 } else { 361 break; /* Error or success. */ 362 } 363 result = BrotliDecoderDecompressStream( 364 s, &available_in, &next_in, &available_out, &next_out, 0); 365 } 366 if (next_out != output) { 367 fwrite(output, 1, (size_t)(next_out - output), fout); 368 } 369 370 if ((result == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) || ferror(fout)) { 371 fprintf(stderr, "failed to write output\n"); 372 } else if (result != BROTLI_DECODER_RESULT_SUCCESS) { 373 /* Error or needs more input. */ 374 fprintf(stderr, "corrupt input\n"); 375 } 376 377 end: 378 free(dictionary); 379 free(input); 380 free(output); 381 BrotliDecoderDestroyInstance(s); 382 return (result == BROTLI_DECODER_RESULT_SUCCESS) ? 1 : 0; 383 } 384 385 static int Compress(int quality, int lgwin, FILE* fin, FILE* fout, 386 const char *dictionary_path) { 387 BrotliEncoderState* s = BrotliEncoderCreateInstance(0, 0, 0); 388 uint8_t* buffer = (uint8_t*)malloc(kFileBufferSize << 1); 389 uint8_t* input = buffer; 390 uint8_t* output = buffer + kFileBufferSize; 391 size_t available_in = 0; 392 const uint8_t* next_in = NULL; 393 size_t available_out = kFileBufferSize; 394 uint8_t* next_out = output; 395 int is_eof = 0; 396 int is_ok = 1; 397 398 if (!s || !buffer) { 399 is_ok = 0; 400 goto finish; 401 } 402 403 BrotliEncoderSetParameter(s, BROTLI_PARAM_QUALITY, (uint32_t)quality); 404 BrotliEncoderSetParameter(s, BROTLI_PARAM_LGWIN, (uint32_t)lgwin); 405 if (dictionary_path != NULL) { 406 size_t dictionary_size = 0; 407 uint8_t* dictionary = ReadDictionary(dictionary_path, &dictionary_size); 408 BrotliEncoderSetCustomDictionary(s, dictionary_size, dictionary); 409 free(dictionary); 410 } 411 412 while (1) { 413 if (available_in == 0 && !is_eof) { 414 available_in = fread(input, 1, kFileBufferSize, fin); 415 next_in = input; 416 if (ferror(fin)) break; 417 is_eof = feof(fin); 418 } 419 420 if (!BrotliEncoderCompressStream(s, 421 is_eof ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_PROCESS, 422 &available_in, &next_in, &available_out, &next_out, NULL)) { 423 is_ok = 0; 424 break; 425 } 426 427 if (available_out != kFileBufferSize) { 428 size_t out_size = kFileBufferSize - available_out; 429 fwrite(output, 1, out_size, fout); 430 if (ferror(fout)) break; 431 available_out = kFileBufferSize; 432 next_out = output; 433 } 434 435 if (BrotliEncoderIsFinished(s)) break; 436 } 437 438 finish: 439 free(buffer); 440 BrotliEncoderDestroyInstance(s); 441 442 if (!is_ok) { 443 /* Should detect OOM? */ 444 fprintf(stderr, "failed to compress data\n"); 445 return 0; 446 } else if (ferror(fout)) { 447 fprintf(stderr, "failed to write output\n"); 448 return 0; 449 } else if (ferror(fin)) { 450 fprintf(stderr, "failed to read input\n"); 451 return 0; 452 } 453 return 1; 454 } 455 456 int main(int argc, char** argv) { 457 char *input_path = 0; 458 char *output_path = 0; 459 char *dictionary_path = 0; 460 int force = 0; 461 int quality = 11; 462 int decompress = 0; 463 int repeat = 1; 464 int verbose = 0; 465 int lgwin = 0; 466 int copy_stat = 1; 467 clock_t clock_start; 468 int i; 469 ParseArgv(argc, argv, &input_path, &output_path, &dictionary_path, &force, 470 &quality, &decompress, &repeat, &verbose, &lgwin, ©_stat); 471 clock_start = clock(); 472 for (i = 0; i < repeat; ++i) { 473 FILE* fin = OpenInputFile(input_path); 474 FILE* fout = OpenOutputFile(output_path, force || (repeat > 1)); 475 int is_ok = 0; 476 if (decompress) { 477 is_ok = Decompress(fin, fout, dictionary_path); 478 } else { 479 is_ok = Compress(quality, lgwin, fin, fout, dictionary_path); 480 } 481 if (!is_ok) { 482 unlink(output_path); 483 exit(1); 484 } 485 if (fclose(fout) != 0) { 486 perror("fclose"); 487 exit(1); 488 } 489 /* TOCTOU violation, but otherwise it is impossible to set file times. */ 490 if (copy_stat && (i + 1 == repeat)) { 491 CopyStat(input_path, output_path); 492 } 493 if (fclose(fin) != 0) { 494 perror("fclose"); 495 exit(1); 496 } 497 } 498 if (verbose) { 499 clock_t clock_end = clock(); 500 double duration = (double)(clock_end - clock_start) / CLOCKS_PER_SEC; 501 int64_t uncompressed_size; 502 double uncompressed_bytes_in_MB; 503 if (duration < 1e-9) { 504 duration = 1e-9; 505 } 506 uncompressed_size = FileSize(decompress ? output_path : input_path); 507 if (uncompressed_size == -1) { 508 fprintf(stderr, "failed to determine uncompressed file size\n"); 509 exit(1); 510 } 511 uncompressed_bytes_in_MB = 512 (double)(repeat * uncompressed_size) / (1024.0 * 1024.0); 513 if (decompress) { 514 printf("Brotli decompression speed: "); 515 } else { 516 printf("Brotli compression speed: "); 517 } 518 printf("%g MB/s\n", uncompressed_bytes_in_MB / duration); 519 } 520 return 0; 521 } 522