• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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, &times);
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, &copy_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