mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 11:37:35 +00:00
329 lines
8.8 KiB
C
329 lines
8.8 KiB
C
/*-*- 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;
|
|
}
|