xref: /aosp_15_r20/external/bazelbuild-rules_android/src/common/golang/ziputils.go (revision 9e965d6fece27a77de5377433c2f7e6999b8cc0b)
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