X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=archival%2Ftar.c;h=280ded4e14e325811e2cf976a050bac6e7ba4b27;hb=619d9b5e6848a72350126ea9c1e413fd133181e3;hp=9dd74536edb010fbc748c83df36e9b804b3831ab;hpb=926031b7640bf5aad2ffcd54b096911743a47d97;p=oweals%2Fbusybox.git diff --git a/archival/tar.c b/archival/tar.c index 9dd74536e..280ded4e1 100644 --- a/archival/tar.c +++ b/archival/tar.c @@ -15,35 +15,125 @@ * Copyright (c) 1999 by David I. Bell * Permission is granted to use, distribute, or modify this source, * provided that this copyright notice remains intact. - * Permission to distribute sash derived code under the GPL has been granted. + * Permission to distribute sash derived code under GPL has been granted. * * Based in part on the tar implementation from busybox-0.28 * Copyright (C) 1995 Bruce Perens * - * Licensed under GPLv2 or later, see file LICENSE in this tarball for details. + * Licensed under GPLv2 or later, see file LICENSE in this source tree. */ +//config:config TAR +//config: bool "tar (40 kb)" +//config: default y +//config: help +//config: tar is an archiving program. It's commonly used with gzip to +//config: create compressed archives. It's probably the most widely used +//config: UNIX archive program. +//config: +//config:config FEATURE_TAR_LONG_OPTIONS +//config: bool "Enable long options" +//config: default y +//config: depends on TAR && LONG_OPTS +//config: +//config:config FEATURE_TAR_CREATE +//config: bool "Enable -c (archive creation)" +//config: default y +//config: depends on TAR +//config: +//config:config FEATURE_TAR_AUTODETECT +//config: bool "Autodetect compressed tarballs" +//config: default y +//config: depends on TAR && (FEATURE_SEAMLESS_Z || FEATURE_SEAMLESS_GZ || FEATURE_SEAMLESS_BZ2 || FEATURE_SEAMLESS_LZMA || FEATURE_SEAMLESS_XZ) +//config: help +//config: With this option tar can automatically detect compressed +//config: tarballs. Currently it works only on files (not pipes etc). +//config: +//config:config FEATURE_TAR_FROM +//config: bool "Enable -X (exclude from) and -T (include from) options)" +//config: default y +//config: depends on TAR +//config: help +//config: If you enable this option you'll be able to specify +//config: a list of files to include or exclude from an archive. +//config: +//config:config FEATURE_TAR_OLDGNU_COMPATIBILITY +//config: bool "Support old tar header format" +//config: default y +//config: depends on TAR || DPKG +//config: help +//config: This option is required to unpack archives created in +//config: the old GNU format; help to kill this old format by +//config: repacking your ancient archives with the new format. +//config: +//config:config FEATURE_TAR_OLDSUN_COMPATIBILITY +//config: bool "Enable untarring of tarballs with checksums produced by buggy Sun tar" +//config: default y +//config: depends on TAR || DPKG +//config: help +//config: This option is required to unpack archives created by some old +//config: version of Sun's tar (it was calculating checksum using signed +//config: arithmetic). It is said to be fixed in newer Sun tar, but "old" +//config: tarballs still exist. +//config: +//config:config FEATURE_TAR_GNU_EXTENSIONS +//config: bool "Support GNU tar extensions (long filenames)" +//config: default y +//config: depends on TAR || DPKG +//config: +//config:config FEATURE_TAR_TO_COMMAND +//config: bool "Support writing to an external program (--to-command)" +//config: default y +//config: depends on TAR && FEATURE_TAR_LONG_OPTIONS +//config: help +//config: If you enable this option you'll be able to instruct tar to send +//config: the contents of each extracted file to the standard input of an +//config: external program. +//config: +//config:config FEATURE_TAR_UNAME_GNAME +//config: bool "Enable use of user and group names" +//config: default y +//config: depends on TAR +//config: help +//config: Enable use of user and group names in tar. This affects contents +//config: listings (-t) and preserving permissions when unpacking (-p). +//config: +200 bytes. +//config: +//config:config FEATURE_TAR_NOPRESERVE_TIME +//config: bool "Enable -m (do not preserve time) GNU option" +//config: default y +//config: depends on TAR +//config: +//config:config FEATURE_TAR_SELINUX +//config: bool "Support extracting SELinux labels" +//config: default n +//config: depends on TAR && SELINUX +//config: help +//config: With this option busybox supports restoring SELinux labels +//config: when extracting files from tar archives. + +//applet:IF_TAR(APPLET(tar, BB_DIR_BIN, BB_SUID_DROP)) +//kbuild:lib-$(CONFIG_TAR) += tar.o + #include #include "libbb.h" -#include "unarchive.h" +#include "common_bufsiz.h" +#include "bb_archive.h" /* FIXME: Stop using this non-standard feature */ #ifndef FNM_LEADING_DIR # define FNM_LEADING_DIR 0 #endif - -//#define DBG(fmt, ...) bb_error_msg("%s: " fmt, __func__, ## __VA_ARGS__) -#define DBG(...) ((void)0) +#if 0 +# define DBG(fmt, ...) bb_error_msg("%s: " fmt, __func__, ## __VA_ARGS__) +#else +# define DBG(...) ((void)0) +#endif +#define DBG_OPTION_PARSING 0 #define block_buf bb_common_bufsiz1 - - -#if !ENABLE_FEATURE_SEAMLESS_GZ && !ENABLE_FEATURE_SEAMLESS_BZ2 -/* Do not pass gzip flag to writeTarFile() */ -#define writeTarFile(tar_fd, verboseFlag, dereferenceFlag, include, exclude, gzip) \ - writeTarFile(tar_fd, verboseFlag, dereferenceFlag, include, exclude) -#endif +#define INIT_G() do { setup_common_bufsiz(); } while (0) #if ENABLE_FEATURE_TAR_CREATE @@ -188,6 +278,23 @@ static void chksum_and_xwrite(int fd, struct tar_header_t* hp) xwrite(fd, hp, sizeof(*hp)); } +static void replace_symlink_placeholders(llist_t *list) +{ + while (list) { + char *target; + + target = list->data + strlen(list->data) + 1; + if (symlink(target, list->data)) { + /* shared message */ + bb_error_msg_and_die("can't create %slink '%s' to '%s'", + "sym", + list->data, target + ); + } + list = list->link; + } +} + #if ENABLE_FEATURE_TAR_GNU_EXTENSIONS static void writeLongname(int fd, int type, const char *name, int dir) { @@ -237,7 +344,7 @@ static int writeTarHeader(struct TarBallInfo *tbInfo, struct tar_header_t header; memset(&header, 0, sizeof(header)); - + strncpy(header.name, header_name, sizeof(header.name)); /* POSIX says to mask mode with 07777. */ @@ -245,7 +352,8 @@ static int writeTarHeader(struct TarBallInfo *tbInfo, PUT_OCTAL(header.uid, statbuf->st_uid); PUT_OCTAL(header.gid, statbuf->st_gid); memset(header.size, '0', sizeof(header.size)-1); /* Regular file size is handled later */ - PUT_OCTAL(header.mtime, statbuf->st_mtime); + /* users report that files with negative st_mtime cause trouble, so: */ + PUT_OCTAL(header.mtime, statbuf->st_mtime >= 0 ? statbuf->st_mtime : 0); /* Enter the user and group names */ safe_strncpy(header.uname, get_cached_username(statbuf->st_uid), sizeof(header.uname)); @@ -297,15 +405,42 @@ static int writeTarHeader(struct TarBallInfo *tbInfo, } else if (S_ISFIFO(statbuf->st_mode)) { header.typeflag = FIFOTYPE; } else if (S_ISREG(statbuf->st_mode)) { - if (sizeof(statbuf->st_size) > 4 - && statbuf->st_size > (off_t)0777777777777LL + /* header.size field is 12 bytes long */ + /* Does octal-encoded size fit? */ + uoff_t filesize = statbuf->st_size; + if (sizeof(filesize) <= 4 + || filesize <= (uoff_t)0777777777777LL ) { + PUT_OCTAL(header.size, filesize); + } + /* Does base256-encoded size fit? + * It always does unless off_t is wider than 64 bits. + */ + else if (ENABLE_FEATURE_TAR_GNU_EXTENSIONS +#if ULLONG_MAX > 0xffffffffffffffffLL /* 2^64-1 */ + && (filesize <= 0x3fffffffffffffffffffffffLL) +#endif + ) { + /* GNU tar uses "base-256 encoding" for very large numbers. + * Encoding is binary, with highest bit always set as a marker + * and sign in next-highest bit: + * 80 00 .. 00 - zero + * bf ff .. ff - largest positive number + * ff ff .. ff - minus 1 + * c0 00 .. 00 - smallest negative number + */ + char *p8 = header.size + sizeof(header.size); + do { + *--p8 = (uint8_t)filesize; + filesize >>= 8; + } while (p8 != header.size); + *p8 |= 0x80; + } else { bb_error_msg_and_die("can't store file '%s' " "of size %"OFF_FMT"u, aborting", fileName, statbuf->st_size); } header.typeflag = REGTYPE; - PUT_OCTAL(header.size, statbuf->st_size); } else { bb_error_msg("%s: unknown file type", fileName); return FALSE; @@ -378,17 +513,8 @@ static int FAST_FUNC writeFileToTarball(const char *fileName, struct stat *statb DBG("writeFileToTarball('%s')", fileName); - /* Strip leading '/' (must be before memorizing hardlink's name) */ - header_name = fileName; - while (header_name[0] == '/') { - static smallint warned; - - if (!warned) { - bb_error_msg("removing leading '/' from member names"); - warned = 1; - } - header_name++; - } + /* Strip leading '/' and such (must be before memorizing hardlink's name) */ + header_name = strip_unsafe_prefix(fileName); if (header_name[0] == '\0') return TRUE; @@ -402,8 +528,8 @@ static int FAST_FUNC writeFileToTarball(const char *fileName, struct stat *statb /* * Check to see if we are dealing with a hard link. * If so - - * Treat the first occurance of a given dev/inode as a file while - * treating any additional occurances as hard links. This is done + * Treat the first occurrence of a given dev/inode as a file while + * treating any additional occurrences as hard links. This is done * by adding the file information to the HardLinkInfo linked list. */ tbInfo->hlInfo = NULL; @@ -481,21 +607,12 @@ static int FAST_FUNC writeFileToTarball(const char *fileName, struct stat *statb return TRUE; } -#if ENABLE_FEATURE_SEAMLESS_GZ || ENABLE_FEATURE_SEAMLESS_BZ2 -# if !(ENABLE_FEATURE_SEAMLESS_GZ && ENABLE_FEATURE_SEAMLESS_BZ2) -# define vfork_compressor(tar_fd, gzip) vfork_compressor(tar_fd) -# endif +#if SEAMLESS_COMPRESSION /* Don't inline: vfork scares gcc and pessimizes code */ -static void NOINLINE vfork_compressor(int tar_fd, int gzip) +static void NOINLINE vfork_compressor(int tar_fd, const char *gzip) { pid_t gzipPid; -# if ENABLE_FEATURE_SEAMLESS_GZ && ENABLE_FEATURE_SEAMLESS_BZ2 - const char *zip_exec = (gzip == 1) ? "gzip" : "bzip2"; -# elif ENABLE_FEATURE_SEAMLESS_GZ - const char *zip_exec = "gzip"; -# else /* only ENABLE_FEATURE_SEAMLESS_BZ2 */ - const char *zip_exec = "bzip2"; -# endif + // On Linux, vfork never unpauses parent early, although standard // allows for that. Do we want to waste bytes checking for it? # define WAIT_FOR_CHILD 0 @@ -509,11 +626,6 @@ static void NOINLINE vfork_compressor(int tar_fd, int gzip) signal(SIGPIPE, SIG_IGN); /* we only want EPIPE on errors */ -# if defined(__GNUC__) && __GNUC__ - /* Avoid vfork clobbering */ - (void) &zip_exec; -# endif - gzipPid = xvfork(); if (gzipPid == 0) { @@ -529,7 +641,7 @@ static void NOINLINE vfork_compressor(int tar_fd, int gzip) xmove_fd(gzipDataPipe.rd, 0); xmove_fd(tar_fd, 1); /* exec gzip/bzip2 program/applet */ - BB_EXECLP(zip_exec, zip_exec, "-f", NULL); + BB_EXECLP(gzip, gzip, "-f", (char *)0); vfork_exec_errno = errno; _exit(EXIT_FAILURE); } @@ -552,16 +664,21 @@ static void NOINLINE vfork_compressor(int tar_fd, int gzip) # endif if (vfork_exec_errno) { errno = vfork_exec_errno; - bb_perror_msg_and_die("can't execute '%s'", zip_exec); + bb_perror_msg_and_die("can't execute '%s'", gzip); } } -#endif /* ENABLE_FEATURE_SEAMLESS_GZ || ENABLE_FEATURE_SEAMLESS_BZ2 */ +#endif /* SEAMLESS_COMPRESSION */ +#if !SEAMLESS_COMPRESSION +/* Do not pass gzip flag to writeTarFile() */ +#define writeTarFile(tar_fd, verboseFlag, recurseFlags, include, exclude, gzip) \ + writeTarFile(tar_fd, verboseFlag, recurseFlags, include, exclude) +#endif /* gcc 4.2.1 inlines it, making code bigger */ static NOINLINE int writeTarFile(int tar_fd, int verboseFlag, - int dereferenceFlag, const llist_t *include, - const llist_t *exclude, int gzip) + int recurseFlags, const llist_t *include, + const llist_t *exclude, const char *gzip) { int errorFlag = FALSE; struct TarBallInfo tbInfo; @@ -572,10 +689,9 @@ static NOINLINE int writeTarFile(int tar_fd, int verboseFlag, /* Store the stat info for the tarball's file, so * can avoid including the tarball into itself.... */ - if (fstat(tbInfo.tarFd, &tbInfo.tarFileStatBuf) < 0) - bb_perror_msg_and_die("can't stat tar file"); + xfstat(tbInfo.tarFd, &tbInfo.tarFileStatBuf, "can't stat tar file"); -#if ENABLE_FEATURE_SEAMLESS_GZ || ENABLE_FEATURE_SEAMLESS_BZ2 +#if SEAMLESS_COMPRESSION if (gzip) vfork_compressor(tbInfo.tarFd, gzip); #endif @@ -584,8 +700,7 @@ static NOINLINE int writeTarFile(int tar_fd, int verboseFlag, /* Read the directory/files and iterate over them one at a time */ while (include) { - if (!recursive_action(include->data, ACTION_RECURSE | - (dereferenceFlag ? ACTION_FOLLOWLINKS : 0), + if (!recursive_action(include->data, recurseFlags, writeFileToTarball, writeFileToTarball, &tbInfo, 0) ) { errorFlag = TRUE; @@ -611,7 +726,7 @@ static NOINLINE int writeTarFile(int tar_fd, int verboseFlag, if (errorFlag) bb_error_msg("error exit delayed from previous errors"); -#if ENABLE_FEATURE_SEAMLESS_GZ || ENABLE_FEATURE_SEAMLESS_BZ2 +#if SEAMLESS_COMPRESSION if (gzip) { int status; if (safe_waitpid(-1, &status, 0) == -1) @@ -623,11 +738,9 @@ static NOINLINE int writeTarFile(int tar_fd, int verboseFlag, #endif return errorFlag; } -#else -int writeTarFile(int tar_fd, int verboseFlag, - int dereferenceFlag, const llist_t *include, - const llist_t *exclude, int gzip); -#endif /* FEATURE_TAR_CREATE */ +#else /* !FEATURE_TAR_CREATE */ +# define writeTarFile(...) 0 +#endif #if ENABLE_FEATURE_TAR_FROM static llist_t *append_file_list_to_list(llist_t *list) @@ -637,76 +750,32 @@ static llist_t *append_file_list_to_list(llist_t *list) llist_t *newlist = NULL; while (list) { - src_stream = xfopen_for_read(llist_pop(&list)); + src_stream = xfopen_stdin(llist_pop(&list)); while ((line = xmalloc_fgetline(src_stream)) != NULL) { /* kill trailing '/' unless the string is just "/" */ char *cp = last_char_is(line, '/'); if (cp > line) *cp = '\0'; - llist_add_to(&newlist, line); + llist_add_to_end(&newlist, line); } fclose(src_stream); } return newlist; } -#else -# define append_file_list_to_list(x) 0 -#endif - -#if ENABLE_FEATURE_SEAMLESS_Z -static char FAST_FUNC get_header_tar_Z(archive_handle_t *archive_handle) -{ - /* Can't lseek over pipes */ - archive_handle->seek = seek_by_read; - - /* do the decompression, and cleanup */ - if (xread_char(archive_handle->src_fd) != 0x1f - || xread_char(archive_handle->src_fd) != 0x9d - ) { - bb_error_msg_and_die("invalid magic"); - } - - open_transformer(archive_handle->src_fd, unpack_Z_stream, "uncompress"); - archive_handle->offset = 0; - while (get_header_tar(archive_handle) == EXIT_SUCCESS) - continue; - - /* Can only do one file at a time */ - return EXIT_FAILURE; -} -#else -# define get_header_tar_Z NULL -#endif - -#ifdef CHECK_FOR_CHILD_EXITCODE -/* Looks like it isn't needed - tar detects malformed (truncated) - * archive if e.g. bunzip2 fails */ -static int child_error; - -static void handle_SIGCHLD(int status) -{ - /* Actually, 'status' is a signo. We reuse it for other needs */ - - /* Wait for any child without blocking */ - if (wait_any_nohang(&status) < 0) - /* wait failed?! I'm confused... */ - return; - - if (WIFEXITED(status) && WEXITSTATUS(status) == 0) - /* child exited with 0 */ - return; - /* Cannot happen? - if (!WIFSIGNALED(status) && !WIFEXITED(status)) return; */ - child_error = 1; -} #endif //usage:#define tar_trivial_usage -//usage: "-[" IF_FEATURE_TAR_CREATE("c") "xt" IF_FEATURE_SEAMLESS_GZ("z") -//usage: IF_FEATURE_SEAMLESS_BZ2("j") IF_FEATURE_SEAMLESS_LZMA("a") -//usage: IF_FEATURE_SEAMLESS_Z("Z") IF_FEATURE_TAR_NOPRESERVE_TIME("m") "vO] " -//usage: IF_FEATURE_TAR_FROM("[-X FILE] ") -//usage: "[-f TARFILE] [-C DIR] [FILE]..." +//usage: "-[" IF_FEATURE_TAR_CREATE("c") "xt" +//usage: IF_FEATURE_SEAMLESS_Z("Z") +//usage: IF_FEATURE_SEAMLESS_GZ("z") +//usage: IF_FEATURE_SEAMLESS_XZ("J") +//usage: IF_FEATURE_SEAMLESS_BZ2("j") +//usage: IF_FEATURE_SEAMLESS_LZMA("a") +//usage: IF_FEATURE_TAR_CREATE("h") +//usage: IF_FEATURE_TAR_NOPRESERVE_TIME("m") +//usage: "vO] " +//usage: IF_FEATURE_TAR_FROM("[-X FILE] [-T FILE] ") +//usage: "[-f TARFILE] [-C DIR] [FILE]..." //usage:#define tar_full_usage "\n\n" //usage: IF_FEATURE_TAR_CREATE("Create, extract, ") //usage: IF_NOT_FEATURE_TAR_CREATE("Extract ") @@ -717,22 +786,24 @@ static void handle_SIGCHLD(int status) //usage: ) //usage: "\n x Extract" //usage: "\n t List" -//usage: "\nOptions:" //usage: "\n f Name of TARFILE ('-' for stdin/out)" //usage: "\n C Change to DIR before operation" //usage: "\n v Verbose" +//usage: IF_FEATURE_SEAMLESS_Z( +//usage: "\n Z (De)compress using compress" +//usage: ) //usage: IF_FEATURE_SEAMLESS_GZ( //usage: "\n z (De)compress using gzip" //usage: ) +//usage: IF_FEATURE_SEAMLESS_XZ( +//usage: "\n J (De)compress using xz" +//usage: ) //usage: IF_FEATURE_SEAMLESS_BZ2( //usage: "\n j (De)compress using bzip2" //usage: ) //usage: IF_FEATURE_SEAMLESS_LZMA( //usage: "\n a (De)compress using lzma" //usage: ) -//usage: IF_FEATURE_SEAMLESS_Z( -//usage: "\n Z (De)compress using compress" -//usage: ) //usage: "\n O Extract to stdout" //usage: IF_FEATURE_TAR_CREATE( //usage: "\n h Follow symlinks" @@ -756,6 +827,7 @@ static void handle_SIGCHLD(int status) // o no-same-owner // p same-permissions // k keep-old +// no-recursion // numeric-owner // no-same-permissions // overwrite @@ -772,9 +844,12 @@ enum { IF_FEATURE_TAR_FROM( OPTBIT_INCLUDE_FROM,) IF_FEATURE_TAR_FROM( OPTBIT_EXCLUDE_FROM,) IF_FEATURE_SEAMLESS_GZ( OPTBIT_GZIP ,) - IF_FEATURE_SEAMLESS_Z( OPTBIT_COMPRESS ,) // 16th bit + IF_FEATURE_SEAMLESS_XZ( OPTBIT_XZ ,) // 16th bit + IF_FEATURE_SEAMLESS_Z( OPTBIT_COMPRESS ,) IF_FEATURE_TAR_NOPRESERVE_TIME(OPTBIT_NOPRESERVE_TIME,) #if ENABLE_FEATURE_TAR_LONG_OPTIONS + OPTBIT_STRIP_COMPONENTS, + OPTBIT_NORECURSION, IF_FEATURE_TAR_TO_COMMAND(OPTBIT_2COMMAND ,) OPTBIT_NUMERIC_OWNER, OPTBIT_NOPRESERVE_PERM, @@ -796,12 +871,17 @@ enum { OPT_INCLUDE_FROM = IF_FEATURE_TAR_FROM( (1 << OPTBIT_INCLUDE_FROM)) + 0, // T OPT_EXCLUDE_FROM = IF_FEATURE_TAR_FROM( (1 << OPTBIT_EXCLUDE_FROM)) + 0, // X OPT_GZIP = IF_FEATURE_SEAMLESS_GZ( (1 << OPTBIT_GZIP )) + 0, // z + OPT_XZ = IF_FEATURE_SEAMLESS_XZ( (1 << OPTBIT_XZ )) + 0, // J OPT_COMPRESS = IF_FEATURE_SEAMLESS_Z( (1 << OPTBIT_COMPRESS )) + 0, // Z - OPT_NOPRESERVE_TIME = IF_FEATURE_TAR_NOPRESERVE_TIME((1 << OPTBIT_NOPRESERVE_TIME)) + 0, // m - OPT_2COMMAND = IF_FEATURE_TAR_TO_COMMAND( (1 << OPTBIT_2COMMAND )) + 0, // to-command - OPT_NUMERIC_OWNER = IF_FEATURE_TAR_LONG_OPTIONS((1 << OPTBIT_NUMERIC_OWNER )) + 0, // numeric-owner - OPT_NOPRESERVE_PERM = IF_FEATURE_TAR_LONG_OPTIONS((1 << OPTBIT_NOPRESERVE_PERM)) + 0, // no-same-permissions - OPT_OVERWRITE = IF_FEATURE_TAR_LONG_OPTIONS((1 << OPTBIT_OVERWRITE )) + 0, // overwrite + OPT_NOPRESERVE_TIME = IF_FEATURE_TAR_NOPRESERVE_TIME((1 << OPTBIT_NOPRESERVE_TIME)) + 0, // m + OPT_STRIP_COMPONENTS = IF_FEATURE_TAR_LONG_OPTIONS((1 << OPTBIT_STRIP_COMPONENTS)) + 0, // strip-components + OPT_NORECURSION = IF_FEATURE_TAR_LONG_OPTIONS((1 << OPTBIT_NORECURSION )) + 0, // no-recursion + OPT_2COMMAND = IF_FEATURE_TAR_TO_COMMAND( (1 << OPTBIT_2COMMAND )) + 0, // to-command + OPT_NUMERIC_OWNER = IF_FEATURE_TAR_LONG_OPTIONS((1 << OPTBIT_NUMERIC_OWNER )) + 0, // numeric-owner + OPT_NOPRESERVE_PERM = IF_FEATURE_TAR_LONG_OPTIONS((1 << OPTBIT_NOPRESERVE_PERM)) + 0, // no-same-permissions + OPT_OVERWRITE = IF_FEATURE_TAR_LONG_OPTIONS((1 << OPTBIT_OVERWRITE )) + 0, // overwrite + + OPT_ANY_COMPRESS = (OPT_BZIP2 | OPT_LZMA | OPT_GZIP | OPT_XZ | OPT_COMPRESS), }; #if ENABLE_FEATURE_TAR_LONG_OPTIONS static const char tar_longopts[] ALIGN1 = @@ -834,12 +914,17 @@ static const char tar_longopts[] ALIGN1 = # if ENABLE_FEATURE_SEAMLESS_GZ "gzip\0" No_argument "z" # endif +# if ENABLE_FEATURE_SEAMLESS_XZ + "xz\0" No_argument "J" +# endif # if ENABLE_FEATURE_SEAMLESS_Z "compress\0" No_argument "Z" # endif # if ENABLE_FEATURE_TAR_NOPRESERVE_TIME "touch\0" No_argument "m" # endif + "strip-components\0" Required_argument "\xf9" + "no-recursion\0" No_argument "\xfa" # if ENABLE_FEATURE_TAR_TO_COMMAND "to-command\0" Required_argument "\xfb" # endif @@ -860,7 +945,6 @@ static const char tar_longopts[] ALIGN1 = int tar_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; int tar_main(int argc UNUSED_PARAM, char **argv) { - char FAST_FUNC (*get_header_ptr)(archive_handle_t *) = get_header_tar; archive_handle_t *tar_handle; char *base_dir = NULL; const char *tar_filename = "-"; @@ -869,6 +953,7 @@ int tar_main(int argc UNUSED_PARAM, char **argv) #if ENABLE_FEATURE_TAR_LONG_OPTIONS && ENABLE_FEATURE_TAR_FROM llist_t *excludes = NULL; #endif + INIT_G(); /* Initialise default values */ tar_handle = init_handle(); @@ -883,18 +968,29 @@ int tar_main(int argc UNUSED_PARAM, char **argv) /* Prepend '-' to the first argument if required */ opt_complementary = "--:" // first arg is options "tt:vv:" // count -t,-v - "?:" // bail out with usage instead of error return - "X::T::" // cumulative lists #if ENABLE_FEATURE_TAR_LONG_OPTIONS && ENABLE_FEATURE_TAR_FROM - "\xff::" // cumulative lists for --exclude + "\xff::" // --exclude=PATTERN is a list #endif IF_FEATURE_TAR_CREATE("c:") "t:x:" // at least one of these is reqd IF_FEATURE_TAR_CREATE("c--tx:t--cx:x--ct") // mutually exclusive - IF_NOT_FEATURE_TAR_CREATE("t--x:x--t"); // mutually exclusive + IF_NOT_FEATURE_TAR_CREATE("t--x:x--t") // mutually exclusive +#if ENABLE_FEATURE_TAR_LONG_OPTIONS + ":\xf9+" // --strip-components=NUM +#endif + ; #if ENABLE_FEATURE_TAR_LONG_OPTIONS applet_long_options = tar_longopts; #endif #if ENABLE_DESKTOP + /* Lie to buildroot when it starts asking stupid questions. */ + if (argv[1] && strcmp(argv[1], "--version") == 0) { + // Output of 'tar --version' examples: + // tar (GNU tar) 1.15.1 + // tar (GNU tar) 1.25 + // bsdtar 2.8.3 - libarchive 2.8.3 + puts("tar (busybox) " BB_VER); + return 0; + } if (argv[1] && argv[1][0] != '-') { /* Compat: * 1st argument without dash handles options with parameters @@ -923,17 +1019,22 @@ int tar_main(int argc UNUSED_PARAM, char **argv) #endif opt = getopt32(argv, "txC:f:Oopvk" - IF_FEATURE_TAR_CREATE( "ch" ) - IF_FEATURE_SEAMLESS_BZ2( "j" ) - IF_FEATURE_SEAMLESS_LZMA("a" ) - IF_FEATURE_TAR_FROM( "T:X:") - IF_FEATURE_SEAMLESS_GZ( "z" ) - IF_FEATURE_SEAMLESS_Z( "Z" ) + IF_FEATURE_TAR_CREATE( "ch" ) + IF_FEATURE_SEAMLESS_BZ2( "j" ) + IF_FEATURE_SEAMLESS_LZMA("a" ) + IF_FEATURE_TAR_FROM( "T:*X:*") + IF_FEATURE_SEAMLESS_GZ( "z" ) + IF_FEATURE_SEAMLESS_XZ( "J" ) + IF_FEATURE_SEAMLESS_Z( "Z" ) IF_FEATURE_TAR_NOPRESERVE_TIME("m") + IF_FEATURE_TAR_LONG_OPTIONS("\xf9:") // --strip-components , &base_dir // -C dir , &tar_filename // -f filename IF_FEATURE_TAR_FROM(, &(tar_handle->accept)) // T IF_FEATURE_TAR_FROM(, &(tar_handle->reject)) // X +#if ENABLE_FEATURE_TAR_LONG_OPTIONS + , &tar_handle->tar__strip_components // --strip-components +#endif IF_FEATURE_TAR_TO_COMMAND(, &(tar_handle->tar__to_command)) // --to-command #if ENABLE_FEATURE_TAR_LONG_OPTIONS && ENABLE_FEATURE_TAR_FROM , &excludes // --exclude @@ -941,11 +1042,49 @@ int tar_main(int argc UNUSED_PARAM, char **argv) , &verboseFlag // combined count for -t and -v , &verboseFlag // combined count for -t and -v ); - //bb_error_msg("opt:%08x", opt); +#if DBG_OPTION_PARSING + bb_error_msg("opt: 0x%08x", opt); +# define showopt(o) bb_error_msg("opt & %s(%x): %x", #o, o, opt & o); + showopt(OPT_TEST ); + showopt(OPT_EXTRACT ); + showopt(OPT_BASEDIR ); + showopt(OPT_TARNAME ); + showopt(OPT_2STDOUT ); + showopt(OPT_NOPRESERVE_OWNER); + showopt(OPT_P ); + showopt(OPT_VERBOSE ); + showopt(OPT_KEEP_OLD ); + showopt(OPT_CREATE ); + showopt(OPT_DEREFERENCE ); + showopt(OPT_BZIP2 ); + showopt(OPT_LZMA ); + showopt(OPT_INCLUDE_FROM ); + showopt(OPT_EXCLUDE_FROM ); + showopt(OPT_GZIP ); + showopt(OPT_XZ ); + showopt(OPT_COMPRESS ); + showopt(OPT_NOPRESERVE_TIME ); + showopt(OPT_STRIP_COMPONENTS); + showopt(OPT_NORECURSION ); + showopt(OPT_2COMMAND ); + showopt(OPT_NUMERIC_OWNER ); + showopt(OPT_NOPRESERVE_PERM ); + showopt(OPT_OVERWRITE ); + showopt(OPT_ANY_COMPRESS ); + bb_error_msg("base_dir:'%s'", base_dir); + bb_error_msg("tar_filename:'%s'", tar_filename); + bb_error_msg("verboseFlag:%d", verboseFlag); + bb_error_msg("tar_handle->tar__to_command:'%s'", tar_handle->tar__to_command); + bb_error_msg("tar_handle->tar__strip_components:%u", tar_handle->tar__strip_components); + return 0; +# undef showopt +#endif argv += optind; - if (verboseFlag) tar_handle->action_header = header_verbose_list; - if (verboseFlag == 1) tar_handle->action_header = header_list; + if (verboseFlag) + tar_handle->action_header = header_verbose_list; + if (verboseFlag == 1) + tar_handle->action_header = header_list; if (opt & OPT_EXTRACT) tar_handle->action_data = data_extract_all; @@ -957,6 +1096,7 @@ int tar_main(int argc UNUSED_PARAM, char **argv) putenv((char*)"TAR_FILETYPE=f"); signal(SIGPIPE, SIG_IGN); tar_handle->action_data = data_extract_to_command; + IF_FEATURE_TAR_TO_COMMAND(tar_handle->tar__to_command_shell = xstrdup(get_shell_name());) } if (opt & OPT_KEEP_OLD) @@ -976,18 +1116,6 @@ int tar_main(int argc UNUSED_PARAM, char **argv) tar_handle->ah_flags |= ARCHIVE_O_TRUNC; } - if (opt & OPT_GZIP) - get_header_ptr = get_header_tar_gz; - - if (opt & OPT_BZIP2) - get_header_ptr = get_header_tar_bz2; - - if (opt & OPT_LZMA) - get_header_ptr = get_header_tar_lzma; - - if (opt & OPT_COMPRESS) - get_header_ptr = get_header_tar_Z; - if (opt & OPT_NOPRESERVE_TIME) tar_handle->ah_flags &= ~ARCHIVE_RESTORE_DATE; @@ -1038,9 +1166,11 @@ int tar_main(int argc UNUSED_PARAM, char **argv) tar_handle->src_fd = tar_fd; tar_handle->seek = seek_by_read; } else { - if (ENABLE_FEATURE_TAR_AUTODETECT && flags == O_RDONLY) { - get_header_ptr = get_header_tar; - tar_handle->src_fd = open_zipped(tar_filename); + if (ENABLE_FEATURE_TAR_AUTODETECT + && flags == O_RDONLY + && !(opt & OPT_ANY_COMPRESS) + ) { + tar_handle->src_fd = open_zipped(tar_filename, /*fail_if_not_compressed:*/ 0); if (tar_handle->src_fd < 0) bb_perror_msg_and_die("can't open '%s'", tar_filename); } else { @@ -1052,28 +1182,76 @@ int tar_main(int argc UNUSED_PARAM, char **argv) if (base_dir) xchdir(base_dir); -#ifdef CHECK_FOR_CHILD_EXITCODE - /* We need to know whether child (gzip/bzip/etc) exits abnormally */ - signal(SIGCHLD, handle_SIGCHLD); -#endif + //if (SEAMLESS_COMPRESSION) + // /* We need to know whether child (gzip/bzip/etc) exits abnormally */ + // signal(SIGCHLD, check_errors_in_children); +#if ENABLE_FEATURE_TAR_CREATE /* Create an archive */ if (opt & OPT_CREATE) { -#if ENABLE_FEATURE_SEAMLESS_GZ || ENABLE_FEATURE_SEAMLESS_BZ2 - int zipMode = 0; - if (ENABLE_FEATURE_SEAMLESS_GZ && (opt & OPT_GZIP)) - zipMode = 1; - if (ENABLE_FEATURE_SEAMLESS_BZ2 && (opt & OPT_BZIP2)) - zipMode = 2; -#endif +# if SEAMLESS_COMPRESSION + const char *zipMode = NULL; + if (opt & OPT_COMPRESS) + zipMode = "compress"; + if (opt & OPT_GZIP) + zipMode = "gzip"; + if (opt & OPT_BZIP2) + zipMode = "bzip2"; + if (opt & OPT_LZMA) + zipMode = "lzma"; + if (opt & OPT_XZ) + zipMode = "xz"; +# endif /* NB: writeTarFile() closes tar_handle->src_fd */ - return writeTarFile(tar_handle->src_fd, verboseFlag, opt & OPT_DEREFERENCE, + return writeTarFile(tar_handle->src_fd, verboseFlag, + (opt & OPT_DEREFERENCE ? ACTION_FOLLOWLINKS : 0) + | (opt & OPT_NORECURSION ? 0 : ACTION_RECURSE), tar_handle->accept, tar_handle->reject, zipMode); } +#endif - while (get_header_ptr(tar_handle) == EXIT_SUCCESS) - continue; + if (opt & OPT_ANY_COMPRESS) { + USE_FOR_MMU(IF_DESKTOP(long long) int FAST_FUNC (*xformer)(transformer_state_t *xstate);) + USE_FOR_NOMMU(const char *xformer_prog;) + + if (opt & OPT_COMPRESS) { + USE_FOR_MMU(IF_FEATURE_SEAMLESS_Z(xformer = unpack_Z_stream;)) + USE_FOR_NOMMU(xformer_prog = "uncompress";) + } + if (opt & OPT_GZIP) { + USE_FOR_MMU(IF_FEATURE_SEAMLESS_GZ(xformer = unpack_gz_stream;)) + USE_FOR_NOMMU(xformer_prog = "gunzip";) + } + if (opt & OPT_BZIP2) { + USE_FOR_MMU(IF_FEATURE_SEAMLESS_BZ2(xformer = unpack_bz2_stream;)) + USE_FOR_NOMMU(xformer_prog = "bunzip2";) + } + if (opt & OPT_LZMA) { + USE_FOR_MMU(IF_FEATURE_SEAMLESS_LZMA(xformer = unpack_lzma_stream;)) + USE_FOR_NOMMU(xformer_prog = "unlzma";) + } + if (opt & OPT_XZ) { + USE_FOR_MMU(IF_FEATURE_SEAMLESS_XZ(xformer = unpack_xz_stream;)) + USE_FOR_NOMMU(xformer_prog = "unxz";) + } + + fork_transformer_with_sig(tar_handle->src_fd, xformer, xformer_prog); + /* Can't lseek over pipes */ + tar_handle->seek = seek_by_read; + /*tar_handle->offset = 0; - already is */ + } + + /* Zero processed headers (== empty file) is not a valid tarball. + * We (ab)use bb_got_signal as exitcode here, + * because check_errors_in_children() uses _it_ as error indicator. + */ + bb_got_signal = EXIT_FAILURE; + + while (get_header_tar(tar_handle) == EXIT_SUCCESS) + bb_got_signal = EXIT_SUCCESS; /* saw at least one header, good */ + + replace_symlink_placeholders(tar_handle->symlink_placeholders); /* Check that every file that should have been extracted was */ while (tar_handle->accept) { @@ -1088,5 +1266,10 @@ int tar_main(int argc UNUSED_PARAM, char **argv) if (ENABLE_FEATURE_CLEAN_UP /* && tar_handle->src_fd != STDIN_FILENO */) close(tar_handle->src_fd); - return EXIT_SUCCESS; + if (SEAMLESS_COMPRESSION || OPT_COMPRESS) { + /* Set bb_got_signal to 1 if a child died with !0 exitcode */ + check_errors_in_children(0); + } + + return bb_got_signal; }