1251875Speter/*-
2251875Speter * SPDX-License-Identifier: BSD-3-Clause
3251875Speter *
4251875Speter * Copyright (c) 1992, 1993
5251875Speter *	The Regents of the University of California.  All rights reserved.
6251875Speter *
7251875Speter * Redistribution and use in source and binary forms, with or without
8251875Speter * modification, are permitted provided that the following conditions
9251875Speter * are met:
10251875Speter * 1. Redistributions of source code must retain the above copyright
11251875Speter *    notice, this list of conditions and the following disclaimer.
12251875Speter * 2. Redistributions in binary form must reproduce the above copyright
13251875Speter *    notice, this list of conditions and the following disclaimer in the
14251875Speter *    documentation and/or other materials provided with the distribution.
15251875Speter * 3. Neither the name of the University nor the names of its contributors
16251875Speter *    may be used to endorse or promote products derived from this software
17251875Speter *    without specific prior written permission.
18251875Speter *
19251875Speter * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20251875Speter * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21251875Speter * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22251875Speter * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23251875Speter * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24251875Speter * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25251875Speter * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26251875Speter * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27251875Speter * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28251875Speter * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29251875Speter * SUCH DAMAGE.
30251875Speter */
31251875Speter
32251875Speter#include <sys/param.h>
33251875Speter#include <sys/stat.h>
34251875Speter#include <sys/time.h>
35251875Speter
36251875Speter#include <err.h>
37251875Speter#include <errno.h>
38251875Speter#include <fcntl.h>
39251875Speter#include <stdarg.h>
40251875Speter#include <stdio.h>
41251875Speter#include <stdlib.h>
42251875Speter#include <string.h>
43251875Speter#include <unistd.h>
44251875Speter
45251875Speter#include "zopen.h"
46251875Speter
47251875Speterstatic void	compress(const char *, const char *, int);
48251875Speterstatic void	cwarn(const char *, ...) __printflike(1, 2);
49251875Speterstatic void	cwarnx(const char *, ...) __printflike(1, 2);
50251875Speterstatic void	decompress(const char *, const char *, int);
51251875Speterstatic int	permission(const char *);
52251875Speterstatic void	setfile(const char *, struct stat *);
53251875Speterstatic void	usage(int);
54251875Speter
55251875Speterstatic int eval, force, verbose;
56251875Speter
57251875Speterint
58251875Spetermain(int argc, char *argv[])
59251875Speter{
60251875Speter	enum {COMPRESS, DECOMPRESS} style;
61251875Speter	size_t len;
62251875Speter	int bits, cat, ch;
63251875Speter	char *p, newname[MAXPATHLEN];
64251875Speter
65251875Speter	cat = 0;
66251875Speter	if ((p = strrchr(argv[0], '/')) == NULL)
67251875Speter		p = argv[0];
68251875Speter	else
69251875Speter		++p;
70251875Speter	if (!strcmp(p, "uncompress"))
71251875Speter		style = DECOMPRESS;
72251875Speter	else if (!strcmp(p, "compress"))
73251875Speter		style = COMPRESS;
74251875Speter	else if (!strcmp(p, "zcat")) {
75251875Speter		cat = 1;
76251875Speter		style = DECOMPRESS;
77251875Speter	} else
78251875Speter		errx(1, "unknown program name");
79251875Speter
80251875Speter	bits = 0;
81251875Speter	while ((ch = getopt(argc, argv, "b:cdfv")) != -1)
82251875Speter		switch(ch) {
83251875Speter		case 'b':
84251875Speter			bits = strtol(optarg, &p, 10);
85251875Speter			if (*p)
86251875Speter				errx(1, "illegal bit count -- %s", optarg);
87251875Speter			break;
88251875Speter		case 'c':
89251875Speter			cat = 1;
90251875Speter			break;
91251875Speter		case 'd':		/* Backward compatible. */
92251875Speter			style = DECOMPRESS;
93251875Speter			break;
94251875Speter		case 'f':
95251875Speter			force = 1;
96251875Speter			break;
97251875Speter		case 'v':
98251875Speter			verbose = 1;
99251875Speter			break;
100251875Speter		case '?':
101251875Speter		default:
102251875Speter			usage(style == COMPRESS);
103251875Speter		}
104251875Speter	argc -= optind;
105251875Speter	argv += optind;
106251875Speter
107251875Speter	if (argc == 0) {
108251875Speter		switch(style) {
109251875Speter		case COMPRESS:
110251875Speter			(void)compress("/dev/stdin", "/dev/stdout", bits);
111251875Speter			break;
112251875Speter		case DECOMPRESS:
113251875Speter			(void)decompress("/dev/stdin", "/dev/stdout", bits);
114251875Speter			break;
115251875Speter		}
116251875Speter		exit (eval);
117251875Speter	}
118251875Speter
119251875Speter	if (cat == 1 && style == COMPRESS && argc > 1)
120251875Speter		errx(1, "the -c option permits only a single file argument");
121251875Speter
122251875Speter	for (; *argv; ++argv)
123251875Speter		switch(style) {
124251875Speter		case COMPRESS:
125251875Speter			if (strcmp(*argv, "-") == 0) {
126251875Speter				compress("/dev/stdin", "/dev/stdout", bits);
127251875Speter				break;
128251875Speter			} else if (cat) {
129251875Speter				compress(*argv, "/dev/stdout", bits);
130251875Speter				break;
131251875Speter			}
132251875Speter			if ((p = strrchr(*argv, '.')) != NULL &&
133251875Speter			    !strcmp(p, ".Z")) {
134251875Speter				cwarnx("%s: name already has trailing .Z",
135251875Speter				    *argv);
136251875Speter				break;
137251875Speter			}
138251875Speter			len = strlen(*argv);
139251875Speter			if (len > sizeof(newname) - 3) {
140251875Speter				cwarnx("%s: name too long", *argv);
141251875Speter				break;
142251875Speter			}
143251875Speter			memmove(newname, *argv, len);
144251875Speter			newname[len] = '.';
145251875Speter			newname[len + 1] = 'Z';
146251875Speter			newname[len + 2] = '\0';
147251875Speter			compress(*argv, newname, bits);
148251875Speter			break;
149251875Speter		case DECOMPRESS:
150251875Speter			if (strcmp(*argv, "-") == 0) {
151251875Speter				decompress("/dev/stdin", "/dev/stdout", bits);
152251875Speter				break;
153251875Speter			}
154251875Speter			len = strlen(*argv);
155251875Speter			if ((p = strrchr(*argv, '.')) == NULL ||
156251875Speter			    strcmp(p, ".Z")) {
157251875Speter				if (len > sizeof(newname) - 3) {
158251875Speter					cwarnx("%s: name too long", *argv);
159251875Speter					break;
160251875Speter				}
161251875Speter				memmove(newname, *argv, len);
162251875Speter				newname[len] = '.';
163251875Speter				newname[len + 1] = 'Z';
164251875Speter				newname[len + 2] = '\0';
165251875Speter				decompress(newname,
166251875Speter				    cat ? "/dev/stdout" : *argv, bits);
167251875Speter			} else {
168251875Speter				if (len - 2 > sizeof(newname) - 1) {
169251875Speter					cwarnx("%s: name too long", *argv);
170251875Speter					break;
171251875Speter				}
172251875Speter				memmove(newname, *argv, len - 2);
173251875Speter				newname[len - 2] = '\0';
174251875Speter				decompress(*argv,
175251875Speter				    cat ? "/dev/stdout" : newname, bits);
176251875Speter			}
177251875Speter			break;
178251875Speter		}
179251875Speter	exit (eval);
180251875Speter}
181251875Speter
182251875Speterstatic void
183251875Spetercompress(const char *in, const char *out, int bits)
184251875Speter{
185251875Speter	size_t nr;
186251875Speter	struct stat isb, sb;
187251875Speter	FILE *ifp, *ofp;
188251875Speter	int exists, isreg, oreg;
189251875Speter	u_char buf[1024];
190251875Speter
191251875Speter	exists = !stat(out, &sb);
192251875Speter	if (!force && exists && S_ISREG(sb.st_mode) && !permission(out))
193251875Speter		return;
194251875Speter	isreg = oreg = !exists || S_ISREG(sb.st_mode);
195251875Speter
196251875Speter	ifp = ofp = NULL;
197251875Speter	if ((ifp = fopen(in, "r")) == NULL) {
198251875Speter		cwarn("%s", in);
199251875Speter		return;
200251875Speter	}
201251875Speter	if (stat(in, &isb)) {		/* DON'T FSTAT! */
202251875Speter		cwarn("%s", in);
203251875Speter		goto err;
204251875Speter	}
205251875Speter	if (!S_ISREG(isb.st_mode))
206251875Speter		isreg = 0;
207251875Speter
208251875Speter	if ((ofp = zopen(out, "w", bits)) == NULL) {
209251875Speter		cwarn("%s", out);
210251875Speter		goto err;
211251875Speter	}
212251875Speter	while ((nr = fread(buf, 1, sizeof(buf), ifp)) != 0)
213251875Speter		if (fwrite(buf, 1, nr, ofp) != nr) {
214251875Speter			cwarn("%s", out);
215251875Speter			goto err;
216251875Speter		}
217251875Speter
218251875Speter	if (ferror(ifp) || fclose(ifp)) {
219251875Speter		cwarn("%s", in);
220251875Speter		goto err;
221251875Speter	}
222251875Speter	ifp = NULL;
223251875Speter
224251875Speter	if (fclose(ofp)) {
225251875Speter		cwarn("%s", out);
226251875Speter		goto err;
227251875Speter	}
228251875Speter	ofp = NULL;
229251875Speter
230251875Speter	if (isreg) {
231251875Speter		if (stat(out, &sb)) {
232251875Speter			cwarn("%s", out);
233251875Speter			goto err;
234251875Speter		}
235251875Speter
236251875Speter		if (!force && sb.st_size >= isb.st_size) {
237251875Speter			if (verbose)
238251875Speter		(void)fprintf(stderr, "%s: file would grow; left unmodified\n",
239251875Speter		    in);
240251875Speter			eval = 2;
241251875Speter			if (unlink(out))
242251875Speter				cwarn("%s", out);
243251875Speter			goto err;
244251875Speter		}
245251875Speter
246251875Speter		setfile(out, &isb);
247251875Speter
248251875Speter		if (unlink(in))
249251875Speter			cwarn("%s", in);
250251875Speter
251251875Speter		if (verbose) {
252251875Speter			(void)fprintf(stderr, "%s: ", out);
253251875Speter			if (isb.st_size > sb.st_size)
254251875Speter				(void)fprintf(stderr, "%.0f%% compression\n",
255251875Speter				    ((float)sb.st_size / isb.st_size) * 100.0);
256251875Speter			else
257251875Speter				(void)fprintf(stderr, "%.0f%% expansion\n",
258251875Speter				    ((float)isb.st_size / sb.st_size) * 100.0);
259251875Speter		}
260251875Speter	}
261251875Speter	return;
262251875Speter
263251875Spetererr:	if (ofp) {
264251875Speter		if (oreg)
265251875Speter			(void)unlink(out);
266251875Speter		(void)fclose(ofp);
267251875Speter	}
268251875Speter	if (ifp)
269251875Speter		(void)fclose(ifp);
270251875Speter}
271251875Speter
272251875Speterstatic void
273251875Speterdecompress(const char *in, const char *out, int bits)
274251875Speter{
275251875Speter	size_t nr;
276251875Speter	struct stat sb;
277251875Speter	FILE *ifp, *ofp;
278251875Speter	int exists, isreg, oreg;
279251875Speter	u_char buf[1024];
280251875Speter
281251875Speter	exists = !stat(out, &sb);
282251875Speter	if (!force && exists && S_ISREG(sb.st_mode) && !permission(out))
283251875Speter		return;
284251875Speter	isreg = oreg = !exists || S_ISREG(sb.st_mode);
285251875Speter
286251875Speter	ifp = ofp = NULL;
287251875Speter	if ((ifp = zopen(in, "r", bits)) == NULL) {
288251875Speter		cwarn("%s", in);
289251875Speter		return;
290251875Speter	}
291251875Speter	if (stat(in, &sb)) {
292251875Speter		cwarn("%s", in);
293251875Speter		goto err;
294251875Speter	}
295251875Speter	if (!S_ISREG(sb.st_mode))
296251875Speter		isreg = 0;
297251875Speter
298251875Speter	/*
299251875Speter	 * Try to read the first few uncompressed bytes from the input file
300251875Speter	 * before blindly truncating the output file.
301251875Speter	 */
302251875Speter	if ((nr = fread(buf, 1, sizeof(buf), ifp)) == 0) {
303251875Speter		cwarn("%s", in);
304251875Speter		(void)fclose(ifp);
305251875Speter		return;
306251875Speter	}
307251875Speter	if ((ofp = fopen(out, "w")) == NULL ||
308251875Speter	    (nr != 0 && fwrite(buf, 1, nr, ofp) != nr)) {
309251875Speter		cwarn("%s", out);
310251875Speter		if (ofp)
311251875Speter			(void)fclose(ofp);
312251875Speter		(void)fclose(ifp);
313251875Speter		return;
314251875Speter	}
315251875Speter
316251875Speter	while ((nr = fread(buf, 1, sizeof(buf), ifp)) != 0)
317251875Speter		if (fwrite(buf, 1, nr, ofp) != nr) {
318251875Speter			cwarn("%s", out);
319251875Speter			goto err;
320251875Speter		}
321251875Speter
322251875Speter	if (ferror(ifp) || fclose(ifp)) {
323251875Speter		cwarn("%s", in);
324251875Speter		goto err;
325251875Speter	}
326251875Speter	ifp = NULL;
327251875Speter
328251875Speter	if (fclose(ofp)) {
329251875Speter		cwarn("%s", out);
330251875Speter		goto err;
331251875Speter	}
332251875Speter
333251875Speter	if (isreg) {
334251875Speter		setfile(out, &sb);
335251875Speter
336251875Speter		if (unlink(in))
337251875Speter			cwarn("%s", in);
338251875Speter	}
339251875Speter	return;
340251875Speter
341251875Spetererr:	if (ofp) {
342251875Speter		if (oreg)
343251875Speter			(void)unlink(out);
344251875Speter		(void)fclose(ofp);
345251875Speter	}
346251875Speter	if (ifp)
347251875Speter		(void)fclose(ifp);
348251875Speter}
349251875Speter
350251875Speterstatic void
351251875Spetersetfile(const char *name, struct stat *fs)
352251875Speter{
353251875Speter	static struct timespec tspec[2];
354251875Speter
355251875Speter	fs->st_mode &= S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO;
356251875Speter
357251875Speter	tspec[0] = fs->st_atim;
358251875Speter	tspec[1] = fs->st_mtim;
359251875Speter	if (utimensat(AT_FDCWD, name, tspec, 0))
360251875Speter		cwarn("utimensat: %s", name);
361251875Speter
362251875Speter	/*
363251875Speter	 * Changing the ownership probably won't succeed, unless we're root
364251875Speter	 * or POSIX_CHOWN_RESTRICTED is not set.  Set uid/gid before setting
365251875Speter	 * the mode; current BSD behavior is to remove all setuid bits on
366251875Speter	 * chown.  If chown fails, lose setuid/setgid bits.
367251875Speter	 */
368251875Speter	if (chown(name, fs->st_uid, fs->st_gid)) {
369251875Speter		if (errno != EPERM)
370251875Speter			cwarn("chown: %s", name);
371251875Speter		fs->st_mode &= ~(S_ISUID|S_ISGID);
372251875Speter	}
373251875Speter	if (chmod(name, fs->st_mode) && errno != EOPNOTSUPP)
374251875Speter		cwarn("chmod: %s", name);
375251875Speter
376251875Speter	if (chflags(name, fs->st_flags) && errno != EOPNOTSUPP)
377251875Speter		cwarn("chflags: %s", name);
378251875Speter}
379251875Speter
380251875Speterstatic int
381251875Speterpermission(const char *fname)
382251875Speter{
383251875Speter	int ch, first;
384251875Speter
385251875Speter	if (!isatty(fileno(stderr)))
386251875Speter		return (0);
387251875Speter	(void)fprintf(stderr, "overwrite %s? ", fname);
388251875Speter	first = ch = getchar();
389251875Speter	while (ch != '\n' && ch != EOF)
390251875Speter		ch = getchar();
391251875Speter	return (first == 'y');
392251875Speter}
393251875Speter
394251875Speterstatic void
395251875Speterusage(int iscompress)
396251875Speter{
397251875Speter	if (iscompress)
398251875Speter		(void)fprintf(stderr,
399251875Speter		    "usage: compress [-cfv] [-b bits] [file ...]\n");
400251875Speter	else
401251875Speter		(void)fprintf(stderr,
402251875Speter		    "usage: uncompress [-c] [-b bits] [file ...]\n");
403251875Speter	exit(1);
404251875Speter}
405251875Speter
406251875Speterstatic void
407251875Spetercwarnx(const char *fmt, ...)
408251875Speter{
409251875Speter	va_list ap;
410251875Speter
411251875Speter	va_start(ap, fmt);
412251875Speter	vwarnx(fmt, ap);
413251875Speter	va_end(ap);
414251875Speter	eval = 1;
415251875Speter}
416251875Speter
417251875Speterstatic void
418251875Spetercwarn(const char *fmt, ...)
419251875Speter{
420251875Speter	va_list ap;
421251875Speter
422251875Speter	va_start(ap, fmt);
423251875Speter	vwarn(fmt, ap);
424251875Speter	va_end(ap);
425251875Speter	eval = 1;
426251875Speter}
427251875Speter