tar: support for tar --numeric-owner. By Natanael Copa.
[oweals/busybox.git] / archival / tar.c
index 64975c42851236bc141c0b8b34e573125ca66b4a..03d66a6929e9e8b2dc8ecca730c6039c0c162d11 100644 (file)
  */
 
 #include <fnmatch.h>
-#include <getopt.h>
 #include "libbb.h"
 #include "unarchive.h"
 
+/* FIXME: Stop using this non-standard feature */
+#ifndef FNM_LEADING_DIR
+#define FNM_LEADING_DIR 0
+#endif
+
+
 #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
+
+
 #if ENABLE_FEATURE_TAR_CREATE
 
 /* Tar file constants  */
@@ -71,26 +84,26 @@ struct TarHeader {            /* byte offset */
 */
 typedef struct HardLinkInfo HardLinkInfo;
 struct HardLinkInfo {
-       HardLinkInfo *next;     /* Next entry in list */
-       dev_t dev;                      /* Device number */
-       ino_t ino;                      /* Inode number */
-       short linkCount;        /* (Hard) Link Count */
-       char name[1];           /* Start of filename (must be last) */
+       HardLinkInfo *next;     /* Next entry in list */
+       dev_t dev;              /* Device number */
+       ino_t ino;              /* Inode number */
+       short linkCount;        /* (Hard) Link Count */
+       char name[1];           /* Start of filename (must be last) */
 };
 
 /* Some info to be carried along when creating a new tarball */
 typedef struct TarBallInfo TarBallInfo;
 struct TarBallInfo {
-       int tarFd;                              /* Open-for-write file descriptor
-                                                          for the tarball */
-       struct stat statBuf;    /* Stat info for the tarball, letting
-                                                          us know the inode and device that the
-                                                          tarball lives, so we can avoid trying
-                                                          to include the tarball into itself */
-       int verboseFlag;                /* Whether to print extra stuff or not */
-       const llist_t *excludeList;     /* List of files to not include */
-       HardLinkInfo *hlInfoHead;       /* Hard Link Tracking Information */
-       HardLinkInfo *hlInfo;   /* Hard Link Info for the current file */
+       int tarFd;                      /* Open-for-write file descriptor
+                                        * for the tarball */
+       struct stat statBuf;            /* Stat info for the tarball, letting
+                                        * us know the inode and device that the
+                                        * tarball lives, so we can avoid trying
+                                        * to include the tarball into itself */
+       int verboseFlag;                /* Whether to print extra stuff or not */
+       const llist_t *excludeList;     /* List of files to not include */
+       HardLinkInfo *hlInfoHead;       /* Hard Link Tracking Information */
+       HardLinkInfo *hlInfo;           /* Hard Link Info for the current file */
 };
 
 /* A nice enum with all the possible tar file content types */
@@ -344,7 +357,8 @@ static int writeTarHeader(struct TarBallInfo *tbInfo,
        if (tbInfo->verboseFlag) {
                FILE *vbFd = stdout;
 
-               if (tbInfo->tarFd == STDOUT_FILENO)     /* If the archive goes to stdout, verbose to stderr */
+               /* If archive goes to stdout, verbose goes to stderr */
+               if (tbInfo->tarFd == STDOUT_FILENO)
                        vbFd = stderr;
                /* GNU "tar cvvf" prints "extended" listing a-la "ls -l" */
                /* We don't have such excesses here: for us "v" == "vv" */
@@ -383,8 +397,8 @@ static int exclude_file(const llist_t *excluded_files, const char *file)
 #define exclude_file(excluded_files, file) 0
 #endif
 
-static int writeFileToTarball(const char *fileName, struct stat *statbuf,
-                       void *userData, int depth ATTRIBUTE_UNUSED)
+static int FAST_FUNC writeFileToTarball(const char *fileName, struct stat *statbuf,
+                       void *userData, int depth UNUSED_PARAM)
 {
        struct TarBallInfo *tbInfo = (struct TarBallInfo *) userData;
        const char *header_name;
@@ -490,92 +504,106 @@ static int writeFileToTarball(const char *fileName, struct stat *statbuf,
        return TRUE;
 }
 
-static int writeTarFile(const int tar_fd, const int verboseFlag,
-       const unsigned long dereferenceFlag, const llist_t *include,
-       const llist_t *exclude, const int gzip)
+#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
+/* Don't inline: vfork scares gcc and pessimizes code */
+static void NOINLINE vfork_compressor(int tar_fd, int gzip)
 {
-       pid_t gzipPid = 0;
-       int errorFlag = FALSE;
-       struct TarBallInfo tbInfo;
-
-       tbInfo.hlInfoHead = NULL;
-
-       fchmod(tar_fd, 0644);
-       tbInfo.tarFd = tar_fd;
-       tbInfo.verboseFlag = verboseFlag;
-
-       /* Store the stat info for the tarball's file, so
-        * can avoid including the tarball into itself....  */
-       if (fstat(tbInfo.tarFd, &tbInfo.statBuf) < 0)
-               bb_perror_msg_and_die("cannot stat tar file");
-
-       if ((ENABLE_FEATURE_TAR_GZIP || ENABLE_FEATURE_TAR_BZIP2) && gzip) {
-// 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
-
-               volatile int vfork_exec_errno = 0;
-#if WAIT_FOR_CHILD
-               struct { int rd; int wr; } gzipStatusPipe;
+       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
-               struct { int rd; int wr; } gzipDataPipe;
-               const char *zip_exec = (gzip == 1) ? "gzip" : "bzip2";
-
-               xpipe(&gzipDataPipe.rd);
+       // 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
+       volatile int vfork_exec_errno = 0;
+       struct fd_pair gzipDataPipe;
 #if WAIT_FOR_CHILD
-               xpipe(&gzipStatusPipe.rd);
+       struct fd_pair gzipStatusPipe;
+       xpiped_pair(gzipStatusPipe);
 #endif
+       xpiped_pair(gzipDataPipe);
 
-               signal(SIGPIPE, SIG_IGN); /* we only want EPIPE on errors */
+       signal(SIGPIPE, SIG_IGN); /* we only want EPIPE on errors */
 
 #if defined(__GNUC__) && __GNUC__
-               /* Avoid vfork clobbering */
-               (void) &include;
-               (void) &errorFlag;
-               (void) &zip_exec;
+       /* Avoid vfork clobbering */
+       (void) &zip_exec;
 #endif
 
-               gzipPid = vfork();
-               if (gzipPid < 0)
-                       bb_perror_msg_and_die("vfork gzip");
+       gzipPid = vfork();
+       if (gzipPid < 0)
+               bb_perror_msg_and_die("vfork");
 
-               if (gzipPid == 0) {
-                       /* child */
-                       xmove_fd(tbInfo.tarFd, 1);
-                       xmove_fd(gzipDataPipe.rd, 0);
-                       close(gzipDataPipe.wr);
+       if (gzipPid == 0) {
+               /* child */
+               /* NB: close _first_, then move fds! */
+               close(gzipDataPipe.wr);
 #if WAIT_FOR_CHILD
-                       close(gzipStatusPipe.rd);
-                       fcntl(gzipStatusPipe.wr, F_SETFD, FD_CLOEXEC);
+               close(gzipStatusPipe.rd);
+               /* gzipStatusPipe.wr will close only on exec -
+                * parent waits for this close to happen */
+               fcntl(gzipStatusPipe.wr, F_SETFD, FD_CLOEXEC);
 #endif
-                       /* exec gzip/bzip2 program/applet */
-                       BB_EXECLP(zip_exec, zip_exec, "-f", NULL);
-                       vfork_exec_errno = errno;
-                       _exit(1);
-               }
+               xmove_fd(gzipDataPipe.rd, 0);
+               xmove_fd(tar_fd, 1);
+               /* exec gzip/bzip2 program/applet */
+               BB_EXECLP(zip_exec, zip_exec, "-f", NULL);
+               vfork_exec_errno = errno;
+               _exit(EXIT_FAILURE);
+       }
 
-               /* parent */
-               xmove_fd(gzipDataPipe.wr, tbInfo.tarFd);
-               close(gzipDataPipe.rd);
+       /* parent */
+       xmove_fd(gzipDataPipe.wr, tar_fd);
+       close(gzipDataPipe.rd);
 #if WAIT_FOR_CHILD
-               close(gzipStatusPipe.wr);
-               while (1) {
-                       char buf;
-                       int n;
+       close(gzipStatusPipe.wr);
+       while (1) {
+               char buf;
+               int n;
+
+               /* Wait until child execs (or fails to) */
+               n = full_read(gzipStatusPipe.rd, &buf, 1);
+               if (n < 0 /* && errno == EAGAIN */)
+                       continue;       /* try it again */
+       }
+       close(gzipStatusPipe.rd);
+#endif
+       if (vfork_exec_errno) {
+               errno = vfork_exec_errno;
+               bb_perror_msg_and_die("cannot exec %s", zip_exec);
+       }
+}
+#endif /* ENABLE_FEATURE_SEAMLESS_GZ || ENABLE_FEATURE_SEAMLESS_BZ2 */
 
-                       /* Wait until child execs (or fails to) */
-                       n = full_read(gzipStatusPipe.rd, &buf, 1);
-                       if ((n < 0) && (/*errno == EAGAIN ||*/ errno == EINTR))
-                               continue;       /* try it again */
 
-               }
-               close(gzipStatusPipe.rd);
+/* 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 errorFlag = FALSE;
+       struct TarBallInfo tbInfo;
+
+       tbInfo.hlInfoHead = NULL;
+       tbInfo.tarFd = tar_fd;
+       tbInfo.verboseFlag = verboseFlag;
+
+       /* Store the stat info for the tarball's file, so
+        * can avoid including the tarball into itself....  */
+       if (fstat(tbInfo.tarFd, &tbInfo.statBuf) < 0)
+               bb_perror_msg_and_die("cannot stat tar file");
+
+#if ENABLE_FEATURE_SEAMLESS_GZ || ENABLE_FEATURE_SEAMLESS_BZ2
+       if (gzip)
+               vfork_compressor(tbInfo.tarFd, gzip);
 #endif
-               if (vfork_exec_errno) {
-                       errno = vfork_exec_errno;
-                       bb_perror_msg_and_die("cannot exec %s", zip_exec);
-               }
-       }
 
        tbInfo.excludeList = exclude;
 
@@ -608,37 +636,34 @@ static int writeTarFile(const int tar_fd, const int verboseFlag,
        if (errorFlag)
                bb_error_msg("error exit delayed from previous errors");
 
-       if (gzipPid) {
+#if ENABLE_FEATURE_SEAMLESS_GZ || ENABLE_FEATURE_SEAMLESS_BZ2
+       if (gzip) {
                int status;
-               if (safe_waitpid(gzipPid, &status, 0) == -1)
+               if (safe_waitpid(-1, &status, 0) == -1)
                        bb_perror_msg("waitpid");
                else if (!WIFEXITED(status) || WEXITSTATUS(status))
                        /* gzip was killed or has exited with nonzero! */
                        errorFlag = TRUE;
        }
+#endif
        return errorFlag;
 }
 #else
-int writeTarFile(const int tar_fd, const int verboseFlag,
-       const unsigned long dereferenceFlag, const llist_t *include,
-       const llist_t *exclude, const int gzip);
+int writeTarFile(int tar_fd, int verboseFlag,
+       int dereferenceFlag, const llist_t *include,
+       const llist_t *exclude, int gzip);
 #endif /* FEATURE_TAR_CREATE */
 
 #if ENABLE_FEATURE_TAR_FROM
 static llist_t *append_file_list_to_list(llist_t *list)
 {
        FILE *src_stream;
-       llist_t *cur = list;
-       llist_t *tmp;
        char *line;
        llist_t *newlist = NULL;
 
-       while (cur) {
-               src_stream = xfopen(cur->data, "r");
-               tmp = cur;
-               cur = cur->link;
-               free(tmp);
-               while ((line = xmalloc_getline(src_stream)) != NULL) {
+       while (list) {
+               src_stream = xfopen_for_read(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)
@@ -653,8 +678,8 @@ static llist_t *append_file_list_to_list(llist_t *list)
 #define append_file_list_to_list(x) 0
 #endif
 
-#if ENABLE_FEATURE_TAR_COMPRESS
-static char get_header_tar_Z(archive_handle_t *archive_handle)
+#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;
@@ -666,7 +691,7 @@ static char get_header_tar_Z(archive_handle_t *archive_handle)
                bb_error_msg_and_die("invalid magic");
        }
 
-       archive_handle->src_fd = open_transformer(archive_handle->src_fd, uncompress, "uncompress");
+       open_transformer(archive_handle->src_fd, unpack_Z_stream, "uncompress");
        archive_handle->offset = 0;
        while (get_header_tar(archive_handle) == EXIT_SUCCESS)
                continue;
@@ -703,16 +728,17 @@ static void handle_SIGCHLD(int status)
 
 enum {
        OPTBIT_KEEP_OLD = 7,
-       USE_FEATURE_TAR_CREATE(  OPTBIT_CREATE      ,)
-       USE_FEATURE_TAR_CREATE(  OPTBIT_DEREFERENCE ,)
-       USE_FEATURE_TAR_BZIP2(   OPTBIT_BZIP2       ,)
-       USE_FEATURE_TAR_LZMA(    OPTBIT_LZMA        ,)
-       USE_FEATURE_TAR_FROM(    OPTBIT_INCLUDE_FROM,)
-       USE_FEATURE_TAR_FROM(    OPTBIT_EXCLUDE_FROM,)
-       USE_FEATURE_TAR_GZIP(    OPTBIT_GZIP        ,)
-       USE_FEATURE_TAR_COMPRESS(OPTBIT_COMPRESS    ,)
+       USE_FEATURE_TAR_CREATE(   OPTBIT_CREATE      ,)
+       USE_FEATURE_TAR_CREATE(   OPTBIT_DEREFERENCE ,)
+       USE_FEATURE_SEAMLESS_BZ2( OPTBIT_BZIP2       ,)
+       USE_FEATURE_SEAMLESS_LZMA(OPTBIT_LZMA        ,)
+       USE_FEATURE_TAR_FROM(     OPTBIT_INCLUDE_FROM,)
+       USE_FEATURE_TAR_FROM(     OPTBIT_EXCLUDE_FROM,)
+       USE_FEATURE_SEAMLESS_GZ(  OPTBIT_GZIP        ,)
+       USE_FEATURE_SEAMLESS_Z(   OPTBIT_COMPRESS    ,)
        OPTBIT_NOPRESERVE_OWN,
        OPTBIT_NOPRESERVE_PERM,
+       OPTBIT_NUMERIC_OWNER,
        OPT_TEST         = 1 << 0, // t
        OPT_EXTRACT      = 1 << 1, // x
        OPT_BASEDIR      = 1 << 2, // C
@@ -721,16 +747,17 @@ enum {
        OPT_P            = 1 << 5, // p
        OPT_VERBOSE      = 1 << 6, // v
        OPT_KEEP_OLD     = 1 << 7, // k
-       OPT_CREATE       = USE_FEATURE_TAR_CREATE(  (1<<OPTBIT_CREATE      )) + 0, // c
-       OPT_DEREFERENCE  = USE_FEATURE_TAR_CREATE(  (1<<OPTBIT_DEREFERENCE )) + 0, // h
-       OPT_BZIP2        = USE_FEATURE_TAR_BZIP2(   (1<<OPTBIT_BZIP2       )) + 0, // j
-       OPT_LZMA         = USE_FEATURE_TAR_LZMA(    (1<<OPTBIT_LZMA        )) + 0, // a
-       OPT_INCLUDE_FROM = USE_FEATURE_TAR_FROM(    (1<<OPTBIT_INCLUDE_FROM)) + 0, // T
-       OPT_EXCLUDE_FROM = USE_FEATURE_TAR_FROM(    (1<<OPTBIT_EXCLUDE_FROM)) + 0, // X
-       OPT_GZIP         = USE_FEATURE_TAR_GZIP(    (1<<OPTBIT_GZIP        )) + 0, // z
-       OPT_COMPRESS     = USE_FEATURE_TAR_COMPRESS((1<<OPTBIT_COMPRESS    )) + 0, // Z
+       OPT_CREATE       = USE_FEATURE_TAR_CREATE(   (1 << OPTBIT_CREATE      )) + 0, // c
+       OPT_DEREFERENCE  = USE_FEATURE_TAR_CREATE(   (1 << OPTBIT_DEREFERENCE )) + 0, // h
+       OPT_BZIP2        = USE_FEATURE_SEAMLESS_BZ2( (1 << OPTBIT_BZIP2       )) + 0, // j
+       OPT_LZMA         = USE_FEATURE_SEAMLESS_LZMA((1 << OPTBIT_LZMA        )) + 0, // a
+       OPT_INCLUDE_FROM = USE_FEATURE_TAR_FROM(     (1 << OPTBIT_INCLUDE_FROM)) + 0, // T
+       OPT_EXCLUDE_FROM = USE_FEATURE_TAR_FROM(     (1 << OPTBIT_EXCLUDE_FROM)) + 0, // X
+       OPT_GZIP         = USE_FEATURE_SEAMLESS_GZ(  (1 << OPTBIT_GZIP        )) + 0, // z
+       OPT_COMPRESS     = USE_FEATURE_SEAMLESS_Z(   (1 << OPTBIT_COMPRESS    )) + 0, // Z
        OPT_NOPRESERVE_OWN  = 1 << OPTBIT_NOPRESERVE_OWN , // no-same-owner
        OPT_NOPRESERVE_PERM = 1 << OPTBIT_NOPRESERVE_PERM, // no-same-permissions
+       OPT_NUMERIC_OWNER = 1 << OPTBIT_NUMERIC_OWNER,
 };
 #if ENABLE_FEATURE_TAR_LONG_OPTIONS
 static const char tar_longopts[] ALIGN1 =
@@ -746,22 +773,23 @@ static const char tar_longopts[] ALIGN1 =
        "create\0"              No_argument       "c"
        "dereference\0"         No_argument       "h"
 # endif
-# if ENABLE_FEATURE_TAR_BZIP2
+# if ENABLE_FEATURE_SEAMLESS_BZ2
        "bzip2\0"               No_argument       "j"
 # endif
-# if ENABLE_FEATURE_TAR_LZMA
+# if ENABLE_FEATURE_SEAMLESS_LZMA
        "lzma\0"                No_argument       "a"
 # endif
 # if ENABLE_FEATURE_TAR_FROM
        "files-from\0"          Required_argument "T"
        "exclude-from\0"        Required_argument "X"
 # endif
-# if ENABLE_FEATURE_TAR_GZIP
+# if ENABLE_FEATURE_SEAMLESS_GZ
        "gzip\0"                No_argument       "z"
 # endif
-# if ENABLE_FEATURE_TAR_COMPRESS
+# if ENABLE_FEATURE_SEAMLESS_Z
        "compress\0"            No_argument       "Z"
 # endif
+       "numeric-owner\0"       No_argument       "\xfc"
        "no-same-owner\0"       No_argument       "\xfd"
        "no-same-permissions\0" No_argument       "\xfe"
        /* --exclude takes next bit position in option mask, */
@@ -774,9 +802,9 @@ static const char tar_longopts[] ALIGN1 =
 #endif
 
 int tar_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
-int tar_main(int argc, char **argv)
+int tar_main(int argc UNUSED_PARAM, char **argv)
 {
-       char (*get_header_ptr)(archive_handle_t *) = get_header_tar;
+       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 = "-";
@@ -788,9 +816,13 @@ int tar_main(int argc, char **argv)
 
        /* Initialise default values */
        tar_handle = init_handle();
-       tar_handle->flags = ARCHIVE_CREATE_LEADING_DIRS
-                         | ARCHIVE_PRESERVE_DATE
-                         | ARCHIVE_EXTRACT_UNCONDITIONAL;
+       tar_handle->ah_flags = ARCHIVE_CREATE_LEADING_DIRS
+                            | ARCHIVE_PRESERVE_DATE
+                            | ARCHIVE_EXTRACT_UNCONDITIONAL;
+
+       /* Apparently only root's tar preserves perms (see bug 3844) */
+       if (getuid() != 0)
+               tar_handle->ah_flags |= ARCHIVE_NOPRESERVE_PERM;
 
        /* Prepend '-' to the first argument if required */
        opt_complementary = "--:" // first arg is options
@@ -808,12 +840,12 @@ int tar_main(int argc, char **argv)
 #endif
        opt = getopt32(argv,
                "txC:f:Opvk"
-               USE_FEATURE_TAR_CREATE(  "ch"  )
-               USE_FEATURE_TAR_BZIP2(   "j"   )
-               USE_FEATURE_TAR_LZMA(    "a"   )
-               USE_FEATURE_TAR_FROM(    "T:X:")
-               USE_FEATURE_TAR_GZIP(    "z"   )
-               USE_FEATURE_TAR_COMPRESS("Z"   )
+               USE_FEATURE_TAR_CREATE(   "ch"  )
+               USE_FEATURE_SEAMLESS_BZ2( "j"   )
+               USE_FEATURE_SEAMLESS_LZMA("a"   )
+               USE_FEATURE_TAR_FROM(     "T:X:")
+               USE_FEATURE_SEAMLESS_GZ(  "z"   )
+               USE_FEATURE_SEAMLESS_Z(   "Z"   )
                , &base_dir // -C dir
                , &tar_filename // -f filename
                USE_FEATURE_TAR_FROM(, &(tar_handle->accept)) // T
@@ -824,6 +856,7 @@ int tar_main(int argc, char **argv)
                , &verboseFlag // combined count for -t and -v
                , &verboseFlag // combined count for -t and -v
                );
+       argv += optind;
 
        if (verboseFlag) tar_handle->action_header = header_verbose_list;
        if (verboseFlag == 1) tar_handle->action_header = header_list;
@@ -835,13 +868,16 @@ int tar_main(int argc, char **argv)
                tar_handle->action_data = data_extract_to_stdout;
 
        if (opt & OPT_KEEP_OLD)
-               tar_handle->flags &= ~ARCHIVE_EXTRACT_UNCONDITIONAL;
+               tar_handle->ah_flags &= ~ARCHIVE_EXTRACT_UNCONDITIONAL;
 
        if (opt & OPT_NOPRESERVE_OWN)
-               tar_handle->flags |= ARCHIVE_NOPRESERVE_OWN;
+               tar_handle->ah_flags |= ARCHIVE_NOPRESERVE_OWN;
 
        if (opt & OPT_NOPRESERVE_PERM)
-               tar_handle->flags |= ARCHIVE_NOPRESERVE_PERM;
+               tar_handle->ah_flags |= ARCHIVE_NOPRESERVE_PERM;
+
+       if (opt & OPT_NUMERIC_OWNER)
+               tar_handle->ah_flags |= ARCHIVE_NUMERIC_OWNER;
 
        if (opt & OPT_GZIP)
                get_header_ptr = get_header_tar_gz;
@@ -869,21 +905,15 @@ int tar_main(int argc, char **argv)
        tar_handle->accept = append_file_list_to_list(tar_handle->accept);
 #endif
 
-       /* Check if we are reading from stdin */
-       if (argv[optind] && *argv[optind] == '-') {
-               /* Default is to read from stdin, so just skip to next arg */
-               optind++;
-       }
-
        /* Setup an array of filenames to work with */
        /* TODO: This is the same as in ar, separate function ? */
-       while (optind < argc) {
+       while (*argv) {
                /* kill trailing '/' unless the string is just "/" */
-               char *cp = last_char_is(argv[optind], '/');
-               if (cp > argv[optind])
+               char *cp = last_char_is(*argv, '/');
+               if (cp > *argv)
                        *cp = '\0';
-               llist_add_to_end(&tar_handle->accept, argv[optind]);
-               optind++;
+               llist_add_to_end(&tar_handle->accept, *argv);
+               argv++;
        }
 
        if (tar_handle->accept || tar_handle->reject)
@@ -901,8 +931,7 @@ int tar_main(int argc, char **argv)
 
                        tar_stream = stdout;
                        /* Mimicking GNU tar 1.15.1: */
-                       flags = O_WRONLY|O_CREAT|O_TRUNC;
-               /* was doing unlink; open(O_WRONLY|O_CREAT|O_EXCL); why? */
+                       flags = O_WRONLY | O_CREAT | O_TRUNC;
                } else {
                        tar_stream = stdin;
                        flags = O_RDONLY;
@@ -912,7 +941,14 @@ int tar_main(int argc, char **argv)
                        tar_handle->src_fd = fileno(tar_stream);
                        tar_handle->seek = seek_by_read;
                } else {
-                       tar_handle->src_fd = xopen(tar_filename, flags);
+                       if (ENABLE_FEATURE_TAR_AUTODETECT && flags == O_RDONLY) {
+                               get_header_ptr = get_header_tar;
+                               tar_handle->src_fd = open_zipped(tar_filename);
+                               if (tar_handle->src_fd < 0)
+                                       bb_perror_msg_and_die("can't open '%s'", tar_filename);
+                       } else {
+                               tar_handle->src_fd = xopen(tar_filename, flags);
+                       }
                }
        }
 
@@ -926,11 +962,13 @@ int tar_main(int argc, char **argv)
 
        /* create an archive */
        if (opt & OPT_CREATE) {
+#if ENABLE_FEATURE_SEAMLESS_GZ || ENABLE_FEATURE_SEAMLESS_BZ2
                int zipMode = 0;
-               if (ENABLE_FEATURE_TAR_GZIP && get_header_ptr == get_header_tar_gz)
+               if (ENABLE_FEATURE_SEAMLESS_GZ && (opt & OPT_GZIP))
                        zipMode = 1;
-               if (ENABLE_FEATURE_TAR_BZIP2 && get_header_ptr == get_header_tar_bz2)
+               if (ENABLE_FEATURE_SEAMLESS_BZ2 && (opt & OPT_BZIP2))
                        zipMode = 2;
+#endif
                /* NB: writeTarFile() closes tar_handle->src_fd */
                return writeTarFile(tar_handle->src_fd, verboseFlag, opt & OPT_DEREFERENCE,
                                tar_handle->accept,
@@ -938,7 +976,7 @@ int tar_main(int argc, char **argv)
        }
 
        while (get_header_ptr(tar_handle) == EXIT_SUCCESS)
-               /* nothing */;
+               continue;
 
        /* Check that every file that should have been extracted was */
        while (tar_handle->accept) {