1 /* Copyright 2014 Google Inc. All Rights Reserved.
3 Distributed under MIT license.
4 See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
7 /* Command line interface for Brotli library. */
15 #include <sys/types.h>
18 #include "../common/constants.h"
19 #include "../common/version.h"
20 #include <brotli/decode.h>
21 #include <brotli/encode.h>
26 #define MAKE_BINARY(FILENO) (FILENO)
30 #include <sys/utime.h>
32 #define MAKE_BINARY(FILENO) (_setmode((FILENO), _O_BINARY), (FILENO))
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
41 #define fdopen _fdopen
42 #define isatty _isatty
43 #define unlink _unlink
44 #define utimbuf _utimbuf
47 #define fopen ms_fopen
50 #define chmod(F, P) (0)
51 #define chown(F, O, G) (0)
53 #if defined(_MSC_VER) && (_MSC_VER >= 1400)
54 #define fseek _fseeki64
55 #define ftell _ftelli64
58 static FILE* ms_fopen(const char* filename
, const char* mode
) {
60 fopen_s(&result
, filename
, mode
);
64 static int ms_open(const char* filename
, int oflag
, int pmode
) {
66 _sopen_s(&result
, filename
, oflag
| O_BINARY
, _SH_DENYNO
, pmode
);
76 COMMAND_TEST_INTEGRITY
,
81 #define DEFAULT_LGWIN 24
82 #define DEFAULT_SUFFIX ".br"
83 #define MAX_OPTIONS 20
84 #define DECODE_HEADER_SIZE 0x10
85 #define GAP_MEM_BLOCK 0x1000
92 BROTLI_BOOL force_overwrite
;
93 BROTLI_BOOL junk_source
;
94 BROTLI_BOOL copy_stat
;
96 BROTLI_BOOL write_to_stdout
;
97 BROTLI_BOOL test_integrity
;
98 BROTLI_BOOL decompress
;
99 BROTLI_BOOL large_window
;
100 const char* output_path
;
102 int not_input_indices
[MAX_OPTIONS
];
103 size_t longest_path_len
;
109 char* modified_path
; /* Storage for path with appended / cut suffix */
112 BROTLI_BOOL iterator_error
;
116 const char* current_input_path
;
117 const char* current_output_path
;
118 int64_t input_file_length
; /* -1, if impossible to calculate */
124 const uint8_t* next_in
;
125 size_t available_out
;
129 /* Parse up to 5 decimal digits. */
130 static BROTLI_BOOL
ParseInt(const char* s
, int low
, int high
, int* result
) {
133 for (i
= 0; i
< 5; ++i
) {
136 if (s
[i
] < '0' || s
[i
] > '9') return BROTLI_FALSE
;
137 value
= (10 * value
) + (c
- '0');
139 if (i
== 0) return BROTLI_FALSE
;
140 if (i
> 1 && s
[0] == '0') return BROTLI_FALSE
;
141 if (s
[i
] != 0) return BROTLI_FALSE
;
142 if (value
< low
|| value
> high
) return BROTLI_FALSE
;
147 /* Returns "base file name" or its tail, if it contains '/' or '\'. */
148 static const char* FileName(const char* path
) {
149 const char* separator_position
= strrchr(path
, '/');
150 if (separator_position
) path
= separator_position
+ 1;
151 separator_position
= strrchr(path
, '\\');
152 if (separator_position
) path
= separator_position
+ 1;
156 /* Detect if the program name is a special alias that infers a command type. */
157 static Command
ParseAlias(const char* name
) {
158 /* TODO: cast name to lower case? */
159 const char* unbrotli
= "unbrotli";
160 size_t unbrotli_len
= strlen(unbrotli
);
161 name
= FileName(name
);
162 /* Partial comparison. On Windows there could be ".exe" suffix. */
163 if (strncmp(name
, unbrotli
, unbrotli_len
) == 0) {
164 char terminator
= name
[unbrotli_len
];
165 if (terminator
== 0 || terminator
== '.') return COMMAND_DECOMPRESS
;
167 return COMMAND_COMPRESS
;
170 static Command
ParseParams(Context
* params
) {
171 int argc
= params
->argc
;
172 char** argv
= params
->argv
;
174 int next_option_index
= 0;
175 size_t input_count
= 0;
176 size_t longest_path_len
= 1;
177 BROTLI_BOOL command_set
= BROTLI_FALSE
;
178 BROTLI_BOOL gmem_set
= BROTLI_FALSE
;
179 BROTLI_BOOL quality_set
= BROTLI_FALSE
;
180 BROTLI_BOOL output_set
= BROTLI_FALSE
;
181 BROTLI_BOOL keep_set
= BROTLI_FALSE
;
182 BROTLI_BOOL lgwin_set
= BROTLI_FALSE
;
183 BROTLI_BOOL suffix_set
= BROTLI_FALSE
;
184 BROTLI_BOOL after_dash_dash
= BROTLI_FALSE
;
185 Command command
= ParseAlias(argv
[0]);
187 for (i
= 1; i
< argc
; ++i
) {
188 const char* arg
= argv
[i
];
189 /* C99 5.1.2.2.1: "members argv[0] through argv[argc-1] inclusive shall
190 contain pointers to strings"; NULL and 0-length are not forbidden. */
191 size_t arg_len
= arg
? strlen(arg
) : 0;
194 params
->not_input_indices
[next_option_index
++] = i
;
198 /* Too many options. The expected longest option list is:
199 "-q 0 -w 10 -o f -D d -S b -d -f -k -n -v --", i.e. 16 items in total.
200 This check is an additional guard that is never triggered, but provides
201 a guard for future changes. */
202 if (next_option_index
> (MAX_OPTIONS
- 2)) {
203 fprintf(stderr
, "too many options passed\n");
204 return COMMAND_INVALID
;
207 /* Input file entry. */
208 if (after_dash_dash
|| arg
[0] != '-' || arg_len
== 1) {
210 if (longest_path_len
< arg_len
) longest_path_len
= arg_len
;
214 /* Not a file entry. */
215 params
->not_input_indices
[next_option_index
++] = i
;
217 /* '--' entry stop parsing arguments. */
218 if (arg_len
== 2 && arg
[1] == '-') {
219 after_dash_dash
= BROTLI_TRUE
;
223 /* Simple / coalesced options. */
226 for (j
= 1; j
< arg_len
; ++j
) {
228 if (c
>= '0' && c
<= '9') {
230 fprintf(stderr
, "quality already set\n");
231 return COMMAND_INVALID
;
233 quality_set
= BROTLI_TRUE
;
234 params
->quality
= c
- '0';
236 } else if (c
== 'c') {
238 fprintf(stderr
, "write to standard output already set\n");
239 return COMMAND_INVALID
;
241 output_set
= BROTLI_TRUE
;
242 params
->write_to_stdout
= BROTLI_TRUE
;
244 } else if (c
== 'd') {
246 fprintf(stderr
, "command already set when parsing -d\n");
247 return COMMAND_INVALID
;
249 command_set
= BROTLI_TRUE
;
250 command
= COMMAND_DECOMPRESS
;
252 } else if (c
== 'f') {
253 if (params
->force_overwrite
) {
254 fprintf(stderr
, "force output overwrite already set\n");
255 return COMMAND_INVALID
;
257 params
->force_overwrite
= BROTLI_TRUE
;
259 } else if (c
== 'h') {
260 /* Don't parse further. */
262 } else if (c
== 'j' || c
== 'k') {
264 fprintf(stderr
, "argument --rm / -j or --keep / -n already set\n");
265 return COMMAND_INVALID
;
267 keep_set
= BROTLI_TRUE
;
268 params
->junk_source
= TO_BROTLI_BOOL(c
== 'j');
270 } else if (c
== 'n') {
271 if (!params
->copy_stat
) {
272 fprintf(stderr
, "argument --no-copy-stat / -n already set\n");
273 return COMMAND_INVALID
;
275 params
->copy_stat
= BROTLI_FALSE
;
277 } else if (c
== 't') {
279 fprintf(stderr
, "command already set when parsing -t\n");
280 return COMMAND_INVALID
;
282 command_set
= BROTLI_TRUE
;
283 command
= COMMAND_TEST_INTEGRITY
;
285 } else if (c
== 'v') {
286 if (params
->verbose
) {
287 fprintf(stderr
, "argument --verbose / -v already set\n");
288 return COMMAND_INVALID
;
290 params
->verbose
= BROTLI_TRUE
;
292 } else if (c
== 'V') {
293 /* Don't parse further. */
294 return COMMAND_VERSION
;
295 } else if (c
== 'Z') {
297 fprintf(stderr
, "quality already set\n");
298 return COMMAND_INVALID
;
300 quality_set
= BROTLI_TRUE
;
301 params
->quality
= 11;
304 /* o/q/w/D/S with parameter is expected */
305 if (c
!= 'o' && c
!= 'q' && c
!= 'w' && c
!= 'D' && c
!= 'S') {
306 fprintf(stderr
, "invalid argument -%c\n", c
);
307 return COMMAND_INVALID
;
309 if (j
+ 1 != arg_len
) {
310 fprintf(stderr
, "expected parameter for argument -%c\n", c
);
311 return COMMAND_INVALID
;
314 if (i
== argc
|| !argv
[i
] || argv
[i
][0] == 0) {
315 fprintf(stderr
, "expected parameter for argument -%c\n", c
);
316 return COMMAND_INVALID
;
318 params
->not_input_indices
[next_option_index
++] = i
;
321 fprintf(stderr
, "write to standard output already set (-o)\n");
322 return COMMAND_INVALID
;
324 params
->output_path
= argv
[i
];
325 } else if (c
== 'g') {
326 gmem_set
= ParseInt(argv
[i
], 1, 0x10, ¶ms
->gmem
);
328 fprintf(stderr
, "error parsing gmem value [%s]\n", argv
[i
]);
329 return COMMAND_INVALID
;
331 } else if (c
== 'q') {
333 fprintf(stderr
, "quality already set\n");
334 return COMMAND_INVALID
;
336 quality_set
= ParseInt(argv
[i
], BROTLI_MIN_QUALITY
,
337 BROTLI_MAX_QUALITY
, ¶ms
->quality
);
339 fprintf(stderr
, "error parsing quality value [%s]\n", argv
[i
]);
340 return COMMAND_INVALID
;
342 } else if (c
== 'w') {
344 fprintf(stderr
, "lgwin parameter already set\n");
345 return COMMAND_INVALID
;
347 lgwin_set
= ParseInt(argv
[i
], 0,
348 BROTLI_MAX_WINDOW_BITS
, ¶ms
->lgwin
);
350 fprintf(stderr
, "error parsing lgwin value [%s]\n", argv
[i
]);
351 return COMMAND_INVALID
;
353 if (params
->lgwin
!= 0 && params
->lgwin
< BROTLI_MIN_WINDOW_BITS
) {
355 "lgwin parameter (%d) smaller than the minimum (%d)\n",
356 params
->lgwin
, BROTLI_MIN_WINDOW_BITS
);
357 return COMMAND_INVALID
;
359 } else if (c
== 'S') {
361 fprintf(stderr
, "suffix already set\n");
362 return COMMAND_INVALID
;
364 suffix_set
= BROTLI_TRUE
;
365 params
->suffix
= argv
[i
];
368 } else { /* Double-dash. */
370 if (strcmp("best", arg
) == 0) {
372 fprintf(stderr
, "quality already set\n");
373 return COMMAND_INVALID
;
375 quality_set
= BROTLI_TRUE
;
376 params
->quality
= 11;
377 } else if (strcmp("decompress", arg
) == 0) {
379 fprintf(stderr
, "command already set when parsing --decompress\n");
380 return COMMAND_INVALID
;
382 command_set
= BROTLI_TRUE
;
383 command
= COMMAND_DECOMPRESS
;
384 } else if (strcmp("force", arg
) == 0) {
385 if (params
->force_overwrite
) {
386 fprintf(stderr
, "force output overwrite already set\n");
387 return COMMAND_INVALID
;
389 params
->force_overwrite
= BROTLI_TRUE
;
390 } else if (strcmp("help", arg
) == 0) {
391 /* Don't parse further. */
393 } else if (strcmp("keep", arg
) == 0) {
395 fprintf(stderr
, "argument --rm / -j or --keep / -n already set\n");
396 return COMMAND_INVALID
;
398 keep_set
= BROTLI_TRUE
;
399 params
->junk_source
= BROTLI_FALSE
;
400 } else if (strcmp("no-copy-stat", arg
) == 0) {
401 if (!params
->copy_stat
) {
402 fprintf(stderr
, "argument --no-copy-stat / -n already set\n");
403 return COMMAND_INVALID
;
405 params
->copy_stat
= BROTLI_FALSE
;
406 } else if (strcmp("rm", arg
) == 0) {
408 fprintf(stderr
, "argument --rm / -j or --keep / -n already set\n");
409 return COMMAND_INVALID
;
411 keep_set
= BROTLI_TRUE
;
412 params
->junk_source
= BROTLI_TRUE
;
413 } else if (strcmp("stdout", arg
) == 0) {
415 fprintf(stderr
, "write to standard output already set\n");
416 return COMMAND_INVALID
;
418 output_set
= BROTLI_TRUE
;
419 params
->write_to_stdout
= BROTLI_TRUE
;
420 } else if (strcmp("test", arg
) == 0) {
422 fprintf(stderr
, "command already set when parsing --test\n");
423 return COMMAND_INVALID
;
425 command_set
= BROTLI_TRUE
;
426 command
= COMMAND_TEST_INTEGRITY
;
427 } else if (strcmp("verbose", arg
) == 0) {
428 if (params
->verbose
) {
429 fprintf(stderr
, "argument --verbose / -v already set\n");
430 return COMMAND_INVALID
;
432 params
->verbose
= BROTLI_TRUE
;
433 } else if (strcmp("version", arg
) == 0) {
434 /* Don't parse further. */
435 return COMMAND_VERSION
;
438 const char* value
= strrchr(arg
, '=');
440 if (!value
|| value
[1] == 0) {
441 fprintf(stderr
, "must pass the parameter as --%s=value\n", arg
);
442 return COMMAND_INVALID
;
444 key_len
= (size_t)(value
- arg
);
446 if (strncmp("lgwin", arg
, key_len
) == 0) {
448 fprintf(stderr
, "lgwin parameter already set\n");
449 return COMMAND_INVALID
;
451 lgwin_set
= ParseInt(value
, 0,
452 BROTLI_MAX_WINDOW_BITS
, ¶ms
->lgwin
);
454 fprintf(stderr
, "error parsing lgwin value [%s]\n", value
);
455 return COMMAND_INVALID
;
457 if (params
->lgwin
!= 0 && params
->lgwin
< BROTLI_MIN_WINDOW_BITS
) {
459 "lgwin parameter (%d) smaller than the minimum (%d)\n",
460 params
->lgwin
, BROTLI_MIN_WINDOW_BITS
);
461 return COMMAND_INVALID
;
463 } else if (strncmp("large_window", arg
, key_len
) == 0) {
464 /* This option is intentionally not mentioned in help. */
466 fprintf(stderr
, "lgwin parameter already set\n");
467 return COMMAND_INVALID
;
469 lgwin_set
= ParseInt(value
, 0,
470 BROTLI_LARGE_MAX_WINDOW_BITS
, ¶ms
->lgwin
);
472 fprintf(stderr
, "error parsing lgwin value [%s]\n", value
);
473 return COMMAND_INVALID
;
475 if (params
->lgwin
!= 0 && params
->lgwin
< BROTLI_MIN_WINDOW_BITS
) {
477 "lgwin parameter (%d) smaller than the minimum (%d)\n",
478 params
->lgwin
, BROTLI_MIN_WINDOW_BITS
);
479 return COMMAND_INVALID
;
481 } else if (strncmp("output", arg
, key_len
) == 0) {
484 "write to standard output already set (--output)\n");
485 return COMMAND_INVALID
;
487 params
->output_path
= value
;
488 } else if (strncmp("gap", arg
, key_len
) == 0) {
489 gmem_set
= ParseInt(value
, 1, 0x10, ¶ms
->gmem
);
491 fprintf(stderr
, "error parsing gmem value [%s]\n", value
);
492 return COMMAND_INVALID
;
494 } else if (strncmp("quality", arg
, key_len
) == 0) {
496 fprintf(stderr
, "quality already set\n");
497 return COMMAND_INVALID
;
499 quality_set
= ParseInt(value
, BROTLI_MIN_QUALITY
,
500 BROTLI_MAX_QUALITY
, ¶ms
->quality
);
502 fprintf(stderr
, "error parsing quality value [%s]\n", value
);
503 return COMMAND_INVALID
;
505 } else if (strncmp("suffix", arg
, key_len
) == 0) {
507 fprintf(stderr
, "suffix already set\n");
508 return COMMAND_INVALID
;
510 suffix_set
= BROTLI_TRUE
;
511 params
->suffix
= value
;
513 fprintf(stderr
, "invalid parameter: [%s]\n", arg
);
514 return COMMAND_INVALID
;
520 params
->input_count
= input_count
;
521 params
->longest_path_len
= longest_path_len
;
522 params
->decompress
= (command
== COMMAND_DECOMPRESS
);
523 params
->test_integrity
= (command
== COMMAND_TEST_INTEGRITY
);
525 if (input_count
> 1 && output_set
) return COMMAND_INVALID
;
526 if (params
->test_integrity
) {
527 if (params
->output_path
) return COMMAND_INVALID
;
528 if (params
->write_to_stdout
) return COMMAND_INVALID
;
530 if (strchr(params
->suffix
, '/') || strchr(params
->suffix
, '\\')) {
531 return COMMAND_INVALID
;
537 static void PrintVersion(void) {
538 int major
= BROTLI_VERSION
>> 24;
539 int minor
= (BROTLI_VERSION
>> 12) & 0xFFF;
540 int patch
= BROTLI_VERSION
& 0xFFF;
541 fprintf(stdout
, "brotli %d.%d.%d\n", major
, minor
, patch
);
544 static void PrintHelp(const char* name
, BROTLI_BOOL error
) {
545 FILE* media
= error
? stderr
: stdout
;
546 /* String is cut to pieces with length less than 509, to conform C90 spec. */
548 "Usage: %s [OPTION]... [FILE]...\n",
552 " -# compression level (0-9)\n"
553 " -c, --stdout write on standard output\n"
554 " -d, --decompress decompress\n"
555 " -f, --force force output file overwrite\n"
556 " -h, --help display this help and exit\n");
558 " -j, --rm remove source file(s)\n"
559 " -k, --keep keep source file(s) (default)\n"
560 " -n, --no-copy-stat do not copy source file(s) attributes\n"
561 " -o FILE, --output=FILE output file (only if 1 input file)\n");
563 " -g NUM, --gap=NUM scratch memory gap level (1-16)\n");
565 " -q NUM, --quality=NUM compression level (%d-%d)\n",
566 BROTLI_MIN_QUALITY
, BROTLI_MAX_QUALITY
);
568 " -t, --test test compressed file integrity\n"
569 " -v, --verbose verbose mode\n");
571 " -w NUM, --lgwin=NUM set LZ77 window size (0, %d-%d)\n",
572 BROTLI_MIN_WINDOW_BITS
, BROTLI_MAX_WINDOW_BITS
);
574 " window size = 2**NUM - 16\n"
575 " 0 lets compressor choose the optimal value\n");
577 " -S SUF, --suffix=SUF output file suffix (default:'%s')\n",
580 " -V, --version display version and exit\n"
581 " -Z, --best use best compression level (11) (default)\n"
582 "Simple options could be coalesced, i.e. '-9kf' is equivalent to '-9 -k -f'.\n"
583 "With no FILE, or when FILE is -, read standard input.\n"
584 "All arguments after '--' are treated as files.\n");
587 static const char* PrintablePath(const char* path
) {
588 return path
? path
: "con";
591 static BROTLI_BOOL
OpenInputFile(const char* input_path
, FILE** f
) {
594 *f
= fdopen(MAKE_BINARY(STDIN_FILENO
), "rb");
597 *f
= fopen(input_path
, "rb");
599 fprintf(stderr
, "failed to open input file [%s]: %s\n",
600 PrintablePath(input_path
), strerror(errno
));
606 static BROTLI_BOOL
OpenOutputFile(const char* output_path
, FILE** f
,
611 *f
= fdopen(MAKE_BINARY(STDOUT_FILENO
), "wb");
614 fd
= open(output_path
, O_CREAT
| (force
? 0 : O_EXCL
) | O_WRONLY
| O_TRUNC
,
617 fd
= open(output_path
, (force
? 0 : O_EXCL
) | O_WRONLY
| O_TRUNC
,
620 fprintf(stderr
, "failed to open output file [%s]: %s\n",
621 PrintablePath(output_path
), strerror(errno
));
626 *f
= fdopen(fd
, "wb");
628 fprintf(stderr
, "failed to open output file [%s]: %s\n",
629 PrintablePath(output_path
), strerror(errno
));
635 static int64_t FileSize(const char* path
) {
636 FILE* f
= fopen(path
, "rb");
641 if (fseek(f
, 0L, SEEK_END
) != 0) {
646 if (fclose(f
) != 0) {
652 /* Copy file times and permissions.
653 TODO: this is a "best effort" implementation; honest cross-platform
654 fully featured implementation is way too hacky; add more hacks by request. */
655 static void CopyStat(const char* input_path
, const char* output_path
) {
657 struct utimbuf times
;
659 if (input_path
== 0 || output_path
== 0) {
662 if (stat(input_path
, &statbuf
) != 0) {
665 times
.actime
= statbuf
.st_atime
;
666 times
.modtime
= statbuf
.st_mtime
;
667 utime(output_path
, ×
);
668 res
= chmod(output_path
, statbuf
.st_mode
& (S_IRWXU
| S_IRWXG
| S_IRWXO
));
670 fprintf(stderr
, "setting access bits failed for [%s]: %s\n",
671 PrintablePath(output_path
), strerror(errno
));
673 res
= chown(output_path
, (uid_t
)-1, statbuf
.st_gid
);
675 fprintf(stderr
, "setting group failed for [%s]: %s\n",
676 PrintablePath(output_path
), strerror(errno
));
678 res
= chown(output_path
, statbuf
.st_uid
, (gid_t
)-1);
680 fprintf(stderr
, "setting user failed for [%s]: %s\n",
681 PrintablePath(output_path
), strerror(errno
));
685 int64_t input_file_length
= 0;
687 static BROTLI_BOOL
NextFile(Context
* context
) {
691 /* Iterator points to last used arg; increment to search for the next one. */
694 context
->input_file_length
= -1;
696 /* No input path; read from console. */
697 if (context
->input_count
== 0) {
698 if (context
->iterator
> 1) return BROTLI_FALSE
;
699 context
->current_input_path
= NULL
;
700 /* Either write to the specified path, or to console. */
701 context
->current_output_path
= context
->output_path
;
705 /* Skip option arguments. */
706 while (context
->iterator
== context
->not_input_indices
[context
->ignore
]) {
711 /* All args are scanned already. */
712 if (context
->iterator
>= context
->argc
) return BROTLI_FALSE
;
714 /* Iterator now points to the input file name. */
715 arg
= context
->argv
[context
->iterator
];
716 arg_len
= strlen(arg
);
717 /* Read from console. */
718 if (arg_len
== 1 && arg
[0] == '-') {
719 context
->current_input_path
= NULL
;
720 context
->current_output_path
= context
->output_path
;
724 if (context
->current_input_path
== NULL
) {
725 context
->current_input_path
= arg
;
727 context
->input_file_length
= FileSize(context
->current_input_path
);
728 context
->current_output_path
= context
->output_path
;
729 if (!context
->decompress
) {
730 input_file_length
+= context
->input_file_length
;
733 if (context
->output_path
) return BROTLI_TRUE
;
734 if (context
->write_to_stdout
) return BROTLI_TRUE
;
736 strcpy(context
->modified_path
, arg
);
737 context
->current_output_path
= context
->modified_path
;
738 /* If output is not specified, input path suffix should match. */
739 if (context
->decompress
) {
740 size_t suffix_len
= strlen(context
->suffix
);
741 char* name
= (char*)FileName(context
->modified_path
);
743 size_t name_len
= strlen(name
);
744 if (name_len
< suffix_len
+ 1) {
745 fprintf(stderr
, "empty output file name for [%s] input file\n",
747 context
->iterator_error
= BROTLI_TRUE
;
750 name_suffix
= name
+ name_len
- suffix_len
;
751 if (strcmp(context
->suffix
, name_suffix
) != 0) {
752 fprintf(stderr
, "input file [%s] suffix mismatch\n",
754 context
->iterator_error
= BROTLI_TRUE
;
760 strcpy(context
->modified_path
+ arg_len
, context
->suffix
);
765 static BROTLI_BOOL
OpenFiles(Context
* context
) {
766 BROTLI_BOOL is_ok
= OpenInputFile(context
->current_input_path
, &context
->fin
);
767 if (context
->decompress
) {
769 // skip the decoder data header
771 fseek(context
->fin
, DECODE_HEADER_SIZE
, SEEK_SET
);
773 if (!context
->test_integrity
&& is_ok
&& context
->fout
== NULL
) {
774 is_ok
= OpenOutputFile(
775 context
->current_output_path
, &context
->fout
, context
->force_overwrite
);
777 if (!context
->decompress
) {
779 // append the decoder data header
781 fseek(context
->fout
, DECODE_HEADER_SIZE
, SEEK_SET
);
786 static BROTLI_BOOL
CloseFiles(Context
* context
, BROTLI_BOOL success
) {
787 BROTLI_BOOL is_ok
= BROTLI_TRUE
;
788 if (!context
->test_integrity
&& context
->fout
) {
789 if (!success
&& context
->current_output_path
) {
790 unlink(context
->current_output_path
);
792 if (fclose(context
->fout
) != 0) {
794 fprintf(stderr
, "fclose failed [%s]: %s\n",
795 PrintablePath(context
->current_output_path
), strerror(errno
));
797 is_ok
= BROTLI_FALSE
;
800 /* TOCTOU violation, but otherwise it is impossible to set file times. */
801 if (success
&& is_ok
&& context
->copy_stat
) {
802 CopyStat(context
->current_input_path
, context
->current_output_path
);
807 if (fclose(context
->fin
) != 0) {
809 fprintf(stderr
, "fclose failed [%s]: %s\n",
810 PrintablePath(context
->current_input_path
), strerror(errno
));
812 is_ok
= BROTLI_FALSE
;
815 if (success
&& context
->junk_source
&& context
->current_input_path
) {
816 unlink(context
->current_input_path
);
820 context
->fout
= NULL
;
825 static const size_t kFileBufferSize
= 1 << 16;
827 static void InitializeBuffers(Context
* context
) {
828 context
->available_in
= 0;
829 context
->next_in
= NULL
;
830 context
->available_out
= kFileBufferSize
;
831 context
->next_out
= context
->output
;
834 static BROTLI_BOOL
HasMoreInput(Context
* context
) {
835 return feof(context
->fin
) ? BROTLI_FALSE
: BROTLI_TRUE
;
838 static BROTLI_BOOL
ProvideInput(Context
* context
) {
839 context
->available_in
=
840 fread(context
->input
, 1, kFileBufferSize
, context
->fin
);
841 context
->next_in
= context
->input
;
842 if (ferror(context
->fin
)) {
843 fprintf(stderr
, "failed to read input [%s]: %s\n",
844 PrintablePath(context
->current_input_path
), strerror(errno
));
850 /* Internal: should be used only in Provide-/Flush-Output. */
851 static BROTLI_BOOL
WriteOutput(Context
* context
) {
852 size_t out_size
= (size_t)(context
->next_out
- context
->output
);
853 if (out_size
== 0) return BROTLI_TRUE
;
854 if (context
->test_integrity
) return BROTLI_TRUE
;
856 fwrite(context
->output
, 1, out_size
, context
->fout
);
857 if (ferror(context
->fout
)) {
858 fprintf(stderr
, "failed to write output [%s]: %s\n",
859 PrintablePath(context
->current_output_path
), strerror(errno
));
865 static BROTLI_BOOL
ProvideOutput(Context
* context
) {
866 if (!WriteOutput(context
)) return BROTLI_FALSE
;
867 context
->available_out
= kFileBufferSize
;
868 context
->next_out
= context
->output
;
872 static BROTLI_BOOL
FlushOutput(Context
* context
) {
873 if (!WriteOutput(context
)) return BROTLI_FALSE
;
874 context
->available_out
= 0;
878 static BROTLI_BOOL
DecompressFile(Context
* context
, BrotliDecoderState
* s
) {
879 BrotliDecoderResult result
= BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT
;
880 InitializeBuffers(context
);
882 if (result
== BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT
) {
883 if (!HasMoreInput(context
)) {
884 fprintf(stderr
, "corrupt input [%s]\n",
885 PrintablePath(context
->current_input_path
));
888 if (!ProvideInput(context
)) return BROTLI_FALSE
;
889 } else if (result
== BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT
) {
890 if (!ProvideOutput(context
)) return BROTLI_FALSE
;
891 } else if (result
== BROTLI_DECODER_RESULT_SUCCESS
) {
892 if (!FlushOutput(context
)) return BROTLI_FALSE
;
893 if (context
->available_in
!= 0 || HasMoreInput(context
)) {
894 fprintf(stderr
, "corrupt input [%s]\n",
895 PrintablePath(context
->current_input_path
));
900 fprintf(stderr
, "corrupt input [%s]\n",
901 PrintablePath(context
->current_input_path
));
905 result
= BrotliDecoderDecompressStream(s
, &context
->available_in
,
906 &context
->next_in
, &context
->available_out
, &context
->next_out
, 0);
910 /* Default brotli_alloc_func */
911 void* BrotliAllocFunc(void* opaque
, size_t size
) {
912 *(size_t *)opaque
= *(size_t *) opaque
+ size
;
916 /* Default brotli_free_func */
917 void BrotliFreeFunc(void* opaque
, void* address
) {
921 size_t scratch_buffer_size
= 0;
923 static BROTLI_BOOL
DecompressFiles(Context
* context
) {
924 while (NextFile(context
)) {
925 BROTLI_BOOL is_ok
= BROTLI_TRUE
;
926 BrotliDecoderState
* s
= BrotliDecoderCreateInstance(BrotliAllocFunc
, BrotliFreeFunc
, &scratch_buffer_size
);
928 fprintf(stderr
, "out of memory\n");
931 /* This allows decoding "large-window" streams. Though it creates
932 fragmentation (new builds decode streams that old builds don't),
933 it is better from used experience perspective. */
934 BrotliDecoderSetParameter(s
, BROTLI_DECODER_PARAM_LARGE_WINDOW
, 1u);
935 is_ok
= OpenFiles(context
);
936 if (is_ok
&& !context
->current_input_path
&&
937 !context
->force_overwrite
&& isatty(STDIN_FILENO
)) {
938 fprintf(stderr
, "Use -h help. Use -f to force input from a terminal.\n");
939 is_ok
= BROTLI_FALSE
;
941 if (is_ok
) is_ok
= DecompressFile(context
, s
);
942 BrotliDecoderDestroyInstance(s
);
943 if (!CloseFiles(context
, is_ok
)) is_ok
= BROTLI_FALSE
;
944 if (!is_ok
) return BROTLI_FALSE
;
949 static BROTLI_BOOL
CompressFile(Context
* context
, BrotliEncoderState
* s
) {
950 BROTLI_BOOL is_eof
= BROTLI_FALSE
;
951 InitializeBuffers(context
);
953 if (context
->available_in
== 0 && !is_eof
) {
954 if (!ProvideInput(context
)) return BROTLI_FALSE
;
955 is_eof
= !HasMoreInput(context
);
958 if (!BrotliEncoderCompressStream(s
,
959 is_eof
? BROTLI_OPERATION_FINISH
: BROTLI_OPERATION_PROCESS
,
960 &context
->available_in
, &context
->next_in
,
961 &context
->available_out
, &context
->next_out
, NULL
)) {
962 /* Should detect OOM? */
963 fprintf(stderr
, "failed to compress data [%s]\n",
964 PrintablePath(context
->current_input_path
));
968 if (context
->available_out
== 0) {
969 if (!ProvideOutput(context
)) return BROTLI_FALSE
;
972 if (BrotliEncoderIsFinished(s
)) {
973 return FlushOutput(context
);
978 static BROTLI_BOOL
CompressFiles(Context
* context
) {
979 while (NextFile(context
)) {
980 BROTLI_BOOL is_ok
= BROTLI_TRUE
;
981 BrotliEncoderState
* s
= BrotliEncoderCreateInstance(NULL
, NULL
, NULL
);
983 fprintf(stderr
, "out of memory\n");
986 BrotliEncoderSetParameter(s
,
987 BROTLI_PARAM_QUALITY
, (uint32_t)context
->quality
);
988 if (context
->lgwin
> 0) {
989 /* Specified by user. */
990 /* Do not enable "large-window" extension, if not required. */
991 if (context
->lgwin
> BROTLI_MAX_WINDOW_BITS
) {
992 BrotliEncoderSetParameter(s
, BROTLI_PARAM_LARGE_WINDOW
, 1u);
994 BrotliEncoderSetParameter(s
,
995 BROTLI_PARAM_LGWIN
, (uint32_t)context
->lgwin
);
997 /* 0, or not specified by user; could be chosen by compressor. */
998 uint32_t lgwin
= DEFAULT_LGWIN
;
999 /* Use file size to limit lgwin. */
1000 if (context
->input_file_length
>= 0) {
1001 int32_t size
= 1 << BROTLI_MIN_WINDOW_BITS
;
1002 lgwin
= BROTLI_MIN_WINDOW_BITS
;
1003 while (size
< context
->input_file_length
) {
1006 if (lgwin
== BROTLI_MAX_WINDOW_BITS
) break;
1009 BrotliEncoderSetParameter(s
, BROTLI_PARAM_LGWIN
, lgwin
);
1011 if (context
->input_file_length
> 0) {
1012 uint32_t size_hint
= context
->input_file_length
< (1 << 30) ?
1013 (uint32_t)context
->input_file_length
: (1u << 30);
1014 BrotliEncoderSetParameter(s
, BROTLI_PARAM_SIZE_HINT
, size_hint
);
1016 is_ok
= OpenFiles(context
);
1017 if (is_ok
&& !context
->current_output_path
&&
1018 !context
->force_overwrite
&& isatty(STDOUT_FILENO
)) {
1019 fprintf(stderr
, "Use -h help. Use -f to force output to a terminal.\n");
1020 is_ok
= BROTLI_FALSE
;
1022 if (is_ok
) is_ok
= CompressFile(context
, s
);
1023 BrotliEncoderDestroyInstance(s
);
1024 if (!CloseFiles(context
, is_ok
)) is_ok
= BROTLI_FALSE
;
1025 if (!is_ok
) return BROTLI_FALSE
;
1030 int main(int argc
, char** argv
) {
1033 Context context_dec
;
1034 BROTLI_BOOL is_ok
= BROTLI_TRUE
;
1037 context
.quality
= 11;
1040 context
.force_overwrite
= BROTLI_FALSE
;
1041 context
.junk_source
= BROTLI_FALSE
;
1042 context
.copy_stat
= BROTLI_TRUE
;
1043 context
.test_integrity
= BROTLI_FALSE
;
1044 context
.verbose
= BROTLI_FALSE
;
1045 context
.write_to_stdout
= BROTLI_FALSE
;
1046 context
.decompress
= BROTLI_FALSE
;
1047 context
.large_window
= BROTLI_FALSE
;
1048 context
.output_path
= NULL
;
1049 context
.suffix
= DEFAULT_SUFFIX
;
1050 for (i
= 0; i
< MAX_OPTIONS
; ++i
) context
.not_input_indices
[i
] = 0;
1051 context
.longest_path_len
= 1;
1052 context
.input_count
= 0;
1054 context
.argc
= argc
;
1055 context
.argv
= argv
;
1056 context
.modified_path
= NULL
;
1057 context
.iterator
= 0;
1059 context
.iterator_error
= BROTLI_FALSE
;
1060 context
.buffer
= NULL
;
1061 context
.current_input_path
= NULL
;
1062 context
.current_output_path
= NULL
;
1064 context
.fout
= NULL
;
1066 command
= ParseParams(&context
);
1068 if (command
== COMMAND_COMPRESS
|| command
== COMMAND_DECOMPRESS
||
1069 command
== COMMAND_TEST_INTEGRITY
) {
1071 size_t modified_path_len
=
1072 context
.longest_path_len
+ strlen(context
.suffix
) + 1;
1073 context
.modified_path
= (char*)malloc(modified_path_len
);
1074 context
.buffer
= (uint8_t*)malloc(kFileBufferSize
* 2);
1075 if (!context
.modified_path
|| !context
.buffer
) {
1076 fprintf(stderr
, "out of memory\n");
1077 is_ok
= BROTLI_FALSE
;
1079 context
.input
= context
.buffer
;
1080 context
.output
= context
.buffer
+ kFileBufferSize
;
1085 if (!is_ok
) command
= COMMAND_NOOP
;
1091 case COMMAND_VERSION
:
1095 case COMMAND_COMPRESS
:
1096 memcpy (&context_dec
, &context
, sizeof (context
));
1097 is_ok
= CompressFiles(&context
);
1101 context_dec
.decompress
= BROTLI_TRUE
;
1102 context_dec
.input_count
= 1;
1103 context_dec
.current_input_path
= context_dec
.output_path
;
1104 context_dec
.fout
= tmpfile ();
1105 is_ok
= DecompressFiles(&context_dec
);
1110 // fill decoder header
1112 context_dec
.fout
= fopen(context_dec
.output_path
, "rb+"); /* open output_path file and add in head info */
1113 fwrite(&input_file_length
, 1, sizeof(int64_t), context_dec
.fout
);
1114 scratch_buffer_size
+= context
.gmem
* GAP_MEM_BLOCK
; /* there is a memory gap between IA32 and X64 environment*/
1115 scratch_buffer_size
+= kFileBufferSize
* 2;
1116 fwrite(&scratch_buffer_size
, 1, sizeof(int64_t), context_dec
.fout
);
1117 if (fclose(context_dec
.fout
) != 0) {
1118 fprintf(stderr
, "failed to update ouptut file: %s\n", context_dec
.output_path
);
1123 case COMMAND_DECOMPRESS
:
1124 case COMMAND_TEST_INTEGRITY
:
1125 is_ok
= DecompressFiles(&context
);
1129 case COMMAND_INVALID
:
1131 is_ok
= (command
== COMMAND_HELP
);
1132 PrintHelp(FileName(argv
[0]), is_ok
);
1136 if (context
.iterator_error
) is_ok
= BROTLI_FALSE
;
1138 free(context
.modified_path
);
1139 free(context
.buffer
);
1141 if (!is_ok
) exit(1);