/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ │ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │ ╞══════════════════════════════════════════════════════════════════════════════╡ │ Copyright 2022 Justine Alexandra Roberts Tunney │ │ │ │ Permission to use, copy, modify, and/or distribute this software for │ │ any purpose with or without fee is hereby granted, provided that the │ │ above copyright notice and this permission notice appear in all copies. │ │ │ │ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ │ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ │ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ │ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ │ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ │ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" #include "libc/errno.h" #include "libc/fmt/magnumstrs.internal.h" #include "libc/limits.h" #include "libc/mem/mem.h" #include "libc/runtime/runtime.h" #include "libc/stdio/stdio.h" #include "libc/str/str.h" #include "libc/sysv/consts/ex.h" #include "libc/sysv/consts/exit.h" #include "libc/sysv/consts/ok.h" #include "third_party/getopt/getopt.internal.h" #include "third_party/zlib/zlib.h" #define USAGE \ " PATH...\n\ \n\ SYNOPSIS\n\ \n\ Compress Files\n\ \n\ FLAGS\n\ \n\ -?\n\ -h help\n\ -f force\n\ -c use stdout\n\ -d decompress\n\ -A append mode\n\ -x exclusive mode\n\ -k keep input file\n\ -0 disable compression\n\ -1 fastest compression\n\ -4 coolest compression\n\ -9 maximum compression\n\ -a ascii mode (ignored)\n\ -F fixed strategy (advanced)\n\ -L filtered strategy (advanced)\n\ -R run length strategy (advanced)\n\ -H huffman only strategy (advanced)\n\ \n" bool opt_keep; bool opt_force; char opt_level; bool opt_append; char opt_strategy; bool opt_exclusive; bool opt_usestdout; bool opt_decompress; const char *prog; char databuf[32768]; char pathbuf[PATH_MAX]; #include "libc/mem/tinymalloc.inc" wontreturn void PrintUsage(int rc, FILE *f) { fputs("usage: ", f); fputs(prog, f); fputs(USAGE, f); exit(rc); } void GetOpts(int argc, char *argv[]) { int opt; while ((opt = getopt(argc, argv, "?hfcdakxALFRHF0123456789")) != -1) { switch (opt) { case 'k': opt_keep = true; break; case 'f': opt_force = true; break; case 'A': opt_append = true; break; case 'c': opt_usestdout = true; break; case 'x': opt_exclusive = true; break; case 'd': opt_decompress = true; break; case 'F': opt_strategy = 'F'; // Z_FIXED break; case 'L': opt_strategy = 'f'; // Z_FILTERED break; case 'R': opt_strategy = 'R'; // Z_RLE break; case 'H': opt_strategy = 'h'; // Z_HUFFMAN_ONLY break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': opt_level = opt; break; case 'h': case '?': PrintUsage(EXIT_SUCCESS, stdout); default: PrintUsage(EX_USAGE, stderr); } } } void Compress(const char *inpath) { FILE *input; gzFile output; int rc, errnum; FILE *closeme = 0; const char *outpath; char *p, openflags[5]; if ((!inpath || opt_usestdout) && (!isatty(1) || opt_force)) { opt_usestdout = true; } else { fputs(prog, stderr); fputs(": compressed data not written to a terminal." " Use -f to force compression.\n", stderr); exit(1); } if (inpath) { input = closeme = fopen(inpath, "rb"); } else { inpath = "/dev/stdin"; input = stdin; } p = openflags; *p++ = opt_append ? 'a' : 'w'; *p++ = 'b'; if (opt_exclusive) *p++ = 'x'; if (opt_level) *p++ = opt_level; if (opt_strategy) *p++ = opt_strategy; *p = 0; if (opt_usestdout) { outpath = "/dev/stdout"; output = gzdopen(1, openflags); } else { if (strlen(inpath) + 3 + 1 > PATH_MAX) _Exit(2); stpcpy(stpcpy(pathbuf, inpath), ".gz"); outpath = pathbuf; output = gzopen(outpath, openflags); } if (!output) { fputs(outpath, stderr); fputs(": gzopen() failed: ", stderr); const char *s = _strerdoc(errno); fputs(s ? s : "EUNKNOWN", stderr); fputs("\n", stderr); exit(1); } do { rc = fread(databuf, 1, sizeof(databuf), input); if (rc == -1) { errnum = 0; fputs(inpath, stderr); fputs(": read failed: ", stderr); const char *s = _strerdoc(ferror(input)); fputs(s ? s : "EUNKNOWN", stderr); fputs("\n", stderr); _Exit(1); } if (!gzwrite(output, databuf, rc)) { fputs(outpath, stderr); fputs(": gzwrite failed: ", stderr); fputs(gzerror(output, &errnum), stderr); fputs("\n", stderr); _Exit(1); } } while (rc == sizeof(databuf)); if (closeme) { if (fclose(closeme)) { fputs(inpath, stderr); fputs(": close failed\n", stderr); _Exit(1); } } if (gzclose(output)) { fputs(outpath, stderr); fputs(": gzclose failed\n", stderr); _Exit(1); } if (!opt_keep && !opt_usestdout && (opt_force || !access(inpath, W_OK))) { unlink(inpath); } } void Decompress(const char *inpath) { FILE *output; gzFile input; FILE *closeme = 0; int rc, n, errnum; const char *outpath; outpath = 0; if (inpath) { input = gzopen(inpath, "rb"); } else { opt_usestdout = true; inpath = "/dev/stdin"; input = gzdopen(0, "rb"); } if (!input) { fputs(inpath, stderr); fputs(": gzopen() failed: ", stderr); const char *s = _strerdoc(errno); fputs(s ? s : "EUNKNOWN", stderr); fputs("\n", stderr); exit(1); } if (opt_usestdout) { output = stdout; outpath = "/dev/stdout"; } else if (endswith(inpath, ".gz")) { n = strlen(inpath); if (n - 3 + 1 > PATH_MAX) _Exit(2); memcpy(pathbuf, inpath, n - 3); pathbuf[n - 3] = 0; outpath = pathbuf; if (!(output = closeme = fopen(outpath, opt_append ? "wa" : "wb"))) { fputs(outpath, stderr); fputs(": open failed: ", stderr); const char *s = _strerdoc(errno); fputs(s ? s : "EUNKNOWN", stderr); fputs("\n", stderr); _Exit(1); } } else { fputs(inpath, stderr); fputs(": needs to end with .gz unless -c is passed\n", stderr); _Exit(1); } do { rc = gzread(input, databuf, sizeof(databuf)); if (rc == -1) { errnum = 0; fputs(inpath, stderr); fputs(": gzread failed: ", stderr); fputs(gzerror(input, &errnum), stderr); fputs("\n", stderr); _Exit(1); } if (fwrite(databuf, rc, 1, output) != 1) { fputs(outpath, stderr); fputs(": write failed: ", stderr); const char *s = _strerdoc(ferror(output)); fputs(s ? s : "EUNKNOWN", stderr); fputs("\n", stderr); _Exit(1); } } while (rc == sizeof(databuf)); if (gzclose(input)) { fputs(inpath, stderr); fputs(": gzclose failed\n", stderr); _Exit(1); } if (closeme) { if (fclose(closeme)) { fputs(outpath, stderr); fputs(": close failed\n", stderr); _Exit(1); } } if (!opt_keep && !opt_usestdout && (opt_force || !access(inpath, W_OK))) { unlink(inpath); } } int main(int argc, char *argv[]) { int i; prog = argv[0]; if (!prog) prog = "gzip"; GetOpts(argc, argv); if (opt_decompress) { if (optind == argc) { Decompress(0); } else { for (i = optind; i < argc; ++i) { Decompress(argv[i]); } } } else { if (optind == argc) { Compress(0); } else { for (i = optind; i < argc; ++i) { Compress(argv[i]); } } } return 0; }