github.com/jogo/docker@v1.7.0-rc1/integration-cli/docker_cli_cp_test.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"os/exec"
     9  	"path"
    10  	"path/filepath"
    11  	"strings"
    12  
    13  	"github.com/go-check/check"
    14  )
    15  
    16  const (
    17  	cpTestPathParent = "/some"
    18  	cpTestPath       = "/some/path"
    19  	cpTestName       = "test"
    20  	cpFullPath       = "/some/path/test"
    21  
    22  	cpContainerContents = "holla, i am the container"
    23  	cpHostContents      = "hello, i am the host"
    24  )
    25  
    26  // Test for #5656
    27  // Check that garbage paths don't escape the container's rootfs
    28  func (s *DockerSuite) TestCpGarbagePath(c *check.C) {
    29  	out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath)
    30  	if exitCode != 0 {
    31  		c.Fatal("failed to create a container", out)
    32  	}
    33  
    34  	cleanedContainerID := strings.TrimSpace(out)
    35  
    36  	out, _ = dockerCmd(c, "wait", cleanedContainerID)
    37  	if strings.TrimSpace(out) != "0" {
    38  		c.Fatal("failed to set up container", out)
    39  	}
    40  
    41  	if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil {
    42  		c.Fatal(err)
    43  	}
    44  
    45  	hostFile, err := os.Create(cpFullPath)
    46  	if err != nil {
    47  		c.Fatal(err)
    48  	}
    49  	defer hostFile.Close()
    50  	defer os.RemoveAll(cpTestPathParent)
    51  
    52  	fmt.Fprintf(hostFile, "%s", cpHostContents)
    53  
    54  	tmpdir, err := ioutil.TempDir("", "docker-integration")
    55  	if err != nil {
    56  		c.Fatal(err)
    57  	}
    58  
    59  	tmpname := filepath.Join(tmpdir, cpTestName)
    60  	defer os.RemoveAll(tmpdir)
    61  
    62  	path := path.Join("../../../../../../../../../../../../", cpFullPath)
    63  
    64  	_, _ = dockerCmd(c, "cp", cleanedContainerID+":"+path, tmpdir)
    65  
    66  	file, _ := os.Open(tmpname)
    67  	defer file.Close()
    68  
    69  	test, err := ioutil.ReadAll(file)
    70  	if err != nil {
    71  		c.Fatal(err)
    72  	}
    73  
    74  	if string(test) == cpHostContents {
    75  		c.Errorf("output matched host file -- garbage path can escape container rootfs")
    76  	}
    77  
    78  	if string(test) != cpContainerContents {
    79  		c.Errorf("output doesn't match the input for garbage path")
    80  	}
    81  
    82  }
    83  
    84  // Check that relative paths are relative to the container's rootfs
    85  func (s *DockerSuite) TestCpRelativePath(c *check.C) {
    86  	out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath)
    87  	if exitCode != 0 {
    88  		c.Fatal("failed to create a container", out)
    89  	}
    90  
    91  	cleanedContainerID := strings.TrimSpace(out)
    92  
    93  	out, _ = dockerCmd(c, "wait", cleanedContainerID)
    94  	if strings.TrimSpace(out) != "0" {
    95  		c.Fatal("failed to set up container", out)
    96  	}
    97  
    98  	if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil {
    99  		c.Fatal(err)
   100  	}
   101  
   102  	hostFile, err := os.Create(cpFullPath)
   103  	if err != nil {
   104  		c.Fatal(err)
   105  	}
   106  	defer hostFile.Close()
   107  	defer os.RemoveAll(cpTestPathParent)
   108  
   109  	fmt.Fprintf(hostFile, "%s", cpHostContents)
   110  
   111  	tmpdir, err := ioutil.TempDir("", "docker-integration")
   112  
   113  	if err != nil {
   114  		c.Fatal(err)
   115  	}
   116  
   117  	tmpname := filepath.Join(tmpdir, cpTestName)
   118  	defer os.RemoveAll(tmpdir)
   119  
   120  	var relPath string
   121  	if path.IsAbs(cpFullPath) {
   122  		// normally this is `filepath.Rel("/", cpFullPath)` but we cannot
   123  		// get this unix-path manipulation on windows with filepath.
   124  		relPath = cpFullPath[1:]
   125  	} else {
   126  		c.Fatalf("path %s was assumed to be an absolute path", cpFullPath)
   127  	}
   128  
   129  	_, _ = dockerCmd(c, "cp", cleanedContainerID+":"+relPath, tmpdir)
   130  
   131  	file, _ := os.Open(tmpname)
   132  	defer file.Close()
   133  
   134  	test, err := ioutil.ReadAll(file)
   135  	if err != nil {
   136  		c.Fatal(err)
   137  	}
   138  
   139  	if string(test) == cpHostContents {
   140  		c.Errorf("output matched host file -- relative path can escape container rootfs")
   141  	}
   142  
   143  	if string(test) != cpContainerContents {
   144  		c.Errorf("output doesn't match the input for relative path")
   145  	}
   146  
   147  }
   148  
   149  // Check that absolute paths are relative to the container's rootfs
   150  func (s *DockerSuite) TestCpAbsolutePath(c *check.C) {
   151  	out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath)
   152  	if exitCode != 0 {
   153  		c.Fatal("failed to create a container", out)
   154  	}
   155  
   156  	cleanedContainerID := strings.TrimSpace(out)
   157  
   158  	out, _ = dockerCmd(c, "wait", cleanedContainerID)
   159  	if strings.TrimSpace(out) != "0" {
   160  		c.Fatal("failed to set up container", out)
   161  	}
   162  
   163  	if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil {
   164  		c.Fatal(err)
   165  	}
   166  
   167  	hostFile, err := os.Create(cpFullPath)
   168  	if err != nil {
   169  		c.Fatal(err)
   170  	}
   171  	defer hostFile.Close()
   172  	defer os.RemoveAll(cpTestPathParent)
   173  
   174  	fmt.Fprintf(hostFile, "%s", cpHostContents)
   175  
   176  	tmpdir, err := ioutil.TempDir("", "docker-integration")
   177  
   178  	if err != nil {
   179  		c.Fatal(err)
   180  	}
   181  
   182  	tmpname := filepath.Join(tmpdir, cpTestName)
   183  	defer os.RemoveAll(tmpdir)
   184  
   185  	path := cpFullPath
   186  
   187  	_, _ = dockerCmd(c, "cp", cleanedContainerID+":"+path, tmpdir)
   188  
   189  	file, _ := os.Open(tmpname)
   190  	defer file.Close()
   191  
   192  	test, err := ioutil.ReadAll(file)
   193  	if err != nil {
   194  		c.Fatal(err)
   195  	}
   196  
   197  	if string(test) == cpHostContents {
   198  		c.Errorf("output matched host file -- absolute path can escape container rootfs")
   199  	}
   200  
   201  	if string(test) != cpContainerContents {
   202  		c.Errorf("output doesn't match the input for absolute path")
   203  	}
   204  
   205  }
   206  
   207  // Test for #5619
   208  // Check that absolute symlinks are still relative to the container's rootfs
   209  func (s *DockerSuite) TestCpAbsoluteSymlink(c *check.C) {
   210  	out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath+" && ln -s "+cpFullPath+" container_path")
   211  	if exitCode != 0 {
   212  		c.Fatal("failed to create a container", out)
   213  	}
   214  
   215  	cleanedContainerID := strings.TrimSpace(out)
   216  
   217  	out, _ = dockerCmd(c, "wait", cleanedContainerID)
   218  	if strings.TrimSpace(out) != "0" {
   219  		c.Fatal("failed to set up container", out)
   220  	}
   221  
   222  	if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil {
   223  		c.Fatal(err)
   224  	}
   225  
   226  	hostFile, err := os.Create(cpFullPath)
   227  	if err != nil {
   228  		c.Fatal(err)
   229  	}
   230  	defer hostFile.Close()
   231  	defer os.RemoveAll(cpTestPathParent)
   232  
   233  	fmt.Fprintf(hostFile, "%s", cpHostContents)
   234  
   235  	tmpdir, err := ioutil.TempDir("", "docker-integration")
   236  
   237  	if err != nil {
   238  		c.Fatal(err)
   239  	}
   240  
   241  	tmpname := filepath.Join(tmpdir, cpTestName)
   242  	defer os.RemoveAll(tmpdir)
   243  
   244  	path := path.Join("/", "container_path")
   245  
   246  	_, _ = dockerCmd(c, "cp", cleanedContainerID+":"+path, tmpdir)
   247  
   248  	file, _ := os.Open(tmpname)
   249  	defer file.Close()
   250  
   251  	test, err := ioutil.ReadAll(file)
   252  	if err != nil {
   253  		c.Fatal(err)
   254  	}
   255  
   256  	if string(test) == cpHostContents {
   257  		c.Errorf("output matched host file -- absolute symlink can escape container rootfs")
   258  	}
   259  
   260  	if string(test) != cpContainerContents {
   261  		c.Errorf("output doesn't match the input for absolute symlink")
   262  	}
   263  
   264  }
   265  
   266  // Test for #5619
   267  // Check that symlinks which are part of the resource path are still relative to the container's rootfs
   268  func (s *DockerSuite) TestCpSymlinkComponent(c *check.C) {
   269  	out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath+" && ln -s "+cpTestPath+" container_path")
   270  	if exitCode != 0 {
   271  		c.Fatal("failed to create a container", out)
   272  	}
   273  
   274  	cleanedContainerID := strings.TrimSpace(out)
   275  
   276  	out, _ = dockerCmd(c, "wait", cleanedContainerID)
   277  	if strings.TrimSpace(out) != "0" {
   278  		c.Fatal("failed to set up container", out)
   279  	}
   280  
   281  	if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil {
   282  		c.Fatal(err)
   283  	}
   284  
   285  	hostFile, err := os.Create(cpFullPath)
   286  	if err != nil {
   287  		c.Fatal(err)
   288  	}
   289  	defer hostFile.Close()
   290  	defer os.RemoveAll(cpTestPathParent)
   291  
   292  	fmt.Fprintf(hostFile, "%s", cpHostContents)
   293  
   294  	tmpdir, err := ioutil.TempDir("", "docker-integration")
   295  
   296  	if err != nil {
   297  		c.Fatal(err)
   298  	}
   299  
   300  	tmpname := filepath.Join(tmpdir, cpTestName)
   301  	defer os.RemoveAll(tmpdir)
   302  
   303  	path := path.Join("/", "container_path", cpTestName)
   304  
   305  	_, _ = dockerCmd(c, "cp", cleanedContainerID+":"+path, tmpdir)
   306  
   307  	file, _ := os.Open(tmpname)
   308  	defer file.Close()
   309  
   310  	test, err := ioutil.ReadAll(file)
   311  	if err != nil {
   312  		c.Fatal(err)
   313  	}
   314  
   315  	if string(test) == cpHostContents {
   316  		c.Errorf("output matched host file -- symlink path component can escape container rootfs")
   317  	}
   318  
   319  	if string(test) != cpContainerContents {
   320  		c.Errorf("output doesn't match the input for symlink path component")
   321  	}
   322  
   323  }
   324  
   325  // Check that cp with unprivileged user doesn't return any error
   326  func (s *DockerSuite) TestCpUnprivilegedUser(c *check.C) {
   327  	testRequires(c, UnixCli) // uses chmod/su: not available on windows
   328  
   329  	out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "touch "+cpTestName)
   330  	if exitCode != 0 {
   331  		c.Fatal("failed to create a container", out)
   332  	}
   333  
   334  	cleanedContainerID := strings.TrimSpace(out)
   335  
   336  	out, _ = dockerCmd(c, "wait", cleanedContainerID)
   337  	if strings.TrimSpace(out) != "0" {
   338  		c.Fatal("failed to set up container", out)
   339  	}
   340  
   341  	tmpdir, err := ioutil.TempDir("", "docker-integration")
   342  	if err != nil {
   343  		c.Fatal(err)
   344  	}
   345  
   346  	defer os.RemoveAll(tmpdir)
   347  
   348  	if err = os.Chmod(tmpdir, 0777); err != nil {
   349  		c.Fatal(err)
   350  	}
   351  
   352  	path := cpTestName
   353  
   354  	_, _, err = runCommandWithOutput(exec.Command("su", "unprivilegeduser", "-c", dockerBinary+" cp "+cleanedContainerID+":"+path+" "+tmpdir))
   355  	if err != nil {
   356  		c.Fatalf("couldn't copy with unprivileged user: %s:%s %s", cleanedContainerID, path, err)
   357  	}
   358  
   359  }
   360  
   361  func (s *DockerSuite) TestCpSpecialFiles(c *check.C) {
   362  	testRequires(c, SameHostDaemon)
   363  
   364  	outDir, err := ioutil.TempDir("", "cp-test-special-files")
   365  	if err != nil {
   366  		c.Fatal(err)
   367  	}
   368  	defer os.RemoveAll(outDir)
   369  
   370  	out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "touch /foo")
   371  	if exitCode != 0 {
   372  		c.Fatal("failed to create a container", out)
   373  	}
   374  
   375  	cleanedContainerID := strings.TrimSpace(out)
   376  
   377  	out, _ = dockerCmd(c, "wait", cleanedContainerID)
   378  	if strings.TrimSpace(out) != "0" {
   379  		c.Fatal("failed to set up container", out)
   380  	}
   381  
   382  	// Copy actual /etc/resolv.conf
   383  	_, _ = dockerCmd(c, "cp", cleanedContainerID+":/etc/resolv.conf", outDir)
   384  
   385  	expected, err := ioutil.ReadFile("/var/lib/docker/containers/" + cleanedContainerID + "/resolv.conf")
   386  	actual, err := ioutil.ReadFile(outDir + "/resolv.conf")
   387  
   388  	if !bytes.Equal(actual, expected) {
   389  		c.Fatalf("Expected copied file to be duplicate of the container resolvconf")
   390  	}
   391  
   392  	// Copy actual /etc/hosts
   393  	_, _ = dockerCmd(c, "cp", cleanedContainerID+":/etc/hosts", outDir)
   394  
   395  	expected, err = ioutil.ReadFile("/var/lib/docker/containers/" + cleanedContainerID + "/hosts")
   396  	actual, err = ioutil.ReadFile(outDir + "/hosts")
   397  
   398  	if !bytes.Equal(actual, expected) {
   399  		c.Fatalf("Expected copied file to be duplicate of the container hosts")
   400  	}
   401  
   402  	// Copy actual /etc/resolv.conf
   403  	_, _ = dockerCmd(c, "cp", cleanedContainerID+":/etc/hostname", outDir)
   404  
   405  	expected, err = ioutil.ReadFile("/var/lib/docker/containers/" + cleanedContainerID + "/hostname")
   406  	actual, err = ioutil.ReadFile(outDir + "/hostname")
   407  
   408  	if !bytes.Equal(actual, expected) {
   409  		c.Fatalf("Expected copied file to be duplicate of the container resolvconf")
   410  	}
   411  
   412  }
   413  
   414  func (s *DockerSuite) TestCpVolumePath(c *check.C) {
   415  	testRequires(c, SameHostDaemon)
   416  
   417  	tmpDir, err := ioutil.TempDir("", "cp-test-volumepath")
   418  	if err != nil {
   419  		c.Fatal(err)
   420  	}
   421  	defer os.RemoveAll(tmpDir)
   422  	outDir, err := ioutil.TempDir("", "cp-test-volumepath-out")
   423  	if err != nil {
   424  		c.Fatal(err)
   425  	}
   426  	defer os.RemoveAll(outDir)
   427  	_, err = os.Create(tmpDir + "/test")
   428  	if err != nil {
   429  		c.Fatal(err)
   430  	}
   431  
   432  	out, exitCode := dockerCmd(c, "run", "-d", "-v", "/foo", "-v", tmpDir+"/test:/test", "-v", tmpDir+":/baz", "busybox", "/bin/sh", "-c", "touch /foo/bar")
   433  	if exitCode != 0 {
   434  		c.Fatal("failed to create a container", out)
   435  	}
   436  
   437  	cleanedContainerID := strings.TrimSpace(out)
   438  
   439  	out, _ = dockerCmd(c, "wait", cleanedContainerID)
   440  	if strings.TrimSpace(out) != "0" {
   441  		c.Fatal("failed to set up container", out)
   442  	}
   443  
   444  	// Copy actual volume path
   445  	_, _ = dockerCmd(c, "cp", cleanedContainerID+":/foo", outDir)
   446  
   447  	stat, err := os.Stat(outDir + "/foo")
   448  	if err != nil {
   449  		c.Fatal(err)
   450  	}
   451  	if !stat.IsDir() {
   452  		c.Fatal("expected copied content to be dir")
   453  	}
   454  	stat, err = os.Stat(outDir + "/foo/bar")
   455  	if err != nil {
   456  		c.Fatal(err)
   457  	}
   458  	if stat.IsDir() {
   459  		c.Fatal("Expected file `bar` to be a file")
   460  	}
   461  
   462  	// Copy file nested in volume
   463  	_, _ = dockerCmd(c, "cp", cleanedContainerID+":/foo/bar", outDir)
   464  
   465  	stat, err = os.Stat(outDir + "/bar")
   466  	if err != nil {
   467  		c.Fatal(err)
   468  	}
   469  	if stat.IsDir() {
   470  		c.Fatal("Expected file `bar` to be a file")
   471  	}
   472  
   473  	// Copy Bind-mounted dir
   474  	_, _ = dockerCmd(c, "cp", cleanedContainerID+":/baz", outDir)
   475  	stat, err = os.Stat(outDir + "/baz")
   476  	if err != nil {
   477  		c.Fatal(err)
   478  	}
   479  	if !stat.IsDir() {
   480  		c.Fatal("Expected `baz` to be a dir")
   481  	}
   482  
   483  	// Copy file nested in bind-mounted dir
   484  	_, _ = dockerCmd(c, "cp", cleanedContainerID+":/baz/test", outDir)
   485  	fb, err := ioutil.ReadFile(outDir + "/baz/test")
   486  	if err != nil {
   487  		c.Fatal(err)
   488  	}
   489  	fb2, err := ioutil.ReadFile(tmpDir + "/test")
   490  	if err != nil {
   491  		c.Fatal(err)
   492  	}
   493  	if !bytes.Equal(fb, fb2) {
   494  		c.Fatalf("Expected copied file to be duplicate of bind-mounted file")
   495  	}
   496  
   497  	// Copy bind-mounted file
   498  	_, _ = dockerCmd(c, "cp", cleanedContainerID+":/test", outDir)
   499  	fb, err = ioutil.ReadFile(outDir + "/test")
   500  	if err != nil {
   501  		c.Fatal(err)
   502  	}
   503  	fb2, err = ioutil.ReadFile(tmpDir + "/test")
   504  	if err != nil {
   505  		c.Fatal(err)
   506  	}
   507  	if !bytes.Equal(fb, fb2) {
   508  		c.Fatalf("Expected copied file to be duplicate of bind-mounted file")
   509  	}
   510  
   511  }
   512  
   513  func (s *DockerSuite) TestCpToDot(c *check.C) {
   514  	out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /test")
   515  	if exitCode != 0 {
   516  		c.Fatal("failed to create a container", out)
   517  	}
   518  
   519  	cleanedContainerID := strings.TrimSpace(out)
   520  
   521  	out, _ = dockerCmd(c, "wait", cleanedContainerID)
   522  	if strings.TrimSpace(out) != "0" {
   523  		c.Fatal("failed to set up container", out)
   524  	}
   525  
   526  	tmpdir, err := ioutil.TempDir("", "docker-integration")
   527  	if err != nil {
   528  		c.Fatal(err)
   529  	}
   530  	defer os.RemoveAll(tmpdir)
   531  	cwd, err := os.Getwd()
   532  	if err != nil {
   533  		c.Fatal(err)
   534  	}
   535  	defer os.Chdir(cwd)
   536  	if err := os.Chdir(tmpdir); err != nil {
   537  		c.Fatal(err)
   538  	}
   539  	_, _ = dockerCmd(c, "cp", cleanedContainerID+":/test", ".")
   540  	content, err := ioutil.ReadFile("./test")
   541  	if string(content) != "lololol\n" {
   542  		c.Fatalf("Wrong content in copied file %q, should be %q", content, "lololol\n")
   543  	}
   544  }
   545  
   546  func (s *DockerSuite) TestCpToStdout(c *check.C) {
   547  	out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /test")
   548  	if exitCode != 0 {
   549  		c.Fatalf("failed to create a container:%s\n", out)
   550  	}
   551  
   552  	cID := strings.TrimSpace(out)
   553  
   554  	out, _ = dockerCmd(c, "wait", cID)
   555  	if strings.TrimSpace(out) != "0" {
   556  		c.Fatalf("failed to set up container:%s\n", out)
   557  	}
   558  
   559  	out, _, err := runCommandPipelineWithOutput(
   560  		exec.Command(dockerBinary, "cp", cID+":/test", "-"),
   561  		exec.Command("tar", "-vtf", "-"))
   562  
   563  	if err != nil {
   564  		c.Fatalf("Failed to run commands: %s", err)
   565  	}
   566  
   567  	if !strings.Contains(out, "test") || !strings.Contains(out, "-rw") {
   568  		c.Fatalf("Missing file from tar TOC:\n%s", out)
   569  	}
   570  }
   571  
   572  func (s *DockerSuite) TestCpNameHasColon(c *check.C) {
   573  	testRequires(c, SameHostDaemon)
   574  
   575  	out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /te:s:t")
   576  	if exitCode != 0 {
   577  		c.Fatal("failed to create a container", out)
   578  	}
   579  
   580  	cleanedContainerID := strings.TrimSpace(out)
   581  
   582  	out, _ = dockerCmd(c, "wait", cleanedContainerID)
   583  	if strings.TrimSpace(out) != "0" {
   584  		c.Fatal("failed to set up container", out)
   585  	}
   586  
   587  	tmpdir, err := ioutil.TempDir("", "docker-integration")
   588  	if err != nil {
   589  		c.Fatal(err)
   590  	}
   591  	defer os.RemoveAll(tmpdir)
   592  	_, _ = dockerCmd(c, "cp", cleanedContainerID+":/te:s:t", tmpdir)
   593  	content, err := ioutil.ReadFile(tmpdir + "/te:s:t")
   594  	if string(content) != "lololol\n" {
   595  		c.Fatalf("Wrong content in copied file %q, should be %q", content, "lololol\n")
   596  	}
   597  }
   598  
   599  func (s *DockerSuite) TestCopyAndRestart(c *check.C) {
   600  	expectedMsg := "hello"
   601  	out, err := exec.Command(dockerBinary, "run", "-d", "busybox", "echo", expectedMsg).CombinedOutput()
   602  	if err != nil {
   603  		c.Fatal(string(out), err)
   604  	}
   605  	id := strings.TrimSpace(string(out))
   606  
   607  	if out, err = exec.Command(dockerBinary, "wait", id).CombinedOutput(); err != nil {
   608  		c.Fatalf("unable to wait for container: %s", err)
   609  	}
   610  
   611  	status := strings.TrimSpace(string(out))
   612  	if status != "0" {
   613  		c.Fatalf("container exited with status %s", status)
   614  	}
   615  
   616  	tmpDir, err := ioutil.TempDir("", "test-docker-restart-after-copy-")
   617  	if err != nil {
   618  		c.Fatalf("unable to make temporary directory: %s", err)
   619  	}
   620  	defer os.RemoveAll(tmpDir)
   621  
   622  	if _, err = exec.Command(dockerBinary, "cp", fmt.Sprintf("%s:/etc/issue", id), tmpDir).CombinedOutput(); err != nil {
   623  		c.Fatalf("unable to copy from busybox container: %s", err)
   624  	}
   625  
   626  	if out, err = exec.Command(dockerBinary, "start", "-a", id).CombinedOutput(); err != nil {
   627  		c.Fatalf("unable to start busybox container after copy: %s - %s", err, out)
   628  	}
   629  
   630  	msg := strings.TrimSpace(string(out))
   631  	if msg != expectedMsg {
   632  		c.Fatalf("expected %q but got %q", expectedMsg, msg)
   633  	}
   634  }