1 /* $NetBSD: compress.c,v 1.18 2000/10/11 14:46:02 is Exp $ */ 2 3 /*- 4 * Copyright (c) 1992, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. All advertising materials mentioning features or use of this software 16 * must display the following acknowledgement: 17 * This product includes software developed by the University of 18 * California, Berkeley and its contributors. 19 * 4. Neither the name of the University nor the names of its contributors 20 * may be used to endorse or promote products derived from this software 21 * without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 */ 35 36 #include <sys/cdefs.h> 37 #ifndef lint 38 __COPYRIGHT("@(#) Copyright (c) 1992, 1993\n\ 39 The Regents of the University of California. All rights reserved.\n"); 40 #endif /* not lint */ 41 42 #ifndef lint 43 #if 0 44 static char sccsid[] = "@(#)compress.c 8.2 (Berkeley) 1/7/94"; 45 #else 46 __RCSID("$NetBSD: compress.c,v 1.18 2000/10/11 14:46:02 is Exp $"); 47 #endif 48 #endif /* not lint */ 49 50 #include <sys/param.h> 51 #include <sys/time.h> 52 #include <sys/stat.h> 53 54 #include <err.h> 55 #include <errno.h> 56 #include <stdio.h> 57 #include <stdlib.h> 58 #include <string.h> 59 #include <unistd.h> 60 61 #ifdef __STDC__ 62 #include <stdarg.h> 63 #else 64 #include <varargs.h> 65 #endif 66 67 void compress __P((char *, char *, int)); 68 void cwarn __P((const char *, ...)) __attribute__((__format__(__printf__,1,2))); 69 void cwarnx __P((const char *, ...)) __attribute__((__format__(__printf__,1,2))); 70 void decompress __P((char *, char *, int)); 71 int permission __P((char *)); 72 void setfile __P((char *, struct stat *)); 73 void usage __P((int)); 74 75 int main __P((int, char *[])); 76 extern FILE *zopen __P((const char *fname, const char *mode, int bits)); 77 78 int eval, force, verbose; 79 int isstdout, isstdin; 80 81 int 82 main(argc, argv) 83 int argc; 84 char *argv[]; 85 { 86 enum {COMPRESS, DECOMPRESS} style = COMPRESS; 87 size_t len; 88 int bits, cat, ch; 89 char *p, newname[MAXPATHLEN]; 90 91 if ((p = strrchr(argv[0], '/')) == NULL) 92 p = argv[0]; 93 else 94 ++p; 95 if (!strcmp(p, "uncompress")) 96 style = DECOMPRESS; 97 else if (!strcmp(p, "compress")) 98 style = COMPRESS; 99 else if (!strcmp(p, "zcat")) { 100 style = DECOMPRESS; 101 cat = 1; 102 } 103 else 104 errx(1, "unknown program name"); 105 106 bits = cat = 0; 107 while ((ch = getopt(argc, argv, "b:cdfv")) != -1) 108 switch(ch) { 109 case 'b': 110 bits = strtol(optarg, &p, 10); 111 if (*p) 112 errx(1, "illegal bit count -- %s", optarg); 113 break; 114 case 'c': 115 cat = 1; 116 break; 117 case 'd': /* Backward compatible. */ 118 style = DECOMPRESS; 119 break; 120 case 'f': 121 force = 1; 122 break; 123 case 'v': 124 verbose = 1; 125 break; 126 case '?': 127 default: 128 usage(style == COMPRESS); 129 } 130 argc -= optind; 131 argv += optind; 132 133 if (argc == 0) { 134 switch(style) { 135 case COMPRESS: 136 isstdout = 1; 137 isstdin = 1; 138 (void)compress("/dev/stdin", "/dev/stdout", bits); 139 break; 140 case DECOMPRESS: 141 isstdout = 1; 142 isstdin = 1; 143 (void)decompress("/dev/stdin", "/dev/stdout", bits); 144 break; 145 } 146 exit (eval); 147 } 148 149 if (cat == 1 && argc > 1) 150 errx(1, "the -c option permits only a single file argument"); 151 152 for (; *argv; ++argv) { 153 isstdout = 0; 154 switch(style) { 155 case COMPRESS: 156 if (cat) { 157 isstdout = 1; 158 compress(*argv, "/dev/stdout", bits); 159 break; 160 } 161 if ((p = strrchr(*argv, '.')) != NULL && 162 !strcmp(p, ".Z")) { 163 cwarnx("%s: name already has trailing .Z", 164 *argv); 165 break; 166 } 167 len = strlen(*argv); 168 if (len > sizeof(newname) - 3) { 169 cwarnx("%s: name too long", *argv); 170 break; 171 } 172 memmove(newname, *argv, len); 173 newname[len] = '.'; 174 newname[len + 1] = 'Z'; 175 newname[len + 2] = '\0'; 176 compress(*argv, newname, bits); 177 break; 178 case DECOMPRESS: 179 len = strlen(*argv); 180 if ((p = strrchr(*argv, '.')) == NULL || 181 strcmp(p, ".Z")) { 182 if (len > sizeof(newname) - 3) { 183 cwarnx("%s: name too long", *argv); 184 break; 185 } 186 memmove(newname, *argv, len); 187 newname[len] = '.'; 188 newname[len + 1] = 'Z'; 189 newname[len + 2] = '\0'; 190 decompress(newname, 191 cat ? "/dev/stdout" : *argv, bits); 192 if (cat) 193 isstdout = 1; 194 } else { 195 if (len - 2 > sizeof(newname) - 1) { 196 cwarnx("%s: name too long", *argv); 197 break; 198 } 199 memmove(newname, *argv, len - 2); 200 newname[len - 2] = '\0'; 201 decompress(*argv, 202 cat ? "/dev/stdout" : newname, bits); 203 if (cat) 204 isstdout = 1; 205 } 206 break; 207 } 208 } 209 exit (eval); 210 } 211 212 void 213 compress(in, out, bits) 214 char *in, *out; 215 int bits; 216 { 217 int nr; 218 struct stat isb, sb; 219 FILE *ifp, *ofp; 220 int exists, isreg, oreg; 221 u_char buf[BUFSIZ]; 222 223 if (!isstdout) { 224 exists = !stat(out, &sb); 225 if (!force && exists && S_ISREG(sb.st_mode) && !permission(out)) 226 return; 227 oreg = !exists || S_ISREG(sb.st_mode); 228 } else 229 oreg = 0; 230 231 ifp = ofp = NULL; 232 if ((ifp = fopen(in, "r")) == NULL) { 233 cwarn("%s", in); 234 return; 235 } 236 237 if (!isstdin) { 238 if (stat(in, &isb)) { /* DON'T FSTAT! */ 239 cwarn("%s", in); 240 goto err; 241 } 242 if (!S_ISREG(isb.st_mode)) 243 isreg = 0; 244 else 245 isreg = 1; 246 } else 247 isreg = 0; 248 249 if ((ofp = zopen(out, "w", bits)) == NULL) { 250 cwarn("%s", out); 251 goto err; 252 } 253 while ((nr = fread(buf, 1, sizeof(buf), ifp)) != 0) 254 if (fwrite(buf, 1, nr, ofp) != nr) { 255 cwarn("%s", out); 256 goto err; 257 } 258 259 if (ferror(ifp) || fclose(ifp)) { 260 cwarn("%s", in); 261 goto err; 262 } 263 ifp = NULL; 264 265 if (fclose(ofp)) { 266 cwarn("%s", out); 267 goto err; 268 } 269 ofp = NULL; 270 271 if (isreg && oreg) { 272 if (stat(out, &sb)) { 273 cwarn("%s", out); 274 goto err; 275 } 276 277 if (!force && sb.st_size >= isb.st_size) { 278 if (verbose) 279 (void)printf("%s: file would grow; left unmodified\n", in); 280 if (unlink(out)) 281 cwarn("%s", out); 282 goto err; 283 } 284 285 setfile(out, &isb); 286 287 if (unlink(in)) 288 cwarn("%s", in); 289 290 if (verbose) { 291 (void)printf("%s: ", out); 292 if (isb.st_size > sb.st_size) 293 (void)printf("%.0f%% compression\n", 294 ((double)sb.st_size / isb.st_size) * 100.0); 295 else 296 (void)printf("%.0f%% expansion\n", 297 ((double)isb.st_size / sb.st_size) * 100.0); 298 } 299 } 300 return; 301 302 err: if (ofp) { 303 if (oreg) 304 (void)unlink(out); 305 (void)fclose(ofp); 306 } 307 if (ifp) 308 (void)fclose(ifp); 309 } 310 311 void 312 decompress(in, out, bits) 313 char *in, *out; 314 int bits; 315 { 316 int nr; 317 struct stat sb; 318 FILE *ifp, *ofp; 319 int exists, isreg, oreg; 320 u_char buf[BUFSIZ]; 321 322 if (!isstdout) { 323 exists = !stat(out, &sb); 324 if (!force && exists && S_ISREG(sb.st_mode) && !permission(out)) 325 return; 326 oreg = !exists || S_ISREG(sb.st_mode); 327 } else 328 oreg = 0; 329 330 ifp = ofp = NULL; 331 if ((ofp = fopen(out, "w")) == NULL) { 332 cwarn("%s", out); 333 return; 334 } 335 336 if ((ifp = zopen(in, "r", bits)) == NULL) { 337 cwarn("%s", in); 338 goto err; 339 } 340 if (!isstdin) { 341 if (stat(in, &sb)) { 342 cwarn("%s", in); 343 goto err; 344 } 345 if (!S_ISREG(sb.st_mode)) 346 isreg = 0; 347 else 348 isreg = 1; 349 } else 350 isreg = 0; 351 352 while ((nr = fread(buf, 1, sizeof(buf), ifp)) != 0) 353 if (fwrite(buf, 1, nr, ofp) != nr) { 354 cwarn("%s", out); 355 goto err; 356 } 357 358 if (ferror(ifp) || fclose(ifp)) { 359 cwarn("%s", in); 360 goto err; 361 } 362 ifp = NULL; 363 364 if (fclose(ofp)) { 365 cwarn("%s", out); 366 goto err; 367 } 368 369 if (isreg && oreg) { 370 setfile(out, &sb); 371 372 if (unlink(in)) 373 cwarn("%s", in); 374 } 375 return; 376 377 err: if (ofp) { 378 if (oreg) 379 (void)unlink(out); 380 (void)fclose(ofp); 381 } 382 if (ifp) 383 (void)fclose(ifp); 384 } 385 386 void 387 setfile(name, fs) 388 char *name; 389 struct stat *fs; 390 { 391 static struct timeval tv[2]; 392 393 fs->st_mode &= S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO; 394 395 TIMESPEC_TO_TIMEVAL(&tv[0], &fs->st_atimespec); 396 TIMESPEC_TO_TIMEVAL(&tv[1], &fs->st_mtimespec); 397 if (utimes(name, tv)) 398 cwarn("utimes: %s", name); 399 400 /* 401 * Changing the ownership probably won't succeed, unless we're root 402 * or POSIX_CHOWN_RESTRICTED is not set. Set uid/gid before setting 403 * the mode; current BSD behavior is to remove all setuid bits on 404 * chown. If chown fails, lose setuid/setgid bits. 405 */ 406 if (chown(name, fs->st_uid, fs->st_gid)) { 407 if (errno != EPERM) 408 cwarn("chown: %s", name); 409 fs->st_mode &= ~(S_ISUID|S_ISGID); 410 } 411 if (chmod(name, fs->st_mode)) 412 cwarn("chown: %s", name); 413 414 /* 415 * Restore the file's flags. However, do this only if the original 416 * file had any flags set; this avoids a warning on file-systems that 417 * do not support flags. 418 */ 419 if (fs->st_flags != 0 && chflags(name, fs->st_flags)) 420 cwarn("chflags: %s", name); 421 } 422 423 int 424 permission(fname) 425 char *fname; 426 { 427 int ch, first; 428 429 if (!isatty(fileno(stderr))) 430 return (0); 431 (void)fprintf(stderr, "overwrite %s? ", fname); 432 first = ch = getchar(); 433 while (ch != '\n' && ch != EOF) 434 ch = getchar(); 435 return (first == 'y'); 436 } 437 438 void 439 usage(iscompress) 440 int iscompress; 441 { 442 if (iscompress) 443 (void)fprintf(stderr, 444 "usage: compress [-cfv] [-b bits] [file ...]\n"); 445 else 446 (void)fprintf(stderr, 447 "usage: uncompress [-c] [-b bits] [file ...]\n"); 448 exit(1); 449 } 450 451 void 452 #if __STDC__ 453 cwarnx(const char *fmt, ...) 454 #else 455 cwarnx(fmt, va_alist) 456 int eval; 457 const char *fmt; 458 va_dcl 459 #endif 460 { 461 va_list ap; 462 #if __STDC__ 463 va_start(ap, fmt); 464 #else 465 va_start(ap); 466 #endif 467 vwarnx(fmt, ap); 468 va_end(ap); 469 eval = 1; 470 } 471 472 void 473 #if __STDC__ 474 cwarn(const char *fmt, ...) 475 #else 476 cwarn(fmt, va_alist) 477 int eval; 478 const char *fmt; 479 va_dcl 480 #endif 481 { 482 va_list ap; 483 #if __STDC__ 484 va_start(ap, fmt); 485 #else 486 va_start(ap); 487 #endif 488 vwarn(fmt, ap); 489 va_end(ap); 490 eval = 1; 491 } 492