Files
android_bootable_recovery/libtar/extract.c
Ethan Yonker 472f506817 Improve progress bar handling for backup / restore / image flash
The progress bar will now be updated during image backups, restores
and during image flashing (except for sparse images which will require
significant changes to libsparse, and except for mtd nand using
flash_utils).

The progress bar will now be updated mid-file for file systems (tar) so
the user will see changes even during large file backup / restore.

Add a new progress tracking class to simplify handling of progress bar
updates. The class will only update the progress bar 5 times a second to
reduce the CPU load from updating the GUI frequently which does affect
backup times.

Change-Id: Iff382faef3df1f86604af336c1a8ce8993cd12c5
2016-03-31 15:44:24 +01:00

603 lines
11 KiB
C

/*
** 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 <stdio.h>
#include <string.h>
#include <sys/param.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <utime.h>
#ifdef STDC_HEADERS
# include <stdlib.h>
#endif
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#ifdef HAVE_SELINUX
# include "selinux/selinux.h"
#endif
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;
}
#ifdef HAVE_SELINUX
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);
}
#endif
#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 %lld 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
{
#ifdef DEBUG
puts(" *** using existing directory");
#endif
return 1;
}
}
else
{
#ifdef DEBUG
perror("mkdir()");
#endif
return -1;
}
}
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;
}