diff --git a/archive.go b/archive.go index 80b305a418..f3f7b8c59e 100644 --- a/archive.go +++ b/archive.go @@ -80,21 +80,73 @@ func (compression *Compression) Extension() string { // Tar creates an archive from the directory at `path`, and returns it as a // stream of bytes. func Tar(path string, compression Compression) (io.Reader, error) { - return TarFilter(path, compression, nil) + return TarFilter(path, compression, nil, true, nil) +} + +func escapeName(name string) string { + escaped := make([]byte,0) + for i, c := range []byte(name) { + if i == 0 && c == '/' { + continue + } + // all printable chars except "-" which is 0x2d + if (0x20 <= c && c <= 0x7E) && c != 0x2d { + escaped = append(escaped, c) + } else { + escaped = append(escaped, fmt.Sprintf("\\%03o", c)...) + } + } + return string(escaped) } // Tar creates an archive from the directory at `path`, only including files whose relative // paths are included in `filter`. If `filter` is nil, then all files are included. -func TarFilter(path string, compression Compression, filter []string) (io.Reader, error) { - args := []string{"tar", "--numeric-owner", "-f", "-", "-C", path} +func TarFilter(path string, compression Compression, filter []string, recursive bool, createFiles []string) (io.Reader, error) { + args := []string{"tar", "--numeric-owner", "-f", "-", "-C", path, "-T", "-",} if filter == nil { filter = []string{"."} } args = append(args, "-c"+compression.Flag()) - for _, f := range filter { - args = append(args, f) + + if !recursive { + args = append(args, "--no-recursion") } - return CmdStream(exec.Command(args[0], args[1:]...)) + + files := "" + for _, f := range filter { + files = files + escapeName(f) + "\n" + } + + tmpDir := "" + + if createFiles != nil { + tmpDir, err := ioutil.TempDir("", "docker-tar") + if err != nil { + return nil, err + } + + files = files + "-C" + tmpDir + "\n" + for _, f := range createFiles { + path := filepath.Join(tmpDir, f) + err := os.MkdirAll(filepath.Dir(path), 0600) + if err != nil { + return nil, err + } + + if file, err := os.OpenFile(path, os.O_CREATE, 0600); err != nil { + return nil, err + } else { + file.Close() + } + files = files + escapeName(f) + "\n" + } + } + + return CmdStream(exec.Command(args[0], args[1:]...), &files, func () { + if tmpDir != "" { + _ = os.RemoveAll(tmpDir) + } + }) } // Untar reads a stream of bytes from `archive`, parses it as a tar archive, @@ -141,7 +193,7 @@ func Untar(archive io.Reader, path string) error { // TarUntar aborts and returns the error. func TarUntar(src string, filter []string, dst string) error { utils.Debugf("TarUntar(%s %s %s)", src, filter, dst) - archive, err := TarFilter(src, Uncompressed, filter) + archive, err := TarFilter(src, Uncompressed, filter, true, nil) if err != nil { return err } @@ -228,7 +280,18 @@ func CopyFileWithTar(src, dst string) error { // CmdStream executes a command, and returns its stdout as a stream. // If the command fails to run or doesn't complete successfully, an error // will be returned, including anything written on stderr. -func CmdStream(cmd *exec.Cmd) (io.Reader, error) { +func CmdStream(cmd *exec.Cmd, input *string, atEnd func()) (io.Reader, error) { + if input != nil { + stdin, err := cmd.StdinPipe() + if err != nil { + return nil, err + } + // Write stdin if any + go func() { + _, _ = stdin.Write([]byte(*input)) + stdin.Close() + }() + } stdout, err := cmd.StdoutPipe() if err != nil { return nil, err @@ -259,6 +322,9 @@ func CmdStream(cmd *exec.Cmd) (io.Reader, error) { } else { pipeW.Close() } + if atEnd != nil { + atEnd() + } }() // Run the command and return the pipe if err := cmd.Start(); err != nil { diff --git a/archive_test.go b/archive_test.go index 9a0a8e1b9e..c86b4511c4 100644 --- a/archive_test.go +++ b/archive_test.go @@ -14,7 +14,7 @@ import ( func TestCmdStreamLargeStderr(t *testing.T) { cmd := exec.Command("/bin/sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello") - out, err := CmdStream(cmd) + out, err := CmdStream(cmd, nil, nil) if err != nil { t.Fatalf("Failed to start command: %s", err) } @@ -35,7 +35,7 @@ func TestCmdStreamLargeStderr(t *testing.T) { func TestCmdStreamBad(t *testing.T) { badCmd := exec.Command("/bin/sh", "-c", "echo hello; echo >&2 error couldn\\'t reverse the phase pulser; exit 1") - out, err := CmdStream(badCmd) + out, err := CmdStream(badCmd, nil, nil) if err != nil { t.Fatalf("Failed to start command: %s", err) } @@ -50,7 +50,7 @@ func TestCmdStreamBad(t *testing.T) { func TestCmdStreamGood(t *testing.T) { cmd := exec.Command("/bin/sh", "-c", "echo hello; exit 0") - out, err := CmdStream(cmd) + out, err := CmdStream(cmd, nil, nil) if err != nil { t.Fatal(err) } diff --git a/container.go b/container.go index 33b410ee5a..bd1d5a41a5 100644 --- a/container.go +++ b/container.go @@ -1277,5 +1277,5 @@ func (container *Container) Copy(resource string) (Archive, error) { filter = []string{path.Base(basePath)} basePath = path.Dir(basePath) } - return TarFilter(basePath, Uncompressed, filter) + return TarFilter(basePath, Uncompressed, filter, true, nil) }