| // Copyright 2018 The Bazel Authors. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| // Package ziputils provides utility functions to work with zip files. |
| package ziputils |
| |
| import ( |
| "archive/zip" |
| "bytes" |
| "io" |
| "io/ioutil" |
| "os" |
| "path/filepath" |
| "strings" |
| "time" |
| |
| "golang.org/x/sync/errgroup" |
| ) |
| |
| // Empty file contains only the End of central directory record. 0x06054b50 |
| // https://en.wikipedia.org/wiki/Zip_(file_format) |
| var ( |
| emptyzip = append([]byte{0x50, 0x4b, 0x05, 0x06}, make([]byte, 18)...) |
| dirPerm os.FileMode = 0755 |
| ) |
| |
| // EmptyZipReader wraps an reader whose contents are the empty zip. |
| type EmptyZipReader struct { |
| *bytes.Reader |
| } |
| |
| // NewEmptyZipReader creates and returns an EmptyZipReader struct. |
| func NewEmptyZipReader() *EmptyZipReader { |
| return &EmptyZipReader{bytes.NewReader(emptyzip)} |
| } |
| |
| // EmptyZip creates empty zip archive. |
| func EmptyZip(dst string) error { |
| zipfile, err := os.Create(dst) |
| if err != nil { |
| return err |
| } |
| defer zipfile.Close() |
| _, err = io.Copy(zipfile, NewEmptyZipReader()) |
| return err |
| } |
| |
| // Zip archives src into dst without compression. |
| func Zip(src, dst string) error { |
| fi, err := os.Stat(src) |
| if err != nil { |
| return err |
| } |
| |
| zipfile, err := os.Create(dst) |
| if err != nil { |
| return err |
| } |
| defer zipfile.Close() |
| |
| archive := zip.NewWriter(zipfile) |
| defer archive.Close() |
| |
| if !fi.Mode().IsDir() { |
| return WriteFile(archive, src, filepath.Base(src)) |
| } |
| |
| return filepath.Walk(src, func(path string, info os.FileInfo, err error) error { |
| if err != nil { |
| return err |
| } |
| |
| if info.IsDir() { |
| return nil |
| } |
| |
| return WriteFile(archive, path, strings.TrimPrefix(path, src+string(filepath.Separator))) |
| }) |
| } |
| |
| // WriteFile writes filename to the out zip writer. |
| func WriteFile(out *zip.Writer, filename, zipFilename string) error { |
| // It's important to set timestamps to zero, otherwise we would break caching for unchanged files |
| f, err := out.CreateHeader(&zip.FileHeader{Name: zipFilename, Method: zip.Store, Modified: time.Unix(0, 0)}) |
| if err != nil { |
| return err |
| } |
| contents, err := ioutil.ReadFile(filename) |
| if err != nil { |
| return err |
| } |
| _, err = f.Write(contents) |
| return err |
| } |
| |
| // WriteReader writes a reader to the out zip writer. |
| func WriteReader(out *zip.Writer, in io.Reader, filename string) error { |
| // It's important to set timestamps to zero, otherwise we would break caching for unchanged files |
| f, err := out.CreateHeader(&zip.FileHeader{Name: filename, Method: zip.Store, Modified: time.Unix(0, 0)}) |
| if err != nil { |
| return err |
| } |
| contents, err := ioutil.ReadAll(in) |
| if err != nil { |
| return err |
| } |
| _, err = f.Write(contents) |
| return err |
| } |
| |
| // Unzip expands srcZip in dst directory |
| func Unzip(srcZip, dst string) error { |
| reader, err := zip.OpenReader(srcZip) |
| if err != nil { |
| return err |
| } |
| defer reader.Close() |
| |
| _, err = os.Stat(dst) |
| if err != nil && !os.IsNotExist(err) { |
| return err |
| } |
| if os.IsNotExist(err) { |
| if err := os.MkdirAll(dst, dirPerm); err != nil { |
| return err |
| } |
| } |
| |
| for _, file := range reader.File { |
| path := filepath.Join(dst, file.Name) |
| |
| if file.FileInfo().IsDir() { |
| if err := os.MkdirAll(path, dirPerm); err != nil { |
| return err |
| } |
| continue |
| } |
| |
| dir := filepath.Dir(path) |
| _, err := os.Stat(dir) |
| if err != nil && !os.IsNotExist(err) { |
| return err |
| } |
| if os.IsNotExist(err) { |
| if err := os.MkdirAll(dir, dirPerm); err != nil { |
| return err |
| } |
| } |
| |
| if err := write(file, path); err != nil { |
| return err |
| } |
| } |
| |
| return nil |
| } |
| |
| // UnzipParallel expands zip archives in parallel. |
| // TODO(b/137549283) Update UnzipParallel and add test |
| func UnzipParallel(srcZipDestMap map[string]string) error { |
| var eg errgroup.Group |
| for z, d := range srcZipDestMap { |
| zip, dest := z, d |
| eg.Go(func() error { return Unzip(zip, dest) }) |
| } |
| return eg.Wait() |
| } |
| |
| func write(zf *zip.File, path string) error { |
| rc, err := zf.Open() |
| if err != nil { |
| return err |
| } |
| defer rc.Close() |
| f, err := os.Create(path) |
| if err != nil { |
| return err |
| } |
| defer f.Close() |
| _, err = io.Copy(f, rc) |
| return err |
| } |