Rewrite sbox to use a textproto manifest

In preparation for more complicated sandboxing that copies tools
and/or inputs into the sandbox directory, make sbox use a textproto
input that describes the commands to be run and the files to copy
in or out of the sandbox.

Bug: 124313442
Test: m checkbuild
Test: rule_builder_test.go
Test: genrule_test.go
Change-Id: I3b918a6643cea77199fd39577ef71e34cdeacdb1
This commit is contained in:
Colin Cross
2020-11-12 08:29:30 -08:00
parent 3d68051218
commit 151b9ff0cf
12 changed files with 760 additions and 249 deletions

View File

@@ -14,8 +14,20 @@
blueprint_go_binary {
name: "sbox",
deps: ["soong-makedeps"],
deps: [
"sbox_proto",
"soong-makedeps",
],
srcs: [
"sbox.go",
],
}
bootstrap_go_package {
name: "sbox_proto",
pkgPath: "android/soong/cmd/sbox/sbox_proto",
deps: ["golang-protobuf-proto"],
srcs: [
"sbox_proto/sbox.pb.go",
],
}

View File

@@ -19,41 +19,39 @@ import (
"errors"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"strconv"
"strings"
"time"
"android/soong/cmd/sbox/sbox_proto"
"android/soong/makedeps"
"github.com/golang/protobuf/proto"
)
var (
sandboxesRoot string
rawCommand string
outputRoot string
manifestFile string
keepOutDir bool
depfileOut string
inputHash string
)
const (
depFilePlaceholder = "__SBOX_DEPFILE__"
sandboxDirPlaceholder = "__SBOX_SANDBOX_DIR__"
)
func init() {
flag.StringVar(&sandboxesRoot, "sandbox-path", "",
"root of temp directory to put the sandbox into")
flag.StringVar(&rawCommand, "c", "",
"command to run")
flag.StringVar(&outputRoot, "output-root", "",
"root of directory to copy outputs into")
flag.StringVar(&manifestFile, "manifest", "",
"textproto manifest describing the sandboxed command(s)")
flag.BoolVar(&keepOutDir, "keep-out-dir", false,
"whether to keep the sandbox directory when done")
flag.StringVar(&depfileOut, "depfile-out", "",
"file path of the depfile to generate. This value will replace '__SBOX_DEPFILE__' in the command and will be treated as an output but won't be added to __SBOX_OUT_FILES__")
flag.StringVar(&inputHash, "input-hash", "",
"This option is ignored. Typical usage is to supply a hash of the list of input names so that the module will be rebuilt if the list (and thus the hash) changes.")
}
func usageViolation(violation string) {
@@ -62,11 +60,7 @@ func usageViolation(violation string) {
}
fmt.Fprintf(os.Stderr,
"Usage: sbox -c <commandToRun> --sandbox-path <sandboxPath> --output-root <outputRoot> [--depfile-out depFile] [--input-hash hash] <outputFile> [<outputFile>...]\n"+
"\n"+
"Deletes <outputRoot>,"+
"runs <commandToRun>,"+
"and moves each <outputFile> out of <sandboxPath> and into <outputRoot>\n")
"Usage: sbox --manifest <manifest> --sandbox-path <sandboxPath>\n")
flag.PrintDefaults()
@@ -103,8 +97,8 @@ func findAllFilesUnder(root string) (paths []string) {
}
func run() error {
if rawCommand == "" {
usageViolation("-c <commandToRun> is required and must be non-empty")
if manifestFile == "" {
usageViolation("--manifest <manifest> is required and must be non-empty")
}
if sandboxesRoot == "" {
// In practice, the value of sandboxesRoot will mostly likely be at a fixed location relative to OUT_DIR,
@@ -114,61 +108,28 @@ func run() error {
// and by passing it as a parameter we don't need to duplicate its value
usageViolation("--sandbox-path <sandboxPath> is required and must be non-empty")
}
if len(outputRoot) == 0 {
usageViolation("--output-root <outputRoot> is required and must be non-empty")
manifest, err := readManifest(manifestFile)
if len(manifest.Commands) == 0 {
return fmt.Errorf("at least one commands entry is required in %q", manifestFile)
}
// the contents of the __SBOX_OUT_FILES__ variable
outputsVarEntries := flag.Args()
if len(outputsVarEntries) == 0 {
usageViolation("at least one output file must be given")
}
// all outputs
var allOutputs []string
// setup directories
err := os.MkdirAll(sandboxesRoot, 0777)
// setup sandbox directory
err = os.MkdirAll(sandboxesRoot, 0777)
if err != nil {
return err
}
err = os.RemoveAll(outputRoot)
if err != nil {
return err
}
err = os.MkdirAll(outputRoot, 0777)
if err != nil {
return err
return fmt.Errorf("failed to create %q: %w", sandboxesRoot, err)
}
tempDir, err := ioutil.TempDir(sandboxesRoot, "sbox")
for i, filePath := range outputsVarEntries {
if !strings.HasPrefix(filePath, "__SBOX_OUT_DIR__/") {
return fmt.Errorf("output files must start with `__SBOX_OUT_DIR__/`")
}
outputsVarEntries[i] = strings.TrimPrefix(filePath, "__SBOX_OUT_DIR__/")
}
allOutputs = append([]string(nil), outputsVarEntries...)
if depfileOut != "" {
sandboxedDepfile, err := filepath.Rel(outputRoot, depfileOut)
if err != nil {
return err
}
allOutputs = append(allOutputs, sandboxedDepfile)
rawCommand = strings.Replace(rawCommand, "__SBOX_DEPFILE__", filepath.Join(tempDir, sandboxedDepfile), -1)
}
if err != nil {
return fmt.Errorf("Failed to create temp dir: %s", err)
return fmt.Errorf("failed to create temporary dir in %q: %w", sandboxesRoot, err)
}
// In the common case, the following line of code is what removes the sandbox
// If a fatal error occurs (such as if our Go process is killed unexpectedly),
// then at the beginning of the next build, Soong will retry the cleanup
// then at the beginning of the next build, Soong will wipe the temporary
// directory.
defer func() {
// in some cases we decline to remove the temp dir, to facilitate debugging
if !keepOutDir {
@@ -176,27 +137,95 @@ func run() error {
}
}()
if strings.Contains(rawCommand, "__SBOX_OUT_DIR__") {
rawCommand = strings.Replace(rawCommand, "__SBOX_OUT_DIR__", tempDir, -1)
}
// If there is more than one command in the manifest use a separate directory for each one.
useSubDir := len(manifest.Commands) > 1
var depFiles []string
if strings.Contains(rawCommand, "__SBOX_OUT_FILES__") {
// expands into a space-separated list of output files to be generated into the sandbox directory
tempOutPaths := []string{}
for _, outputPath := range outputsVarEntries {
tempOutPath := path.Join(tempDir, outputPath)
tempOutPaths = append(tempOutPaths, tempOutPath)
for i, command := range manifest.Commands {
localTempDir := tempDir
if useSubDir {
localTempDir = filepath.Join(localTempDir, strconv.Itoa(i))
}
pathsText := strings.Join(tempOutPaths, " ")
rawCommand = strings.Replace(rawCommand, "__SBOX_OUT_FILES__", pathsText, -1)
}
for _, filePath := range allOutputs {
dir := path.Join(tempDir, filepath.Dir(filePath))
err = os.MkdirAll(dir, 0777)
depFile, err := runCommand(command, localTempDir)
if err != nil {
// Running the command failed, keep the temporary output directory around in
// case a user wants to inspect it for debugging purposes. Soong will delete
// it at the beginning of the next build anyway.
keepOutDir = true
return err
}
if depFile != "" {
depFiles = append(depFiles, depFile)
}
}
depFile := manifest.GetOutputDepfile()
if len(depFiles) > 0 && depFile == "" {
return fmt.Errorf("Sandboxed commands used %s but output depfile is not set in manifest file",
depFilePlaceholder)
}
if depFile != "" {
// Merge the depfiles from each command in the manifest to a single output depfile.
err = rewriteDepFiles(depFiles, depFile)
if err != nil {
return fmt.Errorf("failed merging depfiles: %w", err)
}
}
return nil
}
// readManifest reads an sbox manifest from a textproto file.
func readManifest(file string) (*sbox_proto.Manifest, error) {
manifestData, err := ioutil.ReadFile(file)
if err != nil {
return nil, fmt.Errorf("error reading manifest %q: %w", file, err)
}
manifest := sbox_proto.Manifest{}
err = proto.UnmarshalText(string(manifestData), &manifest)
if err != nil {
return nil, fmt.Errorf("error parsing manifest %q: %w", file, err)
}
return &manifest, nil
}
// runCommand runs a single command from a manifest. If the command references the
// __SBOX_DEPFILE__ placeholder it returns the name of the depfile that was used.
func runCommand(command *sbox_proto.Command, tempDir string) (depFile string, err error) {
rawCommand := command.GetCommand()
if rawCommand == "" {
return "", fmt.Errorf("command is required")
}
err = os.MkdirAll(tempDir, 0777)
if err != nil {
return "", fmt.Errorf("failed to create %q: %w", tempDir, err)
}
// Copy in any files specified by the manifest.
err = linkOrCopyFiles(command.CopyBefore, "", tempDir)
if err != nil {
return "", err
}
if strings.Contains(rawCommand, depFilePlaceholder) {
depFile = filepath.Join(tempDir, "deps.d")
rawCommand = strings.Replace(rawCommand, depFilePlaceholder, depFile, -1)
}
if strings.Contains(rawCommand, sandboxDirPlaceholder) {
rawCommand = strings.Replace(rawCommand, sandboxDirPlaceholder, tempDir, -1)
}
// Emulate ninja's behavior of creating the directories for any output files before
// running the command.
err = makeOutputDirs(command.CopyAfter, tempDir)
if err != nil {
return "", err
}
commandDescription := rawCommand
@@ -205,27 +234,20 @@ func run() error {
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if command.GetChdir() {
cmd.Dir = tempDir
}
err = cmd.Run()
if exit, ok := err.(*exec.ExitError); ok && !exit.Success() {
return fmt.Errorf("sbox command (%s) failed with err %#v\n", commandDescription, err.Error())
return "", fmt.Errorf("sbox command failed with err:\n%s\n%w\n", commandDescription, err)
} else if err != nil {
return err
return "", err
}
// validate that all files are created properly
var missingOutputErrors []string
for _, filePath := range allOutputs {
tempPath := filepath.Join(tempDir, filePath)
fileInfo, err := os.Stat(tempPath)
if err != nil {
missingOutputErrors = append(missingOutputErrors, fmt.Sprintf("%s: does not exist", filePath))
continue
}
if fileInfo.IsDir() {
missingOutputErrors = append(missingOutputErrors, fmt.Sprintf("%s: not a file", filePath))
}
}
missingOutputErrors := validateOutputFiles(command.CopyAfter, tempDir)
if len(missingOutputErrors) > 0 {
// find all created files for making a more informative error message
createdFiles := findAllFilesUnder(tempDir)
@@ -236,7 +258,7 @@ func run() error {
errorMessage += "in sandbox " + tempDir + ",\n"
errorMessage += fmt.Sprintf("failed to create %v files:\n", len(missingOutputErrors))
for _, missingOutputError := range missingOutputErrors {
errorMessage += " " + missingOutputError + "\n"
errorMessage += " " + missingOutputError.Error() + "\n"
}
if len(createdFiles) < 1 {
errorMessage += "created 0 files."
@@ -253,19 +275,137 @@ func run() error {
}
}
// Keep the temporary output directory around in case a user wants to inspect it for debugging purposes.
// Soong will delete it later anyway.
keepOutDir = true
return errors.New(errorMessage)
return "", errors.New(errorMessage)
}
// the created files match the declared files; now move them
for _, filePath := range allOutputs {
tempPath := filepath.Join(tempDir, filePath)
destPath := filePath
if len(outputRoot) != 0 {
destPath = filepath.Join(outputRoot, filePath)
err = moveFiles(command.CopyAfter, tempDir, "")
return depFile, nil
}
// makeOutputDirs creates directories in the sandbox dir for every file that has a rule to be copied
// out of the sandbox. This emulate's Ninja's behavior of creating directories for output files
// so that the tools don't have to.
func makeOutputDirs(copies []*sbox_proto.Copy, sandboxDir string) error {
for _, copyPair := range copies {
dir := joinPath(sandboxDir, filepath.Dir(copyPair.GetFrom()))
err := os.MkdirAll(dir, 0777)
if err != nil {
return err
}
err := os.MkdirAll(filepath.Dir(destPath), 0777)
}
return nil
}
// validateOutputFiles verifies that all files that have a rule to be copied out of the sandbox
// were created by the command.
func validateOutputFiles(copies []*sbox_proto.Copy, sandboxDir string) []error {
var missingOutputErrors []error
for _, copyPair := range copies {
fromPath := joinPath(sandboxDir, copyPair.GetFrom())
fileInfo, err := os.Stat(fromPath)
if err != nil {
missingOutputErrors = append(missingOutputErrors, fmt.Errorf("%s: does not exist", fromPath))
continue
}
if fileInfo.IsDir() {
missingOutputErrors = append(missingOutputErrors, fmt.Errorf("%s: not a file", fromPath))
}
}
return missingOutputErrors
}
// linkOrCopyFiles hardlinks or copies files in or out of the sandbox.
func linkOrCopyFiles(copies []*sbox_proto.Copy, fromDir, toDir string) error {
for _, copyPair := range copies {
fromPath := joinPath(fromDir, copyPair.GetFrom())
toPath := joinPath(toDir, copyPair.GetTo())
err := linkOrCopyOneFile(fromPath, toPath)
if err != nil {
return fmt.Errorf("error copying %q to %q: %w", fromPath, toPath, err)
}
}
return nil
}
// linkOrCopyOneFile first attempts to hardlink a file to a destination, and falls back to making
// a copy if the hardlink fails.
func linkOrCopyOneFile(from string, to string) error {
err := os.MkdirAll(filepath.Dir(to), 0777)
if err != nil {
return err
}
// First try hardlinking
err = os.Link(from, to)
if err != nil {
// Retry with copying in case the source an destination are on different filesystems.
// TODO: check for specific hardlink error?
err = copyOneFile(from, to)
if err != nil {
return err
}
}
return nil
}
// copyOneFile copies a file.
func copyOneFile(from string, to string) error {
stat, err := os.Stat(from)
if err != nil {
return err
}
perm := stat.Mode()
in, err := os.Open(from)
if err != nil {
return err
}
defer in.Close()
out, err := os.Create(to)
if err != nil {
return err
}
defer func() {
out.Close()
if err != nil {
os.Remove(to)
}
}()
_, err = io.Copy(out, in)
if err != nil {
return err
}
if err = out.Close(); err != nil {
return err
}
if err = os.Chmod(to, perm); err != nil {
return err
}
return nil
}
// moveFiles moves files specified by a set of copy rules. It uses os.Rename, so it is restricted
// to moving files where the source and destination are in the same filesystem. This is OK for
// sbox because the temporary directory is inside the out directory. It updates the timestamp
// of the new file.
func moveFiles(copies []*sbox_proto.Copy, fromDir, toDir string) error {
for _, copyPair := range copies {
fromPath := joinPath(fromDir, copyPair.GetFrom())
toPath := joinPath(toDir, copyPair.GetTo())
err := os.MkdirAll(filepath.Dir(toPath), 0777)
if err != nil {
return err
}
err = os.Rename(fromPath, toPath)
if err != nil {
return err
}
@@ -273,37 +413,45 @@ func run() error {
// Update the timestamp of the output file in case the tool wrote an old timestamp (for example, tar can extract
// files with old timestamps).
now := time.Now()
err = os.Chtimes(tempPath, now, now)
if err != nil {
return err
}
err = os.Rename(tempPath, destPath)
err = os.Chtimes(toPath, now, now)
if err != nil {
return err
}
}
// Rewrite the depfile so that it doesn't include the (randomized) sandbox directory
if depfileOut != "" {
in, err := ioutil.ReadFile(depfileOut)
if err != nil {
return err
}
deps, err := makedeps.Parse(depfileOut, bytes.NewBuffer(in))
if err != nil {
return err
}
deps.Output = "outputfile"
err = ioutil.WriteFile(depfileOut, deps.Print(), 0666)
if err != nil {
return err
}
}
// TODO(jeffrygaston) if a process creates more output files than it declares, should there be a warning?
return nil
}
// Rewrite one or more depfiles so that it doesn't include the (randomized) sandbox directory
// to an output file.
func rewriteDepFiles(ins []string, out string) error {
var mergedDeps []string
for _, in := range ins {
data, err := ioutil.ReadFile(in)
if err != nil {
return err
}
deps, err := makedeps.Parse(in, bytes.NewBuffer(data))
if err != nil {
return err
}
mergedDeps = append(mergedDeps, deps.Inputs...)
}
deps := makedeps.Deps{
// Ninja doesn't care what the output file is, so we can use any string here.
Output: "outputfile",
Inputs: mergedDeps,
}
return ioutil.WriteFile(out, deps.Print(), 0666)
}
// joinPath wraps filepath.Join but returns file without appending to dir if file is
// absolute.
func joinPath(dir, file string) string {
if filepath.IsAbs(file) {
return file
}
return filepath.Join(dir, file)
}

View File

@@ -0,0 +1,233 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: sbox.proto
package sbox_proto
import (
fmt "fmt"
proto "github.com/golang/protobuf/proto"
math "math"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
// A set of commands to run in a sandbox.
type Manifest struct {
// A list of commands to run in the sandbox.
Commands []*Command `protobuf:"bytes,1,rep,name=commands" json:"commands,omitempty"`
// If set, GCC-style dependency files from any command that references __SBOX_DEPFILE__ will be
// merged into the given output file relative to the $PWD when sbox was started.
OutputDepfile *string `protobuf:"bytes,2,opt,name=output_depfile,json=outputDepfile" json:"output_depfile,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Manifest) Reset() { *m = Manifest{} }
func (m *Manifest) String() string { return proto.CompactTextString(m) }
func (*Manifest) ProtoMessage() {}
func (*Manifest) Descriptor() ([]byte, []int) {
return fileDescriptor_9d0425bf0de86ed1, []int{0}
}
func (m *Manifest) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Manifest.Unmarshal(m, b)
}
func (m *Manifest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Manifest.Marshal(b, m, deterministic)
}
func (m *Manifest) XXX_Merge(src proto.Message) {
xxx_messageInfo_Manifest.Merge(m, src)
}
func (m *Manifest) XXX_Size() int {
return xxx_messageInfo_Manifest.Size(m)
}
func (m *Manifest) XXX_DiscardUnknown() {
xxx_messageInfo_Manifest.DiscardUnknown(m)
}
var xxx_messageInfo_Manifest proto.InternalMessageInfo
func (m *Manifest) GetCommands() []*Command {
if m != nil {
return m.Commands
}
return nil
}
func (m *Manifest) GetOutputDepfile() string {
if m != nil && m.OutputDepfile != nil {
return *m.OutputDepfile
}
return ""
}
// SandboxManifest describes a command to run in the sandbox.
type Command struct {
// A list of copy rules to run before the sandboxed command. The from field is relative to the
// $PWD when sbox was run, the to field is relative to the top of the temporary sandbox directory.
CopyBefore []*Copy `protobuf:"bytes,1,rep,name=copy_before,json=copyBefore" json:"copy_before,omitempty"`
// If true, change the working directory to the top of the temporary sandbox directory before
// running the command. If false, leave the working directory where it was when sbox was started.
Chdir *bool `protobuf:"varint,2,opt,name=chdir" json:"chdir,omitempty"`
// The command to run.
Command *string `protobuf:"bytes,3,req,name=command" json:"command,omitempty"`
// A list of copy rules to run after the sandboxed command. The from field is relative to the
// top of the temporary sandbox directory, the to field is relative to the $PWD when sbox was run.
CopyAfter []*Copy `protobuf:"bytes,4,rep,name=copy_after,json=copyAfter" json:"copy_after,omitempty"`
// An optional hash of the input files to ensure the textproto files and the sbox rule reruns
// when the lists of inputs changes, even if the inputs are not on the command line.
InputHash *string `protobuf:"bytes,5,opt,name=input_hash,json=inputHash" json:"input_hash,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Command) Reset() { *m = Command{} }
func (m *Command) String() string { return proto.CompactTextString(m) }
func (*Command) ProtoMessage() {}
func (*Command) Descriptor() ([]byte, []int) {
return fileDescriptor_9d0425bf0de86ed1, []int{1}
}
func (m *Command) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Command.Unmarshal(m, b)
}
func (m *Command) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Command.Marshal(b, m, deterministic)
}
func (m *Command) XXX_Merge(src proto.Message) {
xxx_messageInfo_Command.Merge(m, src)
}
func (m *Command) XXX_Size() int {
return xxx_messageInfo_Command.Size(m)
}
func (m *Command) XXX_DiscardUnknown() {
xxx_messageInfo_Command.DiscardUnknown(m)
}
var xxx_messageInfo_Command proto.InternalMessageInfo
func (m *Command) GetCopyBefore() []*Copy {
if m != nil {
return m.CopyBefore
}
return nil
}
func (m *Command) GetChdir() bool {
if m != nil && m.Chdir != nil {
return *m.Chdir
}
return false
}
func (m *Command) GetCommand() string {
if m != nil && m.Command != nil {
return *m.Command
}
return ""
}
func (m *Command) GetCopyAfter() []*Copy {
if m != nil {
return m.CopyAfter
}
return nil
}
func (m *Command) GetInputHash() string {
if m != nil && m.InputHash != nil {
return *m.InputHash
}
return ""
}
// Copy describes a from-to pair of files to copy. The paths may be relative, the root that they
// are relative to is specific to the context the Copy is used in and will be different for
// from and to.
type Copy struct {
From *string `protobuf:"bytes,1,req,name=from" json:"from,omitempty"`
To *string `protobuf:"bytes,2,req,name=to" json:"to,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Copy) Reset() { *m = Copy{} }
func (m *Copy) String() string { return proto.CompactTextString(m) }
func (*Copy) ProtoMessage() {}
func (*Copy) Descriptor() ([]byte, []int) {
return fileDescriptor_9d0425bf0de86ed1, []int{2}
}
func (m *Copy) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Copy.Unmarshal(m, b)
}
func (m *Copy) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Copy.Marshal(b, m, deterministic)
}
func (m *Copy) XXX_Merge(src proto.Message) {
xxx_messageInfo_Copy.Merge(m, src)
}
func (m *Copy) XXX_Size() int {
return xxx_messageInfo_Copy.Size(m)
}
func (m *Copy) XXX_DiscardUnknown() {
xxx_messageInfo_Copy.DiscardUnknown(m)
}
var xxx_messageInfo_Copy proto.InternalMessageInfo
func (m *Copy) GetFrom() string {
if m != nil && m.From != nil {
return *m.From
}
return ""
}
func (m *Copy) GetTo() string {
if m != nil && m.To != nil {
return *m.To
}
return ""
}
func init() {
proto.RegisterType((*Manifest)(nil), "sbox.Manifest")
proto.RegisterType((*Command)(nil), "sbox.Command")
proto.RegisterType((*Copy)(nil), "sbox.Copy")
}
func init() {
proto.RegisterFile("sbox.proto", fileDescriptor_9d0425bf0de86ed1)
}
var fileDescriptor_9d0425bf0de86ed1 = []byte{
// 252 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x90, 0x41, 0x4b, 0xc3, 0x40,
0x10, 0x85, 0x49, 0x9a, 0xd2, 0x66, 0x6a, 0x7b, 0x18, 0x3c, 0xec, 0x45, 0x08, 0x01, 0x21, 0x55,
0xe8, 0xc1, 0x7f, 0x60, 0xf5, 0xe0, 0xc5, 0xcb, 0x1e, 0x45, 0x08, 0xdb, 0x64, 0x97, 0x04, 0x4c,
0x66, 0xd9, 0xdd, 0x82, 0xfd, 0x57, 0xfe, 0x44, 0xd9, 0x49, 0xea, 0xc5, 0xdb, 0xcc, 0xfb, 0x78,
0xf3, 0x1e, 0x03, 0xe0, 0x4f, 0xf4, 0x7d, 0xb0, 0x8e, 0x02, 0x61, 0x16, 0xe7, 0xf2, 0x13, 0xd6,
0xef, 0x6a, 0xec, 0x8d, 0xf6, 0x01, 0xf7, 0xb0, 0x6e, 0x68, 0x18, 0xd4, 0xd8, 0x7a, 0x91, 0x14,
0x8b, 0x6a, 0xf3, 0xb4, 0x3d, 0xb0, 0xe1, 0x65, 0x52, 0xe5, 0x1f, 0xc6, 0x7b, 0xd8, 0xd1, 0x39,
0xd8, 0x73, 0xa8, 0x5b, 0x6d, 0x4d, 0xff, 0xa5, 0x45, 0x5a, 0x24, 0x55, 0x2e, 0xb7, 0x93, 0xfa,
0x3a, 0x89, 0xe5, 0x4f, 0x02, 0xab, 0xd9, 0x8c, 0x8f, 0xb0, 0x69, 0xc8, 0x5e, 0xea, 0x93, 0x36,
0xe4, 0xf4, 0x1c, 0x00, 0xd7, 0x00, 0x7b, 0x91, 0x10, 0xf1, 0x91, 0x29, 0xde, 0xc2, 0xb2, 0xe9,
0xda, 0xde, 0xf1, 0xd9, 0xb5, 0x9c, 0x16, 0x14, 0xb0, 0x9a, 0x1b, 0x88, 0x45, 0x91, 0x56, 0xb9,
0xbc, 0xae, 0xb8, 0x07, 0x76, 0xd7, 0xca, 0x04, 0xed, 0x44, 0xf6, 0xef, 0x76, 0x1e, 0xe9, 0x73,
0x84, 0x78, 0x07, 0xd0, 0x8f, 0xb1, 0x79, 0xa7, 0x7c, 0x27, 0x96, 0x5c, 0x3b, 0x67, 0xe5, 0x4d,
0xf9, 0xae, 0x7c, 0x80, 0x2c, 0x3a, 0x10, 0x21, 0x33, 0x8e, 0x06, 0x91, 0x70, 0x10, 0xcf, 0xb8,
0x83, 0x34, 0x90, 0x48, 0x59, 0x49, 0x03, 0x1d, 0x6f, 0x3e, 0xf8, 0xa1, 0x35, 0x3f, 0xf4, 0x37,
0x00, 0x00, 0xff, 0xff, 0x95, 0x4d, 0xee, 0x7d, 0x5d, 0x01, 0x00, 0x00,
}

View File

@@ -0,0 +1,58 @@
// Copyright 2020 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.
syntax = "proto2";
package sbox;
option go_package = "sbox_proto";
// A set of commands to run in a sandbox.
message Manifest {
// A list of commands to run in the sandbox.
repeated Command commands = 1;
// If set, GCC-style dependency files from any command that references __SBOX_DEPFILE__ will be
// merged into the given output file relative to the $PWD when sbox was started.
optional string output_depfile = 2;
}
// SandboxManifest describes a command to run in the sandbox.
message Command {
// A list of copy rules to run before the sandboxed command. The from field is relative to the
// $PWD when sbox was run, the to field is relative to the top of the temporary sandbox directory.
repeated Copy copy_before = 1;
// If true, change the working directory to the top of the temporary sandbox directory before
// running the command. If false, leave the working directory where it was when sbox was started.
optional bool chdir = 2;
// The command to run.
required string command = 3;
// A list of copy rules to run after the sandboxed command. The from field is relative to the
// top of the temporary sandbox directory, the to field is relative to the $PWD when sbox was run.
repeated Copy copy_after = 4;
// An optional hash of the input files to ensure the textproto files and the sbox rule reruns
// when the lists of inputs changes, even if the inputs are not on the command line.
optional string input_hash = 5;
}
// Copy describes a from-to pair of files to copy. The paths may be relative, the root that they
// are relative to is specific to the context the Copy is used in and will be different for
// from and to.
message Copy {
required string from = 1;
required string to = 2;
}