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