github.com/drone/drone-cache-lib@v0.0.0-20200806063744-981868645a25/archive/tar/tar.go (about)

     1  package tar
     2  
     3  // special thanks to this medium article:
     4  // https://medium.com/@skdomino/taring-untaring-files-in-go-6b07cf56bc07
     5  
     6  import (
     7  	"archive/tar"
     8  	"fmt"
     9  	"io"
    10  	"os"
    11  	"path/filepath"
    12  	"strings"
    13  	"time"
    14  
    15  	log "github.com/sirupsen/logrus"
    16  	"github.com/drone/drone-cache-lib/archive"
    17  )
    18  
    19  type tarArchive struct{}
    20  
    21  // New creates an archive that uses the .tar file format.
    22  func New() archive.Archive {
    23  	return &tarArchive{}
    24  }
    25  
    26  func (a *tarArchive) Pack(srcs []string, w io.Writer) error {
    27  	tw := tar.NewWriter(w)
    28  	defer tw.Close()
    29  
    30  	// Loop through each source
    31  	var fwErr error
    32  	for _, s := range srcs {
    33  		// ensure the src actually exists before trying to tar it
    34  		if _, err := os.Stat(s); err != nil {
    35  			return err
    36  		}
    37  
    38  		// walk path
    39  		fwErr = filepath.Walk(s, func(path string, fi os.FileInfo, err error) error {
    40  			if err != nil {
    41  				return err
    42  			}
    43  
    44  			header, err := tar.FileInfoHeader(fi, fi.Name())
    45  			if err != nil {
    46  				return err
    47  			}
    48  
    49  			var link string
    50  			if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
    51  				if link, err = os.Readlink(path); err != nil {
    52  					return err
    53  				}
    54  				log.Debugf("Symbolic link found at %s to %s", path, link)
    55  
    56  				// Rewrite header for SymLink
    57  				header, err = tar.FileInfoHeader(fi, link)
    58  				if err != nil {
    59  					return err
    60  				}
    61  			}
    62  
    63  			header.Name = strings.TrimPrefix(filepath.ToSlash(path), "/")
    64  
    65  			if err = tw.WriteHeader(header); err != nil {
    66  				return err
    67  			}
    68  
    69  			if !fi.Mode().IsRegular() {
    70  				log.Debugf("Directory found at %s", path)
    71  				return nil
    72  			}
    73  
    74  			log.Debugf("File found at %s", path)
    75  
    76  			file, err := os.Open(path)
    77  			if err != nil {
    78  				return err
    79  			}
    80  
    81  			defer file.Close()
    82  			_, err = io.Copy(tw, file)
    83  			return err
    84  		})
    85  
    86  		if fwErr != nil {
    87  			return fwErr
    88  		}
    89  	}
    90  
    91  	return fwErr
    92  }
    93  
    94  func (a *tarArchive) Unpack(dst string, r io.Reader) error {
    95  	tr := tar.NewReader(r)
    96  
    97  	for {
    98  		header, err := tr.Next()
    99  
   100  		switch {
   101  
   102  		// if no more files are found return
   103  		case err == io.EOF:
   104  			return nil
   105  
   106  		// return any other error
   107  		case err != nil:
   108  			return err
   109  
   110  		// if the header is nil, just skip it (not sure how this happens)
   111  		case header == nil:
   112  			continue
   113  		}
   114  
   115  		// the target location where the dir/file should be created
   116  		target := filepath.Join(dst, header.Name)
   117  
   118  		// the following switch could also be done using fi.Mode(), not sure if there
   119  		// a benefit of using one vs. the other.
   120  		// fi := header.FileInfo()
   121  
   122  		// check the file type
   123  		switch header.Typeflag {
   124  
   125  		// if its a symlink and it doesn't exist create it
   126  		case tar.TypeSymlink:
   127  			log.Debugf("Symlink found at %s", target)
   128  
   129  			// Check if something already exists
   130  			_, err := os.Stat(target)
   131  			if err == nil {
   132  				return fmt.Errorf("Failed to create symlink because file already exists at %s", target)
   133  			}
   134  
   135  			// Create the link
   136  			log.Debugf("Creating link %s to %s", target, header.Linkname)
   137  			err = os.Symlink(header.Linkname, target)
   138  
   139  			if err != nil {
   140  				log.Infof("Failed creating link %s to %s", target, header.Linkname)
   141  				return err
   142  			}
   143  
   144  		// if its a dir and it doesn't exist create it
   145  		case tar.TypeDir:
   146  			log.Debugf("Directory found at %s", target)
   147  			if _, err := os.Stat(target); err != nil {
   148  				if err := os.MkdirAll(target, 0755); err != nil {
   149  					return err
   150  				}
   151  			}
   152  
   153  		// if it's a file create it
   154  		case tar.TypeReg:
   155  			log.Debugf("File found at %s", target)
   156  			f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
   157  			if err != nil {
   158  				return err
   159  			}
   160  
   161  			// copy over contents
   162  			_, err = io.Copy(f, tr)
   163  
   164  			// Explicitly close otherwise too many files remain open
   165  			f.Close()
   166  
   167  			if err != nil {
   168  				return err
   169  			}
   170  
   171  			err = os.Chtimes(target, time.Now(), header.ModTime)
   172  
   173  			if err != nil {
   174  				return err
   175  			}
   176  		}
   177  	}
   178  }