github.com/keltia/go-ipfs@v0.3.8-0.20150909044612-210793031c63/thirdparty/tar/extractor.go (about)

     1  package tar
     2  
     3  import (
     4  	"archive/tar"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	gopath "path"
     9  	fp "path/filepath"
    10  	"strings"
    11  )
    12  
    13  type Extractor struct {
    14  	Path string
    15  }
    16  
    17  func (te *Extractor) Extract(reader io.Reader) error {
    18  	tarReader := tar.NewReader(reader)
    19  
    20  	// Check if the output path already exists, so we know whether we should
    21  	// create our output with that name, or if we should put the output inside
    22  	// a preexisting directory
    23  	rootExists := true
    24  	rootIsDir := false
    25  	if stat, err := os.Stat(te.Path); err != nil && os.IsNotExist(err) {
    26  		rootExists = false
    27  	} else if err != nil {
    28  		return err
    29  	} else if stat.IsDir() {
    30  		rootIsDir = true
    31  	}
    32  
    33  	// files come recursively in order (i == 0 is root directory)
    34  	for i := 0; ; i++ {
    35  		header, err := tarReader.Next()
    36  		if err != nil && err != io.EOF {
    37  			return err
    38  		}
    39  		if header == nil || err == io.EOF {
    40  			break
    41  		}
    42  
    43  		switch header.Typeflag {
    44  		case tar.TypeDir:
    45  			if err := te.extractDir(header, i); err != nil {
    46  				return err
    47  			}
    48  		case tar.TypeReg:
    49  			if err := te.extractFile(header, tarReader, i, rootExists, rootIsDir); err != nil {
    50  				return err
    51  			}
    52  		case tar.TypeSymlink:
    53  			if err := te.extractSymlink(header); err != nil {
    54  				return err
    55  			}
    56  		default:
    57  			return fmt.Errorf("unrecognized tar header type: %d", header.Typeflag)
    58  		}
    59  	}
    60  	return nil
    61  }
    62  
    63  // outputPath returns the path at whicht o place tarPath
    64  func (te *Extractor) outputPath(tarPath string) string {
    65  	elems := strings.Split(tarPath, "/") // break into elems
    66  	elems = elems[1:]                    // remove original root
    67  
    68  	path := fp.Join(elems...)     // join elems
    69  	path = fp.Join(te.Path, path) // rebase on extractor root
    70  	return path
    71  }
    72  
    73  func (te *Extractor) extractDir(h *tar.Header, depth int) error {
    74  	path := te.outputPath(h.Name)
    75  
    76  	if depth == 0 {
    77  		// if this is the root root directory, use it as the output path for remaining files
    78  		te.Path = path
    79  	}
    80  
    81  	err := os.MkdirAll(path, 0755)
    82  	if err != nil {
    83  		return err
    84  	}
    85  
    86  	return nil
    87  }
    88  
    89  func (te *Extractor) extractSymlink(h *tar.Header) error {
    90  	return os.Symlink(h.Linkname, te.outputPath(h.Name))
    91  }
    92  
    93  func (te *Extractor) extractFile(h *tar.Header, r *tar.Reader, depth int, rootExists bool, rootIsDir bool) error {
    94  	path := te.outputPath(h.Name)
    95  
    96  	if depth == 0 { // if depth is 0, this is the only file (we aren't 'ipfs get'ing a directory)
    97  		if rootExists && rootIsDir {
    98  			// putting file inside of a root dir.
    99  			fnameo := gopath.Base(h.Name)
   100  			fnamen := fp.Base(path)
   101  			// add back original name if lost.
   102  			if fnameo != fnamen {
   103  				path = fp.Join(path, fnameo)
   104  			}
   105  		} // else if old file exists, just overwrite it.
   106  	}
   107  
   108  	file, err := os.Create(path)
   109  	if err != nil {
   110  		return err
   111  	}
   112  	defer file.Close()
   113  
   114  	_, err = io.Copy(file, r)
   115  	if err != nil {
   116  		return err
   117  	}
   118  
   119  	return nil
   120  }