Skip to content

Commit 081e9f4

Browse files
authored
Merge pull request #59 from stapelberg/mount
try mounting without fusermount(1) first
2 parents 659cc51 + 53aac50 commit 081e9f4

2 files changed

Lines changed: 109 additions & 34 deletions

File tree

mount_config.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -223,10 +223,9 @@ func escapeOptionsKey(s string) (res string) {
223223
return
224224
}
225225

226-
// Create an options string suitable for passing to the mount helper.
227-
func (c *MountConfig) toOptionsString() string {
226+
func mapToOptionsString(opts map[string]string) string {
228227
var components []string
229-
for k, v := range c.toMap() {
228+
for k, v := range opts {
230229
k = escapeOptionsKey(k)
231230

232231
component := k
@@ -239,3 +238,8 @@ func (c *MountConfig) toOptionsString() string {
239238

240239
return strings.Join(components, ",")
241240
}
241+
242+
// Create an options string suitable for passing to the mount helper.
243+
func (c *MountConfig) toOptionsString() string {
244+
return mapToOptionsString(c.toMap())
245+
}

mount_linux.go

Lines changed: 102 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,21 @@ package fuse
22

33
import (
44
"bytes"
5+
"errors"
56
"fmt"
67
"net"
78
"os"
89
"os/exec"
910
"syscall"
10-
)
1111

12-
// Begin the process of mounting at the given directory, returning a connection
13-
// to the kernel. Mounting continues in the background, and is complete when an
14-
// error is written to the supplied channel. The file system may need to
15-
// service the connection in order for mounting to complete.
16-
func mount(
17-
dir string,
18-
cfg *MountConfig,
19-
ready chan<- error) (dev *os.File, err error) {
20-
// On linux, mounting is never delayed.
21-
ready <- nil
12+
"golang.org/x/sys/unix"
13+
)
2214

15+
func fusermount(dir string, cfg *MountConfig) (*os.File, error) {
2316
// Create a socket pair.
2417
fds, err := syscall.Socketpair(syscall.AF_FILE, syscall.SOCK_STREAM, 0)
2518
if err != nil {
26-
err = fmt.Errorf("Socketpair: %v", err)
27-
return
19+
return nil, fmt.Errorf("Socketpair: %v", err)
2820
}
2921

3022
// Wrap the sockets into os.File objects that we will pass off to fusermount.
@@ -51,63 +43,142 @@ func mount(
5143
// Run the command.
5244
err = cmd.Run()
5345
if err != nil {
54-
err = fmt.Errorf("running fusermount: %v\n\nstderr:\n%s", err, stderr.Bytes())
55-
return
46+
return nil, fmt.Errorf("running fusermount: %v\n\nstderr:\n%s", err, stderr.Bytes())
5647
}
5748

5849
// Wrap the socket file in a connection.
5950
c, err := net.FileConn(readFile)
6051
if err != nil {
61-
err = fmt.Errorf("FileConn: %v", err)
62-
return
52+
return nil, fmt.Errorf("FileConn: %v", err)
6353
}
6454
defer c.Close()
6555

6656
// We expect to have a Unix domain socket.
6757
uc, ok := c.(*net.UnixConn)
6858
if !ok {
69-
err = fmt.Errorf("Expected UnixConn, got %T", c)
70-
return
59+
return nil, fmt.Errorf("Expected UnixConn, got %T", c)
7160
}
7261

7362
// Read a message.
7463
buf := make([]byte, 32) // expect 1 byte
7564
oob := make([]byte, 32) // expect 24 bytes
7665
_, oobn, _, _, err := uc.ReadMsgUnix(buf, oob)
7766
if err != nil {
78-
err = fmt.Errorf("ReadMsgUnix: %v", err)
79-
return
67+
return nil, fmt.Errorf("ReadMsgUnix: %v", err)
8068
}
8169

8270
// Parse the message.
8371
scms, err := syscall.ParseSocketControlMessage(oob[:oobn])
8472
if err != nil {
85-
err = fmt.Errorf("ParseSocketControlMessage: %v", err)
86-
return
73+
return nil, fmt.Errorf("ParseSocketControlMessage: %v", err)
8774
}
8875

8976
// We expect one message.
9077
if len(scms) != 1 {
91-
err = fmt.Errorf("expected 1 SocketControlMessage; got scms = %#v", scms)
92-
return
78+
return nil, fmt.Errorf("expected 1 SocketControlMessage; got scms = %#v", scms)
9379
}
9480

9581
scm := scms[0]
9682

9783
// Pull out the FD returned by fusermount
9884
gotFds, err := syscall.ParseUnixRights(&scm)
9985
if err != nil {
100-
err = fmt.Errorf("syscall.ParseUnixRights: %v", err)
101-
return
86+
return nil, fmt.Errorf("syscall.ParseUnixRights: %v", err)
10287
}
10388

10489
if len(gotFds) != 1 {
105-
err = fmt.Errorf("wanted 1 fd; got %#v", gotFds)
106-
return
90+
return nil, fmt.Errorf("wanted 1 fd; got %#v", gotFds)
10791
}
10892

10993
// Turn the FD into an os.File.
110-
dev = os.NewFile(uintptr(gotFds[0]), "/dev/fuse")
94+
return os.NewFile(uintptr(gotFds[0]), "/dev/fuse"), nil
95+
}
96+
97+
func enableFunc(flag uintptr) func(uintptr) uintptr {
98+
return func(v uintptr) uintptr {
99+
return v | flag
100+
}
101+
}
102+
103+
func disableFunc(flag uintptr) func(uintptr) uintptr {
104+
return func(v uintptr) uintptr {
105+
return v &^ flag
106+
}
107+
}
108+
109+
// As per libfuse/fusermount.c:602: https://bit.ly/2SgtWYM#L602
110+
var mountflagopts = map[string]func(uintptr) uintptr{
111+
"rw": enableFunc(unix.MS_RDONLY),
112+
"ro": disableFunc(unix.MS_RDONLY),
113+
"suid": disableFunc(unix.MS_NOSUID),
114+
"nosuid": enableFunc(unix.MS_NOSUID),
115+
"dev": disableFunc(unix.MS_NODEV),
116+
"nodev": enableFunc(unix.MS_NODEV),
117+
"exec": disableFunc(unix.MS_NOEXEC),
118+
"noexec": enableFunc(unix.MS_NOEXEC),
119+
"async": disableFunc(unix.MS_SYNCHRONOUS),
120+
"sync": enableFunc(unix.MS_SYNCHRONOUS),
121+
"atime": disableFunc(unix.MS_NOATIME),
122+
"noatime": enableFunc(unix.MS_NOATIME),
123+
"dirsync": enableFunc(unix.MS_DIRSYNC),
124+
}
125+
126+
var errFallback = errors.New("sentinel: fallback to fusermount(1)")
127+
128+
func directmount(dir string, cfg *MountConfig) (*os.File, error) {
129+
// We use syscall.Open + os.NewFile instead of os.OpenFile so that the file
130+
// is opened in blocking mode. When opened in non-blocking mode, the Go
131+
// runtime tries to use poll(2), which does not work with /dev/fuse.
132+
fd, err := syscall.Open("/dev/fuse", syscall.O_RDWR, 0644)
133+
if err != nil {
134+
return nil, errFallback
135+
}
136+
dev := os.NewFile(uintptr(fd), "/dev/fuse")
137+
// As per libfuse/fusermount.c:847: https://bit.ly/2SgtWYM#L847
138+
data := fmt.Sprintf("fd=%d,rootmode=40000,user_id=%d,group_id=%d",
139+
dev.Fd(), os.Getuid(), os.Getgid())
140+
// As per libfuse/fusermount.c:749: https://bit.ly/2SgtWYM#L749
141+
mountflag := uintptr(unix.MS_NODEV | unix.MS_NOSUID)
142+
opts := cfg.toMap()
143+
for k := range opts {
144+
fn, ok := mountflagopts[k]
145+
if !ok {
146+
continue
147+
}
148+
mountflag = fn(mountflag)
149+
delete(opts, k)
150+
}
151+
delete(opts, "fsname") // handled via fstype mount(2) parameter
152+
data += "," + mapToOptionsString(opts)
153+
if err := unix.Mount(
154+
cfg.FSName, // source
155+
dir, // target
156+
"fuse", // fstype
157+
mountflag, // mountflag
158+
data, // data
159+
); err != nil {
160+
if err == syscall.EPERM {
161+
return nil, errFallback
162+
163+
}
164+
return nil, err
165+
}
166+
return dev, nil
167+
}
111168

112-
return
169+
// Begin the process of mounting at the given directory, returning a connection
170+
// to the kernel. Mounting continues in the background, and is complete when an
171+
// error is written to the supplied channel. The file system may need to
172+
// service the connection in order for mounting to complete.
173+
func mount(dir string, cfg *MountConfig, ready chan<- error) (*os.File, error) {
174+
// On linux, mounting is never delayed.
175+
ready <- nil
176+
177+
// Try mounting without fusermount(1) first: we might be running as root or
178+
// have the CAP_SYS_ADMIN capability.
179+
dev, err := directmount(dir, cfg)
180+
if err == errFallback {
181+
return fusermount(dir, cfg)
182+
}
183+
return dev, err
113184
}

0 commit comments

Comments
 (0)