bunzip2/gunzip/uncompress/unlzma: merge into common code -
[oweals/busybox.git] / archival / bbunzip.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  *  Modified for busybox by Glenn McGrath <bug1@iinet.net.au>
4  *  Added support output to stdout by Thomas Lundquist <thomasez@zelow.no>
5  *
6  *  Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
7  */
8
9 #include "busybox.h"
10 #include "unarchive.h"
11
12 enum {
13         OPT_STDOUT = 1,
14         OPT_FORCE = 2,
15 /* gunzip only: */
16         OPT_TEST = 4,
17         OPT_DECOMPRESS = 8,
18         OPT_VERBOSE = 0x10,
19 };
20
21 static
22 int open_to_or_warn(int to_fd, const char *filename, int flags, int mode)
23 {
24         int fd = open(filename, flags, mode);
25         if (fd < 0) {
26                 bb_perror_msg("%s", filename);
27                 return 1;
28         }
29         if (fd != to_fd) {
30                 if (dup2(fd, to_fd) < 0)
31                         bb_perror_msg_and_die("cannot dup");
32                 close(fd);
33         }
34         return 0;
35 }
36
37 static
38 int unpack(char **argv,
39         char* (*make_new_name)(char *filename),
40         USE_DESKTOP(long long) int (*unpacker)(void)
41 )
42 {
43         struct stat stat_buf;
44         USE_DESKTOP(long long) int status;
45         char *filename;
46         /* NB: new_name is *possibly* malloc'ed! */
47         smallint exitcode = 0;
48
49         do {
50                 char *new_name = NULL;
51
52                 filename = *argv; /* can be NULL - 'streaming' bunzip2 */
53                 if (filename && LONE_DASH(filename))
54                         filename = NULL;
55
56                 /* Open src */
57                 if (filename) {
58                         if (stat(filename, &stat_buf) != 0) {
59                                 bb_perror_msg("%s", filename);
60  err:
61                                 exitcode = 1;
62                                 goto free_name;
63                         }
64                         if (open_to_or_warn(STDIN_FILENO, filename, O_RDONLY, 0))
65                                 goto err;
66                 }
67
68                 /* Special cases: test, stdout */
69                 if (option_mask32 & (OPT_STDOUT|OPT_TEST)) {
70                         if (option_mask32 & OPT_TEST)
71                                 if (open_to_or_warn(STDOUT_FILENO, bb_dev_null, O_WRONLY, 0))
72                                         goto err;
73                         filename = NULL;
74                 }
75
76                 /* Open dst unless -c, "-" or called as bzcat */
77                 if (filename) {
78                         new_name = make_new_name(filename);
79                         if (!new_name) {
80                                 bb_error_msg("%s: unknown suffix - ignored", filename);
81                                 goto err;
82                         }
83                         /* O_EXCL: "real" bunzip2 doesn't overwrite files too */
84                         /* TODO: "real" gunzip goes not bail out, but goes
85                          * to next file */
86                         if (open_to_or_warn(STDOUT_FILENO, new_name, O_WRONLY | O_CREAT | O_EXCL,
87                                         stat_buf.st_mode))
88                                 goto err;
89                 }
90
91                 /* Check that the input is sane. */
92                 if (isatty(STDIN_FILENO) && (option_mask32 & OPT_FORCE) == 0) {
93                         bb_error_msg_and_die("compressed data not read from terminal, "
94                                         "use -f to force it");
95                 }
96
97                 status = unpacker();
98                 if (status < 0)
99                         exitcode = 1;
100
101                 if (filename) {
102                         char *del = new_name;
103                         if (status >= 0) {
104                                 /* TODO: restore user/group/times here? */
105                                 /* delete _old_ file */
106                                 del = filename;
107                                 /* Restore extension (unless tgz -> tar case) */
108                                 if (new_name == filename)
109                                         filename[strlen(filename)] = '.';
110                         }
111                         if (unlink(del) < 0)
112                                 bb_perror_msg_and_die("cannot remove %s", del);
113 #if 0 /* Currently buggy - wrong name: "a.gz: 261% - replaced with a.gz" */
114                         /* Extreme bloat for gunzip compat */
115                         if (ENABLE_DESKTOP && (option_mask32 & OPT_VERBOSE) && status >= 0) {
116                                 fprintf(stderr, "%s: %u%% - replaced with %s\n",
117                                         filename, (unsigned)(stat_buf.st_size*100 / (status+1)), new_name);
118                         }
119 #endif
120  free_name:
121                         if (new_name != filename)
122                                 free(new_name);
123                 }
124         } while (*argv && *++argv);
125
126         return exitcode;
127 }
128
129 #if ENABLE_BUNZIP2
130
131 static
132 char* make_new_name_bunzip2(char *filename)
133 {
134         char *extension = strrchr(filename, '.');
135         if (!extension || strcmp(extension, ".bz2") != 0) {
136                 /* Mimic GNU gunzip ("real" bunzip2 tries to */
137                 /* unpack file anyway, to file.out) */
138                 return NULL;
139         }
140         *extension = '\0';
141         return filename;
142 }
143
144 static
145 USE_DESKTOP(long long) int unpack_bunzip2(void)
146 {
147         return uncompressStream(STDIN_FILENO, STDOUT_FILENO);
148 }
149
150 int bunzip2_main(int argc, char **argv);
151 int bunzip2_main(int argc, char **argv)
152 {
153         getopt32(argc, argv, "cf");
154         argv += optind;
155         if (applet_name[2] == 'c')
156                 option_mask32 |= OPT_STDOUT;
157
158         return unpack(argv, make_new_name_bunzip2, unpack_bunzip2);
159 }
160
161 #endif
162
163
164 /*
165  * Gzip implementation for busybox
166  *
167  * Based on GNU gzip v1.2.4 Copyright (C) 1992-1993 Jean-loup Gailly.
168  *
169  * Originally adjusted for busybox by Sven Rudolph <sr1@inf.tu-dresden.de>
170  * based on gzip sources
171  *
172  * Adjusted further by Erik Andersen <andersen@codepoet.org> to support files as
173  * well as stdin/stdout, and to generally behave itself wrt command line
174  * handling.
175  *
176  * General cleanup to better adhere to the style guide and make use of standard
177  * busybox functions by Glenn McGrath <bug1@iinet.net.au>
178  *
179  * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
180  *
181  * gzip (GNU zip) -- compress files with zip algorithm and 'compress' interface
182  * Copyright (C) 1992-1993 Jean-loup Gailly
183  * The unzip code was written and put in the public domain by Mark Adler.
184  * Portions of the lzw code are derived from the public domain 'compress'
185  * written by Spencer Thomas, Joe Orost, James Woods, Jim McKie, Steve Davies,
186  * Ken Turkowski, Dave Mack and Peter Jannesen.
187  *
188  * See the license_msg below and the file COPYING for the software license.
189  * See the file algorithm.doc for the compression algorithms and file formats.
190  */
191
192 #if ENABLE_GUNZIP
193
194 static
195 char* make_new_name_gunzip(char *filename)
196 {
197         char *extension = strrchr(filename, '.');
198
199         if (!extension)
200                 return NULL;
201
202         if (strcmp(extension, ".gz") == 0
203 #ifdef CONFIG_FEATURE_GUNZIP_UNCOMPRESS
204          || strcmp(extension, ".Z") == 0
205 #endif
206         ) {
207                 *extension = '\0';
208         } else if(strcmp(extension, ".tgz") == 0) {
209                 filename = xstrdup(filename);
210                 extension = strrchr(filename, '.');
211                 extension[2] = 'a';
212                 extension[3] = 'r';
213         } else {
214                 return NULL;
215         }
216         return filename;
217 }
218
219 static
220 USE_DESKTOP(long long) int unpack_gunzip(void)
221 {
222         USE_DESKTOP(long long) int status = -1;
223
224         /* do the decompression, and cleanup */
225         if (xread_char(STDIN_FILENO) == 0x1f) {
226                 unsigned char magic2;
227
228                 magic2 = xread_char(STDIN_FILENO);
229                 if (ENABLE_FEATURE_GUNZIP_UNCOMPRESS && magic2 == 0x9d) {
230                         status = uncompress(STDIN_FILENO, STDOUT_FILENO);
231                 } else if (magic2 == 0x8b) {
232                         check_header_gzip_or_die(STDIN_FILENO);
233                         status = inflate_gunzip(STDIN_FILENO, STDOUT_FILENO);
234                 } else {
235                         goto bad_magic;
236                 }
237                 if (status < 0) {
238                         bb_error_msg("error inflating");
239                 }
240         } else {
241  bad_magic:
242                 bb_error_msg("invalid magic");
243                 /* status is still == -1 */
244         }
245         return status;
246 }
247
248 int gunzip_main(int argc, char **argv);
249 int gunzip_main(int argc, char **argv)
250 {
251         getopt32(argc, argv, "cftdv");
252         argv += optind;
253         /* if called as zcat */
254         if (applet_name[1] == 'c')
255                 option_mask32 |= OPT_STDOUT;
256
257         return unpack(argv, make_new_name_gunzip, unpack_gunzip);
258 }
259
260 #endif
261
262
263 /*
264  * Small lzma deflate implementation.
265  * Copyright (C) 2006  Aurelien Jacobs <aurel@gnuage.org>
266  *
267  * Based on bunzip.c from busybox
268  *
269  * Licensed under GPL v2, see file LICENSE in this tarball for details.
270  */
271
272 #if ENABLE_UNLZMA
273
274 static
275 char* make_new_name_unlzma(char *filename)
276 {
277         char *extension = strrchr(filename, '.');
278         if (!extension || strcmp(extension, ".lzma") != 0)
279                 return NULL;
280         *extension = '\0';
281         return filename;
282 }
283
284 static
285 USE_DESKTOP(long long) int unpack_unlzma(void)
286 {
287         return unlzma(STDIN_FILENO, STDOUT_FILENO);
288 }
289
290 int unlzma_main(int argc, char **argv);
291 int unlzma_main(int argc, char **argv)
292 {
293         getopt32(argc, argv, "c");
294         argv += optind;
295         /* lzmacat? */
296         if (applet_name[4] == 'c')
297                 option_mask32 |= OPT_STDOUT;
298
299         return unpack(argv, make_new_name_unlzma, unpack_unlzma);
300 }
301
302 #endif
303
304
305 /* vi: set sw=4 ts=4: */
306 /*
307  *      Uncompress applet for busybox (c) 2002 Glenn McGrath
308  *
309  * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
310  */
311
312 #if ENABLE_UNCOMPRESS
313
314 static
315 char* make_new_name_uncompress(char *filename)
316 {
317         char *extension = strrchr(filename, '.');
318         if (!extension || strcmp(extension, ".Z") != 0)
319                 return NULL;
320         *extension = '\0';
321         return filename;
322 }
323
324 static
325 USE_DESKTOP(long long) int unpack_uncompress(void)
326 {
327         USE_DESKTOP(long long) int status = -1;
328
329         if ((xread_char(STDIN_FILENO) != 0x1f) || (xread_char(STDIN_FILENO) != 0x9d)) {
330                 bb_error_msg("invalid magic");
331         } else {
332                 status = uncompress(STDIN_FILENO, STDOUT_FILENO);
333         }
334         return status;
335 }
336
337 int uncompress_main(int argc, char **argv);
338 int uncompress_main(int argc, char **argv)
339 {
340         getopt32(argc, argv, "cf");
341         argv += optind;
342
343         return unpack(argv, make_new_name_uncompress, unpack_uncompress);
344 }
345
346 #endif