// Copyright 2019 Google Inc. 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 main import ( "archive/zip" "context" "fmt" "hash/crc32" "io" "io/ioutil" "os" "path/filepath" ) // ZipArtifact represents a zip file that may be local or remote. type ZipArtifact interface { // Files returns the list of files contained in the zip file. Files() ([]*ZipArtifactFile, error) // Close closes the zip file artifact. Close() } // localZipArtifact is a handle to a local zip file artifact. type localZipArtifact struct { zr *zip.ReadCloser files []*ZipArtifactFile } // NewLocalZipArtifact returns a ZipArtifact for a local zip file.. func NewLocalZipArtifact(name string) (ZipArtifact, error) { zr, err := zip.OpenReader(name) if err != nil { return nil, err } var files []*ZipArtifactFile for _, zf := range zr.File { files = append(files, &ZipArtifactFile{zf}) } return &localZipArtifact{ zr: zr, files: files, }, nil } // Files returns the list of files contained in the local zip file artifact. func (z *localZipArtifact) Files() ([]*ZipArtifactFile, error) { return z.files, nil } // Close closes the buffered reader of the local zip file artifact. func (z *localZipArtifact) Close() { z.zr.Close() } // ZipArtifactFile contains a zip.File handle to the data inside the remote *-target_files-*.zip // build artifact. type ZipArtifactFile struct { *zip.File } // Extract begins extract a file from inside a ZipArtifact. It returns an // ExtractedZipArtifactFile handle. func (zf *ZipArtifactFile) Extract(ctx context.Context, dir string, limiter chan bool) *ExtractedZipArtifactFile { d := &ExtractedZipArtifactFile{ initCh: make(chan struct{}), } go func() { defer close(d.initCh) limiter <- true defer func() { <-limiter }() zr, err := zf.Open() if err != nil { d.err = err return } defer zr.Close() crc := crc32.NewIEEE() r := io.TeeReader(zr, crc) if filepath.Clean(zf.Name) != zf.Name { d.err = fmt.Errorf("invalid filename %q", zf.Name) return } path := filepath.Join(dir, zf.Name) err = os.MkdirAll(filepath.Dir(path), 0777) if err != nil { d.err = err return } err = os.Remove(path) if err != nil && !os.IsNotExist(err) { d.err = err return } if zf.Mode().IsRegular() { w, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, zf.Mode()) if err != nil { d.err = err return } defer w.Close() _, err = io.Copy(w, r) if err != nil { d.err = err return } } else if zf.Mode()&os.ModeSymlink != 0 { target, err := ioutil.ReadAll(r) if err != nil { d.err = err return } err = os.Symlink(string(target), path) if err != nil { d.err = err return } } else { d.err = fmt.Errorf("unknown mode %q", zf.Mode()) return } if crc.Sum32() != zf.CRC32 { d.err = fmt.Errorf("crc mismatch for %v", zf.Name) return } d.path = path }() return d } // ExtractedZipArtifactFile is a handle to a downloaded file from a remoteZipArtifact. The download // may still be in progress, and will be complete with Path() returns. type ExtractedZipArtifactFile struct { initCh chan struct{} err error path string } // Path returns the path to the downloaded file and any errors that occurred during the download. // It will block until the download is complete. func (d *ExtractedZipArtifactFile) Path() (string, error) { <-d.initCh return d.path, d.err }