diff options
-rw-r--r-- | .gitignore | 10 | ||||
-rw-r--r-- | LICENCE.txt | 15 | ||||
-rw-r--r-- | README.txt | 5 | ||||
-rwxr-xr-x | configure | 47 | ||||
-rw-r--r-- | src/main/c/deflate.c | 204 | ||||
-rw-r--r-- | src/main/c/inflate.c | 228 |
6 files changed, 509 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..140bac1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ + +/build +/dist +/external + +# Eclipse +.cproject +.project +.settings + diff --git a/LICENCE.txt b/LICENCE.txt new file mode 100644 index 0000000..f0308ca --- /dev/null +++ b/LICENCE.txt @@ -0,0 +1,15 @@ + +Copyright 2020, Andreas Fankhauser. All rights reserved. + + THIS WORK IS PROVIDED BY THE AUTHOR(S) "AS IS" AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT +OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING +IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY +OF SUCH DAMAGE. + + diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..7b64d36 --- /dev/null +++ b/README.txt @@ -0,0 +1,5 @@ + +Pure DeflateInflate executables +=============================== + + diff --git a/configure b/configure new file mode 100755 index 0000000..cc38eac --- /dev/null +++ b/configure @@ -0,0 +1,47 @@ + +set -e +if test -n "$BASH_VERSION"; then set -o posix; fi + + +# See https://www.gnu.org/prep/standards/html_node/Directory-Variables.html +prefix=/usr/local +exec_prefix='$(PREFIX)' +bindir='$(EXEC_PREFIX)/bin' +host= + + +main() { + parseArgs "$@" + echo TODO create Makefile +} + + +printHelp() { + echo '' + echo "Example usage: $0 --prefix /usr/local" + echo '' + echo 'Options:' + echo ' --prefix <path> Default "/usr/local"' + echo ' --bindir <path> Default "$(EXEC_PREFIX)/bin"' + echo ' --host <name> Default <empty> (Eg "x86_64-w64-mingw32")' + echo '' +} + + +parseArgs() { + # See: https://stackoverflow.com/a/14203146/4415884 + while test $# -gt 0 ; do + case $1 in + --help) printHelp; exit 1; ;; + --prefix) prefix="$2" shift; shift; ;; + --exec_prefix) exec_prefix="$2" shift; shift; ;; + --bindir) bindir="$2" shift; shift; ;; + --host) host="$2" shift; shift; ;; + *) echo "[ERROR] Unexpected argument: $1" exit 1 ;; + esac + done +} + + +main "$@" + diff --git a/src/main/c/deflate.c b/src/main/c/deflate.c new file mode 100644 index 0000000..9043c10 --- /dev/null +++ b/src/main/c/deflate.c @@ -0,0 +1,204 @@ +/* + + See "./inflate.c" for bash helpers. + +*/ + +#include "commonbase.h" + +/* system */ +#include <assert.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> + +/* libs */ +#include "zlib.h" + +/* project */ +#include "string-util.h" +#include "log.h" + + +typedef struct Deflate Deflate; + + +struct Deflate { + int level; + int useZlibHdr; +}; + + +static void printHelp( void ){ + printf("\n %s%s", strrchr(__FILE__,'/')+1, " @ " STR_QUOT(PROJECT_VERSION) "\n" + "\n" + "deflates stdin to stdout\n" + "\n" + "Options:\n" + "\n" + " --level\n" + " Compression level 0-9 (0 fastest, 9 smallest).\n" + "\n" + " --zlib\n" + " Prepend output with a zlib header.\n" + "\n"); +} + + +static int parseArgs( int argc, char**argv, Deflate*deflateCls ){ + deflateCls->level = Z_DEFAULT_COMPRESSION; + deflateCls->useZlibHdr = 0; + for( uint i=1 ; i<argc ; ++i ){ + char *arg = argv[i]; + if( !strcmp(arg,"--help") ){ + printHelp(); return -1; + }else if( !strcmp(arg,"--level") ){ + arg = argv[++i]; + if( arg == NULL ){ fprintf(stderr, "Arg level needs a value\n"); return -1; } + char *end; + deflateCls->level = strtol(arg, &end, 10); + if( arg == end ){ fprintf(stderr, "Failed to parse '%s'\n", arg); return -1; } + if( deflateCls->level < 0 || deflateCls->level > 9 ){ + fprintf(stderr, "OutOfRange: %s %s\n", argv[i-1], arg); return -1; + } + }else if( !strcmp(arg,"--zlib") ){ + deflateCls->useZlibHdr = !0; + }else{ + fprintf(stderr, "Unknown arg: %s\n", arg); + return -1; + } + } + return 0; +} + + +static int doDeflate( Deflate*deflateCls ){ + int err; + z_stream strm; + long tmpLong; + uchar innBuf[65536]; + const int innBuf_cap = sizeof innBuf; + int innBuf_len = 0, innBuf_off = 0; + uchar outBuf[65536]; + const int outBuf_cap = sizeof outBuf; + int outBuf_len = 0; + int zlibHdrWritten = deflateCls->useZlibHdr ? 2 : 0; + int inputIsEOF = 0; + int outputIsEOF = 0; + + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.next_in = innBuf; + strm.avail_in = innBuf_len; + + err = deflateInit(&strm, deflateCls->level); + if( err != Z_OK ){ + LOG_ERROR("deflateInit(): (ret=%d): %s", err, strerror(errno)); + return -1; + } + + for(; !outputIsEOF ;){ + + /* input */ { + int space = innBuf_cap - innBuf_len; + /* look if we can shift */ + //LOG_DEBUG("space=%d\n", space); + if( space <= 0 && innBuf_off > 0 ){ + //LOG_DEBUG("do shifting\n"); + int wr = 0, rd = innBuf_off; + for(;;){ + err = rd - wr; /* chunk length to copy */ + if( rd + err > innBuf_cap) err = innBuf_cap - rd; + if( err <= 0 ) break; + memcpy(innBuf + wr, innBuf + rd, err); + rd += err; + wr += err; + } + innBuf_len -= innBuf_off; + innBuf_off = 0; + space = innBuf_cap - innBuf_len; + //LOG_DEBUG("space=%d (after shift)\n", space); + } + if( space > 0 && !inputIsEOF ){ + err = fread(innBuf + innBuf_len, 1, space, stdin); + if(unlikely( err <= 0 )){ + if( feof(stdin) ){ + inputIsEOF = !0; + }else{ + LOG_ERROR("fread(): %s", strerror(errno)); + return -1; + } + } + assert(err <= space); + innBuf_len += err; + } + assert(innBuf_len - innBuf_off >= 0); + } + + /* process */ { + strm.next_in = innBuf + innBuf_off; + strm.avail_in = innBuf_len - innBuf_off; + strm.next_out = outBuf; + strm.avail_out = outBuf_cap; + assert(strm.avail_out > 0); + //LOG_DEBUG("avail_in=%d, inputIsEOF=%d\n", strm.avail_in, inputIsEOF); + assert(strm.avail_in > 0 || inputIsEOF); + err = deflate(&strm, inputIsEOF ? Z_FINISH : Z_NO_FLUSH); + if(unlikely( err != Z_OK )){ + if( err == Z_STREAM_END ){ + outputIsEOF = !0; + }else{ + LOG_ERROR("deflate(): %s\n", strm.msg); + return -1; + } + } + innBuf_off = strm.next_in - innBuf; + outBuf_len = strm.next_out - outBuf; + } + + /* output */ { + int outBuf_off = 0; + if( zlibHdrWritten < 2 ){ + /* try to skip up to 2 bytes */ + err = 2 - zlibHdrWritten; + if( outBuf_len < err ){ err = outBuf_len; } + zlibHdrWritten += err; + outBuf_off += err; + } + err = fwrite(outBuf + outBuf_off, 1, outBuf_len - outBuf_off, stdout); + if(unlikely( err != outBuf_len - outBuf_off )){ + LOG_ERROR("fwrite(): %s\n", strerror(errno)); + return -1; + } + outBuf_len = 0; + } + } + + err = deflateEnd(&strm); + if( err != Z_OK ){ + LOG_WARN("deflateEnd() ret=%d: %s\n", err, strm.msg); + } + + return 0; +} + + +int deflate_main( int argc, char**argv ){ + int err; + Deflate deflateCls; + #define deflateCls (&deflateCls) + + err = parseArgs(argc, argv, deflateCls); + if( err < 0 ){ return err; } + + stdinModeBinary(); + stdoutModeBinary(); + + err = doDeflate(deflateCls); + if( err < 0 ){ return err; } + + return 0; + #undef deflate +} + diff --git a/src/main/c/inflate.c b/src/main/c/inflate.c new file mode 100644 index 0000000..67ddb71 --- /dev/null +++ b/src/main/c/inflate.c @@ -0,0 +1,228 @@ +/* + + bash-deflate(){ gzip -c | tail -c +10; } + bash-inflate(){ (printf "\x1f\x8b\x08\x00\x00\x00\x00\x00\x00"&&cat -)|gzip -dc; } + +*/ + +#include "commonbase.h" + +/* system */ +#include <assert.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> + +/* libs */ +#include "zlib.h" + +/* project */ +#include "log.h" +#include "string-util.h" + + +typedef struct MyInflate MyInflate; + + +struct MyInflate { + int tryParseHeaders; +}; + + +static void printHelp( void ){ + printf("\n %s%s", strrchr(__FILE__,'/')+1, " @ " STR_QUOT(PROJECT_VERSION) "\n" + "\n" + "inflates stdin to stdout\n" + "\n" + "Options:\n" + "\n" + " --raw\n" + " By default, we'll try to decode leading headers. Using this\n" + " option, no header parsing is done and the input is expected to\n" + " be a pure deflate stream.\n" + "\n" + "\n"); +} + + +static int parseArgs( int argc, char**argv, MyInflate*myInflate ){ + myInflate->tryParseHeaders = !0; /* enabled by default */ + for( uint i=1 ; i<argc ; ++i ){ + char *arg = argv[i]; + if( !strcmp(arg,"--help") ){ + printHelp(); return -1; + }else if( !strcmp(arg,"--raw") ){ + myInflate->tryParseHeaders = 0; + }else{ + fprintf(stderr, "Unknown arg: %s\n", arg); + return -1; + } + } + return 0; +} + + +static int doInflate( MyInflate*myInflate ){ + #define MAX(a, b) ((a) > (b) ? (a) : (b)) + int err; + z_stream strm; + uchar innBuf[65536]; + const int innBuf_cap = sizeof innBuf; + int innBuf_len = 0, innBuf_off = 0; + uchar outBuf[65536]; + const int outBuf_cap = sizeof outBuf; + int outBuf_len = 0; + int inputIsEOF = 0; + int outputIsEOF = 0; + int noProgressSince = 0; + /* only counts while reading through possible file headers. Later, the + * counter will not be updated anymore, to prevent overflows */ + int headerIsPassed = 0; + + strm.zalloc = Z_NULL; + strm.zfree = Z_NULL; + strm.opaque = Z_NULL; + strm.next_in = innBuf; + strm.avail_in = innBuf_len; + + // Pass special 2nd arg. See "https://codeandlife.com/2014/01/01/unzip-library-for-c/" + err = inflateInit2(&strm, -MAX_WBITS); + if( err != Z_OK ){ + LOG_ERROR("inflateInit() ret=%d: %s", err, strerror(errno)); + return -1; + } + + for(; !inputIsEOF || !outputIsEOF ;){ + + /* input */ { + int space = innBuf_cap - innBuf_len; + /* look if we can shift */ + if( space <= 0 && innBuf_off > 0 ){ + //LOG_DEBUG("try-shift space=%d, innBuf_off=%d\n", space, innBuf_off); + int wr = 0, rd = innBuf_off; + for(;;){ + err = rd - wr; /* chunk length to copy */ + if( rd + err > innBuf_cap) err = innBuf_cap - rd; + if( err <= 0 ) break; + memcpy(innBuf + wr, innBuf + rd, err); + rd += err; + wr += err; + } + innBuf_len -= innBuf_off; + innBuf_off = 0; + space = innBuf_cap - innBuf_len; + //LOG_DEBUG("after shift space=%d, innBuf_off=%d, innBuf_len=%d\n", space, innBuf_off, innBuf_len); + } + if( space > 0 && !inputIsEOF ){ + //LOG_TRACE("go read space=%d, inputIsEOF=%d\n", space, inputIsEOF); + err = fread(innBuf + innBuf_len, 1, space, stdin); + //LOG_TRACE("fread() -> %d\n", err); + if(unlikely( err <= 0 )){ + if( feof(stdin) ){ + inputIsEOF = !0; + }else{ + LOG_ERROR("fread(): %s", strerror(errno)); + return -1; + } + } + assert(err <= space); + innBuf_len += err; + } + } + + if(unlikely( !headerIsPassed )){ + #define avail (innBuf_len - innBuf_off) + /* Ensure we have at least two bytes for zlib header detection */ + if( avail < 2 && !inputIsEOF ){ LOG_DEBUG("Not enough data for header detection. have" + " %d\n", avail); continue; } + headerIsPassed = !0; + if( myInflate->tryParseHeaders ){ + int b0 = innBuf[0], b1 = innBuf[1]; + if( b0 == 0x78 && (b1 == 0x01 || b1 == 0x5E || b1 == 0x9C || b1 == 0xDA) ){ + //LOG_DEBUG("looks like a zlib header. Skip it\n"); + innBuf_off += 2; + } + } + #undef avail + } + + /* process */ { + strm.next_in = innBuf + innBuf_off; + strm.avail_in = MAX(0, innBuf_len - innBuf_off); + strm.next_out = outBuf; + strm.avail_out = outBuf_cap; + // TODO remove assert(strm.avail_in >= 0 || inputIsEOF); + assert(strm.avail_out > 0); + int flush = inputIsEOF ? Z_FINISH : Z_NO_FLUSH; + //LOG_DEBUG("inflate(in=0x%p, inLen=%d, %s)\n", strm.next_in, strm.avail_in, + // (flush==Z_FINISH) ? "Z_FINISH" : (flush==Z_FULL_FLUSH) ? "Z_FULL_FLUSH" : "Z_NO_FLUSH"); + errno = 0; + err = inflate(&strm, flush); + if(unlikely( err != Z_OK )){ + if( err == Z_STREAM_END ){ + //LOG_DEBUG("inflate() -> Z_STREAM_END\n"); + outputIsEOF = !0; + }else if( err == Z_BUF_ERROR ){ + /* Could not make any progress. Have to call again with updated buffers */ + noProgressSince += 1; + if( noProgressSince > 42 ){ + LOG_WARN("inflate() -> Z_BUF_ERROR: %s (maybe EOF came too early?)\n", + (strm.msg == NULL) ? "Could not make any progress" : strm.msg); + return -1; + } + }else if( strm.msg != NULL || errno ){ + LOG_ERROR("inflate(): %s\n", (strm.msg != NULL) ? strm.msg : strerror(errno)); + return -1; + }else{ + LOG_ERROR("inflate(): -> %d\n", err); + return -1; + } + }else{ + noProgressSince = 0; /* reset as we just made progress */ + } + innBuf_off += strm.next_in - innBuf; + outBuf_len = strm.next_out - outBuf; + if( innBuf_off > innBuf_len ){ innBuf_off = innBuf_len; } + //LOG_DEBUG("strm.remain=%d, outputIsEOF=%d\n", innBuf_len - innBuf_off, outputIsEOF); + } + + /* output */ { + err = fwrite(outBuf, 1, outBuf_len, stdout); + //LOG_DEBUG("fwrite(0x%p, 1, %d, stdout) -> %d\n", outBuf, outBuf_len, err); + if(unlikely( err != outBuf_len )){ + LOG_ERROR("fwrite(): %s\n", strerror(errno)); + return -1; + } + outBuf_len = 0; + } + } + + err = inflateEnd(&strm); + if( err != Z_OK ){ + LOG_WARN("inflateEnd() ret=%d: %s\n", err, strm.msg); + } + + return 0; + #undef MAX +} + + +int inflate_main( int argc, char**argv ){ + int err; + MyInflate myInflate; + #define myInflate (&myInflate) + + err = parseArgs(argc, argv, myInflate); + if( err < 0 ){ return err; } + + stdinModeBinary(); + stdoutModeBinary(); + + err = doInflate(myInflate); + if( err < 0 ){ return err; } + + return 0; + #undef myInflate +} + + |