tar: improve file status handling on extract

by re-ordering when chmod/chown is done, only a list of directories (not
all files) need be kept for fixing mtime.

this also fixes an issue where set-user-id files in a tar may not work. chmod
is done before chown and before the file is written. if ownership changes, or
the file is being written as a normal user, the setuid bit would be cleared.

also fixes ownership of symbolic links. previously a chown() was called,
which would change the ownership of the link target. lchown() is now
used for symbolic links.

renamed all ent, ent* functions to dir* as it better describes what they
do.

use timespec/utimensat instead of timeval/utimes to get AT_SYMLINK_NOFOLLOW
This commit is contained in:
Brad Barden 2015-11-18 20:50:50 -06:00 committed by sin
parent 85a9254d3a
commit 211c565b3d

69
tar.c
View File

@ -48,12 +48,12 @@ struct header {
char prefix[155]; char prefix[155];
}; };
static struct ent { static struct dirtime {
char *name; char *name;
time_t mtime; time_t mtime;
} *ents; } *dirtimes;
static size_t entslen; static size_t dirtimeslen;
static int tarfd; static int tarfd;
static ino_t tarinode; static ino_t tarinode;
@ -72,20 +72,20 @@ static const char *filtertools[] = {
}; };
static void static void
pushent(char *name, time_t mtime) pushdirtime(char *name, time_t mtime)
{ {
ents = reallocarray(ents, entslen + 1, sizeof(*ents)); dirtimes = reallocarray(dirtimes, dirtimeslen + 1, sizeof(*dirtimes));
ents[entslen].name = strdup(name); dirtimes[dirtimeslen].name = strdup(name);
ents[entslen].mtime = mtime; dirtimes[dirtimeslen].mtime = mtime;
entslen++; dirtimeslen++;
} }
static struct ent * static struct dirtime *
popent(void) popdirtime(void)
{ {
if (entslen) { if (dirtimeslen) {
entslen--; dirtimeslen--;
return &ents[entslen]; return &dirtimes[dirtimeslen];
} }
return NULL; return NULL;
} }
@ -254,6 +254,7 @@ unarchive(char *fname, ssize_t l, char b[BLKSIZ])
long mode, major, minor, type, mtime, uid, gid; long mode, major, minor, type, mtime, uid, gid;
struct header *h = (struct header *)b; struct header *h = (struct header *)b;
int fd = -1; int fd = -1;
struct timespec times[2];
if (!mflag && ((mtime = strtol(h->mtime, &p, 8)) < 0 || *p != '\0')) if (!mflag && ((mtime = strtol(h->mtime, &p, 8)) < 0 || *p != '\0'))
eprintf("strtol %s: invalid number\n", h->mtime); eprintf("strtol %s: invalid number\n", h->mtime);
@ -273,8 +274,6 @@ unarchive(char *fname, ssize_t l, char b[BLKSIZ])
fd = open(fname, O_WRONLY | O_TRUNC | O_CREAT, 0600); fd = open(fname, O_WRONLY | O_TRUNC | O_CREAT, 0600);
if (fd < 0) if (fd < 0)
eprintf("open %s:", fname); eprintf("open %s:", fname);
if (fchmod(fd, mode) < 0)
eprintf("fchmod %s:", fname);
break; break;
case HARDLINK: case HARDLINK:
case SYMLINK: case SYMLINK:
@ -290,6 +289,7 @@ unarchive(char *fname, ssize_t l, char b[BLKSIZ])
eprintf("strtol %s: invalid number\n", h->mode); eprintf("strtol %s: invalid number\n", h->mode);
if (mkdir(fname, (mode_t)mode) < 0 && errno != EEXIST) if (mkdir(fname, (mode_t)mode) < 0 && errno != EEXIST)
eprintf("mkdir %s:", fname); eprintf("mkdir %s:", fname);
pushdirtime(fname, mtime);
break; break;
case CHARDEV: case CHARDEV:
case BLOCKDEV: case BLOCKDEV:
@ -317,8 +317,6 @@ unarchive(char *fname, ssize_t l, char b[BLKSIZ])
eprintf("strtol %s: invalid number\n", h->uid); eprintf("strtol %s: invalid number\n", h->uid);
if ((gid = strtol(h->gid, &p, 8)) < 0 || *p != '\0') if ((gid = strtol(h->gid, &p, 8)) < 0 || *p != '\0')
eprintf("strtol %s: invalid number\n", h->gid); eprintf("strtol %s: invalid number\n", h->gid);
if (!getuid() && chown(fname, uid, gid))
weprintf("chown %s:", fname);
if (fd != -1) { if (fd != -1) {
for (; l > 0; l -= BLKSIZ) for (; l > 0; l -= BLKSIZ)
@ -327,7 +325,20 @@ unarchive(char *fname, ssize_t l, char b[BLKSIZ])
close(fd); close(fd);
} }
pushent(fname, mtime); times[0].tv_sec = times[1].tv_sec = mtime;
times[0].tv_nsec = times[1].tv_nsec = 0;
if (!mflag && utimensat(AT_FDCWD, fname, times, AT_SYMLINK_NOFOLLOW) < 0)
weprintf("utimensat %s:\n", fname);
if (h->type == SYMLINK) {
if (!getuid() && lchown(fname, uid, gid))
weprintf("lchown %s:\n", fname);
} else {
if (!getuid() && chown(fname, uid, gid))
weprintf("chown %s:\n", fname);
if (chmod(fname, mode) < 0)
eprintf("fchmod %s:\n", fname);
}
return 0; return 0;
} }
@ -420,9 +431,9 @@ static void
xt(int argc, char *argv[], int mode) xt(int argc, char *argv[], int mode)
{ {
char b[BLKSIZ], fname[256 + 1], *p; char b[BLKSIZ], fname[256 + 1], *p;
struct timeval times[2]; struct timespec times[2];
struct header *h = (struct header *)b; struct header *h = (struct header *)b;
struct ent *ent; struct dirtime *dirtime;
long size; long size;
int i, n; int i, n;
int (*fn)(char *, ssize_t, char[BLKSIZ]) = (mode == 'x') ? unarchive : print; int (*fn)(char *, ssize_t, char[BLKSIZ]) = (mode == 'x') ? unarchive : print;
@ -463,16 +474,16 @@ xt(int argc, char *argv[], int mode)
puts(fname); puts(fname);
} }
if (!mflag) { if (mode == 'x' && !mflag) {
while ((ent = popent())) { while ((dirtime = popdirtime())) {
times[0].tv_sec = times[1].tv_sec = ent->mtime; times[0].tv_sec = times[1].tv_sec = dirtime->mtime;
times[0].tv_usec = times[1].tv_usec = 0; times[0].tv_nsec = times[1].tv_nsec = 0;
if (utimes(ent->name, times) < 0) if (utimensat(AT_FDCWD, dirtime->name, times, 0) < 0)
weprintf("utimes %s:", ent->name); eprintf("utimensat %s:", fname);
free(ent->name); free(dirtime->name);
} }
free(ents); free(dirtimes);
ents = NULL; dirtimes = NULL;
} }
} }