blob: f124e40b0dfffdc5dbb04a7719caf80cbc28ebd2 [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 (
Dan Willemsenc89b6f12019-08-29 14:47:40 -070018 "bytes"
Ulf Adamsb73daa52020-11-25 23:09:09 +010019 "crypto/sha1"
20 "encoding/hex"
Jeff Gaston90cfb092017-09-26 16:46:10 -070021 "errors"
Jeff Gaston93f0f372017-11-01 13:33:02 -070022 "flag"
Jeff Gastonefc1b412017-03-29 17:29:06 -070023 "fmt"
Colin Crosse16ce362020-11-12 08:29:30 -080024 "io"
Jeff Gastonefc1b412017-03-29 17:29:06 -070025 "io/ioutil"
26 "os"
27 "os/exec"
Jeff Gastonefc1b412017-03-29 17:29:06 -070028 "path/filepath"
Colin Crosse16ce362020-11-12 08:29:30 -080029 "strconv"
Jeff Gastonefc1b412017-03-29 17:29:06 -070030 "strings"
Colin Crossd1c1e6f2019-03-29 13:54:39 -070031 "time"
Dan Willemsenc89b6f12019-08-29 14:47:40 -070032
Colin Crosse16ce362020-11-12 08:29:30 -080033 "android/soong/cmd/sbox/sbox_proto"
Dan Willemsenc89b6f12019-08-29 14:47:40 -070034 "android/soong/makedeps"
Colin Crosse55bd422021-03-23 13:44:30 -070035 "android/soong/response"
Colin Crosse16ce362020-11-12 08:29:30 -080036
37 "github.com/golang/protobuf/proto"
Jeff Gastonefc1b412017-03-29 17:29:06 -070038)
39
Jeff Gaston93f0f372017-11-01 13:33:02 -070040var (
41 sandboxesRoot string
Colin Crosse16ce362020-11-12 08:29:30 -080042 manifestFile string
Jeff Gaston93f0f372017-11-01 13:33:02 -070043 keepOutDir bool
Colin Crosse16ce362020-11-12 08:29:30 -080044)
45
46const (
47 depFilePlaceholder = "__SBOX_DEPFILE__"
48 sandboxDirPlaceholder = "__SBOX_SANDBOX_DIR__"
Jeff Gaston93f0f372017-11-01 13:33:02 -070049)
50
51func init() {
52 flag.StringVar(&sandboxesRoot, "sandbox-path", "",
53 "root of temp directory to put the sandbox into")
Colin Crosse16ce362020-11-12 08:29:30 -080054 flag.StringVar(&manifestFile, "manifest", "",
55 "textproto manifest describing the sandboxed command(s)")
Jeff Gaston93f0f372017-11-01 13:33:02 -070056 flag.BoolVar(&keepOutDir, "keep-out-dir", false,
57 "whether to keep the sandbox directory when done")
Jeff Gaston93f0f372017-11-01 13:33:02 -070058}
59
60func usageViolation(violation string) {
61 if violation != "" {
62 fmt.Fprintf(os.Stderr, "Usage error: %s.\n\n", violation)
63 }
64
65 fmt.Fprintf(os.Stderr,
Colin Crosse16ce362020-11-12 08:29:30 -080066 "Usage: sbox --manifest <manifest> --sandbox-path <sandboxPath>\n")
Jeff Gaston93f0f372017-11-01 13:33:02 -070067
68 flag.PrintDefaults()
69
70 os.Exit(1)
71}
72
Jeff Gastonefc1b412017-03-29 17:29:06 -070073func main() {
Jeff Gaston93f0f372017-11-01 13:33:02 -070074 flag.Usage = func() {
75 usageViolation("")
76 }
77 flag.Parse()
78
Jeff Gastonefc1b412017-03-29 17:29:06 -070079 error := run()
80 if error != nil {
81 fmt.Fprintln(os.Stderr, error)
82 os.Exit(1)
83 }
84}
85
Jeff Gaston90cfb092017-09-26 16:46:10 -070086func findAllFilesUnder(root string) (paths []string) {
87 paths = []string{}
88 filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
89 if !info.IsDir() {
90 relPath, err := filepath.Rel(root, path)
91 if err != nil {
92 // couldn't find relative path from ancestor?
93 panic(err)
94 }
95 paths = append(paths, relPath)
96 }
97 return nil
98 })
99 return paths
100}
101
Jeff Gastonefc1b412017-03-29 17:29:06 -0700102func run() error {
Colin Crosse16ce362020-11-12 08:29:30 -0800103 if manifestFile == "" {
104 usageViolation("--manifest <manifest> is required and must be non-empty")
Jeff Gastonefc1b412017-03-29 17:29:06 -0700105 }
Jeff Gaston02a684b2017-10-27 14:59:27 -0700106 if sandboxesRoot == "" {
Jeff Gastonefc1b412017-03-29 17:29:06 -0700107 // In practice, the value of sandboxesRoot will mostly likely be at a fixed location relative to OUT_DIR,
108 // and the sbox executable will most likely be at a fixed location relative to OUT_DIR too, so
109 // the value of sandboxesRoot will most likely be at a fixed location relative to the sbox executable
110 // However, Soong also needs to be able to separately remove the sandbox directory on startup (if it has anything left in it)
111 // and by passing it as a parameter we don't need to duplicate its value
Jeff Gaston93f0f372017-11-01 13:33:02 -0700112 usageViolation("--sandbox-path <sandboxPath> is required and must be non-empty")
Jeff Gastonefc1b412017-03-29 17:29:06 -0700113 }
Colin Crosse16ce362020-11-12 08:29:30 -0800114
115 manifest, err := readManifest(manifestFile)
116
117 if len(manifest.Commands) == 0 {
118 return fmt.Errorf("at least one commands entry is required in %q", manifestFile)
Jeff Gaston193f2fb2017-06-12 15:00:12 -0700119 }
120
Colin Crosse16ce362020-11-12 08:29:30 -0800121 // setup sandbox directory
122 err = os.MkdirAll(sandboxesRoot, 0777)
Jeff Gaston8a88db52017-11-06 13:33:14 -0800123 if err != nil {
Colin Crosse16ce362020-11-12 08:29:30 -0800124 return fmt.Errorf("failed to create %q: %w", sandboxesRoot, err)
Jeff Gaston8a88db52017-11-06 13:33:14 -0800125 }
Jeff Gastonefc1b412017-03-29 17:29:06 -0700126
Ulf Adamsb73daa52020-11-25 23:09:09 +0100127 // This tool assumes that there are no two concurrent runs with the same
128 // manifestFile. It should therefore be safe to use the hash of the
129 // manifestFile as the temporary directory name. We do this because it
130 // makes the temporary directory name deterministic. There are some
131 // tools that embed the name of the temporary output in the output, and
132 // they otherwise cause non-determinism, which then poisons actions
133 // depending on this one.
134 hash := sha1.New()
135 hash.Write([]byte(manifestFile))
136 tempDir := filepath.Join(sandboxesRoot, "sbox", hex.EncodeToString(hash.Sum(nil)))
137
138 err = os.RemoveAll(tempDir)
139 if err != nil {
140 return err
141 }
142 err = os.MkdirAll(tempDir, 0777)
Jeff Gastonefc1b412017-03-29 17:29:06 -0700143 if err != nil {
Colin Crosse16ce362020-11-12 08:29:30 -0800144 return fmt.Errorf("failed to create temporary dir in %q: %w", sandboxesRoot, err)
Jeff Gastonefc1b412017-03-29 17:29:06 -0700145 }
146
147 // In the common case, the following line of code is what removes the sandbox
148 // If a fatal error occurs (such as if our Go process is killed unexpectedly),
Colin Crosse16ce362020-11-12 08:29:30 -0800149 // then at the beginning of the next build, Soong will wipe the temporary
150 // directory.
Jeff Gastonf49082a2017-06-07 13:22:22 -0700151 defer func() {
152 // in some cases we decline to remove the temp dir, to facilitate debugging
Jeff Gaston93f0f372017-11-01 13:33:02 -0700153 if !keepOutDir {
Jeff Gastonf49082a2017-06-07 13:22:22 -0700154 os.RemoveAll(tempDir)
155 }
156 }()
Jeff Gastonefc1b412017-03-29 17:29:06 -0700157
Colin Crosse16ce362020-11-12 08:29:30 -0800158 // If there is more than one command in the manifest use a separate directory for each one.
159 useSubDir := len(manifest.Commands) > 1
160 var commandDepFiles []string
Jeff Gastonefc1b412017-03-29 17:29:06 -0700161
Colin Crosse16ce362020-11-12 08:29:30 -0800162 for i, command := range manifest.Commands {
163 localTempDir := tempDir
164 if useSubDir {
165 localTempDir = filepath.Join(localTempDir, strconv.Itoa(i))
Jeff Gastonefc1b412017-03-29 17:29:06 -0700166 }
Colin Crosse16ce362020-11-12 08:29:30 -0800167 depFile, err := runCommand(command, localTempDir)
Jeff Gaston02a684b2017-10-27 14:59:27 -0700168 if err != nil {
Colin Crosse16ce362020-11-12 08:29:30 -0800169 // Running the command failed, keep the temporary output directory around in
170 // case a user wants to inspect it for debugging purposes. Soong will delete
171 // it at the beginning of the next build anyway.
172 keepOutDir = true
Jeff Gaston02a684b2017-10-27 14:59:27 -0700173 return err
174 }
Colin Crosse16ce362020-11-12 08:29:30 -0800175 if depFile != "" {
176 commandDepFiles = append(commandDepFiles, depFile)
177 }
178 }
179
180 outputDepFile := manifest.GetOutputDepfile()
181 if len(commandDepFiles) > 0 && outputDepFile == "" {
182 return fmt.Errorf("Sandboxed commands used %s but output depfile is not set in manifest file",
183 depFilePlaceholder)
184 }
185
186 if outputDepFile != "" {
187 // Merge the depfiles from each command in the manifest to a single output depfile.
188 err = rewriteDepFiles(commandDepFiles, outputDepFile)
189 if err != nil {
190 return fmt.Errorf("failed merging depfiles: %w", err)
191 }
192 }
193
194 return nil
195}
196
197// readManifest reads an sbox manifest from a textproto file.
198func readManifest(file string) (*sbox_proto.Manifest, error) {
199 manifestData, err := ioutil.ReadFile(file)
200 if err != nil {
201 return nil, fmt.Errorf("error reading manifest %q: %w", file, err)
202 }
203
204 manifest := sbox_proto.Manifest{}
205
206 err = proto.UnmarshalText(string(manifestData), &manifest)
207 if err != nil {
208 return nil, fmt.Errorf("error parsing manifest %q: %w", file, err)
209 }
210
211 return &manifest, nil
212}
213
214// runCommand runs a single command from a manifest. If the command references the
215// __SBOX_DEPFILE__ placeholder it returns the name of the depfile that was used.
216func runCommand(command *sbox_proto.Command, tempDir string) (depFile string, err error) {
217 rawCommand := command.GetCommand()
218 if rawCommand == "" {
219 return "", fmt.Errorf("command is required")
220 }
221
Colin Crosse55bd422021-03-23 13:44:30 -0700222 pathToTempDirInSbox := tempDir
223 if command.GetChdir() {
224 pathToTempDirInSbox = "."
225 }
226
Colin Crosse16ce362020-11-12 08:29:30 -0800227 err = os.MkdirAll(tempDir, 0777)
228 if err != nil {
229 return "", fmt.Errorf("failed to create %q: %w", tempDir, err)
230 }
231
232 // Copy in any files specified by the manifest.
Colin Crossfc2d8422021-04-15 18:36:42 -0700233 err = copyFiles(command.CopyBefore, "", tempDir, false)
Colin Crosse16ce362020-11-12 08:29:30 -0800234 if err != nil {
235 return "", err
236 }
Colin Crosse55bd422021-03-23 13:44:30 -0700237 err = copyRspFiles(command.RspFiles, tempDir, pathToTempDirInSbox)
238 if err != nil {
239 return "", err
Colin Crossc590ec42021-03-11 17:20:02 -0800240 }
241
Colin Crosse16ce362020-11-12 08:29:30 -0800242 if strings.Contains(rawCommand, depFilePlaceholder) {
Colin Crossc590ec42021-03-11 17:20:02 -0800243 depFile = filepath.Join(pathToTempDirInSbox, "deps.d")
Colin Crosse16ce362020-11-12 08:29:30 -0800244 rawCommand = strings.Replace(rawCommand, depFilePlaceholder, depFile, -1)
245 }
246
247 if strings.Contains(rawCommand, sandboxDirPlaceholder) {
Colin Crossc590ec42021-03-11 17:20:02 -0800248 rawCommand = strings.Replace(rawCommand, sandboxDirPlaceholder, pathToTempDirInSbox, -1)
Colin Crosse16ce362020-11-12 08:29:30 -0800249 }
250
251 // Emulate ninja's behavior of creating the directories for any output files before
252 // running the command.
253 err = makeOutputDirs(command.CopyAfter, tempDir)
254 if err != nil {
255 return "", err
Jeff Gastonefc1b412017-03-29 17:29:06 -0700256 }
257
258 cmd := exec.Command("bash", "-c", rawCommand)
Colin Cross4258a392021-04-15 18:50:11 -0700259 buf := &bytes.Buffer{}
Jeff Gastonefc1b412017-03-29 17:29:06 -0700260 cmd.Stdin = os.Stdin
Colin Cross4258a392021-04-15 18:50:11 -0700261 cmd.Stdout = buf
262 cmd.Stderr = buf
Colin Crosse16ce362020-11-12 08:29:30 -0800263
264 if command.GetChdir() {
265 cmd.Dir = tempDir
Colin Crossc590ec42021-03-11 17:20:02 -0800266 path := os.Getenv("PATH")
267 absPath, err := makeAbsPathEnv(path)
268 if err != nil {
269 return "", err
270 }
271 err = os.Setenv("PATH", absPath)
272 if err != nil {
273 return "", fmt.Errorf("Failed to update PATH: %w", err)
274 }
Colin Crosse16ce362020-11-12 08:29:30 -0800275 }
Jeff Gastonefc1b412017-03-29 17:29:06 -0700276 err = cmd.Run()
Jeff Gaston193f2fb2017-06-12 15:00:12 -0700277
Colin Crossfc2d8422021-04-15 18:36:42 -0700278 if err != nil {
279 // The command failed, do a best effort copy of output files out of the sandbox. This is
280 // especially useful for linters with baselines that print an error message on failure
281 // with a command to copy the output lint errors to the new baseline. Use a copy instead of
282 // a move to leave the sandbox intact for manual inspection
283 copyFiles(command.CopyAfter, tempDir, "", true)
284 }
285
Colin Cross4258a392021-04-15 18:50:11 -0700286 // If the command was executed but failed with an error, print a debugging message before
287 // the command's output so it doesn't scroll the real error message off the screen.
Jeff Gastonefc1b412017-03-29 17:29:06 -0700288 if exit, ok := err.(*exec.ExitError); ok && !exit.Success() {
Colin Cross4258a392021-04-15 18:50:11 -0700289 fmt.Fprintf(os.Stderr,
290 "The failing command was run inside an sbox sandbox in temporary directory\n"+
291 "%s\n"+
292 "The failing command line was:\n"+
293 "%s\n",
294 tempDir, rawCommand)
295 }
296
297 // Write the command's combined stdout/stderr.
298 os.Stdout.Write(buf.Bytes())
299
300 if err != nil {
Colin Crosse16ce362020-11-12 08:29:30 -0800301 return "", err
Jeff Gastonefc1b412017-03-29 17:29:06 -0700302 }
303
Colin Crosse16ce362020-11-12 08:29:30 -0800304 missingOutputErrors := validateOutputFiles(command.CopyAfter, tempDir)
305
Jeff Gaston90cfb092017-09-26 16:46:10 -0700306 if len(missingOutputErrors) > 0 {
307 // find all created files for making a more informative error message
308 createdFiles := findAllFilesUnder(tempDir)
309
310 // build error message
311 errorMessage := "mismatch between declared and actual outputs\n"
Colin Cross4258a392021-04-15 18:50:11 -0700312 errorMessage += "in sbox command(" + rawCommand + ")\n\n"
Jeff Gaston90cfb092017-09-26 16:46:10 -0700313 errorMessage += "in sandbox " + tempDir + ",\n"
314 errorMessage += fmt.Sprintf("failed to create %v files:\n", len(missingOutputErrors))
315 for _, missingOutputError := range missingOutputErrors {
Colin Crosse16ce362020-11-12 08:29:30 -0800316 errorMessage += " " + missingOutputError.Error() + "\n"
Jeff Gaston90cfb092017-09-26 16:46:10 -0700317 }
318 if len(createdFiles) < 1 {
319 errorMessage += "created 0 files."
320 } else {
321 errorMessage += fmt.Sprintf("did create %v files:\n", len(createdFiles))
322 creationMessages := createdFiles
323 maxNumCreationLines := 10
324 if len(creationMessages) > maxNumCreationLines {
325 creationMessages = creationMessages[:maxNumCreationLines]
326 creationMessages = append(creationMessages, fmt.Sprintf("...%v more", len(createdFiles)-maxNumCreationLines))
327 }
328 for _, creationMessage := range creationMessages {
329 errorMessage += " " + creationMessage + "\n"
330 }
331 }
332
Colin Crosse16ce362020-11-12 08:29:30 -0800333 return "", errors.New(errorMessage)
Jeff Gastonf49082a2017-06-07 13:22:22 -0700334 }
335 // the created files match the declared files; now move them
Colin Crosse16ce362020-11-12 08:29:30 -0800336 err = moveFiles(command.CopyAfter, tempDir, "")
337
338 return depFile, nil
339}
340
341// makeOutputDirs creates directories in the sandbox dir for every file that has a rule to be copied
342// out of the sandbox. This emulate's Ninja's behavior of creating directories for output files
343// so that the tools don't have to.
344func makeOutputDirs(copies []*sbox_proto.Copy, sandboxDir string) error {
345 for _, copyPair := range copies {
346 dir := joinPath(sandboxDir, filepath.Dir(copyPair.GetFrom()))
347 err := os.MkdirAll(dir, 0777)
348 if err != nil {
349 return err
Jeff Gaston193f2fb2017-06-12 15:00:12 -0700350 }
Colin Crosse16ce362020-11-12 08:29:30 -0800351 }
352 return nil
353}
354
355// validateOutputFiles verifies that all files that have a rule to be copied out of the sandbox
356// were created by the command.
357func validateOutputFiles(copies []*sbox_proto.Copy, sandboxDir string) []error {
358 var missingOutputErrors []error
359 for _, copyPair := range copies {
360 fromPath := joinPath(sandboxDir, copyPair.GetFrom())
361 fileInfo, err := os.Stat(fromPath)
362 if err != nil {
363 missingOutputErrors = append(missingOutputErrors, fmt.Errorf("%s: does not exist", fromPath))
364 continue
365 }
366 if fileInfo.IsDir() {
367 missingOutputErrors = append(missingOutputErrors, fmt.Errorf("%s: not a file", fromPath))
368 }
369 }
370 return missingOutputErrors
371}
372
Colin Crossfc2d8422021-04-15 18:36:42 -0700373// copyFiles copies files in or out of the sandbox. If allowFromNotExists is true then errors
374// caused by a from path not existing are ignored.
375func copyFiles(copies []*sbox_proto.Copy, fromDir, toDir string, allowFromNotExists bool) error {
Colin Crosse16ce362020-11-12 08:29:30 -0800376 for _, copyPair := range copies {
377 fromPath := joinPath(fromDir, copyPair.GetFrom())
378 toPath := joinPath(toDir, copyPair.GetTo())
Colin Crossfc2d8422021-04-15 18:36:42 -0700379 err := copyOneFile(fromPath, toPath, copyPair.GetExecutable(), allowFromNotExists)
Colin Crosse16ce362020-11-12 08:29:30 -0800380 if err != nil {
381 return fmt.Errorf("error copying %q to %q: %w", fromPath, toPath, err)
382 }
383 }
384 return nil
385}
386
Colin Crossfc2d8422021-04-15 18:36:42 -0700387// copyOneFile copies a file and its permissions. If forceExecutable is true it adds u+x to the
388// permissions. If allowFromNotExists is true it returns nil if the from path doesn't exist.
389func copyOneFile(from string, to string, forceExecutable, allowFromNotExists bool) error {
Colin Crosse16ce362020-11-12 08:29:30 -0800390 err := os.MkdirAll(filepath.Dir(to), 0777)
391 if err != nil {
392 return err
393 }
394
Colin Crosse16ce362020-11-12 08:29:30 -0800395 stat, err := os.Stat(from)
396 if err != nil {
Colin Crossfc2d8422021-04-15 18:36:42 -0700397 if os.IsNotExist(err) && allowFromNotExists {
398 return nil
399 }
Colin Crosse16ce362020-11-12 08:29:30 -0800400 return err
401 }
402
403 perm := stat.Mode()
Colin Crossfc2d8422021-04-15 18:36:42 -0700404 if forceExecutable {
Colin Cross859dfd92020-11-30 20:12:47 -0800405 perm = perm | 0100 // u+x
406 }
Colin Crosse16ce362020-11-12 08:29:30 -0800407
408 in, err := os.Open(from)
409 if err != nil {
410 return err
411 }
412 defer in.Close()
413
Colin Cross607c0b72021-03-31 12:54:06 -0700414 // Remove the target before copying. In most cases the file won't exist, but if there are
415 // duplicate copy rules for a file and the source file was read-only the second copy could
416 // fail.
417 err = os.Remove(to)
418 if err != nil && !os.IsNotExist(err) {
419 return err
420 }
421
Colin Crosse16ce362020-11-12 08:29:30 -0800422 out, err := os.Create(to)
423 if err != nil {
424 return err
425 }
426 defer func() {
427 out.Close()
428 if err != nil {
429 os.Remove(to)
430 }
431 }()
432
433 _, err = io.Copy(out, in)
434 if err != nil {
435 return err
436 }
437
438 if err = out.Close(); err != nil {
439 return err
440 }
441
442 if err = os.Chmod(to, perm); err != nil {
443 return err
444 }
445
446 return nil
447}
448
Colin Crosse55bd422021-03-23 13:44:30 -0700449// copyRspFiles copies rsp files into the sandbox with path mappings, and also copies the files
450// listed into the sandbox.
451func copyRspFiles(rspFiles []*sbox_proto.RspFile, toDir, toDirInSandbox string) error {
452 for _, rspFile := range rspFiles {
453 err := copyOneRspFile(rspFile, toDir, toDirInSandbox)
454 if err != nil {
455 return err
456 }
457 }
458 return nil
459}
460
461// copyOneRspFiles copies an rsp file into the sandbox with path mappings, and also copies the files
462// listed into the sandbox.
463func copyOneRspFile(rspFile *sbox_proto.RspFile, toDir, toDirInSandbox string) error {
464 in, err := os.Open(rspFile.GetFile())
465 if err != nil {
466 return err
467 }
468 defer in.Close()
469
470 files, err := response.ReadRspFile(in)
471 if err != nil {
472 return err
473 }
474
475 for i, from := range files {
476 // Convert the real path of the input file into the path inside the sandbox using the
477 // path mappings.
478 to := applyPathMappings(rspFile.PathMappings, from)
479
480 // Copy the file into the sandbox.
Colin Crossfc2d8422021-04-15 18:36:42 -0700481 err := copyOneFile(from, joinPath(toDir, to), false, false)
Colin Crosse55bd422021-03-23 13:44:30 -0700482 if err != nil {
483 return err
484 }
485
486 // Rewrite the name in the list of files to be relative to the sandbox directory.
487 files[i] = joinPath(toDirInSandbox, to)
488 }
489
490 // Convert the real path of the rsp file into the path inside the sandbox using the path
491 // mappings.
492 outRspFile := joinPath(toDir, applyPathMappings(rspFile.PathMappings, rspFile.GetFile()))
493
494 err = os.MkdirAll(filepath.Dir(outRspFile), 0777)
495 if err != nil {
496 return err
497 }
498
499 out, err := os.Create(outRspFile)
500 if err != nil {
501 return err
502 }
503 defer out.Close()
504
505 // Write the rsp file with converted paths into the sandbox.
506 err = response.WriteRspFile(out, files)
507 if err != nil {
508 return err
509 }
510
511 return nil
512}
513
514// applyPathMappings takes a list of path mappings and a path, and returns the path with the first
515// matching path mapping applied. If the path does not match any of the path mappings then it is
516// returned unmodified.
517func applyPathMappings(pathMappings []*sbox_proto.PathMapping, path string) string {
518 for _, mapping := range pathMappings {
519 if strings.HasPrefix(path, mapping.GetFrom()+"/") {
520 return joinPath(mapping.GetTo()+"/", strings.TrimPrefix(path, mapping.GetFrom()+"/"))
521 }
522 }
523 return path
524}
525
Colin Crosse16ce362020-11-12 08:29:30 -0800526// moveFiles moves files specified by a set of copy rules. It uses os.Rename, so it is restricted
527// to moving files where the source and destination are in the same filesystem. This is OK for
528// sbox because the temporary directory is inside the out directory. It updates the timestamp
529// of the new file.
530func moveFiles(copies []*sbox_proto.Copy, fromDir, toDir string) error {
531 for _, copyPair := range copies {
532 fromPath := joinPath(fromDir, copyPair.GetFrom())
533 toPath := joinPath(toDir, copyPair.GetTo())
534 err := os.MkdirAll(filepath.Dir(toPath), 0777)
535 if err != nil {
536 return err
537 }
538
539 err = os.Rename(fromPath, toPath)
Jeff Gaston8a88db52017-11-06 13:33:14 -0800540 if err != nil {
541 return err
542 }
Colin Crossd1c1e6f2019-03-29 13:54:39 -0700543
544 // Update the timestamp of the output file in case the tool wrote an old timestamp (for example, tar can extract
545 // files with old timestamps).
546 now := time.Now()
Colin Crosse16ce362020-11-12 08:29:30 -0800547 err = os.Chtimes(toPath, now, now)
Jeff Gastonefc1b412017-03-29 17:29:06 -0700548 if err != nil {
549 return err
550 }
551 }
Jeff Gastonefc1b412017-03-29 17:29:06 -0700552 return nil
553}
Colin Crosse16ce362020-11-12 08:29:30 -0800554
555// Rewrite one or more depfiles so that it doesn't include the (randomized) sandbox directory
556// to an output file.
557func rewriteDepFiles(ins []string, out string) error {
558 var mergedDeps []string
559 for _, in := range ins {
560 data, err := ioutil.ReadFile(in)
561 if err != nil {
562 return err
563 }
564
565 deps, err := makedeps.Parse(in, bytes.NewBuffer(data))
566 if err != nil {
567 return err
568 }
569 mergedDeps = append(mergedDeps, deps.Inputs...)
570 }
571
572 deps := makedeps.Deps{
573 // Ninja doesn't care what the output file is, so we can use any string here.
574 Output: "outputfile",
575 Inputs: mergedDeps,
576 }
577
578 // Make the directory for the output depfile in case it is in a different directory
579 // than any of the output files.
580 outDir := filepath.Dir(out)
581 err := os.MkdirAll(outDir, 0777)
582 if err != nil {
583 return fmt.Errorf("failed to create %q: %w", outDir, err)
584 }
585
586 return ioutil.WriteFile(out, deps.Print(), 0666)
587}
588
589// joinPath wraps filepath.Join but returns file without appending to dir if file is
590// absolute.
591func joinPath(dir, file string) string {
592 if filepath.IsAbs(file) {
593 return file
594 }
595 return filepath.Join(dir, file)
596}
Colin Crossc590ec42021-03-11 17:20:02 -0800597
598func makeAbsPathEnv(pathEnv string) (string, error) {
599 pathEnvElements := filepath.SplitList(pathEnv)
600 for i, p := range pathEnvElements {
601 if !filepath.IsAbs(p) {
602 absPath, err := filepath.Abs(p)
603 if err != nil {
604 return "", fmt.Errorf("failed to make PATH entry %q absolute: %w", p, err)
605 }
606 pathEnvElements[i] = absPath
607 }
608 }
609 return strings.Join(pathEnvElements, string(filepath.ListSeparator)), nil
610}