github.com/kekek/gb@v0.4.5-0.20170222120241-d4ba64b0b297/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  }
    52  
    53  func untarfile(dest string, h *tar.Header, r io.Reader) error {
    54  	path := filepath.Join(dest, h.Name)
    55  	switch h.Typeflag {
    56  	case tar.TypeDir:
    57  		return os.Mkdir(path, os.FileMode(h.Mode))
    58  	case tar.TypeReg:
    59  		return writefile(path, r, os.FileMode(h.Mode))
    60  	case tar.TypeXGlobalHeader:
    61  		// ignore PAX headers
    62  		return nil
    63  	case tar.TypeSymlink:
    64  		// symlinks are not supported by the go tool or windows so
    65  		// cannot be part of a valie package. Any symlinks in the tarball
    66  		// will be in parts of the release that we can safely ignore.
    67  		return nil
    68  	default:
    69  		return errors.Errorf("unsupported header type: %c", rune(h.Typeflag))
    70  	}
    71  }
    72  
    73  func writefile(path string, r io.Reader, mode os.FileMode) error {
    74  	dir, _ := filepath.Split(path)
    75  	if err := os.MkdirAll(dir, mode); err != nil {
    76  		return errors.Wrap(err, "mkdirall failed")
    77  	}
    78  
    79  	w, err := os.Create(path)
    80  	if err != nil {
    81  		return errors.Wrap(err, "could not create destination")
    82  	}
    83  	if _, err := io.Copy(w, r); err != nil {
    84  		w.Close()
    85  		return err
    86  	}
    87  	return w.Close()
    88  }
    89  
    90  func exists(path string) bool {
    91  	_, err := os.Stat(path)
    92  	return err == nil || !os.IsNotExist(err)
    93  }