busybox/archival/libarchive/data_extract_all.c
<<
>>
Prefs
   1/* vi: set sw=4 ts=4: */
   2/*
   3 * Licensed under GPLv2 or later, see file LICENSE in this source tree.
   4 */
   5#include "libbb.h"
   6#include "bb_archive.h"
   7
   8void FAST_FUNC data_extract_all(archive_handle_t *archive_handle)
   9{
  10        file_header_t *file_header = archive_handle->file_header;
  11        int dst_fd;
  12        int res;
  13        char *hard_link;
  14#if ENABLE_FEATURE_TAR_LONG_OPTIONS
  15        char *dst_name;
  16#else
  17# define dst_name (file_header->name)
  18#endif
  19
  20#if ENABLE_FEATURE_TAR_SELINUX
  21        char *sctx = archive_handle->tar__sctx[PAX_NEXT_FILE];
  22        if (!sctx)
  23                sctx = archive_handle->tar__sctx[PAX_GLOBAL];
  24        if (sctx) { /* setfscreatecon is 4 syscalls, avoid if possible */
  25                setfscreatecon(sctx);
  26                free(archive_handle->tar__sctx[PAX_NEXT_FILE]);
  27                archive_handle->tar__sctx[PAX_NEXT_FILE] = NULL;
  28        }
  29#endif
  30
  31        /* Hard links are encoded as regular files of size 0
  32         * with a nonempty link field */
  33        hard_link = NULL;
  34        if (S_ISREG(file_header->mode) && file_header->size == 0)
  35                hard_link = file_header->link_target;
  36
  37#if ENABLE_FEATURE_TAR_LONG_OPTIONS
  38        dst_name = file_header->name;
  39        if (archive_handle->tar__strip_components) {
  40                unsigned n = archive_handle->tar__strip_components;
  41                do {
  42                        dst_name = strchr(dst_name, '/');
  43                        if (!dst_name || dst_name[1] == '\0') {
  44                                data_skip(archive_handle);
  45                                goto ret;
  46                        }
  47                        dst_name++;
  48                        /*
  49                         * Link target is shortened only for hardlinks:
  50                         * softlinks restored unchanged.
  51                         */
  52                        if (hard_link) {
  53// GNU tar 1.26 does not check that we reached end of link name:
  54// if "dir/hardlink" is hardlinked to "file",
  55// tar xvf a.tar --strip-components=1 says:
  56//  tar: hardlink: Cannot hard link to '': No such file or directory
  57// and continues processing. We silently skip such entries.
  58                                hard_link = strchr(hard_link, '/');
  59                                if (!hard_link || hard_link[1] == '\0') {
  60                                        data_skip(archive_handle);
  61                                        goto ret;
  62                                }
  63                                hard_link++;
  64                        }
  65                } while (--n != 0);
  66        }
  67#endif
  68
  69        if (archive_handle->ah_flags & ARCHIVE_CREATE_LEADING_DIRS) {
  70                char *slash = strrchr(dst_name, '/');
  71                if (slash) {
  72                        *slash = '\0';
  73                        bb_make_directory(dst_name, -1, FILEUTILS_RECUR);
  74                        *slash = '/';
  75                }
  76        }
  77
  78        if (archive_handle->ah_flags & ARCHIVE_UNLINK_OLD) {
  79                /* Remove the entry if it exists */
  80                if (!S_ISDIR(file_header->mode)) {
  81                        if (hard_link) {
  82                                /* Ugly special case:
  83                                 * tar cf t.tar hardlink1 hardlink2 hardlink1
  84                                 * results in this tarball structure:
  85                                 * hardlink1
  86                                 * hardlink2 -> hardlink1
  87                                 * hardlink1 -> hardlink1 <== !!!
  88                                 */
  89                                if (strcmp(hard_link, dst_name) == 0)
  90                                        goto ret;
  91                        }
  92                        /* Proceed with deleting */
  93                        if (unlink(dst_name) == -1
  94                         && errno != ENOENT
  95                        ) {
  96                                bb_perror_msg_and_die("can't remove old file %s",
  97                                                dst_name);
  98                        }
  99                }
 100        }
 101        else if (archive_handle->ah_flags & ARCHIVE_EXTRACT_NEWER) {
 102                /* Remove the existing entry if its older than the extracted entry */
 103                struct stat existing_sb;
 104                if (lstat(dst_name, &existing_sb) == -1) {
 105                        if (errno != ENOENT) {
 106                                bb_simple_perror_msg_and_die("can't stat old file");
 107                        }
 108                }
 109                else if (existing_sb.st_mtime >= file_header->mtime) {
 110                        if (!S_ISDIR(file_header->mode)) {
 111                                bb_error_msg("%s not created: newer or "
 112                                        "same age file exists", dst_name);
 113                        }
 114                        data_skip(archive_handle);
 115                        goto ret;
 116                }
 117                else if ((unlink(dst_name) == -1) && (errno != EISDIR)) {
 118                        bb_perror_msg_and_die("can't remove old file %s",
 119                                        dst_name);
 120                }
 121        }
 122
 123        /* Handle hard links separately */
 124        if (hard_link) {
 125                create_or_remember_link(&archive_handle->link_placeholders,
 126                                hard_link,
 127                                dst_name,
 128                                1);
 129                /* Hardlinks have no separate mode/ownership, skip chown/chmod */
 130                goto ret;
 131        }
 132
 133        /* Create the filesystem entry */
 134        switch (file_header->mode & S_IFMT) {
 135        case S_IFREG: {
 136                /* Regular file */
 137                char *dst_nameN;
 138                int flags = O_WRONLY | O_CREAT | O_EXCL;
 139                if (archive_handle->ah_flags & ARCHIVE_O_TRUNC)
 140                        flags = O_WRONLY | O_CREAT | O_TRUNC;
 141                dst_nameN = dst_name;
 142#ifdef ARCHIVE_REPLACE_VIA_RENAME
 143                if (archive_handle->ah_flags & ARCHIVE_REPLACE_VIA_RENAME)
 144                        /* rpm-style temp file name */
 145                        dst_nameN = xasprintf("%s;%x", dst_name, (int)getpid());
 146#endif
 147                dst_fd = xopen3(dst_nameN,
 148                        flags,
 149                        file_header->mode
 150                        );
 151                bb_copyfd_exact_size(archive_handle->src_fd, dst_fd, file_header->size);
 152                close(dst_fd);
 153#ifdef ARCHIVE_REPLACE_VIA_RENAME
 154                if (archive_handle->ah_flags & ARCHIVE_REPLACE_VIA_RENAME) {
 155                        xrename(dst_nameN, dst_name);
 156                        free(dst_nameN);
 157                }
 158#endif
 159                break;
 160        }
 161        case S_IFDIR:
 162//TODO: this causes problems if tarball contains a r-xr-xr-x directory:
 163// we create this directory, and then fail to create files inside it
 164// (if tar xf isn't run as root).
 165// GNU tar works around this by chmod-ing directories *after* all files are extracted.
 166                res = mkdir(dst_name, file_header->mode);
 167                if ((res != 0)
 168                 && (errno != EISDIR) /* btw, Linux doesn't return this */
 169                 && (errno != EEXIST)
 170                ) {
 171                        bb_perror_msg("can't make dir %s", dst_name);
 172                }
 173                break;
 174        case S_IFLNK:
 175                /* Symlink */
 176//TODO: what if file_header->link_target == NULL (say, corrupted tarball?)
 177
 178                /* To avoid a directory traversal attack via symlinks,
 179                 * do not restore symlinks with ".." components
 180                 * or symlinks starting with "/", unless a magic
 181                 * envvar is set.
 182                 *
 183                 * For example, consider a .tar created via:
 184                 *  $ tar cvf bug.tar anything.txt
 185                 *  $ ln -s /tmp symlink
 186                 *  $ tar --append -f bug.tar symlink
 187                 *  $ rm symlink
 188                 *  $ mkdir symlink
 189                 *  $ tar --append -f bug.tar symlink/evil.py
 190                 *
 191                 * This will result in an archive that contains:
 192                 *  $ tar --list -f bug.tar
 193                 *  anything.txt
 194                 *  symlink [-> /tmp]
 195                 *  symlink/evil.py
 196                 *
 197                 * Untarring bug.tar would otherwise place evil.py in '/tmp'.
 198                 */
 199                create_or_remember_link(&archive_handle->link_placeholders,
 200                                file_header->link_target,
 201                                dst_name,
 202                                0);
 203                break;
 204        case S_IFSOCK:
 205        case S_IFBLK:
 206        case S_IFCHR:
 207        case S_IFIFO:
 208                res = mknod(dst_name, file_header->mode, file_header->device);
 209                if (res != 0) {
 210                        bb_perror_msg("can't create node %s", dst_name);
 211                }
 212                break;
 213        default:
 214                bb_simple_error_msg_and_die("unrecognized file type");
 215        }
 216
 217        if (!S_ISLNK(file_header->mode)) {
 218                if (!(archive_handle->ah_flags & ARCHIVE_DONT_RESTORE_OWNER)) {
 219                        uid_t uid = file_header->uid;
 220                        gid_t gid = file_header->gid;
 221#if ENABLE_FEATURE_TAR_UNAME_GNAME
 222                        if (!(archive_handle->ah_flags & ARCHIVE_NUMERIC_OWNER)) {
 223                                if (file_header->tar__uname) {
 224//TODO: cache last name/id pair?
 225                                        struct passwd *pwd = getpwnam(file_header->tar__uname);
 226                                        if (pwd) uid = pwd->pw_uid;
 227                                }
 228                                if (file_header->tar__gname) {
 229                                        struct group *grp = getgrnam(file_header->tar__gname);
 230                                        if (grp) gid = grp->gr_gid;
 231                                }
 232                        }
 233#endif
 234                        /* GNU tar 1.15.1 uses chown, not lchown */
 235                        chown(dst_name, uid, gid);
 236                }
 237                /* uclibc has no lchmod, glibc is even stranger -
 238                 * it has lchmod which seems to do nothing!
 239                 * so we use chmod... */
 240                if (!(archive_handle->ah_flags & ARCHIVE_DONT_RESTORE_PERM)) {
 241                        chmod(dst_name, file_header->mode);
 242                }
 243                if (archive_handle->ah_flags & ARCHIVE_RESTORE_DATE) {
 244                        struct timeval t[2];
 245
 246                        t[1].tv_sec = t[0].tv_sec = file_header->mtime;
 247                        t[1].tv_usec = t[0].tv_usec = 0;
 248                        utimes(dst_name, t);
 249                }
 250        }
 251
 252 ret: ;
 253#if ENABLE_FEATURE_TAR_SELINUX
 254        if (sctx) {
 255                /* reset the context after creating an entry */
 256                setfscreatecon(NULL);
 257        }
 258#endif
 259}
 260