blob: e1a7af905795c00b5067e37c8b2b1510b618bf58 [file] [log] [blame]
// 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
}