| /** @file | |
| BrotliCompress Compress/Decompress tool (BrotliCompress) | |
| Copyright (c) 2020, ByoSoft Corporation. All rights reserved.<BR> | |
| Copyright (c) 2025, Intel Corporation. All rights reserved.<BR> | |
| SPDX-License-Identifier: BSD-2-Clause-Patent | |
| **/ | |
| /* Command line interface for Brotli library. */ | |
| #include <errno.h> | |
| #include <fcntl.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <sys/stat.h> | |
| #include <sys/types.h> | |
| #include <time.h> | |
| #include "./brotli/c/common/constants.h" | |
| #include "./brotli/c/common/version.h" | |
| #include <brotli/decode.h> | |
| #include <brotli/encode.h> | |
| #if !defined(_WIN32) | |
| #include <unistd.h> | |
| #include <utime.h> | |
| #else | |
| #include <io.h> | |
| #include <share.h> | |
| #include <sys/utime.h> | |
| #if !defined(__MINGW32__) | |
| #define STDIN_FILENO _fileno(stdin) | |
| #define STDOUT_FILENO _fileno(stdout) | |
| #define S_IRUSR S_IREAD | |
| #define S_IWUSR S_IWRITE | |
| #endif | |
| #define fopen ms_fopen | |
| #define open ms_open | |
| #if defined(_MSC_VER) && (_MSC_VER >= 1400) | |
| #define fseek _fseeki64 | |
| #define ftell _ftelli64 | |
| #endif | |
| static FILE* ms_fopen(const char* FileName, const char* Mode) { | |
| FILE* Result; | |
| Result = NULL; | |
| fopen_s(&Result, FileName, Mode); | |
| return Result; | |
| } | |
| #if !defined(__MINGW32__) | |
| static int ms_open(const char* FileName, int Oflag, int Pmode) { | |
| int Result; | |
| Result = -1; | |
| _sopen_s(&Result, FileName, Oflag | O_BINARY, _SH_DENYNO, Pmode); | |
| return Result; | |
| } | |
| #endif | |
| #endif /* WIN32 */ | |
| #ifndef _MAX_PATH | |
| #define _MAX_PATH 500 | |
| #endif | |
| #define DEFAULT_LGWIN 22 | |
| #define DECODE_HEADER_SIZE 0x10 | |
| #define GAP_MEM_BLOCK 0x1000 | |
| size_t ScratchBufferSize = 0; | |
| static const size_t kFileBufferSize = 1 << 19; | |
| static void Version(void) { | |
| int Major; | |
| int Minor; | |
| int Patch; | |
| Major = BROTLI_VERSION >> 24; | |
| Minor = (BROTLI_VERSION >> 12) & 0xFFF; | |
| Patch = BROTLI_VERSION & 0xFFF; | |
| printf("BrotliCompress %d.%d.%d\n", Major, Minor, Patch); | |
| } | |
| static void Usage() { | |
| printf("Usage: %s [OPTION]... [FILE]...\n", __FILE__); | |
| printf( | |
| "Options:\n" | |
| " -e, --compress compress\n" | |
| " -d, --decompress decompress\n" | |
| " -h, --help display this help and exit\n"); | |
| printf( | |
| " -o FILE, --output=FILE output file (only if 1 input file)\n"); | |
| printf( | |
| " -g NUM, --gap=NUM scratch memory gap level (1-16)\n"); | |
| printf( | |
| " -q NUM, --quality=NUM compression level (%d-%d)\n", | |
| BROTLI_MIN_QUALITY, BROTLI_MAX_QUALITY); | |
| printf( | |
| " -v, --version display version and exit\n"); | |
| } | |
| static int64_t FileSize(const char* Path) { | |
| FILE *FileHandle; | |
| int64_t RetVal; | |
| FileHandle = fopen(Path, "rb"); | |
| if (FileHandle == NULL) { | |
| printf ("Failed to open file [%s]\n", Path); | |
| return -1; | |
| } | |
| if (fseek(FileHandle, 0L, SEEK_END) != 0) { | |
| printf ("Failed to seek file [%s]\n", Path); | |
| fclose(FileHandle); | |
| return -1; | |
| } | |
| RetVal = ftell(FileHandle); | |
| if (fclose(FileHandle) != 0) { | |
| printf ("Failed to close file [%s]\n", Path); | |
| return -1; | |
| } | |
| return RetVal; | |
| } | |
| static BROTLI_BOOL HasMoreInput(FILE *FileHandle) { | |
| return feof(FileHandle) ? BROTLI_FALSE : BROTLI_TRUE; | |
| } | |
| int OpenFiles(char *InputFile, FILE **InHandle, char *OutputFile, FILE **OutHandle) { | |
| *InHandle = NULL; | |
| *OutHandle = NULL; | |
| *InHandle = fopen(InputFile, "rb"); | |
| if (*InHandle == NULL) { | |
| printf("Failed to open input file [%s]\n", InputFile); | |
| return BROTLI_FALSE; | |
| } | |
| *OutHandle = fopen(OutputFile, "wb+"); | |
| if (*OutHandle == NULL) { | |
| printf("Failed to open output file [%s]\n", OutputFile); | |
| fclose(*InHandle); | |
| return BROTLI_FALSE; | |
| } | |
| return BROTLI_TRUE; | |
| } | |
| int CompressFile(char *InputFile, uint8_t *InputBuffer, char *OutputFile, uint8_t *OutputBuffer, int Quality, int Gap) { | |
| int64_t InputFileSize; | |
| FILE *InputFileHandle; | |
| FILE *OutputFileHandle; | |
| BrotliEncoderState *EncodeState; | |
| uint32_t LgWin; | |
| BROTLI_BOOL IsEof; | |
| size_t AvailableIn; | |
| const uint8_t *NextIn; | |
| size_t AvailableOut; | |
| uint8_t *NextOut; | |
| uint8_t *Input; | |
| uint8_t *Output; | |
| size_t TotalOut; | |
| size_t OutSize; | |
| uint32_t SizeHint; | |
| BROTLI_BOOL IsOk; | |
| AvailableIn = 0; | |
| IsEof = BROTLI_FALSE; | |
| Input = InputBuffer; | |
| Output = OutputBuffer; | |
| IsOk = BROTLI_TRUE; | |
| LgWin = DEFAULT_LGWIN; | |
| InputFileSize = FileSize(InputFile); | |
| IsOk = OpenFiles(InputFile, &InputFileHandle, OutputFile, &OutputFileHandle); | |
| if (!IsOk) { | |
| return IsOk; | |
| } | |
| fseek (OutputFileHandle, DECODE_HEADER_SIZE, SEEK_SET); | |
| EncodeState = BrotliEncoderCreateInstance(NULL, NULL, NULL); | |
| if (!EncodeState) { | |
| printf("Out of memory\n"); | |
| IsOk = BROTLI_FALSE; | |
| goto Finish; | |
| } | |
| BrotliEncoderSetParameter(EncodeState, BROTLI_PARAM_QUALITY, (uint32_t)Quality); | |
| if (InputFileSize >= 0) { | |
| LgWin = BROTLI_MIN_WINDOW_BITS; | |
| while (BROTLI_MAX_BACKWARD_LIMIT(LgWin) < InputFileSize) { | |
| LgWin++; | |
| if (LgWin == BROTLI_MAX_WINDOW_BITS) { | |
| break; | |
| } | |
| } | |
| } | |
| BrotliEncoderSetParameter(EncodeState, BROTLI_PARAM_LGWIN, LgWin); | |
| if (InputFileSize > 0) { | |
| SizeHint = InputFileSize < (1 << 30)? (uint32_t)InputFileSize : (1u << 30); | |
| BrotliEncoderSetParameter(EncodeState, BROTLI_PARAM_SIZE_HINT, SizeHint); | |
| } | |
| AvailableIn = 0; | |
| NextIn = NULL; | |
| AvailableOut = kFileBufferSize; | |
| NextOut = Output; | |
| for (;;) { | |
| if (AvailableIn == 0 && !IsEof) { | |
| AvailableIn = fread(Input, 1, kFileBufferSize, InputFileHandle); | |
| NextIn = Input; | |
| if (ferror(InputFileHandle)) { | |
| printf("Failed to read input [%s]\n", InputFile); | |
| IsOk = BROTLI_FALSE; | |
| goto Finish; | |
| } | |
| IsEof = !HasMoreInput(InputFileHandle); | |
| } | |
| if (!IsEof){ | |
| do{ | |
| if (!BrotliEncoderCompressStream(EncodeState, | |
| BROTLI_OPERATION_FLUSH, | |
| &AvailableIn, &NextIn, &AvailableOut, &NextOut, &TotalOut)) { | |
| printf("Failed to compress data [%s]\n", InputFile); | |
| IsOk = BROTLI_FALSE; | |
| goto Finish; | |
| } | |
| OutSize = (size_t)(NextOut - Output); | |
| if (OutSize > 0) { | |
| fwrite(Output, 1, OutSize, OutputFileHandle); | |
| if (ferror(OutputFileHandle)) { | |
| printf("Failed to write output [%s]\n", OutputFile); | |
| IsOk = BROTLI_FALSE; | |
| goto Finish; | |
| } | |
| } | |
| NextOut = Output; | |
| AvailableOut = kFileBufferSize; | |
| } | |
| while (AvailableIn > 0 || BrotliEncoderHasMoreOutput(EncodeState)); | |
| } | |
| else{ | |
| do{ | |
| if (!BrotliEncoderCompressStream(EncodeState, | |
| BROTLI_OPERATION_FINISH, | |
| &AvailableIn, &NextIn, &AvailableOut, &NextOut, &TotalOut)) { | |
| printf("Failed to compress data [%s]\n", InputFile); | |
| IsOk = BROTLI_FALSE; | |
| goto Finish; | |
| } | |
| OutSize = (size_t)(NextOut - Output); | |
| if (OutSize > 0) { | |
| fwrite(Output, 1, OutSize, OutputFileHandle); | |
| if (ferror(OutputFileHandle)) { | |
| printf("Failed to write output [%s]\n", OutputFile); | |
| IsOk = BROTLI_FALSE; | |
| goto Finish; | |
| } | |
| } | |
| NextOut = Output; | |
| AvailableOut = kFileBufferSize; | |
| } | |
| while (AvailableIn > 0 || BrotliEncoderHasMoreOutput(EncodeState)); | |
| } | |
| if (BrotliEncoderIsFinished(EncodeState)){ | |
| break; | |
| } | |
| } | |
| Finish: | |
| if (EncodeState) { | |
| BrotliEncoderDestroyInstance(EncodeState); | |
| } | |
| if (InputFileHandle) { | |
| fclose(InputFileHandle); | |
| } | |
| if (OutputFileHandle) { | |
| fclose(OutputFileHandle); | |
| } | |
| return IsOk; | |
| } | |
| /* Default BrotliAllocFunc */ | |
| void* BrotliAllocFunc(void* Opaque, size_t Size) { | |
| *(size_t *)Opaque = *(size_t *) Opaque + Size; | |
| return malloc(Size); | |
| } | |
| /* Default BrotliFreeFunc */ | |
| void BrotliFreeFunc(void* Opaque, void* Address) { | |
| free(Address); | |
| } | |
| int DecompressFile(char *InputFile, uint8_t *InputBuffer, char *OutputFile, uint8_t *OutputBuffer, int Quality, int Gap) { | |
| FILE *InputFileHandle; | |
| FILE *OutputFileHandle; | |
| BrotliDecoderState *DecoderState; | |
| BrotliDecoderResult Result; | |
| size_t AvailableIn; | |
| const uint8_t *NextIn; | |
| size_t AvailableOut; | |
| uint8_t *NextOut; | |
| uint8_t *Input; | |
| uint8_t *Output; | |
| size_t OutSize; | |
| BROTLI_BOOL IsOk; | |
| AvailableIn = 0; | |
| Input = InputBuffer; | |
| Output = OutputBuffer; | |
| IsOk = BROTLI_TRUE; | |
| IsOk = OpenFiles(InputFile, &InputFileHandle, OutputFile, &OutputFileHandle); | |
| if (!IsOk) { | |
| return IsOk; | |
| } | |
| fseek(InputFileHandle, DECODE_HEADER_SIZE, SEEK_SET); | |
| DecoderState = BrotliDecoderCreateInstance(BrotliAllocFunc, BrotliFreeFunc, &ScratchBufferSize); | |
| if (!DecoderState) { | |
| printf("Out of memory\n"); | |
| IsOk = BROTLI_FALSE; | |
| goto Finish; | |
| } | |
| /* This allows decoding "large-window" streams. Though it creates | |
| fragmentation (new builds decode streams that old builds don't), | |
| it is better from used experience perspective. */ | |
| BrotliDecoderSetParameter(DecoderState, BROTLI_DECODER_PARAM_LARGE_WINDOW, 1u); | |
| AvailableIn = 0; | |
| NextIn = NULL; | |
| AvailableOut = kFileBufferSize; | |
| NextOut = Output; | |
| Result = BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT; | |
| for (;;) { | |
| if (Result == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) { | |
| if (!HasMoreInput(InputFileHandle)) { | |
| printf("Corrupt input [%s]\n", InputFile); | |
| IsOk = BROTLI_FALSE; | |
| goto Finish; | |
| } | |
| AvailableIn = fread(Input, 1, kFileBufferSize, InputFileHandle); | |
| NextIn = Input; | |
| if (ferror(InputFileHandle)) { | |
| printf("Failed to read input [%s]\n", InputFile); | |
| IsOk = BROTLI_FALSE; | |
| goto Finish; | |
| } | |
| } else if (Result == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) { | |
| OutSize = (size_t) (NextOut - Output); | |
| if (OutSize > 0) { | |
| fwrite(Output, 1, OutSize, OutputFileHandle); | |
| if (ferror(OutputFileHandle)) { | |
| printf("Failed to write output [%s]\n", OutputFile); | |
| IsOk = BROTLI_FALSE; | |
| goto Finish; | |
| } | |
| } | |
| AvailableOut = kFileBufferSize; | |
| NextOut = Output; | |
| } else if (Result == BROTLI_DECODER_RESULT_SUCCESS) { | |
| OutSize = (size_t) (NextOut - Output); | |
| if (OutSize > 0) { | |
| fwrite(Output, 1, OutSize, OutputFileHandle); | |
| if (ferror(OutputFileHandle)) { | |
| printf("Failed to write output [%s]\n", OutputFile); | |
| IsOk = BROTLI_FALSE; | |
| goto Finish; | |
| } | |
| } | |
| AvailableOut = 0; | |
| if (AvailableIn != 0 || HasMoreInput(InputFileHandle)) { | |
| printf("Corrupt input [%s]\n", InputFile); | |
| IsOk = BROTLI_FALSE; | |
| goto Finish; | |
| } | |
| } else { | |
| printf("Corrupt input [%s]\n", InputFile); | |
| IsOk = BROTLI_FALSE; | |
| goto Finish; | |
| } | |
| if (!HasMoreInput(InputFileHandle) && Result == BROTLI_DECODER_RESULT_SUCCESS ) { | |
| break; | |
| } | |
| Result = BrotliDecoderDecompressStream(DecoderState, &AvailableIn, &NextIn, &AvailableOut, &NextOut, 0); | |
| } | |
| Finish: | |
| if (DecoderState) { | |
| BrotliDecoderDestroyInstance(DecoderState); | |
| } | |
| if (InputFileHandle) { | |
| fclose(InputFileHandle); | |
| } | |
| if (OutputFileHandle) { | |
| fclose(OutputFileHandle); | |
| } | |
| return IsOk; | |
| } | |
| int main(int argc, char** argv) { | |
| BROTLI_BOOL CompressBool; | |
| BROTLI_BOOL DecompressBool; | |
| char *OutputFile; | |
| char *InputFile; | |
| char OutputTmpFile[_MAX_PATH]; | |
| FILE *OutputHandle; | |
| int Quality; | |
| int Gap; | |
| int OutputFileLength; | |
| int InputFileLength; | |
| int Ret; | |
| size_t InputFileSize; | |
| uint8_t *Buffer; | |
| uint8_t *InputBuffer; | |
| uint8_t *OutputBuffer; | |
| int64_t Size; | |
| InputFile = NULL; | |
| OutputFile = NULL; | |
| CompressBool = BROTLI_FALSE; | |
| DecompressBool = BROTLI_FALSE; | |
| // | |
| //Set default Quality and Gap | |
| // | |
| Quality = 9; | |
| Gap = 1; | |
| InputFileSize = 0; | |
| Ret = 0; | |
| if (argc < 2) { | |
| Usage(); | |
| return 1; | |
| } | |
| if (strcmp(argv[1], "-h") == 0 || strcmp (argv[1], "--help") == 0 ) { | |
| Usage(); | |
| return 0; | |
| } | |
| if (strcmp(argv[1], "-v") == 0 || strcmp (argv[1], "--version") == 0 ) { | |
| Version(); | |
| return 0; | |
| } | |
| while (argc > 1) { | |
| if (strcmp(argv[1], "-e") == 0 || strcmp(argv[1], "--compress") == 0 ) { | |
| CompressBool = BROTLI_TRUE; | |
| if (DecompressBool) { | |
| printf("Can't use -e/--compress with -d/--decompess on the same time\n"); | |
| return 1; | |
| } | |
| argc--; | |
| argv++; | |
| continue; | |
| } | |
| if (strcmp(argv[1], "-d") == 0 || strcmp(argv[1], "--decompress") == 0 ) { | |
| DecompressBool = BROTLI_TRUE; | |
| if (CompressBool) { | |
| printf("Can't use -e/--compress with -d/--decompess on the same time\n"); | |
| return 1; | |
| } | |
| argc--; | |
| argv++; | |
| continue; | |
| } | |
| if (strcmp(argv[1], "-o") == 0 || strncmp(argv[1], "--output", 8) == 0) { | |
| if (strcmp(argv[1], "-o") == 0) { | |
| OutputFileLength = strlen(argv[2]); | |
| if (OutputFileLength > _MAX_PATH) { | |
| printf ("The file path %s is too long\n", argv[2]); | |
| return 1; | |
| } | |
| OutputFile = argv[2]; | |
| if (OutputFile == NULL) { | |
| fprintf(stderr, "Input file can't be null\n"); | |
| return 1; | |
| } | |
| argc--; | |
| argv++; | |
| } else { | |
| OutputFileLength = strlen(argv[1] - 9); | |
| OutputFile = (char *)argv[1] + 9; | |
| } | |
| argc--; | |
| argv++; | |
| continue; | |
| } | |
| if (strcmp(argv[1], "-q") == 0 || strncmp(argv[1], "--quality", 9) == 0) { | |
| if (strcmp(argv[1], "-q") == 0) { | |
| Quality = strtol(argv[2], NULL, 16); | |
| argc--; | |
| argv++; | |
| } else { | |
| Quality = strtol((char *)argv[1] + 10, NULL, 16); | |
| } | |
| argc--; | |
| argv++; | |
| continue; | |
| } | |
| if (strcmp(argv[1], "-g") == 0 || strncmp(argv[1], "--gap", 5) == 0) { | |
| if (strcmp(argv[1], "-g") == 0) { | |
| Gap = strtol(argv[2], NULL, 16); | |
| argc--; | |
| argv++; | |
| } else { | |
| Gap = strtol((char *)argv[1] + 6, NULL, 16); | |
| } | |
| argc--; | |
| argv++; | |
| continue; | |
| } | |
| if (argc > 1) { | |
| InputFileLength = strlen(argv[1]); | |
| if (InputFileLength > _MAX_PATH - 1) { | |
| printf ("The file path %s is too long\n", argv[2]); | |
| return 1; | |
| } | |
| InputFile = argv[1]; | |
| if (InputFile == NULL) { | |
| printf("Input file can't be null\n"); | |
| return 1; | |
| } | |
| argc--; | |
| argv++; | |
| } | |
| } | |
| Buffer = (uint8_t*)malloc(kFileBufferSize * 2); | |
| if (!Buffer) { | |
| printf("Out of memory\n"); | |
| goto Finish; | |
| } | |
| memset(Buffer, 0, kFileBufferSize*2); | |
| InputBuffer = Buffer; | |
| OutputBuffer = Buffer + kFileBufferSize; | |
| if (CompressBool) { | |
| // | |
| // Compress file | |
| // | |
| Ret = CompressFile(InputFile, InputBuffer, OutputFile, OutputBuffer, Quality, Gap); | |
| if (!Ret) { | |
| printf ("Failed to compress file [%s]\n", InputFile); | |
| goto Finish; | |
| } | |
| // | |
| // Decompress file for get Outputfile size | |
| // | |
| strcpy (OutputTmpFile, OutputFile); | |
| if (strlen(InputFile) + strlen(".tmp") < _MAX_PATH) { | |
| strcat(OutputTmpFile, ".tmp"); | |
| } else { | |
| printf ("Output file path is too long[%s]\n", OutputFile); | |
| Ret = BROTLI_FALSE; | |
| goto Finish; | |
| } | |
| memset(Buffer, 0, kFileBufferSize*2); | |
| Ret = DecompressFile(OutputFile, InputBuffer, OutputTmpFile, OutputBuffer, Quality, Gap); | |
| if (!Ret) { | |
| printf ("Failed to decompress file [%s]\n", OutputFile); | |
| goto Finish; | |
| } | |
| remove (OutputTmpFile); | |
| // | |
| // fill decoder header | |
| // | |
| InputFileSize = FileSize(InputFile); | |
| Size = (int64_t)InputFileSize; | |
| OutputHandle = fopen(OutputFile, "rb+"); /* open output_path file and add in head info */ | |
| fwrite(&Size, 1, sizeof(int64_t), OutputHandle); | |
| ScratchBufferSize += Gap * GAP_MEM_BLOCK; /* there is a memory gap between IA32 and X64 environment*/ | |
| ScratchBufferSize += kFileBufferSize * 2; | |
| Size = (int64_t) ScratchBufferSize; | |
| fwrite(&Size, 1, sizeof(int64_t), OutputHandle); | |
| if (fclose(OutputHandle) != 0) { | |
| printf("Failed to close output file [%s]\n", OutputFile); | |
| Ret = BROTLI_FALSE; | |
| goto Finish; | |
| } | |
| } else { | |
| Ret = DecompressFile(InputFile, InputBuffer, OutputFile, OutputBuffer, Quality, Gap); | |
| if (!Ret) { | |
| printf ("Failed to decompress file [%s]\n", InputFile); | |
| goto Finish; | |
| } | |
| } | |
| Finish: | |
| if (Buffer != NULL) { | |
| free (Buffer); | |
| } | |
| return !Ret; | |
| } |