blob: 08ce889a4f482cf8e2a9c69a8980a204fa4d632e [file] [log] [blame]
Colin Crossc45c3b52019-03-26 15:50:03 -07001// Copyright 2019 Google Inc. 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
15package main
16
17import (
18 "archive/zip"
19 "context"
20 "fmt"
21 "hash/crc32"
22 "io"
23 "io/ioutil"
24 "os"
25 "path/filepath"
26)
27
28// ZipArtifact represents a zip file that may be local or remote.
29type ZipArtifact interface {
30 // Files returns the list of files contained in the zip file.
31 Files() ([]*ZipArtifactFile, error)
32
33 // Close closes the zip file artifact.
34 Close()
35}
36
37// localZipArtifact is a handle to a local zip file artifact.
38type localZipArtifact struct {
39 zr *zip.ReadCloser
40 files []*ZipArtifactFile
41}
42
43// NewLocalZipArtifact returns a ZipArtifact for a local zip file..
44func NewLocalZipArtifact(name string) (ZipArtifact, error) {
45 zr, err := zip.OpenReader(name)
46 if err != nil {
47 return nil, err
48 }
49
50 var files []*ZipArtifactFile
51 for _, zf := range zr.File {
52 files = append(files, &ZipArtifactFile{zf})
53 }
54
55 return &localZipArtifact{
56 zr: zr,
57 files: files,
58 }, nil
59}
60
61// Files returns the list of files contained in the local zip file artifact.
62func (z *localZipArtifact) Files() ([]*ZipArtifactFile, error) {
63 return z.files, nil
64}
65
66// Close closes the buffered reader of the local zip file artifact.
67func (z *localZipArtifact) Close() {
68 z.zr.Close()
69}
70
71// ZipArtifactFile contains a zip.File handle to the data inside the remote *-target_files-*.zip
72// build artifact.
73type ZipArtifactFile struct {
74 *zip.File
75}
76
77// Extract begins extract a file from inside a ZipArtifact. It returns an
78// ExtractedZipArtifactFile handle.
79func (zf *ZipArtifactFile) Extract(ctx context.Context, dir string,
80 limiter chan bool) *ExtractedZipArtifactFile {
81
82 d := &ExtractedZipArtifactFile{
83 initCh: make(chan struct{}),
84 }
85
86 go func() {
87 defer close(d.initCh)
88 limiter <- true
89 defer func() { <-limiter }()
90
91 zr, err := zf.Open()
92 if err != nil {
93 d.err = err
94 return
95 }
96 defer zr.Close()
97
98 crc := crc32.NewIEEE()
99 r := io.TeeReader(zr, crc)
100
101 if filepath.Clean(zf.Name) != zf.Name {
102 d.err = fmt.Errorf("invalid filename %q", zf.Name)
103 return
104 }
105 path := filepath.Join(dir, zf.Name)
106
107 err = os.MkdirAll(filepath.Dir(path), 0777)
108 if err != nil {
109 d.err = err
110 return
111 }
112
113 err = os.Remove(path)
114 if err != nil && !os.IsNotExist(err) {
115 d.err = err
116 return
117 }
118
119 if zf.Mode().IsRegular() {
120 w, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, zf.Mode())
121 if err != nil {
122 d.err = err
123 return
124 }
125 defer w.Close()
126
127 _, err = io.Copy(w, r)
128 if err != nil {
129 d.err = err
130 return
131 }
132 } else if zf.Mode()&os.ModeSymlink != 0 {
133 target, err := ioutil.ReadAll(r)
134 if err != nil {
135 d.err = err
136 return
137 }
138
139 err = os.Symlink(string(target), path)
140 if err != nil {
141 d.err = err
142 return
143 }
144 } else {
145 d.err = fmt.Errorf("unknown mode %q", zf.Mode())
146 return
147 }
148
149 if crc.Sum32() != zf.CRC32 {
150 d.err = fmt.Errorf("crc mismatch for %v", zf.Name)
151 return
152 }
153
154 d.path = path
155 }()
156
157 return d
158}
159
160// ExtractedZipArtifactFile is a handle to a downloaded file from a remoteZipArtifact. The download
161// may still be in progress, and will be complete with Path() returns.
162type ExtractedZipArtifactFile struct {
163 initCh chan struct{}
164 err error
165
166 path string
167}
168
169// Path returns the path to the downloaded file and any errors that occurred during the download.
170// It will block until the download is complete.
171func (d *ExtractedZipArtifactFile) Path() (string, error) {
172 <-d.initCh
173 return d.path, d.err
174}