1 /* minigzip.c -- simulate gzip using the zlib compression library
2 * Copyright (C) 1995-2026 Jean-loup Gailly
3 * For conditions of distribution and use, see copyright notice in zlib.h
4 */
5
6 /*
7 * minigzip is a minimal implementation of the gzip utility. This is
8 * only an example of using zlib and isn't meant to replace the
9 * full-featured gzip. No attempt is made to deal with file systems
10 * limiting names to 14 or 8+3 characters, etc... Error checking is
11 * very limited. So use minigzip only for testing; use gzip for the
12 * real thing. On MSDOS, use only on file names without extension
13 * or in pipe mode.
14 */
15
16 /* @(#) $Id$ */
17
18 #ifndef _POSIX_C_SOURCE
19 # define _POSIX_C_SOURCE 200112L
20 #endif
21
22 #if defined(_WIN32) && !defined(_CRT_SECURE_NO_WARNINGS)
23 # define _CRT_SECURE_NO_WARNINGS
24 #endif
25 #if defined(_WIN32) && !defined(_CRT_NONSTDC_NO_DEPRECATE)
26 # define _CRT_NONSTDC_NO_DEPRECATE
27 #endif
28
29 #include "zlib.h"
30 #include <stdio.h>
31
32 #ifdef STDC
33 # include <string.h>
34 # include <stdlib.h>
35 #endif
36
37 #ifdef USE_MMAP
38 # include <sys/types.h>
39 # include <sys/mman.h>
40 # include <sys/stat.h>
41 #endif
42
43 #if defined(MSDOS) || defined(OS2) || defined(WIN32) || defined(__CYGWIN__)
44 # include <fcntl.h>
45 # include <io.h>
46 # ifdef UNDER_CE
47 # include <stdlib.h>
48 # endif
49 # define SET_BINARY_MODE(file) setmode(fileno(file), O_BINARY)
50 #else
51 # define SET_BINARY_MODE(file)
52 #endif
53
54 #ifdef VMS
55 # define unlink delete
56 # define GZ_SUFFIX "-gz"
57 #endif
58 #if defined(__riscos) && !defined(__TARGET_UNIXLIB__)
59 # define GZ_SUFFIX "/gz"
60 # ifndef __GNUC__
61 # define unlink remove
62 # define fileno(file) file->__file
63 # endif
64 #endif
65 #if defined(__MWERKS__) && __dest_os != __be_os && __dest_os != __win32_os
66 # include <unix.h> /* for fileno */
67 #endif
68
69 #if !defined(Z_HAVE_UNISTD_H) && !defined(_LARGEFILE64_SOURCE)
70 #ifndef WIN32 /* unlink already in stdio.h for WIN32 */
71 extern int unlink(const char *);
72 #endif
73 #endif
74
75 #if defined(UNDER_CE)
76 # include <windows.h>
77 # define perror(s) pwinerror(s)
78
79 /* Map the Windows error number in ERROR to a locale-dependent error
80 message string and return a pointer to it. Typically, the values
81 for ERROR come from GetLastError.
82
83 The string pointed to shall not be modified by the application,
84 but may be overwritten by a subsequent call to strwinerror
85
86 The strwinerror function does not change the current setting
87 of GetLastError. */
88
strwinerror(error)89 static char *strwinerror (error)
90 DWORD error;
91 {
92 static char buf[1024];
93
94 wchar_t *msgbuf;
95 DWORD lasterr = GetLastError();
96 DWORD chars = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM
97 | FORMAT_MESSAGE_ALLOCATE_BUFFER,
98 NULL,
99 error,
100 0, /* Default language */
101 (LPVOID)&msgbuf,
102 0,
103 NULL);
104 if (chars != 0) {
105 /* If there is an \r\n appended, zap it. */
106 if (chars >= 2
107 && msgbuf[chars - 2] == '\r' && msgbuf[chars - 1] == '\n') {
108 chars -= 2;
109 msgbuf[chars] = 0;
110 }
111
112 if (chars > sizeof (buf) - 1) {
113 chars = sizeof (buf) - 1;
114 msgbuf[chars] = 0;
115 }
116
117 wcstombs(buf, msgbuf, chars + 1);
118 LocalFree(msgbuf);
119 }
120 else {
121 sprintf(buf, "unknown win32 error (%lu)", error);
122 }
123
124 SetLastError(lasterr);
125 return buf;
126 }
127
pwinerror(s)128 static void pwinerror (s)
129 const char *s;
130 {
131 if (s && *s)
132 fprintf(stderr, "%s: %s\n", s, strwinerror(GetLastError ()));
133 else
134 fprintf(stderr, "%s\n", strwinerror(GetLastError ()));
135 }
136
137 #endif /* UNDER_CE */
138
139 #ifndef GZ_SUFFIX
140 # define GZ_SUFFIX ".gz"
141 #endif
142 #define SUFFIX_LEN (sizeof(GZ_SUFFIX)-1)
143
144 #define BUFLEN 16384
145 #define MAX_NAME_LEN 1024
146
147 #ifdef MAXSEG_64K
148 # define local static
149 /* Needed for systems with limitation on stack size. */
150 #else
151 # define local
152 #endif
153
154 /* ===========================================================================
155 * Safe string copy. Copy up to len bytes from src to dst, if src terminates
156 * with a null by then. If not, copy len-1 bytes from src, terminating it with
157 * a null in dst[len-1], cutting src short. Return a pointer to the terminating
158 * null. If len is zero, nothing is written to *dst and NULL is returned.
159 */
string_copy(char * dst,char const * src,z_size_t len)160 static char *string_copy(char *dst, char const *src, z_size_t len) {
161 if (len == 0)
162 return NULL;
163 while (--len) {
164 *dst = *src++;
165 if (*dst == 0)
166 return dst;
167 dst++;
168 }
169 *dst = 0;
170 return dst;
171 }
172
173 #ifdef Z_SOLO
174 /* for Z_SOLO, create simplified gz* functions using deflate and inflate */
175
176 #if defined(Z_HAVE_UNISTD_H) || defined(Z_LARGE)
177 # include <unistd.h> /* for unlink() */
178 #endif
179
myalloc(void * q,unsigned n,unsigned m)180 static void *myalloc(void *q, unsigned n, unsigned m) {
181 (void)q;
182 return calloc(n, m);
183 }
184
myfree(void * q,void * p)185 static void myfree(void *q, void *p) {
186 (void)q;
187 free(p);
188 }
189
190 typedef struct gzFile_s {
191 FILE *file;
192 int write;
193 int err;
194 char *msg;
195 z_stream strm;
196 } *gzFile;
197
gz_open(const char * path,int fd,const char * mode)198 static gzFile gz_open(const char *path, int fd, const char *mode) {
199 gzFile gz;
200 int ret;
201
202 gz = malloc(sizeof(struct gzFile_s));
203 if (gz == NULL)
204 return NULL;
205 gz->write = strchr(mode, 'w') != NULL;
206 gz->strm.zalloc = myalloc;
207 gz->strm.zfree = myfree;
208 gz->strm.opaque = Z_NULL;
209 if (gz->write)
210 ret = deflateInit2(&(gz->strm), -1, 8, 15 + 16, 8, 0);
211 else {
212 gz->strm.next_in = 0;
213 gz->strm.avail_in = Z_NULL;
214 ret = inflateInit2(&(gz->strm), 15 + 16);
215 }
216 if (ret != Z_OK) {
217 free(gz);
218 return NULL;
219 }
220 gz->file = path == NULL ? fdopen(fd, gz->write ? "wb" : "rb") :
221 fopen(path, gz->write ? "wb" : "rb");
222 if (gz->file == NULL) {
223 gz->write ? deflateEnd(&(gz->strm)) : inflateEnd(&(gz->strm));
224 free(gz);
225 return NULL;
226 }
227 gz->err = 0;
228 gz->msg = "";
229 return gz;
230 }
231
gzopen(const char * path,const char * mode)232 static gzFile gzopen(const char *path, const char *mode) {
233 return gz_open(path, -1, mode);
234 }
235
gzdopen(int fd,const char * mode)236 static gzFile gzdopen(int fd, const char *mode) {
237 return gz_open(NULL, fd, mode);
238 }
239
gzwrite(gzFile gz,const void * buf,unsigned len)240 static int gzwrite(gzFile gz, const void *buf, unsigned len) {
241 z_stream *strm;
242 unsigned char out[BUFLEN];
243
244 if (gz == NULL || !gz->write)
245 return 0;
246 strm = &(gz->strm);
247 strm->next_in = (void *)buf;
248 strm->avail_in = len;
249 do {
250 strm->next_out = out;
251 strm->avail_out = BUFLEN;
252 (void)deflate(strm, Z_NO_FLUSH);
253 fwrite(out, 1, BUFLEN - strm->avail_out, gz->file);
254 } while (strm->avail_out == 0);
255 return (int)len;
256 }
257
gzread(gzFile gz,void * buf,unsigned len)258 static int gzread(gzFile gz, void *buf, unsigned len) {
259 int ret;
260 unsigned got;
261 unsigned char in[1];
262 z_stream *strm;
263
264 if (gz == NULL || gz->write)
265 return 0;
266 if (gz->err)
267 return 0;
268 strm = &(gz->strm);
269 strm->next_out = (void *)buf;
270 strm->avail_out = len;
271 do {
272 got = (unsigned)fread(in, 1, 1, gz->file);
273 if (got == 0)
274 break;
275 strm->next_in = in;
276 strm->avail_in = 1;
277 ret = inflate(strm, Z_NO_FLUSH);
278 if (ret == Z_DATA_ERROR) {
279 gz->err = Z_DATA_ERROR;
280 gz->msg = strm->msg;
281 return 0;
282 }
283 if (ret == Z_STREAM_END)
284 inflateReset(strm);
285 } while (strm->avail_out);
286 return (int)(len - strm->avail_out);
287 }
288
gzclose(gzFile gz)289 static int gzclose(gzFile gz) {
290 z_stream *strm;
291 unsigned char out[BUFLEN];
292
293 if (gz == NULL)
294 return Z_STREAM_ERROR;
295 strm = &(gz->strm);
296 if (gz->write) {
297 strm->next_in = Z_NULL;
298 strm->avail_in = 0;
299 do {
300 strm->next_out = out;
301 strm->avail_out = BUFLEN;
302 (void)deflate(strm, Z_FINISH);
303 fwrite(out, 1, BUFLEN - strm->avail_out, gz->file);
304 } while (strm->avail_out == 0);
305 deflateEnd(strm);
306 }
307 else
308 inflateEnd(strm);
309 fclose(gz->file);
310 free(gz);
311 return Z_OK;
312 }
313
gzerror(gzFile gz,int * err)314 static const char *gzerror(gzFile gz, int *err) {
315 *err = gz->err;
316 return gz->msg;
317 }
318
319 #endif
320
321 static char *prog;
322
323 /* ===========================================================================
324 * Display error message and exit
325 */
error(const char * msg)326 static void error(const char *msg) {
327 fprintf(stderr, "%s: %s\n", prog, msg);
328 exit(1);
329 }
330
331 #ifdef USE_MMAP /* MMAP version, Miguel Albrecht <malbrech@eso.org> */
332
333 /* Try compressing the input file at once using mmap. Return Z_OK if
334 * success, Z_ERRNO otherwise.
335 */
gz_compress_mmap(FILE * in,gzFile out)336 static int gz_compress_mmap(FILE *in, gzFile out) {
337 int len;
338 int err;
339 int ifd = fileno(in);
340 caddr_t buf; /* mmap'ed buffer for the entire input file */
341 off_t buf_len; /* length of the input file */
342 struct stat sb;
343
344 /* Determine the size of the file, needed for mmap: */
345 if (fstat(ifd, &sb) < 0) return Z_ERRNO;
346 buf_len = sb.st_size;
347 if (buf_len <= 0) return Z_ERRNO;
348
349 /* Now do the actual mmap: */
350 buf = mmap((caddr_t) 0, buf_len, PROT_READ, MAP_SHARED, ifd, (off_t)0);
351 if (buf == (caddr_t)(-1)) return Z_ERRNO;
352
353 /* Compress the whole file at once: */
354 len = gzwrite(out, (char *)buf, (unsigned)buf_len);
355
356 if (len != (int)buf_len) error(gzerror(out, &err));
357
358 munmap(buf, buf_len);
359 fclose(in);
360 if (gzclose(out) != Z_OK) error("failed gzclose");
361 return Z_OK;
362 }
363 #endif /* USE_MMAP */
364
365 /* ===========================================================================
366 * Compress input to output then close both files.
367 */
368
gz_compress(FILE * in,gzFile out)369 static void gz_compress(FILE *in, gzFile out) {
370 local char buf[BUFLEN];
371 int len;
372 int err;
373
374 #ifdef USE_MMAP
375 /* Try first compressing with mmap. If mmap fails (minigzip used in a
376 * pipe), use the normal fread loop.
377 */
378 if (gz_compress_mmap(in, out) == Z_OK) return;
379 #endif
380 for (;;) {
381 len = (int)fread(buf, 1, sizeof(buf), in);
382 if (ferror(in)) {
383 perror("fread");
384 exit(1);
385 }
386 if (len == 0) break;
387
388 if (gzwrite(out, buf, (unsigned)len) != len) error(gzerror(out, &err));
389 }
390 fclose(in);
391 if (gzclose(out) != Z_OK) error("failed gzclose");
392 }
393
394 /* ===========================================================================
395 * Uncompress input to output then close both files.
396 */
gz_uncompress(gzFile in,FILE * out)397 static void gz_uncompress(gzFile in, FILE *out) {
398 local char buf[BUFLEN];
399 int len;
400 int err;
401
402 for (;;) {
403 len = gzread(in, buf, sizeof(buf));
404 if (len < 0) error (gzerror(in, &err));
405 if (len == 0) break;
406
407 if ((int)fwrite(buf, 1, (unsigned)len, out) != len) {
408 error("failed fwrite");
409 }
410 }
411 if (fclose(out)) error("failed fclose");
412
413 if (gzclose(in) != Z_OK) error("failed gzclose");
414 }
415
416
417 /* ===========================================================================
418 * Compress the given file: create a corresponding .gz file and remove the
419 * original.
420 */
file_compress(char * file,char * mode)421 static void file_compress(char *file, char *mode) {
422 local char outfile[MAX_NAME_LEN+1], *end;
423 FILE *in;
424 gzFile out;
425
426 if (strlen(file) + strlen(GZ_SUFFIX) >= sizeof(outfile)) {
427 fprintf(stderr, "%s: filename too long\n", prog);
428 exit(1);
429 }
430
431 end = string_copy(outfile, file, sizeof(outfile));
432 string_copy(end, GZ_SUFFIX, sizeof(outfile) - (z_size_t)(end - outfile));
433
434 in = fopen(file, "rb");
435 if (in == NULL) {
436 perror(file);
437 exit(1);
438 }
439 out = gzopen(outfile, mode);
440 if (out == NULL) {
441 fclose(in);
442 fprintf(stderr, "%s: can't gzopen %s\n", prog, outfile);
443 exit(1);
444 }
445 gz_compress(in, out);
446
447 unlink(file);
448 }
449
450
451 /* ===========================================================================
452 * Uncompress the given file and remove the original.
453 */
file_uncompress(char * file)454 static void file_uncompress(char *file) {
455 local char buf[MAX_NAME_LEN+1];
456 char *infile, *outfile;
457 FILE *out;
458 gzFile in;
459 z_size_t len = strlen(file);
460
461 if (len + strlen(GZ_SUFFIX) >= sizeof(buf)) {
462 fprintf(stderr, "%s: filename too long\n", prog);
463 exit(1);
464 }
465
466 string_copy(buf, file, sizeof(buf));
467
468 if (len > SUFFIX_LEN && strcmp(file+len-SUFFIX_LEN, GZ_SUFFIX) == 0) {
469 infile = file;
470 outfile = buf;
471 outfile[len-3] = '\0';
472 } else {
473 outfile = file;
474 infile = buf;
475 string_copy(buf + len, GZ_SUFFIX, sizeof(buf) - len);
476 }
477 in = gzopen(infile, "rb");
478 if (in == NULL) {
479 fprintf(stderr, "%s: can't gzopen %s\n", prog, infile);
480 exit(1);
481 }
482 out = fopen(outfile, "wb");
483 if (out == NULL) {
484 gzclose(in);
485 perror(file);
486 exit(1);
487 }
488
489 gz_uncompress(in, out);
490
491 unlink(infile);
492 }
493
494
495 /* ===========================================================================
496 * Usage: minigzip [-c] [-d] [-f] [-h] [-r] [-1 to -9] [files...]
497 * -c : write to standard output
498 * -d : decompress
499 * -f : compress with Z_FILTERED
500 * -h : compress with Z_HUFFMAN_ONLY
501 * -r : compress with Z_RLE
502 * -1 to -9 : compression level
503 */
504
main(int argc,char * argv[])505 int main(int argc, char *argv[]) {
506 int copyout = 0;
507 int uncompr = 0;
508 gzFile file;
509 char *bname, outmode[5];
510
511 string_copy(outmode, "wb6 ", sizeof(outmode));
512 prog = argv[0];
513 bname = strrchr(argv[0], '/');
514 if (bname)
515 bname++;
516 else
517 bname = argv[0];
518 argc--, argv++;
519
520 if (!strcmp(bname, "gunzip"))
521 uncompr = 1;
522 else if (!strcmp(bname, "zcat"))
523 copyout = uncompr = 1;
524
525 while (argc > 0) {
526 if (strcmp(*argv, "-c") == 0)
527 copyout = 1;
528 else if (strcmp(*argv, "-d") == 0)
529 uncompr = 1;
530 else if (strcmp(*argv, "-f") == 0)
531 outmode[3] = 'f';
532 else if (strcmp(*argv, "-h") == 0)
533 outmode[3] = 'h';
534 else if (strcmp(*argv, "-r") == 0)
535 outmode[3] = 'R';
536 else if ((*argv)[0] == '-' && (*argv)[1] >= '1' && (*argv)[1] <= '9' &&
537 (*argv)[2] == 0)
538 outmode[2] = (*argv)[1];
539 else
540 break;
541 argc--, argv++;
542 }
543 if (outmode[3] == ' ')
544 outmode[3] = 0;
545 if (argc == 0) {
546 SET_BINARY_MODE(stdin);
547 SET_BINARY_MODE(stdout);
548 if (uncompr) {
549 file = gzdopen(fileno(stdin), "rb");
550 if (file == NULL) error("can't gzdopen stdin");
551 gz_uncompress(file, stdout);
552 } else {
553 file = gzdopen(fileno(stdout), outmode);
554 if (file == NULL) error("can't gzdopen stdout");
555 gz_compress(stdin, file);
556 }
557 } else {
558 if (copyout) {
559 SET_BINARY_MODE(stdout);
560 }
561 do {
562 if (uncompr) {
563 if (copyout) {
564 file = gzopen(*argv, "rb");
565 if (file == NULL)
566 fprintf(stderr, "%s: can't gzopen %s\n", prog, *argv);
567 else
568 gz_uncompress(file, stdout);
569 } else {
570 file_uncompress(*argv);
571 }
572 } else {
573 if (copyout) {
574 FILE * in = fopen(*argv, "rb");
575
576 if (in == NULL) {
577 perror(*argv);
578 } else {
579 file = gzdopen(fileno(stdout), outmode);
580 if (file == NULL) error("can't gzdopen stdout");
581
582 gz_compress(in, file);
583 }
584
585 } else {
586 file_compress(*argv, outmode);
587 }
588 }
589 } while (argv++, --argc);
590 }
591 return 0;
592 }
593