github.com/mizzy/docker@v1.5.0/pkg/archive/archive_test.go (about)

     1  package archive
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"io/ioutil"
     8  	"os"
     9  	"os/exec"
    10  	"path"
    11  	"path/filepath"
    12  	"strings"
    13  	"syscall"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
    18  )
    19  
    20  func TestCmdStreamLargeStderr(t *testing.T) {
    21  	cmd := exec.Command("/bin/sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello")
    22  	out, err := CmdStream(cmd, nil)
    23  	if err != nil {
    24  		t.Fatalf("Failed to start command: %s", err)
    25  	}
    26  	errCh := make(chan error)
    27  	go func() {
    28  		_, err := io.Copy(ioutil.Discard, out)
    29  		errCh <- err
    30  	}()
    31  	select {
    32  	case err := <-errCh:
    33  		if err != nil {
    34  			t.Fatalf("Command should not have failed (err=%.100s...)", err)
    35  		}
    36  	case <-time.After(5 * time.Second):
    37  		t.Fatalf("Command did not complete in 5 seconds; probable deadlock")
    38  	}
    39  }
    40  
    41  func TestCmdStreamBad(t *testing.T) {
    42  	badCmd := exec.Command("/bin/sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1")
    43  	out, err := CmdStream(badCmd, nil)
    44  	if err != nil {
    45  		t.Fatalf("Failed to start command: %s", err)
    46  	}
    47  	if output, err := ioutil.ReadAll(out); err == nil {
    48  		t.Fatalf("Command should have failed")
    49  	} else if err.Error() != "exit status 1: error couldn't reverse the phase pulser\n" {
    50  		t.Fatalf("Wrong error value (%s)", err)
    51  	} else if s := string(output); s != "hello\n" {
    52  		t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output)
    53  	}
    54  }
    55  
    56  func TestCmdStreamGood(t *testing.T) {
    57  	cmd := exec.Command("/bin/sh", "-c", "echo hello; exit 0")
    58  	out, err := CmdStream(cmd, nil)
    59  	if err != nil {
    60  		t.Fatal(err)
    61  	}
    62  	if output, err := ioutil.ReadAll(out); err != nil {
    63  		t.Fatalf("Command should not have failed (err=%s)", err)
    64  	} else if s := string(output); s != "hello\n" {
    65  		t.Fatalf("Command output should be '%s', not '%s'", "hello\\n", output)
    66  	}
    67  }
    68  
    69  func TestTarFiles(t *testing.T) {
    70  	// try without hardlinks
    71  	if err := checkNoChanges(1000, false); err != nil {
    72  		t.Fatal(err)
    73  	}
    74  	// try with hardlinks
    75  	if err := checkNoChanges(1000, true); err != nil {
    76  		t.Fatal(err)
    77  	}
    78  }
    79  
    80  func checkNoChanges(fileNum int, hardlinks bool) error {
    81  	srcDir, err := ioutil.TempDir("", "docker-test-srcDir")
    82  	if err != nil {
    83  		return err
    84  	}
    85  	defer os.RemoveAll(srcDir)
    86  
    87  	destDir, err := ioutil.TempDir("", "docker-test-destDir")
    88  	if err != nil {
    89  		return err
    90  	}
    91  	defer os.RemoveAll(destDir)
    92  
    93  	_, err = prepareUntarSourceDirectory(fileNum, srcDir, hardlinks)
    94  	if err != nil {
    95  		return err
    96  	}
    97  
    98  	err = TarUntar(srcDir, destDir)
    99  	if err != nil {
   100  		return err
   101  	}
   102  
   103  	changes, err := ChangesDirs(destDir, srcDir)
   104  	if err != nil {
   105  		return err
   106  	}
   107  	if len(changes) > 0 {
   108  		return fmt.Errorf("with %d files and %v hardlinks: expected 0 changes, got %d", fileNum, hardlinks, len(changes))
   109  	}
   110  	return nil
   111  }
   112  
   113  func tarUntar(t *testing.T, origin string, options *TarOptions) ([]Change, error) {
   114  	archive, err := TarWithOptions(origin, options)
   115  	if err != nil {
   116  		t.Fatal(err)
   117  	}
   118  	defer archive.Close()
   119  
   120  	buf := make([]byte, 10)
   121  	if _, err := archive.Read(buf); err != nil {
   122  		return nil, err
   123  	}
   124  	wrap := io.MultiReader(bytes.NewReader(buf), archive)
   125  
   126  	detectedCompression := DetectCompression(buf)
   127  	compression := options.Compression
   128  	if detectedCompression.Extension() != compression.Extension() {
   129  		return nil, fmt.Errorf("Wrong compression detected. Actual compression: %s, found %s", compression.Extension(), detectedCompression.Extension())
   130  	}
   131  
   132  	tmp, err := ioutil.TempDir("", "docker-test-untar")
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  	defer os.RemoveAll(tmp)
   137  	if err := Untar(wrap, tmp, nil); err != nil {
   138  		return nil, err
   139  	}
   140  	if _, err := os.Stat(tmp); err != nil {
   141  		return nil, err
   142  	}
   143  
   144  	return ChangesDirs(origin, tmp)
   145  }
   146  
   147  func TestTarUntar(t *testing.T) {
   148  	origin, err := ioutil.TempDir("", "docker-test-untar-origin")
   149  	if err != nil {
   150  		t.Fatal(err)
   151  	}
   152  	defer os.RemoveAll(origin)
   153  	if err := ioutil.WriteFile(path.Join(origin, "1"), []byte("hello world"), 0700); err != nil {
   154  		t.Fatal(err)
   155  	}
   156  	if err := ioutil.WriteFile(path.Join(origin, "2"), []byte("welcome!"), 0700); err != nil {
   157  		t.Fatal(err)
   158  	}
   159  	if err := ioutil.WriteFile(path.Join(origin, "3"), []byte("will be ignored"), 0700); err != nil {
   160  		t.Fatal(err)
   161  	}
   162  
   163  	for _, c := range []Compression{
   164  		Uncompressed,
   165  		Gzip,
   166  	} {
   167  		changes, err := tarUntar(t, origin, &TarOptions{
   168  			Compression:     c,
   169  			ExcludePatterns: []string{"3"},
   170  		})
   171  
   172  		if err != nil {
   173  			t.Fatalf("Error tar/untar for compression %s: %s", c.Extension(), err)
   174  		}
   175  
   176  		if len(changes) != 1 || changes[0].Path != "/3" {
   177  			t.Fatalf("Unexpected differences after tarUntar: %v", changes)
   178  		}
   179  	}
   180  }
   181  
   182  func TestTarWithOptions(t *testing.T) {
   183  	origin, err := ioutil.TempDir("", "docker-test-untar-origin")
   184  	if err != nil {
   185  		t.Fatal(err)
   186  	}
   187  	defer os.RemoveAll(origin)
   188  	if err := ioutil.WriteFile(path.Join(origin, "1"), []byte("hello world"), 0700); err != nil {
   189  		t.Fatal(err)
   190  	}
   191  	if err := ioutil.WriteFile(path.Join(origin, "2"), []byte("welcome!"), 0700); err != nil {
   192  		t.Fatal(err)
   193  	}
   194  
   195  	cases := []struct {
   196  		opts       *TarOptions
   197  		numChanges int
   198  	}{
   199  		{&TarOptions{IncludeFiles: []string{"1"}}, 1},
   200  		{&TarOptions{ExcludePatterns: []string{"2"}}, 1},
   201  	}
   202  	for _, testCase := range cases {
   203  		changes, err := tarUntar(t, origin, testCase.opts)
   204  		if err != nil {
   205  			t.Fatalf("Error tar/untar when testing inclusion/exclusion: %s", err)
   206  		}
   207  		if len(changes) != testCase.numChanges {
   208  			t.Errorf("Expected %d changes, got %d for %+v:",
   209  				testCase.numChanges, len(changes), testCase.opts)
   210  		}
   211  	}
   212  }
   213  
   214  // Some tar archives such as http://haproxy.1wt.eu/download/1.5/src/devel/haproxy-1.5-dev21.tar.gz
   215  // use PAX Global Extended Headers.
   216  // Failing prevents the archives from being uncompressed during ADD
   217  func TestTypeXGlobalHeaderDoesNotFail(t *testing.T) {
   218  	hdr := tar.Header{Typeflag: tar.TypeXGlobalHeader}
   219  	tmpDir, err := ioutil.TempDir("", "docker-test-archive-pax-test")
   220  	if err != nil {
   221  		t.Fatal(err)
   222  	}
   223  	defer os.RemoveAll(tmpDir)
   224  	err = createTarFile(filepath.Join(tmpDir, "pax_global_header"), tmpDir, &hdr, nil, true)
   225  	if err != nil {
   226  		t.Fatal(err)
   227  	}
   228  }
   229  
   230  // Some tar have both GNU specific (huge uid) and Ustar specific (long name) things.
   231  // Not supposed to happen (should use PAX instead of Ustar for long name) but it does and it should still work.
   232  func TestUntarUstarGnuConflict(t *testing.T) {
   233  	f, err := os.Open("testdata/broken.tar")
   234  	if err != nil {
   235  		t.Fatal(err)
   236  	}
   237  	found := false
   238  	tr := tar.NewReader(f)
   239  	// Iterate through the files in the archive.
   240  	for {
   241  		hdr, err := tr.Next()
   242  		if err == io.EOF {
   243  			// end of tar archive
   244  			break
   245  		}
   246  		if err != nil {
   247  			t.Fatal(err)
   248  		}
   249  		if hdr.Name == "root/.cpanm/work/1395823785.24209/Plack-1.0030/blib/man3/Plack::Middleware::LighttpdScriptNameFix.3pm" {
   250  			found = true
   251  			break
   252  		}
   253  	}
   254  	if !found {
   255  		t.Fatalf("%s not found in the archive", "root/.cpanm/work/1395823785.24209/Plack-1.0030/blib/man3/Plack::Middleware::LighttpdScriptNameFix.3pm")
   256  	}
   257  }
   258  
   259  func TestTarWithHardLink(t *testing.T) {
   260  	origin, err := ioutil.TempDir("", "docker-test-tar-hardlink")
   261  	if err != nil {
   262  		t.Fatal(err)
   263  	}
   264  	defer os.RemoveAll(origin)
   265  	if err := ioutil.WriteFile(path.Join(origin, "1"), []byte("hello world"), 0700); err != nil {
   266  		t.Fatal(err)
   267  	}
   268  	if err := os.Link(path.Join(origin, "1"), path.Join(origin, "2")); err != nil {
   269  		t.Fatal(err)
   270  	}
   271  
   272  	var i1, i2 uint64
   273  	if i1, err = getNlink(path.Join(origin, "1")); err != nil {
   274  		t.Fatal(err)
   275  	}
   276  	// sanity check that we can hardlink
   277  	if i1 != 2 {
   278  		t.Skipf("skipping since hardlinks don't work here; expected 2 links, got %d", i1)
   279  	}
   280  
   281  	dest, err := ioutil.TempDir("", "docker-test-tar-hardlink-dest")
   282  	if err != nil {
   283  		t.Fatal(err)
   284  	}
   285  	defer os.RemoveAll(dest)
   286  
   287  	// we'll do this in two steps to separate failure
   288  	fh, err := Tar(origin, Uncompressed)
   289  	if err != nil {
   290  		t.Fatal(err)
   291  	}
   292  
   293  	// ensure we can read the whole thing with no error, before writing back out
   294  	buf, err := ioutil.ReadAll(fh)
   295  	if err != nil {
   296  		t.Fatal(err)
   297  	}
   298  
   299  	bRdr := bytes.NewReader(buf)
   300  	err = Untar(bRdr, dest, &TarOptions{Compression: Uncompressed})
   301  	if err != nil {
   302  		t.Fatal(err)
   303  	}
   304  
   305  	if i1, err = getInode(path.Join(dest, "1")); err != nil {
   306  		t.Fatal(err)
   307  	}
   308  	if i2, err = getInode(path.Join(dest, "2")); err != nil {
   309  		t.Fatal(err)
   310  	}
   311  
   312  	if i1 != i2 {
   313  		t.Errorf("expected matching inodes, but got %d and %d", i1, i2)
   314  	}
   315  }
   316  
   317  func getNlink(path string) (uint64, error) {
   318  	stat, err := os.Stat(path)
   319  	if err != nil {
   320  		return 0, err
   321  	}
   322  	statT, ok := stat.Sys().(*syscall.Stat_t)
   323  	if !ok {
   324  		return 0, fmt.Errorf("expected type *syscall.Stat_t, got %t", stat.Sys())
   325  	}
   326  	return statT.Nlink, nil
   327  }
   328  
   329  func getInode(path string) (uint64, error) {
   330  	stat, err := os.Stat(path)
   331  	if err != nil {
   332  		return 0, err
   333  	}
   334  	statT, ok := stat.Sys().(*syscall.Stat_t)
   335  	if !ok {
   336  		return 0, fmt.Errorf("expected type *syscall.Stat_t, got %t", stat.Sys())
   337  	}
   338  	return statT.Ino, nil
   339  }
   340  
   341  func prepareUntarSourceDirectory(numberOfFiles int, targetPath string, makeLinks bool) (int, error) {
   342  	fileData := []byte("fooo")
   343  	for n := 0; n < numberOfFiles; n++ {
   344  		fileName := fmt.Sprintf("file-%d", n)
   345  		if err := ioutil.WriteFile(path.Join(targetPath, fileName), fileData, 0700); err != nil {
   346  			return 0, err
   347  		}
   348  		if makeLinks {
   349  			if err := os.Link(path.Join(targetPath, fileName), path.Join(targetPath, fileName+"-link")); err != nil {
   350  				return 0, err
   351  			}
   352  		}
   353  	}
   354  	totalSize := numberOfFiles * len(fileData)
   355  	return totalSize, nil
   356  }
   357  
   358  func BenchmarkTarUntar(b *testing.B) {
   359  	origin, err := ioutil.TempDir("", "docker-test-untar-origin")
   360  	if err != nil {
   361  		b.Fatal(err)
   362  	}
   363  	tempDir, err := ioutil.TempDir("", "docker-test-untar-destination")
   364  	if err != nil {
   365  		b.Fatal(err)
   366  	}
   367  	target := path.Join(tempDir, "dest")
   368  	n, err := prepareUntarSourceDirectory(100, origin, false)
   369  	if err != nil {
   370  		b.Fatal(err)
   371  	}
   372  	defer os.RemoveAll(origin)
   373  	defer os.RemoveAll(tempDir)
   374  
   375  	b.ResetTimer()
   376  	b.SetBytes(int64(n))
   377  	for n := 0; n < b.N; n++ {
   378  		err := TarUntar(origin, target)
   379  		if err != nil {
   380  			b.Fatal(err)
   381  		}
   382  		os.RemoveAll(target)
   383  	}
   384  }
   385  
   386  func BenchmarkTarUntarWithLinks(b *testing.B) {
   387  	origin, err := ioutil.TempDir("", "docker-test-untar-origin")
   388  	if err != nil {
   389  		b.Fatal(err)
   390  	}
   391  	tempDir, err := ioutil.TempDir("", "docker-test-untar-destination")
   392  	if err != nil {
   393  		b.Fatal(err)
   394  	}
   395  	target := path.Join(tempDir, "dest")
   396  	n, err := prepareUntarSourceDirectory(100, origin, true)
   397  	if err != nil {
   398  		b.Fatal(err)
   399  	}
   400  	defer os.RemoveAll(origin)
   401  	defer os.RemoveAll(tempDir)
   402  
   403  	b.ResetTimer()
   404  	b.SetBytes(int64(n))
   405  	for n := 0; n < b.N; n++ {
   406  		err := TarUntar(origin, target)
   407  		if err != nil {
   408  			b.Fatal(err)
   409  		}
   410  		os.RemoveAll(target)
   411  	}
   412  }
   413  
   414  func TestUntarInvalidFilenames(t *testing.T) {
   415  	for i, headers := range [][]*tar.Header{
   416  		{
   417  			{
   418  				Name:     "../victim/dotdot",
   419  				Typeflag: tar.TypeReg,
   420  				Mode:     0644,
   421  			},
   422  		},
   423  		{
   424  			{
   425  				// Note the leading slash
   426  				Name:     "/../victim/slash-dotdot",
   427  				Typeflag: tar.TypeReg,
   428  				Mode:     0644,
   429  			},
   430  		},
   431  	} {
   432  		if err := testBreakout("untar", "docker-TestUntarInvalidFilenames", headers); err != nil {
   433  			t.Fatalf("i=%d. %v", i, err)
   434  		}
   435  	}
   436  }
   437  
   438  func TestUntarInvalidHardlink(t *testing.T) {
   439  	for i, headers := range [][]*tar.Header{
   440  		{ // try reading victim/hello (../)
   441  			{
   442  				Name:     "dotdot",
   443  				Typeflag: tar.TypeLink,
   444  				Linkname: "../victim/hello",
   445  				Mode:     0644,
   446  			},
   447  		},
   448  		{ // try reading victim/hello (/../)
   449  			{
   450  				Name:     "slash-dotdot",
   451  				Typeflag: tar.TypeLink,
   452  				// Note the leading slash
   453  				Linkname: "/../victim/hello",
   454  				Mode:     0644,
   455  			},
   456  		},
   457  		{ // try writing victim/file
   458  			{
   459  				Name:     "loophole-victim",
   460  				Typeflag: tar.TypeLink,
   461  				Linkname: "../victim",
   462  				Mode:     0755,
   463  			},
   464  			{
   465  				Name:     "loophole-victim/file",
   466  				Typeflag: tar.TypeReg,
   467  				Mode:     0644,
   468  			},
   469  		},
   470  		{ // try reading victim/hello (hardlink, symlink)
   471  			{
   472  				Name:     "loophole-victim",
   473  				Typeflag: tar.TypeLink,
   474  				Linkname: "../victim",
   475  				Mode:     0755,
   476  			},
   477  			{
   478  				Name:     "symlink",
   479  				Typeflag: tar.TypeSymlink,
   480  				Linkname: "loophole-victim/hello",
   481  				Mode:     0644,
   482  			},
   483  		},
   484  		{ // Try reading victim/hello (hardlink, hardlink)
   485  			{
   486  				Name:     "loophole-victim",
   487  				Typeflag: tar.TypeLink,
   488  				Linkname: "../victim",
   489  				Mode:     0755,
   490  			},
   491  			{
   492  				Name:     "hardlink",
   493  				Typeflag: tar.TypeLink,
   494  				Linkname: "loophole-victim/hello",
   495  				Mode:     0644,
   496  			},
   497  		},
   498  		{ // Try removing victim directory (hardlink)
   499  			{
   500  				Name:     "loophole-victim",
   501  				Typeflag: tar.TypeLink,
   502  				Linkname: "../victim",
   503  				Mode:     0755,
   504  			},
   505  			{
   506  				Name:     "loophole-victim",
   507  				Typeflag: tar.TypeReg,
   508  				Mode:     0644,
   509  			},
   510  		},
   511  	} {
   512  		if err := testBreakout("untar", "docker-TestUntarInvalidHardlink", headers); err != nil {
   513  			t.Fatalf("i=%d. %v", i, err)
   514  		}
   515  	}
   516  }
   517  
   518  func TestUntarInvalidSymlink(t *testing.T) {
   519  	for i, headers := range [][]*tar.Header{
   520  		{ // try reading victim/hello (../)
   521  			{
   522  				Name:     "dotdot",
   523  				Typeflag: tar.TypeSymlink,
   524  				Linkname: "../victim/hello",
   525  				Mode:     0644,
   526  			},
   527  		},
   528  		{ // try reading victim/hello (/../)
   529  			{
   530  				Name:     "slash-dotdot",
   531  				Typeflag: tar.TypeSymlink,
   532  				// Note the leading slash
   533  				Linkname: "/../victim/hello",
   534  				Mode:     0644,
   535  			},
   536  		},
   537  		{ // try writing victim/file
   538  			{
   539  				Name:     "loophole-victim",
   540  				Typeflag: tar.TypeSymlink,
   541  				Linkname: "../victim",
   542  				Mode:     0755,
   543  			},
   544  			{
   545  				Name:     "loophole-victim/file",
   546  				Typeflag: tar.TypeReg,
   547  				Mode:     0644,
   548  			},
   549  		},
   550  		{ // try reading victim/hello (symlink, symlink)
   551  			{
   552  				Name:     "loophole-victim",
   553  				Typeflag: tar.TypeSymlink,
   554  				Linkname: "../victim",
   555  				Mode:     0755,
   556  			},
   557  			{
   558  				Name:     "symlink",
   559  				Typeflag: tar.TypeSymlink,
   560  				Linkname: "loophole-victim/hello",
   561  				Mode:     0644,
   562  			},
   563  		},
   564  		{ // try reading victim/hello (symlink, hardlink)
   565  			{
   566  				Name:     "loophole-victim",
   567  				Typeflag: tar.TypeSymlink,
   568  				Linkname: "../victim",
   569  				Mode:     0755,
   570  			},
   571  			{
   572  				Name:     "hardlink",
   573  				Typeflag: tar.TypeLink,
   574  				Linkname: "loophole-victim/hello",
   575  				Mode:     0644,
   576  			},
   577  		},
   578  		{ // try removing victim directory (symlink)
   579  			{
   580  				Name:     "loophole-victim",
   581  				Typeflag: tar.TypeSymlink,
   582  				Linkname: "../victim",
   583  				Mode:     0755,
   584  			},
   585  			{
   586  				Name:     "loophole-victim",
   587  				Typeflag: tar.TypeReg,
   588  				Mode:     0644,
   589  			},
   590  		},
   591  		{ // try writing to victim/newdir/newfile with a symlink in the path
   592  			{
   593  				// this header needs to be before the next one, or else there is an error
   594  				Name:     "dir/loophole",
   595  				Typeflag: tar.TypeSymlink,
   596  				Linkname: "../../victim",
   597  				Mode:     0755,
   598  			},
   599  			{
   600  				Name:     "dir/loophole/newdir/newfile",
   601  				Typeflag: tar.TypeReg,
   602  				Mode:     0644,
   603  			},
   604  		},
   605  	} {
   606  		if err := testBreakout("untar", "docker-TestUntarInvalidSymlink", headers); err != nil {
   607  			t.Fatalf("i=%d. %v", i, err)
   608  		}
   609  	}
   610  }
   611  
   612  func TestTempArchiveCloseMultipleTimes(t *testing.T) {
   613  	reader := ioutil.NopCloser(strings.NewReader("hello"))
   614  	tempArchive, err := NewTempArchive(reader, "")
   615  	buf := make([]byte, 10)
   616  	n, err := tempArchive.Read(buf)
   617  	if n != 5 {
   618  		t.Fatalf("Expected to read 5 bytes. Read %d instead", n)
   619  	}
   620  	for i := 0; i < 3; i++ {
   621  		if err = tempArchive.Close(); err != nil {
   622  			t.Fatalf("i=%d. Unexpected error closing temp archive: %v", i, err)
   623  		}
   624  	}
   625  }