blob: 8fed040ba4a5726c6f5f86eabf0ec9c23835bebc [file] [log] [blame]
Jeff Gastonefc1b412017-03-29 17:29:06 -07001// Copyright 2017 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 "fmt"
19 "io/ioutil"
20 "os"
21 "os/exec"
22 "path"
23 "path/filepath"
24 "strings"
25)
26
27func main() {
28 error := run()
29 if error != nil {
30 fmt.Fprintln(os.Stderr, error)
31 os.Exit(1)
32 }
33}
34
35var usage = "Usage: sbox -c <commandToRun> --sandbox-path <sandboxPath> <outputFiles>"
36
37func usageError(violation string) error {
38 return fmt.Errorf("Usage error: %s.\n %s", violation, usage)
39}
40
41func run() error {
42 var outFiles []string
43 args := os.Args[1:]
44
45 var rawCommand string
46 var sandboxesRoot string
Jeff Gastonf49082a2017-06-07 13:22:22 -070047 removeTempDir := true
Jeff Gastonefc1b412017-03-29 17:29:06 -070048
49 for i := 0; i < len(args); i++ {
50 arg := args[i]
51 if arg == "--sandbox-path" {
52 sandboxesRoot = args[i+1]
53 i++
54 } else if arg == "-c" {
55 rawCommand = args[i+1]
56 i++
57 } else {
58 outFiles = append(outFiles, arg)
59 }
60 }
61 if len(rawCommand) == 0 {
62 return usageError("-c <commandToRun> is required and must be non-empty")
63 }
64 if outFiles == nil {
65 return usageError("at least one output file must be given")
66 }
67 if len(sandboxesRoot) == 0 {
68 // In practice, the value of sandboxesRoot will mostly likely be at a fixed location relative to OUT_DIR,
69 // and the sbox executable will most likely be at a fixed location relative to OUT_DIR too, so
70 // the value of sandboxesRoot will most likely be at a fixed location relative to the sbox executable
71 // However, Soong also needs to be able to separately remove the sandbox directory on startup (if it has anything left in it)
72 // and by passing it as a parameter we don't need to duplicate its value
73 return usageError("--sandbox-path <sandboxPath> is required and must be non-empty")
74 }
75
76 os.MkdirAll(sandboxesRoot, 0777)
77
78 tempDir, err := ioutil.TempDir(sandboxesRoot, "sbox")
79 if err != nil {
80 return fmt.Errorf("Failed to create temp dir: %s", err)
81 }
82
83 // In the common case, the following line of code is what removes the sandbox
84 // If a fatal error occurs (such as if our Go process is killed unexpectedly),
85 // then at the beginning of the next build, Soong will retry the cleanup
Jeff Gastonf49082a2017-06-07 13:22:22 -070086 defer func() {
87 // in some cases we decline to remove the temp dir, to facilitate debugging
88 if removeTempDir {
89 os.RemoveAll(tempDir)
90 }
91 }()
Jeff Gastonefc1b412017-03-29 17:29:06 -070092
93 if strings.Contains(rawCommand, "__SBOX_OUT_DIR__") {
94 rawCommand = strings.Replace(rawCommand, "__SBOX_OUT_DIR__", tempDir, -1)
95 }
96
97 if strings.Contains(rawCommand, "__SBOX_OUT_FILES__") {
98 // expands into a space-separated list of output files to be generated into the sandbox directory
99 tempOutPaths := []string{}
100 for _, outputPath := range outFiles {
101 tempOutPath := path.Join(tempDir, outputPath)
102 tempOutPaths = append(tempOutPaths, tempOutPath)
103 }
104 pathsText := strings.Join(tempOutPaths, " ")
105 rawCommand = strings.Replace(rawCommand, "__SBOX_OUT_FILES__", pathsText, -1)
106 }
107
108 for _, filePath := range outFiles {
109 os.MkdirAll(path.Join(tempDir, filepath.Dir(filePath)), 0777)
110 }
111
112 cmd := exec.Command("bash", "-c", rawCommand)
113 cmd.Stdin = os.Stdin
114 cmd.Stdout = os.Stdout
115 cmd.Stderr = os.Stderr
116 err = cmd.Run()
117 if exit, ok := err.(*exec.ExitError); ok && !exit.Success() {
118 return fmt.Errorf("sbox command %#v failed with err %#v\n", cmd, err)
119 } else if err != nil {
120 return err
121 }
122
Jeff Gastonf49082a2017-06-07 13:22:22 -0700123 // validate that all files are created properly
124 var outputErrors []error
Jeff Gastonefc1b412017-03-29 17:29:06 -0700125 for _, filePath := range outFiles {
126 tempPath := filepath.Join(tempDir, filePath)
127 fileInfo, err := os.Stat(tempPath)
128 if err != nil {
Jeff Gastonf49082a2017-06-07 13:22:22 -0700129 outputErrors = append(outputErrors, fmt.Errorf("failed to create expected output file: %s\n", tempPath))
130 continue
Jeff Gastonefc1b412017-03-29 17:29:06 -0700131 }
132 if fileInfo.IsDir() {
Jeff Gastonf49082a2017-06-07 13:22:22 -0700133 outputErrors = append(outputErrors, fmt.Errorf("Output path %s refers to a directory, not a file. This is not permitted because it prevents robust up-to-date checks\n", filePath))
Jeff Gastonefc1b412017-03-29 17:29:06 -0700134 }
Jeff Gastonf49082a2017-06-07 13:22:22 -0700135 }
136 if len(outputErrors) > 0 {
137 // Keep the temporary output directory around in case a user wants to inspect it for debugging purposes.
138 // Soong will delete it later anyway.
139 removeTempDir = false
140 return fmt.Errorf("mismatch between declared and actual outputs in sbox command (%s):\n%v", rawCommand, outputErrors)
141 }
142 // the created files match the declared files; now move them
143 for _, filePath := range outFiles {
144 tempPath := filepath.Join(tempDir, filePath)
145 err := os.Rename(tempPath, filePath)
Jeff Gastonefc1b412017-03-29 17:29:06 -0700146 if err != nil {
147 return err
148 }
149 }
Jeff Gastonf49082a2017-06-07 13:22:22 -0700150
Jeff Gastonefc1b412017-03-29 17:29:06 -0700151 // TODO(jeffrygaston) if a process creates more output files than it declares, should there be a warning?
152 return nil
153}