github.com/gsquire/gb@v0.4.4-0.20161112235727-3982dc872064/internal/untar/untar.go (about)

     1  package untar
     2  
     3  import (
     4  	"archive/tar"
     5  	"io"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  
    10  	"github.com/pkg/errors"
    11  )
    12  
    13  // Untar extracts the contents of r to the destination dest.
    14  // dest must not aleady exist.
    15  func Untar(dest string, r io.Reader) error {
    16  	if exists(dest) {
    17  		return errors.Errorf("%q must not exist", dest)
    18  	}
    19  	parent, _ := filepath.Split(dest)
    20  	tmpdir, err := ioutil.TempDir(parent, ".untar")
    21  	if err != nil {
    22  		return err
    23  	}
    24  
    25  	if err := untar(tmpdir, r); err != nil {
    26  		os.RemoveAll(tmpdir)
    27  		return err
    28  	}
    29  
    30  	if err := os.Rename(tmpdir, dest); err != nil {
    31  		os.RemoveAll(tmpdir)
    32  		return err
    33  	}
    34  	return nil
    35  }
    36  
    37  func untar(dest string, r io.Reader) error {
    38  	tr := tar.NewReader(r)
    39  	for {
    40  		h, err := tr.Next()
    41  		if err == io.EOF {
    42  			return nil
    43  		}
    44  		if err != nil {
    45  			return err
    46  		}
    47  		if err := untarfile(dest, h, tr); err != nil {
    48  			return err
    49  		}
    50  	}
    51  	return nil
    52  }
    53  
    54  func untarfile(dest string, h *tar.Header, r io.Reader) error {
    55  	path := filepath.Join(dest, h.Name)
    56  	switch h.Typeflag {
    57  	case tar.TypeDir:
    58  		return os.Mkdir(path, os.FileMode(h.Mode))
    59  	case tar.TypeReg:
    60  		return writefile(path, r, os.FileMode(h.Mode))
    61  	case tar.TypeXGlobalHeader:
    62  		// ignore PAX headers
    63  		return nil
    64  	case tar.TypeSymlink:
    65  		// symlinks are not supported by the go tool or windows so
    66  		// cannot be part of a valie package. Any symlinks in the tarball
    67  		// will be in parts of the release that we can safely ignore.
    68  		return nil
    69  	default:
    70  		return errors.Errorf("unsupported header type: %c", rune(h.Typeflag))
    71  	}
    72  }
    73  
    74  func writefile(path string, r io.Reader, mode os.FileMode) error {
    75  	dir, _ := filepath.Split(path)
    76  	if err := os.MkdirAll(dir, mode); err != nil {
    77  		return errors.Wrap(err, "mkdirall failed")
    78  	}
    79  
    80  	w, err := os.Create(path)
    81  	if err != nil {
    82  		return errors.Wrap(err, "could not create destination")
    83  	}
    84  	if _, err := io.Copy(w, r); err != nil {
    85  		w.Close()
    86  		return err
    87  	}
    88  	return w.Close()
    89  }
    90  
    91  func exists(path string) bool {
    92  	_, err := os.Stat(path)
    93  	return err == nil || !os.IsNotExist(err)
    94  }