diff options
-rw-r--r-- | android/rule_builder.go | 186 | ||||
-rw-r--r-- | genrule/Android.bp | 50 | ||||
-rw-r--r-- | genrule/genrule.go | 53 |
3 files changed, 255 insertions, 34 deletions
diff --git a/android/rule_builder.go b/android/rule_builder.go index 18bbcab5c..56de9cd00 100644 --- a/android/rule_builder.go +++ b/android/rule_builder.go @@ -38,6 +38,9 @@ const sboxOutSubDir = "out" const sboxToolsSubDir = "tools" const sboxOutDir = sboxSandboxBaseDir + "/" + sboxOutSubDir +const nsjailToolsSubDir = "tools" +const nsjailOutDir = "out" + // RuleBuilder provides an alternative to ModuleContext.Rule and ModuleContext.Build to add a command line to the build // graph. type RuleBuilder struct { @@ -59,6 +62,9 @@ type RuleBuilder struct { sboxManifestPath WritablePath missingDeps []string args map[string]string + nsjail bool + nsjailBasePath WritablePath + nsjailImplicits Paths } // NewRuleBuilder returns a newly created RuleBuilder. @@ -165,12 +171,43 @@ func (r *RuleBuilder) Sbox(outputDir WritablePath, manifestPath WritablePath) *R if len(r.commands) > 0 { panic("Sbox() may not be called after Command()") } + if r.nsjail { + panic("Sbox() may not be called after Nsjail()") + } r.sbox = true r.outDir = outputDir r.sboxManifestPath = manifestPath return r } +// Nsjail marks the rule as needing to be wrapped by nsjail. The outputDir should point to the +// output directory that nsjail will mount to out/. It should not be written to by any other rule. +// baseDir should point to a location where nsjail will mount to /nsjail_build_sandbox, which will +// be the working directory of the command. +func (r *RuleBuilder) Nsjail(outputDir WritablePath, baseDir WritablePath) *RuleBuilder { + if len(r.commands) > 0 { + panic("Nsjail() may not be called after Command()") + } + if r.sbox { + panic("Nsjail() may not be called after Sbox()") + } + r.nsjail = true + r.outDir = outputDir + r.nsjailBasePath = baseDir + return r +} + +// NsjailImplicits adds implicit inputs that are not directly mounted. This is useful when +// the rule mounts directories, as files within those directories can be globbed and +// tracked as dependencies with NsjailImplicits(). +func (r *RuleBuilder) NsjailImplicits(inputs Paths) *RuleBuilder { + if !r.nsjail { + panic("NsjailImplicits() must be called after Nsjail()") + } + r.nsjailImplicits = append(r.nsjailImplicits, inputs...) + return r +} + // SandboxTools enables tool sandboxing for the rule by copying any referenced tools into the // sandbox. func (r *RuleBuilder) SandboxTools() *RuleBuilder { @@ -514,7 +551,73 @@ func (r *RuleBuilder) build(name string, desc string, ninjaEscapeCommandString b commandString := strings.Join(commands, " && ") - if r.sbox { + if !r.sbox { + // If not using sbox the rule will run the command directly, put the hash of the + // list of input files in a comment at the end of the command line to ensure ninja + // reruns the rule when the list of input files changes. + commandString += " # hash of input list: " + hashSrcFiles(inputs) + } + + if r.nsjail { + var nsjailCmd strings.Builder + nsjailPath := r.ctx.Config().PrebuiltBuildTool(r.ctx, "nsjail") + nsjailCmd.WriteString("mkdir -p ") + nsjailCmd.WriteString(r.nsjailBasePath.String()) + nsjailCmd.WriteString(" && ") + nsjailCmd.WriteString(nsjailPath.String()) + nsjailCmd.WriteRune(' ') + nsjailCmd.WriteString("-B $PWD/") + nsjailCmd.WriteString(r.nsjailBasePath.String()) + nsjailCmd.WriteString(":nsjail_build_sandbox") + + // out is mounted to $(genDir). + nsjailCmd.WriteString(" -B $PWD/") + nsjailCmd.WriteString(r.outDir.String()) + nsjailCmd.WriteString(":nsjail_build_sandbox/out") + + for _, input := range inputs { + nsjailCmd.WriteString(" -R $PWD/") + nsjailCmd.WriteString(input.String()) + nsjailCmd.WriteString(":nsjail_build_sandbox/") + nsjailCmd.WriteString(r.nsjailPathForInputRel(input)) + } + for _, tool := range tools { + nsjailCmd.WriteString(" -R $PWD/") + nsjailCmd.WriteString(tool.String()) + nsjailCmd.WriteString(":nsjail_build_sandbox/") + nsjailCmd.WriteString(nsjailPathForToolRel(r.ctx, tool)) + } + inputs = append(inputs, tools...) + for _, c := range r.commands { + for _, tool := range c.packagedTools { + nsjailCmd.WriteString(" -R $PWD/") + nsjailCmd.WriteString(tool.srcPath.String()) + nsjailCmd.WriteString(":nsjail_build_sandbox/") + nsjailCmd.WriteString(nsjailPathForPackagedToolRel(tool)) + inputs = append(inputs, tool.srcPath) + } + } + + // These five directories are necessary to run native host tools like /bin/bash and py3-cmd. + nsjailCmd.WriteString(" -R /bin") + nsjailCmd.WriteString(" -R /lib") + nsjailCmd.WriteString(" -R /lib64") + nsjailCmd.WriteString(" -R /dev") + nsjailCmd.WriteString(" -R /usr") + + nsjailCmd.WriteString(" -m none:/tmp:tmpfs:size=1073741824") // 1GB, should be enough + nsjailCmd.WriteString(" -D nsjail_build_sandbox") + nsjailCmd.WriteString(" --disable_rlimits") + nsjailCmd.WriteString(" -q") + nsjailCmd.WriteString(" -- ") + nsjailCmd.WriteString("/bin/bash -c ") + nsjailCmd.WriteString(proptools.ShellEscape(commandString)) + + commandString = nsjailCmd.String() + + inputs = append(inputs, nsjailPath) + inputs = append(inputs, r.nsjailImplicits...) + } else if r.sbox { // If running the command inside sbox, write the rule data out to an sbox // manifest.textproto. manifest := sbox_proto.Manifest{} @@ -734,11 +837,6 @@ func (r *RuleBuilder) build(name string, desc string, ninjaEscapeCommandString b rewrapperCommand := r.rbeParams.NoVarTemplate(r.ctx.Config().RBEWrapper()) commandString = rewrapperCommand + " bash -c '" + strings.ReplaceAll(commandString, `'`, `'\''`) + "'" } - } else { - // If not using sbox the rule will run the command directly, put the hash of the - // list of input files in a comment at the end of the command line to ensure ninja - // reruns the rule when the list of input files changes. - commandString += " # hash of input list: " + hashSrcFiles(inputs) } // Ninja doesn't like multiple outputs when depfiles are enabled, move all but the first output to @@ -869,6 +967,8 @@ func (c *RuleBuilderCommand) PathForInput(path Path) string { rel = filepath.Join(sboxSandboxBaseDir, rel) } return rel + } else if c.rule.nsjail { + return c.rule.nsjailPathForInputRel(path) } return path.String() } @@ -894,6 +994,10 @@ func (c *RuleBuilderCommand) PathForOutput(path WritablePath) string { // Errors will be handled in RuleBuilder.Build where we have a context to report them rel, _, _ := maybeRelErr(c.rule.outDir.String(), path.String()) return filepath.Join(sboxOutDir, rel) + } else if c.rule.nsjail { + // Errors will be handled in RuleBuilder.Build where we have a context to report them + rel, _, _ := maybeRelErr(c.rule.outDir.String(), path.String()) + return filepath.Join(nsjailOutDir, rel) } return path.String() } @@ -945,15 +1049,49 @@ func sboxPathForPackagedToolRel(spec PackagingSpec) string { return filepath.Join(sboxToolsSubDir, "out", spec.relPathInPackage) } +func nsjailPathForToolRel(ctx BuilderContext, path Path) string { + // Errors will be handled in RuleBuilder.Build where we have a context to report them + toolDir := pathForInstall(ctx, ctx.Config().BuildOS, ctx.Config().BuildArch, "") + relOutSoong, isRelOutSoong, _ := maybeRelErr(toolDir.String(), path.String()) + if isRelOutSoong { + // The tool is in the Soong output directory, it will be copied to __SBOX_OUT_DIR__/tools/out + return filepath.Join(nsjailToolsSubDir, "out", relOutSoong) + } + // The tool is in the source directory, it will be copied to __SBOX_OUT_DIR__/tools/src + return filepath.Join(nsjailToolsSubDir, "src", path.String()) +} + +func (r *RuleBuilder) nsjailPathForInputRel(path Path) string { + rel, isRelSboxOut, _ := maybeRelErr(r.outDir.String(), path.String()) + if isRelSboxOut { + return filepath.Join(nsjailOutDir, rel) + } + return path.String() +} + +func (r *RuleBuilder) nsjailPathsForInputsRel(paths Paths) []string { + ret := make([]string, len(paths)) + for i, path := range paths { + ret[i] = r.nsjailPathForInputRel(path) + } + return ret +} + +func nsjailPathForPackagedToolRel(spec PackagingSpec) string { + return filepath.Join(nsjailToolsSubDir, "out", spec.relPathInPackage) +} + // PathForPackagedTool takes a PackageSpec for a tool and returns the corresponding path for the // tool after copying it into the sandbox. This can be used on the RuleBuilder command line to // reference the tool. func (c *RuleBuilderCommand) PathForPackagedTool(spec PackagingSpec) string { - if !c.rule.sboxTools { - panic("PathForPackagedTool() requires SandboxTools()") + if c.rule.sboxTools { + return filepath.Join(sboxSandboxBaseDir, sboxPathForPackagedToolRel(spec)) + } else if c.rule.nsjail { + return nsjailPathForPackagedToolRel(spec) + } else { + panic("PathForPackagedTool() requires SandboxTools() or Nsjail()") } - - return filepath.Join(sboxSandboxBaseDir, sboxPathForPackagedToolRel(spec)) } // PathForTool takes a path to a tool, which may be an output file or a source file, and returns @@ -962,6 +1100,8 @@ func (c *RuleBuilderCommand) PathForPackagedTool(spec PackagingSpec) string { func (c *RuleBuilderCommand) PathForTool(path Path) string { if c.rule.sbox && c.rule.sboxTools { return filepath.Join(sboxSandboxBaseDir, sboxPathForToolRel(c.rule.ctx, path)) + } else if c.rule.nsjail { + return nsjailPathForToolRel(c.rule.ctx, path) } return path.String() } @@ -976,6 +1116,12 @@ func (c *RuleBuilderCommand) PathsForTools(paths Paths) []string { ret = append(ret, filepath.Join(sboxSandboxBaseDir, sboxPathForToolRel(c.rule.ctx, path))) } return ret + } else if c.rule.nsjail { + var ret []string + for _, path := range paths { + ret = append(ret, nsjailPathForToolRel(c.rule.ctx, path)) + } + return ret } return paths.Strings() } @@ -983,20 +1129,22 @@ func (c *RuleBuilderCommand) PathsForTools(paths Paths) []string { // PackagedTool adds the specified tool path to the command line. It can only be used with tool // sandboxing enabled by SandboxTools(), and will copy the tool into the sandbox. func (c *RuleBuilderCommand) PackagedTool(spec PackagingSpec) *RuleBuilderCommand { - if !c.rule.sboxTools { - panic("PackagedTool() requires SandboxTools()") - } - c.packagedTools = append(c.packagedTools, spec) - c.Text(sboxPathForPackagedToolRel(spec)) + if c.rule.sboxTools { + c.Text(sboxPathForPackagedToolRel(spec)) + } else if c.rule.nsjail { + c.Text(nsjailPathForPackagedToolRel(spec)) + } else { + panic("PackagedTool() requires SandboxTools() or Nsjail()") + } return c } // ImplicitPackagedTool copies the specified tool into the sandbox without modifying the command // line. It can only be used with tool sandboxing enabled by SandboxTools(). func (c *RuleBuilderCommand) ImplicitPackagedTool(spec PackagingSpec) *RuleBuilderCommand { - if !c.rule.sboxTools { - panic("ImplicitPackagedTool() requires SandboxTools()") + if !c.rule.sboxTools && !c.rule.nsjail { + panic("ImplicitPackagedTool() requires SandboxTools() or Nsjail()") } c.packagedTools = append(c.packagedTools, spec) @@ -1006,8 +1154,8 @@ func (c *RuleBuilderCommand) ImplicitPackagedTool(spec PackagingSpec) *RuleBuild // ImplicitPackagedTools copies the specified tools into the sandbox without modifying the command // line. It can only be used with tool sandboxing enabled by SandboxTools(). func (c *RuleBuilderCommand) ImplicitPackagedTools(specs []PackagingSpec) *RuleBuilderCommand { - if !c.rule.sboxTools { - panic("ImplicitPackagedTools() requires SandboxTools()") + if !c.rule.sboxTools && !c.rule.nsjail { + panic("ImplicitPackagedTools() requires SandboxTools() or Nsjail()") } c.packagedTools = append(c.packagedTools, specs...) diff --git a/genrule/Android.bp b/genrule/Android.bp index f4197e691..49df48075 100644 --- a/genrule/Android.bp +++ b/genrule/Android.bp @@ -25,3 +25,53 @@ bootstrap_go_package { // Used by plugins visibility: ["//visibility:public"], } + +genrule { + name: "nsjail_genrule_test_input", + cmd: "echo nsjail_genrule_test_input > $(out)", + out: ["nsjail_genrule_test_input.txt"], +} + +// Pseudo-test that's run on checkbuilds to verify consistent directory +// structure for genrules using sbox or nsjail. +genrule_defaults { + name: "nsjail_genrule_test_gen_defaults", + // verify both relative paths and its contents + cmd: "(echo $(out) $(genDir) && sha256sum " + + "$(location get_clang_version) " + + "$(location py3-cmd) " + + "$(location genrule.go) " + + "$(location :nsjail_genrule_test_input) " + + "$(locations *.go)) | sed 's@\\./@@g' > $(out)", + tools: [ + "get_clang_version", // random tool + "py3-cmd", // random prebuilt tool + ], + tool_files: ["genrule.go"], // random local file + srcs: [ + ":nsjail_genrule_test_input", // random OutputFileProducer + "*.go", // random glob + ], + out: ["nsjail_genrule_test.txt"], +} + +genrule { + name: "nsjail_genrule_test_gen_without_nsjail", + defaults: ["nsjail_genrule_test_gen_defaults"], +} + +genrule { + name: "nsjail_genrule_test_gen_with_nsjail", + defaults: ["nsjail_genrule_test_gen_defaults"], + use_nsjail: true, +} + +genrule { + name: "nsjail_genrule_test", + srcs: [ + ":nsjail_genrule_test_gen_without_nsjail", + ":nsjail_genrule_test_gen_with_nsjail", + ], + cmd: "diff $(in) > $(out)", + out: ["nsjail_genrule_test"], +} diff --git a/genrule/genrule.go b/genrule/genrule.go index a48038bac..0aecc452c 100644 --- a/genrule/genrule.go +++ b/genrule/genrule.go @@ -21,6 +21,7 @@ package genrule import ( "fmt" "io" + "path/filepath" "strconv" "strings" @@ -210,6 +211,9 @@ type generateTask struct { // For gensrsc sharding. shard int shards int + + // For nsjail tasks + useNsjail bool } func (g *Module) GeneratedSourceFiles() android.Paths { @@ -454,21 +458,26 @@ func (g *Module) generateCommonBuildActions(ctx android.ModuleContext) { // Pick a unique path outside the task.genDir for the sbox manifest textproto, // a unique rule name, and the user-visible description. - manifestName := "genrule.sbox.textproto" + var rule *android.RuleBuilder desc := "generate" name := "generator" - if task.shards > 0 { - manifestName = "genrule_" + strconv.Itoa(task.shard) + ".sbox.textproto" - desc += " " + strconv.Itoa(task.shard) - name += strconv.Itoa(task.shard) - } else if len(task.out) == 1 { - desc += " " + task.out[0].Base() - } + if task.useNsjail { + rule = android.NewRuleBuilder(pctx, ctx).Nsjail(task.genDir, android.PathForModuleOut(ctx, "nsjail_build_sandbox")) + } else { + manifestName := "genrule.sbox.textproto" + if task.shards > 0 { + manifestName = "genrule_" + strconv.Itoa(task.shard) + ".sbox.textproto" + desc += " " + strconv.Itoa(task.shard) + name += strconv.Itoa(task.shard) + } else if len(task.out) == 1 { + desc += " " + task.out[0].Base() + } - manifestPath := android.PathForModuleOut(ctx, manifestName) + manifestPath := android.PathForModuleOut(ctx, manifestName) - // Use a RuleBuilder to create a rule that runs the command inside an sbox sandbox. - rule := getSandboxedRuleBuilder(ctx, android.NewRuleBuilder(pctx, ctx).Sbox(task.genDir, manifestPath)) + // Use a RuleBuilder to create a rule that runs the command inside an sbox sandbox. + rule = getSandboxedRuleBuilder(ctx, android.NewRuleBuilder(pctx, ctx).Sbox(task.genDir, manifestPath)) + } if Bool(g.properties.Write_if_changed) { rule.Restat() } @@ -569,6 +578,15 @@ func (g *Module) generateCommonBuildActions(ctx android.ModuleContext) { cmd.OrderOnly(ctx.Config().BuildNumberFile(ctx)) } + if task.useNsjail { + for _, input := range task.in { + // can fail if input is a file. + if paths, err := ctx.GlobWithDeps(filepath.Join(input.String(), "**/*"), nil); err == nil { + rule.NsjailImplicits(android.PathsForSource(ctx, paths)) + } + } + } + // Create the rule to run the genrule command inside sbox. rule.Build(name, desc) @@ -832,15 +850,18 @@ func NewGenRule() *Module { properties := &genRuleProperties{} taskGenerator := func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask { + useNsjail := Bool(properties.Use_nsjail) + outs := make(android.WritablePaths, len(properties.Out)) for i, out := range properties.Out { outs[i] = android.PathForModuleGen(ctx, out) } return []generateTask{{ - in: srcFiles, - out: outs, - genDir: android.PathForModuleGen(ctx), - cmd: rawCommand, + in: srcFiles, + out: outs, + genDir: android.PathForModuleGen(ctx), + cmd: rawCommand, + useNsjail: useNsjail, }} } @@ -855,6 +876,8 @@ func GenRuleFactory() android.Module { } type genRuleProperties struct { + Use_nsjail *bool + // names of the output files that will be generated Out []string `android:"arch_variant"` } |