diff --git a/CMakeLists.txt b/CMakeLists.txt index 61378cdae..bf5204d00 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,7 +61,6 @@ endfunction(read_macro) read_macro("c/common/version.h" "BROTLI_VERSION_MAJOR" BROTLI_VERSION_MAJOR) read_macro("c/common/version.h" "BROTLI_VERSION_MINOR" BROTLI_VERSION_MINOR) read_macro("c/common/version.h" "BROTLI_VERSION_PATCH" BROTLI_VERSION_PATCH) -set(BROTLI_VERSION "${BROTLI_VERSION_MAJOR}.${BROTLI_VERSION_MINOR}.${BROTLI_VERSION_PATCH}") mark_as_advanced(BROTLI_VERSION BROTLI_VERSION_MAJOR BROTLI_VERSION_MINOR BROTLI_VERSION_PATCH) # ABI Version information diff --git a/c/tools/brotli.c b/c/tools/brotli.c index f7e531eb7..432fa5237 100644 --- a/c/tools/brotli.c +++ b/c/tools/brotli.c @@ -22,6 +22,7 @@ #include #include +#include #include "../common/constants.h" #include "../common/version.h" @@ -107,6 +108,7 @@ typedef struct { int verbosity; BROTLI_BOOL force_overwrite; BROTLI_BOOL junk_source; + BROTLI_BOOL reject_uncompressible; BROTLI_BOOL copy_stat; BROTLI_BOOL write_to_stdout; BROTLI_BOOL test_integrity; @@ -205,6 +207,7 @@ static Command ParseParams(Context* params) { BROTLI_BOOL quality_set = BROTLI_FALSE; BROTLI_BOOL output_set = BROTLI_FALSE; BROTLI_BOOL keep_set = BROTLI_FALSE; + BROTLI_BOOL squash_set = BROTLI_FALSE; BROTLI_BOOL lgwin_set = BROTLI_FALSE; BROTLI_BOOL suffix_set = BROTLI_FALSE; BROTLI_BOOL after_dash_dash = BROTLI_FALSE; @@ -300,6 +303,14 @@ static Command ParseParams(Context* params) { } params->copy_stat = BROTLI_FALSE; continue; + } else if (c == 's') { + if (squash_set) { + fprintf(stderr, "argument --squash / -s already set\n"); + return COMMAND_INVALID; + } + squash_set = BROTLI_TRUE; + params->reject_uncompressible = BROTLI_TRUE; + continue; } else if (c == 't') { if (command_set) { fprintf(stderr, "command already set when parsing -t\n"); @@ -436,6 +447,14 @@ static Command ParseParams(Context* params) { } keep_set = BROTLI_TRUE; params->junk_source = BROTLI_TRUE; + } else if (strcmp("squash", arg) == 0) { + if (squash_set) { + fprintf(stderr, "argument --squash / -s already set\n"); + return COMMAND_INVALID; + } + squash_set = BROTLI_TRUE; + params->reject_uncompressible = BROTLI_TRUE; + continue; } else if (strcmp("stdout", arg) == 0) { if (output_set) { fprintf(stderr, "write to standard output already set\n"); @@ -553,6 +572,9 @@ static Command ParseParams(Context* params) { if (params->output_path) return COMMAND_INVALID; if (params->write_to_stdout) return COMMAND_INVALID; } + if (params->reject_uncompressible && params->write_to_stdout) { + return COMMAND_INVALID; + } if (strchr(params->suffix, '/') || strchr(params->suffix, '\\')) { return COMMAND_INVALID; } @@ -582,6 +604,7 @@ static void PrintHelp(const char* name, BROTLI_BOOL error) { " -h, --help display this help and exit\n"); fprintf(media, " -j, --rm remove source file(s)\n" +" -s, --squash remove destination file if larger than source\n" " -k, --keep keep source file(s) (default)\n" " -n, --no-copy-stat do not copy source file(s) attributes\n" " -o FILE, --output=FILE output file (only if 1 input file)\n"); @@ -867,22 +890,23 @@ static BROTLI_BOOL OpenFiles(Context* context) { return is_ok; } -static BROTLI_BOOL CloseFiles(Context* context, BROTLI_BOOL success) { +static BROTLI_BOOL CloseFiles(Context* context, BROTLI_BOOL rm_input, + BROTLI_BOOL rm_output) { BROTLI_BOOL is_ok = BROTLI_TRUE; if (!context->test_integrity && context->fout) { - if (!success && context->current_output_path) { - unlink(context->current_output_path); - } if (fclose(context->fout) != 0) { - if (success) { + if (is_ok) { fprintf(stderr, "fclose failed [%s]: %s\n", PrintablePath(context->current_output_path), strerror(errno)); } is_ok = BROTLI_FALSE; } + if (rm_output && context->current_output_path) { + unlink(context->current_output_path); + } /* TOCTOU violation, but otherwise it is impossible to set file times. */ - if (success && is_ok && context->copy_stat) { + if (!rm_output && is_ok && context->copy_stat) { CopyStat(context->current_input_path, context->current_output_path); } } @@ -896,7 +920,7 @@ static BROTLI_BOOL CloseFiles(Context* context, BROTLI_BOOL success) { is_ok = BROTLI_FALSE; } } - if (success && context->junk_source && context->current_input_path) { + if (rm_input && context->current_input_path) { unlink(context->current_input_path); } @@ -1058,6 +1082,8 @@ static BROTLI_BOOL DecompressFile(Context* context, BrotliDecoderState* s) { static BROTLI_BOOL DecompressFiles(Context* context) { while (NextFile(context)) { BROTLI_BOOL is_ok = BROTLI_TRUE; + BROTLI_BOOL rm_input = BROTLI_FALSE; + BROTLI_BOOL rm_output = BROTLI_TRUE; BrotliDecoderState* s = BrotliDecoderCreateInstance(NULL, NULL, NULL); if (!s) { fprintf(stderr, "out of memory\n"); @@ -1079,7 +1105,9 @@ static BROTLI_BOOL DecompressFiles(Context* context) { } if (is_ok) is_ok = DecompressFile(context, s); BrotliDecoderDestroyInstance(s); - if (!CloseFiles(context, is_ok)) is_ok = BROTLI_FALSE; + rm_output = !is_ok; + rm_input = !rm_output && context->junk_source; + if (!CloseFiles(context, rm_input, rm_output)) is_ok = BROTLI_FALSE; if (!is_ok) return BROTLI_FALSE; } return BROTLI_TRUE; @@ -1124,6 +1152,8 @@ static BROTLI_BOOL CompressFile(Context* context, BrotliEncoderState* s) { static BROTLI_BOOL CompressFiles(Context* context) { while (NextFile(context)) { BROTLI_BOOL is_ok = BROTLI_TRUE; + BROTLI_BOOL rm_input = BROTLI_FALSE; + BROTLI_BOOL rm_output = BROTLI_TRUE; BrotliEncoderState* s = BrotliEncoderCreateInstance(NULL, NULL, NULL); if (!s) { fprintf(stderr, "out of memory\n"); @@ -1169,7 +1199,17 @@ static BROTLI_BOOL CompressFiles(Context* context) { } if (is_ok) is_ok = CompressFile(context, s); BrotliEncoderDestroyInstance(s); - if (!CloseFiles(context, is_ok)) is_ok = BROTLI_FALSE; + rm_output = !is_ok; + if (is_ok && context->reject_uncompressible) { + if (context->total_out >= context->total_in) { + rm_output = BROTLI_TRUE; + if (context->verbosity > 0) { + fprintf(stderr, "Output is larger than input\n"); + } + } + } + rm_input = !rm_output && context->junk_source; + if (!CloseFiles(context, rm_input, rm_output)) is_ok = BROTLI_FALSE; if (!is_ok) return BROTLI_FALSE; } return BROTLI_TRUE; @@ -1186,6 +1226,7 @@ int main(int argc, char** argv) { context.verbosity = 0; context.force_overwrite = BROTLI_FALSE; context.junk_source = BROTLI_FALSE; + context.reject_uncompressible = BROTLI_FALSE; context.copy_stat = BROTLI_TRUE; context.test_integrity = BROTLI_FALSE; context.write_to_stdout = BROTLI_FALSE;