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