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