aboutsummaryrefslogtreecommitdiff
path: root/src/common/golang/ziputils.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/common/golang/ziputils.go')
-rw-r--r--src/common/golang/ziputils.go193
1 files changed, 193 insertions, 0 deletions
diff --git a/src/common/golang/ziputils.go b/src/common/golang/ziputils.go
new file mode 100644
index 0000000..e1a7af9
--- /dev/null
+++ b/src/common/golang/ziputils.go
@@ -0,0 +1,193 @@
+// 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
+}