mirror of https://github.com/jetkvm/kvm.git
143 lines
3.5 KiB
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")
|
|
}
|