|
@@ -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 {
|