/usr.bin/compress/compress.c

https://bitbucket.org/freebsd/freebsd-head/ · C · 436 lines · 350 code · 48 blank · 38 comment · 109 complexity · d1ad417e4e8dcd2e91cebb2dc931249e MD5 · raw file

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