[PATCH 2/2] tar: improve file status handling on extract

From: Brad Barden <brad_AT_13os.net>
Date: Wed, 18 Nov 2015 20:50:50 -0600

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
---
 tar.c | 69 +++++++++++++++++++++++++++++++++++++++----------------------------
 1 file changed, 40 insertions(+), 29 deletions(-)
diff --git a/tar.c b/tar.c
index de58247..e8dd26a 100644
--- a/tar.c
+++ b/tar.c
_AT_@ -48,12 +48,12 @@ struct header {
 	char prefix[155];
 };
 
-static struct ent {
+static struct dirtime {
 	char *name;
 	time_t mtime;
-} *ents;
+} *dirtimes;
 
-static size_t entslen;
+static size_t dirtimeslen;
 
 static int tarfd;
 static ino_t tarinode;
_AT_@ -72,20 +72,20 @@ static const char *filtertools[] = {
 };
 
 static void
-pushent(char *name, time_t mtime)
+pushdirtime(char *name, time_t mtime)
 {
-	ents = reallocarray(ents, entslen + 1, sizeof(*ents));
-	ents[entslen].name = strdup(name);
-	ents[entslen].mtime = mtime;
-	entslen++;
+	dirtimes = reallocarray(dirtimes, dirtimeslen + 1, sizeof(*dirtimes));
+	dirtimes[dirtimeslen].name = strdup(name);
+	dirtimes[dirtimeslen].mtime = mtime;
+	dirtimeslen++;
 }
 
-static struct ent *
-popent(void)
+static struct dirtime *
+popdirtime(void)
 {
-	if (entslen) {
-		entslen--;
-		return &ents[entslen];
+	if (dirtimeslen) {
+		dirtimeslen--;
+		return &dirtimes[dirtimeslen];
 	}
 	return NULL;
 }
_AT_@ -254,6 +254,7 @@ unarchive(char *fname, ssize_t l, char b[BLKSIZ])
 	long mode, major, minor, type, mtime, uid, gid;
 	struct header *h = (struct header *)b;
 	int fd = -1;
+	struct timespec times[2];
 
 	if (!mflag && ((mtime = strtol(h->mtime, &p, 8)) < 0 || *p != '\0'))
 		eprintf("strtol %s: invalid number\n", h->mtime);
_AT_@ -273,8 +274,6 @@ unarchive(char *fname, ssize_t l, char b[BLKSIZ])
 		fd = open(fname, O_WRONLY | O_TRUNC | O_CREAT, 0600);
 		if (fd < 0)
 			eprintf("open %s:", fname);
-		if (fchmod(fd, mode) < 0)
-			eprintf("fchmod %s:", fname);
 		break;
 	case HARDLINK:
 	case SYMLINK:
_AT_@ -290,6 +289,7 @@ unarchive(char *fname, ssize_t l, char b[BLKSIZ])
 			eprintf("strtol %s: invalid number\n", h->mode);
 		if (mkdir(fname, (mode_t)mode) < 0 && errno != EEXIST)
 			eprintf("mkdir %s:", fname);
+		pushdirtime(fname, mtime);
 		break;
 	case CHARDEV:
 	case BLOCKDEV:
_AT_@ -317,8 +317,6 @@ unarchive(char *fname, ssize_t l, char b[BLKSIZ])
 		eprintf("strtol %s: invalid number\n", h->uid);
 	if ((gid = strtol(h->gid, &p, 8)) < 0 || *p != '\0')
 		eprintf("strtol %s: invalid number\n", h->gid);
-	if (!getuid() && chown(fname, uid, gid))
-		weprintf("chown %s:", fname);
 
 	if (fd != -1) {
 		for (; l > 0; l -= BLKSIZ)
_AT_@ -327,7 +325,20 @@ unarchive(char *fname, ssize_t l, char b[BLKSIZ])
 		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;
 }
 
_AT_@ -420,9 +431,9 @@ static void
 xt(int argc, char *argv[], int mode)
 {
 	char b[BLKSIZ], fname[256 + 1], *p;
-	struct timeval times[2];
+	struct timespec times[2];
 	struct header *h = (struct header *)b;
-	struct ent *ent;
+	struct dirtime *dirtime;
 	long size;
 	int i, n;
 	int (*fn)(char *, ssize_t, char[BLKSIZ]) = (mode == 'x') ? unarchive : print;
_AT_@ -463,16 +474,16 @@ xt(int argc, char *argv[], int mode)
 			puts(fname);
 	}
 
-	if (!mflag) {
-		while ((ent = popent())) {
-			times[0].tv_sec = times[1].tv_sec = ent->mtime;
-			times[0].tv_usec = times[1].tv_usec = 0;
-			if (utimes(ent->name, times) < 0)
-				weprintf("utimes %s:", ent->name);
-			free(ent->name);
+	if (mode == 'x' && !mflag) {
+		while ((dirtime = popdirtime())) {
+			times[0].tv_sec = times[1].tv_sec = dirtime->mtime;
+			times[0].tv_nsec = times[1].tv_nsec = 0;
+			if (utimensat(AT_FDCWD, dirtime->name, times, 0) < 0)
+				eprintf("utimensat %s:", fname);
+			free(dirtime->name);
 		}
-		free(ents);
-		ents = NULL;
+		free(dirtimes);
+		dirtimes = NULL;
 	}
 }
 
-- 
2.3.6
--PEIAKu/WMn1b1Hv9--
Received on Mon Sep 17 2001 - 00:00:00 CEST

This archive was generated by hypermail 2.3.0 : Thu Nov 19 2015 - 10:24:10 CET