1// 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} 175