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 b29adf9
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 0 deletions.
2 changes: 2 additions & 0 deletions pkg/filesystem/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ go_library(
"local_directory_linux.go",
"local_directory_unix.go",
"local_directory_windows.go",
"mount_linux.go",
"mount_unsupported.go",
],
importpath = "github.com/buildbarn/bb-storage/pkg/filesystem",
visibility = ["//visibility:public"],
Expand Down
6 changes: 6 additions & 0 deletions pkg/filesystem/directory.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,12 @@ 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) error
Unmount(mountpoint path.Component) error
}

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

import (
"fmt"
"io/fs"
"os"
"runtime"
"sync"
"syscall"

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

"golang.org/x/sys/unix"
"google.golang.org/grpc/codes"
Expand All @@ -17,6 +22,10 @@ import (
// rawDeviceNumber is the equivalent of POSIX dev_t.
type rawDeviceNumber = uint64

var (
fchdirLock = sync.Mutex{}
)

func (d *localDirectory) Mknod(name path.Component, perm os.FileMode, deviceNumber DeviceNumber) error {
defer runtime.KeepAlive(d)

Expand All @@ -34,3 +43,32 @@ func (d *localDirectory) Mknod(name path.Component, perm os.FileMode, deviceNumb
func clonefileImpl(oldFD int, oldName string, newFD int, newName string) error {
return status.Error(codes.Unimplemented, "Clonefile is only supported on Darwin")
}

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

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

err = syscall.Fchdir(dfd)
if err != nil {
err = &fs.PathError{Op: "fchdir", Path: fmt.Sprintf("%d", dfd), Err: err}
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(mountpoint path.Component) error {
return fchdir_unmountat(d.fd, mountpoint.String())
}
141 changes: 141 additions & 0 deletions pkg/filesystem/mount_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
//go:build linux
// +build linux

package filesystem

import (
"errors"
"fmt"
"unsafe"

"github.com/buildbarn/bb-storage/pkg/filesystem/path"
"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
}

func (d *localDirectory) Mount(mountpoint path.Component, source string, fstype string) error {
mfd, err := mountat(d.fd, fstype, source, mountpoint.String())
if err != nil {
return util.StatusWrap(err, "Mountat")
}
defer unix.Close(mfd)

return nil
}

0 comments on commit b29adf9

Please sign in to comment.