containerd/archive/tar_test.go
Michael Crosby c3872b848f Move RootPath to fs package
This moves the RootPath function out of the archive package and into the
fs package for external use.

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
2017-07-26 14:26:27 -04:00

904 lines
22 KiB
Go

// +build !windows
package archive
import (
"archive/tar"
"bytes"
"context"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"testing"
"time"
_ "crypto/sha256"
"github.com/containerd/containerd/fs"
"github.com/containerd/containerd/fs/fstest"
"github.com/pkg/errors"
)
const tarCmd = "tar"
// baseApplier creates a basic filesystem layout
// with multiple types of files for basic tests.
var baseApplier = fstest.Apply(
fstest.CreateDir("/etc/", 0755),
fstest.CreateFile("/etc/hosts", []byte("127.0.0.1 localhost"), 0644),
fstest.Link("/etc/hosts", "/etc/hosts.allow"),
fstest.CreateDir("/usr/local/lib", 0755),
fstest.CreateFile("/usr/local/lib/libnothing.so", []byte{0x00, 0x00}, 0755),
fstest.Symlink("libnothing.so", "/usr/local/lib/libnothing.so.2"),
fstest.CreateDir("/home", 0755),
fstest.CreateDir("/home/derek", 0700),
)
func TestUnpack(t *testing.T) {
requireTar(t)
if err := testApply(baseApplier); err != nil {
t.Fatalf("Test apply failed: %+v", err)
}
}
func TestBaseDiff(t *testing.T) {
requireTar(t)
if err := testBaseDiff(baseApplier); err != nil {
t.Fatalf("Test base diff failed: %+v", err)
}
}
func TestRelativeSymlinks(t *testing.T) {
breakoutLinks := []fstest.Applier{
fstest.Apply(
baseApplier,
fstest.Symlink("../other", "/home/derek/other"),
fstest.Symlink("../../etc", "/home/derek/etc"),
fstest.Symlink("up/../../other", "/home/derek/updown"),
),
fstest.Apply(
baseApplier,
fstest.Symlink("../../../breakout", "/home/derek/breakout"),
),
fstest.Apply(
baseApplier,
fstest.Symlink("../../breakout", "/breakout"),
),
fstest.Apply(
baseApplier,
fstest.Symlink("etc/../../upandout", "/breakout"),
),
fstest.Apply(
baseApplier,
fstest.Symlink("derek/../../../downandout", "/home/breakout"),
),
fstest.Apply(
baseApplier,
fstest.Symlink("/etc", "localetc"),
),
}
for _, bo := range breakoutLinks {
if err := testDiffApply(bo); err != nil {
t.Fatalf("Test apply failed: %+v", err)
}
}
}
func TestSymlinks(t *testing.T) {
links := [][2]fstest.Applier{
{
fstest.Apply(
fstest.CreateDir("/bin/", 0755),
fstest.CreateFile("/bin/superbinary", []byte{0x00, 0x00}, 0755),
fstest.Symlink("../bin/superbinary", "/bin/other1"),
),
fstest.Apply(
fstest.Remove("/bin/other1"),
fstest.Symlink("/bin/superbinary", "/bin/other1"),
fstest.Symlink("../bin/superbinary", "/bin/other2"),
fstest.Symlink("superbinary", "/bin/other3"),
),
},
{
fstest.Apply(
fstest.CreateDir("/bin/", 0755),
fstest.CreateDir("/sbin/", 0755),
fstest.CreateFile("/sbin/superbinary", []byte{0x00, 0x00}, 0755),
fstest.Symlink("/sbin/superbinary", "/bin/superbinary"),
fstest.Symlink("../bin/superbinary", "/bin/other1"),
),
fstest.Apply(
fstest.Remove("/bin/other1"),
fstest.Symlink("/bin/superbinary", "/bin/other1"),
fstest.Symlink("superbinary", "/bin/other2"),
),
},
{
fstest.Apply(
fstest.CreateDir("/bin/", 0755),
fstest.CreateDir("/sbin/", 0755),
fstest.CreateFile("/sbin/superbinary", []byte{0x00, 0x00}, 0755),
fstest.Symlink("../sbin/superbinary", "/bin/superbinary"),
fstest.Symlink("../bin/superbinary", "/bin/other1"),
),
fstest.Apply(
fstest.Remove("/bin/other1"),
fstest.Symlink("/bin/superbinary", "/bin/other1"),
),
},
{
fstest.Apply(
fstest.CreateDir("/bin/", 0755),
fstest.CreateFile("/bin/actualbinary", []byte{0x00, 0x00}, 0755),
fstest.Symlink("actualbinary", "/bin/superbinary"),
fstest.Symlink("../bin/superbinary", "/bin/other1"),
fstest.Symlink("superbinary", "/bin/other2"),
),
fstest.Apply(
fstest.Remove("/bin/other1"),
fstest.Remove("/bin/other2"),
fstest.Symlink("/bin/superbinary", "/bin/other1"),
fstest.Symlink("superbinary", "/bin/other2"),
),
},
{
fstest.Apply(
fstest.CreateDir("/bin/", 0755),
fstest.CreateFile("/bin/actualbinary", []byte{0x00, 0x00}, 0755),
fstest.Symlink("actualbinary", "/bin/myapp"),
),
fstest.Apply(
fstest.Remove("/bin/myapp"),
fstest.CreateDir("/bin/myapp", 0755),
),
},
}
for i, l := range links {
if err := testDiffApply(l[0], l[1]); err != nil {
t.Fatalf("Test[%d] apply failed: %+v", i+1, err)
}
}
}
func TestBreakouts(t *testing.T) {
tc := TarContext{}.WithUidGid(os.Getuid(), os.Getgid()).WithModTime(time.Now().UTC())
expected := "unbroken"
unbrokenCheck := func(root string) error {
b, err := ioutil.ReadFile(filepath.Join(root, "etc", "unbroken"))
if err != nil {
return errors.Wrap(err, "failed to read unbroken")
}
if string(b) != expected {
return errors.Errorf("/etc/unbroken: unexpected value %s, expected %s", b, expected)
}
return nil
}
errFileDiff := errors.New("files differ")
sameFile := func(f1, f2 string) func(string) error {
return func(root string) error {
p1, err := fs.RootPath(root, f1)
if err != nil {
return err
}
p2, err := fs.RootPath(root, f2)
if err != nil {
return err
}
s1, err := os.Stat(p1)
if err != nil {
return err
}
s2, err := os.Stat(p2)
if err != nil {
return err
}
if !os.SameFile(s1, s2) {
return errors.Wrapf(errFileDiff, "%#v and %#v", s1, s2)
}
return nil
}
}
notSameFile := func(f1, f2 string) func(string) error {
same := sameFile(f1, f2)
return func(root string) error {
err := same(root)
if err == nil {
return errors.New("files are the same, expected diff")
}
if errors.Cause(err) != errFileDiff {
return err
}
return nil
}
}
breakouts := []struct {
name string
w WriterToTar
apply fstest.Applier
validator func(string) error
}{
{
name: "SymlinkAbsolute",
w: TarAll(
tc.Dir("etc", 0755),
tc.Symlink("/etc", "localetc"),
tc.File("/localetc/unbroken", []byte(expected), 0644),
),
validator: unbrokenCheck,
},
{
name: "SymlinkUpAndOut",
w: TarAll(
tc.Dir("etc", 0755),
tc.Dir("dummy", 0755),
tc.Symlink("/dummy/../etc", "localetc"),
tc.File("/localetc/unbroken", []byte(expected), 0644),
),
validator: unbrokenCheck,
},
{
name: "SymlinkMultipleAbsolute",
w: TarAll(
tc.Dir("etc", 0755),
tc.Dir("dummy", 0755),
tc.Symlink("/etc", "/dummy/etc"),
tc.Symlink("/dummy/etc", "localetc"),
tc.File("/dummy/etc/unbroken", []byte(expected), 0644),
),
validator: unbrokenCheck,
},
{
name: "SymlinkMultipleRelative",
w: TarAll(
tc.Dir("etc", 0755),
tc.Dir("dummy", 0755),
tc.Symlink("/etc", "/dummy/etc"),
tc.Symlink("./dummy/etc", "localetc"),
tc.File("/dummy/etc/unbroken", []byte(expected), 0644),
),
validator: unbrokenCheck,
},
{
name: "SymlinkEmptyFile",
w: TarAll(
tc.Dir("etc", 0755),
tc.File("etc/emptied", []byte("notempty"), 0644),
tc.Symlink("/etc", "localetc"),
tc.File("/localetc/emptied", []byte{}, 0644),
),
validator: func(root string) error {
b, err := ioutil.ReadFile(filepath.Join(root, "etc", "emptied"))
if err != nil {
return errors.Wrap(err, "failed to read unbroken")
}
if len(b) > 0 {
return errors.Errorf("/etc/emptied: non-empty")
}
return nil
},
},
{
name: "HardlinkRelative",
w: TarAll(
tc.Dir("etc", 0770),
tc.File("/etc/passwd", []byte("inside"), 0644),
tc.Dir("breakouts", 0755),
tc.Symlink("../../etc", "breakouts/d1"),
tc.Link("/breakouts/d1/passwd", "breakouts/mypasswd"),
),
validator: sameFile("/breakouts/mypasswd", "/etc/passwd"),
},
{
name: "HardlinkDownAndOut",
w: TarAll(
tc.Dir("etc", 0770),
tc.File("/etc/passwd", []byte("inside"), 0644),
tc.Dir("breakouts", 0755),
tc.Dir("downandout", 0755),
tc.Symlink("../downandout/../../etc", "breakouts/d1"),
tc.Link("/breakouts/d1/passwd", "breakouts/mypasswd"),
),
validator: sameFile("/breakouts/mypasswd", "/etc/passwd"),
},
{
name: "HardlinkAbsolute",
w: TarAll(
tc.Dir("etc", 0770),
tc.File("/etc/passwd", []byte("inside"), 0644),
tc.Symlink("/etc", "localetc"),
tc.Link("/localetc/passwd", "localpasswd"),
),
validator: sameFile("localpasswd", "/etc/passwd"),
},
{
name: "HardlinkRelativeLong",
w: TarAll(
tc.Dir("etc", 0770),
tc.File("/etc/passwd", []byte("inside"), 0644),
tc.Symlink("../../../../../../../etc", "localetc"),
tc.Link("/localetc/passwd", "localpasswd"),
),
validator: sameFile("localpasswd", "/etc/passwd"),
},
{
name: "HardlinkRelativeUpAndOut",
w: TarAll(
tc.Dir("etc", 0770),
tc.File("/etc/passwd", []byte("inside"), 0644),
tc.Symlink("upandout/../../../etc", "localetc"),
tc.Link("/localetc/passwd", "localpasswd"),
),
validator: sameFile("localpasswd", "/etc/passwd"),
},
{
name: "HardlinkDirectRelative",
w: TarAll(
tc.Dir("etc", 0770),
tc.File("/etc/passwd", []byte("inside"), 0644),
tc.Link("../../../../../etc/passwd", "localpasswd"),
),
validator: sameFile("localpasswd", "/etc/passwd"),
},
{
name: "HardlinkDirectAbsolute",
w: TarAll(
tc.Dir("etc", 0770),
tc.File("/etc/passwd", []byte("inside"), 0644),
tc.Link("/etc/passwd", "localpasswd"),
),
validator: sameFile("localpasswd", "/etc/passwd"),
},
{
name: "HardlinkSymlinkRelative",
w: TarAll(
tc.Dir("etc", 0770),
tc.File("/etc/passwd", []byte("inside"), 0644),
tc.Symlink("../../../../../etc/passwd", "passwdlink"),
tc.Link("/passwdlink", "localpasswd"),
),
validator: sameFile("/localpasswd", "/etc/passwd"),
},
{
name: "HardlinkSymlinkAbsolute",
w: TarAll(
tc.Dir("etc", 0770),
tc.File("/etc/passwd", []byte("inside"), 0644),
tc.Symlink("/etc/passwd", "passwdlink"),
tc.Link("/passwdlink", "localpasswd"),
),
validator: sameFile("/localpasswd", "/etc/passwd"),
},
{
name: "SymlinkParentDirectory",
w: TarAll(
tc.Dir("etc", 0770),
tc.File("/etc/passwd", []byte("inside"), 0644),
tc.Symlink("/etc/", ".."),
tc.Link("/etc/passwd", "localpasswd"),
),
validator: sameFile("/localpasswd", "/etc/passwd"),
},
{
name: "SymlinkEmptyFilename",
w: TarAll(
tc.Dir("etc", 0770),
tc.File("/etc/passwd", []byte("inside"), 0644),
tc.Symlink("/etc/", ""),
tc.Link("/etc/passwd", "localpasswd"),
),
validator: sameFile("/localpasswd", "/etc/passwd"),
},
{
name: "SymlinkParentRelative",
w: TarAll(
tc.Dir("etc", 0770),
tc.File("/etc/passwd", []byte("inside"), 0644),
tc.Symlink("/etc/", "localetc/sub/.."),
tc.Link("/etc/passwd", "/localetc/localpasswd"),
),
validator: sameFile("/localetc/localpasswd", "/etc/passwd"),
},
{
name: "SymlinkSlashEnded",
w: TarAll(
tc.Dir("etc", 0770),
tc.File("/etc/passwd", []byte("inside"), 0644),
tc.Dir("localetc/", 0770),
tc.Link("/etc/passwd", "/localetc/localpasswd"),
),
validator: sameFile("/localetc/localpasswd", "/etc/passwd"),
},
{
name: "SymlinkOverrideDirectory",
apply: fstest.Apply(
fstest.CreateDir("/etc/", 0755),
fstest.CreateFile("/etc/passwd", []byte("inside"), 0644),
fstest.CreateDir("/localetc/", 0755),
),
w: TarAll(
tc.Symlink("/etc", "localetc"),
tc.Link("/etc/passwd", "/localetc/localpasswd"),
),
validator: sameFile("/localetc/localpasswd", "/etc/passwd"),
},
{
name: "SymlinkOverrideDirectoryRelative",
apply: fstest.Apply(
fstest.CreateDir("/etc/", 0755),
fstest.CreateFile("/etc/passwd", []byte("inside"), 0644),
fstest.CreateDir("/localetc/", 0755),
),
w: TarAll(
tc.Symlink("../../etc", "localetc"),
tc.Link("/etc/passwd", "/localetc/localpasswd"),
),
validator: sameFile("/localetc/localpasswd", "/etc/passwd"),
},
{
name: "DirectoryOverrideSymlink",
apply: fstest.Apply(
fstest.CreateDir("/etc/", 0755),
fstest.CreateFile("/etc/passwd", []byte("inside"), 0644),
fstest.Symlink("/etc", "localetc"),
),
w: TarAll(
tc.Dir("/localetc/", 0755),
tc.Link("/etc/passwd", "/localetc/localpasswd"),
),
validator: sameFile("/localetc/localpasswd", "/etc/passwd"),
},
{
name: "DirectoryOverrideSymlinkAndHardlink",
apply: fstest.Apply(
fstest.CreateDir("/etc/", 0755),
fstest.CreateFile("/etc/passwd", []byte("inside"), 0644),
fstest.Symlink("etc", "localetc"),
fstest.Link("/etc/passwd", "/localetc/localpasswd"),
),
w: TarAll(
tc.Dir("/localetc/", 0755),
tc.File("/localetc/localpasswd", []byte("different"), 0644),
),
validator: notSameFile("/localetc/localpasswd", "/etc/passwd"),
},
}
for _, bo := range breakouts {
t.Run(bo.name, makeWriterToTarTest(bo.w, bo.apply, bo.validator))
}
}
func TestDiffApply(t *testing.T) {
fstest.FSSuite(t, diffApplier{})
}
func TestApplyTar(t *testing.T) {
tc := TarContext{}.WithUidGid(os.Getuid(), os.Getgid()).WithModTime(time.Now().UTC())
directoriesExist := func(dirs ...string) func(string) error {
return func(root string) error {
for _, d := range dirs {
p, err := fs.RootPath(root, d)
if err != nil {
return err
}
if _, err := os.Stat(p); err != nil {
return errors.Wrapf(err, "failure checking existance for %v", d)
}
}
return nil
}
}
tests := []struct {
name string
w WriterToTar
apply fstest.Applier
validator func(string) error
}{
{
name: "DirectoryCreation",
apply: fstest.Apply(
fstest.CreateDir("/etc/", 0755),
),
w: TarAll(
tc.Dir("/etc/subdir", 0755),
tc.Dir("/etc/subdir2/", 0755),
tc.Dir("/etc/subdir2/more", 0755),
tc.Dir("/other/noparent-1/1", 0755),
tc.Dir("/other/noparent-2/2/", 0755),
),
validator: directoriesExist(
"etc/subdir",
"etc/subdir2",
"etc/subdir2/more",
"other/noparent-1/1",
"other/noparent-2/2",
),
},
}
for _, at := range tests {
t.Run(at.name, makeWriterToTarTest(at.w, at.apply, at.validator))
}
}
func testApply(a fstest.Applier) error {
td, err := ioutil.TempDir("", "test-apply-")
if err != nil {
return errors.Wrap(err, "failed to create temp dir")
}
defer os.RemoveAll(td)
dest, err := ioutil.TempDir("", "test-apply-dest-")
if err != nil {
return errors.Wrap(err, "failed to create temp dir")
}
defer os.RemoveAll(dest)
if err := a.Apply(td); err != nil {
return errors.Wrap(err, "failed to apply filesystem changes")
}
tarArgs := []string{"c", "-C", td}
names, err := readDirNames(td)
if err != nil {
return errors.Wrap(err, "failed to read directory names")
}
tarArgs = append(tarArgs, names...)
cmd := exec.Command(tarCmd, tarArgs...)
arch, err := cmd.StdoutPipe()
if err != nil {
return errors.Wrap(err, "failed to create stdout pipe")
}
if err := cmd.Start(); err != nil {
return errors.Wrap(err, "failed to start command")
}
if _, err := Apply(context.Background(), dest, arch); err != nil {
return errors.Wrap(err, "failed to apply tar stream")
}
return fstest.CheckDirectoryEqual(td, dest)
}
func testBaseDiff(a fstest.Applier) error {
td, err := ioutil.TempDir("", "test-base-diff-")
if err != nil {
return errors.Wrap(err, "failed to create temp dir")
}
defer os.RemoveAll(td)
dest, err := ioutil.TempDir("", "test-base-diff-dest-")
if err != nil {
return errors.Wrap(err, "failed to create temp dir")
}
defer os.RemoveAll(dest)
if err := a.Apply(td); err != nil {
return errors.Wrap(err, "failed to apply filesystem changes")
}
arch := Diff(context.Background(), "", td)
cmd := exec.Command(tarCmd, "x", "-C", dest)
cmd.Stdin = arch
if err := cmd.Run(); err != nil {
return errors.Wrap(err, "tar command failed")
}
return fstest.CheckDirectoryEqual(td, dest)
}
func testDiffApply(appliers ...fstest.Applier) error {
td, err := ioutil.TempDir("", "test-diff-apply-")
if err != nil {
return errors.Wrap(err, "failed to create temp dir")
}
defer os.RemoveAll(td)
dest, err := ioutil.TempDir("", "test-diff-apply-dest-")
if err != nil {
return errors.Wrap(err, "failed to create temp dir")
}
defer os.RemoveAll(dest)
for _, a := range appliers {
if err := a.Apply(td); err != nil {
return errors.Wrap(err, "failed to apply filesystem changes")
}
}
// Apply base changes before diff
if len(appliers) > 1 {
for _, a := range appliers[:len(appliers)-1] {
if err := a.Apply(dest); err != nil {
return errors.Wrap(err, "failed to apply base filesystem changes")
}
}
}
diffBytes, err := ioutil.ReadAll(Diff(context.Background(), dest, td))
if err != nil {
return errors.Wrap(err, "failed to create diff")
}
if _, err := Apply(context.Background(), dest, bytes.NewReader(diffBytes)); err != nil {
return errors.Wrap(err, "failed to apply tar stream")
}
return fstest.CheckDirectoryEqual(td, dest)
}
func makeWriterToTarTest(wt WriterToTar, a fstest.Applier, validate func(string) error) func(*testing.T) {
return func(t *testing.T) {
td, err := ioutil.TempDir("", "test-writer-to-tar-")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
}
defer os.RemoveAll(td)
if a != nil {
if err := a.Apply(td); err != nil {
t.Fatalf("Failed to apply filesystem to directory: %v", err)
}
}
tr := TarFromWriterTo(wt)
if _, err := Apply(context.Background(), td, tr); err != nil {
t.Fatalf("Failed to apply tar: %v", err)
}
if validate != nil {
if err := validate(td); err != nil {
t.Errorf("Validation failed: %v", err)
}
}
}
}
type diffApplier struct{}
func (d diffApplier) TestContext(ctx context.Context) (context.Context, func(), error) {
base, err := ioutil.TempDir("", "test-diff-apply-")
if err != nil {
return ctx, nil, errors.Wrap(err, "failed to create temp dir")
}
return context.WithValue(ctx, d, base), func() {
os.RemoveAll(base)
}, nil
}
func (d diffApplier) Apply(ctx context.Context, a fstest.Applier) (string, func(), error) {
base := ctx.Value(d).(string)
applyCopy, err := ioutil.TempDir("", "test-diffapply-apply-copy-")
if err != nil {
return "", nil, errors.Wrap(err, "failed to create temp dir")
}
defer os.RemoveAll(applyCopy)
if err = fs.CopyDir(applyCopy, base); err != nil {
return "", nil, errors.Wrap(err, "failed to copy base")
}
if err := a.Apply(applyCopy); err != nil {
return "", nil, errors.Wrap(err, "failed to apply changes to copy of base")
}
diffBytes, err := ioutil.ReadAll(Diff(ctx, base, applyCopy))
if err != nil {
return "", nil, errors.Wrap(err, "failed to create diff")
}
if _, err = Apply(ctx, base, bytes.NewReader(diffBytes)); err != nil {
return "", nil, errors.Wrap(err, "failed to apply tar stream")
}
return base, nil, nil
}
func readDirNames(p string) ([]string, error) {
fis, err := ioutil.ReadDir(p)
if err != nil {
return nil, err
}
names := make([]string, len(fis))
for i, fi := range fis {
names[i] = fi.Name()
}
return names, nil
}
func requireTar(t *testing.T) {
if _, err := exec.LookPath(tarCmd); err != nil {
t.Skipf("%s not found, skipping", tarCmd)
}
}
// WriterToTar is an type which writes to a tar writer
type WriterToTar interface {
WriteTo(*tar.Writer) error
}
type writerToFn func(*tar.Writer) error
func (w writerToFn) WriteTo(tw *tar.Writer) error {
return w(tw)
}
// TarAll creates a WriterToTar which calls all the provided writers
// in the order in which they are provided.
func TarAll(wt ...WriterToTar) WriterToTar {
return writerToFn(func(tw *tar.Writer) error {
for _, w := range wt {
if err := w.WriteTo(tw); err != nil {
return err
}
}
return nil
})
}
// TarFromWriterTo is used to create a tar stream from a tar record
// creator. This can be used to manifacture more specific tar records
// which allow testing specific tar cases which may be encountered
// by the untar process.
func TarFromWriterTo(wt WriterToTar) io.ReadCloser {
r, w := io.Pipe()
go func() {
tw := tar.NewWriter(w)
if err := wt.WriteTo(tw); err != nil {
w.CloseWithError(err)
return
}
w.CloseWithError(tw.Close())
}()
return r
}
// TarContext is used to create tar records
type TarContext struct {
Uid int
Gid int
// ModTime sets the modtimes for all files, if nil the current time
// is used for each file when it was written
ModTime *time.Time
Xattrs map[string]string
}
func (tc TarContext) newHeader(mode os.FileMode, name, link string, size int64) *tar.Header {
ti := tarInfo{
name: name,
mode: mode,
size: size,
modt: tc.ModTime,
hdr: &tar.Header{
Uid: tc.Uid,
Gid: tc.Gid,
Xattrs: tc.Xattrs,
},
}
if mode&os.ModeSymlink == 0 && link != "" {
ti.hdr.Typeflag = tar.TypeLink
ti.hdr.Linkname = link
}
hdr, err := tar.FileInfoHeader(ti, link)
if err != nil {
// Only returns an error on bad input mode
panic(err)
}
return hdr
}
type tarInfo struct {
name string
mode os.FileMode
size int64
modt *time.Time
hdr *tar.Header
}
func (ti tarInfo) Name() string {
return ti.name
}
func (ti tarInfo) Size() int64 {
return ti.size
}
func (ti tarInfo) Mode() os.FileMode {
return ti.mode
}
func (ti tarInfo) ModTime() time.Time {
if ti.modt != nil {
return *ti.modt
}
return time.Now().UTC()
}
func (ti tarInfo) IsDir() bool {
return (ti.mode & os.ModeDir) != 0
}
func (ti tarInfo) Sys() interface{} {
return ti.hdr
}
func (tc TarContext) WithUidGid(uid, gid int) TarContext {
ntc := tc
ntc.Uid = uid
ntc.Gid = gid
return ntc
}
func (tc TarContext) WithModTime(modtime time.Time) TarContext {
ntc := tc
ntc.ModTime = &modtime
return ntc
}
// WithXattrs adds these xattrs to all files, merges with any
// previously added xattrs
func (tc TarContext) WithXattrs(xattrs map[string]string) TarContext {
ntc := tc
if ntc.Xattrs == nil {
ntc.Xattrs = map[string]string{}
}
for k, v := range xattrs {
ntc.Xattrs[k] = v
}
return ntc
}
func (tc TarContext) File(name string, content []byte, perm os.FileMode) WriterToTar {
return writerToFn(func(tw *tar.Writer) error {
return writeHeaderAndContent(tw, tc.newHeader(perm, name, "", int64(len(content))), content)
})
}
func (tc TarContext) Dir(name string, perm os.FileMode) WriterToTar {
return writerToFn(func(tw *tar.Writer) error {
return writeHeaderAndContent(tw, tc.newHeader(perm|os.ModeDir, name, "", 0), nil)
})
}
func (tc TarContext) Symlink(oldname, newname string) WriterToTar {
return writerToFn(func(tw *tar.Writer) error {
return writeHeaderAndContent(tw, tc.newHeader(0777|os.ModeSymlink, newname, oldname, 0), nil)
})
}
func (tc TarContext) Link(oldname, newname string) WriterToTar {
return writerToFn(func(tw *tar.Writer) error {
return writeHeaderAndContent(tw, tc.newHeader(0777, newname, oldname, 0), nil)
})
}
func writeHeaderAndContent(tw *tar.Writer, h *tar.Header, b []byte) error {
if h.Size != int64(len(b)) {
return errors.New("bad content length")
}
if err := tw.WriteHeader(h); err != nil {
return err
}
if len(b) > 0 {
if _, err := tw.Write(b); err != nil {
return err
}
}
return nil
}