*: teach tar et. al. to understand .xz by heart
[platform/upstream/busybox.git] / archival / 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 #include "libbb.h"
8 #include "unarchive.h"
9
10 enum {
11         OPT_STDOUT     = 1 << 0,
12         OPT_FORCE      = 1 << 1,
13         /* only some decompressors: */
14         OPT_VERBOSE    = 1 << 2,
15         OPT_DECOMPRESS = 1 << 3,
16         OPT_TEST       = 1 << 4,
17 };
18
19 static
20 int open_to_or_warn(int to_fd, const char *filename, int flags, int mode)
21 {
22         int fd = open3_or_warn(filename, flags, mode);
23         if (fd < 0) {
24                 return 1;
25         }
26         xmove_fd(fd, to_fd);
27         return 0;
28 }
29
30 int FAST_FUNC bbunpack(char **argv,
31         char* (*make_new_name)(char *filename),
32         IF_DESKTOP(long long) int (*unpacker)(unpack_info_t *info)
33 )
34 {
35         struct stat stat_buf;
36         IF_DESKTOP(long long) int status;
37         char *filename, *new_name;
38         smallint exitcode = 0;
39         unpack_info_t info;
40
41         do {
42                 /* NB: new_name is *maybe* malloc'ed! */
43                 new_name = NULL;
44                 filename = *argv; /* can be NULL - 'streaming' bunzip2 */
45
46                 if (filename && LONE_DASH(filename))
47                         filename = NULL;
48
49                 /* Open src */
50                 if (filename) {
51                         if (stat(filename, &stat_buf) != 0) {
52                                 bb_simple_perror_msg(filename);
53  err:
54                                 exitcode = 1;
55                                 goto free_name;
56                         }
57                         if (open_to_or_warn(STDIN_FILENO, filename, O_RDONLY, 0))
58                                 goto err;
59                 }
60
61                 /* Special cases: test, stdout */
62                 if (option_mask32 & (OPT_STDOUT|OPT_TEST)) {
63                         if (option_mask32 & OPT_TEST)
64                                 if (open_to_or_warn(STDOUT_FILENO, bb_dev_null, O_WRONLY, 0))
65                                         goto err;
66                         filename = NULL;
67                 }
68
69                 /* Open dst if we are going to unpack to file */
70                 if (filename) {
71                         new_name = make_new_name(filename);
72                         if (!new_name) {
73                                 bb_error_msg("%s: unknown suffix - ignored", filename);
74                                 goto err;
75                         }
76
77                         /* -f: overwrite existing output files */
78                         if (option_mask32 & OPT_FORCE) {
79                                 unlink(new_name);
80                         }
81
82                         /* O_EXCL: "real" bunzip2 doesn't overwrite files */
83                         /* GNU gunzip does not bail out, but goes to next file */
84                         if (open_to_or_warn(STDOUT_FILENO, new_name, O_WRONLY | O_CREAT | O_EXCL,
85                                         stat_buf.st_mode))
86                                 goto err;
87                 }
88
89                 /* Check that the input is sane */
90                 if (isatty(STDIN_FILENO) && (option_mask32 & OPT_FORCE) == 0) {
91                         bb_error_msg_and_die("compressed data not read from terminal, "
92                                         "use -f to force it");
93                 }
94
95                 /* memset(&info, 0, sizeof(info)); */
96                 info.mtime = 0; /* so far it has one member only */
97                 status = unpacker(&info);
98                 if (status < 0)
99                         exitcode = 1;
100                 xclose(STDOUT_FILENO); /* with error check! */
101
102                 if (filename) {
103                         char *del = new_name;
104                         if (status >= 0) {
105                                 /* TODO: restore other things? */
106                                 if (info.mtime) {
107                                         struct timeval times[2];
108
109                                         times[1].tv_sec = times[0].tv_sec = info.mtime;
110                                         times[1].tv_usec = times[0].tv_usec = 0;
111                                         /* Note: we closed it first.
112                                          * On some systems calling utimes
113                                          * then closing resets the mtime
114                                          * back to current time. */
115                                         utimes(new_name, times); /* ignoring errors */
116                                 }
117
118                                 /* Delete _compressed_ file */
119                                 del = filename;
120                                 /* restore extension (unless tgz -> tar case) */
121                                 if (new_name == filename)
122                                         filename[strlen(filename)] = '.';
123                         }
124                         xunlink(del);
125
126 #if 0 /* Currently buggy - wrong name: "a.gz: 261% - replaced with a.gz" */
127                         /* Extreme bloat for gunzip compat */
128                         if (ENABLE_DESKTOP && (option_mask32 & OPT_VERBOSE) && status >= 0) {
129                                 fprintf(stderr, "%s: %u%% - replaced with %s\n",
130                                         filename, (unsigned)(stat_buf.st_size*100 / (status+1)), new_name);
131                         }
132 #endif
133
134  free_name:
135                         if (new_name != filename)
136                                 free(new_name);
137                 }
138         } while (*argv && *++argv);
139
140         return exitcode;
141 }
142
143 #if ENABLE_UNCOMPRESS || ENABLE_BUNZIP2 || ENABLE_UNLZMA || ENABLE_UNXZ
144 static
145 char* make_new_name_generic(char *filename, const char *expected_ext)
146 {
147         char *extension = strrchr(filename, '.');
148         if (!extension || strcmp(extension + 1, expected_ext) != 0) {
149                 /* Mimic GNU gunzip - "real" bunzip2 tries to */
150                 /* unpack file anyway, to file.out */
151                 return NULL;
152         }
153         *extension = '\0';
154         return filename;
155 }
156 #endif
157
158
159 /*
160  * Uncompress applet for busybox (c) 2002 Glenn McGrath
161  *
162  * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
163  */
164 #if ENABLE_UNCOMPRESS
165 static
166 char* make_new_name_uncompress(char *filename)
167 {
168         return make_new_name_generic(filename, "Z");
169 }
170 static
171 IF_DESKTOP(long long) int unpack_uncompress(unpack_info_t *info UNUSED_PARAM)
172 {
173         IF_DESKTOP(long long) int status = -1;
174
175         if ((xread_char(STDIN_FILENO) != 0x1f) || (xread_char(STDIN_FILENO) != 0x9d)) {
176                 bb_error_msg("invalid magic");
177         } else {
178                 status = unpack_Z_stream(STDIN_FILENO, STDOUT_FILENO);
179         }
180         return status;
181 }
182 int uncompress_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
183 int uncompress_main(int argc UNUSED_PARAM, char **argv)
184 {
185         getopt32(argv, "cf");
186         argv += optind;
187
188         return bbunpack(argv, make_new_name_uncompress, unpack_uncompress);
189 }
190 #endif
191
192
193 /*
194  * Gzip implementation for busybox
195  *
196  * Based on GNU gzip v1.2.4 Copyright (C) 1992-1993 Jean-loup Gailly.
197  *
198  * Originally adjusted for busybox by Sven Rudolph <sr1@inf.tu-dresden.de>
199  * based on gzip sources
200  *
201  * Adjusted further by Erik Andersen <andersen@codepoet.org> to support files as
202  * well as stdin/stdout, and to generally behave itself wrt command line
203  * handling.
204  *
205  * General cleanup to better adhere to the style guide and make use of standard
206  * busybox functions by Glenn McGrath
207  *
208  * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
209  *
210  * gzip (GNU zip) -- compress files with zip algorithm and 'compress' interface
211  * Copyright (C) 1992-1993 Jean-loup Gailly
212  * The unzip code was written and put in the public domain by Mark Adler.
213  * Portions of the lzw code are derived from the public domain 'compress'
214  * written by Spencer Thomas, Joe Orost, James Woods, Jim McKie, Steve Davies,
215  * Ken Turkowski, Dave Mack and Peter Jannesen.
216  *
217  * See the license_msg below and the file COPYING for the software license.
218  * See the file algorithm.doc for the compression algorithms and file formats.
219  */
220 #if ENABLE_GUNZIP
221 static
222 char* make_new_name_gunzip(char *filename)
223 {
224         char *extension = strrchr(filename, '.');
225
226         if (!extension)
227                 return NULL;
228
229         extension++;
230         if (strcmp(extension, "tgz" + 1) == 0
231 #if ENABLE_FEATURE_SEAMLESS_Z
232          || (extension[0] == 'Z' && extension[1] == '\0')
233 #endif
234         ) {
235                 extension[-1] = '\0';
236         } else if (strcmp(extension, "tgz") == 0) {
237                 filename = xstrdup(filename);
238                 extension = strrchr(filename, '.');
239                 extension[2] = 'a';
240                 extension[3] = 'r';
241         } else {
242                 return NULL;
243         }
244         return filename;
245 }
246 static
247 IF_DESKTOP(long long) int unpack_gunzip(unpack_info_t *info)
248 {
249         IF_DESKTOP(long long) int status = -1;
250
251         /* do the decompression, and cleanup */
252         if (xread_char(STDIN_FILENO) == 0x1f) {
253                 unsigned char magic2;
254
255                 magic2 = xread_char(STDIN_FILENO);
256                 if (ENABLE_FEATURE_SEAMLESS_Z && magic2 == 0x9d) {
257                         status = unpack_Z_stream(STDIN_FILENO, STDOUT_FILENO);
258                 } else if (magic2 == 0x8b) {
259                         status = unpack_gz_stream_with_info(STDIN_FILENO, STDOUT_FILENO, info);
260                 } else {
261                         goto bad_magic;
262                 }
263                 if (status < 0) {
264                         bb_error_msg("error inflating");
265                 }
266         } else {
267  bad_magic:
268                 bb_error_msg("invalid magic");
269                 /* status is still == -1 */
270         }
271         return status;
272 }
273 /*
274  * Linux kernel build uses gzip -d -n. We accept and ignore it.
275  * Man page says:
276  * -n --no-name
277  * gzip: do not save the original file name and time stamp.
278  * (The original name is always saved if the name had to be truncated.)
279  * gunzip: do not restore the original file name/time even if present
280  * (remove only the gzip suffix from the compressed file name).
281  * This option is the default when decompressing.
282  * -N --name
283  * gzip: always save the original file name and time stamp (this is the default)
284  * gunzip: restore the original file name and time stamp if present.
285  */
286 int gunzip_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
287 int gunzip_main(int argc UNUSED_PARAM, char **argv)
288 {
289         getopt32(argv, "cfvdtn");
290         argv += optind;
291         /* if called as zcat */
292         if (applet_name[1] == 'c')
293                 option_mask32 |= OPT_STDOUT;
294
295         return bbunpack(argv, make_new_name_gunzip, unpack_gunzip);
296 }
297 #endif
298
299
300 /*
301  * Modified for busybox by Glenn McGrath
302  * Added support output to stdout by Thomas Lundquist <thomasez@zelow.no>
303  *
304  * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
305  */
306 #if ENABLE_BUNZIP2
307 static
308 char* make_new_name_bunzip2(char *filename)
309 {
310         return make_new_name_generic(filename, "bz2");
311 }
312 static
313 IF_DESKTOP(long long) int unpack_bunzip2(unpack_info_t *info UNUSED_PARAM)
314 {
315         return unpack_bz2_stream_prime(STDIN_FILENO, STDOUT_FILENO);
316 }
317 int bunzip2_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
318 int bunzip2_main(int argc UNUSED_PARAM, char **argv)
319 {
320         getopt32(argv, "cfvdt");
321         argv += optind;
322         if (applet_name[2] == 'c') /* bzcat */
323                 option_mask32 |= OPT_STDOUT;
324
325         return bbunpack(argv, make_new_name_bunzip2, unpack_bunzip2);
326 }
327 #endif
328
329
330 /*
331  * Small lzma deflate implementation.
332  * Copyright (C) 2006  Aurelien Jacobs <aurel@gnuage.org>
333  *
334  * Based on bunzip.c from busybox
335  *
336  * Licensed under GPL v2, see file LICENSE in this tarball for details.
337  */
338 #if ENABLE_UNLZMA
339 static
340 char* make_new_name_unlzma(char *filename)
341 {
342         return make_new_name_generic(filename, "lzma");
343 }
344 static
345 IF_DESKTOP(long long) int unpack_unlzma(unpack_info_t *info UNUSED_PARAM)
346 {
347         return unpack_lzma_stream(STDIN_FILENO, STDOUT_FILENO);
348 }
349 int unlzma_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
350 int unlzma_main(int argc UNUSED_PARAM, char **argv)
351 {
352         IF_LZMA(int opts =) getopt32(argv, "cfvdt");
353 # if ENABLE_LZMA
354         /* lzma without -d or -t? */
355         if (applet_name[2] == 'm' && !(opts & (OPT_DECOMPRESS|OPT_TEST)))
356                 bb_show_usage();
357 # endif
358         /* lzcat? */
359         if (applet_name[2] == 'c')
360                 option_mask32 |= OPT_STDOUT;
361
362         argv += optind;
363         return bbunpack(argv, make_new_name_unlzma, unpack_unlzma);
364 }
365 #endif
366
367
368 #if ENABLE_UNXZ
369 static
370 char* make_new_name_unxz(char *filename)
371 {
372         return make_new_name_generic(filename, "xz");
373 }
374 static
375 IF_DESKTOP(long long) int unpack_unxz(unpack_info_t *info UNUSED_PARAM)
376 {
377         return unpack_xz_stream(STDIN_FILENO, STDOUT_FILENO);
378 }
379 int unxz_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
380 int unxz_main(int argc UNUSED_PARAM, char **argv)
381 {
382         int opts = getopt32(argv, "cfvdt");
383 # if ENABLE_XZ
384         /* xz without -d or -t? */
385         if (applet_name[2] == '\0' && !(opts & (OPT_DECOMPRESS|OPT_TEST)))
386                 bb_show_usage();
387 # endif
388         /* xzcat? */
389         if (applet_name[2] == 'c')
390                 option_mask32 |= OPT_STDOUT;
391
392         argv += optind;
393         return bbunpack(argv, make_new_name_unxz, unpack_unxz);
394 }
395 #endif