/* ** 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 ** Campus Information Technologies and Educational Services ** University of Illinois at Urbana-Champaign */ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef STDC_HEADERS # include #endif #ifdef HAVE_UNISTD_H # include #endif #include #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; }