Skip to content

Commit

Permalink
Mount special filesystems 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. The filesystems are
required for many tools to work.

Solves: #115
  • Loading branch information
stagnation committed Feb 16, 2024
1 parent fdbe722 commit 07ffd9d
Show file tree
Hide file tree
Showing 6 changed files with 373 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.
string source = 2;
// Type of filesystem, see the mount(8) man page.
string filesystem_type = 3;
}
4 changes: 4 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 Expand Up @@ -70,13 +72,15 @@ go_test(
"apple_xcode_resolving_runner_test.go",
"clean_runner_test.go",
"local_runner_test.go",
"mounting_runner_test.go",
"path_existence_checking_runner_test.go",
"temporary_directory_symlinking_runner_test.go",
],
deps = [
":runner",
"//internal/mock",
"//pkg/cleaner",
"//pkg/proto/configuration/bb_runner",
"//pkg/proto/resourceusage",
"//pkg/proto/runner",
"@com_github_buildbarn_bb_storage//pkg/filesystem",
Expand Down
103 changes: 103 additions & 0 deletions pkg/runner/mounting_runner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package runner

import (
"context"

"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 (r *mountingRunner) Run(ctx context.Context, request *runner_pb.RunRequest) (response *runner_pb.RunResponse, err error) {
rootResolver := buildDirectoryPathResolver{
stack: util.NewNonEmptyStack(filesystem.NopDirectoryCloser(r.buildDirectory)),
}
defer rootResolver.closeAll()
if err := path.Resolve(request.InputRootDirectory, path.NewRelativeScopeWalker(&rootResolver)); err != nil {
return nil, util.StatusWrap(err, "Invalid input root.")
}

inputRoot := rootResolver.stack.Peek()
terminal := rootResolver.TerminalName
if terminal != nil {
inputRoot, err = inputRoot.EnterDirectory(*terminal)
if err != nil {
return nil, util.StatusWrap(err, "Invalid input root.")
}
}

defer inputRoot.Close()

mountResolver := buildDirectoryPathResolver{
stack: util.NewNonEmptyStack(filesystem.NopDirectoryCloser(inputRoot)),
}
defer mountResolver.closeAll()
if err := path.Resolve(r.mount.Mountpoint, path.NewRelativeScopeWalker(&mountResolver)); err != nil {
return nil, util.StatusWrap(err, "Invalid mountpoint directory path.")
}

mountDir := mountResolver.stack.Peek()
defer mountDir.Close()
if mountResolver.TerminalName == nil {
return nil, status.Errorf(codes.InvalidArgument, "Could not resolve mountpoint basename: %#v", r.mount.Mountpoint)
}

mountname := *mountResolver.TerminalName
if err := mountDir.Mount(mountname, r.mount.Source, r.mount.FilesystemType); err != nil {
return nil, util.StatusWrapf(err, "Failed to mount %#v in the input root", r.mount.Mountpoint)
}

// We only need the mounting directory file descriptor open.
rootResolver.closeAll()
// Already peeked the mounting directory
// we must now pop it to save it from the close iteration.
maybe, ok := mountResolver.stack.PopSingle()
if ok {
// PopSingle does not return anything if it is the initial element in the stack,
// which we got from Peek earlier.
defer maybe.Close()
mountDir = maybe
}
mountResolver.closeAll()

response, err = r.base.Run(ctx, request)
if err != nil {
return nil, err
}
if err2 := mountDir.Unmount(mountname); err2 != nil {
err = util.StatusFromMultiple([]error{
err,
util.StatusWrapf(err2, "Failed to unmount %#v in the input root", r.mount.Mountpoint),
})
}

return response, nil
}

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

0 comments on commit 07ffd9d

Please sign in to comment.