You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

175 lines
3.9 KiB

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