1// Copyright 2018 The Bazel Authors. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15// Package ziputils provides utility functions to work with zip files. 16package ziputils 17 18import ( 19 "archive/zip" 20 "bytes" 21 "io" 22 "io/ioutil" 23 "os" 24 "path/filepath" 25 "strings" 26 "time" 27 28 "golang.org/x/sync/errgroup" 29) 30 31// Empty file contains only the End of central directory record. 0x06054b50 32// https://en.wikipedia.org/wiki/Zip_(file_format) 33var ( 34 emptyzip = append([]byte{0x50, 0x4b, 0x05, 0x06}, make([]byte, 18)...) 35 dirPerm os.FileMode = 0755 36) 37 38// EmptyZipReader wraps an reader whose contents are the empty zip. 39type EmptyZipReader struct { 40 *bytes.Reader 41} 42 43// NewEmptyZipReader creates and returns an EmptyZipReader struct. 44func NewEmptyZipReader() *EmptyZipReader { 45 return &EmptyZipReader{bytes.NewReader(emptyzip)} 46} 47 48// EmptyZip creates empty zip archive. 49func EmptyZip(dst string) error { 50 zipfile, err := os.Create(dst) 51 if err != nil { 52 return err 53 } 54 defer zipfile.Close() 55 _, err = io.Copy(zipfile, NewEmptyZipReader()) 56 return err 57} 58 59// Zip archives src into dst without compression. 60func Zip(src, dst string) error { 61 fi, err := os.Stat(src) 62 if err != nil { 63 return err 64 } 65 66 zipfile, err := os.Create(dst) 67 if err != nil { 68 return err 69 } 70 defer zipfile.Close() 71 72 archive := zip.NewWriter(zipfile) 73 defer archive.Close() 74 75 if !fi.Mode().IsDir() { 76 return WriteFile(archive, src, filepath.Base(src)) 77 } 78 79 return filepath.Walk(src, func(path string, info os.FileInfo, err error) error { 80 if err != nil { 81 return err 82 } 83 84 if info.IsDir() { 85 return nil 86 } 87 88 return WriteFile(archive, path, strings.TrimPrefix(path, src+string(filepath.Separator))) 89 }) 90} 91 92// WriteFile writes filename to the out zip writer. 93func WriteFile(out *zip.Writer, filename, zipFilename string) error { 94 // It's important to set timestamps to zero, otherwise we would break caching for unchanged files 95 f, err := out.CreateHeader(&zip.FileHeader{Name: zipFilename, Method: zip.Store, Modified: time.Unix(0, 0)}) 96 if err != nil { 97 return err 98 } 99 contents, err := ioutil.ReadFile(filename) 100 if err != nil { 101 return err 102 } 103 _, err = f.Write(contents) 104 return err 105} 106 107// WriteReader writes a reader to the out zip writer. 108func WriteReader(out *zip.Writer, in io.Reader, filename string) error { 109 // It's important to set timestamps to zero, otherwise we would break caching for unchanged files 110 f, err := out.CreateHeader(&zip.FileHeader{Name: filename, Method: zip.Store, Modified: time.Unix(0, 0)}) 111 if err != nil { 112 return err 113 } 114 contents, err := ioutil.ReadAll(in) 115 if err != nil { 116 return err 117 } 118 _, err = f.Write(contents) 119 return err 120} 121 122// Unzip expands srcZip in dst directory 123func Unzip(srcZip, dst string) error { 124 reader, err := zip.OpenReader(srcZip) 125 if err != nil { 126 return err 127 } 128 defer reader.Close() 129 130 _, err = os.Stat(dst) 131 if err != nil && !os.IsNotExist(err) { 132 return err 133 } 134 if os.IsNotExist(err) { 135 if err := os.MkdirAll(dst, dirPerm); err != nil { 136 return err 137 } 138 } 139 140 for _, file := range reader.File { 141 path := filepath.Join(dst, file.Name) 142 143 if file.FileInfo().IsDir() { 144 if err := os.MkdirAll(path, dirPerm); err != nil { 145 return err 146 } 147 continue 148 } 149 150 dir := filepath.Dir(path) 151 _, err := os.Stat(dir) 152 if err != nil && !os.IsNotExist(err) { 153 return err 154 } 155 if os.IsNotExist(err) { 156 if err := os.MkdirAll(dir, dirPerm); err != nil { 157 return err 158 } 159 } 160 161 if err := write(file, path); err != nil { 162 return err 163 } 164 } 165 166 return nil 167} 168 169// UnzipParallel expands zip archives in parallel. 170// TODO(b/137549283) Update UnzipParallel and add test 171func UnzipParallel(srcZipDestMap map[string]string) error { 172 var eg errgroup.Group 173 for z, d := range srcZipDestMap { 174 zip, dest := z, d 175 eg.Go(func() error { return Unzip(zip, dest) }) 176 } 177 return eg.Wait() 178} 179 180func write(zf *zip.File, path string) error { 181 rc, err := zf.Open() 182 if err != nil { 183 return err 184 } 185 defer rc.Close() 186 f, err := os.Create(path) 187 if err != nil { 188 return err 189 } 190 defer f.Close() 191 _, err = io.Copy(f, rc) 192 return err 193} 194