7

I have a tarball and the files it contains do not have the right user and group ownership. Files are owned jenkins:jenkins and I'd like them to be owned naftuli:othergroup.

Is there a way for me to edit the tarball in place so that the files it contains become owned by naftuli:othergroup?

The problem I'm facing is that at the time of the tarball creation, the jenkins user doesn't have permissions to chown things to different users and groups. At the time of extraction, I will have permissions to create those files as the users mentioned, so I need to edit the user ids and group ids within the tarball.

4 Answers 4

6

Archive::Tar or similar software would be one method.

% touch foo  
% tar cvf x foo
foo
% tar tvf x          
-rw-rw-r--  1 jdoe12   jdoe12           0 May  6 20:36 foo
% perl -MArchive::Tar -e '$t=Archive::Tar->new;$t->read("x");$t->chown("foo","root");$t->write("y")'
% tar tvf y
-rw-rw-r--  1 root     jdoe12           0 May  6 20:36 foo
% 
0
6

You cannot change permissions explicitly on extraction, but you can put the preferred identities into the tarball on creation (even when not running as root).

tar cvf /tmp/tarball.tar --owner=naftuli --group othergroup files...

If the source system doesn't know your account details you can suffix the names with the preferred uid and gid (e.g. --owner=naftuli:3000).

Alternatively, if you already have a tarball you can extract the files created by jenkins as yourself (naftuli:othergroup) and because you are not root the files will become owned by you.

5
  • Is there a way to actually edit the tarball? This is a good solution. May 6, 2016 at 19:53
  • @NaftuliTzviKay I don't know any way of editing the tarball in place. The Perl solution offered to you can read and write the tarball as a stream. But like I say, if you extract the tarball and you're not root then the files will be owned by you (rather than by the original writer) anyway. May 6, 2016 at 21:45
  • Note: --user seems to be Linux-only Sep 25, 2018 at 22:22
  • Can't find the --user option in GNU tar 1.34, but there is --owner=NAME[:UID]. Sep 21, 2021 at 18:36
  • @StefanvandenAkker thank you for picking that up. I wonder how I got --user? I'll amend the answer now Sep 22, 2021 at 10:33
2

Note the answer using Archive::Tar reads the archive into memory. Using Python's tarfile module, it is possible to avoid this. Unfortunately, I haven't managed to stream from stdin yet, as extractfile tries to seek.

#!/usr/bin/env python3
import tarfile
import sys

with tarfile.open(name=sys.argv[1], mode="r") as in_tar, \
    tarfile.open(fileobj=sys.stdout.buffer, mode="w|") as out_tar:
    for member in in_tar.getmembers():
        member.uname = member.gname = "root"
        if member.isfile():
            with in_tar.extractfile(member) as file:
                out_tar.addfile(member, file)
        else:
            out_tar.addfile(member)

Update: The following Go program does work on streams:

package main

import (
        "archive/tar"
        "io"
        "log"
        "os"
)

func main() {
        tr := tar.NewReader(os.Stdin)
        tw := tar.NewWriter(os.Stdout)

        for {
                hdr, err := tr.Next()
                if err == io.EOF {
                        break
                } else if err != nil {
                        log.Fatal(err)
                }

                hdr.Uname, hdr.Gname = "root", "root"
                if err := tw.WriteHeader(hdr); err != nil {
                        log.Fatal(err)
                }

                if hdr.Typeflag == tar.TypeReg {
                        if _, err := io.Copy(tw, tr); err != nil {
                                log.Fatal(err)
                        }
                }
        }

        if err := tw.Close(); err != nil {
                log.Fatal(err)
        }
}
2
  • 1
    The problem in your code is that you use getmembers(). That function first reads the whole archive before returning the list of members. This means that to read a file in the archive, extractfile would have to read backwards. The solution is, to iterate through the members one-by-one without reading in the whole archive at once. You do this by using for member in in_tar instead. Then you can open in_tar with mode="r|*" and things will work as expected.
    – josch
    Dec 31, 2019 at 9:39
  • The implementation in go is still superior because in contrast to Python's tarfile module, archive/tar from go supports extended attributes in the PAX header.
    – josch
    Jan 13, 2020 at 20:14
1

If you're using macOS, its version of tar has an "mtree" extension that can do this. The resulting tar file can be extracted in Linux (i.e., it's portable).

From the EXAMPLES section of the macOS tar manpage:

$ cat input.mtree
#mtree
usr/bin uid=0 gid=0 mode=0755 type=dir
usr/bin/ls uid=0 gid=0 mode=0755 type=file content=myls
$ tar -cvf output.tar @input.mtree

The #mtree comment at the top of the input file is required. There are also uname and gname options (instead of uid/gid) for setting symbolic names instead of numeric values. See man mtree 5 for a list of all keywords.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .