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 }