2013-10-31 19:57:45 -04:00
|
|
|
package archive
|
2013-03-11 08:42:36 -04:00
|
|
|
|
|
|
|
import (
|
2015-05-01 18:01:10 -04:00
|
|
|
"archive/tar"
|
2013-06-14 13:47:49 -04:00
|
|
|
"bytes"
|
|
|
|
"fmt"
|
2013-03-29 07:42:17 -04:00
|
|
|
"io"
|
2013-03-11 08:42:36 -04:00
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
2014-10-20 15:35:48 -04:00
|
|
|
"path/filepath"
|
2016-02-12 16:58:57 -05:00
|
|
|
"runtime"
|
2014-12-03 15:36:57 -05:00
|
|
|
"strings"
|
2013-03-11 08:42:36 -04:00
|
|
|
"testing"
|
2013-03-29 17:09:25 -04:00
|
|
|
"time"
|
2017-04-10 16:47:41 -04:00
|
|
|
|
Remove pkg/testutil/assert in favor of testify
I noticed that we're using a homegrown package for assertions. The
functions are extremely similar to testify, but with enough slight
differences to be confusing (for example, Equal takes its arguments in a
different order). We already vendor testify, and it's used in a few
places by tests.
I also found some problems with pkg/testutil/assert. For example, the
NotNil function seems to be broken. It checks the argument against
"nil", which only works for an interface. If you pass in a nil map or
slice, the equality check will fail.
In the interest of avoiding NIH, I'm proposing replacing
pkg/testutil/assert with testify. The test code looks almost the same,
but we avoid the confusion of having two similar but slightly different
assertion packages, and having to maintain our own package instead of
using a commonly-used one.
In the process, I found a few places where the tests should halt if an
assertion fails, so I've made those cases (that I noticed) use "require"
instead of "assert", and I've vendored the "require" package from
testify alongside the already-present "assert" package.
Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
2017-04-13 18:45:37 -04:00
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/stretchr/testify/require"
|
2013-03-11 08:42:36 -04:00
|
|
|
)
|
|
|
|
|
2016-02-12 16:58:57 -05:00
|
|
|
var tmp string
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
tmp = "/tmp/"
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
tmp = os.Getenv("TEMP") + `\`
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-24 11:53:41 -04:00
|
|
|
var defaultArchiver = NewDefaultArchiver()
|
|
|
|
|
|
|
|
func defaultTarUntar(src, dst string) error {
|
|
|
|
return defaultArchiver.TarUntar(src, dst)
|
2015-04-08 07:29:32 -04:00
|
|
|
}
|
|
|
|
|
2017-05-24 11:53:41 -04:00
|
|
|
func defaultUntarPath(src, dst string) error {
|
|
|
|
return defaultArchiver.UntarPath(src, dst)
|
2015-04-08 07:29:32 -04:00
|
|
|
}
|
|
|
|
|
2017-05-24 11:53:41 -04:00
|
|
|
func defaultCopyFileWithTar(src, dst string) (err error) {
|
|
|
|
return defaultArchiver.CopyFileWithTar(src, dst)
|
2015-04-08 07:29:32 -04:00
|
|
|
}
|
|
|
|
|
2017-05-24 11:53:41 -04:00
|
|
|
func defaultCopyWithTar(src, dst string) error {
|
|
|
|
return defaultArchiver.CopyWithTar(src, dst)
|
2015-04-08 07:29:32 -04:00
|
|
|
}
|
|
|
|
|
2015-11-20 11:49:33 -05:00
|
|
|
func TestIsArchivePathDir(t *testing.T) {
|
2016-02-12 16:58:57 -05:00
|
|
|
cmd := exec.Command("sh", "-c", "mkdir -p /tmp/archivedir")
|
2015-11-20 11:49:33 -05:00
|
|
|
output, err := cmd.CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Fail to create an archive file for test : %s.", output)
|
|
|
|
}
|
2016-02-12 16:58:57 -05:00
|
|
|
if IsArchivePath(tmp + "archivedir") {
|
2015-11-20 11:49:33 -05:00
|
|
|
t.Fatalf("Incorrectly recognised directory as an archive")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestIsArchivePathInvalidFile(t *testing.T) {
|
2016-06-07 03:45:21 -04:00
|
|
|
cmd := exec.Command("sh", "-c", "dd if=/dev/zero bs=1024 count=1 of=/tmp/archive && gzip --stdout /tmp/archive > /tmp/archive.gz")
|
2015-11-20 11:49:33 -05:00
|
|
|
output, err := cmd.CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Fail to create an archive file for test : %s.", output)
|
|
|
|
}
|
2016-02-12 16:58:57 -05:00
|
|
|
if IsArchivePath(tmp + "archive") {
|
2015-11-20 11:49:33 -05:00
|
|
|
t.Fatalf("Incorrectly recognised invalid tar path as archive")
|
|
|
|
}
|
2016-02-12 16:58:57 -05:00
|
|
|
if IsArchivePath(tmp + "archive.gz") {
|
2015-11-20 11:49:33 -05:00
|
|
|
t.Fatalf("Incorrectly recognised invalid compressed tar path as archive")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestIsArchivePathTar(t *testing.T) {
|
2016-06-07 03:45:21 -04:00
|
|
|
var whichTar string
|
|
|
|
if runtime.GOOS == "solaris" {
|
|
|
|
whichTar = "gtar"
|
|
|
|
} else {
|
|
|
|
whichTar = "tar"
|
|
|
|
}
|
|
|
|
cmdStr := fmt.Sprintf("touch /tmp/archivedata && %s -cf /tmp/archive /tmp/archivedata && gzip --stdout /tmp/archive > /tmp/archive.gz", whichTar)
|
|
|
|
cmd := exec.Command("sh", "-c", cmdStr)
|
2015-11-20 11:49:33 -05:00
|
|
|
output, err := cmd.CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Fail to create an archive file for test : %s.", output)
|
|
|
|
}
|
2016-02-12 16:58:57 -05:00
|
|
|
if !IsArchivePath(tmp + "/archive") {
|
2015-11-20 11:49:33 -05:00
|
|
|
t.Fatalf("Did not recognise valid tar path as archive")
|
|
|
|
}
|
2016-02-12 16:58:57 -05:00
|
|
|
if !IsArchivePath(tmp + "archive.gz") {
|
2015-11-20 11:49:33 -05:00
|
|
|
t.Fatalf("Did not recognise valid compressed tar path as archive")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-12 21:39:16 -04:00
|
|
|
func testDecompressStream(t *testing.T, ext, compressCommand string) {
|
|
|
|
cmd := exec.Command("sh", "-c",
|
|
|
|
fmt.Sprintf("touch /tmp/archive && %s /tmp/archive", compressCommand))
|
2015-04-08 07:29:32 -04:00
|
|
|
output, err := cmd.CombinedOutput()
|
|
|
|
if err != nil {
|
2016-09-12 21:39:16 -04:00
|
|
|
t.Fatalf("Failed to create an archive file for test : %s.", output)
|
2015-04-08 07:29:32 -04:00
|
|
|
}
|
2016-09-12 21:39:16 -04:00
|
|
|
filename := "archive." + ext
|
|
|
|
archive, err := os.Open(tmp + filename)
|
2016-06-24 23:57:21 -04:00
|
|
|
if err != nil {
|
2016-09-12 21:39:16 -04:00
|
|
|
t.Fatalf("Failed to open file %s: %v", filename, err)
|
2016-06-24 23:57:21 -04:00
|
|
|
}
|
|
|
|
defer archive.Close()
|
|
|
|
|
2016-09-12 21:39:16 -04:00
|
|
|
r, err := DecompressStream(archive)
|
2015-04-08 07:29:32 -04:00
|
|
|
if err != nil {
|
2016-09-12 21:39:16 -04:00
|
|
|
t.Fatalf("Failed to decompress %s: %v", filename, err)
|
2015-04-08 07:29:32 -04:00
|
|
|
}
|
2016-09-12 21:39:16 -04:00
|
|
|
if _, err = ioutil.ReadAll(r); err != nil {
|
|
|
|
t.Fatalf("Failed to read the decompressed stream: %v ", err)
|
2015-04-08 07:29:32 -04:00
|
|
|
}
|
2016-09-12 21:39:16 -04:00
|
|
|
if err = r.Close(); err != nil {
|
|
|
|
t.Fatalf("Failed to close the decompressed stream: %v ", err)
|
2016-06-24 23:57:21 -04:00
|
|
|
}
|
2016-09-12 21:39:16 -04:00
|
|
|
}
|
2016-06-24 23:57:21 -04:00
|
|
|
|
2016-09-12 21:39:16 -04:00
|
|
|
func TestDecompressStreamGzip(t *testing.T) {
|
|
|
|
testDecompressStream(t, "gz", "gzip -f")
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestDecompressStreamBzip2(t *testing.T) {
|
|
|
|
testDecompressStream(t, "bz2", "bzip2 -f")
|
2015-04-08 07:29:32 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestDecompressStreamXz(t *testing.T) {
|
2016-02-12 16:58:57 -05:00
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
t.Skip("Xz not present in msys2")
|
|
|
|
}
|
2016-09-12 21:39:16 -04:00
|
|
|
testDecompressStream(t, "xz", "xz -f")
|
2015-04-08 07:29:32 -04:00
|
|
|
}
|
|
|
|
|
2017-01-16 23:45:27 -05:00
|
|
|
func TestCompressStreamXzUnsupported(t *testing.T) {
|
2016-02-12 16:58:57 -05:00
|
|
|
dest, err := os.Create(tmp + "dest")
|
2015-04-08 07:29:32 -04:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Fail to create the destination file")
|
|
|
|
}
|
2016-06-24 23:57:21 -04:00
|
|
|
defer dest.Close()
|
|
|
|
|
2015-04-08 07:29:32 -04:00
|
|
|
_, err = CompressStream(dest, Xz)
|
|
|
|
if err == nil {
|
|
|
|
t.Fatalf("Should fail as xz is unsupported for compression format.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCompressStreamBzip2Unsupported(t *testing.T) {
|
2016-02-12 16:58:57 -05:00
|
|
|
dest, err := os.Create(tmp + "dest")
|
2015-04-08 07:29:32 -04:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Fail to create the destination file")
|
|
|
|
}
|
2016-06-24 23:57:21 -04:00
|
|
|
defer dest.Close()
|
|
|
|
|
2015-04-08 07:29:32 -04:00
|
|
|
_, err = CompressStream(dest, Xz)
|
|
|
|
if err == nil {
|
|
|
|
t.Fatalf("Should fail as xz is unsupported for compression format.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCompressStreamInvalid(t *testing.T) {
|
2016-02-12 16:58:57 -05:00
|
|
|
dest, err := os.Create(tmp + "dest")
|
2015-04-08 07:29:32 -04:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Fail to create the destination file")
|
|
|
|
}
|
2016-06-24 23:57:21 -04:00
|
|
|
defer dest.Close()
|
|
|
|
|
2015-04-08 07:29:32 -04:00
|
|
|
_, err = CompressStream(dest, -1)
|
|
|
|
if err == nil {
|
|
|
|
t.Fatalf("Should fail as xz is unsupported for compression format.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestExtensionInvalid(t *testing.T) {
|
|
|
|
compression := Compression(-1)
|
|
|
|
output := compression.Extension()
|
|
|
|
if output != "" {
|
|
|
|
t.Fatalf("The extension of an invalid compression should be an empty string.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestExtensionUncompressed(t *testing.T) {
|
|
|
|
compression := Uncompressed
|
|
|
|
output := compression.Extension()
|
|
|
|
if output != "tar" {
|
2016-05-07 21:36:10 -04:00
|
|
|
t.Fatalf("The extension of an uncompressed archive should be 'tar'.")
|
2015-04-08 07:29:32 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
func TestExtensionBzip2(t *testing.T) {
|
|
|
|
compression := Bzip2
|
|
|
|
output := compression.Extension()
|
|
|
|
if output != "tar.bz2" {
|
|
|
|
t.Fatalf("The extension of a bzip2 archive should be 'tar.bz2'")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
func TestExtensionGzip(t *testing.T) {
|
|
|
|
compression := Gzip
|
|
|
|
output := compression.Extension()
|
|
|
|
if output != "tar.gz" {
|
|
|
|
t.Fatalf("The extension of a bzip2 archive should be 'tar.gz'")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
func TestExtensionXz(t *testing.T) {
|
|
|
|
compression := Xz
|
|
|
|
output := compression.Extension()
|
|
|
|
if output != "tar.xz" {
|
|
|
|
t.Fatalf("The extension of a bzip2 archive should be 'tar.xz'")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-03-29 07:42:17 -04:00
|
|
|
func TestCmdStreamLargeStderr(t *testing.T) {
|
2016-02-12 16:58:57 -05:00
|
|
|
cmd := exec.Command("sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello")
|
fix a race crash when building with "ADD some-broken.tar.xz ..."
The race is between pools.Put which calls buf.Reset and exec.Cmd
doing io.Copy from the buffer; it caused a runtime crash, as
described in #16924:
``` docker-daemon cat the-tarball.xz | xz -d -c -q | docker-untar /path/to/... (aufs ) ```
When docker-untar side fails (like try to set xattr on aufs, or a broken
tar), invokeUnpack will be responsible to exhaust all input, otherwise
`xz` will be write pending for ever.
this change add a receive only channel to cmdStream, and will close it
to notify it's now safe to close the input stream;
in CmdStream the change to use Stdin / Stdout / Stderr keeps the
code simple, os/exec.Cmd will spawn goroutines and call io.Copy automatically.
the CmdStream is actually called in the same file only, change it
lowercase to mark as private.
[...]
INFO[0000] Docker daemon commit=0a8c2e3 execdriver=native-0.2 graphdriver=aufs version=1.8.2
DEBU[0006] Calling POST /build
INFO[0006] POST /v1.20/build?cgroupparent=&cpuperiod=0&cpuquota=0&cpusetcpus=&cpusetmems=&cpushares=0&dockerfile=Dockerfile&memory=0&memswap=0&rm=1&t=gentoo-x32&ulimits=null
DEBU[0008] [BUILDER] Cache miss
DEBU[0009] Couldn't untar /home/lib-docker-v1.8.2-tmp/tmp/docker-build316710953/stage3-x32-20151004.tar.xz to /home/lib-docker-v1.8.2-tmp/aufs/mnt/d909abb87150463939c13e8a349b889a72d9b14f0cfcab42a8711979be285537: Untar re-exec error: exit status 1: output: operation not supported
DEBU[0009] CopyFileWithTar(/home/lib-docker-v1.8.2-tmp/tmp/docker-build316710953/stage3-x32-20151004.tar.xz, /home/lib-docker-v1.8.2-tmp/aufs/mnt/d909abb87150463939c13e8a349b889a72d9b14f0cfcab42a8711979be285537/)
panic: runtime error: slice bounds out of range
goroutine 42 [running]:
bufio.(*Reader).fill(0xc208187800)
/usr/local/go/src/bufio/bufio.go:86 +0x2db
bufio.(*Reader).WriteTo(0xc208187800, 0x7ff39602d150, 0xc2083f11a0, 0x508000, 0x0, 0x0)
/usr/local/go/src/bufio/bufio.go:449 +0x27e
io.Copy(0x7ff39602d150, 0xc2083f11a0, 0x7ff3960261f8, 0xc208187800, 0x0, 0x0, 0x0)
/usr/local/go/src/io/io.go:354 +0xb2
github.com/docker/docker/pkg/archive.func·006()
/go/src/github.com/docker/docker/pkg/archive/archive.go:817 +0x71
created by github.com/docker/docker/pkg/archive.CmdStream
/go/src/github.com/docker/docker/pkg/archive/archive.go:819 +0x1ec
goroutine 1 [chan receive]:
main.(*DaemonCli).CmdDaemon(0xc20809da30, 0xc20800a020, 0xd, 0xd, 0x0, 0x0)
/go/src/github.com/docker/docker/docker/daemon.go:289 +0x1781
reflect.callMethod(0xc208140090, 0xc20828fce0)
/usr/local/go/src/reflect/value.go:605 +0x179
reflect.methodValueCall(0xc20800a020, 0xd, 0xd, 0x1, 0xc208140090, 0x0, 0x0, 0xc208140090, 0x0, 0x45343f, ...)
/usr/local/go/src/reflect/asm_amd64.s:29 +0x36
github.com/docker/docker/cli.(*Cli).Run(0xc208129fb0, 0xc20800a010, 0xe, 0xe, 0x0, 0x0)
/go/src/github.com/docker/docker/cli/cli.go:89 +0x38e
main.main()
/go/src/github.com/docker/docker/docker/docker.go:69 +0x428
goroutine 5 [syscall]:
os/signal.loop()
/usr/local/go/src/os/signal/signal_unix.go:21 +0x1f
created by os/signal.init·1
/usr/local/go/src/os/signal/signal_unix.go:27 +0x35
Signed-off-by: Derek Ch <denc716@gmail.com>
2015-10-09 15:54:21 -04:00
|
|
|
out, _, err := cmdStream(cmd, nil)
|
2013-03-29 07:42:17 -04:00
|
|
|
if err != nil {
|
2013-07-04 17:28:49 -04:00
|
|
|
t.Fatalf("Failed to start command: %s", err)
|
2013-03-29 07:42:17 -04:00
|
|
|
}
|
2013-03-29 17:09:25 -04:00
|
|
|
errCh := make(chan error)
|
|
|
|
go func() {
|
|
|
|
_, err := io.Copy(ioutil.Discard, out)
|
|
|
|
errCh <- err
|
|
|
|
}()
|
|
|
|
select {
|
|
|
|
case err := <-errCh:
|
|
|
|
if err != nil {
|
2013-07-04 17:28:49 -04:00
|
|
|
t.Fatalf("Command should not have failed (err=%.100s...)", err)
|
2013-03-29 17:09:25 -04:00
|
|
|
}
|
|
|
|
case <-time.After(5 * time.Second):
|
|
|
|
t.Fatalf("Command did not complete in 5 seconds; probable deadlock")
|
2013-03-29 07:42:17 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-03-11 08:42:36 -04:00
|
|
|
func TestCmdStreamBad(t *testing.T) {
|
2016-03-01 16:23:29 -05:00
|
|
|
// TODO Windows: Figure out why this is failing in CI but not locally
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
t.Skip("Failing on Windows CI machines")
|
|
|
|
}
|
2016-02-12 16:58:57 -05:00
|
|
|
badCmd := exec.Command("sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1")
|
fix a race crash when building with "ADD some-broken.tar.xz ..."
The race is between pools.Put which calls buf.Reset and exec.Cmd
doing io.Copy from the buffer; it caused a runtime crash, as
described in #16924:
``` docker-daemon cat the-tarball.xz | xz -d -c -q | docker-untar /path/to/... (aufs ) ```
When docker-untar side fails (like try to set xattr on aufs, or a broken
tar), invokeUnpack will be responsible to exhaust all input, otherwise
`xz` will be write pending for ever.
this change add a receive only channel to cmdStream, and will close it
to notify it's now safe to close the input stream;
in CmdStream the change to use Stdin / Stdout / Stderr keeps the
code simple, os/exec.Cmd will spawn goroutines and call io.Copy automatically.
the CmdStream is actually called in the same file only, change it
lowercase to mark as private.
[...]
INFO[0000] Docker daemon commit=0a8c2e3 execdriver=native-0.2 graphdriver=aufs version=1.8.2
DEBU[0006] Calling POST /build
INFO[0006] POST /v1.20/build?cgroupparent=&cpuperiod=0&cpuquota=0&cpusetcpus=&cpusetmems=&cpushares=0&dockerfile=Dockerfile&memory=0&memswap=0&rm=1&t=gentoo-x32&ulimits=null
DEBU[0008] [BUILDER] Cache miss
DEBU[0009] Couldn't untar /home/lib-docker-v1.8.2-tmp/tmp/docker-build316710953/stage3-x32-20151004.tar.xz to /home/lib-docker-v1.8.2-tmp/aufs/mnt/d909abb87150463939c13e8a349b889a72d9b14f0cfcab42a8711979be285537: Untar re-exec error: exit status 1: output: operation not supported
DEBU[0009] CopyFileWithTar(/home/lib-docker-v1.8.2-tmp/tmp/docker-build316710953/stage3-x32-20151004.tar.xz, /home/lib-docker-v1.8.2-tmp/aufs/mnt/d909abb87150463939c13e8a349b889a72d9b14f0cfcab42a8711979be285537/)
panic: runtime error: slice bounds out of range
goroutine 42 [running]:
bufio.(*Reader).fill(0xc208187800)
/usr/local/go/src/bufio/bufio.go:86 +0x2db
bufio.(*Reader).WriteTo(0xc208187800, 0x7ff39602d150, 0xc2083f11a0, 0x508000, 0x0, 0x0)
/usr/local/go/src/bufio/bufio.go:449 +0x27e
io.Copy(0x7ff39602d150, 0xc2083f11a0, 0x7ff3960261f8, 0xc208187800, 0x0, 0x0, 0x0)
/usr/local/go/src/io/io.go:354 +0xb2
github.com/docker/docker/pkg/archive.func·006()
/go/src/github.com/docker/docker/pkg/archive/archive.go:817 +0x71
created by github.com/docker/docker/pkg/archive.CmdStream
/go/src/github.com/docker/docker/pkg/archive/archive.go:819 +0x1ec
goroutine 1 [chan receive]:
main.(*DaemonCli).CmdDaemon(0xc20809da30, 0xc20800a020, 0xd, 0xd, 0x0, 0x0)
/go/src/github.com/docker/docker/docker/daemon.go:289 +0x1781
reflect.callMethod(0xc208140090, 0xc20828fce0)
/usr/local/go/src/reflect/value.go:605 +0x179
reflect.methodValueCall(0xc20800a020, 0xd, 0xd, 0x1, 0xc208140090, 0x0, 0x0, 0xc208140090, 0x0, 0x45343f, ...)
/usr/local/go/src/reflect/asm_amd64.s:29 +0x36
github.com/docker/docker/cli.(*Cli).Run(0xc208129fb0, 0xc20800a010, 0xe, 0xe, 0x0, 0x0)
/go/src/github.com/docker/docker/cli/cli.go:89 +0x38e
main.main()
/go/src/github.com/docker/docker/docker/docker.go:69 +0x428
goroutine 5 [syscall]:
os/signal.loop()
/usr/local/go/src/os/signal/signal_unix.go:21 +0x1f
created by os/signal.init·1
/usr/local/go/src/os/signal/signal_unix.go:27 +0x35
Signed-off-by: Derek Ch <denc716@gmail.com>
2015-10-09 15:54:21 -04:00
|
|
|
out, _, err := cmdStream(badCmd, nil)
|
2013-03-11 08:42:36 -04:00
|
|
|
if err != nil {
|
2013-07-04 17:28:49 -04:00
|
|
|
t.Fatalf("Failed to start command: %s", err)
|
2013-03-11 08:42:36 -04:00
|
|
|
}
|
|
|
|
if output, err := ioutil.ReadAll(out); err == nil {
|
|
|
|
t.Fatalf("Command should have failed")
|
|
|
|
} else if err.Error() != "exit status 1: error couldn't reverse the phase pulser\n" {
|
2013-07-04 17:28:49 -04:00
|
|
|
t.Fatalf("Wrong error value (%s)", err)
|
2013-03-11 08:42:36 -04:00
|
|
|
} else if s := string(output); s != "hello\n" {
|
|
|
|
t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCmdStreamGood(t *testing.T) {
|
2016-02-12 16:58:57 -05:00
|
|
|
cmd := exec.Command("sh", "-c", "echo hello; exit 0")
|
fix a race crash when building with "ADD some-broken.tar.xz ..."
The race is between pools.Put which calls buf.Reset and exec.Cmd
doing io.Copy from the buffer; it caused a runtime crash, as
described in #16924:
``` docker-daemon cat the-tarball.xz | xz -d -c -q | docker-untar /path/to/... (aufs ) ```
When docker-untar side fails (like try to set xattr on aufs, or a broken
tar), invokeUnpack will be responsible to exhaust all input, otherwise
`xz` will be write pending for ever.
this change add a receive only channel to cmdStream, and will close it
to notify it's now safe to close the input stream;
in CmdStream the change to use Stdin / Stdout / Stderr keeps the
code simple, os/exec.Cmd will spawn goroutines and call io.Copy automatically.
the CmdStream is actually called in the same file only, change it
lowercase to mark as private.
[...]
INFO[0000] Docker daemon commit=0a8c2e3 execdriver=native-0.2 graphdriver=aufs version=1.8.2
DEBU[0006] Calling POST /build
INFO[0006] POST /v1.20/build?cgroupparent=&cpuperiod=0&cpuquota=0&cpusetcpus=&cpusetmems=&cpushares=0&dockerfile=Dockerfile&memory=0&memswap=0&rm=1&t=gentoo-x32&ulimits=null
DEBU[0008] [BUILDER] Cache miss
DEBU[0009] Couldn't untar /home/lib-docker-v1.8.2-tmp/tmp/docker-build316710953/stage3-x32-20151004.tar.xz to /home/lib-docker-v1.8.2-tmp/aufs/mnt/d909abb87150463939c13e8a349b889a72d9b14f0cfcab42a8711979be285537: Untar re-exec error: exit status 1: output: operation not supported
DEBU[0009] CopyFileWithTar(/home/lib-docker-v1.8.2-tmp/tmp/docker-build316710953/stage3-x32-20151004.tar.xz, /home/lib-docker-v1.8.2-tmp/aufs/mnt/d909abb87150463939c13e8a349b889a72d9b14f0cfcab42a8711979be285537/)
panic: runtime error: slice bounds out of range
goroutine 42 [running]:
bufio.(*Reader).fill(0xc208187800)
/usr/local/go/src/bufio/bufio.go:86 +0x2db
bufio.(*Reader).WriteTo(0xc208187800, 0x7ff39602d150, 0xc2083f11a0, 0x508000, 0x0, 0x0)
/usr/local/go/src/bufio/bufio.go:449 +0x27e
io.Copy(0x7ff39602d150, 0xc2083f11a0, 0x7ff3960261f8, 0xc208187800, 0x0, 0x0, 0x0)
/usr/local/go/src/io/io.go:354 +0xb2
github.com/docker/docker/pkg/archive.func·006()
/go/src/github.com/docker/docker/pkg/archive/archive.go:817 +0x71
created by github.com/docker/docker/pkg/archive.CmdStream
/go/src/github.com/docker/docker/pkg/archive/archive.go:819 +0x1ec
goroutine 1 [chan receive]:
main.(*DaemonCli).CmdDaemon(0xc20809da30, 0xc20800a020, 0xd, 0xd, 0x0, 0x0)
/go/src/github.com/docker/docker/docker/daemon.go:289 +0x1781
reflect.callMethod(0xc208140090, 0xc20828fce0)
/usr/local/go/src/reflect/value.go:605 +0x179
reflect.methodValueCall(0xc20800a020, 0xd, 0xd, 0x1, 0xc208140090, 0x0, 0x0, 0xc208140090, 0x0, 0x45343f, ...)
/usr/local/go/src/reflect/asm_amd64.s:29 +0x36
github.com/docker/docker/cli.(*Cli).Run(0xc208129fb0, 0xc20800a010, 0xe, 0xe, 0x0, 0x0)
/go/src/github.com/docker/docker/cli/cli.go:89 +0x38e
main.main()
/go/src/github.com/docker/docker/docker/docker.go:69 +0x428
goroutine 5 [syscall]:
os/signal.loop()
/usr/local/go/src/os/signal/signal_unix.go:21 +0x1f
created by os/signal.init·1
/usr/local/go/src/os/signal/signal_unix.go:27 +0x35
Signed-off-by: Derek Ch <denc716@gmail.com>
2015-10-09 15:54:21 -04:00
|
|
|
out, _, err := cmdStream(cmd, nil)
|
2013-03-11 08:42:36 -04:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if output, err := ioutil.ReadAll(out); err != nil {
|
|
|
|
t.Fatalf("Command should not have failed (err=%s)", err)
|
|
|
|
} else if s := string(output); s != "hello\n" {
|
|
|
|
t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-24 11:03:33 -04:00
|
|
|
func TestUntarPathWithInvalidDest(t *testing.T) {
|
|
|
|
tempFolder, err := ioutil.TempDir("", "docker-archive-test")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer os.RemoveAll(tempFolder)
|
2016-02-12 16:58:57 -05:00
|
|
|
invalidDestFolder := filepath.Join(tempFolder, "invalidDest")
|
2015-04-24 11:03:33 -04:00
|
|
|
// Create a src file
|
2016-02-12 16:58:57 -05:00
|
|
|
srcFile := filepath.Join(tempFolder, "src")
|
|
|
|
tarFile := filepath.Join(tempFolder, "src.tar")
|
2015-11-23 20:20:44 -05:00
|
|
|
os.Create(srcFile)
|
|
|
|
os.Create(invalidDestFolder) // being a file (not dir) should cause an error
|
2016-02-12 16:58:57 -05:00
|
|
|
|
|
|
|
// Translate back to Unix semantics as next exec.Command is run under sh
|
|
|
|
srcFileU := srcFile
|
|
|
|
tarFileU := tarFile
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
tarFileU = "/tmp/" + filepath.Base(filepath.Dir(tarFile)) + "/src.tar"
|
|
|
|
srcFileU = "/tmp/" + filepath.Base(filepath.Dir(srcFile)) + "/src"
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd := exec.Command("sh", "-c", "tar cf "+tarFileU+" "+srcFileU)
|
2015-11-23 20:20:44 -05:00
|
|
|
_, err = cmd.CombinedOutput()
|
2015-04-24 11:03:33 -04:00
|
|
|
if err != nil {
|
2015-11-23 20:20:44 -05:00
|
|
|
t.Fatal(err)
|
2015-04-24 11:03:33 -04:00
|
|
|
}
|
2015-11-23 20:20:44 -05:00
|
|
|
|
2017-05-24 11:53:41 -04:00
|
|
|
err = defaultUntarPath(tarFile, invalidDestFolder)
|
2015-04-24 11:03:33 -04:00
|
|
|
if err == nil {
|
|
|
|
t.Fatalf("UntarPath with invalid destination path should throw an error.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestUntarPathWithInvalidSrc(t *testing.T) {
|
|
|
|
dest, err := ioutil.TempDir("", "docker-archive-test")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Fail to create the destination file")
|
|
|
|
}
|
|
|
|
defer os.RemoveAll(dest)
|
2017-05-24 11:53:41 -04:00
|
|
|
err = defaultUntarPath("/invalid/path", dest)
|
2015-04-24 11:03:33 -04:00
|
|
|
if err == nil {
|
|
|
|
t.Fatalf("UntarPath with invalid src path should throw an error.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestUntarPath(t *testing.T) {
|
|
|
|
tmpFolder, err := ioutil.TempDir("", "docker-archive-test")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer os.RemoveAll(tmpFolder)
|
2016-02-12 16:58:57 -05:00
|
|
|
srcFile := filepath.Join(tmpFolder, "src")
|
|
|
|
tarFile := filepath.Join(tmpFolder, "src.tar")
|
|
|
|
os.Create(filepath.Join(tmpFolder, "src"))
|
|
|
|
|
|
|
|
destFolder := filepath.Join(tmpFolder, "dest")
|
2015-04-24 11:03:33 -04:00
|
|
|
err = os.MkdirAll(destFolder, 0740)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Fail to create the destination file")
|
|
|
|
}
|
2016-02-12 16:58:57 -05:00
|
|
|
|
|
|
|
// Translate back to Unix semantics as next exec.Command is run under sh
|
|
|
|
srcFileU := srcFile
|
|
|
|
tarFileU := tarFile
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
tarFileU = "/tmp/" + filepath.Base(filepath.Dir(tarFile)) + "/src.tar"
|
|
|
|
srcFileU = "/tmp/" + filepath.Base(filepath.Dir(srcFile)) + "/src"
|
|
|
|
}
|
|
|
|
cmd := exec.Command("sh", "-c", "tar cf "+tarFileU+" "+srcFileU)
|
|
|
|
_, err = cmd.CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2017-05-24 11:53:41 -04:00
|
|
|
err = defaultUntarPath(tarFile, destFolder)
|
2015-04-24 11:03:33 -04:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("UntarPath shouldn't throw an error, %s.", err)
|
|
|
|
}
|
2016-02-12 16:58:57 -05:00
|
|
|
expectedFile := filepath.Join(destFolder, srcFileU)
|
2015-04-24 11:03:33 -04:00
|
|
|
_, err = os.Stat(expectedFile)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Destination folder should contain the source file but did not.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Do the same test as above but with the destination as file, it should fail
|
|
|
|
func TestUntarPathWithDestinationFile(t *testing.T) {
|
|
|
|
tmpFolder, err := ioutil.TempDir("", "docker-archive-test")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer os.RemoveAll(tmpFolder)
|
2016-02-12 16:58:57 -05:00
|
|
|
srcFile := filepath.Join(tmpFolder, "src")
|
|
|
|
tarFile := filepath.Join(tmpFolder, "src.tar")
|
|
|
|
os.Create(filepath.Join(tmpFolder, "src"))
|
|
|
|
|
|
|
|
// Translate back to Unix semantics as next exec.Command is run under sh
|
|
|
|
srcFileU := srcFile
|
|
|
|
tarFileU := tarFile
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
tarFileU = "/tmp/" + filepath.Base(filepath.Dir(tarFile)) + "/src.tar"
|
|
|
|
srcFileU = "/tmp/" + filepath.Base(filepath.Dir(srcFile)) + "/src"
|
|
|
|
}
|
|
|
|
cmd := exec.Command("sh", "-c", "tar cf "+tarFileU+" "+srcFileU)
|
2015-04-24 11:03:33 -04:00
|
|
|
_, err = cmd.CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2016-02-12 16:58:57 -05:00
|
|
|
destFile := filepath.Join(tmpFolder, "dest")
|
2015-04-24 11:03:33 -04:00
|
|
|
_, err = os.Create(destFile)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Fail to create the destination file")
|
|
|
|
}
|
2017-05-24 11:53:41 -04:00
|
|
|
err = defaultUntarPath(tarFile, destFile)
|
2015-04-24 11:03:33 -04:00
|
|
|
if err == nil {
|
|
|
|
t.Fatalf("UntarPath should throw an error if the destination if a file")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Do the same test as above but with the destination folder already exists
|
|
|
|
// and the destination file is a directory
|
|
|
|
// It's working, see https://github.com/docker/docker/issues/10040
|
|
|
|
func TestUntarPathWithDestinationSrcFileAsFolder(t *testing.T) {
|
|
|
|
tmpFolder, err := ioutil.TempDir("", "docker-archive-test")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer os.RemoveAll(tmpFolder)
|
2016-02-12 16:58:57 -05:00
|
|
|
srcFile := filepath.Join(tmpFolder, "src")
|
|
|
|
tarFile := filepath.Join(tmpFolder, "src.tar")
|
2015-04-24 11:03:33 -04:00
|
|
|
os.Create(srcFile)
|
2016-02-12 16:58:57 -05:00
|
|
|
|
|
|
|
// Translate back to Unix semantics as next exec.Command is run under sh
|
|
|
|
srcFileU := srcFile
|
|
|
|
tarFileU := tarFile
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
tarFileU = "/tmp/" + filepath.Base(filepath.Dir(tarFile)) + "/src.tar"
|
|
|
|
srcFileU = "/tmp/" + filepath.Base(filepath.Dir(srcFile)) + "/src"
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd := exec.Command("sh", "-c", "tar cf "+tarFileU+" "+srcFileU)
|
2015-04-24 11:03:33 -04:00
|
|
|
_, err = cmd.CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2016-02-12 16:58:57 -05:00
|
|
|
destFolder := filepath.Join(tmpFolder, "dest")
|
2015-04-24 11:03:33 -04:00
|
|
|
err = os.MkdirAll(destFolder, 0740)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Fail to create the destination folder")
|
|
|
|
}
|
|
|
|
// Let's create a folder that will has the same path as the extracted file (from tar)
|
2016-02-12 16:58:57 -05:00
|
|
|
destSrcFileAsFolder := filepath.Join(destFolder, srcFileU)
|
2015-04-24 11:03:33 -04:00
|
|
|
err = os.MkdirAll(destSrcFileAsFolder, 0740)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2017-05-24 11:53:41 -04:00
|
|
|
err = defaultUntarPath(tarFile, destFolder)
|
2015-04-24 11:03:33 -04:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("UntarPath should throw not throw an error if the extracted file already exists and is a folder")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCopyWithTarInvalidSrc(t *testing.T) {
|
|
|
|
tempFolder, err := ioutil.TempDir("", "docker-archive-test")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(nil)
|
|
|
|
}
|
2016-02-12 16:58:57 -05:00
|
|
|
destFolder := filepath.Join(tempFolder, "dest")
|
|
|
|
invalidSrc := filepath.Join(tempFolder, "doesnotexists")
|
2015-04-24 11:03:33 -04:00
|
|
|
err = os.MkdirAll(destFolder, 0740)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2017-05-24 11:53:41 -04:00
|
|
|
err = defaultCopyWithTar(invalidSrc, destFolder)
|
2015-04-24 11:03:33 -04:00
|
|
|
if err == nil {
|
|
|
|
t.Fatalf("archiver.CopyWithTar with invalid src path should throw an error.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCopyWithTarInexistentDestWillCreateIt(t *testing.T) {
|
|
|
|
tempFolder, err := ioutil.TempDir("", "docker-archive-test")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(nil)
|
|
|
|
}
|
2016-02-12 16:58:57 -05:00
|
|
|
srcFolder := filepath.Join(tempFolder, "src")
|
|
|
|
inexistentDestFolder := filepath.Join(tempFolder, "doesnotexists")
|
2015-04-24 11:03:33 -04:00
|
|
|
err = os.MkdirAll(srcFolder, 0740)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2017-05-24 11:53:41 -04:00
|
|
|
err = defaultCopyWithTar(srcFolder, inexistentDestFolder)
|
2015-04-24 11:03:33 -04:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("CopyWithTar with an inexistent folder shouldn't fail.")
|
|
|
|
}
|
|
|
|
_, err = os.Stat(inexistentDestFolder)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("CopyWithTar with an inexistent folder should create it.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test CopyWithTar with a file as src
|
|
|
|
func TestCopyWithTarSrcFile(t *testing.T) {
|
|
|
|
folder, err := ioutil.TempDir("", "docker-archive-test")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer os.RemoveAll(folder)
|
2016-02-12 16:58:57 -05:00
|
|
|
dest := filepath.Join(folder, "dest")
|
|
|
|
srcFolder := filepath.Join(folder, "src")
|
|
|
|
src := filepath.Join(folder, filepath.Join("src", "src"))
|
2015-04-24 11:03:33 -04:00
|
|
|
err = os.MkdirAll(srcFolder, 0740)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
err = os.MkdirAll(dest, 0740)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
ioutil.WriteFile(src, []byte("content"), 0777)
|
2017-05-24 11:53:41 -04:00
|
|
|
err = defaultCopyWithTar(src, dest)
|
2015-04-24 11:03:33 -04:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("archiver.CopyWithTar shouldn't throw an error, %s.", err)
|
|
|
|
}
|
|
|
|
_, err = os.Stat(dest)
|
|
|
|
// FIXME Check the content
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Destination file should be the same as the source.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test CopyWithTar with a folder as src
|
|
|
|
func TestCopyWithTarSrcFolder(t *testing.T) {
|
|
|
|
folder, err := ioutil.TempDir("", "docker-archive-test")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer os.RemoveAll(folder)
|
2016-02-12 16:58:57 -05:00
|
|
|
dest := filepath.Join(folder, "dest")
|
|
|
|
src := filepath.Join(folder, filepath.Join("src", "folder"))
|
2015-04-24 11:03:33 -04:00
|
|
|
err = os.MkdirAll(src, 0740)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
err = os.MkdirAll(dest, 0740)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2016-02-12 16:58:57 -05:00
|
|
|
ioutil.WriteFile(filepath.Join(src, "file"), []byte("content"), 0777)
|
2017-05-24 11:53:41 -04:00
|
|
|
err = defaultCopyWithTar(src, dest)
|
2015-04-24 11:03:33 -04:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("archiver.CopyWithTar shouldn't throw an error, %s.", err)
|
|
|
|
}
|
|
|
|
_, err = os.Stat(dest)
|
|
|
|
// FIXME Check the content (the file inside)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Destination folder should contain the source file but did not.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCopyFileWithTarInvalidSrc(t *testing.T) {
|
|
|
|
tempFolder, err := ioutil.TempDir("", "docker-archive-test")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer os.RemoveAll(tempFolder)
|
2016-02-12 16:58:57 -05:00
|
|
|
destFolder := filepath.Join(tempFolder, "dest")
|
2015-04-24 11:03:33 -04:00
|
|
|
err = os.MkdirAll(destFolder, 0740)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2016-02-12 16:58:57 -05:00
|
|
|
invalidFile := filepath.Join(tempFolder, "doesnotexists")
|
2017-05-24 11:53:41 -04:00
|
|
|
err = defaultCopyFileWithTar(invalidFile, destFolder)
|
2015-04-24 11:03:33 -04:00
|
|
|
if err == nil {
|
|
|
|
t.Fatalf("archiver.CopyWithTar with invalid src path should throw an error.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCopyFileWithTarInexistentDestWillCreateIt(t *testing.T) {
|
|
|
|
tempFolder, err := ioutil.TempDir("", "docker-archive-test")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(nil)
|
|
|
|
}
|
|
|
|
defer os.RemoveAll(tempFolder)
|
2016-02-12 16:58:57 -05:00
|
|
|
srcFile := filepath.Join(tempFolder, "src")
|
|
|
|
inexistentDestFolder := filepath.Join(tempFolder, "doesnotexists")
|
2015-04-24 11:03:33 -04:00
|
|
|
_, err = os.Create(srcFile)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2017-05-24 11:53:41 -04:00
|
|
|
err = defaultCopyFileWithTar(srcFile, inexistentDestFolder)
|
2015-04-24 11:03:33 -04:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("CopyWithTar with an inexistent folder shouldn't fail.")
|
|
|
|
}
|
|
|
|
_, err = os.Stat(inexistentDestFolder)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("CopyWithTar with an inexistent folder should create it.")
|
|
|
|
}
|
|
|
|
// FIXME Test the src file and content
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCopyFileWithTarSrcFolder(t *testing.T) {
|
|
|
|
folder, err := ioutil.TempDir("", "docker-archive-copyfilewithtar-test")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer os.RemoveAll(folder)
|
2016-02-12 16:58:57 -05:00
|
|
|
dest := filepath.Join(folder, "dest")
|
|
|
|
src := filepath.Join(folder, "srcfolder")
|
2015-04-24 11:03:33 -04:00
|
|
|
err = os.MkdirAll(src, 0740)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
err = os.MkdirAll(dest, 0740)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2017-05-24 11:53:41 -04:00
|
|
|
err = defaultCopyFileWithTar(src, dest)
|
2015-04-24 11:03:33 -04:00
|
|
|
if err == nil {
|
|
|
|
t.Fatalf("CopyFileWithTar should throw an error with a folder.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestCopyFileWithTarSrcFile(t *testing.T) {
|
|
|
|
folder, err := ioutil.TempDir("", "docker-archive-test")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer os.RemoveAll(folder)
|
2016-02-12 16:58:57 -05:00
|
|
|
dest := filepath.Join(folder, "dest")
|
|
|
|
srcFolder := filepath.Join(folder, "src")
|
|
|
|
src := filepath.Join(folder, filepath.Join("src", "src"))
|
2015-04-24 11:03:33 -04:00
|
|
|
err = os.MkdirAll(srcFolder, 0740)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
err = os.MkdirAll(dest, 0740)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
ioutil.WriteFile(src, []byte("content"), 0777)
|
2017-05-24 11:53:41 -04:00
|
|
|
err = defaultCopyWithTar(src, dest+"/")
|
2015-04-24 11:03:33 -04:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("archiver.CopyFileWithTar shouldn't throw an error, %s.", err)
|
|
|
|
}
|
|
|
|
_, err = os.Stat(dest)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Destination folder should contain the source file but did not.")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-26 11:55:23 -04:00
|
|
|
func TestTarFiles(t *testing.T) {
|
2016-02-12 16:58:57 -05:00
|
|
|
// TODO Windows: Figure out how to port this test.
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
t.Skip("Failing on Windows")
|
|
|
|
}
|
2014-09-26 11:55:23 -04:00
|
|
|
// try without hardlinks
|
|
|
|
if err := checkNoChanges(1000, false); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
// try with hardlinks
|
|
|
|
if err := checkNoChanges(1000, true); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func checkNoChanges(fileNum int, hardlinks bool) error {
|
|
|
|
srcDir, err := ioutil.TempDir("", "docker-test-srcDir")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer os.RemoveAll(srcDir)
|
|
|
|
|
|
|
|
destDir, err := ioutil.TempDir("", "docker-test-destDir")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer os.RemoveAll(destDir)
|
|
|
|
|
|
|
|
_, err = prepareUntarSourceDirectory(fileNum, srcDir, hardlinks)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-05-24 11:53:41 -04:00
|
|
|
err = defaultTarUntar(srcDir, destDir)
|
2014-09-26 11:55:23 -04:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
changes, err := ChangesDirs(destDir, srcDir)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if len(changes) > 0 {
|
|
|
|
return fmt.Errorf("with %d files and %v hardlinks: expected 0 changes, got %d", fileNum, hardlinks, len(changes))
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2014-02-13 19:05:36 -05:00
|
|
|
func tarUntar(t *testing.T, origin string, options *TarOptions) ([]Change, error) {
|
|
|
|
archive, err := TarWithOptions(origin, options)
|
2013-03-11 08:42:36 -04:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2014-02-14 06:41:46 -05:00
|
|
|
defer archive.Close()
|
2013-06-14 13:47:49 -04:00
|
|
|
|
|
|
|
buf := make([]byte, 10)
|
|
|
|
if _, err := archive.Read(buf); err != nil {
|
2014-02-13 19:05:36 -05:00
|
|
|
return nil, err
|
2013-06-14 13:47:49 -04:00
|
|
|
}
|
2014-02-14 06:41:46 -05:00
|
|
|
wrap := io.MultiReader(bytes.NewReader(buf), archive)
|
2013-06-14 13:47:49 -04:00
|
|
|
|
|
|
|
detectedCompression := DetectCompression(buf)
|
2014-02-13 19:05:36 -05:00
|
|
|
compression := options.Compression
|
2013-06-14 13:47:49 -04:00
|
|
|
if detectedCompression.Extension() != compression.Extension() {
|
2014-02-13 19:05:36 -05:00
|
|
|
return nil, fmt.Errorf("Wrong compression detected. Actual compression: %s, found %s", compression.Extension(), detectedCompression.Extension())
|
2013-06-14 13:47:49 -04:00
|
|
|
}
|
|
|
|
|
2013-03-11 08:42:36 -04:00
|
|
|
tmp, err := ioutil.TempDir("", "docker-test-untar")
|
|
|
|
if err != nil {
|
2014-02-13 19:05:36 -05:00
|
|
|
return nil, err
|
2013-03-11 08:42:36 -04:00
|
|
|
}
|
|
|
|
defer os.RemoveAll(tmp)
|
2014-02-14 06:41:46 -05:00
|
|
|
if err := Untar(wrap, tmp, nil); err != nil {
|
2014-02-13 19:05:36 -05:00
|
|
|
return nil, err
|
2013-03-11 08:42:36 -04:00
|
|
|
}
|
|
|
|
if _, err := os.Stat(tmp); err != nil {
|
2014-02-13 19:05:36 -05:00
|
|
|
return nil, err
|
2013-06-14 13:47:49 -04:00
|
|
|
}
|
2014-01-20 06:07:34 -05:00
|
|
|
|
2014-02-13 19:05:36 -05:00
|
|
|
return ChangesDirs(origin, tmp)
|
2013-06-14 13:47:49 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestTarUntar(t *testing.T) {
|
2016-02-12 16:58:57 -05:00
|
|
|
// TODO Windows: Figure out how to fix this test.
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
t.Skip("Failing on Windows")
|
2014-02-13 19:05:36 -05:00
|
|
|
}
|
2015-04-08 07:29:32 -04:00
|
|
|
origin, err := ioutil.TempDir("", "docker-test-untar-origin")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer os.RemoveAll(origin)
|
2016-02-12 16:58:57 -05:00
|
|
|
if err := ioutil.WriteFile(filepath.Join(origin, "1"), []byte("hello world"), 0700); err != nil {
|
2015-04-08 07:29:32 -04:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2016-02-12 16:58:57 -05:00
|
|
|
if err := ioutil.WriteFile(filepath.Join(origin, "2"), []byte("welcome!"), 0700); err != nil {
|
2015-04-08 07:29:32 -04:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2016-02-12 16:58:57 -05:00
|
|
|
if err := ioutil.WriteFile(filepath.Join(origin, "3"), []byte("will be ignored"), 0700); err != nil {
|
2015-04-08 07:29:32 -04:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, c := range []Compression{
|
|
|
|
Uncompressed,
|
|
|
|
Gzip,
|
|
|
|
} {
|
|
|
|
changes, err := tarUntar(t, origin, &TarOptions{
|
|
|
|
Compression: c,
|
|
|
|
ExcludePatterns: []string{"3"},
|
|
|
|
})
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Error tar/untar for compression %s: %s", c.Extension(), err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(changes) != 1 || changes[0].Path != "/3" {
|
|
|
|
t.Fatalf("Unexpected differences after tarUntar: %v", changes)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-13 19:05:36 -05:00
|
|
|
func TestTarWithOptions(t *testing.T) {
|
2016-02-12 16:58:57 -05:00
|
|
|
// TODO Windows: Figure out how to fix this test.
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
t.Skip("Failing on Windows")
|
|
|
|
}
|
2014-02-13 19:05:36 -05:00
|
|
|
origin, err := ioutil.TempDir("", "docker-test-untar-origin")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2015-04-08 07:29:32 -04:00
|
|
|
if _, err := ioutil.TempDir(origin, "folder"); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2014-02-13 19:05:36 -05:00
|
|
|
defer os.RemoveAll(origin)
|
2016-02-12 16:58:57 -05:00
|
|
|
if err := ioutil.WriteFile(filepath.Join(origin, "1"), []byte("hello world"), 0700); err != nil {
|
2014-02-13 19:05:36 -05:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2016-02-12 16:58:57 -05:00
|
|
|
if err := ioutil.WriteFile(filepath.Join(origin, "2"), []byte("welcome!"), 0700); err != nil {
|
2014-02-13 19:05:36 -05:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
cases := []struct {
|
|
|
|
opts *TarOptions
|
|
|
|
numChanges int
|
|
|
|
}{
|
2015-04-08 07:29:32 -04:00
|
|
|
{&TarOptions{IncludeFiles: []string{"1"}}, 2},
|
2014-10-23 17:30:11 -04:00
|
|
|
{&TarOptions{ExcludePatterns: []string{"2"}}, 1},
|
2015-04-08 07:29:32 -04:00
|
|
|
{&TarOptions{ExcludePatterns: []string{"1", "folder*"}}, 2},
|
|
|
|
{&TarOptions{IncludeFiles: []string{"1", "1"}}, 2},
|
2015-07-24 17:12:55 -04:00
|
|
|
{&TarOptions{IncludeFiles: []string{"1"}, RebaseNames: map[string]string{"1": "test"}}, 4},
|
2014-02-13 19:05:36 -05:00
|
|
|
}
|
|
|
|
for _, testCase := range cases {
|
|
|
|
changes, err := tarUntar(t, origin, testCase.opts)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Error tar/untar when testing inclusion/exclusion: %s", err)
|
|
|
|
}
|
|
|
|
if len(changes) != testCase.numChanges {
|
|
|
|
t.Errorf("Expected %d changes, got %d for %+v:",
|
|
|
|
testCase.numChanges, len(changes), testCase.opts)
|
|
|
|
}
|
2013-03-11 08:42:36 -04:00
|
|
|
}
|
|
|
|
}
|
2014-01-31 08:48:06 -05:00
|
|
|
|
|
|
|
// Some tar archives such as http://haproxy.1wt.eu/download/1.5/src/devel/haproxy-1.5-dev21.tar.gz
|
|
|
|
// use PAX Global Extended Headers.
|
|
|
|
// Failing prevents the archives from being uncompressed during ADD
|
|
|
|
func TestTypeXGlobalHeaderDoesNotFail(t *testing.T) {
|
|
|
|
hdr := tar.Header{Typeflag: tar.TypeXGlobalHeader}
|
2014-10-20 15:35:48 -04:00
|
|
|
tmpDir, err := ioutil.TempDir("", "docker-test-archive-pax-test")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer os.RemoveAll(tmpDir)
|
2016-02-12 19:05:50 -05:00
|
|
|
err = createTarFile(filepath.Join(tmpDir, "pax_global_header"), tmpDir, &hdr, nil, true, nil, false)
|
2014-01-31 08:48:06 -05:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
}
|
2014-05-15 18:45:40 -04:00
|
|
|
|
|
|
|
// Some tar have both GNU specific (huge uid) and Ustar specific (long name) things.
|
|
|
|
// Not supposed to happen (should use PAX instead of Ustar for long name) but it does and it should still work.
|
|
|
|
func TestUntarUstarGnuConflict(t *testing.T) {
|
|
|
|
f, err := os.Open("testdata/broken.tar")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2016-06-24 23:57:21 -04:00
|
|
|
defer f.Close()
|
|
|
|
|
2014-05-15 18:45:40 -04:00
|
|
|
found := false
|
|
|
|
tr := tar.NewReader(f)
|
|
|
|
// Iterate through the files in the archive.
|
|
|
|
for {
|
|
|
|
hdr, err := tr.Next()
|
|
|
|
if err == io.EOF {
|
|
|
|
// end of tar archive
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if hdr.Name == "root/.cpanm/work/1395823785.24209/Plack-1.0030/blib/man3/Plack::Middleware::LighttpdScriptNameFix.3pm" {
|
|
|
|
found = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !found {
|
2014-06-12 01:15:53 -04:00
|
|
|
t.Fatalf("%s not found in the archive", "root/.cpanm/work/1395823785.24209/Plack-1.0030/blib/man3/Plack::Middleware::LighttpdScriptNameFix.3pm")
|
2014-05-15 18:45:40 -04:00
|
|
|
}
|
|
|
|
}
|
2014-07-16 14:50:02 -04:00
|
|
|
|
2014-09-26 11:55:23 -04:00
|
|
|
func prepareUntarSourceDirectory(numberOfFiles int, targetPath string, makeLinks bool) (int, error) {
|
2014-07-16 14:50:02 -04:00
|
|
|
fileData := []byte("fooo")
|
|
|
|
for n := 0; n < numberOfFiles; n++ {
|
|
|
|
fileName := fmt.Sprintf("file-%d", n)
|
2016-02-12 16:58:57 -05:00
|
|
|
if err := ioutil.WriteFile(filepath.Join(targetPath, fileName), fileData, 0700); err != nil {
|
2014-07-16 14:50:02 -04:00
|
|
|
return 0, err
|
|
|
|
}
|
2014-09-26 11:55:23 -04:00
|
|
|
if makeLinks {
|
2016-02-12 16:58:57 -05:00
|
|
|
if err := os.Link(filepath.Join(targetPath, fileName), filepath.Join(targetPath, fileName+"-link")); err != nil {
|
2014-09-26 11:55:23 -04:00
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
}
|
2014-07-16 14:50:02 -04:00
|
|
|
}
|
|
|
|
totalSize := numberOfFiles * len(fileData)
|
|
|
|
return totalSize, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func BenchmarkTarUntar(b *testing.B) {
|
|
|
|
origin, err := ioutil.TempDir("", "docker-test-untar-origin")
|
|
|
|
if err != nil {
|
|
|
|
b.Fatal(err)
|
|
|
|
}
|
|
|
|
tempDir, err := ioutil.TempDir("", "docker-test-untar-destination")
|
|
|
|
if err != nil {
|
|
|
|
b.Fatal(err)
|
|
|
|
}
|
2016-02-12 16:58:57 -05:00
|
|
|
target := filepath.Join(tempDir, "dest")
|
2014-09-26 11:55:23 -04:00
|
|
|
n, err := prepareUntarSourceDirectory(100, origin, false)
|
2014-07-16 14:50:02 -04:00
|
|
|
if err != nil {
|
|
|
|
b.Fatal(err)
|
|
|
|
}
|
2014-09-26 11:55:23 -04:00
|
|
|
defer os.RemoveAll(origin)
|
|
|
|
defer os.RemoveAll(tempDir)
|
|
|
|
|
2014-07-16 14:50:02 -04:00
|
|
|
b.ResetTimer()
|
|
|
|
b.SetBytes(int64(n))
|
2014-09-26 11:55:23 -04:00
|
|
|
for n := 0; n < b.N; n++ {
|
2017-05-24 11:53:41 -04:00
|
|
|
err := defaultTarUntar(origin, target)
|
2014-09-26 11:55:23 -04:00
|
|
|
if err != nil {
|
|
|
|
b.Fatal(err)
|
|
|
|
}
|
|
|
|
os.RemoveAll(target)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func BenchmarkTarUntarWithLinks(b *testing.B) {
|
|
|
|
origin, err := ioutil.TempDir("", "docker-test-untar-origin")
|
|
|
|
if err != nil {
|
|
|
|
b.Fatal(err)
|
|
|
|
}
|
|
|
|
tempDir, err := ioutil.TempDir("", "docker-test-untar-destination")
|
|
|
|
if err != nil {
|
|
|
|
b.Fatal(err)
|
|
|
|
}
|
2016-02-12 16:58:57 -05:00
|
|
|
target := filepath.Join(tempDir, "dest")
|
2014-09-26 11:55:23 -04:00
|
|
|
n, err := prepareUntarSourceDirectory(100, origin, true)
|
|
|
|
if err != nil {
|
|
|
|
b.Fatal(err)
|
|
|
|
}
|
2014-07-16 14:50:02 -04:00
|
|
|
defer os.RemoveAll(origin)
|
|
|
|
defer os.RemoveAll(tempDir)
|
2014-09-26 11:55:23 -04:00
|
|
|
|
|
|
|
b.ResetTimer()
|
|
|
|
b.SetBytes(int64(n))
|
2014-07-16 14:50:02 -04:00
|
|
|
for n := 0; n < b.N; n++ {
|
2017-05-24 11:53:41 -04:00
|
|
|
err := defaultTarUntar(origin, target)
|
2014-07-16 14:50:02 -04:00
|
|
|
if err != nil {
|
|
|
|
b.Fatal(err)
|
|
|
|
}
|
|
|
|
os.RemoveAll(target)
|
|
|
|
}
|
|
|
|
}
|
2014-10-20 15:35:48 -04:00
|
|
|
|
|
|
|
func TestUntarInvalidFilenames(t *testing.T) {
|
2016-02-12 16:58:57 -05:00
|
|
|
// TODO Windows: Figure out how to fix this test.
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
t.Skip("Passes but hits breakoutError: platform and architecture is not supported")
|
|
|
|
}
|
2014-10-20 15:35:48 -04:00
|
|
|
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("untar", "docker-TestUntarInvalidFilenames", headers); err != nil {
|
|
|
|
t.Fatalf("i=%d. %v", i, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-01 18:55:28 -05:00
|
|
|
func TestUntarHardlinkToSymlink(t *testing.T) {
|
2016-02-12 16:58:57 -05:00
|
|
|
// TODO Windows. There may be a way of running this, but turning off for now
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
t.Skip("hardlinks on Windows")
|
|
|
|
}
|
2015-03-01 18:55:28 -05:00
|
|
|
for i, headers := range [][]*tar.Header{
|
|
|
|
{
|
|
|
|
{
|
|
|
|
Name: "symlink1",
|
|
|
|
Typeflag: tar.TypeSymlink,
|
|
|
|
Linkname: "regfile",
|
|
|
|
Mode: 0644,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "symlink2",
|
|
|
|
Typeflag: tar.TypeLink,
|
|
|
|
Linkname: "symlink1",
|
|
|
|
Mode: 0644,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "regfile",
|
|
|
|
Typeflag: tar.TypeReg,
|
|
|
|
Mode: 0644,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
} {
|
|
|
|
if err := testBreakout("untar", "docker-TestUntarHardlinkToSymlink", headers); err != nil {
|
|
|
|
t.Fatalf("i=%d. %v", i, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-20 15:35:48 -04:00
|
|
|
func TestUntarInvalidHardlink(t *testing.T) {
|
2016-02-12 16:58:57 -05:00
|
|
|
// TODO Windows. There may be a way of running this, but turning off for now
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
t.Skip("hardlinks on Windows")
|
|
|
|
}
|
2014-10-20 15:35:48 -04:00
|
|
|
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("untar", "docker-TestUntarInvalidHardlink", headers); err != nil {
|
|
|
|
t.Fatalf("i=%d. %v", i, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestUntarInvalidSymlink(t *testing.T) {
|
2016-02-12 16:58:57 -05:00
|
|
|
// TODO Windows. There may be a way of running this, but turning off for now
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
t.Skip("hardlinks on Windows")
|
|
|
|
}
|
2014-10-20 15:35:48 -04:00
|
|
|
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,
|
|
|
|
},
|
|
|
|
},
|
2014-11-19 11:27:34 -05:00
|
|
|
{ // try writing to victim/newdir/newfile with a symlink in the path
|
|
|
|
{
|
|
|
|
// this header needs to be before the next one, or else there is an error
|
|
|
|
Name: "dir/loophole",
|
|
|
|
Typeflag: tar.TypeSymlink,
|
|
|
|
Linkname: "../../victim",
|
|
|
|
Mode: 0755,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "dir/loophole/newdir/newfile",
|
|
|
|
Typeflag: tar.TypeReg,
|
|
|
|
Mode: 0644,
|
|
|
|
},
|
|
|
|
},
|
2014-10-20 15:35:48 -04:00
|
|
|
} {
|
|
|
|
if err := testBreakout("untar", "docker-TestUntarInvalidSymlink", headers); err != nil {
|
|
|
|
t.Fatalf("i=%d. %v", i, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-12-03 15:36:57 -05:00
|
|
|
|
|
|
|
func TestTempArchiveCloseMultipleTimes(t *testing.T) {
|
|
|
|
reader := ioutil.NopCloser(strings.NewReader("hello"))
|
|
|
|
tempArchive, err := NewTempArchive(reader, "")
|
|
|
|
buf := make([]byte, 10)
|
|
|
|
n, err := tempArchive.Read(buf)
|
|
|
|
if n != 5 {
|
|
|
|
t.Fatalf("Expected to read 5 bytes. Read %d instead", n)
|
|
|
|
}
|
|
|
|
for i := 0; i < 3; i++ {
|
|
|
|
if err = tempArchive.Close(); err != nil {
|
|
|
|
t.Fatalf("i=%d. Unexpected error closing temp archive: %v", i, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-02-21 15:07:45 -05:00
|
|
|
|
2017-04-05 18:25:29 -04:00
|
|
|
func TestReplaceFileTarWrapper(t *testing.T) {
|
|
|
|
filesInArchive := 20
|
|
|
|
testcases := []struct {
|
|
|
|
doc string
|
|
|
|
filename string
|
|
|
|
modifier TarModifierFunc
|
|
|
|
expected string
|
|
|
|
fileCount int
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
doc: "Modifier creates a new file",
|
|
|
|
filename: "newfile",
|
|
|
|
modifier: createModifier(t),
|
|
|
|
expected: "the new content",
|
|
|
|
fileCount: filesInArchive + 1,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
doc: "Modifier replaces a file",
|
|
|
|
filename: "file-2",
|
|
|
|
modifier: createOrReplaceModifier,
|
|
|
|
expected: "the new content",
|
|
|
|
fileCount: filesInArchive,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
doc: "Modifier replaces the last file",
|
|
|
|
filename: fmt.Sprintf("file-%d", filesInArchive-1),
|
|
|
|
modifier: createOrReplaceModifier,
|
|
|
|
expected: "the new content",
|
|
|
|
fileCount: filesInArchive,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
doc: "Modifier appends to a file",
|
|
|
|
filename: "file-3",
|
|
|
|
modifier: appendModifier,
|
|
|
|
expected: "fooo\nnext line",
|
|
|
|
fileCount: filesInArchive,
|
|
|
|
},
|
2017-02-21 15:07:45 -05:00
|
|
|
}
|
|
|
|
|
2017-04-05 18:25:29 -04:00
|
|
|
for _, testcase := range testcases {
|
|
|
|
sourceArchive, cleanup := buildSourceArchive(t, filesInArchive)
|
|
|
|
defer cleanup()
|
2017-02-21 15:07:45 -05:00
|
|
|
|
2017-04-05 18:25:29 -04:00
|
|
|
resultArchive := ReplaceFileTarWrapper(
|
|
|
|
sourceArchive,
|
|
|
|
map[string]TarModifierFunc{testcase.filename: testcase.modifier})
|
2017-02-21 15:07:45 -05:00
|
|
|
|
2017-04-05 18:25:29 -04:00
|
|
|
actual := readFileFromArchive(t, resultArchive, testcase.filename, testcase.fileCount, testcase.doc)
|
Remove pkg/testutil/assert in favor of testify
I noticed that we're using a homegrown package for assertions. The
functions are extremely similar to testify, but with enough slight
differences to be confusing (for example, Equal takes its arguments in a
different order). We already vendor testify, and it's used in a few
places by tests.
I also found some problems with pkg/testutil/assert. For example, the
NotNil function seems to be broken. It checks the argument against
"nil", which only works for an interface. If you pass in a nil map or
slice, the equality check will fail.
In the interest of avoiding NIH, I'm proposing replacing
pkg/testutil/assert with testify. The test code looks almost the same,
but we avoid the confusion of having two similar but slightly different
assertion packages, and having to maintain our own package instead of
using a commonly-used one.
In the process, I found a few places where the tests should halt if an
assertion fails, so I've made those cases (that I noticed) use "require"
instead of "assert", and I've vendored the "require" package from
testify alongside the already-present "assert" package.
Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
2017-04-13 18:45:37 -04:00
|
|
|
assert.Equal(t, testcase.expected, actual, testcase.doc)
|
2017-02-21 15:07:45 -05:00
|
|
|
}
|
2017-04-05 18:25:29 -04:00
|
|
|
}
|
2017-02-21 15:07:45 -05:00
|
|
|
|
2017-04-05 18:25:29 -04:00
|
|
|
func buildSourceArchive(t *testing.T, numberOfFiles int) (io.ReadCloser, func()) {
|
|
|
|
srcDir, err := ioutil.TempDir("", "docker-test-srcDir")
|
Remove pkg/testutil/assert in favor of testify
I noticed that we're using a homegrown package for assertions. The
functions are extremely similar to testify, but with enough slight
differences to be confusing (for example, Equal takes its arguments in a
different order). We already vendor testify, and it's used in a few
places by tests.
I also found some problems with pkg/testutil/assert. For example, the
NotNil function seems to be broken. It checks the argument against
"nil", which only works for an interface. If you pass in a nil map or
slice, the equality check will fail.
In the interest of avoiding NIH, I'm proposing replacing
pkg/testutil/assert with testify. The test code looks almost the same,
but we avoid the confusion of having two similar but slightly different
assertion packages, and having to maintain our own package instead of
using a commonly-used one.
In the process, I found a few places where the tests should halt if an
assertion fails, so I've made those cases (that I noticed) use "require"
instead of "assert", and I've vendored the "require" package from
testify alongside the already-present "assert" package.
Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
2017-04-13 18:45:37 -04:00
|
|
|
require.NoError(t, err)
|
2017-02-21 15:07:45 -05:00
|
|
|
|
2017-04-05 18:25:29 -04:00
|
|
|
_, err = prepareUntarSourceDirectory(numberOfFiles, srcDir, false)
|
Remove pkg/testutil/assert in favor of testify
I noticed that we're using a homegrown package for assertions. The
functions are extremely similar to testify, but with enough slight
differences to be confusing (for example, Equal takes its arguments in a
different order). We already vendor testify, and it's used in a few
places by tests.
I also found some problems with pkg/testutil/assert. For example, the
NotNil function seems to be broken. It checks the argument against
"nil", which only works for an interface. If you pass in a nil map or
slice, the equality check will fail.
In the interest of avoiding NIH, I'm proposing replacing
pkg/testutil/assert with testify. The test code looks almost the same,
but we avoid the confusion of having two similar but slightly different
assertion packages, and having to maintain our own package instead of
using a commonly-used one.
In the process, I found a few places where the tests should halt if an
assertion fails, so I've made those cases (that I noticed) use "require"
instead of "assert", and I've vendored the "require" package from
testify alongside the already-present "assert" package.
Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
2017-04-13 18:45:37 -04:00
|
|
|
require.NoError(t, err)
|
2017-02-21 15:07:45 -05:00
|
|
|
|
2017-04-05 18:25:29 -04:00
|
|
|
sourceArchive, err := TarWithOptions(srcDir, &TarOptions{})
|
Remove pkg/testutil/assert in favor of testify
I noticed that we're using a homegrown package for assertions. The
functions are extremely similar to testify, but with enough slight
differences to be confusing (for example, Equal takes its arguments in a
different order). We already vendor testify, and it's used in a few
places by tests.
I also found some problems with pkg/testutil/assert. For example, the
NotNil function seems to be broken. It checks the argument against
"nil", which only works for an interface. If you pass in a nil map or
slice, the equality check will fail.
In the interest of avoiding NIH, I'm proposing replacing
pkg/testutil/assert with testify. The test code looks almost the same,
but we avoid the confusion of having two similar but slightly different
assertion packages, and having to maintain our own package instead of
using a commonly-used one.
In the process, I found a few places where the tests should halt if an
assertion fails, so I've made those cases (that I noticed) use "require"
instead of "assert", and I've vendored the "require" package from
testify alongside the already-present "assert" package.
Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
2017-04-13 18:45:37 -04:00
|
|
|
require.NoError(t, err)
|
2017-04-05 18:25:29 -04:00
|
|
|
return sourceArchive, func() {
|
|
|
|
os.RemoveAll(srcDir)
|
|
|
|
sourceArchive.Close()
|
2017-02-21 15:07:45 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-05 18:25:29 -04:00
|
|
|
func createOrReplaceModifier(path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) {
|
|
|
|
return &tar.Header{
|
|
|
|
Mode: 0600,
|
|
|
|
Typeflag: tar.TypeReg,
|
|
|
|
}, []byte("the new content"), nil
|
2017-02-21 15:07:45 -05:00
|
|
|
}
|
|
|
|
|
2017-04-05 18:25:29 -04:00
|
|
|
func createModifier(t *testing.T) TarModifierFunc {
|
|
|
|
return func(path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) {
|
|
|
|
assert.Nil(t, content)
|
|
|
|
return createOrReplaceModifier(path, header, content)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func appendModifier(path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) {
|
|
|
|
buffer := bytes.Buffer{}
|
|
|
|
if content != nil {
|
|
|
|
if _, err := buffer.ReadFrom(content); err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
buffer.WriteString("\nnext line")
|
|
|
|
return &tar.Header{Mode: 0600, Typeflag: tar.TypeReg}, buffer.Bytes(), nil
|
2017-02-21 15:07:45 -05:00
|
|
|
}
|
|
|
|
|
2017-04-05 18:25:29 -04:00
|
|
|
func readFileFromArchive(t *testing.T, archive io.ReadCloser, name string, expectedCount int, doc string) string {
|
|
|
|
destDir, err := ioutil.TempDir("", "docker-test-destDir")
|
Remove pkg/testutil/assert in favor of testify
I noticed that we're using a homegrown package for assertions. The
functions are extremely similar to testify, but with enough slight
differences to be confusing (for example, Equal takes its arguments in a
different order). We already vendor testify, and it's used in a few
places by tests.
I also found some problems with pkg/testutil/assert. For example, the
NotNil function seems to be broken. It checks the argument against
"nil", which only works for an interface. If you pass in a nil map or
slice, the equality check will fail.
In the interest of avoiding NIH, I'm proposing replacing
pkg/testutil/assert with testify. The test code looks almost the same,
but we avoid the confusion of having two similar but slightly different
assertion packages, and having to maintain our own package instead of
using a commonly-used one.
In the process, I found a few places where the tests should halt if an
assertion fails, so I've made those cases (that I noticed) use "require"
instead of "assert", and I've vendored the "require" package from
testify alongside the already-present "assert" package.
Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
2017-04-13 18:45:37 -04:00
|
|
|
require.NoError(t, err)
|
2017-04-05 18:25:29 -04:00
|
|
|
defer os.RemoveAll(destDir)
|
|
|
|
|
|
|
|
err = Untar(archive, destDir, nil)
|
Remove pkg/testutil/assert in favor of testify
I noticed that we're using a homegrown package for assertions. The
functions are extremely similar to testify, but with enough slight
differences to be confusing (for example, Equal takes its arguments in a
different order). We already vendor testify, and it's used in a few
places by tests.
I also found some problems with pkg/testutil/assert. For example, the
NotNil function seems to be broken. It checks the argument against
"nil", which only works for an interface. If you pass in a nil map or
slice, the equality check will fail.
In the interest of avoiding NIH, I'm proposing replacing
pkg/testutil/assert with testify. The test code looks almost the same,
but we avoid the confusion of having two similar but slightly different
assertion packages, and having to maintain our own package instead of
using a commonly-used one.
In the process, I found a few places where the tests should halt if an
assertion fails, so I've made those cases (that I noticed) use "require"
instead of "assert", and I've vendored the "require" package from
testify alongside the already-present "assert" package.
Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
2017-04-13 18:45:37 -04:00
|
|
|
require.NoError(t, err)
|
2017-04-05 18:25:29 -04:00
|
|
|
|
|
|
|
files, _ := ioutil.ReadDir(destDir)
|
Remove pkg/testutil/assert in favor of testify
I noticed that we're using a homegrown package for assertions. The
functions are extremely similar to testify, but with enough slight
differences to be confusing (for example, Equal takes its arguments in a
different order). We already vendor testify, and it's used in a few
places by tests.
I also found some problems with pkg/testutil/assert. For example, the
NotNil function seems to be broken. It checks the argument against
"nil", which only works for an interface. If you pass in a nil map or
slice, the equality check will fail.
In the interest of avoiding NIH, I'm proposing replacing
pkg/testutil/assert with testify. The test code looks almost the same,
but we avoid the confusion of having two similar but slightly different
assertion packages, and having to maintain our own package instead of
using a commonly-used one.
In the process, I found a few places where the tests should halt if an
assertion fails, so I've made those cases (that I noticed) use "require"
instead of "assert", and I've vendored the "require" package from
testify alongside the already-present "assert" package.
Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
2017-04-13 18:45:37 -04:00
|
|
|
assert.Len(t, files, expectedCount, doc)
|
2017-04-05 18:25:29 -04:00
|
|
|
|
|
|
|
content, err := ioutil.ReadFile(filepath.Join(destDir, name))
|
Remove pkg/testutil/assert in favor of testify
I noticed that we're using a homegrown package for assertions. The
functions are extremely similar to testify, but with enough slight
differences to be confusing (for example, Equal takes its arguments in a
different order). We already vendor testify, and it's used in a few
places by tests.
I also found some problems with pkg/testutil/assert. For example, the
NotNil function seems to be broken. It checks the argument against
"nil", which only works for an interface. If you pass in a nil map or
slice, the equality check will fail.
In the interest of avoiding NIH, I'm proposing replacing
pkg/testutil/assert with testify. The test code looks almost the same,
but we avoid the confusion of having two similar but slightly different
assertion packages, and having to maintain our own package instead of
using a commonly-used one.
In the process, I found a few places where the tests should halt if an
assertion fails, so I've made those cases (that I noticed) use "require"
instead of "assert", and I've vendored the "require" package from
testify alongside the already-present "assert" package.
Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
2017-04-13 18:45:37 -04:00
|
|
|
assert.NoError(t, err)
|
2017-04-05 18:25:29 -04:00
|
|
|
return string(content)
|
2017-02-21 15:07:45 -05:00
|
|
|
}
|