compress.c revision 225736
1219019Sgabor/*- 2219019Sgabor * Copyright (c) 1992, 1993 3219019Sgabor * The Regents of the University of California. All rights reserved. 4219019Sgabor * 5219019Sgabor * Redistribution and use in source and binary forms, with or without 6219019Sgabor * modification, are permitted provided that the following conditions 7219019Sgabor * are met: 8219019Sgabor * 1. Redistributions of source code must retain the above copyright 9219019Sgabor * notice, this list of conditions and the following disclaimer. 10219019Sgabor * 2. Redistributions in binary form must reproduce the above copyright 11219019Sgabor * notice, this list of conditions and the following disclaimer in the 12219019Sgabor * documentation and/or other materials provided with the distribution. 13219019Sgabor * 4. Neither the name of the University nor the names of its contributors 14219019Sgabor * may be used to endorse or promote products derived from this software 15219019Sgabor * without specific prior written permission. 16219019Sgabor * 17219019Sgabor * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 18219019Sgabor * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19219019Sgabor * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20219019Sgabor * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 21219019Sgabor * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22219019Sgabor * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23219019Sgabor * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24219019Sgabor * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25219019Sgabor * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26219019Sgabor * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27219019Sgabor * SUCH DAMAGE. 28219019Sgabor */ 29219019Sgabor 30219019Sgabor#ifndef lint 31219019Sgaborstatic const char copyright[] = 32219019Sgabor"@(#) Copyright (c) 1992, 1993\n\ 33219019Sgabor The Regents of the University of California. All rights reserved.\n"; 34219019Sgabor#endif 35219019Sgabor 36219019Sgabor#if 0 37219019Sgabor#ifndef lint 38219019Sgaborstatic char sccsid[] = "@(#)compress.c 8.2 (Berkeley) 1/7/94"; 39219019Sgabor#endif 40219019Sgabor#endif 41219019Sgabor 42219019Sgabor#include <sys/cdefs.h> 43219019Sgabor__FBSDID("$FreeBSD: stable/9/usr.bin/compress/compress.c 216370 2010-12-11 08:32:16Z joel $"); 44219019Sgabor 45219019Sgabor#include <sys/param.h> 46219019Sgabor#include <sys/stat.h> 47219019Sgabor#include <sys/time.h> 48219019Sgabor 49219019Sgabor#include <err.h> 50219019Sgabor#include <errno.h> 51219019Sgabor#include <stdarg.h> 52219019Sgabor#include <stdio.h> 53219019Sgabor#include <stdlib.h> 54219019Sgabor#include <string.h> 55219019Sgabor#include <unistd.h> 56219019Sgabor 57219019Sgabor#include "zopen.h" 58219019Sgabor 59219019Sgaborvoid compress(const char *, const char *, int); 60219019Sgaborvoid cwarn(const char *, ...) __printflike(1, 2); 61219019Sgaborvoid cwarnx(const char *, ...) __printflike(1, 2); 62219019Sgaborvoid decompress(const char *, const char *, int); 63219019Sgaborint permission(const char *); 64219019Sgaborvoid setfile(const char *, struct stat *); 65219019Sgaborvoid usage(int); 66219019Sgabor 67219019Sgaborint eval, force, verbose; 68219019Sgabor 69219019Sgaborint 70219019Sgabormain(int argc, char *argv[]) 71219019Sgabor{ 72219019Sgabor enum {COMPRESS, DECOMPRESS} style; 73219019Sgabor size_t len; 74219019Sgabor int bits, cat, ch; 75219019Sgabor char *p, newname[MAXPATHLEN]; 76219019Sgabor 77219019Sgabor cat = 0; 78219019Sgabor if ((p = rindex(argv[0], '/')) == NULL) 79219019Sgabor p = argv[0]; 80219019Sgabor else 81219019Sgabor ++p; 82219019Sgabor if (!strcmp(p, "uncompress")) 83219019Sgabor style = DECOMPRESS; 84219019Sgabor else if (!strcmp(p, "compress")) 85219019Sgabor style = COMPRESS; 86219019Sgabor else if (!strcmp(p, "zcat")) { 87219019Sgabor cat = 1; 88219019Sgabor style = DECOMPRESS; 89219019Sgabor } else 90219019Sgabor errx(1, "unknown program name"); 91219019Sgabor 92219019Sgabor bits = 0; 93219019Sgabor while ((ch = getopt(argc, argv, "b:cdfv")) != -1) 94219019Sgabor switch(ch) { 95219019Sgabor case 'b': 96219019Sgabor bits = strtol(optarg, &p, 10); 97219019Sgabor if (*p) 98219019Sgabor errx(1, "illegal bit count -- %s", optarg); 99219019Sgabor break; 100219019Sgabor case 'c': 101219019Sgabor cat = 1; 102219019Sgabor break; 103219019Sgabor case 'd': /* Backward compatible. */ 104219019Sgabor style = DECOMPRESS; 105219019Sgabor break; 106219019Sgabor case 'f': 107219019Sgabor force = 1; 108219019Sgabor break; 109219019Sgabor case 'v': 110219019Sgabor verbose = 1; 111219019Sgabor break; 112219019Sgabor case '?': 113219019Sgabor default: 114219019Sgabor usage(style == COMPRESS); 115219019Sgabor } 116219019Sgabor argc -= optind; 117219019Sgabor argv += optind; 118219019Sgabor 119219019Sgabor if (argc == 0) { 120219019Sgabor switch(style) { 121219019Sgabor case COMPRESS: 122219019Sgabor (void)compress("/dev/stdin", "/dev/stdout", bits); 123219019Sgabor break; 124219019Sgabor case DECOMPRESS: 125219019Sgabor (void)decompress("/dev/stdin", "/dev/stdout", bits); 126219019Sgabor break; 127219019Sgabor } 128219019Sgabor exit (eval); 129219019Sgabor } 130219019Sgabor 131219019Sgabor if (cat == 1 && argc > 1) 132219019Sgabor errx(1, "the -c option permits only a single file argument"); 133219019Sgabor 134219019Sgabor for (; *argv; ++argv) 135219019Sgabor switch(style) { 136219019Sgabor case COMPRESS: 137219019Sgabor if (strcmp(*argv, "-") == 0) { 138219019Sgabor compress("/dev/stdin", "/dev/stdout", bits); 139219019Sgabor break; 140219019Sgabor } else if (cat) { 141219019Sgabor compress(*argv, "/dev/stdout", bits); 142219019Sgabor break; 143219019Sgabor } 144219019Sgabor if ((p = rindex(*argv, '.')) != NULL && 145219019Sgabor !strcmp(p, ".Z")) { 146219019Sgabor cwarnx("%s: name already has trailing .Z", 147219019Sgabor *argv); 148219019Sgabor break; 149219019Sgabor } 150219019Sgabor len = strlen(*argv); 151219019Sgabor if (len > sizeof(newname) - 3) { 152219019Sgabor cwarnx("%s: name too long", *argv); 153219019Sgabor break; 154219019Sgabor } 155219019Sgabor memmove(newname, *argv, len); 156219019Sgabor newname[len] = '.'; 157219019Sgabor newname[len + 1] = 'Z'; 158219019Sgabor newname[len + 2] = '\0'; 159219019Sgabor compress(*argv, newname, bits); 160219019Sgabor break; 161219019Sgabor case DECOMPRESS: 162219019Sgabor if (strcmp(*argv, "-") == 0) { 163219019Sgabor decompress("/dev/stdin", "/dev/stdout", bits); 164219019Sgabor break; 165219019Sgabor } 166219019Sgabor len = strlen(*argv); 167219019Sgabor if ((p = rindex(*argv, '.')) == NULL || 168219019Sgabor strcmp(p, ".Z")) { 169219019Sgabor if (len > sizeof(newname) - 3) { 170219019Sgabor cwarnx("%s: name too long", *argv); 171219019Sgabor break; 172219019Sgabor } 173219019Sgabor memmove(newname, *argv, len); 174219019Sgabor newname[len] = '.'; 175219019Sgabor newname[len + 1] = 'Z'; 176219019Sgabor newname[len + 2] = '\0'; 177219019Sgabor decompress(newname, 178219019Sgabor cat ? "/dev/stdout" : *argv, bits); 179219019Sgabor } else { 180219019Sgabor if (len - 2 > sizeof(newname) - 1) { 181219019Sgabor cwarnx("%s: name too long", *argv); 182219019Sgabor break; 183219019Sgabor } 184219019Sgabor memmove(newname, *argv, len - 2); 185219019Sgabor newname[len - 2] = '\0'; 186219019Sgabor decompress(*argv, 187219019Sgabor cat ? "/dev/stdout" : newname, bits); 188219019Sgabor } 189219019Sgabor break; 190219019Sgabor } 191219019Sgabor exit (eval); 192219019Sgabor} 193219019Sgabor 194219019Sgaborvoid 195219019Sgaborcompress(const char *in, const char *out, int bits) 196219019Sgabor{ 197219019Sgabor size_t nr; 198219019Sgabor struct stat isb, sb; 199219019Sgabor FILE *ifp, *ofp; 200219019Sgabor int exists, isreg, oreg; 201219019Sgabor u_char buf[1024]; 202219019Sgabor 203219019Sgabor exists = !stat(out, &sb); 204219019Sgabor if (!force && exists && S_ISREG(sb.st_mode) && !permission(out)) 205219019Sgabor return; 206219019Sgabor isreg = oreg = !exists || S_ISREG(sb.st_mode); 207219019Sgabor 208219019Sgabor ifp = ofp = NULL; 209219019Sgabor if ((ifp = fopen(in, "r")) == NULL) { 210219019Sgabor cwarn("%s", in); 211219019Sgabor return; 212219019Sgabor } 213219019Sgabor if (stat(in, &isb)) { /* DON'T FSTAT! */ 214219019Sgabor cwarn("%s", in); 215219019Sgabor goto err; 216219019Sgabor } 217219019Sgabor if (!S_ISREG(isb.st_mode)) 218219019Sgabor isreg = 0; 219219019Sgabor 220219019Sgabor if ((ofp = zopen(out, "w", bits)) == NULL) { 221219019Sgabor cwarn("%s", out); 222219019Sgabor goto err; 223219019Sgabor } 224219019Sgabor while ((nr = fread(buf, 1, sizeof(buf), ifp)) != 0) 225219019Sgabor if (fwrite(buf, 1, nr, ofp) != nr) { 226219019Sgabor cwarn("%s", out); 227219019Sgabor goto err; 228219019Sgabor } 229219019Sgabor 230219019Sgabor if (ferror(ifp) || fclose(ifp)) { 231219019Sgabor cwarn("%s", in); 232219019Sgabor goto err; 233219019Sgabor } 234219019Sgabor ifp = NULL; 235219019Sgabor 236219019Sgabor if (fclose(ofp)) { 237219019Sgabor cwarn("%s", out); 238219019Sgabor goto err; 239219019Sgabor } 240219019Sgabor ofp = NULL; 241219019Sgabor 242219019Sgabor if (isreg) { 243219019Sgabor if (stat(out, &sb)) { 244219019Sgabor cwarn("%s", out); 245219019Sgabor goto err; 246219019Sgabor } 247219019Sgabor 248219019Sgabor if (!force && sb.st_size >= isb.st_size) { 249219019Sgabor if (verbose) 250219019Sgabor (void)fprintf(stderr, "%s: file would grow; left unmodified\n", 251219019Sgabor in); 252219019Sgabor eval = 2; 253219019Sgabor if (unlink(out)) 254219019Sgabor cwarn("%s", out); 255219019Sgabor goto err; 256219019Sgabor } 257219019Sgabor 258219019Sgabor setfile(out, &isb); 259219019Sgabor 260219019Sgabor if (unlink(in)) 261219019Sgabor cwarn("%s", in); 262219019Sgabor 263219019Sgabor if (verbose) { 264219019Sgabor (void)fprintf(stderr, "%s: ", out); 265219019Sgabor if (isb.st_size > sb.st_size) 266219019Sgabor (void)fprintf(stderr, "%.0f%% compression\n", 267219019Sgabor ((float)sb.st_size / isb.st_size) * 100.0); 268219019Sgabor else 269219019Sgabor (void)fprintf(stderr, "%.0f%% expansion\n", 270219019Sgabor ((float)isb.st_size / sb.st_size) * 100.0); 271219019Sgabor } 272219019Sgabor } 273219019Sgabor return; 274219019Sgabor 275219019Sgaborerr: if (ofp) { 276219019Sgabor if (oreg) 277219019Sgabor (void)unlink(out); 278219019Sgabor (void)fclose(ofp); 279219019Sgabor } 280219019Sgabor if (ifp) 281219019Sgabor (void)fclose(ifp); 282219019Sgabor} 283219019Sgabor 284219019Sgaborvoid 285219019Sgabordecompress(const char *in, const char *out, int bits) 286219019Sgabor{ 287219019Sgabor size_t nr; 288219019Sgabor struct stat sb; 289219019Sgabor FILE *ifp, *ofp; 290219019Sgabor int exists, isreg, oreg; 291219019Sgabor u_char buf[1024]; 292219019Sgabor 293219019Sgabor exists = !stat(out, &sb); 294219019Sgabor if (!force && exists && S_ISREG(sb.st_mode) && !permission(out)) 295219019Sgabor return; 296219019Sgabor isreg = oreg = !exists || S_ISREG(sb.st_mode); 297219019Sgabor 298219019Sgabor ifp = ofp = NULL; 299219019Sgabor if ((ifp = zopen(in, "r", bits)) == NULL) { 300219019Sgabor cwarn("%s", in); 301219019Sgabor return; 302219019Sgabor } 303219019Sgabor if (stat(in, &sb)) { 304219019Sgabor cwarn("%s", in); 305219019Sgabor goto err; 306219019Sgabor } 307219019Sgabor if (!S_ISREG(sb.st_mode)) 308219019Sgabor isreg = 0; 309219019Sgabor 310219019Sgabor /* 311219019Sgabor * Try to read the first few uncompressed bytes from the input file 312219019Sgabor * before blindly truncating the output file. 313219019Sgabor */ 314219019Sgabor if ((nr = fread(buf, 1, sizeof(buf), ifp)) == 0) { 315219019Sgabor cwarn("%s", in); 316219019Sgabor (void)fclose(ifp); 317219019Sgabor return; 318219019Sgabor } 319219019Sgabor if ((ofp = fopen(out, "w")) == NULL || 320219019Sgabor (nr != 0 && fwrite(buf, 1, nr, ofp) != nr)) { 321219019Sgabor cwarn("%s", out); 322219019Sgabor (void)fclose(ifp); 323219019Sgabor return; 324219019Sgabor } 325219019Sgabor 326219019Sgabor while ((nr = fread(buf, 1, sizeof(buf), ifp)) != 0) 327219019Sgabor if (fwrite(buf, 1, nr, ofp) != nr) { 328219019Sgabor cwarn("%s", out); 329219019Sgabor goto err; 330219019Sgabor } 331219019Sgabor 332219019Sgabor if (ferror(ifp) || fclose(ifp)) { 333219019Sgabor cwarn("%s", in); 334219019Sgabor goto err; 335219019Sgabor } 336219019Sgabor ifp = NULL; 337219019Sgabor 338219019Sgabor if (fclose(ofp)) { 339219019Sgabor cwarn("%s", out); 340219019Sgabor goto err; 341219019Sgabor } 342219019Sgabor 343219019Sgabor if (isreg) { 344219019Sgabor setfile(out, &sb); 345219019Sgabor 346219019Sgabor if (unlink(in)) 347219019Sgabor cwarn("%s", in); 348219019Sgabor } 349 return; 350 351err: if (ofp) { 352 if (oreg) 353 (void)unlink(out); 354 (void)fclose(ofp); 355 } 356 if (ifp) 357 (void)fclose(ifp); 358} 359 360void 361setfile(const char *name, struct stat *fs) 362{ 363 static struct timeval tv[2]; 364 365 fs->st_mode &= S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO; 366 367 TIMESPEC_TO_TIMEVAL(&tv[0], &fs->st_atim); 368 TIMESPEC_TO_TIMEVAL(&tv[1], &fs->st_mtim); 369 if (utimes(name, tv)) 370 cwarn("utimes: %s", name); 371 372 /* 373 * Changing the ownership probably won't succeed, unless we're root 374 * or POSIX_CHOWN_RESTRICTED is not set. Set uid/gid before setting 375 * the mode; current BSD behavior is to remove all setuid bits on 376 * chown. If chown fails, lose setuid/setgid bits. 377 */ 378 if (chown(name, fs->st_uid, fs->st_gid)) { 379 if (errno != EPERM) 380 cwarn("chown: %s", name); 381 fs->st_mode &= ~(S_ISUID|S_ISGID); 382 } 383 if (chmod(name, fs->st_mode) && errno != EOPNOTSUPP) 384 cwarn("chmod: %s", name); 385 386 if (chflags(name, fs->st_flags) && errno != EOPNOTSUPP) 387 cwarn("chflags: %s", name); 388} 389 390int 391permission(const char *fname) 392{ 393 int ch, first; 394 395 if (!isatty(fileno(stderr))) 396 return (0); 397 (void)fprintf(stderr, "overwrite %s? ", fname); 398 first = ch = getchar(); 399 while (ch != '\n' && ch != EOF) 400 ch = getchar(); 401 return (first == 'y'); 402} 403 404void 405usage(int iscompress) 406{ 407 if (iscompress) 408 (void)fprintf(stderr, 409 "usage: compress [-cfv] [-b bits] [file ...]\n"); 410 else 411 (void)fprintf(stderr, 412 "usage: uncompress [-c] [-b bits] [file ...]\n"); 413 exit(1); 414} 415 416void 417cwarnx(const char *fmt, ...) 418{ 419 va_list ap; 420 421 va_start(ap, fmt); 422 vwarnx(fmt, ap); 423 va_end(ap); 424 eval = 1; 425} 426 427void 428cwarn(const char *fmt, ...) 429{ 430 va_list ap; 431 432 va_start(ap, fmt); 433 vwarn(fmt, ap); 434 va_end(ap); 435 eval = 1; 436} 437