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