kvm/vendor/github.com/hanwen/go-fuse/v2/fuse/mount_freebsd.go

143 lines
3.5 KiB
Go

package fuse
import (
"fmt"
"os"
"strings"
"syscall"
)
func callMountFuseFs(mountPoint string, opts *MountOptions) (devFuseFd int, err error) {
bin, err := fusermountBinary()
if err != nil {
return -1, err
}
// Convenience for cleaning up open FDs in the event of an error.
closeFd := func(fd int) {
if err != nil {
syscall.Close(fd)
}
}
// Use syscall.Open instead of os.OpenFile to avoid Go garbage collecting an
// [os.File], which will close the FD later, but we need to keep it open for
// the life of the FUSE mount.
const devFuse = "/dev/fuse"
devFuseFd, err = syscall.Open(devFuse, syscall.O_RDWR, 0)
if err != nil {
return -1, fmt.Errorf(
"failed to get a RDWR file descriptor for %s: %w",
devFuse, err)
}
defer closeFd(devFuseFd)
// We will also defensively direct the forked process's stdin to the null
// device to prevent any unintended side effects.
devNullFd, err := syscall.Open(os.DevNull, syscall.O_RDONLY, 0)
if err != nil {
return -1, fmt.Errorf(
"failed to get a RDONLY file descriptor for %s: %w",
os.DevNull, err)
}
defer closeFd(devNullFd)
// Use syscall.ForkExec directly instead of exec.Command because we need to
// pass raw file descriptors to the child, rather than a garbage collected
// os.File.
env := []string{"MOUNT_FUSEFS_CALL_BY_LIB=1"}
argv := []string{
bin,
"--safe",
"-o", strings.Join(opts.optionsStrings(), ","),
"3",
mountPoint,
}
fds := []uintptr{
uintptr(devNullFd),
uintptr(os.Stdout.Fd()),
uintptr(os.Stderr.Fd()),
uintptr(devFuseFd),
}
pid, err := syscall.ForkExec(bin, argv, &syscall.ProcAttr{
Env: env,
Files: fds,
})
if err != nil {
return -1, fmt.Errorf("failed to fork mount_fusefs: %w", err)
}
var ws syscall.WaitStatus
_, err = syscall.Wait4(pid, &ws, 0, nil)
if err != nil {
return -1, fmt.Errorf(
"failed to wait for exit status of mount_fusefs: %w", err)
}
if ws.ExitStatus() != 0 {
return -1, fmt.Errorf(
"mount_fusefs: exited with status %d", ws.ExitStatus())
}
return devFuseFd, nil
}
func mount(mountPoint string, opts *MountOptions, ready chan<- error) (fd int, err error) {
// Note: opts.DirectMount is not supported in FreeBSD, but the intended
// behavior is to *attempt* a direct mount when it's set, not to return an
// error. So in this case, we just ignore it and use the binary from
// fusermountBinary().
// Using the same logic from libfuse to prevent chaos
for {
f, err := os.OpenFile("/dev/null", os.O_RDWR, 0o000)
if err != nil {
return -1, err
}
if f.Fd() > 2 {
f.Close()
break
}
}
// Magic `/dev/fd/N` mountpoint. See the docs for NewServer() for how this
// works.
fd = parseFuseFd(mountPoint)
if fd >= 0 {
if opts.Debug {
opts.Logger.Printf("mount: magic mountpoint %q, using fd %d", mountPoint, fd)
}
} else {
// Usual case: mount via the `fusermount` suid helper
fd, err = callMountFuseFs(mountPoint, opts)
if err != nil {
return
}
}
// golang sets CLOEXEC on file descriptors when they are
// acquired through normal operations (e.g. open).
// However, for raw FDs, we have to set CLOEXEC manually.
syscall.CloseOnExec(fd)
close(ready)
return fd, err
}
func unmount(mountPoint string, opts *MountOptions) (err error) {
_ = opts
return syscall.Unmount(mountPoint, 0)
}
func fusermountBinary() (string, error) {
binPaths := []string{
"/sbin/mount_fusefs",
}
for _, path := range binPaths {
if _, err := os.Stat(path); err == nil {
return path, nil
}
}
return "", fmt.Errorf("no FUSE mount utility found")
}