0506361a60
The source tree will eventually be made ReadOnly, and recipes that write directly to the source tree will fail. Use a pattern-match approach on the results of stdout/stderr to provide hints to the user in such a scenario. If multiple patterns are found in raw output, print error hint corresponding to first pattern match. first pattern match is chosen since the failing function will be at the top of the stack, and hence will be logged first Test: Wrote a unit test to assert errorhint is added to output. Wrote an integration test that writes to a file in the source tree 1. When source_tree is RO, the recipe fails and an error hint is printed to stdout 2. When source tree is RW, the recipe succeeds and no error hint is printed Bug: 174726238 Change-Id: Id67b48f8094cdf8a571c239ae469d60464a1e89c
249 lines
6.7 KiB
Go
249 lines
6.7 KiB
Go
// Copyright 2017 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 build
|
|
|
|
import (
|
|
"bytes"
|
|
"os"
|
|
"os/exec"
|
|
"os/user"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
type Sandbox struct {
|
|
Enabled bool
|
|
DisableWhenUsingGoma bool
|
|
|
|
AllowBuildBrokenUsesNetwork bool
|
|
}
|
|
|
|
var (
|
|
noSandbox = Sandbox{}
|
|
basicSandbox = Sandbox{
|
|
Enabled: true,
|
|
}
|
|
|
|
dumpvarsSandbox = basicSandbox
|
|
katiSandbox = basicSandbox
|
|
soongSandbox = basicSandbox
|
|
ninjaSandbox = Sandbox{
|
|
Enabled: true,
|
|
DisableWhenUsingGoma: true,
|
|
|
|
AllowBuildBrokenUsesNetwork: true,
|
|
}
|
|
)
|
|
|
|
const nsjailPath = "prebuilts/build-tools/linux-x86/bin/nsjail"
|
|
|
|
var sandboxConfig struct {
|
|
once sync.Once
|
|
|
|
working bool
|
|
group string
|
|
srcDir string
|
|
outDir string
|
|
distDir string
|
|
}
|
|
|
|
func (c *Cmd) sandboxSupported() bool {
|
|
if !c.Sandbox.Enabled {
|
|
return false
|
|
}
|
|
|
|
// Goma is incompatible with PID namespaces and Mount namespaces. b/122767582
|
|
if c.Sandbox.DisableWhenUsingGoma && c.config.UseGoma() {
|
|
return false
|
|
}
|
|
|
|
sandboxConfig.once.Do(func() {
|
|
sandboxConfig.group = "nogroup"
|
|
if _, err := user.LookupGroup(sandboxConfig.group); err != nil {
|
|
sandboxConfig.group = "nobody"
|
|
}
|
|
|
|
// These directories will be bind mounted
|
|
// so we need full non-symlink paths
|
|
sandboxConfig.srcDir = absPath(c.ctx, ".")
|
|
if derefPath, err := filepath.EvalSymlinks(sandboxConfig.srcDir); err == nil {
|
|
sandboxConfig.srcDir = absPath(c.ctx, derefPath)
|
|
}
|
|
sandboxConfig.outDir = absPath(c.ctx, c.config.OutDir())
|
|
if derefPath, err := filepath.EvalSymlinks(sandboxConfig.outDir); err == nil {
|
|
sandboxConfig.outDir = absPath(c.ctx, derefPath)
|
|
}
|
|
sandboxConfig.distDir = absPath(c.ctx, c.config.DistDir())
|
|
if derefPath, err := filepath.EvalSymlinks(sandboxConfig.distDir); err == nil {
|
|
sandboxConfig.distDir = absPath(c.ctx, derefPath)
|
|
}
|
|
|
|
sandboxArgs := []string{
|
|
"-H", "android-build",
|
|
"-e",
|
|
"-u", "nobody",
|
|
"-g", sandboxConfig.group,
|
|
"-R", "/",
|
|
// Mount tmp before srcDir
|
|
// srcDir is /tmp/.* in integration tests, which is a child dir of /tmp
|
|
// nsjail throws an error if a child dir is mounted before its parent
|
|
"-B", "/tmp",
|
|
"-B", sandboxConfig.srcDir,
|
|
"-B", sandboxConfig.outDir,
|
|
}
|
|
|
|
if _, err := os.Stat(sandboxConfig.distDir); !os.IsNotExist(err) {
|
|
//Mount dist dir as read-write if it already exists
|
|
sandboxArgs = append(sandboxArgs, "-B",
|
|
sandboxConfig.distDir)
|
|
}
|
|
|
|
sandboxArgs = append(sandboxArgs,
|
|
"--disable_clone_newcgroup",
|
|
"--",
|
|
"/bin/bash", "-c", `if [ $(hostname) == "android-build" ]; then echo "Android" "Success"; else echo Failure; fi`)
|
|
|
|
cmd := exec.CommandContext(c.ctx.Context, nsjailPath, sandboxArgs...)
|
|
|
|
cmd.Env = c.config.Environment().Environ()
|
|
|
|
c.ctx.Verboseln(cmd.Args)
|
|
data, err := cmd.CombinedOutput()
|
|
if err == nil && bytes.Contains(data, []byte("Android Success")) {
|
|
sandboxConfig.working = true
|
|
return
|
|
}
|
|
|
|
c.ctx.Println("Build sandboxing disabled due to nsjail error.")
|
|
|
|
for _, line := range strings.Split(strings.TrimSpace(string(data)), "\n") {
|
|
c.ctx.Verboseln(line)
|
|
}
|
|
|
|
if err == nil {
|
|
c.ctx.Verboseln("nsjail exited successfully, but without the correct output")
|
|
} else if e, ok := err.(*exec.ExitError); ok {
|
|
c.ctx.Verbosef("nsjail failed with %v", e.ProcessState.String())
|
|
} else {
|
|
c.ctx.Verbosef("nsjail failed with %v", err)
|
|
}
|
|
})
|
|
|
|
return sandboxConfig.working
|
|
}
|
|
|
|
func (c *Cmd) wrapSandbox() {
|
|
wd, _ := os.Getwd()
|
|
|
|
var srcDirMountFlag string
|
|
if c.config.sandboxConfig.SrcDirIsRO() {
|
|
srcDirMountFlag = "-R"
|
|
} else {
|
|
srcDirMountFlag = "-B" //Read-Write
|
|
}
|
|
|
|
sandboxArgs := []string{
|
|
// The executable to run
|
|
"-x", c.Path,
|
|
|
|
// Set the hostname to something consistent
|
|
"-H", "android-build",
|
|
|
|
// Use the current working dir
|
|
"--cwd", wd,
|
|
|
|
// No time limit
|
|
"-t", "0",
|
|
|
|
// Keep all environment variables, we already filter them out
|
|
// in soong_ui
|
|
"-e",
|
|
|
|
// Mount /proc read-write, necessary to run a nested nsjail or minijail0
|
|
"--proc_rw",
|
|
|
|
// Use a consistent user & group.
|
|
// Note that these are mapped back to the real UID/GID when
|
|
// doing filesystem operations, so they're rather arbitrary.
|
|
"-u", "nobody",
|
|
"-g", sandboxConfig.group,
|
|
|
|
// Set high values, as nsjail uses low defaults.
|
|
"--rlimit_as", "soft",
|
|
"--rlimit_core", "soft",
|
|
"--rlimit_cpu", "soft",
|
|
"--rlimit_fsize", "soft",
|
|
"--rlimit_nofile", "soft",
|
|
|
|
// For now, just map everything. Make most things readonly.
|
|
"-R", "/",
|
|
|
|
// Mount a writable tmp dir
|
|
"-B", "/tmp",
|
|
|
|
// Mount source
|
|
srcDirMountFlag, sandboxConfig.srcDir,
|
|
|
|
//Mount out dir as read-write
|
|
"-B", sandboxConfig.outDir,
|
|
|
|
// Disable newcgroup for now, since it may require newer kernels
|
|
// TODO: try out cgroups
|
|
"--disable_clone_newcgroup",
|
|
|
|
// Only log important warnings / errors
|
|
"-q",
|
|
}
|
|
|
|
// Mount srcDir RW allowlists as Read-Write
|
|
if len(c.config.sandboxConfig.SrcDirRWAllowlist()) > 0 && !c.config.sandboxConfig.SrcDirIsRO() {
|
|
errMsg := `Product source tree has been set as ReadWrite, RW allowlist not necessary.
|
|
To recover, either
|
|
1. Unset BUILD_BROKEN_SRC_DIR_IS_WRITABLE #or
|
|
2. Unset BUILD_BROKEN_SRC_DIR_RW_ALLOWLIST`
|
|
c.ctx.Fatalln(errMsg)
|
|
}
|
|
for _, srcDirChild := range c.config.sandboxConfig.SrcDirRWAllowlist() {
|
|
sandboxArgs = append(sandboxArgs, "-B", srcDirChild)
|
|
}
|
|
|
|
if _, err := os.Stat(sandboxConfig.distDir); !os.IsNotExist(err) {
|
|
//Mount dist dir as read-write if it already exists
|
|
sandboxArgs = append(sandboxArgs, "-B", sandboxConfig.distDir)
|
|
}
|
|
|
|
if c.Sandbox.AllowBuildBrokenUsesNetwork && c.config.BuildBrokenUsesNetwork() {
|
|
c.ctx.Printf("AllowBuildBrokenUsesNetwork: %v", c.Sandbox.AllowBuildBrokenUsesNetwork)
|
|
c.ctx.Printf("BuildBrokenUsesNetwork: %v", c.config.BuildBrokenUsesNetwork())
|
|
sandboxArgs = append(sandboxArgs, "-N")
|
|
} else if dlv, _ := c.config.Environment().Get("SOONG_DELVE"); dlv != "" {
|
|
// The debugger is enabled and soong_build will pause until a remote delve process connects, allow
|
|
// network connections.
|
|
sandboxArgs = append(sandboxArgs, "-N")
|
|
}
|
|
|
|
// Stop nsjail from parsing arguments
|
|
sandboxArgs = append(sandboxArgs, "--")
|
|
|
|
c.Args = append(sandboxArgs, c.Args[1:]...)
|
|
c.Path = nsjailPath
|
|
|
|
env := Environment(c.Env)
|
|
if _, hasUser := env.Get("USER"); hasUser {
|
|
env.Set("USER", "nobody")
|
|
}
|
|
c.Env = []string(env)
|
|
}
|