#if HAVE_NBTOOL_CONFIG_H
#include "nbtool_config.h"
#endif
#include <sys/cdefs.h>
#ifndef lint
__COPYRIGHT("@(#) Copyright (c) 1997-2026 Matthew R. Green. "
"All rights reserved.");
__RCSID("$NetBSD: gzip.c,v 1.130 2026/04/30 13:35:32 simonb Exp $");
#endif
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <inttypes.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <zlib.h>
#include <fts.h>
#include <libgen.h>
#include <stdarg.h>
#include <getopt.h>
#include <time.h>
#include <paths.h>
#ifndef PRIdOFF
#define PRIdOFF PRId64
#endif
enum filetype {
FT_GZIP,
#ifndef NO_BZIP2_SUPPORT
FT_BZIP2,
#endif
#ifndef NO_COMPRESS_SUPPORT
FT_Z,
#endif
#ifndef NO_PACK_SUPPORT
FT_PACK,
#endif
#ifndef NO_XZ_SUPPORT
FT_XZ,
#endif
#ifndef NO_ZSTD_SUPPORT
FT_ZSTD,
#endif
#ifndef NO_LZ_SUPPORT
FT_LZ,
#endif
FT_LAST,
FT_UNKNOWN
};
#ifndef NO_BZIP2_SUPPORT
#include <bzlib.h>
#define BZ2_SUFFIX ".bz2"
#define BZIP2_MAGIC "\102\132\150"
#endif
#ifndef NO_COMPRESS_SUPPORT
#define Z_SUFFIX ".Z"
#define Z_MAGIC "\037\235"
#endif
#ifndef NO_PACK_SUPPORT
#define PACK_MAGIC "\037\036"
#endif
#ifndef NO_XZ_SUPPORT
#include <lzma.h>
#define XZ_SUFFIX ".xz"
#define XZ_MAGIC "\3757zXZ"
#endif
#ifndef NO_ZSTD_SUPPORT
#include <zstd.h>
#define ZSTD_SUFFIX ".zst"
#define ZSTD_MAGIC "\050\265\057\375"
#endif
#ifndef NO_LZ_SUPPORT
#define LZ_SUFFIX ".lz"
#define LZ_MAGIC "LZIP"
#endif
#define GZ_SUFFIX ".gz"
#define BUFLEN (64 * 1024)
#define GZIP_MAGIC0 0x1F
#define GZIP_MAGIC1 0x8B
#define GZIP_OMAGIC1 0x9E
#define GZIP_TIMESTAMP (off_t)4
#define GZIP_ORIGNAME (off_t)10
#define HEAD_CRC 0x02
#define EXTRA_FIELD 0x04
#define ORIG_NAME 0x08
#define COMMENT 0x10
#define OS_CODE 3
typedef struct {
const char *zipped;
int ziplen;
const char *normal;
} suffixes_t;
static suffixes_t suffixes[] = {
#define SUFFIX(Z, N) {Z, sizeof Z - 1, N}
SUFFIX(GZ_SUFFIX, ""),
#ifndef SMALL
SUFFIX(GZ_SUFFIX, ""),
SUFFIX(".z", ""),
SUFFIX("-gz", ""),
SUFFIX("-z", ""),
SUFFIX("_z", ""),
SUFFIX(".taz", ".tar"),
SUFFIX(".tgz", ".tar"),
#ifndef NO_BZIP2_SUPPORT
SUFFIX(BZ2_SUFFIX, ""),
#endif
#ifndef NO_COMPRESS_SUPPORT
SUFFIX(Z_SUFFIX, ""),
#endif
#ifndef NO_XZ_SUPPORT
SUFFIX(XZ_SUFFIX, ""),
#endif
#ifndef NO_ZSTD_SUPPORT
SUFFIX(ZSTD_SUFFIX, ""),
#endif
#ifndef NO_LZ_SUPPORT
SUFFIX(LZ_SUFFIX, ""),
#endif
SUFFIX(GZ_SUFFIX, ""),
#endif
#undef SUFFIX
};
#define NUM_SUFFIXES (sizeof suffixes / sizeof suffixes[0])
#define SUFFIX_MAXLEN 30
static const char gzip_version[] = "NetBSD gzip 20260107";
static int cflag;
static int dflag;
static int lflag;
static int numflag = 6;
#ifndef SMALL
static int fflag;
static int kflag;
static int nflag;
static int Nflag;
static int qflag;
static int rflag;
static int tflag;
static int vflag;
#ifdef SIGINFO
static sig_atomic_t print_info = 0;
#endif
#else
#define qflag 0
#define tflag 0
#endif
static int exit_value = 0;
static const char *infile;
static void maybe_err(const char *fmt, ...) __printflike(1, 2) __dead;
#if !defined(NO_BZIP2_SUPPORT) || !defined(NO_PACK_SUPPORT) || \
!defined(NO_XZ_SUPPORT) || !defined(NO_ZSTD_SUPPORT)
static void maybe_errx(const char *fmt, ...) __printflike(1, 2) __dead;
#endif
static void maybe_warn(const char *fmt, ...) __printflike(1, 2);
static void maybe_warnx(const char *fmt, ...) __printflike(1, 2);
static enum filetype file_gettype(u_char *);
#ifdef SMALL
#define gz_compress(if, of, sz, fn, tm) gz_compress(if, of, sz)
#endif
static off_t gz_compress(int, int, off_t *, const char *, uint32_t);
static off_t gz_uncompress(int, int, char *, size_t, off_t *, const char *);
static off_t file_compress(char *, char *, size_t);
static off_t file_uncompress(char *, char *, size_t);
static void handle_pathname(char *);
static void handle_file(char *, struct stat *);
static void handle_fd_decomp(int fd);
static void handle_stdout(void);
static void print_ratio(off_t, off_t, FILE *);
static void print_list(int fd, off_t, const char *, time_t);
static void usage(void) __dead;
static void display_version(void) __dead;
static const suffixes_t *check_suffix(char *, int);
static ssize_t read_retry(int, void *, size_t);
static ssize_t write_retry(int, const void *, size_t);
static void print_list_out(off_t, off_t, const char*);
#ifdef SMALL
#define infile_set(f,t) infile_set(f)
#endif
static void infile_set(const char *newinfile, off_t total);
#ifdef SMALL
#define unlink_input(f, sb) unlink(f)
#define check_siginfo()
#define setup_signals()
#define infile_newdata(t)
#else
static off_t infile_total;
static off_t infile_current;
#ifdef SIGINFO
static void check_siginfo(void);
#else
#define check_siginfo()
#endif
static void license(void) __dead;
static off_t cat_fd(unsigned char *, size_t, off_t *, int fd);
static void prepend_gzip(char *, int *, char ***);
static void handle_dir(char *);
static void print_verbage(const char *, const char *, off_t, off_t);
static void print_test(const char *, int);
static void copymodes(int fd, const struct stat *, const char *file);
static int check_outfile(const char *outfile);
static void setup_signals(void);
static void infile_newdata(size_t newdata);
static void infile_clear(void);
#endif
#ifndef NO_BZIP2_SUPPORT
static off_t unbzip2(int, int, char *, size_t, off_t *);
#endif
#ifndef NO_COMPRESS_SUPPORT
static FILE *zdopen(int);
static off_t zuncompress(FILE *, FILE *, char *, size_t, off_t *);
#endif
#ifndef NO_PACK_SUPPORT
static off_t unpack(int, int, char *, size_t, off_t *);
#endif
#ifndef NO_XZ_SUPPORT
static off_t unxz(int, int, char *, size_t, off_t *);
static off_t unxz_len(int);
#endif
#ifndef NO_ZSTD_SUPPORT
static off_t unzstd(int, int, char *, size_t, off_t *);
#endif
#ifndef NO_LZ_SUPPORT
static off_t unlz(int, int, char *, size_t, off_t *);
#endif
#ifdef SMALL
#define getopt_long(a,b,c,d,e) getopt(a,b,c)
#else
static const struct option longopts[] = {
{ "stdout", no_argument, 0, 'c' },
{ "to-stdout", no_argument, 0, 'c' },
{ "decompress", no_argument, 0, 'd' },
{ "uncompress", no_argument, 0, 'd' },
{ "force", no_argument, 0, 'f' },
{ "help", no_argument, 0, 'h' },
{ "keep", no_argument, 0, 'k' },
{ "list", no_argument, 0, 'l' },
{ "no-name", no_argument, 0, 'n' },
{ "name", no_argument, 0, 'N' },
{ "quiet", no_argument, 0, 'q' },
{ "recursive", no_argument, 0, 'r' },
{ "suffix", required_argument, 0, 'S' },
{ "test", no_argument, 0, 't' },
{ "verbose", no_argument, 0, 'v' },
{ "version", no_argument, 0, 'V' },
{ "fast", no_argument, 0, '1' },
{ "best", no_argument, 0, '9' },
{ "ascii", no_argument, 0, 'a' },
{ "license", no_argument, 0, 'L' },
{ NULL, no_argument, 0, 0 },
};
#endif
int
main(int argc, char **argv)
{
const char *progname = getprogname();
#ifndef SMALL
char *gzip;
int len;
#endif
int ch;
setup_signals();
#ifndef SMALL
if ((gzip = getenv("GZIP")) != NULL)
prepend_gzip(gzip, &argc, &argv);
#endif
if (strcmp(progname, "gunzip") == 0)
dflag = 1;
else if (strcmp(progname, "zcat") == 0 ||
strcmp(progname, "gzcat") == 0)
dflag = cflag = 1;
#ifdef SMALL
#define OPT_LIST "123456789cdhlVn"
#else
#define OPT_LIST "123456789acdfhkLlNnqrS:tVv"
#endif
while ((ch = getopt_long(argc, argv, OPT_LIST, longopts, NULL)) != -1) {
switch (ch) {
case '1': case '2': case '3':
case '4': case '5': case '6':
case '7': case '8': case '9':
numflag = ch - '0';
break;
case 'c':
cflag = 1;
break;
case 'd':
dflag = 1;
break;
case 'l':
lflag = 1;
dflag = 1;
break;
case 'V':
display_version();
#ifndef SMALL
case 'a':
break;
case 'f':
fflag = 1;
break;
case 'k':
kflag = 1;
break;
case 'L':
license();
case 'N':
nflag = 0;
Nflag = 1;
break;
case 'n':
nflag = 1;
Nflag = 0;
break;
case 'q':
qflag = 1;
break;
case 'r':
rflag = 1;
break;
case 'S':
len = strlen(optarg);
if (len != 0) {
if (len > SUFFIX_MAXLEN)
errx(1, "incorrect suffix: '%s'", optarg);
suffixes[0].zipped = optarg;
suffixes[0].ziplen = len;
} else {
suffixes[NUM_SUFFIXES - 1].zipped = "";
suffixes[NUM_SUFFIXES - 1].ziplen = 0;
}
break;
case 't':
cflag = 1;
tflag = 1;
dflag = 1;
break;
case 'v':
vflag = 1;
break;
#else
case 'n':
break;
#endif
default:
usage();
}
}
argv += optind;
argc -= optind;
if (argc == 0) {
if (dflag)
handle_fd_decomp(STDIN_FILENO);
else
handle_stdout();
} else {
do {
handle_pathname(argv[0]);
} while (*++argv);
}
#ifndef SMALL
if (qflag == 0 && lflag && argc > 1)
print_list(-1, 0, "(totals)", 0);
#endif
exit(exit_value);
}
void
maybe_warn(const char *fmt, ...)
{
va_list ap;
if (qflag == 0) {
va_start(ap, fmt);
vwarn(fmt, ap);
va_end(ap);
}
if (exit_value == 0)
exit_value = 1;
}
void
maybe_warnx(const char *fmt, ...)
{
va_list ap;
if (qflag == 0) {
va_start(ap, fmt);
vwarnx(fmt, ap);
va_end(ap);
}
if (exit_value == 0)
exit_value = 1;
}
void
maybe_err(const char *fmt, ...)
{
va_list ap;
if (qflag == 0) {
va_start(ap, fmt);
vwarn(fmt, ap);
va_end(ap);
}
exit(2);
}
#if !defined(NO_BZIP2_SUPPORT) || !defined(NO_PACK_SUPPORT) || \
!defined(NO_XZ_SUPPORT) || !defined(NO_ZSTD_SUPPORT)
void
maybe_errx(const char *fmt, ...)
{
va_list ap;
if (qflag == 0) {
va_start(ap, fmt);
vwarnx(fmt, ap);
va_end(ap);
}
exit(2);
}
#endif
#ifndef SMALL
static void
prepend_gzip(char *gzip, int *argc, char ***argv)
{
char *s, **nargv, **ac;
int nenvarg = 0, i;
for (s = gzip;;) {
while (*s == ' ' || *s == '\t')
s++;
if (*s == 0)
goto count_done;
nenvarg++;
while (*s != ' ' && *s != '\t')
if (*s++ == 0)
goto count_done;
}
count_done:
if (nenvarg == 0)
return;
*argc += nenvarg;
ac = *argv;
nargv = (char **)malloc((*argc + 1) * sizeof(char *));
if (nargv == NULL)
maybe_err("malloc");
*argv = nargv;
i = 0;
nargv[i++] = *(ac++);
s = strdup(gzip);
if (s == NULL)
maybe_err("strdup");
for (;;) {
while (*s == ' ' || *s == '\t')
s++;
if (*s == 0)
goto copy_done;
nargv[i++] = s;
while (*s != ' ' && *s != '\t')
if (*s++ == 0)
goto copy_done;
*s++ = 0;
}
copy_done:
while (*ac)
nargv[i++] = *(ac++);
nargv[i] = NULL;
}
#endif
static off_t
gz_compress(int in, int out, off_t *gsizep, const char *origname, uint32_t mtime)
{
z_stream z;
char *outbufp, *inbufp;
off_t in_tot = 0, out_tot = 0;
ssize_t in_size;
int i, error;
uLong crc;
#ifdef SMALL
static char header[] = { GZIP_MAGIC0, GZIP_MAGIC1, Z_DEFLATED, 0,
0, 0, 0, 0,
0, OS_CODE };
#endif
outbufp = malloc(BUFLEN);
inbufp = malloc(BUFLEN);
if (outbufp == NULL || inbufp == NULL) {
maybe_err("malloc failed");
goto out;
}
memset(&z, 0, sizeof z);
z.zalloc = Z_NULL;
z.zfree = Z_NULL;
z.opaque = 0;
#ifdef SMALL
memcpy(outbufp, header, sizeof header);
i = sizeof header;
#else
if (nflag != 0) {
mtime = 0;
origname = "";
}
i = snprintf(outbufp, BUFLEN, "%c%c%c%c%c%c%c%c%c%c%s",
GZIP_MAGIC0, GZIP_MAGIC1, Z_DEFLATED,
*origname ? ORIG_NAME : 0,
mtime & 0xff,
(mtime >> 8) & 0xff,
(mtime >> 16) & 0xff,
(mtime >> 24) & 0xff,
numflag == 1 ? 4 : numflag == 9 ? 2 : 0,
OS_CODE, origname);
if (i >= BUFLEN)
maybe_err("snprintf");
if (*origname)
i++;
#endif
z.next_out = (unsigned char *)outbufp + i;
z.avail_out = BUFLEN - i;
error = deflateInit2(&z, numflag, Z_DEFLATED,
(-MAX_WBITS), 8, Z_DEFAULT_STRATEGY);
if (error != Z_OK) {
maybe_warnx("deflateInit2 failed");
in_tot = -1;
goto out;
}
crc = crc32(0L, Z_NULL, 0);
for (;;) {
check_siginfo();
if (z.avail_out == 0) {
if (write_retry(out, outbufp, BUFLEN) != BUFLEN) {
maybe_warn("write");
out_tot = -1;
goto out;
}
out_tot += BUFLEN;
z.next_out = (unsigned char *)outbufp;
z.avail_out = BUFLEN;
}
if (z.avail_in == 0) {
in_size = read(in, inbufp, BUFLEN);
if (in_size < 0) {
maybe_warn("read");
in_tot = -1;
goto out;
}
if (in_size == 0)
break;
infile_newdata(in_size);
crc = crc32(crc, (const Bytef *)inbufp, (unsigned)in_size);
in_tot += in_size;
z.next_in = (unsigned char *)inbufp;
z.avail_in = in_size;
}
error = deflate(&z, Z_NO_FLUSH);
if (error != Z_OK && error != Z_STREAM_END) {
maybe_warnx("deflate failed");
in_tot = -1;
goto out;
}
}
for (;;) {
size_t len;
ssize_t w;
error = deflate(&z, Z_FINISH);
if (error != Z_OK && error != Z_STREAM_END) {
maybe_warnx("deflate failed");
in_tot = -1;
goto out;
}
len = (char *)z.next_out - outbufp;
w = write_retry(out, outbufp, len);
if (w == -1 || (size_t)w != len) {
maybe_warn("write");
out_tot = -1;
goto out;
}
out_tot += len;
z.next_out = (unsigned char *)outbufp;
z.avail_out = BUFLEN;
if (error == Z_STREAM_END)
break;
}
if (deflateEnd(&z) != Z_OK) {
maybe_warnx("deflateEnd failed");
in_tot = -1;
goto out;
}
i = snprintf(outbufp, BUFLEN, "%c%c%c%c%c%c%c%c",
(int)crc & 0xff,
(int)(crc >> 8) & 0xff,
(int)(crc >> 16) & 0xff,
(int)(crc >> 24) & 0xff,
(int)in_tot & 0xff,
(int)(in_tot >> 8) & 0xff,
(int)(in_tot >> 16) & 0xff,
(int)(in_tot >> 24) & 0xff);
if (i != 8)
maybe_err("snprintf");
#if 0
if (in_tot > 0xffffffff)
maybe_warn("input file size >= 4GB cannot be saved");
#endif
if (write_retry(out, outbufp, i) != i) {
maybe_warn("write");
in_tot = -1;
} else
out_tot += i;
out:
if (inbufp != NULL)
free(inbufp);
if (outbufp != NULL)
free(outbufp);
if (gsizep)
*gsizep = out_tot;
return in_tot;
}
static off_t
gz_uncompress(int in, int out, char *pre, size_t prelen, off_t *gsizep,
const char *filename)
{
z_stream z;
char *outbufp, *inbufp;
off_t out_tot = -1, in_tot = 0;
uint32_t out_sub_tot = 0;
enum {
GZSTATE_MAGIC0,
GZSTATE_MAGIC1,
GZSTATE_METHOD,
GZSTATE_FLAGS,
GZSTATE_SKIPPING,
GZSTATE_EXTRA,
GZSTATE_EXTRA2,
GZSTATE_EXTRA3,
GZSTATE_ORIGNAME,
GZSTATE_COMMENT,
GZSTATE_HEAD_CRC1,
GZSTATE_HEAD_CRC2,
GZSTATE_INIT,
GZSTATE_READ,
GZSTATE_CRC,
GZSTATE_LEN,
} state = GZSTATE_MAGIC0;
int flags = 0, skip_count = 0;
int error = Z_STREAM_ERROR, done_reading = 0;
uLong crc = 0;
ssize_t wr;
int needmore = 0;
#define ADVANCE() { z.next_in++; z.avail_in--; }
if ((outbufp = malloc(BUFLEN)) == NULL) {
maybe_err("malloc failed");
goto out2;
}
if ((inbufp = malloc(BUFLEN)) == NULL) {
maybe_err("malloc failed");
goto out1;
}
memset(&z, 0, sizeof z);
z.avail_in = prelen;
z.next_in = (unsigned char *)pre;
z.avail_out = BUFLEN;
z.next_out = (unsigned char *)outbufp;
z.zalloc = NULL;
z.zfree = NULL;
z.opaque = 0;
in_tot = prelen;
out_tot = 0;
for (;;) {
check_siginfo();
if ((z.avail_in == 0 || needmore) && done_reading == 0) {
ssize_t in_size;
if (z.avail_in > 0) {
memmove(inbufp, z.next_in, z.avail_in);
}
z.next_in = (unsigned char *)inbufp;
in_size = read(in, z.next_in + z.avail_in,
BUFLEN - z.avail_in);
if (in_size == -1) {
maybe_warn("failed to read stdin");
goto stop_and_fail;
} else if (in_size == 0) {
done_reading = 1;
}
infile_newdata(in_size);
z.avail_in += in_size;
needmore = 0;
in_tot += in_size;
}
if (z.avail_in == 0) {
if (done_reading && state != GZSTATE_MAGIC0) {
maybe_warnx("%s: unexpected end of file",
filename);
goto stop_and_fail;
}
goto stop;
}
switch (state) {
case GZSTATE_MAGIC0:
if (*z.next_in != GZIP_MAGIC0) {
if (in_tot > 0) {
maybe_warnx("%s: trailing garbage "
"ignored", filename);
goto stop;
}
maybe_warnx("input not gziped (MAGIC0)");
exit_value = 2;
goto stop_and_fail;
}
ADVANCE();
state++;
out_sub_tot = 0;
crc = crc32(0L, Z_NULL, 0);
break;
case GZSTATE_MAGIC1:
if (*z.next_in != GZIP_MAGIC1 &&
*z.next_in != GZIP_OMAGIC1) {
maybe_warnx("input not gziped (MAGIC1)");
goto stop_and_fail;
}
ADVANCE();
state++;
break;
case GZSTATE_METHOD:
if (*z.next_in != Z_DEFLATED) {
maybe_warnx("unknown compression method");
goto stop_and_fail;
}
ADVANCE();
state++;
break;
case GZSTATE_FLAGS:
flags = *z.next_in;
ADVANCE();
skip_count = 6;
state++;
break;
case GZSTATE_SKIPPING:
if (skip_count > 0) {
skip_count--;
ADVANCE();
} else
state++;
break;
case GZSTATE_EXTRA:
if ((flags & EXTRA_FIELD) == 0) {
state = GZSTATE_ORIGNAME;
break;
}
skip_count = *z.next_in;
ADVANCE();
state++;
break;
case GZSTATE_EXTRA2:
skip_count |= ((*z.next_in) << 8);
ADVANCE();
state++;
break;
case GZSTATE_EXTRA3:
if (skip_count > 0) {
skip_count--;
ADVANCE();
} else
state++;
break;
case GZSTATE_ORIGNAME:
if ((flags & ORIG_NAME) == 0) {
state++;
break;
}
if (*z.next_in == 0)
state++;
ADVANCE();
break;
case GZSTATE_COMMENT:
if ((flags & COMMENT) == 0) {
state++;
break;
}
if (*z.next_in == 0)
state++;
ADVANCE();
break;
case GZSTATE_HEAD_CRC1:
if (flags & HEAD_CRC)
skip_count = 2;
else
skip_count = 0;
state++;
break;
case GZSTATE_HEAD_CRC2:
if (skip_count > 0) {
skip_count--;
ADVANCE();
} else
state++;
break;
case GZSTATE_INIT:
if (inflateInit2(&z, -MAX_WBITS) != Z_OK) {
maybe_warnx("failed to inflateInit");
goto stop_and_fail;
}
state++;
break;
case GZSTATE_READ:
error = inflate(&z, Z_FINISH);
switch (error) {
case Z_BUF_ERROR:
if (z.avail_out > 0 && !done_reading)
continue;
case Z_STREAM_END:
case Z_OK:
break;
case Z_NEED_DICT:
maybe_warnx("Z_NEED_DICT error");
goto stop_and_fail;
case Z_DATA_ERROR:
maybe_warnx("data stream error");
goto stop_and_fail;
case Z_STREAM_ERROR:
maybe_warnx("internal stream error");
goto stop_and_fail;
case Z_MEM_ERROR:
maybe_warnx("memory allocation error");
goto stop_and_fail;
default:
maybe_warn("unknown error from inflate(): %d",
error);
}
wr = BUFLEN - z.avail_out;
if (wr != 0) {
crc = crc32(crc, (const Bytef *)outbufp, (unsigned)wr);
if (
#ifndef SMALL
tflag == 0 &&
#endif
write_retry(out, outbufp, wr) != wr) {
maybe_warn("error writing to output");
goto stop_and_fail;
}
out_tot += wr;
out_sub_tot += wr;
}
if (error == Z_STREAM_END) {
inflateEnd(&z);
state++;
}
z.next_out = (unsigned char *)outbufp;
z.avail_out = BUFLEN;
break;
case GZSTATE_CRC:
{
uLong origcrc;
if (z.avail_in < 4) {
if (!done_reading) {
needmore = 1;
continue;
}
maybe_warnx("truncated input");
goto stop_and_fail;
}
origcrc = ((unsigned)z.next_in[0] & 0xff) |
((unsigned)z.next_in[1] & 0xff) << 8 |
((unsigned)z.next_in[2] & 0xff) << 16 |
((unsigned)z.next_in[3] & 0xff) << 24;
if (origcrc != crc) {
maybe_warnx("invalid compressed"
" data--crc error");
goto stop_and_fail;
}
}
z.avail_in -= 4;
z.next_in += 4;
if (!z.avail_in && done_reading) {
goto stop;
}
state++;
break;
case GZSTATE_LEN:
{
uLong origlen;
if (z.avail_in < 4) {
if (!done_reading) {
needmore = 1;
continue;
}
maybe_warnx("truncated input");
goto stop_and_fail;
}
origlen = ((unsigned)z.next_in[0] & 0xff) |
((unsigned)z.next_in[1] & 0xff) << 8 |
((unsigned)z.next_in[2] & 0xff) << 16 |
((unsigned)z.next_in[3] & 0xff) << 24;
if (origlen != out_sub_tot) {
maybe_warnx("invalid compressed"
" data--length error");
goto stop_and_fail;
}
}
z.avail_in -= 4;
z.next_in += 4;
if (error < 0) {
maybe_warnx("decompression error");
goto stop_and_fail;
}
state = GZSTATE_MAGIC0;
break;
}
continue;
stop_and_fail:
out_tot = -1;
stop:
break;
}
if (state > GZSTATE_INIT)
inflateEnd(&z);
free(inbufp);
out1:
free(outbufp);
out2:
if (gsizep)
*gsizep = in_tot;
return (out_tot);
}
#ifndef SMALL
static void
copymodes(int fd, const struct stat *sbp, const char *file)
{
struct timeval times[2];
struct stat sb;
if (sbp == NULL) {
mode_t mask = umask(022);
(void)fchmod(fd, DEFFILEMODE & ~mask);
(void)umask(mask);
return;
}
sb = *sbp;
if (fchown(fd, sb.st_uid, sb.st_gid) < 0) {
if (errno != EPERM)
maybe_warn("couldn't fchown: %s", file);
sb.st_mode &= ~(S_ISUID|S_ISGID);
}
sb.st_mode &= S_ISUID | S_ISGID | S_IRWXU | S_IRWXG | S_IRWXO;
if (fchmod(fd, sb.st_mode) < 0)
maybe_warn("couldn't fchmod: %s", file);
#if !HAVE_NBTOOL_CONFIG_H
TIMESPEC_TO_TIMEVAL(×[0], &sb.st_atimespec);
TIMESPEC_TO_TIMEVAL(×[1], &sb.st_mtimespec);
if (futimes(fd, times) < 0)
maybe_warn("couldn't utimes: %s", file);
if (sb.st_flags != 0 && fchflags(fd, sb.st_flags) < 0)
maybe_warn("couldn't fchflags: %s", file);
#endif
}
#endif
static enum filetype
file_gettype(u_char *buf)
{
if (buf[0] == GZIP_MAGIC0 &&
(buf[1] == GZIP_MAGIC1 || buf[1] == GZIP_OMAGIC1))
return FT_GZIP;
else
#ifndef NO_BZIP2_SUPPORT
if (memcmp(buf, BZIP2_MAGIC, 3) == 0 &&
buf[3] >= '0' && buf[3] <= '9')
return FT_BZIP2;
else
#endif
#ifndef NO_COMPRESS_SUPPORT
if (memcmp(buf, Z_MAGIC, 2) == 0)
return FT_Z;
else
#endif
#ifndef NO_PACK_SUPPORT
if (memcmp(buf, PACK_MAGIC, 2) == 0)
return FT_PACK;
else
#endif
#ifndef NO_XZ_SUPPORT
if (memcmp(buf, XZ_MAGIC, 4) == 0)
return FT_XZ;
else
#endif
#ifndef NO_ZSTD_SUPPORT
if (memcmp(buf, ZSTD_MAGIC, 4) == 0)
return FT_ZSTD;
else
#endif
#ifndef NO_LZ_SUPPORT
if (memcmp(buf, LZ_MAGIC, 4) == 0)
return FT_LZ;
else
#endif
return FT_UNKNOWN;
}
#ifndef SMALL
static int
check_outfile(const char *outfile)
{
struct stat sb;
int ok = 1;
if (lflag == 0 && stat(outfile, &sb) == 0) {
if (fflag)
unlink(outfile);
else if (isatty(STDIN_FILENO)) {
char ans[10] = { 'n', '\0' };
fprintf(stderr, "%s already exists -- do you wish to "
"overwrite (y or n)? " , outfile);
(void)fgets(ans, sizeof(ans) - 1, stdin);
if (ans[0] != 'y' && ans[0] != 'Y') {
fprintf(stderr, "\tnot overwriting\n");
ok = 0;
} else
unlink(outfile);
} else {
maybe_warnx("%s already exists -- skipping", outfile);
ok = 0;
}
}
return ok;
}
static void
unlink_input(const char *file, const struct stat *sb)
{
struct stat nsb;
if (kflag)
return;
if (stat(file, &nsb) != 0)
return;
if (nsb.st_dev != sb->st_dev || nsb.st_ino != sb->st_ino)
return;
unlink(file);
}
#ifdef SIGINFO
static void
got_siginfo(int signo)
{
print_info = 1;
}
#endif
static void
setup_signals(void)
{
#ifdef SIGINFO
signal(SIGINFO, got_siginfo);
#endif
}
static void
infile_newdata(size_t newdata)
{
infile_current += newdata;
}
#endif
static void
infile_set(const char *newinfile, off_t total)
{
if (newinfile)
infile = newinfile;
#ifndef SMALL
infile_total = total;
#endif
}
static void
infile_clear(void)
{
infile = NULL;
#ifndef SMALL
infile_total = infile_current = 0;
#endif
}
static const suffixes_t *
check_suffix(char *file, int xlate)
{
const suffixes_t *s;
int len = strlen(file);
char *sp;
for (s = suffixes; s != suffixes + NUM_SUFFIXES; s++) {
if (s->ziplen >= len)
continue;
sp = file + len - s->ziplen;
if (strcmp(s->zipped, sp) != 0)
continue;
if (xlate)
strcpy(sp, s->normal);
return s;
}
return NULL;
}
static off_t
file_compress(char *file, char *outfile, size_t outsize)
{
int in;
int out;
off_t size, in_size;
#ifndef SMALL
struct stat isb, osb;
const suffixes_t *suff;
#endif
in = open(file, O_RDONLY);
if (in == -1) {
maybe_warn("can't open %s", file);
return -1;
}
#ifndef SMALL
if (fstat(in, &isb) != 0) {
close(in);
maybe_warn("can't stat %s", file);
return -1;
}
infile_set(file, isb.st_size);
#endif
if (cflag == 0) {
#ifndef SMALL
if (isb.st_nlink > 1 && fflag == 0) {
maybe_warnx("%s has %d other link%s -- "
"skipping", file, isb.st_nlink - 1,
isb.st_nlink == 1 ? "" : "s");
close(in);
return -1;
}
if (fflag == 0 && (suff = check_suffix(file, 0))
&& suff->zipped[0] != 0) {
maybe_warnx("%s already has %s suffix -- unchanged",
file, suff->zipped);
close(in);
return -1;
}
#endif
if ((size_t)snprintf(outfile, outsize, "%s%s",
file, suffixes[0].zipped) >= outsize)
memcpy(outfile + outsize - suffixes[0].ziplen - 1,
suffixes[0].zipped, suffixes[0].ziplen + 1);
#ifndef SMALL
if (check_outfile(outfile) == 0) {
close(in);
return -1;
}
#endif
}
if (cflag == 0) {
out = open(outfile, O_WRONLY | O_CREAT | O_EXCL, 0600);
if (out == -1) {
maybe_warn("could not create output: %s", outfile);
fclose(stdin);
return -1;
}
} else
out = STDOUT_FILENO;
in_size = gz_compress(in, out, &size, basename(file), (uint32_t)isb.st_mtime);
(void)close(in);
if (cflag != 0)
return in_size == -1 ? -1 : size;
#ifndef SMALL
if (fstat(out, &osb) != 0) {
maybe_warn("couldn't stat: %s", outfile);
goto bad_outfile;
}
if (osb.st_size != size) {
maybe_warnx("output file: %s wrong size (%" PRIdOFF
" != %" PRIdOFF "), deleting",
outfile, osb.st_size, size);
goto bad_outfile;
}
copymodes(out, &isb, outfile);
#endif
if (close(out) == -1)
maybe_warn("couldn't close output");
unlink_input(file, &isb);
return size;
#ifndef SMALL
bad_outfile:
if (close(out) == -1)
maybe_warn("couldn't close output");
maybe_warnx("leaving original %s", file);
unlink(outfile);
return size;
#endif
}
static off_t
file_uncompress(char *file, char *outfile, size_t outsize)
{
struct stat isb, osb;
off_t size;
ssize_t rbytes;
unsigned char fourbytes[4];
enum filetype method;
int fd, ofd, zfd = -1;
size_t in_size;
#ifndef SMALL
ssize_t rv;
time_t timestamp = 0;
char name[PATH_MAX + 1];
#endif
fd = open(file, O_RDONLY);
if (fd < 0) {
maybe_warn("can't open %s", file);
goto lose;
}
if (fstat(fd, &isb) != 0) {
close(fd);
maybe_warn("can't stat %s", file);
goto lose;
}
if (S_ISREG(isb.st_mode))
in_size = isb.st_size;
else
in_size = 0;
infile_set(file, in_size);
strlcpy(outfile, file, outsize);
if (check_suffix(outfile, 1) == NULL && !(cflag || lflag)) {
maybe_warnx("%s: unknown suffix -- ignored", file);
goto lose;
}
rbytes = read(fd, fourbytes, sizeof fourbytes);
if (rbytes != sizeof fourbytes) {
#ifndef SMALL
if (fflag)
goto lose;
#endif
if (rbytes == -1)
maybe_warn("can't read %s", file);
else
goto unexpected_EOF;
goto lose;
}
infile_newdata(rbytes);
method = file_gettype(fourbytes);
#ifndef SMALL
if (fflag == 0 && method == FT_UNKNOWN) {
maybe_warnx("%s: not in gzip format", file);
goto lose;
}
#endif
#ifndef SMALL
if (method == FT_GZIP && Nflag) {
unsigned char ts[4];
rv = pread(fd, ts, sizeof ts, GZIP_TIMESTAMP);
if (rv >= 0 && rv < (ssize_t)(sizeof ts))
goto unexpected_EOF;
if (rv == -1) {
if (!fflag)
maybe_warn("can't read %s", file);
goto lose;
}
infile_newdata(rv);
timestamp = ts[3] << 24 | ts[2] << 16 | ts[1] << 8 | ts[0];
if (fourbytes[3] & ORIG_NAME) {
rbytes = pread(fd, name, sizeof(name) - 1, GZIP_ORIGNAME);
if (rbytes < 0) {
maybe_warn("can't read %s", file);
goto lose;
}
if (name[0] != '\0') {
char *dp, *nf;
name[rbytes] = '\0';
nf = strrchr(name, '/');
if (nf == NULL)
nf = name;
else
nf++;
dp = strrchr(file, '/');
if (dp == NULL)
dp = file;
else
dp++;
snprintf(outfile, outsize, "%.*s%.*s",
(int) (dp - file),
file, (int) rbytes, nf);
}
}
}
#endif
lseek(fd, 0, SEEK_SET);
if (cflag == 0 || lflag) {
#ifndef SMALL
if (isb.st_nlink > 1 && lflag == 0 && fflag == 0) {
maybe_warnx("%s has %d other links -- skipping",
file, isb.st_nlink - 1);
goto lose;
}
if (nflag == 0 && timestamp)
isb.st_mtime = timestamp;
if (check_outfile(outfile) == 0)
goto lose;
#endif
}
if (cflag)
zfd = STDOUT_FILENO;
else if (lflag)
zfd = -1;
else {
zfd = open(outfile, O_WRONLY|O_CREAT|O_EXCL, 0600);
if (zfd == STDOUT_FILENO) {
zfd = dup(zfd);
close(STDOUT_FILENO);
}
if (zfd == -1) {
maybe_warn("can't open %s", outfile);
goto lose;
}
}
switch (method) {
#ifndef NO_BZIP2_SUPPORT
case FT_BZIP2:
if (lflag) {
maybe_warnx("no -l with bzip2 files");
goto lose;
}
size = unbzip2(fd, zfd, NULL, 0, NULL);
break;
#endif
#ifndef NO_COMPRESS_SUPPORT
case FT_Z: {
FILE *in, *out;
if (lflag) {
maybe_warnx("no -l with Lempel-Ziv files");
goto lose;
}
if ((in = zdopen(fd)) == NULL) {
maybe_warn("zdopen for read: %s", file);
goto lose;
}
out = fdopen(dup(zfd), "w");
if (out == NULL) {
maybe_warn("fdopen for write: %s", outfile);
fclose(in);
goto lose;
}
size = zuncompress(in, out, NULL, 0, NULL);
if (ferror(in) | fclose(in)) {
maybe_warn("failed infile fclose");
unlink(outfile);
(void)fclose(out);
}
if (fclose(out) != 0) {
maybe_warn("failed outfile fclose");
unlink(outfile);
goto lose;
}
break;
}
#endif
#ifndef NO_PACK_SUPPORT
case FT_PACK:
if (lflag) {
maybe_warnx("no -l with packed files");
goto lose;
}
size = unpack(fd, zfd, NULL, 0, NULL);
break;
#endif
#ifndef NO_XZ_SUPPORT
case FT_XZ:
if (lflag) {
size = unxz_len(fd);
print_list_out(in_size, size, file);
return -1;
}
size = unxz(fd, zfd, NULL, 0, NULL);
break;
#endif
#ifndef NO_ZSTD_SUPPORT
case FT_ZSTD:
if (lflag) {
maybe_warnx("no -l with zstd files");
goto lose;
}
size = unzstd(fd, zfd, NULL, 0, NULL);
break;
#endif
#ifndef NO_LZ_SUPPORT
case FT_LZ:
if (lflag) {
maybe_warnx("no -l with lzip files");
goto lose;
}
size = unlz(fd, zfd, NULL, 0, NULL);
break;
#endif
#ifndef SMALL
case FT_UNKNOWN:
if (lflag) {
maybe_warnx("no -l for unknown filetypes");
goto lose;
}
size = cat_fd(NULL, 0, NULL, fd);
break;
#endif
default:
if (lflag) {
print_list(fd, in_size, outfile, isb.st_mtime);
close(fd);
return -1;
}
size = gz_uncompress(fd, zfd, NULL, 0, NULL, file);
break;
}
if (close(fd) != 0)
maybe_warn("couldn't close input");
if (zfd != STDOUT_FILENO && close(zfd) != 0)
maybe_warn("couldn't close output");
if (size == -1) {
if (cflag == 0)
unlink(outfile);
maybe_warnx("%s: uncompress failed", file);
return -1;
}
#ifndef SMALL
if (tflag)
return size;
#endif
if (cflag)
return size;
ofd = open(outfile, O_RDWR, 0);
if (ofd == -1) {
maybe_warn("couldn't open (leaving original): %s",
outfile);
return -1;
}
if (fstat(ofd, &osb) != 0) {
maybe_warn("couldn't stat (leaving original): %s",
outfile);
close(ofd);
return -1;
}
if (osb.st_size != size) {
maybe_warnx("stat gave different size: %" PRIdOFF
" != %" PRIdOFF " (leaving original)",
size, osb.st_size);
close(ofd);
unlink(outfile);
return -1;
}
unlink_input(file, &isb);
#ifndef SMALL
copymodes(ofd, &isb, outfile);
#endif
close(ofd);
return size;
unexpected_EOF:
maybe_warnx("%s: unexpected end of file", file);
lose:
if (fd != -1)
close(fd);
if (zfd != -1 && zfd != STDOUT_FILENO)
close(fd);
return -1;
}
#ifndef check_siginfo
static void
check_siginfo(void)
{
static int ttyfd = -2;
char buf[2048];
int n;
if (print_info == 0)
return;
if (!infile)
goto out;
if (ttyfd == -2)
ttyfd = open(_PATH_TTY, O_RDWR | O_CLOEXEC);
if (ttyfd == -1)
goto out;
if (infile_total) {
const double pcent = (100.0 * infile_current) / infile_total;
n = snprintf(buf, sizeof(buf),
"%s: %s: done %ju/%ju bytes (%3.2f%%)\n",
getprogname(), infile, (uintmax_t)infile_current,
(uintmax_t)infile_total, pcent);
} else {
n = snprintf(buf, sizeof(buf), "%s: %s: done %ju bytes\n",
getprogname(), infile, (uintmax_t)infile_current);
}
if (n <= 0)
goto out;
write(ttyfd, buf, (size_t)n);
out:
print_info = 0;
}
#endif
static off_t
cat_fd(unsigned char * prepend, size_t count, off_t *gsizep, int fd)
{
char buf[BUFLEN];
off_t in_tot;
ssize_t w;
in_tot = count;
w = write_retry(STDOUT_FILENO, prepend, count);
if (w == -1 || (size_t)w != count) {
maybe_warn("write to stdout");
return -1;
}
for (;;) {
ssize_t rv;
rv = read(fd, buf, sizeof buf);
if (rv == 0)
break;
if (rv < 0) {
maybe_warn("read from fd %d", fd);
break;
}
infile_newdata(rv);
if (write_retry(STDOUT_FILENO, buf, rv) != rv) {
maybe_warn("write to stdout");
break;
}
in_tot += rv;
}
if (gsizep)
*gsizep = in_tot;
return (in_tot);
}
static void
handle_fd_decomp(int fd)
{
struct stat isb;
unsigned char *prebuf = NULL;
size_t prebuf_len;
const size_t minbuf_len = 4;
size_t in_size;
off_t usize, gsize;
enum filetype method;
ssize_t bytes_read;
#ifndef NO_COMPRESS_SUPPORT
FILE *in;
#endif
#ifndef SMALL
if (fflag == 0 && lflag == 0 && isatty(fd)) {
maybe_warnx("standard input is a terminal -- ignoring");
goto out;
}
#endif
if (fstat(fd, &isb) < 0) {
maybe_warn("fstat");
goto out;
}
if (S_ISREG(isb.st_mode))
in_size = isb.st_size;
else
in_size = 0;
if (isb.st_blksize)
prebuf_len = isb.st_blksize;
else
prebuf_len = minbuf_len;
prebuf = malloc(prebuf_len);
if (prebuf == NULL)
maybe_err("malloc");
infile_set("(stdin)", in_size);
if (lflag) {
print_list(fd, in_size, infile, isb.st_mtime);
goto out;
}
bytes_read = read_retry(fd, prebuf, prebuf_len);
if (bytes_read == -1) {
maybe_warn("can't read stdin");
goto out;
} else if ((size_t)bytes_read < minbuf_len) {
maybe_warnx("(stdin): unexpected end of file");
goto out;
}
method = file_gettype(prebuf);
switch (method) {
default:
#ifndef SMALL
if (fflag == 0) {
maybe_warnx("unknown compression format");
goto out;
}
usize = cat_fd(prebuf, bytes_read, &gsize, fd);
break;
#endif
case FT_GZIP:
usize = gz_uncompress(fd, STDOUT_FILENO,
(char *)prebuf, bytes_read, &gsize, "(stdin)");
break;
#ifndef NO_BZIP2_SUPPORT
case FT_BZIP2:
usize = unbzip2(fd, STDOUT_FILENO,
(char *)prebuf, bytes_read, &gsize);
break;
#endif
#ifndef NO_COMPRESS_SUPPORT
case FT_Z:
if ((in = zdopen(fd)) == NULL) {
maybe_warnx("zopen of stdin");
goto out;
}
usize = zuncompress(in, stdout, (char *)prebuf,
bytes_read, &gsize);
fclose(in);
break;
#endif
#ifndef NO_PACK_SUPPORT
case FT_PACK:
usize = unpack(fd, STDOUT_FILENO,
(char *)prebuf, bytes_read, &gsize);
break;
#endif
#ifndef NO_XZ_SUPPORT
case FT_XZ:
usize = unxz(fd, STDOUT_FILENO,
(char *)prebuf, bytes_read, &gsize);
break;
#endif
#ifndef NO_ZSTD_SUPPORT
case FT_ZSTD:
usize = unzstd(fd, STDOUT_FILENO,
(char *)prebuf, bytes_read, &gsize);
break;
#endif
#ifndef NO_LZ_SUPPORT
case FT_LZ:
usize = unlz(fd, STDOUT_FILENO,
(char *)prebuf, bytes_read, &gsize);
break;
#endif
}
#ifndef SMALL
if (vflag && !tflag && usize != -1 && gsize != -1)
print_verbage(NULL, NULL, usize, gsize);
if (vflag && tflag)
print_test("(stdin)", usize != -1);
#else
(void)&usize;
#endif
out:
free(prebuf);
infile_clear();
}
static void
handle_stdout(void)
{
off_t gsize;
#ifndef SMALL
off_t usize;
struct stat sb;
time_t systime;
uint32_t mtime;
int ret;
infile_set("<stdout>", 0);
if (fflag == 0 && isatty(STDOUT_FILENO)) {
maybe_warnx("standard output is a terminal -- ignoring");
return;
}
ret = fstat(STDIN_FILENO, &sb);
if (ret < 0) {
maybe_warn("Can't stat stdin");
return;
}
if (S_ISREG(sb.st_mode)) {
infile_set("<stdout>", sb.st_size);
mtime = (uint32_t)sb.st_mtime;
} else {
systime = time(NULL);
if (systime == -1) {
maybe_warn("time");
return;
}
mtime = (uint32_t)systime;
}
usize =
#endif
gz_compress(STDIN_FILENO, STDOUT_FILENO, &gsize, "", mtime);
#ifndef SMALL
if (vflag && !tflag && usize != -1 && gsize != -1)
print_verbage(NULL, NULL, usize, gsize);
#endif
}
static void
handle_pathname(char *path)
{
char *opath = path, *s = NULL;
ssize_t len;
int slen;
struct stat sb;
if (path[0] == '-' && path[1] == '\0') {
if (dflag)
handle_fd_decomp(STDIN_FILENO);
else
handle_stdout();
return;
}
retry:
if (stat(path, &sb) != 0) {
if (dflag && s == NULL && errno == ENOENT) {
len = strlen(path);
slen = suffixes[0].ziplen;
s = malloc(len + slen + 1);
if (s == NULL)
maybe_err("malloc");
memcpy(s, path, len);
memcpy(s + len, suffixes[0].zipped, slen + 1);
path = s;
goto retry;
}
maybe_warn("can't stat: %s", opath);
goto out;
}
if (S_ISDIR(sb.st_mode)) {
#ifndef SMALL
if (rflag)
handle_dir(path);
else
#endif
maybe_warnx("%s is a directory", path);
goto out;
}
if (S_ISREG(sb.st_mode))
handle_file(path, &sb);
else if (dflag && cflag &&
(S_ISBLK(sb.st_mode) || S_ISCHR(sb.st_mode) ||
S_ISFIFO(sb.st_mode) || S_ISSOCK(sb.st_mode))) {
int fd;
fd = open(path, O_RDONLY);
if (fd < 0) {
maybe_warn("open: %s", path);
} else {
handle_fd_decomp(fd);
close(fd);
}
} else
maybe_warnx("%s is not a regular file", path);
out:
if (s)
free(s);
}
static void
handle_file(char *file, struct stat *sbp)
{
off_t usize, gsize;
char outfile[PATH_MAX];
infile_set(file, sbp->st_size);
if (dflag) {
usize = file_uncompress(file, outfile, sizeof(outfile));
#ifndef SMALL
if (vflag && tflag)
print_test(file, usize != -1);
#endif
if (usize == -1)
return;
gsize = sbp->st_size;
} else {
gsize = file_compress(file, outfile, sizeof(outfile));
if (gsize == -1)
return;
usize = sbp->st_size;
}
infile_clear();
#ifndef SMALL
if (vflag && !tflag)
print_verbage(file, (cflag) ? NULL : outfile, usize, gsize);
#endif
}
#ifndef SMALL
static void
handle_dir(char *dir)
{
char *path_argv[2];
FTS *fts;
FTSENT *entry;
path_argv[0] = dir;
path_argv[1] = 0;
fts = fts_open(path_argv, FTS_PHYSICAL | FTS_NOCHDIR, NULL);
if (fts == NULL) {
warn("couldn't fts_open %s", dir);
return;
}
while ((entry = fts_read(fts))) {
switch(entry->fts_info) {
case FTS_D:
case FTS_DP:
continue;
case FTS_DNR:
case FTS_ERR:
case FTS_NS:
maybe_warn("%s", entry->fts_path);
continue;
case FTS_F:
handle_file(entry->fts_path, entry->fts_statp);
}
}
(void)fts_close(fts);
}
#endif
static void
print_ratio(off_t in, off_t out, FILE *where)
{
int percent10;
off_t diff;
char buff[8];
int len;
diff = in - out/2;
if (in == 0 && out == 0)
percent10 = 0;
else if (diff < 0)
percent10 = -999;
else {
while (in > 0x100000) {
diff >>= 1;
in >>= 1;
}
if (in != 0)
percent10 = ((u_int)diff * 2000) / (u_int)in - 1000;
else
percent10 = 0;
}
len = snprintf(buff, sizeof buff, "%2.2d.", percent10);
buff[len - 1] = buff[len - 2];
buff[len - 2] = '.';
fprintf(where, "%5s%%", buff);
}
#ifndef SMALL
static void
print_verbage(const char *file, const char *nfile, off_t usize, off_t gsize)
{
if (file)
fprintf(stderr, "%s:%s ", file,
strlen(file) < 7 ? "\t\t" : "\t");
print_ratio(usize, gsize, stderr);
if (nfile)
fprintf(stderr, " -- replaced with %s", nfile);
fprintf(stderr, "\n");
fflush(stderr);
}
static void
print_test(const char *file, int ok)
{
if (exit_value == 0 && ok == 0)
exit_value = 1;
fprintf(stderr, "%s:%s %s\n", file,
strlen(file) < 7 ? "\t\t" : "\t", ok ? "OK" : "NOT OK");
fflush(stderr);
}
#endif
static void
print_list(int fd, off_t out, const char *outfile, time_t ts)
{
static int first = 1;
#ifndef SMALL
static off_t in_tot, out_tot;
uint32_t crc = 0;
#endif
off_t in = 0, rv;
if (first) {
#ifndef SMALL
if (vflag)
printf("method crc date time ");
#endif
if (qflag == 0)
printf(" compressed uncompressed "
"ratio uncompressed_name\n");
}
first = 0;
#ifndef SMALL
if (fd == -1) {
in = in_tot;
out = out_tot;
} else
#endif
{
rv = lseek(fd, (off_t)(-8), SEEK_END);
if (rv != -1) {
unsigned char buf[8];
uint32_t usize;
rv = read(fd, (char *)buf, sizeof(buf));
if (rv == -1)
maybe_warn("read of uncompressed size");
else if (rv != sizeof(buf))
maybe_warnx("read of uncompressed size");
else {
usize = buf[4];
usize |= (unsigned int)buf[5] << 8;
usize |= (unsigned int)buf[6] << 16;
usize |= (unsigned int)buf[7] << 24;
in = (off_t)usize;
#ifndef SMALL
crc = buf[0];
crc |= (unsigned int)buf[1] << 8;
crc |= (unsigned int)buf[2] << 16;
crc |= (unsigned int)buf[3] << 24;
#endif
}
}
}
#ifndef SMALL
if (vflag && fd == -1)
printf(" ");
else if (vflag) {
char *date = ctime(&ts);
date += 4;
date[12] = 0;
printf("%5s %08x %11s ", "defla", crc, date);
}
in_tot += in;
out_tot += out;
#endif
print_list_out(out, in, outfile);
}
static void
print_list_out(off_t out, off_t in, const char *outfile)
{
printf("%12llu %12llu ", (unsigned long long)out, (unsigned long long)in);
print_ratio(in, out, stdout);
printf(" %s\n", outfile);
}
#ifndef SMALL
static void
license(void)
{
fprintf(stderr, "%s\n", gzip_version);
fprintf(stderr, "%s\n",
"Copyright (c) 1997-2026 Matthew R. Green\n"
"All rights reserved.\n"
"Redistribution and use in source and binary forms, with or without\n"
"modification, are permitted provided that the following conditions\n"
"are met:\n"
"1. Redistributions of source code must retain the above copyright\n"
"notice, this list of conditions and the following disclaimer.\n"
"2. Redistributions in binary form must reproduce the above copyright\n"
"notice, this list of conditions and the following disclaimer in the\n"
"documentation and/or other materials provided with the distribution.");
exit(0);
}
#endif
static void
usage(void)
{
fprintf(stderr, "%s\n", gzip_version);
fprintf(stderr,
"usage: %s [-" OPT_LIST "] [<file> [<file> ...]]\n"
#ifndef SMALL
" -1 --fast fastest (worst) compression\n"
" -2 .. -8 set compression level\n"
" -9 --best best (slowest) compression\n"
" -a --ascii ignored on NetBSD\n"
" -c --stdout write to stdout, keep original files\n"
" --to-stdout\n"
" -d --decompress uncompress files\n"
" --uncompress\n"
" -f --force force overwriting & compress links\n"
" -h --help display this help\n"
" -k --keep don't delete input files during operation\n"
" -L --license show the program copyright and license\n"
" -l --list list compressed file contents\n"
" -N --name save or restore original file name and time stamp\n"
" -n --no-name don't save original file name or time stamp\n"
" -q --quiet output no warnings\n"
" -r --recursive recursively compress files in directories\n"
" -S .suf use suffix .suf instead of .gz\n"
" --suffix .suf\n"
" -t --test test compressed file\n"
" -V --version display program version\n"
" -v --verbose print extra statistics\n",
#else
,
#endif
getprogname());
exit(0);
}
static void
display_version(void)
{
fprintf(stderr, "%s\n", gzip_version);
exit(0);
}
#ifndef NO_BZIP2_SUPPORT
#include "unbzip2.c"
#endif
#ifndef NO_COMPRESS_SUPPORT
#include "zuncompress.c"
#endif
#ifndef NO_PACK_SUPPORT
#include "unpack.c"
#endif
#ifndef NO_XZ_SUPPORT
#include "unxz.c"
#endif
#ifndef NO_ZSTD_SUPPORT
#include "unzstd.c"
#endif
#ifndef NO_LZ_SUPPORT
#include "unlz.c"
#endif
static ssize_t
read_retry(int fd, void *buf, size_t sz)
{
char *cp = buf;
size_t left = MIN(sz, (size_t) SSIZE_MAX);
while (left > 0) {
ssize_t ret;
ret = read(fd, cp, left);
if (ret == -1) {
return ret;
} else if (ret == 0) {
break;
}
cp += ret;
left -= ret;
}
return sz - left;
}
static ssize_t
write_retry(int fd, const void *buf, size_t sz)
{
const char *cp = buf;
size_t left = MIN(sz, (size_t) SSIZE_MAX);
while (left > 0) {
ssize_t ret;
ret = write(fd, cp, left);
if (ret == -1) {
return ret;
} else if (ret == 0) {
abort();
}
cp += ret;
left -= ret;
}
return sz - left;
}