Merge pull request #59 from stapelberg/mount
try mounting without fusermount(1) firstgeesefs-0-30-9
commit
081e9f4bc7
|
@ -223,10 +223,9 @@ func escapeOptionsKey(s string) (res string) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create an options string suitable for passing to the mount helper.
|
func mapToOptionsString(opts map[string]string) string {
|
||||||
func (c *MountConfig) toOptionsString() string {
|
|
||||||
var components []string
|
var components []string
|
||||||
for k, v := range c.toMap() {
|
for k, v := range opts {
|
||||||
k = escapeOptionsKey(k)
|
k = escapeOptionsKey(k)
|
||||||
|
|
||||||
component := k
|
component := k
|
||||||
|
@ -239,3 +238,8 @@ func (c *MountConfig) toOptionsString() string {
|
||||||
|
|
||||||
return strings.Join(components, ",")
|
return strings.Join(components, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create an options string suitable for passing to the mount helper.
|
||||||
|
func (c *MountConfig) toOptionsString() string {
|
||||||
|
return mapToOptionsString(c.toMap())
|
||||||
|
}
|
||||||
|
|
135
mount_linux.go
135
mount_linux.go
|
@ -2,29 +2,21 @@ package fuse
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Begin the process of mounting at the given directory, returning a connection
|
func fusermount(dir string, cfg *MountConfig) (*os.File, error) {
|
||||||
// to the kernel. Mounting continues in the background, and is complete when an
|
|
||||||
// error is written to the supplied channel. The file system may need to
|
|
||||||
// service the connection in order for mounting to complete.
|
|
||||||
func mount(
|
|
||||||
dir string,
|
|
||||||
cfg *MountConfig,
|
|
||||||
ready chan<- error) (dev *os.File, err error) {
|
|
||||||
// On linux, mounting is never delayed.
|
|
||||||
ready <- nil
|
|
||||||
|
|
||||||
// Create a socket pair.
|
// Create a socket pair.
|
||||||
fds, err := syscall.Socketpair(syscall.AF_FILE, syscall.SOCK_STREAM, 0)
|
fds, err := syscall.Socketpair(syscall.AF_FILE, syscall.SOCK_STREAM, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("Socketpair: %v", err)
|
return nil, fmt.Errorf("Socketpair: %v", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrap the sockets into os.File objects that we will pass off to fusermount.
|
// Wrap the sockets into os.File objects that we will pass off to fusermount.
|
||||||
|
@ -51,23 +43,20 @@ func mount(
|
||||||
// Run the command.
|
// Run the command.
|
||||||
err = cmd.Run()
|
err = cmd.Run()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("running fusermount: %v\n\nstderr:\n%s", err, stderr.Bytes())
|
return nil, fmt.Errorf("running fusermount: %v\n\nstderr:\n%s", err, stderr.Bytes())
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrap the socket file in a connection.
|
// Wrap the socket file in a connection.
|
||||||
c, err := net.FileConn(readFile)
|
c, err := net.FileConn(readFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("FileConn: %v", err)
|
return nil, fmt.Errorf("FileConn: %v", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
|
||||||
// We expect to have a Unix domain socket.
|
// We expect to have a Unix domain socket.
|
||||||
uc, ok := c.(*net.UnixConn)
|
uc, ok := c.(*net.UnixConn)
|
||||||
if !ok {
|
if !ok {
|
||||||
err = fmt.Errorf("Expected UnixConn, got %T", c)
|
return nil, fmt.Errorf("Expected UnixConn, got %T", c)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read a message.
|
// Read a message.
|
||||||
|
@ -75,21 +64,18 @@ func mount(
|
||||||
oob := make([]byte, 32) // expect 24 bytes
|
oob := make([]byte, 32) // expect 24 bytes
|
||||||
_, oobn, _, _, err := uc.ReadMsgUnix(buf, oob)
|
_, oobn, _, _, err := uc.ReadMsgUnix(buf, oob)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("ReadMsgUnix: %v", err)
|
return nil, fmt.Errorf("ReadMsgUnix: %v", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the message.
|
// Parse the message.
|
||||||
scms, err := syscall.ParseSocketControlMessage(oob[:oobn])
|
scms, err := syscall.ParseSocketControlMessage(oob[:oobn])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("ParseSocketControlMessage: %v", err)
|
return nil, fmt.Errorf("ParseSocketControlMessage: %v", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We expect one message.
|
// We expect one message.
|
||||||
if len(scms) != 1 {
|
if len(scms) != 1 {
|
||||||
err = fmt.Errorf("expected 1 SocketControlMessage; got scms = %#v", scms)
|
return nil, fmt.Errorf("expected 1 SocketControlMessage; got scms = %#v", scms)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
scm := scms[0]
|
scm := scms[0]
|
||||||
|
@ -97,17 +83,102 @@ func mount(
|
||||||
// Pull out the FD returned by fusermount
|
// Pull out the FD returned by fusermount
|
||||||
gotFds, err := syscall.ParseUnixRights(&scm)
|
gotFds, err := syscall.ParseUnixRights(&scm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("syscall.ParseUnixRights: %v", err)
|
return nil, fmt.Errorf("syscall.ParseUnixRights: %v", err)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(gotFds) != 1 {
|
if len(gotFds) != 1 {
|
||||||
err = fmt.Errorf("wanted 1 fd; got %#v", gotFds)
|
return nil, fmt.Errorf("wanted 1 fd; got %#v", gotFds)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Turn the FD into an os.File.
|
// Turn the FD into an os.File.
|
||||||
dev = os.NewFile(uintptr(gotFds[0]), "/dev/fuse")
|
return os.NewFile(uintptr(gotFds[0]), "/dev/fuse"), nil
|
||||||
|
}
|
||||||
return
|
|
||||||
|
func enableFunc(flag uintptr) func(uintptr) uintptr {
|
||||||
|
return func(v uintptr) uintptr {
|
||||||
|
return v | flag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func disableFunc(flag uintptr) func(uintptr) uintptr {
|
||||||
|
return func(v uintptr) uintptr {
|
||||||
|
return v &^ flag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// As per libfuse/fusermount.c:602: https://bit.ly/2SgtWYM#L602
|
||||||
|
var mountflagopts = map[string]func(uintptr) uintptr{
|
||||||
|
"rw": enableFunc(unix.MS_RDONLY),
|
||||||
|
"ro": disableFunc(unix.MS_RDONLY),
|
||||||
|
"suid": disableFunc(unix.MS_NOSUID),
|
||||||
|
"nosuid": enableFunc(unix.MS_NOSUID),
|
||||||
|
"dev": disableFunc(unix.MS_NODEV),
|
||||||
|
"nodev": enableFunc(unix.MS_NODEV),
|
||||||
|
"exec": disableFunc(unix.MS_NOEXEC),
|
||||||
|
"noexec": enableFunc(unix.MS_NOEXEC),
|
||||||
|
"async": disableFunc(unix.MS_SYNCHRONOUS),
|
||||||
|
"sync": enableFunc(unix.MS_SYNCHRONOUS),
|
||||||
|
"atime": disableFunc(unix.MS_NOATIME),
|
||||||
|
"noatime": enableFunc(unix.MS_NOATIME),
|
||||||
|
"dirsync": enableFunc(unix.MS_DIRSYNC),
|
||||||
|
}
|
||||||
|
|
||||||
|
var errFallback = errors.New("sentinel: fallback to fusermount(1)")
|
||||||
|
|
||||||
|
func directmount(dir string, cfg *MountConfig) (*os.File, error) {
|
||||||
|
// We use syscall.Open + os.NewFile instead of os.OpenFile so that the file
|
||||||
|
// is opened in blocking mode. When opened in non-blocking mode, the Go
|
||||||
|
// runtime tries to use poll(2), which does not work with /dev/fuse.
|
||||||
|
fd, err := syscall.Open("/dev/fuse", syscall.O_RDWR, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errFallback
|
||||||
|
}
|
||||||
|
dev := os.NewFile(uintptr(fd), "/dev/fuse")
|
||||||
|
// As per libfuse/fusermount.c:847: https://bit.ly/2SgtWYM#L847
|
||||||
|
data := fmt.Sprintf("fd=%d,rootmode=40000,user_id=%d,group_id=%d",
|
||||||
|
dev.Fd(), os.Getuid(), os.Getgid())
|
||||||
|
// As per libfuse/fusermount.c:749: https://bit.ly/2SgtWYM#L749
|
||||||
|
mountflag := uintptr(unix.MS_NODEV | unix.MS_NOSUID)
|
||||||
|
opts := cfg.toMap()
|
||||||
|
for k := range opts {
|
||||||
|
fn, ok := mountflagopts[k]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mountflag = fn(mountflag)
|
||||||
|
delete(opts, k)
|
||||||
|
}
|
||||||
|
delete(opts, "fsname") // handled via fstype mount(2) parameter
|
||||||
|
data += "," + mapToOptionsString(opts)
|
||||||
|
if err := unix.Mount(
|
||||||
|
cfg.FSName, // source
|
||||||
|
dir, // target
|
||||||
|
"fuse", // fstype
|
||||||
|
mountflag, // mountflag
|
||||||
|
data, // data
|
||||||
|
); err != nil {
|
||||||
|
if err == syscall.EPERM {
|
||||||
|
return nil, errFallback
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dev, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Begin the process of mounting at the given directory, returning a connection
|
||||||
|
// to the kernel. Mounting continues in the background, and is complete when an
|
||||||
|
// error is written to the supplied channel. The file system may need to
|
||||||
|
// service the connection in order for mounting to complete.
|
||||||
|
func mount(dir string, cfg *MountConfig, ready chan<- error) (*os.File, error) {
|
||||||
|
// On linux, mounting is never delayed.
|
||||||
|
ready <- nil
|
||||||
|
|
||||||
|
// Try mounting without fusermount(1) first: we might be running as root or
|
||||||
|
// have the CAP_SYS_ADMIN capability.
|
||||||
|
dev, err := directmount(dir, cfg)
|
||||||
|
if err == errFallback {
|
||||||
|
return fusermount(dir, cfg)
|
||||||
|
}
|
||||||
|
return dev, err
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue