diff options
Diffstat (limited to '')
-rw-r--r-- | libtar/extract.c | 667 |
1 files changed, 667 insertions, 0 deletions
diff --git a/libtar/extract.c b/libtar/extract.c new file mode 100644 index 000000000..ea86c233b --- /dev/null +++ b/libtar/extract.c @@ -0,0 +1,667 @@ +/* +** Copyright 1998-2003 University of Illinois Board of Trustees +** Copyright 1998-2003 Mark D. Roth +** All rights reserved. +** +** extract.c - libtar code to extract a file from a tar archive +** +** Mark D. Roth <roth@uiuc.edu> +** Campus Information Technologies and Educational Services +** University of Illinois at Urbana-Champaign +*/ + +#include <internal.h> + +#include <inttypes.h> +#include <stdio.h> +#include <string.h> +#include <sys/param.h> +#include <sys/types.h> +#include <fcntl.h> +#include <errno.h> +#include <utime.h> + +#include <sys/capability.h> +#include <sys/xattr.h> +#include <linux/xattr.h> + +#ifdef STDC_HEADERS +# include <stdlib.h> +#endif + +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif + +#include <selinux/selinux.h> + +#ifdef HAVE_EXT4_CRYPT +# include "ext4crypt_tar.h" +#endif +#include "android_utils.h" + +const unsigned long long progress_size = (unsigned long long)(T_BLOCKSIZE); + +static int +tar_set_file_perms(TAR *t, const char *realname) +{ + mode_t mode; + uid_t uid; + gid_t gid; + struct utimbuf ut; + const char *filename; + char *pn; + + pn = th_get_pathname(t); + filename = (realname ? realname : pn); + mode = th_get_mode(t); + uid = th_get_uid(t); + gid = th_get_gid(t); + ut.modtime = ut.actime = th_get_mtime(t); + +#ifdef DEBUG + printf("tar_set_file_perms(): setting perms: %s (mode %04o, uid %d, gid %d)\n", + filename, mode, uid, gid); +#endif + + /* change owner/group */ + if (geteuid() == 0) +#ifdef HAVE_LCHOWN + if (lchown(filename, uid, gid) == -1) + { +# ifdef DEBUG + fprintf(stderr, "lchown(\"%s\", %d, %d): %s\n", + filename, uid, gid, strerror(errno)); +# endif +#else /* ! HAVE_LCHOWN */ + if (!TH_ISSYM(t) && chown(filename, uid, gid) == -1) + { +# ifdef DEBUG + fprintf(stderr, "chown(\"%s\", %d, %d): %s\n", + filename, uid, gid, strerror(errno)); +# endif +#endif /* HAVE_LCHOWN */ + return -1; + } + + /* change access/modification time */ + if (!TH_ISSYM(t) && utime(filename, &ut) == -1) + { +#ifdef DEBUG + perror("utime()"); +#endif + return -1; + } + + /* change permissions */ + if (!TH_ISSYM(t) && chmod(filename, mode) == -1) + { +#ifdef DEBUG + perror("chmod()"); +#endif + return -1; + } + + return 0; +} + + +/* switchboard */ +int +tar_extract_file(TAR *t, const char *realname, const char *prefix, const int *progress_fd) +{ + int i; +#ifdef LIBTAR_FILE_HASH + char *lnp; + char *pn; + int pathname_len; + int realname_len; +#endif + + if (t->options & TAR_NOOVERWRITE) + { + struct stat s; + + if (lstat(realname, &s) == 0 || errno != ENOENT) + { + errno = EEXIST; + return -1; + } + } + + if (TH_ISDIR(t)) + { + i = tar_extract_dir(t, realname); + if (i == 1) + i = 0; + } + else if (TH_ISLNK(t)) + i = tar_extract_hardlink(t, realname, prefix); + else if (TH_ISSYM(t)) + i = tar_extract_symlink(t, realname); + else if (TH_ISCHR(t)) + i = tar_extract_chardev(t, realname); + else if (TH_ISBLK(t)) + i = tar_extract_blockdev(t, realname); + else if (TH_ISFIFO(t)) + i = tar_extract_fifo(t, realname); + else /* if (TH_ISREG(t)) */ + i = tar_extract_regfile(t, realname, progress_fd); + + if (i != 0) { + fprintf(stderr, "tar_extract_file(): failed to extract %s !!!\n", realname); + return i; + } + + i = tar_set_file_perms(t, realname); + if (i != 0) { + fprintf(stderr, "tar_extract_file(): failed to set permissions on %s !!!\n", realname); + return i; + } + + if((t->options & TAR_STORE_SELINUX) && t->th_buf.selinux_context != NULL) + { +#ifdef DEBUG + printf("tar_extract_file(): restoring SELinux context %s to file %s\n", t->th_buf.selinux_context, realname); +#endif + if (lsetfilecon(realname, t->th_buf.selinux_context) < 0) + fprintf(stderr, "tar_extract_file(): failed to restore SELinux context %s to file %s !!!\n", t->th_buf.selinux_context, realname); + } + + if((t->options & TAR_STORE_POSIX_CAP) && t->th_buf.has_cap_data) + { +#if 1 //def DEBUG + printf("tar_extract_file(): restoring posix capabilities to file %s\n", realname); + print_caps(&t->th_buf.cap_data); +#endif + if (setxattr(realname, XATTR_NAME_CAPS, &t->th_buf.cap_data, sizeof(struct vfs_cap_data), 0) < 0) + fprintf(stderr, "tar_extract_file(): failed to restore posix capabilities to file %s !!!\n", realname); + } + +#ifdef LIBTAR_FILE_HASH + pn = th_get_pathname(t); + pathname_len = strlen(pn) + 1; + realname_len = strlen(realname) + 1; + lnp = (char *)calloc(1, pathname_len + realname_len); + if (lnp == NULL) + return -1; + strcpy(&lnp[0], pn); + strcpy(&lnp[pathname_len], realname); +#ifdef DEBUG + printf("tar_extract_file(): calling libtar_hash_add(): key=\"%s\", " + "value=\"%s\"\n", pn, realname); +#endif + if (libtar_hash_add(t->h, lnp) != 0) + return -1; + free(lnp); +#endif + + return 0; +} + + +/* extract regular file */ +int +tar_extract_regfile(TAR *t, const char *realname, const int *progress_fd) +{ + int64_t size, i; + ssize_t k; + int fdout; + char buf[T_BLOCKSIZE]; + const char *filename; + char *pn; + +#ifdef DEBUG + printf(" ==> tar_extract_regfile(realname=\"%s\")\n", realname); +#endif + + if (!TH_ISREG(t)) + { + errno = EINVAL; + return -1; + } + + pn = th_get_pathname(t); + filename = (realname ? realname : pn); + size = th_get_size(t); + + if (mkdirhier(dirname(filename)) == -1) + return -1; + + printf(" ==> extracting: %s (file size %" PRId64 " bytes)\n", + filename, size); + + fdout = open(filename, O_WRONLY | O_CREAT | O_TRUNC +#ifdef O_BINARY + | O_BINARY +#endif + , 0666); + if (fdout == -1) + { +#ifdef DEBUG + perror("open()"); +#endif + return -1; + } + + /* extract the file */ + for (i = size; i > 0; i -= T_BLOCKSIZE) + { + k = tar_block_read(t, buf); + if (k != T_BLOCKSIZE) + { + if (k != -1) + errno = EINVAL; + close(fdout); + return -1; + } + + /* write block to output file */ + if (write(fdout, buf, + ((i > T_BLOCKSIZE) ? T_BLOCKSIZE : i)) == -1) + { + close(fdout); + return -1; + } + else + { + if (*progress_fd != 0) + write(*progress_fd, &progress_size, sizeof(progress_size)); + } + } + + /* close output file */ + if (close(fdout) == -1) + return -1; + +#ifdef DEBUG + printf("### done extracting %s\n", filename); +#endif + + return 0; +} + + +/* skip regfile */ +int +tar_skip_regfile(TAR *t) +{ + int64_t size, i; + ssize_t k; + char buf[T_BLOCKSIZE]; + + if (!TH_ISREG(t)) + { + errno = EINVAL; + return -1; + } + + size = th_get_size(t); + for (i = size; i > 0; i -= T_BLOCKSIZE) + { + k = tar_block_read(t, buf); + if (k != T_BLOCKSIZE) + { + if (k != -1) + errno = EINVAL; + return -1; + } + } + + return 0; +} + + +/* hardlink */ +int +tar_extract_hardlink(TAR * t, const char *realname, const char *prefix) +{ + const char *filename; + char *pn; + char *linktgt = NULL; + char *newtgt = NULL; + char *lnp; + libtar_hashptr_t hp; + + if (!TH_ISLNK(t)) + { + errno = EINVAL; + return -1; + } + + pn = th_get_pathname(t); + filename = (realname ? realname : pn); + if (mkdirhier(dirname(filename)) == -1) + return -1; + if (unlink(filename) == -1 && errno != ENOENT) + return -1; + libtar_hashptr_reset(&hp); + if (libtar_hash_getkey(t->h, &hp, th_get_linkname(t), + (libtar_matchfunc_t)libtar_str_match) != 0) + { + lnp = (char *)libtar_hashptr_data(&hp); + linktgt = &lnp[strlen(lnp) + 1]; + } + else + linktgt = th_get_linkname(t); + + newtgt = strdup(linktgt); + sprintf(linktgt, "%s/%s", prefix, newtgt); + + printf(" ==> extracting: %s (link to %s)\n", filename, linktgt); + + if (link(linktgt, filename) == -1) + { + fprintf(stderr, "tar_extract_hardlink(): failed restore of hardlink '%s' but returning as if nothing bad happened\n", filename); + return 0; // Used to be -1 + } + + return 0; +} + + +/* symlink */ +int +tar_extract_symlink(TAR *t, const char *realname) +{ + const char *filename; + char *pn; + + if (!TH_ISSYM(t)) + { + errno = EINVAL; + return -1; + } + + pn = th_get_pathname(t); + filename = (realname ? realname : pn); + if (mkdirhier(dirname(filename)) == -1) + return -1; + + if (unlink(filename) == -1 && errno != ENOENT) + return -1; + + printf(" ==> extracting: %s (symlink to %s)\n", + filename, th_get_linkname(t)); + + if (symlink(th_get_linkname(t), filename) == -1) + { +#ifdef DEBUG + perror("symlink()"); +#endif + return -1; + } + + return 0; +} + + +/* character device */ +int +tar_extract_chardev(TAR *t, const char *realname) +{ + mode_t mode; + unsigned long devmaj, devmin; + const char *filename; + char *pn; + + if (!TH_ISCHR(t)) + { + errno = EINVAL; + return -1; + } + + pn = th_get_pathname(t); + filename = (realname ? realname : pn); + mode = th_get_mode(t); + devmaj = th_get_devmajor(t); + devmin = th_get_devminor(t); + + if (mkdirhier(dirname(filename)) == -1) + return -1; + + printf(" ==> extracting: %s (character device %ld,%ld)\n", + filename, devmaj, devmin); + + if (mknod(filename, mode | S_IFCHR, + compat_makedev(devmaj, devmin)) == -1) + { + fprintf(stderr, "tar_extract_chardev(): failed restore of character device '%s' but returning as if nothing bad happened\n", filename); + return 0; // Used to be -1 + } + + return 0; +} + + +/* block device */ +int +tar_extract_blockdev(TAR *t, const char *realname) +{ + mode_t mode; + unsigned long devmaj, devmin; + const char *filename; + char *pn; + + if (!TH_ISBLK(t)) + { + errno = EINVAL; + return -1; + } + + pn = th_get_pathname(t); + filename = (realname ? realname : pn); + mode = th_get_mode(t); + devmaj = th_get_devmajor(t); + devmin = th_get_devminor(t); + + if (mkdirhier(dirname(filename)) == -1) + return -1; + + printf(" ==> extracting: %s (block device %ld,%ld)\n", + filename, devmaj, devmin); + + if (mknod(filename, mode | S_IFBLK, + compat_makedev(devmaj, devmin)) == -1) + { + fprintf(stderr, "tar_extract_blockdev(): failed restore of block device '%s' but returning as if nothing bad happened\n", filename); + return 0; // Used to be -1 + } + + return 0; +} + +/* directory */ +int +tar_extract_dir(TAR *t, const char *realname) +{ + mode_t mode; + const char *filename; + char *pn; + + if (!TH_ISDIR(t)) + { + errno = EINVAL; + return -1; + } + pn = th_get_pathname(t); + filename = (realname ? realname : pn); + mode = th_get_mode(t); + + if (mkdirhier(dirname(filename)) == -1) + return -1; + + printf(" ==> extracting: %s (mode %04o, directory)\n", filename, + mode); + + if (mkdir(filename, mode) == -1) + { + if (errno == EEXIST) + { + if (chmod(filename, mode) == -1) + { +#ifdef DEBUG + perror("chmod()"); +#endif + return -1; + } + else + { +#if 1 //def DEBUG + puts(" *** using existing directory"); +#endif + return 1; + } + } + else + { +#ifdef DEBUG + perror("mkdir()"); +#endif + return -1; + } + } + + if (t->options & TAR_STORE_ANDROID_USER_XATTR) + { + if (t->th_buf.has_user_default) { +#if 1 //def DEBUG + printf("tar_extract_file(): restoring android user.default xattr to %s\n", realname); +#endif + if (setxattr(realname, "user.default", NULL, 0, 0) < 0) { + fprintf(stderr, "tar_extract_file(): failed to restore android user.default to file %s !!!\n", realname); + return -1; + } + } + if (t->th_buf.has_user_cache) { +#if 1 //def DEBUG + printf("tar_extract_file(): restoring android user.inode_cache xattr to %s\n", realname); +#endif + if (write_path_inode(realname, "cache", "user.inode_cache")) + return -1; + } + if (t->th_buf.has_user_code_cache) { +#if 1 //def DEBUG + printf("tar_extract_file(): restoring android user.inode_code_cache xattr to %s\n", realname); +#endif + if (write_path_inode(realname, "code_cache", "user.inode_code_cache")) + return -1; + } + } + +#ifdef HAVE_EXT4_CRYPT + if(t->th_buf.eep != NULL) + { +#ifdef DEBUG + printf("tar_extract_file(): restoring EXT4 crypt policy %s to dir %s\n", t->th_buf.eep->master_key_descriptor, realname); +#endif + char binary_policy[EXT4_KEY_DESCRIPTOR_SIZE]; + if (!lookup_ref_tar(t->th_buf.eep->master_key_descriptor, &binary_policy[0])) { + printf("error looking up proper e4crypt policy for '%s' - %s\n", realname, t->th_buf.eep->master_key_descriptor); + return -1; + } + char policy_hex[EXT4_KEY_DESCRIPTOR_SIZE_HEX]; + policy_to_hex(binary_policy, policy_hex); + printf("restoring policy %s > '%s' to '%s'\n", t->th_buf.eep->master_key_descriptor, policy_hex, realname); + memcpy(&t->th_buf.eep->master_key_descriptor, binary_policy, EXT4_KEY_DESCRIPTOR_SIZE); + if (!e4crypt_policy_set_struct(realname, t->th_buf.eep)) + { + printf("tar_extract_file(): failed to restore EXT4 crypt policy to dir '%s' '%s'!!!\n", realname, policy_hex); + //return -1; // This may not be an error in some cases, so log and ignore + } + } +#endif + + return 0; +} + + +/* FIFO */ +int +tar_extract_fifo(TAR *t, const char *realname) +{ + mode_t mode; + const char *filename; + char *pn; + + if (!TH_ISFIFO(t)) + { + errno = EINVAL; + return -1; + } + + pn = th_get_pathname(t); + filename = (realname ? realname : pn); + mode = th_get_mode(t); + + if (mkdirhier(dirname(filename)) == -1) + return -1; + + + printf(" ==> extracting: %s (fifo)\n", filename); + + if (mkfifo(filename, mode) == -1) + { +#ifdef DEBUG + perror("mkfifo()"); +#endif + return -1; + } + + return 0; +} + +/* extract file contents from a tarchive */ +int +tar_extract_file_contents(TAR *t, void *buf, size_t *lenp) +{ + char block[T_BLOCKSIZE]; + int64_t size, i; + ssize_t k; + +#ifdef DEBUG + printf(" ==> tar_extract_file_contents\n"); +#endif + + if (!TH_ISREG(t)) + { + errno = EINVAL; + return -1; + } + + size = th_get_size(t); + if ((uint64_t)size > *lenp) + { + errno = ENOSPC; + return -1; + } + + /* extract the file */ + for (i = size; i >= T_BLOCKSIZE; i -= T_BLOCKSIZE) + { + k = tar_block_read(t, buf); + if (k != T_BLOCKSIZE) + { + if (k != -1) + errno = EINVAL; + return -1; + } + buf = (char *)buf + T_BLOCKSIZE; + } + if (i > 0) { + k = tar_block_read(t, block); + if (k != T_BLOCKSIZE) + { + if (k != -1) + errno = EINVAL; + return -1; + } + memcpy(buf, block, i); + } + *lenp = (size_t)size; + +#ifdef DEBUG + printf("### done extracting contents\n"); +#endif + return 0; +} |