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