ash: less hackish implementation of evaltreenr()
[oweals/busybox.git] / archival / tar.c
index caf4363dea69f63cae561d5623c5890f65160061..280ded4e14e325811e2cf976a050bac6e7ba4b27 100644 (file)
  *
  * Licensed under GPLv2 or later, see file LICENSE in this source tree.
  */
-/* TODO: security with -C DESTDIR option can be enhanced.
- * Consider tar file created via:
- * $ tar cvf bug.tar anything.txt
- * $ ln -s /tmp symlink
- * $ tar --append -f bug.tar symlink
- * $ rm symlink
- * $ mkdir symlink
- * $ tar --append -f bug.tar symlink/evil.py
- *
- * This will result in an archive which contains:
- * $ tar --list -f bug.tar
- * anything.txt
- * symlink
- * symlink/evil.py
- *
- * Untarring it puts evil.py in '/tmp' even if the -C DESTDIR is given.
- * This doesn't feel right, and IIRC GNU tar doesn't do that.
- */
 
 //config:config TAR
-//config:      bool "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:      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 archive creation"
+//config:      bool "Enable -c (archive creation)"
 //config:      default y
 //config:      depends on TAR
-//config:      help
-//config:        If you enable this option you'll be able to create
-//config:        tar archives using the `-c' option.
 //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:      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:      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 for old tar header format"
+//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:      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:      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 for GNU tar extensions (long filenames)"
+//config:      bool "Support GNU tar extensions (long filenames)"
 //config:      default y
 //config:      depends on TAR || DPKG
-//config:      help
-//config:        With this option busybox supports GNU long filenames and
-//config:        linknames.
-//config:
-//config:config FEATURE_TAR_LONG_OPTIONS
-//config:      bool "Enable long options"
-//config:      default y
-//config:      depends on TAR && LONG_OPTS
-//config:      help
-//config:        Enable use of long options, increases size by about 400 Bytes
 //config:
 //config:config FEATURE_TAR_TO_COMMAND
-//config:      bool "Support for writing to an external program"
+//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:      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:        Enables use of user and group names in tar. This affects contents
-//config:        listings (-t) and preserving permissions when unpacking (-p).
-//config:        +200 bytes.
+//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) option"
+//config:      bool "Enable -m (do not preserve time) GNU option"
 //config:      default y
 //config:      depends on TAR
-//config:      help
-//config:        With this option busybox supports GNU tar -m
-//config:        (do not preserve time) option.
 //config:
 //config:config FEATURE_TAR_SELINUX
-//config:      bool "Support for extracting SELinux labels"
+//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.
+//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
 
 
 #define block_buf bb_common_bufsiz1
+#define INIT_G() do { setup_common_bufsiz(); } while (0)
 
 
 #if ENABLE_FEATURE_TAR_CREATE
@@ -306,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)
 {
@@ -539,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;
@@ -964,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();
@@ -978,7 +968,6 @@ 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
-               IF_FEATURE_TAR_FROM("X::T::") // cumulative lists
 #if ENABLE_FEATURE_TAR_LONG_OPTIONS && ENABLE_FEATURE_TAR_FROM
                "\xff::" // --exclude=PATTERN is a list
 #endif
@@ -1030,13 +1019,13 @@ 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_XZ(  "J"   )
-               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
@@ -1197,9 +1186,10 @@ int tar_main(int argc UNUSED_PARAM, char **argv)
        //      /* 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 SEAMLESS_COMPRESSION
+# if SEAMLESS_COMPRESSION
                const char *zipMode = NULL;
                if (opt & OPT_COMPRESS)
                        zipMode = "compress";
@@ -1211,7 +1201,7 @@ int tar_main(int argc UNUSED_PARAM, char **argv)
                        zipMode = "lzma";
                if (opt & OPT_XZ)
                        zipMode = "xz";
-#endif
+# endif
                /* NB: writeTarFile() closes tar_handle->src_fd */
                return writeTarFile(tar_handle->src_fd, verboseFlag,
                                (opt & OPT_DEREFERENCE ? ACTION_FOLLOWLINKS : 0)
@@ -1219,26 +1209,32 @@ int tar_main(int argc UNUSED_PARAM, char **argv)
                                tar_handle->accept,
                                tar_handle->reject, zipMode);
        }
+#endif
 
        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(xformer = unpack_Z_stream;)
+               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(xformer = unpack_gz_stream;)
+               }
+               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(xformer = unpack_bz2_stream;)
+               }
+               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(xformer = unpack_lzma_stream;)
+               }
+               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(xformer = unpack_xz_stream;)
+               }
+               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 */
@@ -1255,6 +1251,8 @@ int tar_main(int argc UNUSED_PARAM, char **argv)
        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) {
                if (!find_list_entry(tar_handle->reject, tar_handle->accept->data)