blob: f0999c05ee00c42d3d782d3d6912be8800026d94 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package artifacts
import (
"archive/tar"
"compress/bzip2"
"io"
"os"
"path/filepath"
"go.chromium.org/luci/common/errors"
)
// LocalPaths points to locally downloaded copies of Autotest build artifacts.
type LocalPaths struct {
// Path to test_suites.tar.bz2
// This archive contains the test suite control files and is ~4 MiB.
TestSuitesArchive string
// Path to downloaded control_files.tar
// This archive contains the test control files and is ~10 MiB
ControlFilesArchive string
}
// ExtractControlFiles extracts Autotest control files from build artifacts
// downloaded locally.
//
// On success, outdir contains the unarchived control files.
func ExtractControlFiles(paths LocalPaths, outdir string) error {
var merr errors.MultiError
if paths.ControlFilesArchive != "" {
merr = append(merr, unarchiveControlFiles(paths.ControlFilesArchive, outdir))
}
if paths.TestSuitesArchive != "" {
merr = append(merr, unarchiveTestSuites(paths.TestSuitesArchive, outdir))
}
return unwrapMultiErrorIfNil(merr)
}
func unwrapMultiErrorIfNil(merr errors.MultiError) error {
if merr.First() == nil {
return nil
}
return merr
}
func unarchiveControlFiles(archive string, outdir string) error {
r, err := os.Open(archive)
if err != nil {
return errors.Annotate(err, "unarchiveControlFiles").Err()
}
if err := untarAll(r, outdir); err != nil {
return errors.Annotate(err, "unarchiveControlFiles from %s", archive).Err()
}
return nil
}
func unarchiveTestSuites(archive string, outdir string) error {
r, err := os.Open(archive)
if err != nil {
return errors.Annotate(err, "unarchiveTestSuites").Err()
}
gr := bzip2.NewReader(r)
if err := untarAll(gr, outdir); err != nil {
return errors.Annotate(err, "unarchiveTestSuites from %s", archive).Err()
}
return nil
}
func untarAll(r io.Reader, outdir string) error {
tr := tar.NewReader(r)
for {
h, err := tr.Next()
switch {
case err == io.EOF:
// Scanned all files.
return nil
case err != nil:
return errors.Annotate(err, "untarMatching").Err()
default:
if err := extractOne(tr, h, outdir); err != nil {
return errors.Annotate(err, "untarMatching").Err()
}
}
}
}
func extractOne(r io.Reader, h *tar.Header, outdir string) error {
t := filepath.Join(outdir, h.Name)
if h.FileInfo().IsDir() {
return createDir(t)
}
return writeFile(r, t, h.Size)
}
func createDir(path string) error {
return os.MkdirAll(path, 0750)
}
// writeFile writes one file of given size from a Read()er to path.
func writeFile(r io.Reader, path string, size int64) error {
if err := createContainingDir(path); err != nil {
return errors.Annotate(err, "writeFile").Err()
}
f, err := os.Create(path)
if err != nil {
return errors.Annotate(err, "writeFile").Err()
}
n, err := io.CopyN(f, r, size)
if err != nil {
return errors.Annotate(err, "writeFile to %s", path).Err()
}
if n != size {
return errors.Reason("writeFile to %s: wrote %d bytes, want %d", path, n, size).Err()
}
return nil
}
func createContainingDir(path string) error {
return createDir(filepath.Dir(path))
}