فهرست منبع

improve untar when using files instead of directories. Specifies behavior on non-existant targets.

Docker-DCO-1.1-Signed-off-by: Tibor Vass <teabee89@gmail.com> (github: tiborvass)
Tibor Vass 11 سال پیش
والد
کامیت
1c8d3106df
2فایلهای تغییر یافته به همراه71 افزوده شده و 4 حذف شده
  1. 33 4
      archive/archive.go
  2. 38 0
      archive/archive_test.go

+ 33 - 4
archive/archive.go

@@ -365,10 +365,12 @@ func TarFilter(srcPath string, options *TarOptions) (io.ReadCloser, error) {
 }
 
 // Untar reads a stream of bytes from `archive`, parses it as a tar archive,
-// and unpacks it into the directory at `path`.
+// and unpacks it into the directory at `dest`.
 // The archive may be compressed with one of the following algorithms:
 //  identity (uncompressed), gzip, bzip2, xz.
-// FIXME: specify behavior when target path exists vs. doesn't exist.
+// If `dest` does not exist, it is created unless there are multiple entries in `archive`.
+// In the latter case, an error is returned.
+// An other error is returned if `dest` exists but is not a directory, to prevent overwriting.
 func Untar(archive io.Reader, dest string, options *TarOptions) error {
 	if archive == nil {
 		return fmt.Errorf("Empty archive")
@@ -382,7 +384,21 @@ func Untar(archive io.Reader, dest string, options *TarOptions) error {
 
 	tr := tar.NewReader(decompressedArchive)
 
-	var dirs []*tar.Header
+	var (
+		dirs            []*tar.Header
+		destNotExist    bool
+		multipleEntries bool
+	)
+
+	if fi, err := os.Lstat(dest); err != nil {
+		if !os.IsNotExist(err) {
+			return err
+		}
+		// destination does not exist, so it is assumed it has to be created.
+		destNotExist = true
+	} else if !fi.IsDir() {
+		return fmt.Errorf("Trying to untar to `%s`: exists but not a directory", dest)
+	}
 
 	// Iterate through the files in the archive.
 	for {
@@ -395,6 +411,11 @@ func Untar(archive io.Reader, dest string, options *TarOptions) error {
 			return err
 		}
 
+		// Return an error if destination needs to be created and there is more than 1 entry in the tar stream.
+		if destNotExist && multipleEntries {
+			return fmt.Errorf("Trying to untar an archive with multiple entries to an inexistant target `%s`: did you mean `%s` instead?", dest, filepath.Dir(dest))
+		}
+
 		// Normalize name, for safety and for a simple is-root check
 		hdr.Name = filepath.Clean(hdr.Name)
 
@@ -410,7 +431,12 @@ func Untar(archive io.Reader, dest string, options *TarOptions) error {
 			}
 		}
 
-		path := filepath.Join(dest, hdr.Name)
+		var path string
+		if destNotExist {
+			path = dest // we are renaming hdr.Name to dest
+		} else {
+			path = filepath.Join(dest, hdr.Name)
+		}
 
 		// If path exits we almost always just want to remove and replace it
 		// The only exception is when it is a directory *and* the file from
@@ -430,6 +456,9 @@ func Untar(archive io.Reader, dest string, options *TarOptions) error {
 			return err
 		}
 
+		// Successfully added an entry. Predicting multiple entries for next iteration (not current one).
+		multipleEntries = true
+
 		// Directory mtimes must be handled at the end to avoid further
 		// file creation in them to modify the directory mtime
 		if hdr.Typeflag == tar.TypeDir {

+ 38 - 0
archive/archive_test.go

@@ -128,6 +128,44 @@ func TestTarUntar(t *testing.T) {
 	}
 }
 
+func TestTarUntarFile(t *testing.T) {
+	origin, err := ioutil.TempDir("", "docker-test-untar-origin-file")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.RemoveAll(origin)
+
+	if err := os.MkdirAll(path.Join(origin, "before"), 0700); err != nil {
+		t.Fatal(err)
+	}
+	if err := os.MkdirAll(path.Join(origin, "after"), 0700); err != nil {
+		t.Fatal(err)
+	}
+	if err := ioutil.WriteFile(path.Join(origin, "before", "file"), []byte("hello world"), 0700); err != nil {
+		t.Fatal(err)
+	}
+
+	tar, err := TarFilter(path.Join(origin, "before"), &TarOptions{Compression: Uncompressed, Includes: []string{"file"}})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if err := Untar(tar, path.Join(origin, "after", "file2"), nil); err != nil {
+		t.Fatal(err)
+	}
+
+	catCmd := exec.Command("cat", path.Join(origin, "after", "file2"))
+	out, err := CmdStream(catCmd, nil)
+	if err != nil {
+		t.Fatalf("Failed to start command: %s", err)
+	}
+	if output, err := ioutil.ReadAll(out); err != nil {
+		t.Error(err)
+	} else if string(output) != "hello world" {
+		t.Fatalf("Expected 'hello world', got '%s'", output)
+	}
+}
+
 // 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