Skip to content

Commit

Permalink
Add support for passing FUSE file descriptors as mount point (#1103)
Browse files Browse the repository at this point in the history
## Description of change

fuser v0.15.0 added support for creating a `Session` from existing FUSE
file descriptor (via `Session::from_fd`). This PR adds this support to
Mountpoint. It allows passing FUSE file descriptor as mount point in the
form of `/dev/fd/{fd}`.

An example usage of this feature can be seen with a helper Go script,
[mounthelper.go](https://github.com/awslabs/mountpoint-s3/blob/86bdefa5147a7edc533a6be5d2724fec74ba91fb/examples/fuse-fd-mount-point/mounthelper.go):

```bash
$ go build mounthelper.go
$ sudo /sbin/setcap 'cap_sys_admin=ep' ./mounthelper # `mount` syscall requires `CAP_SYS_ADMIN`, alternatively, `mounthelper` can be run as root
$ ./mounthelper -mountpoint /tmp/mountpoint -bucket bucketname
bucket bucketname is mounted at /dev/fd/3
2024/11/07 17:23:42 Filesystem mounted, waiting for ctrl+c signal to terminate 

$ # in a different terminal session
$ echo "Hello at `date`" > /tmp/mountpoint/helloworld
$ cat /tmp/mountpoint/helloworld
Hello at Thu Nov  7 17:32:33 UTC 2024
$ rm /tmp/mountpoint/helloworld
$ cat /tmp/mountpoint/helloworld
cat: /tmp/mountpoint/helloworld: No such file or directory
```

Relevant issues: This PR resurrects a previous PR to add this feature:
#537

## Does this change impact existing behavior?

Shouldn't affect any existing behavior as we had an “is directory?”
check for passed mount points before, and it shouldn't have been
possible to pass a file descriptor as a mount point prior to this
change.

## Does this change need a changelog entry in any of the crates?

Updated CHANGELOG for `mountpoint-s3`.

---

By submitting this pull request, I confirm that my contribution is made
under the terms of the Apache 2.0 license and I agree to the terms of
the [Developer Certificate of Origin
(DCO)](https://developercertificate.org/).

---------

Signed-off-by: Burak Varli <[email protected]>
Signed-off-by: Burak Varlı <[email protected]>
Signed-off-by: Burak Varlı <[email protected]>
Signed-off-by: Daniel Carl Jones <[email protected]>
Signed-off-by: Daniel Carl Jones <[email protected]>
Co-authored-by: Daniel Carl Jones <[email protected]>
Co-authored-by: Daniel Carl Jones <[email protected]>
  • Loading branch information
3 people authored Dec 17, 2024
1 parent 631e6e0 commit 602f371
Show file tree
Hide file tree
Showing 8 changed files with 723 additions and 114 deletions.
19 changes: 19 additions & 0 deletions doc/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,25 @@ ExecStop=/usr/bin/fusermount -u /home/ec2-user/s3-bucket-mount
WantedBy=remote-fs.target
```

### Providing a FUSE file descriptor for mounting

Mountpoint supports mounting S3 buckets at a given path, or using a provided FUSE file descriptor (only on Linux).

For directory mount points, the passed path must be an existing directory.

For FUSE file descriptors on Linux, you can specify an open FUSE file descriptor as a mount point with `/dev/fd/N` syntax.
This is useful in container environments to achieve unprivileged mounts.
In this case, the caller is responsible for the following:
1. Opening the FUSE device (`/dev/fuse`) in read-write mode to obtain a file descriptor.
2. Performing the `mount` syscall with the desired mount point, the file descriptor, and any mount options.
Mountpoint by default uses and recommends enabling `nodev`, `nosuid`, `default_permissions`, and `noatime` mount options.
See the [Linux kernel documentation](https://man7.org/linux/man-pages/man8/mount.fuse3.8.html#OPTIONS) for more details on mount options.
3. Spawning Mountpoint with the file descriptor using `/dev/fd/N` syntax as the mount point argument.
4. Closing the file descriptor in the parent process.
5. Performing the `unmount` syscall on the mount point when unmounting is desired or when the Mountpoint process terminates.

See [mounthelper.go](https://github.com/awslabs/mountpoint-s3/tree/main/examples/fuse-fd-mount-point/mounthelper.go) as an example usage of this feature.

## Caching configuration

Mountpoint can optionally cache object metadata and content to reduce cost and improve performance for repeated reads to the same file.
Expand Down
116 changes: 116 additions & 0 deletions examples/fuse-fd-mount-point/mounthelper.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// An example script showing usage of FUSE file descriptor as a mount point.
//
// Example usage:
// $ go build mounthelper.go
// $ sudo /sbin/setcap 'cap_sys_admin=ep' ./mounthelper # `mount` syscall requires `CAP_SYS_ADMIN`, alternatively, `mounthelper` can be run as root
// $ ./mounthelper -mountpoint /tmp/mountpoint -bucket bucketname
// $ # Mountpoint mounted at /tmp/mountpoint until `mounthelper` is terminated with ctrl+c.
package main

import (
"flag"
"fmt"
"log"
"os"
"os/exec"
"os/signal"
"strings"
"syscall"
)

var mountPoint = flag.String("mountpoint", "", "Path to mount the filesystem at")
var bucket = flag.String("bucket", "", "S3 Bucket to mount")

func main() {
flag.Parse()

// `os.MkdirAll` will return `nil` if `mountPoint` is an already existing directory.
if err := os.MkdirAll(*mountPoint, 0644); err != nil {
log.Panicf("Failed to create target mount point %s: %v\n", *mountPoint, err)
}

// 1. Open FUSE device
const USE_DEFAULT_PERM = 0
fd, err := syscall.Open("/dev/fuse", os.O_RDWR, USE_DEFAULT_PERM)
if err != nil {
log.Panicf("Failed to open /dev/fuse: %v\n", err)
}

// Ensure to close the file descriptor in case of an error, if there are no errors,
// the file descriptor will be closed and `closeFd` will set to `false`.
closeFd := true
defer func() {
if closeFd {
err := syscall.Close(fd)
if err != nil {
log.Panicf("Failed to close fd on parent: %v\n", err)
}
}
}()

var stat syscall.Stat_t
err = syscall.Stat(*mountPoint, &stat)
if err != nil {
log.Panicf("Failed to stat mount point %s: %v\n", *mountPoint, err)
}

// 2. Perform `mount` syscall
// These mount options and flags match those typically set when using Mountpoint.
// Some are set by the underlying FUSE library.
// Mountpoint sets (correct at the time of authoring this comment):
// * `noatime` to avoid unsupported access time updates.
// * `default_permissions` to tell the Kernel to evaluate permissions itself, since Mountpoint does not currently provide any handler for FUSE `access`.
options := []string{
fmt.Sprintf("fd=%d", fd),
fmt.Sprintf("rootmode=%o", stat.Mode),
fmt.Sprintf("user_id=%d", os.Geteuid()),
fmt.Sprintf("group_id=%d", os.Getegid()),
"default_permissions",
}
var flags uintptr = syscall.MS_NOSUID | syscall.MS_NODEV | syscall.MS_NOATIME
err = syscall.Mount("mountpoint-s3", *mountPoint, "fuse", flags, strings.Join(options, ","))
if err != nil {
log.Panicf("Failed to call mount syscall: %v\n", err)
}

// 3. Define and defer call to `unmount` syscall, to be invoked once script terminates
defer func() {
err := syscall.Unmount(*mountPoint, 0)
if err != nil {
log.Printf("Failed to unmount %s: %v\n", *mountPoint, err)
} else {
log.Printf("Succesfully unmounted %s\n", *mountPoint)
}
}()

// 4. Spawn Mountpoint with the fd
mountpointCmd := exec.Command("./target/release/mount-s3",
*bucket,
fmt.Sprintf("/dev/fd/%d", fd),
// Other mount options can be added here
"--prefix=some_s3_prefix/",
"--allow-delete",
)
mountpointCmd.Stdout = os.Stdout
mountpointCmd.Stderr = os.Stderr
err = mountpointCmd.Run()
if err != nil {
log.Panicf("Failed to start Mountpoint: %v\n", err)
}

// 4. Close fd on parent
err = syscall.Close(fd)
if err != nil {
log.Panicf("Failed to close fd on parent: %v\n", err)
}
// As we expliclity closed it, no need for `defer`red close to happen.
closeFd = false

done := make(chan os.Signal, 1)
signal.Notify(done, syscall.SIGINT, syscall.SIGTERM)

log.Print("Filesystem mounted, waiting for ctrl+c signal to terminate")
<-done

// 5. Unmounting will happen here due to `defer` in step 3.
}
7 changes: 7 additions & 0 deletions mountpoint-s3/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
## Unreleased

### New features

* Mountpoint now supports specifying an open FUSE file descriptor in place of the mount path by using the syntax `/dev/fd/N`.
See [mounthelper.go](https://github.com/awslabs/mountpoint-s3/tree/main/examples/fuse-fd-mount-point/mounthelper.go) as an example usage and see
[Configuring mount point](https://github.com/awslabs/mountpoint-s3/blob/main/doc/CONFIGURATION.md#configuring-mount-point) about more details on configuring this feature.
([#1103](https://github.com/awslabs/mountpoint-s3/pull/1103))

### Other changes

* Fix an issue where an interrupt during `readdir` syscall leads to an error. ([#965](https://github.com/awslabs/mountpoint-s3/pull/965))
Expand Down
Loading

1 comment on commit 602f371

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'Throughput Benchmark (S3 Standard)'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 2.

Benchmark suite Current: 602f371 Previous: 631e6e0 Ratio
sequential_read 326.31943359375 MiB/s 1178.14248046875 MiB/s 3.61

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.