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
ms_fopen(const char * filename,const char * mode)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
ms_open(const char * filename,int oflag,int pmode)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
ParseQuality(const char * s,int * quality)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
ParseArgv(int argc,char ** argv,char ** input_path,char ** output_path,char ** dictionary_path,int * force,int * quality,int * decompress,int * repeat,int * verbose,int * lgwin,int * copy_stat)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
OpenInputFile(const char * input_path)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
OpenOutputFile(const char * output_path,const int force)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
FileSize(const char * path)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. */
CopyStat(const char * input_path,const char * output_path)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. */
ReadDictionary(const char * path,size_t * dictionary_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
Decompress(FILE * fin,FILE * fout,const char * dictionary_path)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
Compress(int quality,int lgwin,FILE * fin,FILE * fout,const char * dictionary_path)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
main(int argc,char ** argv)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