1 /* minigzip.c -- simulate gzip using the zlib compression library
2  * Copyright (C) 1995-2006, 2010, 2011, 2016 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.
13  */
14 
15 #include "zbuild.h"
16 #ifdef ZLIB_COMPAT
17 #  include "zlib.h"
18 #else
19 #  include "zlib-ng.h"
20 #endif
21 #include <stdio.h>
22 
23 #include <string.h>
24 #include <stdlib.h>
25 
26 #ifdef USE_MMAP
27 #  include <sys/types.h>
28 #  include <sys/mman.h>
29 #  include <sys/stat.h>
30 #endif
31 
32 #if defined(_WIN32) || defined(__CYGWIN__)
33 #  include <fcntl.h>
34 #  include <io.h>
35 #  define SET_BINARY_MODE(file) setmode(fileno(file), O_BINARY)
36 #else
37 #  define SET_BINARY_MODE(file)
38 #endif
39 
40 #if defined(_MSC_VER) && _MSC_VER < 1900
41 #  define snprintf _snprintf
42 #endif
43 
44 #if !defined(Z_HAVE_UNISTD_H) && !defined(_LARGEFILE64_SOURCE)
45 #ifndef _WIN32 /* unlink already in stdio.h for Win32 */
46 extern int unlink (const char *);
47 #endif
48 #endif
49 
50 #ifndef GZ_SUFFIX
51 #  define GZ_SUFFIX ".gz"
52 #endif
53 #define SUFFIX_LEN (sizeof(GZ_SUFFIX)-1)
54 
55 #ifndef BUFLEN
56 #  define BUFLEN     16384       /* read buffer size */
57 #endif
58 #define BUFLENW     (BUFLEN * 3) /* write buffer size */
59 #define MAX_NAME_LEN 1024
60 
61 static char *prog;
62 
63 void error            (const char *msg);
64 void gz_compress      (FILE *in, gzFile out);
65 #ifdef USE_MMAP
66 int  gz_compress_mmap (FILE *in, gzFile out);
67 #endif
68 void gz_uncompress    (gzFile in, FILE *out);
69 void file_compress    (char *file, char *mode, int keep);
70 void file_uncompress  (char *file, int keep);
71 int  main             (int argc, char *argv[]);
72 
73 /* ===========================================================================
74  * Display error message and exit
75  */
error(const char * msg)76 void error(const char *msg) {
77     fprintf(stderr, "%s: %s\n", prog, msg);
78     exit(1);
79 }
80 
81 /* ===========================================================================
82  * Compress input to output then close both files.
83  */
84 
gz_compress(FILE * in,gzFile out)85 void gz_compress(FILE *in, gzFile out) {
86     char *buf;
87     int len;
88     int err;
89 
90 #ifdef USE_MMAP
91     /* Try first compressing with mmap. If mmap fails (minigzip used in a
92      * pipe), use the normal fread loop.
93      */
94     if (gz_compress_mmap(in, out) == Z_OK) return;
95 #endif
96     buf = (char *)calloc(BUFLEN, 1);
97     if (buf == NULL) {
98         perror("out of memory");
99         exit(1);
100     }
101 
102     for (;;) {
103         len = (int)fread(buf, 1, BUFLEN, in);
104         if (ferror(in)) {
105             free(buf);
106             perror("fread");
107             exit(1);
108         }
109         if (len == 0) break;
110 
111         if (PREFIX(gzwrite)(out, buf, (unsigned)len) != len) error(PREFIX(gzerror)(out, &err));
112     }
113     free(buf);
114     fclose(in);
115     if (PREFIX(gzclose)(out) != Z_OK) error("failed gzclose");
116 }
117 
118 #ifdef USE_MMAP /* MMAP version, Miguel Albrecht <[email protected]> */
119 
120 /* Try compressing the input file at once using mmap. Return Z_OK if
121  * if success, Z_ERRNO otherwise.
122  */
gz_compress_mmap(FILE * in,gzFile out)123 int gz_compress_mmap(FILE *in, gzFile out) {
124     int len;
125     int err;
126     int ifd = fileno(in);
127     char *buf;      /* mmap'ed buffer for the entire input file */
128     off_t buf_len;  /* length of the input file */
129     struct stat sb;
130 
131     /* Determine the size of the file, needed for mmap: */
132     if (fstat(ifd, &sb) < 0) return Z_ERRNO;
133     buf_len = sb.st_size;
134     if (buf_len <= 0) return Z_ERRNO;
135 
136     /* Now do the actual mmap: */
137     buf = mmap((void *)0, buf_len, PROT_READ, MAP_SHARED, ifd, (off_t)0);
138     if (buf == (char *)(-1)) return Z_ERRNO;
139 
140     /* Compress the whole file at once: */
141     len = PREFIX(gzwrite)(out, buf, (unsigned)buf_len);
142 
143     if (len != (int)buf_len) error(PREFIX(gzerror)(out, &err));
144 
145     munmap(buf, buf_len);
146     fclose(in);
147     if (PREFIX(gzclose)(out) != Z_OK) error("failed gzclose");
148     return Z_OK;
149 }
150 #endif /* USE_MMAP */
151 
152 /* ===========================================================================
153  * Uncompress input to output then close both files.
154  */
gz_uncompress(gzFile in,FILE * out)155 void gz_uncompress(gzFile in, FILE *out) {
156     char *buf = (char *)malloc(BUFLENW);
157     int len;
158     int err;
159 
160     if (buf == NULL) error("out of memory");
161 
162     for (;;) {
163         len = PREFIX(gzread)(in, buf, BUFLENW);
164         if (len < 0) {
165             free(buf);
166             error(PREFIX(gzerror)(in, &err));
167         }
168         if (len == 0) break;
169 
170         if ((int)fwrite(buf, 1, (unsigned)len, out) != len) {
171             free(buf);
172             error("failed fwrite");
173         }
174     }
175     free(buf);
176     if (fclose(out)) error("failed fclose");
177 
178     if (PREFIX(gzclose)(in) != Z_OK) error("failed gzclose");
179 }
180 
181 
182 /* ===========================================================================
183  * Compress the given file: create a corresponding .gz file and remove the
184  * original.
185  */
file_compress(char * file,char * mode,int keep)186 void file_compress(char *file, char *mode, int keep) {
187     char outfile[MAX_NAME_LEN];
188     FILE *in;
189     gzFile out;
190 
191     if (strlen(file) + strlen(GZ_SUFFIX) >= sizeof(outfile)) {
192         fprintf(stderr, "%s: filename too long\n", prog);
193         exit(1);
194     }
195 
196     snprintf(outfile, sizeof(outfile), "%s%s", file, GZ_SUFFIX);
197 
198     in = fopen(file, "rb");
199     if (in == NULL) {
200         perror(file);
201         exit(1);
202     }
203     out = PREFIX(gzopen)(outfile, mode);
204     if (out == NULL) {
205         fprintf(stderr, "%s: can't gzopen %s\n", prog, outfile);
206         exit(1);
207     }
208     gz_compress(in, out);
209 
210     if (!keep)
211         unlink(file);
212 }
213 
214 
215 /* ===========================================================================
216  * Uncompress the given file and remove the original.
217  */
file_uncompress(char * file,int keep)218 void file_uncompress(char *file, int keep) {
219     char buf[MAX_NAME_LEN];
220     char *infile, *outfile;
221     FILE *out;
222     gzFile in;
223     size_t len = strlen(file);
224 
225     if (len + strlen(GZ_SUFFIX) >= sizeof(buf)) {
226         fprintf(stderr, "%s: filename too long\n", prog);
227         exit(1);
228     }
229 
230     snprintf(buf, sizeof(buf), "%s", file);
231 
232     if (len > SUFFIX_LEN && strcmp(file+len-SUFFIX_LEN, GZ_SUFFIX) == 0) {
233         infile = file;
234         outfile = buf;
235         outfile[len-3] = '\0';
236     } else {
237         outfile = file;
238         infile = buf;
239         snprintf(buf + len, sizeof(buf) - len, "%s", GZ_SUFFIX);
240     }
241     in = PREFIX(gzopen)(infile, "rb");
242     if (in == NULL) {
243         fprintf(stderr, "%s: can't gzopen %s\n", prog, infile);
244         exit(1);
245     }
246     out = fopen(outfile, "wb");
247     if (out == NULL) {
248         perror(file);
249         exit(1);
250     }
251 
252     gz_uncompress(in, out);
253 
254     if (!keep)
255         unlink(infile);
256 }
257 
show_help(void)258 void show_help(void) {
259     printf("Usage: minigzip [-c] [-d] [-k] [-f|-h|-R|-F|-T] [-A] [-0 to -9] [files...]\n\n" \
260            "  -c : write to standard output\n" \
261            "  -d : decompress\n" \
262            "  -k : keep input files\n" \
263            "  -f : compress with Z_FILTERED\n" \
264            "  -h : compress with Z_HUFFMAN_ONLY\n" \
265            "  -R : compress with Z_RLE\n" \
266            "  -F : compress with Z_FIXED\n" \
267            "  -T : stored raw\n" \
268            "  -A : auto detect type\n" \
269            "  -0 to -9 : compression level\n\n");
270 }
271 
main(int argc,char * argv[])272 int main(int argc, char *argv[]) {
273     int copyout = 0;
274     int uncompr = 0;
275     int keep = 0;
276     int i = 0;
277     gzFile file;
278     char *bname, outmode[20];
279     char *strategy = "";
280     char *level = "6";
281     char *type = "b";
282 
283     prog = argv[i];
284     bname = strrchr(argv[i], '/');
285     if (bname)
286         bname++;
287     else
288         bname = argv[i];
289 
290     if (!strcmp(bname, "gunzip"))
291         uncompr = 1;
292     else if (!strcmp(bname, "zcat"))
293         copyout = uncompr = 1;
294 
295     for (i = 1; i < argc; i++) {
296         if (strcmp(argv[i], "-c") == 0)
297             copyout = 1;
298         else if (strcmp(argv[i], "-d") == 0)
299             uncompr = 1;
300         else if (strcmp(argv[i], "-k") == 0)
301             keep = 1;
302         else if (strcmp(argv[i], "-A") == 0)
303             type = "";
304         else if (argv[i][0] == '-' && (argv[i][1] == 'f' || argv[i][1] == 'h' ||
305                  argv[i][1] == 'R' || argv[i][1] == 'F' || argv[i][1] == 'T') && argv[i][2] == 0)
306             strategy = argv[i] + 1;
307         else if (argv[i][0] == '-' && argv[i][1] >= '0' && argv[i][1] <= '9' && argv[i][2] == 0)
308             level = argv[i] + 1;
309         else if (strcmp(argv[i], "--help") == 0) {
310             show_help();
311             return 0;
312         } else if (argv[i][0] == '-') {
313             show_help();
314             return 64;   /* EX_USAGE */
315         } else {
316             break;
317         }
318     }
319 
320     snprintf(outmode, sizeof(outmode), "w%s%s%s", type, strategy, level);
321 
322     if (i == argc) {
323         SET_BINARY_MODE(stdin);
324         SET_BINARY_MODE(stdout);
325         if (uncompr) {
326             file = PREFIX(gzdopen)(fileno(stdin), "rb");
327             if (file == NULL) error("can't gzdopen stdin");
328             gz_uncompress(file, stdout);
329         } else {
330             file = PREFIX(gzdopen)(fileno(stdout), outmode);
331             if (file == NULL) error("can't gzdopen stdout");
332             gz_compress(stdin, file);
333         }
334     } else {
335         if (copyout) {
336             SET_BINARY_MODE(stdout);
337         }
338         do {
339             if (uncompr) {
340                 if (copyout) {
341                     file = PREFIX(gzopen)(argv[i], "rb");
342                     if (file == NULL)
343                         fprintf(stderr, "%s: can't gzopen %s\n", prog, argv[i]);
344                     else
345                         gz_uncompress(file, stdout);
346                 } else {
347                     file_uncompress(argv[i], keep);
348                 }
349             } else {
350                 if (copyout) {
351                     FILE * in = fopen(argv[i], "rb");
352 
353                     if (in == NULL) {
354                         perror(argv[i]);
355                     } else {
356                         file = PREFIX(gzdopen)(fileno(stdout), outmode);
357                         if (file == NULL) error("can't gzdopen stdout");
358 
359                         gz_compress(in, file);
360                     }
361 
362                 } else {
363                     file_compress(argv[i], outmode, keep);
364                 }
365             }
366         } while (++i < argc);
367     }
368     return 0;
369 }
370