Skip to content

Commit

Permalink
Add mount functionality to 'Directory'
Browse files Browse the repository at this point in the history
This is needed for 'chroot' runners that must mount the special
filesystems '/proc' and '/sys' in the input root.
  • Loading branch information
stagnation committed Sep 6, 2023
1 parent 1eb2bad commit 03950d3
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 0 deletions.
1 change: 1 addition & 0 deletions pkg/filesystem/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ go_library(
"local_directory_linux.go",
"local_directory_unix.go",
"local_directory_windows.go",
"mount_unix.go",
],
importpath = "github.com/buildbarn/bb-storage/pkg/filesystem",
visibility = ["//visibility:public"],
Expand Down
9 changes: 9 additions & 0 deletions pkg/filesystem/directory.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package filesystem
import (
"io"
"os"
"sync"
"time"

"github.com/buildbarn/bb-storage/pkg/filesystem/path"
Expand Down Expand Up @@ -112,6 +113,14 @@ type Directory interface {
// Function that base types may use to implement calls that
// require double dispatching, such as hardlinking and renaming.
Apply(arg interface{}) error

// Mount and Unmount.
// This uses `Mountat` functionality, but not `Unmountat`.
// see https://github.com/buildbarn/bb-remote-execution/issues/115 for information.
Mount(mountpoint path.Component, source string, fstype string, options int) error
// The Unmount call changes the working directory internally
// and must use a program-scope mutex to isolate this side effect.
Unmount(lock *sync.Mutex, mountpoint path.Component) error
}

// DirectoryCloser is a Directory handle that can be released.
Expand Down
53 changes: 53 additions & 0 deletions pkg/filesystem/local_directory_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
package filesystem

import (
"fmt"
"io"
"io/fs"
"os"
"runtime"
"sort"
Expand All @@ -17,6 +19,7 @@ import (

"golang.org/x/sys/unix"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)

type localDirectory struct {
Expand Down Expand Up @@ -460,3 +463,53 @@ func (d *localDirectory) Apply(arg interface{}) error {
return syscall.EXDEV
}
}

func (d *localDirectory) Mount(mountpoint path.Component, source string, fstype string, options int) error {
if options != 0 {
return status.Error(codes.InvalidArgument, "Options are not yet supported in `mountat`.")
}
mfd, err := mountat(d.fd, fstype, source, mountpoint.String())
if err != nil {
return util.StatusWrap(err, "Mountat")
}
defer unix.Close(mfd)

return nil
}

// Fchdir changes the current working directory to the directory file descriptor.
// If there is an error, it will be of type *PathError.
func Fchdir(dfd int) error {
if e := syscall.Fchdir(dfd); e != nil {
return &fs.PathError{Op: "fchdir", Path: fmt.Sprintf("%d", dfd), Err: e}
}
return nil
}

// Uses `unmount` on relative pathnames with `fchdir`.
func fchdir_unmountat(lock *sync.Mutex, dfd int, mountname string) error {
lock.Lock()
defer lock.Unlock()

store, err := os.Getwd()
if err != nil {
return util.StatusWrap(err, "Could not store the working directory %#v.")
}
defer os.Chdir(store)

err = Fchdir(dfd)
if err != nil {
return util.StatusWrapf(err, "Could not change working directory to %#v.", dfd)
}

err = unix.Unmount(mountname, 0)
if err != nil {
return util.StatusWrap(err, "Unmount")
}

return nil
}

func (d *localDirectory) Unmount(lock *sync.Mutex, mountpoint path.Component) error {
return fchdir_unmountat(lock, d.fd, mountpoint.String())
}
130 changes: 130 additions & 0 deletions pkg/filesystem/mount_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
//go:build darwin || freebsd || linux
// +build darwin freebsd linux

package filesystem

import (
"errors"
"fmt"
"unsafe"

"github.com/buildbarn/bb-storage/pkg/util"

"golang.org/x/sys/unix"
)

const (
FSCONFIG_SET_FLAG = 0
FSCONFIG_SET_STRING = 1
FSCONFIG_SET_BINARY = 2
FSCONFIG_SET_PATH = 3
FSCONFIG_SET_PATH_EMPTY = 4
FSCONFIG_SET_FD = 5
FSCONFIG_CMD_CREATE = 6
FSCONFIG_CMD_RECONFIGURE = 7
)

// Rudimentary function wrapper for the `fsconfig` syscall.
//
// This is implemented as a stop gap solution until real support is merged into the `unix` library.
// See this patchset: https://go-review.googlesource.com/c/sys/+/399995/ .
// This only implements the two commands needed for basic `mountat` functionality.
// And will just exit if any other command is called.
//
// TODO(nils): construct proper `syscall.E*` errors.
// like `unix.errnoErr`, but the function is not exported.
func fsconfig(fsfd int, cmd int, key string, value string, flags int) (err error) {
switch cmd {
case FSCONFIG_SET_STRING:
if len(key) == 0 || len(value) == 0 {
err = errors.New("`key` and `value` must be provided")
return
}
case FSCONFIG_CMD_CREATE:
if len(key) != 0 || len(value) != 0 {
err = errors.New("`key` and `value` must be empty")
return
}
default:
err = errors.New("not implemented: " + fmt.Sprintf("%d", cmd))
return
}

var _p0 *byte
var _p1 *byte

_p0, err = unix.BytePtrFromString(key)
if err != nil {
return
}
if key == "" {
_p0 = nil
}

_p1, err = unix.BytePtrFromString(value)
if err != nil {
return
}
if value == "" {
_p1 = nil
}

r0, _, e1 := unix.Syscall6(
unix.SYS_FSCONFIG,
uintptr(fsfd),
uintptr(cmd),
uintptr(unsafe.Pointer(_p0)),
uintptr(unsafe.Pointer(_p1)),
uintptr(flags),
0,
)
ret := int(r0)
if e1 != 0 {
err = e1
return
}
if ret < 0 {
err = errors.New("negative return code, not converted to an error in `Syscall`: " + fmt.Sprintf("%d", ret))
return
}

return
}

// Mounts the `source` filesystem on `mountname` inside a directory
// given as `dfd` file descriptor,
// using the `fstype` filesystem type.
// This returns a file descriptor to the mount object,
// that can be used to move it again.
// Remember to close it before unmounting,
// or unmount will fail with EBUSY.
//
// TODO: Options cannot be sent to the syscalls.
func mountat(dfd int, fstype, source, mountname string) (int, error) {

fd, err := unix.Fsopen(fstype, unix.FSOPEN_CLOEXEC)
if err != nil {
return -1, util.StatusWrapf(err, "Fsopen '%s'", fstype)
}

err = fsconfig(fd, FSCONFIG_SET_STRING, "source", source, 0)
if err != nil {
return -1, util.StatusWrapf(err, "Fsconfig source '%s'", source)
}

err = fsconfig(fd, FSCONFIG_CMD_CREATE, "", "", 0)
if err != nil {
return -1, util.StatusWrap(err, "Fsconfig create")
}

mfd, err := unix.Fsmount(fd, unix.FSMOUNT_CLOEXEC, unix.MS_NOEXEC)
if err != nil {
return -1, util.StatusWrap(err, "Fsmount")
}
err = unix.MoveMount(mfd, "", dfd, mountname, unix.MOVE_MOUNT_F_EMPTY_PATH)
if err != nil {
return -1, util.StatusWrapf(err, "Movemount mountname '%s' in file descriptor %d", mountname, dfd)
}

return mfd, nil
}

0 comments on commit 03950d3

Please sign in to comment.