github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/internal/build/archive.go (about) 1 // Copyright 2018 The go-ethereum Authors 2 // Copyright 2019 The go-aigar Authors 3 // This file is part of the go-aigar library. 4 // 5 // The go-aigar library is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Lesser General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // The go-aigar library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public License 16 // along with the go-aigar library. If not, see <http://www.gnu.org/licenses/>. 17 18 package build 19 20 import ( 21 "archive/tar" 22 "archive/zip" 23 "compress/gzip" 24 "fmt" 25 "io" 26 "os" 27 "path/filepath" 28 "strings" 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 }) 164 } 165 166 func (a *TarballArchive) Header(fi os.FileInfo) (io.Writer, error) { 167 head, err := tar.FileInfoHeader(fi, "") 168 if err != nil { 169 return nil, fmt.Errorf("can't make tar header: %v", err) 170 } 171 head.Name = a.dir + head.Name 172 if err := a.tarw.WriteHeader(head); err != nil { 173 return nil, fmt.Errorf("can't add tar header: %v", err) 174 } 175 return a.tarw, nil 176 } 177 178 func (a *TarballArchive) Close() error { 179 if err := a.tarw.Close(); err != nil { 180 return err 181 } 182 if err := a.gzw.Close(); err != nil { 183 return err 184 } 185 return a.file.Close() 186 } 187 188 func ExtractTarballArchive(archive string, dest string) error { 189 // We're only interested in gzipped archives, wrap the reader now 190 ar, err := os.Open(archive) 191 if err != nil { 192 return err 193 } 194 defer ar.Close() 195 196 gzr, err := gzip.NewReader(ar) 197 if err != nil { 198 return err 199 } 200 defer gzr.Close() 201 202 // Iterate over all the files in the tarball 203 tr := tar.NewReader(gzr) 204 for { 205 // Fetch the next tarball header and abort if needed 206 header, err := tr.Next() 207 if err != nil { 208 if err == io.EOF { 209 return nil 210 } 211 return err 212 } 213 // Figure out the target and create it 214 target := filepath.Join(dest, header.Name) 215 216 switch header.Typeflag { 217 case tar.TypeDir: 218 if err := os.MkdirAll(target, 0755); err != nil { 219 return err 220 } 221 case tar.TypeReg: 222 file, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)) 223 if err != nil { 224 return err 225 } 226 if _, err := io.Copy(file, tr); err != nil { 227 return err 228 } 229 file.Close() 230 } 231 } 232 }