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 * 4. Neither the name of the University nor the names of its contributors
141590Srgrimes *    may be used to endorse or promote products derived from this software
151590Srgrimes *    without specific prior written permission.
161590Srgrimes *
171590Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
181590Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
191590Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
201590Srgrimes * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
211590Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
221590Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
231590Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
241590Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
251590Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
261590Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
271590Srgrimes * SUCH DAMAGE.
281590Srgrimes */
291590Srgrimes
301590Srgrimes#ifndef lint
3141568Sarchiestatic const char copyright[] =
321590Srgrimes"@(#) Copyright (c) 1992, 1993\n\
331590Srgrimes	The Regents of the University of California.  All rights reserved.\n";
3487247Smarkm#endif
351590Srgrimes
3687628Sdwmalone#if 0
371590Srgrimes#ifndef lint
3887628Sdwmalonestatic char sccsid[] = "@(#)compress.c	8.2 (Berkeley) 1/7/94";
3958630Scharnier#endif
4087628Sdwmalone#endif
411590Srgrimes
4287628Sdwmalone#include <sys/cdefs.h>
4387628Sdwmalone__FBSDID("$FreeBSD$");
4487628Sdwmalone
451590Srgrimes#include <sys/param.h>
461590Srgrimes#include <sys/stat.h>
4766907Swollman#include <sys/time.h>
481590Srgrimes
491590Srgrimes#include <err.h>
501590Srgrimes#include <errno.h>
5193055Simp#include <stdarg.h>
521590Srgrimes#include <stdio.h>
531590Srgrimes#include <stdlib.h>
541590Srgrimes#include <string.h>
551590Srgrimes#include <unistd.h>
561590Srgrimes
5718053Sbde#include "zopen.h"
5818053Sbde
59227236Sedstatic void	compress(const char *, const char *, int);
60227236Sedstatic void	cwarn(const char *, ...) __printflike(1, 2);
61227236Sedstatic void	cwarnx(const char *, ...) __printflike(1, 2);
62227236Sedstatic void	decompress(const char *, const char *, int);
63227236Sedstatic int	permission(const char *);
64227236Sedstatic void	setfile(const char *, struct stat *);
65227236Sedstatic void	usage(int);
661590Srgrimes
67227236Sedstatic int eval, force, verbose;
681590Srgrimes
691590Srgrimesint
70100820Sdwmalonemain(int argc, char *argv[])
711590Srgrimes{
7240547Sbde	enum {COMPRESS, DECOMPRESS} style;
731590Srgrimes	size_t len;
741590Srgrimes	int bits, cat, ch;
751590Srgrimes	char *p, newname[MAXPATHLEN];
761590Srgrimes
7740547Sbde	cat = 0;
78229403Sed	if ((p = strrchr(argv[0], '/')) == NULL)
791590Srgrimes		p = argv[0];
801590Srgrimes	else
811590Srgrimes		++p;
821590Srgrimes	if (!strcmp(p, "uncompress"))
831590Srgrimes		style = DECOMPRESS;
848874Srgrimes	else if (!strcmp(p, "compress"))
851590Srgrimes		style = COMPRESS;
8640534Smsmith	else if (!strcmp(p, "zcat")) {
8740547Sbde		cat = 1;
8840534Smsmith		style = DECOMPRESS;
8940534Smsmith	} else
901590Srgrimes		errx(1, "unknown program name");
911590Srgrimes
9240547Sbde	bits = 0;
9324360Simp	while ((ch = getopt(argc, argv, "b:cdfv")) != -1)
941590Srgrimes		switch(ch) {
951590Srgrimes		case 'b':
961590Srgrimes			bits = strtol(optarg, &p, 10);
971590Srgrimes			if (*p)
981590Srgrimes				errx(1, "illegal bit count -- %s", optarg);
991590Srgrimes			break;
1001590Srgrimes		case 'c':
1011590Srgrimes			cat = 1;
1021590Srgrimes			break;
1031590Srgrimes		case 'd':		/* Backward compatible. */
1041590Srgrimes			style = DECOMPRESS;
1051590Srgrimes			break;
1061590Srgrimes		case 'f':
1071590Srgrimes			force = 1;
1081590Srgrimes			break;
1091590Srgrimes		case 'v':
1101590Srgrimes			verbose = 1;
1111590Srgrimes			break;
1121590Srgrimes		case '?':
1131590Srgrimes		default:
1141590Srgrimes			usage(style == COMPRESS);
1151590Srgrimes		}
1161590Srgrimes	argc -= optind;
1171590Srgrimes	argv += optind;
1181590Srgrimes
1191590Srgrimes	if (argc == 0) {
1201590Srgrimes		switch(style) {
1211590Srgrimes		case COMPRESS:
1221590Srgrimes			(void)compress("/dev/stdin", "/dev/stdout", bits);
1231590Srgrimes			break;
1241590Srgrimes		case DECOMPRESS:
1251590Srgrimes			(void)decompress("/dev/stdin", "/dev/stdout", bits);
1261590Srgrimes			break;
1271590Srgrimes		}
1281590Srgrimes		exit (eval);
1291590Srgrimes	}
1301590Srgrimes
1311590Srgrimes	if (cat == 1 && argc > 1)
1321590Srgrimes		errx(1, "the -c option permits only a single file argument");
1331590Srgrimes
1341590Srgrimes	for (; *argv; ++argv)
1351590Srgrimes		switch(style) {
1361590Srgrimes		case COMPRESS:
13796772Stjr			if (strcmp(*argv, "-") == 0) {
13896772Stjr				compress("/dev/stdin", "/dev/stdout", bits);
13996772Stjr				break;
14096772Stjr			} else if (cat) {
1411590Srgrimes				compress(*argv, "/dev/stdout", bits);
1421590Srgrimes				break;
1431590Srgrimes			}
144229403Sed			if ((p = strrchr(*argv, '.')) != NULL &&
1451590Srgrimes			    !strcmp(p, ".Z")) {
1461590Srgrimes				cwarnx("%s: name already has trailing .Z",
1471590Srgrimes				    *argv);
1481590Srgrimes				break;
1491590Srgrimes			}
1501590Srgrimes			len = strlen(*argv);
1511590Srgrimes			if (len > sizeof(newname) - 3) {
1521590Srgrimes				cwarnx("%s: name too long", *argv);
1531590Srgrimes				break;
1541590Srgrimes			}
1551590Srgrimes			memmove(newname, *argv, len);
1561590Srgrimes			newname[len] = '.';
1571590Srgrimes			newname[len + 1] = 'Z';
1581590Srgrimes			newname[len + 2] = '\0';
1591590Srgrimes			compress(*argv, newname, bits);
1601590Srgrimes			break;
1611590Srgrimes		case DECOMPRESS:
16296772Stjr			if (strcmp(*argv, "-") == 0) {
16396772Stjr				decompress("/dev/stdin", "/dev/stdout", bits);
16496772Stjr				break;
16596772Stjr			}
1661590Srgrimes			len = strlen(*argv);
167229403Sed			if ((p = strrchr(*argv, '.')) == NULL ||
1681590Srgrimes			    strcmp(p, ".Z")) {
1691590Srgrimes				if (len > sizeof(newname) - 3) {
1701590Srgrimes					cwarnx("%s: name too long", *argv);
1711590Srgrimes					break;
1721590Srgrimes				}
1731590Srgrimes				memmove(newname, *argv, len);
1741590Srgrimes				newname[len] = '.';
1751590Srgrimes				newname[len + 1] = 'Z';
1761590Srgrimes				newname[len + 2] = '\0';
1771590Srgrimes				decompress(newname,
1781590Srgrimes				    cat ? "/dev/stdout" : *argv, bits);
1791590Srgrimes			} else {
1801590Srgrimes				if (len - 2 > sizeof(newname) - 1) {
1811590Srgrimes					cwarnx("%s: name too long", *argv);
1821590Srgrimes					break;
1831590Srgrimes				}
1841590Srgrimes				memmove(newname, *argv, len - 2);
1851590Srgrimes				newname[len - 2] = '\0';
1861590Srgrimes				decompress(*argv,
1871590Srgrimes				    cat ? "/dev/stdout" : newname, bits);
1881590Srgrimes			}
1891590Srgrimes			break;
1901590Srgrimes		}
1911590Srgrimes	exit (eval);
1921590Srgrimes}
1931590Srgrimes
194227236Sedstatic void
195100820Sdwmalonecompress(const char *in, const char *out, int bits)
1961590Srgrimes{
19787214Smarkm	size_t nr;
1981590Srgrimes	struct stat isb, sb;
1991590Srgrimes	FILE *ifp, *ofp;
2001590Srgrimes	int exists, isreg, oreg;
2011590Srgrimes	u_char buf[1024];
2021590Srgrimes
2031590Srgrimes	exists = !stat(out, &sb);
2041590Srgrimes	if (!force && exists && S_ISREG(sb.st_mode) && !permission(out))
2051590Srgrimes		return;
2061590Srgrimes	isreg = oreg = !exists || S_ISREG(sb.st_mode);
2071590Srgrimes
2081590Srgrimes	ifp = ofp = NULL;
2091590Srgrimes	if ((ifp = fopen(in, "r")) == NULL) {
2101590Srgrimes		cwarn("%s", in);
2111590Srgrimes		return;
2121590Srgrimes	}
2131590Srgrimes	if (stat(in, &isb)) {		/* DON'T FSTAT! */
2141590Srgrimes		cwarn("%s", in);
2151590Srgrimes		goto err;
2161590Srgrimes	}
2171590Srgrimes	if (!S_ISREG(isb.st_mode))
2181590Srgrimes		isreg = 0;
2191590Srgrimes
2201590Srgrimes	if ((ofp = zopen(out, "w", bits)) == NULL) {
2211590Srgrimes		cwarn("%s", out);
2221590Srgrimes		goto err;
2231590Srgrimes	}
2241590Srgrimes	while ((nr = fread(buf, 1, sizeof(buf), ifp)) != 0)
2251590Srgrimes		if (fwrite(buf, 1, nr, ofp) != nr) {
2261590Srgrimes			cwarn("%s", out);
2271590Srgrimes			goto err;
2281590Srgrimes		}
2291590Srgrimes
2301590Srgrimes	if (ferror(ifp) || fclose(ifp)) {
2311590Srgrimes		cwarn("%s", in);
2321590Srgrimes		goto err;
2331590Srgrimes	}
2341590Srgrimes	ifp = NULL;
2351590Srgrimes
2361590Srgrimes	if (fclose(ofp)) {
2371590Srgrimes		cwarn("%s", out);
2381590Srgrimes		goto err;
2391590Srgrimes	}
2401590Srgrimes	ofp = NULL;
2411590Srgrimes
2421590Srgrimes	if (isreg) {
2431590Srgrimes		if (stat(out, &sb)) {
2441590Srgrimes			cwarn("%s", out);
2451590Srgrimes			goto err;
2461590Srgrimes		}
2471590Srgrimes
2481590Srgrimes		if (!force && sb.st_size >= isb.st_size) {
2491590Srgrimes			if (verbose)
25096770Stjr		(void)fprintf(stderr, "%s: file would grow; left unmodified\n",
25196770Stjr		    in);
25296769Stjr			eval = 2;
2531590Srgrimes			if (unlink(out))
2541590Srgrimes				cwarn("%s", out);
2551590Srgrimes			goto err;
2561590Srgrimes		}
2571590Srgrimes
2581590Srgrimes		setfile(out, &isb);
2591590Srgrimes
2601590Srgrimes		if (unlink(in))
2611590Srgrimes			cwarn("%s", in);
2621590Srgrimes
2631590Srgrimes		if (verbose) {
26496770Stjr			(void)fprintf(stderr, "%s: ", out);
2651590Srgrimes			if (isb.st_size > sb.st_size)
26696770Stjr				(void)fprintf(stderr, "%.0f%% compression\n",
2671590Srgrimes				    ((float)sb.st_size / isb.st_size) * 100.0);
2681590Srgrimes			else
26996770Stjr				(void)fprintf(stderr, "%.0f%% expansion\n",
2701590Srgrimes				    ((float)isb.st_size / sb.st_size) * 100.0);
2711590Srgrimes		}
2721590Srgrimes	}
2731590Srgrimes	return;
2741590Srgrimes
2751590Srgrimeserr:	if (ofp) {
2761590Srgrimes		if (oreg)
2771590Srgrimes			(void)unlink(out);
2781590Srgrimes		(void)fclose(ofp);
2791590Srgrimes	}
2801590Srgrimes	if (ifp)
2811590Srgrimes		(void)fclose(ifp);
2821590Srgrimes}
2831590Srgrimes
284227236Sedstatic void
285100820Sdwmalonedecompress(const char *in, const char *out, int bits)
2861590Srgrimes{
28787214Smarkm	size_t nr;
2881590Srgrimes	struct stat sb;
2891590Srgrimes	FILE *ifp, *ofp;
2901590Srgrimes	int exists, isreg, oreg;
2911590Srgrimes	u_char buf[1024];
2921590Srgrimes
2931590Srgrimes	exists = !stat(out, &sb);
2941590Srgrimes	if (!force && exists && S_ISREG(sb.st_mode) && !permission(out))
2951590Srgrimes		return;
2961590Srgrimes	isreg = oreg = !exists || S_ISREG(sb.st_mode);
2971590Srgrimes
2981590Srgrimes	ifp = ofp = NULL;
2991590Srgrimes	if ((ifp = zopen(in, "r", bits)) == NULL) {
3001590Srgrimes		cwarn("%s", in);
301116336Strhodes		return;
3021590Srgrimes	}
3031590Srgrimes	if (stat(in, &sb)) {
3041590Srgrimes		cwarn("%s", in);
3051590Srgrimes		goto err;
3061590Srgrimes	}
3071590Srgrimes	if (!S_ISREG(sb.st_mode))
3081590Srgrimes		isreg = 0;
3091590Srgrimes
310116336Strhodes	/*
311116336Strhodes	 * Try to read the first few uncompressed bytes from the input file
312116336Strhodes	 * before blindly truncating the output file.
313116336Strhodes	 */
314116336Strhodes	if ((nr = fread(buf, 1, sizeof(buf), ifp)) == 0) {
315116336Strhodes		cwarn("%s", in);
316116336Strhodes		(void)fclose(ifp);
317116336Strhodes		return;
318116336Strhodes	}
319116336Strhodes	if ((ofp = fopen(out, "w")) == NULL ||
320116336Strhodes	    (nr != 0 && fwrite(buf, 1, nr, ofp) != nr)) {
321116336Strhodes		cwarn("%s", out);
322116336Strhodes		(void)fclose(ifp);
323116336Strhodes		return;
324116336Strhodes	}
325116336Strhodes
3261590Srgrimes	while ((nr = fread(buf, 1, sizeof(buf), ifp)) != 0)
3271590Srgrimes		if (fwrite(buf, 1, nr, ofp) != nr) {
3281590Srgrimes			cwarn("%s", out);
3291590Srgrimes			goto err;
3301590Srgrimes		}
3311590Srgrimes
3321590Srgrimes	if (ferror(ifp) || fclose(ifp)) {
3331590Srgrimes		cwarn("%s", in);
3341590Srgrimes		goto err;
3351590Srgrimes	}
3361590Srgrimes	ifp = NULL;
3371590Srgrimes
3381590Srgrimes	if (fclose(ofp)) {
3391590Srgrimes		cwarn("%s", out);
3401590Srgrimes		goto err;
3411590Srgrimes	}
3421590Srgrimes
3431590Srgrimes	if (isreg) {
3441590Srgrimes		setfile(out, &sb);
3451590Srgrimes
3461590Srgrimes		if (unlink(in))
3471590Srgrimes			cwarn("%s", in);
3481590Srgrimes	}
3491590Srgrimes	return;
3501590Srgrimes
3511590Srgrimeserr:	if (ofp) {
3521590Srgrimes		if (oreg)
3531590Srgrimes			(void)unlink(out);
3541590Srgrimes		(void)fclose(ofp);
3551590Srgrimes	}
3561590Srgrimes	if (ifp)
3571590Srgrimes		(void)fclose(ifp);
3581590Srgrimes}
3591590Srgrimes
360227236Sedstatic void
361100820Sdwmalonesetfile(const char *name, struct stat *fs)
3621590Srgrimes{
3631590Srgrimes	static struct timeval tv[2];
3641590Srgrimes
3651590Srgrimes	fs->st_mode &= S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO;
3661590Srgrimes
367205793Sed	TIMESPEC_TO_TIMEVAL(&tv[0], &fs->st_atim);
368205793Sed	TIMESPEC_TO_TIMEVAL(&tv[1], &fs->st_mtim);
3691590Srgrimes	if (utimes(name, tv))
3701590Srgrimes		cwarn("utimes: %s", name);
3711590Srgrimes
3721590Srgrimes	/*
3731590Srgrimes	 * Changing the ownership probably won't succeed, unless we're root
3741590Srgrimes	 * or POSIX_CHOWN_RESTRICTED is not set.  Set uid/gid before setting
3751590Srgrimes	 * the mode; current BSD behavior is to remove all setuid bits on
3761590Srgrimes	 * chown.  If chown fails, lose setuid/setgid bits.
3771590Srgrimes	 */
3781590Srgrimes	if (chown(name, fs->st_uid, fs->st_gid)) {
3791590Srgrimes		if (errno != EPERM)
3801590Srgrimes			cwarn("chown: %s", name);
3811590Srgrimes		fs->st_mode &= ~(S_ISUID|S_ISGID);
3821590Srgrimes	}
38360622Shoek	if (chmod(name, fs->st_mode) && errno != EOPNOTSUPP)
38458630Scharnier		cwarn("chmod: %s", name);
3851590Srgrimes
38660622Shoek	if (chflags(name, fs->st_flags) && errno != EOPNOTSUPP)
3871590Srgrimes		cwarn("chflags: %s", name);
3881590Srgrimes}
3891590Srgrimes
390227236Sedstatic int
391100820Sdwmalonepermission(const char *fname)
3921590Srgrimes{
3931590Srgrimes	int ch, first;
3941590Srgrimes
3951590Srgrimes	if (!isatty(fileno(stderr)))
3961590Srgrimes		return (0);
3971590Srgrimes	(void)fprintf(stderr, "overwrite %s? ", fname);
3981590Srgrimes	first = ch = getchar();
3991590Srgrimes	while (ch != '\n' && ch != EOF)
4001590Srgrimes		ch = getchar();
4011590Srgrimes	return (first == 'y');
4021590Srgrimes}
4031590Srgrimes
404227236Sedstatic void
405100820Sdwmaloneusage(int iscompress)
4061590Srgrimes{
4071590Srgrimes	if (iscompress)
4081590Srgrimes		(void)fprintf(stderr,
4091590Srgrimes		    "usage: compress [-cfv] [-b bits] [file ...]\n");
4101590Srgrimes	else
4111590Srgrimes		(void)fprintf(stderr,
4121590Srgrimes		    "usage: uncompress [-c] [-b bits] [file ...]\n");
4131590Srgrimes	exit(1);
4141590Srgrimes}
4151590Srgrimes
416227236Sedstatic void
4171590Srgrimescwarnx(const char *fmt, ...)
4181590Srgrimes{
4191590Srgrimes	va_list ap;
42093055Simp
4211590Srgrimes	va_start(ap, fmt);
4221590Srgrimes	vwarnx(fmt, ap);
4231590Srgrimes	va_end(ap);
4241590Srgrimes	eval = 1;
4251590Srgrimes}
4261590Srgrimes
427227236Sedstatic void
4281590Srgrimescwarn(const char *fmt, ...)
4291590Srgrimes{
4301590Srgrimes	va_list ap;
43193055Simp
4321590Srgrimes	va_start(ap, fmt);
4331590Srgrimes	vwarn(fmt, ap);
4341590Srgrimes	va_end(ap);
4351590Srgrimes	eval = 1;
4361590Srgrimes}
437