compress.c revision 116336
11590Srgrimes/*-
21590Srgrimes * Copyright (c) 1992, 1993
31590Srgrimes *	The Regents of the University of California.  All rights reserved.
41590Srgrimes *
51590Srgrimes * Redistribution and use in source and binary forms, with or without
61590Srgrimes * modification, are permitted provided that the following conditions
71590Srgrimes * are met:
81590Srgrimes * 1. Redistributions of source code must retain the above copyright
91590Srgrimes *    notice, this list of conditions and the following disclaimer.
101590Srgrimes * 2. Redistributions in binary form must reproduce the above copyright
111590Srgrimes *    notice, this list of conditions and the following disclaimer in the
121590Srgrimes *    documentation and/or other materials provided with the distribution.
131590Srgrimes * 3. All advertising materials mentioning features or use of this software
141590Srgrimes *    must display the following acknowledgement:
151590Srgrimes *	This product includes software developed by the University of
161590Srgrimes *	California, Berkeley and its contributors.
171590Srgrimes * 4. Neither the name of the University nor the names of its contributors
181590Srgrimes *    may be used to endorse or promote products derived from this software
191590Srgrimes *    without specific prior written permission.
201590Srgrimes *
211590Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
221590Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
231590Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
241590Srgrimes * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
251590Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
261590Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
271590Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
281590Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
291590Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
301590Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
311590Srgrimes * SUCH DAMAGE.
321590Srgrimes */
331590Srgrimes
341590Srgrimes#ifndef lint
3541568Sarchiestatic const char copyright[] =
361590Srgrimes"@(#) Copyright (c) 1992, 1993\n\
371590Srgrimes	The Regents of the University of California.  All rights reserved.\n";
3887247Smarkm#endif
391590Srgrimes
4087628Sdwmalone#if 0
411590Srgrimes#ifndef lint
4287628Sdwmalonestatic char sccsid[] = "@(#)compress.c	8.2 (Berkeley) 1/7/94";
4358630Scharnier#endif
4487628Sdwmalone#endif
451590Srgrimes
4687628Sdwmalone#include <sys/cdefs.h>
4787628Sdwmalone__FBSDID("$FreeBSD: head/usr.bin/compress/compress.c 116336 2003-06-14 13:41:31Z trhodes $");
4887628Sdwmalone
491590Srgrimes#include <sys/param.h>
501590Srgrimes#include <sys/stat.h>
5166907Swollman#include <sys/time.h>
521590Srgrimes
531590Srgrimes#include <err.h>
541590Srgrimes#include <errno.h>
5593055Simp#include <stdarg.h>
561590Srgrimes#include <stdio.h>
571590Srgrimes#include <stdlib.h>
581590Srgrimes#include <string.h>
591590Srgrimes#include <unistd.h>
601590Srgrimes
6118053Sbde#include "zopen.h"
6218053Sbde
6392920Simpvoid	compress(const char *, const char *, int);
6492920Simpvoid	cwarn(const char *, ...) __printflike(1, 2);
6592920Simpvoid	cwarnx(const char *, ...) __printflike(1, 2);
6692920Simpvoid	decompress(const char *, const char *, int);
6792920Simpint	permission(const char *);
6892920Simpvoid	setfile(const char *, struct stat *);
6992920Simpvoid	usage(int);
701590Srgrimes
711590Srgrimesint eval, force, verbose;
721590Srgrimes
731590Srgrimesint
74100820Sdwmalonemain(int argc, char *argv[])
751590Srgrimes{
7640547Sbde	enum {COMPRESS, DECOMPRESS} style;
771590Srgrimes	size_t len;
781590Srgrimes	int bits, cat, ch;
791590Srgrimes	char *p, newname[MAXPATHLEN];
801590Srgrimes
8140547Sbde	cat = 0;
821590Srgrimes	if ((p = rindex(argv[0], '/')) == NULL)
831590Srgrimes		p = argv[0];
841590Srgrimes	else
851590Srgrimes		++p;
861590Srgrimes	if (!strcmp(p, "uncompress"))
871590Srgrimes		style = DECOMPRESS;
888874Srgrimes	else if (!strcmp(p, "compress"))
891590Srgrimes		style = COMPRESS;
9040534Smsmith	else if (!strcmp(p, "zcat")) {
9140547Sbde		cat = 1;
9240534Smsmith		style = DECOMPRESS;
9340534Smsmith	} else
941590Srgrimes		errx(1, "unknown program name");
951590Srgrimes
9640547Sbde	bits = 0;
9724360Simp	while ((ch = getopt(argc, argv, "b:cdfv")) != -1)
981590Srgrimes		switch(ch) {
991590Srgrimes		case 'b':
1001590Srgrimes			bits = strtol(optarg, &p, 10);
1011590Srgrimes			if (*p)
1021590Srgrimes				errx(1, "illegal bit count -- %s", optarg);
1031590Srgrimes			break;
1041590Srgrimes		case 'c':
1051590Srgrimes			cat = 1;
1061590Srgrimes			break;
1071590Srgrimes		case 'd':		/* Backward compatible. */
1081590Srgrimes			style = DECOMPRESS;
1091590Srgrimes			break;
1101590Srgrimes		case 'f':
1111590Srgrimes			force = 1;
1121590Srgrimes			break;
1131590Srgrimes		case 'v':
1141590Srgrimes			verbose = 1;
1151590Srgrimes			break;
1161590Srgrimes		case '?':
1171590Srgrimes		default:
1181590Srgrimes			usage(style == COMPRESS);
1191590Srgrimes		}
1201590Srgrimes	argc -= optind;
1211590Srgrimes	argv += optind;
1221590Srgrimes
1231590Srgrimes	if (argc == 0) {
1241590Srgrimes		switch(style) {
1251590Srgrimes		case COMPRESS:
1261590Srgrimes			(void)compress("/dev/stdin", "/dev/stdout", bits);
1271590Srgrimes			break;
1281590Srgrimes		case DECOMPRESS:
1291590Srgrimes			(void)decompress("/dev/stdin", "/dev/stdout", bits);
1301590Srgrimes			break;
1311590Srgrimes		}
1321590Srgrimes		exit (eval);
1331590Srgrimes	}
1341590Srgrimes
1351590Srgrimes	if (cat == 1 && argc > 1)
1361590Srgrimes		errx(1, "the -c option permits only a single file argument");
1371590Srgrimes
1381590Srgrimes	for (; *argv; ++argv)
1391590Srgrimes		switch(style) {
1401590Srgrimes		case COMPRESS:
14196772Stjr			if (strcmp(*argv, "-") == 0) {
14296772Stjr				compress("/dev/stdin", "/dev/stdout", bits);
14396772Stjr				break;
14496772Stjr			} else if (cat) {
1451590Srgrimes				compress(*argv, "/dev/stdout", bits);
1461590Srgrimes				break;
1471590Srgrimes			}
1481590Srgrimes			if ((p = rindex(*argv, '.')) != NULL &&
1491590Srgrimes			    !strcmp(p, ".Z")) {
1501590Srgrimes				cwarnx("%s: name already has trailing .Z",
1511590Srgrimes				    *argv);
1521590Srgrimes				break;
1531590Srgrimes			}
1541590Srgrimes			len = strlen(*argv);
1551590Srgrimes			if (len > sizeof(newname) - 3) {
1561590Srgrimes				cwarnx("%s: name too long", *argv);
1571590Srgrimes				break;
1581590Srgrimes			}
1591590Srgrimes			memmove(newname, *argv, len);
1601590Srgrimes			newname[len] = '.';
1611590Srgrimes			newname[len + 1] = 'Z';
1621590Srgrimes			newname[len + 2] = '\0';
1631590Srgrimes			compress(*argv, newname, bits);
1641590Srgrimes			break;
1651590Srgrimes		case DECOMPRESS:
16696772Stjr			if (strcmp(*argv, "-") == 0) {
16796772Stjr				decompress("/dev/stdin", "/dev/stdout", bits);
16896772Stjr				break;
16996772Stjr			}
1701590Srgrimes			len = strlen(*argv);
1711590Srgrimes			if ((p = rindex(*argv, '.')) == NULL ||
1721590Srgrimes			    strcmp(p, ".Z")) {
1731590Srgrimes				if (len > sizeof(newname) - 3) {
1741590Srgrimes					cwarnx("%s: name too long", *argv);
1751590Srgrimes					break;
1761590Srgrimes				}
1771590Srgrimes				memmove(newname, *argv, len);
1781590Srgrimes				newname[len] = '.';
1791590Srgrimes				newname[len + 1] = 'Z';
1801590Srgrimes				newname[len + 2] = '\0';
1811590Srgrimes				decompress(newname,
1821590Srgrimes				    cat ? "/dev/stdout" : *argv, bits);
1831590Srgrimes			} else {
1841590Srgrimes				if (len - 2 > sizeof(newname) - 1) {
1851590Srgrimes					cwarnx("%s: name too long", *argv);
1861590Srgrimes					break;
1871590Srgrimes				}
1881590Srgrimes				memmove(newname, *argv, len - 2);
1891590Srgrimes				newname[len - 2] = '\0';
1901590Srgrimes				decompress(*argv,
1911590Srgrimes				    cat ? "/dev/stdout" : newname, bits);
1921590Srgrimes			}
1931590Srgrimes			break;
1941590Srgrimes		}
1951590Srgrimes	exit (eval);
1961590Srgrimes}
1971590Srgrimes
1981590Srgrimesvoid
199100820Sdwmalonecompress(const char *in, const char *out, int bits)
2001590Srgrimes{
20187214Smarkm	size_t nr;
2021590Srgrimes	struct stat isb, sb;
2031590Srgrimes	FILE *ifp, *ofp;
2041590Srgrimes	int exists, isreg, oreg;
2051590Srgrimes	u_char buf[1024];
2061590Srgrimes
2071590Srgrimes	exists = !stat(out, &sb);
2081590Srgrimes	if (!force && exists && S_ISREG(sb.st_mode) && !permission(out))
2091590Srgrimes		return;
2101590Srgrimes	isreg = oreg = !exists || S_ISREG(sb.st_mode);
2111590Srgrimes
2121590Srgrimes	ifp = ofp = NULL;
2131590Srgrimes	if ((ifp = fopen(in, "r")) == NULL) {
2141590Srgrimes		cwarn("%s", in);
2151590Srgrimes		return;
2161590Srgrimes	}
2171590Srgrimes	if (stat(in, &isb)) {		/* DON'T FSTAT! */
2181590Srgrimes		cwarn("%s", in);
2191590Srgrimes		goto err;
2201590Srgrimes	}
2211590Srgrimes	if (!S_ISREG(isb.st_mode))
2221590Srgrimes		isreg = 0;
2231590Srgrimes
2241590Srgrimes	if ((ofp = zopen(out, "w", bits)) == NULL) {
2251590Srgrimes		cwarn("%s", out);
2261590Srgrimes		goto err;
2271590Srgrimes	}
2281590Srgrimes	while ((nr = fread(buf, 1, sizeof(buf), ifp)) != 0)
2291590Srgrimes		if (fwrite(buf, 1, nr, ofp) != nr) {
2301590Srgrimes			cwarn("%s", out);
2311590Srgrimes			goto err;
2321590Srgrimes		}
2331590Srgrimes
2341590Srgrimes	if (ferror(ifp) || fclose(ifp)) {
2351590Srgrimes		cwarn("%s", in);
2361590Srgrimes		goto err;
2371590Srgrimes	}
2381590Srgrimes	ifp = NULL;
2391590Srgrimes
2401590Srgrimes	if (fclose(ofp)) {
2411590Srgrimes		cwarn("%s", out);
2421590Srgrimes		goto err;
2431590Srgrimes	}
2441590Srgrimes	ofp = NULL;
2451590Srgrimes
2461590Srgrimes	if (isreg) {
2471590Srgrimes		if (stat(out, &sb)) {
2481590Srgrimes			cwarn("%s", out);
2491590Srgrimes			goto err;
2501590Srgrimes		}
2511590Srgrimes
2521590Srgrimes		if (!force && sb.st_size >= isb.st_size) {
2531590Srgrimes			if (verbose)
25496770Stjr		(void)fprintf(stderr, "%s: file would grow; left unmodified\n",
25596770Stjr		    in);
25696769Stjr			eval = 2;
2571590Srgrimes			if (unlink(out))
2581590Srgrimes				cwarn("%s", out);
2591590Srgrimes			goto err;
2601590Srgrimes		}
2611590Srgrimes
2621590Srgrimes		setfile(out, &isb);
2631590Srgrimes
2641590Srgrimes		if (unlink(in))
2651590Srgrimes			cwarn("%s", in);
2661590Srgrimes
2671590Srgrimes		if (verbose) {
26896770Stjr			(void)fprintf(stderr, "%s: ", out);
2691590Srgrimes			if (isb.st_size > sb.st_size)
27096770Stjr				(void)fprintf(stderr, "%.0f%% compression\n",
2711590Srgrimes				    ((float)sb.st_size / isb.st_size) * 100.0);
2721590Srgrimes			else
27396770Stjr				(void)fprintf(stderr, "%.0f%% expansion\n",
2741590Srgrimes				    ((float)isb.st_size / sb.st_size) * 100.0);
2751590Srgrimes		}
2761590Srgrimes	}
2771590Srgrimes	return;
2781590Srgrimes
2791590Srgrimeserr:	if (ofp) {
2801590Srgrimes		if (oreg)
2811590Srgrimes			(void)unlink(out);
2821590Srgrimes		(void)fclose(ofp);
2831590Srgrimes	}
2841590Srgrimes	if (ifp)
2851590Srgrimes		(void)fclose(ifp);
2861590Srgrimes}
2871590Srgrimes
2881590Srgrimesvoid
289100820Sdwmalonedecompress(const char *in, const char *out, int bits)
2901590Srgrimes{
29187214Smarkm	size_t nr;
2921590Srgrimes	struct stat sb;
2931590Srgrimes	FILE *ifp, *ofp;
2941590Srgrimes	int exists, isreg, oreg;
2951590Srgrimes	u_char buf[1024];
2961590Srgrimes
2971590Srgrimes	exists = !stat(out, &sb);
2981590Srgrimes	if (!force && exists && S_ISREG(sb.st_mode) && !permission(out))
2991590Srgrimes		return;
3001590Srgrimes	isreg = oreg = !exists || S_ISREG(sb.st_mode);
3011590Srgrimes
3021590Srgrimes	ifp = ofp = NULL;
3031590Srgrimes	if ((ifp = zopen(in, "r", bits)) == NULL) {
3041590Srgrimes		cwarn("%s", in);
305116336Strhodes		return;
3061590Srgrimes	}
3071590Srgrimes	if (stat(in, &sb)) {
3081590Srgrimes		cwarn("%s", in);
3091590Srgrimes		goto err;
3101590Srgrimes	}
3111590Srgrimes	if (!S_ISREG(sb.st_mode))
3121590Srgrimes		isreg = 0;
3131590Srgrimes
314116336Strhodes	/*
315116336Strhodes	 * Try to read the first few uncompressed bytes from the input file
316116336Strhodes	 * before blindly truncating the output file.
317116336Strhodes	 */
318116336Strhodes	if ((nr = fread(buf, 1, sizeof(buf), ifp)) == 0) {
319116336Strhodes		cwarn("%s", in);
320116336Strhodes		(void)fclose(ifp);
321116336Strhodes		return;
322116336Strhodes	}
323116336Strhodes	if ((ofp = fopen(out, "w")) == NULL ||
324116336Strhodes	    (nr != 0 && fwrite(buf, 1, nr, ofp) != nr)) {
325116336Strhodes		cwarn("%s", out);
326116336Strhodes		(void)fclose(ifp);
327116336Strhodes		return;
328116336Strhodes	}
329116336Strhodes
3301590Srgrimes	while ((nr = fread(buf, 1, sizeof(buf), ifp)) != 0)
3311590Srgrimes		if (fwrite(buf, 1, nr, ofp) != nr) {
3321590Srgrimes			cwarn("%s", out);
3331590Srgrimes			goto err;
3341590Srgrimes		}
3351590Srgrimes
3361590Srgrimes	if (ferror(ifp) || fclose(ifp)) {
3371590Srgrimes		cwarn("%s", in);
3381590Srgrimes		goto err;
3391590Srgrimes	}
3401590Srgrimes	ifp = NULL;
3411590Srgrimes
3421590Srgrimes	if (fclose(ofp)) {
3431590Srgrimes		cwarn("%s", out);
3441590Srgrimes		goto err;
3451590Srgrimes	}
3461590Srgrimes
3471590Srgrimes	if (isreg) {
3481590Srgrimes		setfile(out, &sb);
3491590Srgrimes
3501590Srgrimes		if (unlink(in))
3511590Srgrimes			cwarn("%s", in);
3521590Srgrimes	}
3531590Srgrimes	return;
3541590Srgrimes
3551590Srgrimeserr:	if (ofp) {
3561590Srgrimes		if (oreg)
3571590Srgrimes			(void)unlink(out);
3581590Srgrimes		(void)fclose(ofp);
3591590Srgrimes	}
3601590Srgrimes	if (ifp)
3611590Srgrimes		(void)fclose(ifp);
3621590Srgrimes}
3631590Srgrimes
3641590Srgrimesvoid
365100820Sdwmalonesetfile(const char *name, struct stat *fs)
3661590Srgrimes{
3671590Srgrimes	static struct timeval tv[2];
3681590Srgrimes
3691590Srgrimes	fs->st_mode &= S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO;
3701590Srgrimes
3711590Srgrimes	TIMESPEC_TO_TIMEVAL(&tv[0], &fs->st_atimespec);
3721590Srgrimes	TIMESPEC_TO_TIMEVAL(&tv[1], &fs->st_mtimespec);
3731590Srgrimes	if (utimes(name, tv))
3741590Srgrimes		cwarn("utimes: %s", name);
3751590Srgrimes
3761590Srgrimes	/*
3771590Srgrimes	 * Changing the ownership probably won't succeed, unless we're root
3781590Srgrimes	 * or POSIX_CHOWN_RESTRICTED is not set.  Set uid/gid before setting
3791590Srgrimes	 * the mode; current BSD behavior is to remove all setuid bits on
3801590Srgrimes	 * chown.  If chown fails, lose setuid/setgid bits.
3811590Srgrimes	 */
3821590Srgrimes	if (chown(name, fs->st_uid, fs->st_gid)) {
3831590Srgrimes		if (errno != EPERM)
3841590Srgrimes			cwarn("chown: %s", name);
3851590Srgrimes		fs->st_mode &= ~(S_ISUID|S_ISGID);
3861590Srgrimes	}
38760622Shoek	if (chmod(name, fs->st_mode) && errno != EOPNOTSUPP)
38858630Scharnier		cwarn("chmod: %s", name);
3891590Srgrimes
39060622Shoek	if (chflags(name, fs->st_flags) && errno != EOPNOTSUPP)
3911590Srgrimes		cwarn("chflags: %s", name);
3921590Srgrimes}
3931590Srgrimes
3941590Srgrimesint
395100820Sdwmalonepermission(const char *fname)
3961590Srgrimes{
3971590Srgrimes	int ch, first;
3981590Srgrimes
3991590Srgrimes	if (!isatty(fileno(stderr)))
4001590Srgrimes		return (0);
4011590Srgrimes	(void)fprintf(stderr, "overwrite %s? ", fname);
4021590Srgrimes	first = ch = getchar();
4031590Srgrimes	while (ch != '\n' && ch != EOF)
4041590Srgrimes		ch = getchar();
4051590Srgrimes	return (first == 'y');
4061590Srgrimes}
4071590Srgrimes
4081590Srgrimesvoid
409100820Sdwmaloneusage(int iscompress)
4101590Srgrimes{
4111590Srgrimes	if (iscompress)
4121590Srgrimes		(void)fprintf(stderr,
4131590Srgrimes		    "usage: compress [-cfv] [-b bits] [file ...]\n");
4141590Srgrimes	else
4151590Srgrimes		(void)fprintf(stderr,
4161590Srgrimes		    "usage: uncompress [-c] [-b bits] [file ...]\n");
4171590Srgrimes	exit(1);
4181590Srgrimes}
4191590Srgrimes
4201590Srgrimesvoid
4211590Srgrimescwarnx(const char *fmt, ...)
4221590Srgrimes{
4231590Srgrimes	va_list ap;
42493055Simp
4251590Srgrimes	va_start(ap, fmt);
4261590Srgrimes	vwarnx(fmt, ap);
4271590Srgrimes	va_end(ap);
4281590Srgrimes	eval = 1;
4291590Srgrimes}
4301590Srgrimes
4311590Srgrimesvoid
4321590Srgrimescwarn(const char *fmt, ...)
4331590Srgrimes{
4341590Srgrimes	va_list ap;
43593055Simp
4361590Srgrimes	va_start(ap, fmt);
4371590Srgrimes	vwarn(fmt, ap);
4381590Srgrimes	va_end(ap);
4391590Srgrimes	eval = 1;
4401590Srgrimes}
441