986ab525b8
a failure exit code. delay the error exit until all (good) files have been extracted. filesystem errors (nodes of wrong type, permission problems, etc) still cause immediate failure.
430 lines
11 KiB
C
430 lines
11 KiB
C
/* vi: set sw=4 ts=4: */
|
|
/*
|
|
* Mini unzip implementation for busybox
|
|
*
|
|
* Copyright (C) 2004 by Ed Clark
|
|
*
|
|
* Loosely based on original busybox unzip applet by Laurence Anderson.
|
|
* All options and features should work in this version.
|
|
*
|
|
* Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
|
|
*/
|
|
|
|
/* For reference see
|
|
* http://www.pkware.com/company/standards/appnote/
|
|
* http://www.info-zip.org/pub/infozip/doc/appnote-iz-latest.zip
|
|
*/
|
|
|
|
/* TODO
|
|
* Endian issues
|
|
* Zip64 + other methods
|
|
* Improve handling of zip format, ie.
|
|
* - deferred CRC, comp. & uncomp. lengths (zip header flags bit 3)
|
|
* - unix file permissions, etc.
|
|
* - central directory
|
|
*/
|
|
|
|
#include <fcntl.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include "unarchive.h"
|
|
#include "busybox.h"
|
|
|
|
#if BB_BIG_ENDIAN
|
|
static inline unsigned short
|
|
__swap16(unsigned short x) {
|
|
return (((uint16_t)(x) & 0xFF) << 8) | (((uint16_t)(x) & 0xFF00) >> 8);
|
|
}
|
|
|
|
static inline uint32_t
|
|
__swap32(uint32_t x) {
|
|
return (((x & 0xFF) << 24) |
|
|
((x & 0xFF00) << 8) |
|
|
((x & 0xFF0000) >> 8) |
|
|
((x & 0xFF000000) >> 24));
|
|
}
|
|
#else /* it's little-endian */
|
|
# define __swap16(x) (x)
|
|
# define __swap32(x) (x)
|
|
#endif /* BB_BIG_ENDIAN */
|
|
|
|
#define ZIP_FILEHEADER_MAGIC __swap32(0x04034b50)
|
|
#define ZIP_CDS_MAGIC __swap32(0x02014b50)
|
|
#define ZIP_CDS_END_MAGIC __swap32(0x06054b50)
|
|
#define ZIP_DD_MAGIC __swap32(0x08074b50)
|
|
|
|
extern unsigned int gunzip_crc;
|
|
extern unsigned int gunzip_bytes_out;
|
|
|
|
typedef union {
|
|
unsigned char raw[26];
|
|
struct {
|
|
unsigned short version; /* 0-1 */
|
|
unsigned short flags; /* 2-3 */
|
|
unsigned short method; /* 4-5 */
|
|
unsigned short modtime; /* 6-7 */
|
|
unsigned short moddate; /* 8-9 */
|
|
unsigned int crc32 ATTRIBUTE_PACKED; /* 10-13 */
|
|
unsigned int cmpsize ATTRIBUTE_PACKED; /* 14-17 */
|
|
unsigned int ucmpsize ATTRIBUTE_PACKED; /* 18-21 */
|
|
unsigned short filename_len; /* 22-23 */
|
|
unsigned short extra_len; /* 24-25 */
|
|
} formated ATTRIBUTE_PACKED;
|
|
} zip_header_t;
|
|
|
|
static void unzip_skip(int fd, off_t skip)
|
|
{
|
|
if (lseek(fd, skip, SEEK_CUR) == (off_t)-1) {
|
|
if ((errno != ESPIPE) || (bb_copyfd_size(fd, -1, skip) != skip)) {
|
|
bb_error_msg_and_die("Seek failure");
|
|
}
|
|
}
|
|
}
|
|
|
|
static void unzip_read(int fd, void *buf, size_t count)
|
|
{
|
|
if (bb_xread(fd, buf, count) != count) {
|
|
bb_error_msg_and_die("Read failure");
|
|
}
|
|
}
|
|
|
|
static void unzip_create_leading_dirs(char *fn)
|
|
{
|
|
/* Create all leading directories */
|
|
char *name = bb_xstrdup(fn);
|
|
if (bb_make_directory(dirname(name), 0777, FILEUTILS_RECUR)) {
|
|
bb_error_msg_and_die("Failed to create directory");
|
|
}
|
|
free(name);
|
|
}
|
|
|
|
static int unzip_extract(zip_header_t *zip_header, int src_fd, int dst_fd)
|
|
{
|
|
if (zip_header->formated.method == 0) {
|
|
/* Method 0 - stored (not compressed) */
|
|
int size = zip_header->formated.ucmpsize;
|
|
if (size && (bb_copyfd_size(src_fd, dst_fd, size) != size)) {
|
|
bb_error_msg_and_die("Cannot complete extraction");
|
|
}
|
|
|
|
} else {
|
|
/* Method 8 - inflate */
|
|
inflate_init(zip_header->formated.cmpsize);
|
|
inflate_unzip(src_fd, dst_fd);
|
|
inflate_cleanup();
|
|
/* Validate decompression - crc */
|
|
if (zip_header->formated.crc32 != (gunzip_crc ^ 0xffffffffL)) {
|
|
bb_error_msg("Invalid compressed data--crc error");
|
|
return 1;
|
|
}
|
|
/* Validate decompression - size */
|
|
if (zip_header->formated.ucmpsize != gunzip_bytes_out) {
|
|
bb_error_msg("Invalid compressed data--length error");
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int unzip_main(int argc, char **argv)
|
|
{
|
|
zip_header_t zip_header;
|
|
enum {v_silent, v_normal, v_list} verbosity = v_normal;
|
|
enum {o_prompt, o_never, o_always} overwrite = o_prompt;
|
|
unsigned int total_size = 0;
|
|
unsigned int total_entries = 0;
|
|
int src_fd = -1, dst_fd = -1;
|
|
char *src_fn = NULL, *dst_fn = NULL;
|
|
llist_t *zaccept = NULL;
|
|
llist_t *zreject = NULL;
|
|
char *base_dir = NULL;
|
|
int failed, i, opt, opt_range = 0, list_header_done = 0;
|
|
char key_buf[512];
|
|
struct stat stat_buf;
|
|
|
|
while((opt = getopt(argc, argv, "-d:lnopqx")) != -1) {
|
|
switch(opt_range) {
|
|
case 0: /* Options */
|
|
switch(opt) {
|
|
case 'l': /* List */
|
|
verbosity = v_list;
|
|
break;
|
|
|
|
case 'n': /* Never overwrite existing files */
|
|
overwrite = o_never;
|
|
break;
|
|
|
|
case 'o': /* Always overwrite existing files */
|
|
overwrite = o_always;
|
|
break;
|
|
|
|
case 'p': /* Extract files to stdout and fall through to set verbosity */
|
|
dst_fd = STDOUT_FILENO;
|
|
|
|
case 'q': /* Be quiet */
|
|
verbosity = (verbosity == v_normal) ? v_silent : verbosity;
|
|
break;
|
|
|
|
case 1 : /* The zip file */
|
|
src_fn = bb_xstrndup(optarg, strlen(optarg)+4);
|
|
opt_range++;
|
|
break;
|
|
|
|
default:
|
|
bb_show_usage();
|
|
|
|
}
|
|
break;
|
|
|
|
case 1: /* Include files */
|
|
if (opt == 1) {
|
|
zaccept = llist_add_to(zaccept, optarg);
|
|
|
|
} else if (opt == 'd') {
|
|
base_dir = optarg;
|
|
opt_range += 2;
|
|
|
|
} else if (opt == 'x') {
|
|
opt_range++;
|
|
|
|
} else {
|
|
bb_show_usage();
|
|
}
|
|
break;
|
|
|
|
case 2 : /* Exclude files */
|
|
if (opt == 1) {
|
|
zreject = llist_add_to(zreject, optarg);
|
|
|
|
} else if (opt == 'd') { /* Extract to base directory */
|
|
base_dir = optarg;
|
|
opt_range++;
|
|
|
|
} else {
|
|
bb_show_usage();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
bb_show_usage();
|
|
}
|
|
}
|
|
|
|
if (src_fn == NULL) {
|
|
bb_show_usage();
|
|
}
|
|
|
|
/* Open input file */
|
|
if (strcmp("-", src_fn) == 0) {
|
|
src_fd = STDIN_FILENO;
|
|
/* Cannot use prompt mode since zip data is arriving on STDIN */
|
|
overwrite = (overwrite == o_prompt) ? o_never : overwrite;
|
|
|
|
} else {
|
|
static const char *const extn[] = {"", ".zip", ".ZIP"};
|
|
int orig_src_fn_len = strlen(src_fn);
|
|
for(i = 0; (i < 3) && (src_fd == -1); i++) {
|
|
strcpy(src_fn + orig_src_fn_len, extn[i]);
|
|
src_fd = open(src_fn, O_RDONLY);
|
|
}
|
|
if (src_fd == -1) {
|
|
src_fn[orig_src_fn_len] = 0;
|
|
bb_error_msg_and_die("Cannot open %s, %s.zip, %s.ZIP", src_fn, src_fn, src_fn);
|
|
}
|
|
}
|
|
|
|
/* Change dir if necessary */
|
|
if (base_dir && chdir(base_dir)) {
|
|
bb_perror_msg_and_die("Cannot chdir");
|
|
}
|
|
|
|
if (verbosity != v_silent)
|
|
printf("Archive: %s\n", src_fn);
|
|
|
|
failed = 0;
|
|
|
|
while (1) {
|
|
unsigned int magic;
|
|
|
|
/* Check magic number */
|
|
unzip_read(src_fd, &magic, 4);
|
|
if (magic == ZIP_CDS_MAGIC) {
|
|
break;
|
|
} else if (magic != ZIP_FILEHEADER_MAGIC) {
|
|
bb_error_msg_and_die("Invalid zip magic %08X", magic);
|
|
}
|
|
|
|
/* Read the file header */
|
|
unzip_read(src_fd, zip_header.raw, 26);
|
|
#if BB_BIG_ENDIAN
|
|
zip_header.formated.version = __swap16(zip_header.formated.version);
|
|
zip_header.formated.flags = __swap16(zip_header.formated.flags);
|
|
zip_header.formated.method = __swap16(zip_header.formated.method);
|
|
zip_header.formated.modtime = __swap16(zip_header.formated.modtime);
|
|
zip_header.formated.moddate = __swap16(zip_header.formated.moddate);
|
|
zip_header.formated.crc32 = __swap32(zip_header.formated.crc32);
|
|
zip_header.formated.cmpsize = __swap32(zip_header.formated.cmpsize);
|
|
zip_header.formated.ucmpsize = __swap32(zip_header.formated.ucmpsize);
|
|
zip_header.formated.filename_len = __swap16(zip_header.formated.filename_len);
|
|
zip_header.formated.extra_len = __swap16(zip_header.formated.extra_len);
|
|
#endif /* BB_BIG_ENDIAN */
|
|
if ((zip_header.formated.method != 0) && (zip_header.formated.method != 8)) {
|
|
bb_error_msg_and_die("Unsupported compression method %d", zip_header.formated.method);
|
|
}
|
|
|
|
/* Read filename */
|
|
free(dst_fn);
|
|
dst_fn = xmalloc(zip_header.formated.filename_len + 1);
|
|
unzip_read(src_fd, dst_fn, zip_header.formated.filename_len);
|
|
dst_fn[zip_header.formated.filename_len] = 0;
|
|
|
|
/* Skip extra header bytes */
|
|
unzip_skip(src_fd, zip_header.formated.extra_len);
|
|
|
|
if ((verbosity == v_list) && !list_header_done){
|
|
printf(" Length Date Time Name\n");
|
|
printf(" -------- ---- ---- ----\n");
|
|
list_header_done = 1;
|
|
}
|
|
|
|
/* Filter zip entries */
|
|
if (find_list_entry(zreject, dst_fn) ||
|
|
(zaccept && !find_list_entry(zaccept, dst_fn))) { /* Skip entry */
|
|
i = 'n';
|
|
|
|
} else { /* Extract entry */
|
|
total_size += zip_header.formated.ucmpsize;
|
|
|
|
if (verbosity == v_list) { /* List entry */
|
|
unsigned int dostime = zip_header.formated.modtime | (zip_header.formated.moddate << 16);
|
|
printf("%9u %02u-%02u-%02u %02u:%02u %s\n",
|
|
zip_header.formated.ucmpsize,
|
|
(dostime & 0x01e00000) >> 21,
|
|
(dostime & 0x001f0000) >> 16,
|
|
(((dostime & 0xfe000000) >> 25) + 1980) % 100,
|
|
(dostime & 0x0000f800) >> 11,
|
|
(dostime & 0x000007e0) >> 5,
|
|
dst_fn);
|
|
total_entries++;
|
|
i = 'n';
|
|
|
|
} else if (dst_fd == STDOUT_FILENO) { /* Extracting to STDOUT */
|
|
i = -1;
|
|
|
|
} else if (last_char_is(dst_fn, '/')) { /* Extract directory */
|
|
if (stat(dst_fn, &stat_buf) == -1) {
|
|
if (errno != ENOENT) {
|
|
bb_perror_msg_and_die("Cannot stat '%s'",dst_fn);
|
|
}
|
|
if (verbosity == v_normal) {
|
|
printf(" creating: %s\n", dst_fn);
|
|
}
|
|
unzip_create_leading_dirs(dst_fn);
|
|
if (bb_make_directory(dst_fn, 0777, 0)) {
|
|
bb_error_msg_and_die("Failed to create directory");
|
|
}
|
|
} else {
|
|
if (!S_ISDIR(stat_buf.st_mode)) {
|
|
bb_error_msg_and_die("'%s' exists but is not directory", dst_fn);
|
|
}
|
|
}
|
|
i = 'n';
|
|
|
|
} else { /* Extract file */
|
|
_check_file:
|
|
if (stat(dst_fn, &stat_buf) == -1) { /* File does not exist */
|
|
if (errno != ENOENT) {
|
|
bb_perror_msg_and_die("Cannot stat '%s'",dst_fn);
|
|
}
|
|
i = 'y';
|
|
|
|
} else { /* File already exists */
|
|
if (overwrite == o_never) {
|
|
i = 'n';
|
|
|
|
} else if (S_ISREG(stat_buf.st_mode)) { /* File is regular file */
|
|
if (overwrite == o_always) {
|
|
i = 'y';
|
|
} else {
|
|
printf("replace %s? [y]es, [n]o, [A]ll, [N]one, [r]ename: ", dst_fn);
|
|
if (!fgets(key_buf, 512, stdin)) {
|
|
bb_perror_msg_and_die("Cannot read input");
|
|
}
|
|
i = key_buf[0];
|
|
}
|
|
|
|
} else { /* File is not regular file */
|
|
bb_error_msg_and_die("'%s' exists but is not regular file",dst_fn);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (i) {
|
|
case 'A':
|
|
overwrite = o_always;
|
|
case 'y': /* Open file and fall into unzip */
|
|
unzip_create_leading_dirs(dst_fn);
|
|
dst_fd = bb_xopen(dst_fn, O_WRONLY | O_CREAT);
|
|
case -1: /* Unzip */
|
|
if (verbosity == v_normal) {
|
|
printf(" inflating: %s\n", dst_fn);
|
|
}
|
|
if (unzip_extract(&zip_header, src_fd, dst_fd)) {
|
|
failed = 1;
|
|
}
|
|
if (dst_fd != STDOUT_FILENO) {
|
|
/* closing STDOUT is potentially bad for future business */
|
|
close(dst_fd);
|
|
}
|
|
break;
|
|
|
|
case 'N':
|
|
overwrite = o_never;
|
|
case 'n':
|
|
/* Skip entry data */
|
|
unzip_skip(src_fd, zip_header.formated.cmpsize);
|
|
break;
|
|
|
|
case 'r':
|
|
/* Prompt for new name */
|
|
printf("new name: ");
|
|
if (!fgets(key_buf, 512, stdin)) {
|
|
bb_perror_msg_and_die("Cannot read input");
|
|
}
|
|
free(dst_fn);
|
|
dst_fn = bb_xstrdup(key_buf);
|
|
chomp(dst_fn);
|
|
goto _check_file;
|
|
|
|
default:
|
|
printf("error: invalid response [%c]\n",(char)i);
|
|
goto _check_file;
|
|
}
|
|
|
|
/* Data descriptor section */
|
|
if (zip_header.formated.flags & 4) {
|
|
/* skip over duplicate crc, compressed size and uncompressed size */
|
|
unzip_skip(src_fd, 12);
|
|
}
|
|
}
|
|
|
|
if (verbosity == v_list) {
|
|
printf(" -------- -------\n"
|
|
"%9d %d files\n", total_size, total_entries);
|
|
}
|
|
|
|
return failed;
|
|
}
|
|
|
|
/* END CODE */
|
|
/*
|
|
Local Variables:
|
|
c-file-style: "linux"
|
|
c-basic-offset: 4
|
|
tab-width: 4
|
|
End:
|
|
*/
|