github.com/theQRL/go-zond@v0.1.1/internal/build/archive.go (about) 1 // Copyright 2016 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package build 18 19 import ( 20 "archive/tar" 21 "archive/zip" 22 "compress/gzip" 23 "errors" 24 "fmt" 25 "io" 26 "os" 27 "path/filepath" 28 "strings" 29 "time" 30 ) 31 32 type Archive interface { 33 // Directory adds a new directory entry to the archive and sets the 34 // directory for subsequent calls to Header. 35 Directory(name string) error 36 37 // Header adds a new file to the archive. The file is added to the directory 38 // set by Directory. The content of the file must be written to the returned 39 // writer. 40 Header(os.FileInfo) (io.Writer, error) 41 42 // Close flushes the archive and closes the underlying file. 43 Close() error 44 } 45 46 func NewArchive(file *os.File) (Archive, string) { 47 switch { 48 case strings.HasSuffix(file.Name(), ".zip"): 49 return NewZipArchive(file), strings.TrimSuffix(file.Name(), ".zip") 50 case strings.HasSuffix(file.Name(), ".tar.gz"): 51 return NewTarballArchive(file), strings.TrimSuffix(file.Name(), ".tar.gz") 52 default: 53 return nil, "" 54 } 55 } 56 57 // AddFile appends an existing file to an archive. 58 func AddFile(a Archive, file string) error { 59 fd, err := os.Open(file) 60 if err != nil { 61 return err 62 } 63 defer fd.Close() 64 fi, err := fd.Stat() 65 if err != nil { 66 return err 67 } 68 w, err := a.Header(fi) 69 if err != nil { 70 return err 71 } 72 if _, err := io.Copy(w, fd); err != nil { 73 return err 74 } 75 return nil 76 } 77 78 // WriteArchive creates an archive containing the given files. 79 func WriteArchive(name string, files []string) (err error) { 80 archfd, err := os.Create(name) 81 if err != nil { 82 return err 83 } 84 85 defer func() { 86 archfd.Close() 87 // Remove the half-written archive on failure. 88 if err != nil { 89 os.Remove(name) 90 } 91 }() 92 archive, basename := NewArchive(archfd) 93 if archive == nil { 94 return errors.New("unknown archive extension") 95 } 96 fmt.Println(name) 97 if err := archive.Directory(basename); err != nil { 98 return err 99 } 100 for _, file := range files { 101 fmt.Println(" +", filepath.Base(file)) 102 if err := AddFile(archive, file); err != nil { 103 return err 104 } 105 } 106 return archive.Close() 107 } 108 109 type ZipArchive struct { 110 dir string 111 zipw *zip.Writer 112 file io.Closer 113 } 114 115 func NewZipArchive(w io.WriteCloser) Archive { 116 return &ZipArchive{"", zip.NewWriter(w), w} 117 } 118 119 func (a *ZipArchive) Directory(name string) error { 120 a.dir = name + "/" 121 return nil 122 } 123 124 func (a *ZipArchive) Header(fi os.FileInfo) (io.Writer, error) { 125 head, err := zip.FileInfoHeader(fi) 126 if err != nil { 127 return nil, fmt.Errorf("can't make zip header: %v", err) 128 } 129 head.Name = a.dir + head.Name 130 head.Method = zip.Deflate 131 w, err := a.zipw.CreateHeader(head) 132 if err != nil { 133 return nil, fmt.Errorf("can't add zip header: %v", err) 134 } 135 return w, nil 136 } 137 138 func (a *ZipArchive) Close() error { 139 if err := a.zipw.Close(); err != nil { 140 return err 141 } 142 return a.file.Close() 143 } 144 145 type TarballArchive struct { 146 dir string 147 tarw *tar.Writer 148 gzw *gzip.Writer 149 file io.Closer 150 } 151 152 func NewTarballArchive(w io.WriteCloser) Archive { 153 gzw := gzip.NewWriter(w) 154 tarw := tar.NewWriter(gzw) 155 return &TarballArchive{"", tarw, gzw, w} 156 } 157 158 func (a *TarballArchive) Directory(name string) error { 159 a.dir = name + "/" 160 return a.tarw.WriteHeader(&tar.Header{ 161 Name: a.dir, 162 Mode: 0755, 163 Typeflag: tar.TypeDir, 164 ModTime: time.Now(), 165 }) 166 } 167 168 func (a *TarballArchive) Header(fi os.FileInfo) (io.Writer, error) { 169 head, err := tar.FileInfoHeader(fi, "") 170 if err != nil { 171 return nil, fmt.Errorf("can't make tar header: %v", err) 172 } 173 head.Name = a.dir + head.Name 174 if err := a.tarw.WriteHeader(head); err != nil { 175 return nil, fmt.Errorf("can't add tar header: %v", err) 176 } 177 return a.tarw, nil 178 } 179 180 func (a *TarballArchive) Close() error { 181 if err := a.tarw.Close(); err != nil { 182 return err 183 } 184 if err := a.gzw.Close(); err != nil { 185 return err 186 } 187 return a.file.Close() 188 } 189 190 // ExtractArchive unpacks a .zip or .tar.gz archive to the destination directory. 191 func ExtractArchive(archive string, dest string) error { 192 ar, err := os.Open(archive) 193 if err != nil { 194 return err 195 } 196 defer ar.Close() 197 198 switch { 199 case strings.HasSuffix(archive, ".tar.gz"): 200 return extractTarball(ar, dest) 201 case strings.HasSuffix(archive, ".zip"): 202 return extractZip(ar, dest) 203 default: 204 return fmt.Errorf("unhandled archive type %s", archive) 205 } 206 } 207 208 // extractTarball unpacks a .tar.gz file. 209 func extractTarball(ar io.Reader, dest string) error { 210 gzr, err := gzip.NewReader(ar) 211 if err != nil { 212 return err 213 } 214 defer gzr.Close() 215 216 tr := tar.NewReader(gzr) 217 for { 218 // Move to the next file header. 219 header, err := tr.Next() 220 if err != nil { 221 if err == io.EOF { 222 return nil 223 } 224 return err 225 } 226 // We only care about regular files, directory modes 227 // and special file types are not supported. 228 if header.Typeflag == tar.TypeReg { 229 armode := header.FileInfo().Mode() 230 err := extractFile(header.Name, armode, tr, dest) 231 if err != nil { 232 return fmt.Errorf("extract %s: %v", header.Name, err) 233 } 234 } 235 } 236 } 237 238 // extractZip unpacks the given .zip file. 239 func extractZip(ar *os.File, dest string) error { 240 info, err := ar.Stat() 241 if err != nil { 242 return err 243 } 244 zr, err := zip.NewReader(ar, info.Size()) 245 if err != nil { 246 return err 247 } 248 249 for _, zf := range zr.File { 250 if !zf.Mode().IsRegular() { 251 continue 252 } 253 254 data, err := zf.Open() 255 if err != nil { 256 return err 257 } 258 err = extractFile(zf.Name, zf.Mode(), data, dest) 259 data.Close() 260 if err != nil { 261 return fmt.Errorf("extract %s: %v", zf.Name, err) 262 } 263 } 264 return nil 265 } 266 267 // extractFile extracts a single file from an archive. 268 func extractFile(arpath string, armode os.FileMode, data io.Reader, dest string) error { 269 // Check that path is inside destination directory. 270 target := filepath.Join(dest, filepath.FromSlash(arpath)) 271 if !strings.HasPrefix(target, filepath.Clean(dest)+string(os.PathSeparator)) { 272 return fmt.Errorf("path %q escapes archive destination", target) 273 } 274 275 // Ensure the destination directory exists. 276 if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil { 277 return err 278 } 279 280 // Copy file data. 281 file, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, armode) 282 if err != nil { 283 return err 284 } 285 if _, err := io.Copy(file, data); err != nil { 286 file.Close() 287 os.Remove(target) 288 return err 289 } 290 return file.Close() 291 }