Skip to content

Commit

Permalink
Mount special filesystem in chroot runners
Browse files Browse the repository at this point in the history
This can be used to mount special filesystems like '/proc' and '/sys' in
the input root of actions if 'chroot' is enabled.
  • Loading branch information
stagnation committed Jan 31, 2024
1 parent a56ecc6 commit 9dbbedf
Show file tree
Hide file tree
Showing 5 changed files with 268 additions and 29 deletions.
7 changes: 7 additions & 0 deletions cmd/bb_runner/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ func main() {
buildDirectoryPath,
commandCreator,
configuration.SetTmpdirEnvironmentVariable)
for _, mountinfo := range configuration.InputRootMounts {
r = runner.NewMountingRunner(
r,
buildDirectory,
mountinfo,
)
}

// Let bb_runner replace temporary directories with symbolic
// links pointing to the temporary directory set up by
Expand Down
157 changes: 128 additions & 29 deletions pkg/proto/configuration/bb_runner/bb_runner.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 33 additions & 0 deletions pkg/proto/configuration/bb_runner/bb_runner.proto
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,37 @@ message ApplicationConfiguration {
// https://github.com/bazelbuild/bazel/blob/master/src/main/java/com/google/devtools/build/lib/exec/local/XcodeLocalEnvProvider.java
// https://www.smileykeith.com/2021/03/08/locking-xcode-in-bazel/
map<string, string> apple_xcode_developer_directories = 14;

// Mount special filesystems in the input root. This is useful when
// running with `chroot_into_input_root`. some tools require access to
// special filesystems that are created when the operating system
// boots. An input root with a full userland implementation may need
// these.
//
// The mount point directories must exist in the input root.
//
// Typical choices are:
//
// inputRootMounts: [
// {
// mountpoint: 'proc',
// source: '/proc',
// filesystemType: 'proc',
// },
// {
// mountpoint: 'sys',
// source: '/sys',
// filesystemType: 'sysfs',
// },
// ],
repeated InputMountOptions input_root_mounts = 15;
}

message InputMountOptions {
// Mount a filesystem in the input root, a relative path.
string mountpoint = 1;
// Source filesystem from the runner's operating system, absolute path.
string source = 2;
// Type of filesystem, see `mount`'s man page.
string filesystem_type = 3;
}
2 changes: 2 additions & 0 deletions pkg/runner/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ go_library(
"local_runner_rss_kibibytes.go",
"local_runner_unix.go",
"local_runner_windows.go",
"mounting_runner.go",
"path_existence_checking_runner.go",
"temporary_directory_installing_runner.go",
"temporary_directory_symlinking_runner.go",
Expand All @@ -19,6 +20,7 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//pkg/cleaner",
"//pkg/proto/configuration/bb_runner",
"//pkg/proto/runner",
"//pkg/proto/tmp_installer",
"@com_github_buildbarn_bb_storage//pkg/filesystem",
Expand Down
98 changes: 98 additions & 0 deletions pkg/runner/mounting_runner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package runner

import (
"context"
"strings"

"github.com/buildbarn/bb-remote-execution/pkg/proto/configuration/bb_runner"
runner_pb "github.com/buildbarn/bb-remote-execution/pkg/proto/runner"
"github.com/buildbarn/bb-storage/pkg/filesystem"
"github.com/buildbarn/bb-storage/pkg/filesystem/path"
"github.com/buildbarn/bb-storage/pkg/util"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
)

type mountingRunner struct {
base runner_pb.RunnerServer
buildDirectory filesystem.Directory
mount *bb_runner.InputMountOptions
}

// NewMountingRunner is a decorator for Runner
// that mounts `mount` before running a build action.
//
// This decorator can be used for chroot runners
// that must mount special filesystems into the input root.
func NewMountingRunner(base runner_pb.RunnerServer, buildDirectory filesystem.Directory, mount *bb_runner.InputMountOptions) runner_pb.RunnerServer {
return &mountingRunner{
buildDirectory: buildDirectory,
mount: mount,
base: base,
}
}

func open(buildDirectory filesystem.Directory, inputRootDirectory string) (filesystem.DirectoryCloser, error) {
// TODO(nils): How to best open the input root `Directory`
// It must be a `localDirectory`, but outer directories can be `lazyDirectory`.
// NB: We iterate over Directory, as the initial buildDirectory is not a DirectoryCloser.
iterator := buildDirectory
var dir filesystem.DirectoryCloser

parts := strings.Split(inputRootDirectory, "/")
if len(parts) == 0 {
return nil, status.Error(codes.FailedPrecondition, "Unexpected empty inputRootDirectory")
}
for i, segment := range parts {
var err error
component, ok := path.NewComponent(segment)
if !ok {
return nil, status.Errorf(codes.InvalidArgument, "Directory path segment %#v has an invalid name", segment)
}
dir, err = iterator.EnterDirectory(component)
if err != nil {
return nil, status.Errorf(codes.FailedPrecondition, "Could not enter directory component %#v in %#v", segment, buildDirectory)
}
if i < len(parts)-1 {
defer dir.Close()
}

iterator = dir.(filesystem.Directory)
}
return dir, nil
}

func (r *mountingRunner) Run(ctx context.Context, request *runner_pb.RunRequest) (response *runner_pb.RunResponse, err error) {
root, err := open(r.buildDirectory, request.InputRootDirectory)
if err != nil {
return nil, status.Errorf(codes.FailedPrecondition, "Could not enter input root directory %#v", request.InputRootDirectory)
}
defer root.Close()

mountpoint, ok := path.NewComponent(r.mount.Mountpoint)
if !ok {
return nil, status.Errorf(codes.InvalidArgument, "Mountpoint %#v has an invalid name", r.mount.Mountpoint)
}
if err := root.Mount(mountpoint, r.mount.Source, r.mount.FilesystemType); err != nil {
return nil, util.StatusWrapf(err, "Failed to mount %#v in the input root", r.mount)
}

response, err = r.base.Run(ctx, request)
if err != nil {
return nil, err
}
// TODO: is `root` open here or do we need to open it again?
if err2 := root.Unmount(mountpoint); err2 != nil {
err = util.StatusFromMultiple([]error{
err,
util.StatusWrap(err2, "Failed to unmount %#v in the input root"),
})
}

return response, nil
}

func (r *mountingRunner) CheckReadiness(ctx context.Context, request *runner_pb.CheckReadinessRequest) (*emptypb.Empty, error) {
return r.base.CheckReadiness(ctx, request)
}

0 comments on commit 9dbbedf

Please sign in to comment.