1 /* $OpenBSD: main.c,v 1.15 2000/12/12 16:23:27 millert 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 static char copyright[] = 37 "@(#) Copyright (c) 1992, 1993\n\ 38 The Regents of the University of California. All rights reserved.\n"; 39 40 #ifndef lint 41 #if 0 42 static char sccsid[] = "@(#)compress.c 8.2 (Berkeley) 1/7/94"; 43 #else 44 static char rcsid[] = "$OpenBSD: main.c,v 1.15 2000/12/12 16:23:27 millert Exp $"; 45 #endif 46 #endif /* not lint */ 47 48 #include <sys/param.h> 49 #include <sys/time.h> 50 #include <sys/stat.h> 51 52 #include <err.h> 53 #include <errno.h> 54 #include <stdio.h> 55 #include <stdlib.h> 56 #include <string.h> 57 #include <unistd.h> 58 #include <fcntl.h> 59 #include <paths.h> 60 #include "compress.h" 61 62 #define min(a,b) ((a) < (b)? (a) : (b)) 63 64 int pipin = 0, force = 0, verbose = 0, testmode = 0, list = 0, nosave = 0; 65 extern char *__progname; 66 67 struct compressor { 68 char *name; 69 char *suffix; 70 int (*check_header) __P((int, struct stat *, const char *)); 71 void *(*open) __P((int, const char *, int)); 72 int (*read) __P((void *, char *, int)); 73 int (*write) __P((void *, const char *, int)); 74 int (*close) __P((void *)); 75 } c_table[] = { 76 #define M_COMPRESS (&c_table[0]) 77 { "compress", ".Z", z_check_header, z_open, zread, zwrite, zclose }, 78 #define M_DEFLATE (&c_table[1]) 79 { "deflate", ".gz", gz_check_header, gz_open, gz_read, gz_write, gz_close }, 80 { NULL } 81 }; 82 83 int permission __P((char *)); 84 void setfile __P((char *, struct stat *)); 85 void usage __P((void)); 86 int compress 87 __P((const char *, const char *, register struct compressor *, int)); 88 int decompress 89 __P((const char *, const char *, register struct compressor *, int)); 90 struct compressor *check_method __P((int, const char *)); 91 92 struct stat sb, osb; 93 94 int 95 main(argc, argv) 96 int argc; 97 char *argv[]; 98 { 99 int ch, bits, cat, decomp, error; 100 struct compressor *method; 101 int exists, isreg, oreg; 102 char *infile, outfile[MAXPATHLEN+4], suffix[16]; 103 char *p; 104 int rc = 0; 105 106 bits = cat = decomp = 0; 107 p = __progname; 108 if (p[0] == 'g') { 109 method = M_DEFLATE; 110 p++; 111 } else 112 method = M_COMPRESS; 113 114 decomp = 0; 115 if (!strcmp(p, "zcat")) { 116 decomp++; 117 cat++; 118 } else { 119 if (p[0] == 'u' && p[1] == 'n') { 120 p += 2; 121 decomp++; 122 } 123 124 if (strcmp(p, "zip") && 125 strcmp(p, "compress")) 126 errx(1, "unknown program name"); 127 } 128 129 outfile[0] = '\0'; 130 while ((ch = getopt(argc, argv, "0123456789b:cdfghlnOo:qS:tv")) != -1) 131 switch(ch) { 132 case '0': 133 case '1': 134 case '2': 135 case '3': 136 case '4': 137 case '5': 138 case '6': 139 case '7': 140 case '8': 141 case '9': 142 method = M_DEFLATE; 143 bits = ch - '0'; 144 break; 145 case 'b': 146 bits = strtol(optarg, &p, 10); 147 /* 148 * POSIX 1002.3 says 9 <= bits <= 14 for portable 149 * apps, but says the implementation may allow 150 * greater. 151 */ 152 if (*p) 153 errx(1, "illegal bit count -- %s", optarg); 154 break; 155 case 'c': 156 cat++; 157 break; 158 case 'd': /* Backward compatible. */ 159 decomp++; 160 break; 161 case 'f': 162 force++; 163 break; 164 case 'g': 165 method = M_DEFLATE; 166 break; 167 case 'l': 168 list++; 169 break; 170 case 'L': 171 fputs(copyright, stderr); 172 case 'n': 173 nosave++; 174 break; 175 case 'N': 176 nosave = 0; 177 break; 178 case 'O': 179 method = M_COMPRESS; 180 break; 181 case 'o': 182 strncpy(outfile, optarg, sizeof(outfile)-1); 183 outfile[sizeof(outfile)-1] = '\0'; 184 break; 185 case 'q': 186 verbose = -1; 187 break; 188 case 'S': 189 p = suffix; 190 if (optarg[0] != '.') 191 *p++ = '.'; 192 strncpy(p, optarg, sizeof(suffix) - (p - suffix) - 1); 193 break; 194 case 't': 195 testmode++; 196 break; 197 case 'v': 198 verbose++; 199 break; 200 case 'h': 201 case '?': 202 default: 203 usage(); 204 } 205 argc -= optind; 206 argv += optind; 207 208 do { 209 if (*argv != NULL) { 210 infile = *argv; 211 if (outfile[0] == '\0') { 212 if (!decomp && !cat && outfile[0] == '\0') { 213 int len; 214 char *p; 215 216 snprintf(outfile, sizeof(outfile), 217 "%s%s", infile, 218 method->suffix); 219 220 len = strlen(outfile); 221 if (len > MAXPATHLEN) { 222 errx(1, "pathname%s too long", 223 method->suffix); 224 } 225 226 p = strrchr(outfile, '/'); 227 if (p == NULL) p = outfile; 228 len = strlen(p); 229 if (len > NAME_MAX) { 230 errx(1, "filename%s too long", 231 method->suffix); 232 } 233 } else if (decomp && !cat) { 234 char *p = strrchr(infile, '.'); 235 if (p != NULL) 236 for (method = &c_table[0]; 237 method->name != NULL && 238 !strcmp(p, method->suffix); 239 method++) 240 ; 241 if (method->name != NULL) { 242 int l = min(sizeof(outfile), 243 (p - infile)); 244 strncpy(outfile, infile, l); 245 outfile[l] = '\0'; 246 } 247 } 248 } 249 } else { 250 infile = "/dev/stdin"; 251 pipin++; 252 } 253 254 if (testmode) 255 strcpy(outfile, _PATH_DEVNULL); 256 else if (cat || outfile[0] == '\0') { 257 strcpy(outfile, "/dev/stdout"); 258 cat++; 259 } 260 261 exists = !stat(outfile, &sb); 262 if (!force && exists && S_ISREG(sb.st_mode) && 263 !permission(outfile)) { 264 argv++; 265 continue; 266 } 267 isreg = oreg = !exists || S_ISREG(sb.st_mode); 268 269 if (stat(infile, &sb) != 0 && verbose >= 0) 270 err(1, "%s", infile); 271 272 if (!S_ISREG(sb.st_mode)) 273 isreg = 0; 274 275 if (verbose > 0) 276 fprintf(stderr, "%s:\t", infile); 277 278 error = (decomp? decompress: compress) 279 (infile, outfile, method, bits); 280 281 if (!error && isreg && stat(outfile, &osb) == 0) { 282 283 if (!force && !decomp && osb.st_size >= sb.st_size) { 284 if (verbose > 0) 285 fprintf(stderr, "file would grow; " 286 "left unmodified\n"); 287 error = 1; 288 rc = 2; 289 } else { 290 291 setfile(outfile, &sb); 292 293 if (unlink(infile) && verbose >= 0) 294 warn("%s", infile); 295 296 if (verbose > 0) { 297 u_int ratio; 298 ratio = (1000*osb.st_size)/sb.st_size; 299 fprintf(stderr, "%u", ratio / 10); 300 if (ratio % 10) 301 fprintf(stderr, ".%u", 302 ratio % 10); 303 fputc('%', stderr); 304 fputc(' ', stderr); 305 } 306 } 307 } 308 309 if (error && oreg && unlink(outfile) && errno != ENOENT && 310 verbose >= 0) 311 warn("%s", outfile); 312 else if (!error && verbose > 0) 313 fputs("OK\n", stderr); 314 315 outfile[0] = '\0'; 316 if (*argv != NULL) 317 argv++; 318 319 } while (*argv != NULL); 320 321 return (rc); 322 } 323 324 int 325 compress(in, out, method, bits) 326 const char *in; 327 const char *out; 328 register struct compressor *method; 329 int bits; 330 { 331 register int ifd; 332 int ofd; 333 register void *cookie; 334 register ssize_t nr; 335 u_char buf[Z_BUFSIZE]; 336 int error; 337 338 error = 0; 339 cookie = NULL; 340 341 if ((ofd = open(out, O_WRONLY|O_CREAT, S_IWUSR)) < 0) { 342 if (verbose >= 0) 343 warn("%s", out); 344 return -1; 345 } 346 347 if (method != M_COMPRESS && !force && isatty(ofd)) { 348 if (verbose >= 0) 349 warnx("%s: won't write compressed data to terminal", 350 out); 351 return -1; 352 } 353 354 if ((ifd = open(in, O_RDONLY)) >= 0 && 355 (cookie = (*method->open)(ofd, "w", bits)) != NULL) { 356 357 while ((nr = read(ifd, buf, sizeof(buf))) > 0) 358 if ((method->write)(cookie, buf, nr) != nr) { 359 if (verbose >= 0) 360 warn("%s", out); 361 error++; 362 break; 363 } 364 } 365 366 if (ifd < 0 || close(ifd) || nr < 0) { 367 if (!error && verbose >= 0) 368 warn("%s", in); 369 error++; 370 } 371 372 if (cookie == NULL || (method->close)(cookie)) { 373 if (!error && verbose >= 0) 374 warn("%s", out); 375 error++; 376 (void) close(ofd); 377 } 378 379 return error? -1 : 0; 380 } 381 382 struct compressor * 383 check_method(fd, out) 384 int fd; 385 const char *out; 386 { 387 register struct compressor *method; 388 389 for (method = &c_table[0]; 390 method->name != NULL && 391 !(*method->check_header)(fd, &sb, out); 392 method++) 393 ; 394 395 if (method->name == NULL) 396 method = NULL; 397 398 return method; 399 } 400 401 int 402 decompress(in, out, method, bits) 403 const char *in; 404 const char *out; 405 register struct compressor *method; 406 int bits; 407 { 408 int ifd; 409 register int ofd; 410 register void *cookie; 411 register ssize_t nr; 412 u_char buf[Z_BUFSIZE]; 413 int error; 414 415 error = 0; 416 cookie = NULL; 417 418 if ((ifd = open(in, O_RDONLY)) < 0) { 419 if (verbose >= 0) 420 warn("%s", in); 421 return -1; 422 } 423 424 if (!force && isatty(ifd)) { 425 if (verbose >= 0) 426 warnx("%s: won't read compressed data from terminal", 427 in); 428 close (ifd); 429 return -1; 430 } 431 432 if (!pipin && (method = check_method(ifd, out)) == NULL) { 433 if (verbose >= 0) 434 warnx("%s: unrecognized file format", in); 435 close (ifd); 436 return -1; 437 } 438 439 if ((ofd = open(out, O_WRONLY|O_CREAT, S_IWUSR)) >= 0 && 440 (cookie = (*method->open)(ifd, "r", bits)) != NULL) { 441 442 while ((nr = (method->read)(cookie, buf, sizeof(buf))) > 0) 443 if (write(ofd, buf, nr) != nr) { 444 if (verbose >= 0) 445 warn("%s", out); 446 error++; 447 break; 448 } 449 } 450 451 if (ofd < 0 || close(ofd)) { 452 if (!error && verbose >= 0) 453 warn("%s", out); 454 error++; 455 } 456 457 if (cookie == NULL || (method->close)(cookie) || nr < 0) { 458 if (!error && verbose >= 0) 459 warn("%s", in); 460 error++; 461 (void) close (ifd); 462 } 463 464 return error; 465 } 466 467 void 468 setfile(name, fs) 469 char *name; 470 register struct stat *fs; 471 { 472 static struct timeval tv[2]; 473 474 fs->st_mode &= S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO; 475 476 TIMESPEC_TO_TIMEVAL(&tv[0], &fs->st_atimespec); 477 TIMESPEC_TO_TIMEVAL(&tv[1], &fs->st_mtimespec); 478 if (utimes(name, tv)) 479 warn("utimes: %s", name); 480 481 /* 482 * Changing the ownership probably won't succeed, unless we're root 483 * or POSIX_CHOWN_RESTRICTED is not set. Set uid/gid before setting 484 * the mode; current BSD behavior is to remove all setuid bits on 485 * chown. If chown fails, lose setuid/setgid bits. 486 */ 487 if (chown(name, fs->st_uid, fs->st_gid)) { 488 if (errno != EPERM) 489 warn("chown: %s", name); 490 fs->st_mode &= ~(S_ISUID|S_ISGID); 491 } 492 if (chmod(name, fs->st_mode)) 493 warn("chown: %s", name); 494 495 if (fs->st_flags && chflags(name, fs->st_flags)) 496 warn("chflags: %s", name); 497 } 498 499 int 500 permission(fname) 501 char *fname; 502 { 503 int ch, first; 504 505 if (!isatty(fileno(stderr))) 506 return (0); 507 (void)fprintf(stderr, "overwrite %s? ", fname); 508 first = ch = getchar(); 509 while (ch != '\n' && ch != EOF) 510 ch = getchar(); 511 return (first == 'y'); 512 } 513 514 void 515 usage() 516 { 517 fprintf(stderr, 518 "usage: %s [-cdfghlnOtqv] [-b <bits>] [-[0-9]] [file ...]\n", 519 __progname); 520 exit(1); 521 } 522 523