commit 211c565b3d560ad16ef20359d6efe37d0783c583
Author:     Brad Barden <[email protected]>
AuthorDate: Wed Nov 18 20:50:50 2015 -0600
Commit:     sin <[email protected]>
CommitDate: Fri Nov 20 09:58:38 2015 +0000

    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

diff --git a/tar.c b/tar.c
index de58247..e8dd26a 100644
--- a/tar.c
+++ b/tar.c
@@ -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;
@@ -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;
 }
@@ -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);
@@ -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:
@@ -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:
@@ -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)
@@ -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;
 }
 
@@ -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;
@@ -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;
        }
 }
 

Reply via email to