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