56b732058e
Signed-off-by: John Howard <jhoward@microsoft.com> If fixes an error in sameFsTime which was using `==` to compare two times. The correct way is to use go's built-in timea.Equals(timeb). In changes_windows, it uses sameFsTime to compare mTim of a `system.StatT` to allow TestChangesDirsMutated to operate correctly now. Note there is slight different between the Linux and Windows implementations of detecting changes. Due to https://github.com/moby/moby/issues/9874, and the fix at https://github.com/moby/moby/pull/11422, Linux does not consider a change to the directory time as a change. Windows on NTFS does. See https://github.com/moby/moby/pull/37982 for more information. The result in `TestChangesDirsMutated`, `dir3` is NOT considered a change in Linux, but IS considered a change on Windows. The test mutates dir3 to have a mtime of +1 second. With a handful of tests still outstanding, this change ports most of the unit tests under pkg/archive to Windows. It provides an implementation of `copyDir` in tests for Windows. To make a copy similar to Linux's `cp -a` while preserving timestamps and links to both valid and invalid targets, xcopy isn't sufficient. So I used robocopy, but had to circumvent certain exit codes that robocopy exits with which are warnings. Link to article describing this is in the code.
373 lines
6.9 KiB
Go
373 lines
6.9 KiB
Go
package archive // import "github.com/docker/docker/pkg/archive"
|
|
|
|
import (
|
|
"archive/tar"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/docker/docker/pkg/ioutils"
|
|
)
|
|
|
|
func TestApplyLayerInvalidFilenames(t *testing.T) {
|
|
for i, headers := range [][]*tar.Header{
|
|
{
|
|
{
|
|
Name: "../victim/dotdot",
|
|
Typeflag: tar.TypeReg,
|
|
Mode: 0644,
|
|
},
|
|
},
|
|
{
|
|
{
|
|
// Note the leading slash
|
|
Name: "/../victim/slash-dotdot",
|
|
Typeflag: tar.TypeReg,
|
|
Mode: 0644,
|
|
},
|
|
},
|
|
} {
|
|
if err := testBreakout("applylayer", "docker-TestApplyLayerInvalidFilenames", headers); err != nil {
|
|
t.Fatalf("i=%d. %v", i, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestApplyLayerInvalidHardlink(t *testing.T) {
|
|
for i, headers := range [][]*tar.Header{
|
|
{ // try reading victim/hello (../)
|
|
{
|
|
Name: "dotdot",
|
|
Typeflag: tar.TypeLink,
|
|
Linkname: "../victim/hello",
|
|
Mode: 0644,
|
|
},
|
|
},
|
|
{ // try reading victim/hello (/../)
|
|
{
|
|
Name: "slash-dotdot",
|
|
Typeflag: tar.TypeLink,
|
|
// Note the leading slash
|
|
Linkname: "/../victim/hello",
|
|
Mode: 0644,
|
|
},
|
|
},
|
|
{ // try writing victim/file
|
|
{
|
|
Name: "loophole-victim",
|
|
Typeflag: tar.TypeLink,
|
|
Linkname: "../victim",
|
|
Mode: 0755,
|
|
},
|
|
{
|
|
Name: "loophole-victim/file",
|
|
Typeflag: tar.TypeReg,
|
|
Mode: 0644,
|
|
},
|
|
},
|
|
{ // try reading victim/hello (hardlink, symlink)
|
|
{
|
|
Name: "loophole-victim",
|
|
Typeflag: tar.TypeLink,
|
|
Linkname: "../victim",
|
|
Mode: 0755,
|
|
},
|
|
{
|
|
Name: "symlink",
|
|
Typeflag: tar.TypeSymlink,
|
|
Linkname: "loophole-victim/hello",
|
|
Mode: 0644,
|
|
},
|
|
},
|
|
{ // Try reading victim/hello (hardlink, hardlink)
|
|
{
|
|
Name: "loophole-victim",
|
|
Typeflag: tar.TypeLink,
|
|
Linkname: "../victim",
|
|
Mode: 0755,
|
|
},
|
|
{
|
|
Name: "hardlink",
|
|
Typeflag: tar.TypeLink,
|
|
Linkname: "loophole-victim/hello",
|
|
Mode: 0644,
|
|
},
|
|
},
|
|
{ // Try removing victim directory (hardlink)
|
|
{
|
|
Name: "loophole-victim",
|
|
Typeflag: tar.TypeLink,
|
|
Linkname: "../victim",
|
|
Mode: 0755,
|
|
},
|
|
{
|
|
Name: "loophole-victim",
|
|
Typeflag: tar.TypeReg,
|
|
Mode: 0644,
|
|
},
|
|
},
|
|
} {
|
|
if err := testBreakout("applylayer", "docker-TestApplyLayerInvalidHardlink", headers); err != nil {
|
|
t.Fatalf("i=%d. %v", i, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestApplyLayerInvalidSymlink(t *testing.T) {
|
|
for i, headers := range [][]*tar.Header{
|
|
{ // try reading victim/hello (../)
|
|
{
|
|
Name: "dotdot",
|
|
Typeflag: tar.TypeSymlink,
|
|
Linkname: "../victim/hello",
|
|
Mode: 0644,
|
|
},
|
|
},
|
|
{ // try reading victim/hello (/../)
|
|
{
|
|
Name: "slash-dotdot",
|
|
Typeflag: tar.TypeSymlink,
|
|
// Note the leading slash
|
|
Linkname: "/../victim/hello",
|
|
Mode: 0644,
|
|
},
|
|
},
|
|
{ // try writing victim/file
|
|
{
|
|
Name: "loophole-victim",
|
|
Typeflag: tar.TypeSymlink,
|
|
Linkname: "../victim",
|
|
Mode: 0755,
|
|
},
|
|
{
|
|
Name: "loophole-victim/file",
|
|
Typeflag: tar.TypeReg,
|
|
Mode: 0644,
|
|
},
|
|
},
|
|
{ // try reading victim/hello (symlink, symlink)
|
|
{
|
|
Name: "loophole-victim",
|
|
Typeflag: tar.TypeSymlink,
|
|
Linkname: "../victim",
|
|
Mode: 0755,
|
|
},
|
|
{
|
|
Name: "symlink",
|
|
Typeflag: tar.TypeSymlink,
|
|
Linkname: "loophole-victim/hello",
|
|
Mode: 0644,
|
|
},
|
|
},
|
|
{ // try reading victim/hello (symlink, hardlink)
|
|
{
|
|
Name: "loophole-victim",
|
|
Typeflag: tar.TypeSymlink,
|
|
Linkname: "../victim",
|
|
Mode: 0755,
|
|
},
|
|
{
|
|
Name: "hardlink",
|
|
Typeflag: tar.TypeLink,
|
|
Linkname: "loophole-victim/hello",
|
|
Mode: 0644,
|
|
},
|
|
},
|
|
{ // try removing victim directory (symlink)
|
|
{
|
|
Name: "loophole-victim",
|
|
Typeflag: tar.TypeSymlink,
|
|
Linkname: "../victim",
|
|
Mode: 0755,
|
|
},
|
|
{
|
|
Name: "loophole-victim",
|
|
Typeflag: tar.TypeReg,
|
|
Mode: 0644,
|
|
},
|
|
},
|
|
} {
|
|
if err := testBreakout("applylayer", "docker-TestApplyLayerInvalidSymlink", headers); err != nil {
|
|
t.Fatalf("i=%d. %v", i, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestApplyLayerWhiteouts(t *testing.T) {
|
|
wd, err := ioutil.TempDir("", "graphdriver-test-whiteouts")
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer os.RemoveAll(wd)
|
|
|
|
base := []string{
|
|
".baz",
|
|
"bar/",
|
|
"bar/bax",
|
|
"bar/bay/",
|
|
"baz",
|
|
"foo/",
|
|
"foo/.abc",
|
|
"foo/.bcd/",
|
|
"foo/.bcd/a",
|
|
"foo/cde/",
|
|
"foo/cde/def",
|
|
"foo/cde/efg",
|
|
"foo/fgh",
|
|
"foobar",
|
|
}
|
|
|
|
type tcase struct {
|
|
change, expected []string
|
|
}
|
|
|
|
tcases := []tcase{
|
|
{
|
|
base,
|
|
base,
|
|
},
|
|
{
|
|
[]string{
|
|
".bay",
|
|
".wh.baz",
|
|
"foo/",
|
|
"foo/.bce",
|
|
"foo/.wh..wh..opq",
|
|
"foo/cde/",
|
|
"foo/cde/efg",
|
|
},
|
|
[]string{
|
|
".bay",
|
|
".baz",
|
|
"bar/",
|
|
"bar/bax",
|
|
"bar/bay/",
|
|
"foo/",
|
|
"foo/.bce",
|
|
"foo/cde/",
|
|
"foo/cde/efg",
|
|
"foobar",
|
|
},
|
|
},
|
|
{
|
|
[]string{
|
|
".bay",
|
|
".wh..baz",
|
|
".wh.foobar",
|
|
"foo/",
|
|
"foo/.abc",
|
|
"foo/.wh.cde",
|
|
"bar/",
|
|
},
|
|
[]string{
|
|
".bay",
|
|
"bar/",
|
|
"bar/bax",
|
|
"bar/bay/",
|
|
"foo/",
|
|
"foo/.abc",
|
|
"foo/.bce",
|
|
},
|
|
},
|
|
{
|
|
[]string{
|
|
".abc",
|
|
".wh..wh..opq",
|
|
"foobar",
|
|
},
|
|
[]string{
|
|
".abc",
|
|
"foobar",
|
|
},
|
|
},
|
|
}
|
|
|
|
for i, tc := range tcases {
|
|
l, err := makeTestLayer(tc.change)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err = UnpackLayer(wd, l, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
err = l.Close()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
paths, err := readDirContents(wd)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if !reflect.DeepEqual(tc.expected, paths) {
|
|
t.Fatalf("invalid files for layer %d: expected %q, got %q", i, tc.expected, paths)
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
func makeTestLayer(paths []string) (rc io.ReadCloser, err error) {
|
|
tmpDir, err := ioutil.TempDir("", "graphdriver-test-mklayer")
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer func() {
|
|
if err != nil {
|
|
os.RemoveAll(tmpDir)
|
|
}
|
|
}()
|
|
for _, p := range paths {
|
|
// Source files are always in Unix format. But we use filepath on
|
|
// creation to be platform agnostic.
|
|
if p[len(p)-1] == '/' {
|
|
if err = os.MkdirAll(filepath.Join(tmpDir, p), 0700); err != nil {
|
|
return
|
|
}
|
|
} else {
|
|
if err = ioutil.WriteFile(filepath.Join(tmpDir, p), nil, 0600); err != nil {
|
|
return
|
|
}
|
|
}
|
|
}
|
|
archive, err := Tar(tmpDir, Uncompressed)
|
|
if err != nil {
|
|
return
|
|
}
|
|
return ioutils.NewReadCloserWrapper(archive, func() error {
|
|
err := archive.Close()
|
|
os.RemoveAll(tmpDir)
|
|
return err
|
|
}), nil
|
|
}
|
|
|
|
func readDirContents(root string) ([]string, error) {
|
|
var files []string
|
|
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if path == root {
|
|
return nil
|
|
}
|
|
rel, err := filepath.Rel(root, path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if info.IsDir() {
|
|
rel = rel + string(filepath.Separator)
|
|
}
|
|
// Append in Unix semantics
|
|
files = append(files, filepath.ToSlash(rel))
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return files, nil
|
|
}
|