github.com/scaleoutsean/fusego@v0.0.0-20220224074057-4a6429e46bb8/mount_linux.go (about) 1 package fuse 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "net" 8 "os" 9 "os/exec" 10 "syscall" 11 12 "golang.org/x/sys/unix" 13 ) 14 15 func fusermount(dir string, cfg *MountConfig) (*os.File, error) { 16 // Create a socket pair. 17 fds, err := syscall.Socketpair(syscall.AF_FILE, syscall.SOCK_STREAM, 0) 18 if err != nil { 19 return nil, fmt.Errorf("Socketpair: %v", err) 20 } 21 22 // Wrap the sockets into os.File objects that we will pass off to fusermount. 23 writeFile := os.NewFile(uintptr(fds[0]), "fusermount-child-writes") 24 defer writeFile.Close() 25 26 readFile := os.NewFile(uintptr(fds[1]), "fusermount-parent-reads") 27 defer readFile.Close() 28 29 // Start fusermount, passing it a buffer in which to write stderr. 30 var stderr bytes.Buffer 31 32 cmd := exec.Command( 33 "fusermount", 34 "-o", cfg.toOptionsString(), 35 "--", 36 dir, 37 ) 38 39 cmd.Env = append(os.Environ(), "_FUSE_COMMFD=3") 40 cmd.ExtraFiles = []*os.File{writeFile} 41 cmd.Stderr = &stderr 42 43 // Run the command. 44 err = cmd.Run() 45 if err != nil { 46 return nil, fmt.Errorf("running fusermount: %v\n\nstderr:\n%s", err, stderr.Bytes()) 47 } 48 49 // Wrap the socket file in a connection. 50 c, err := net.FileConn(readFile) 51 if err != nil { 52 return nil, fmt.Errorf("FileConn: %v", err) 53 } 54 defer c.Close() 55 56 // We expect to have a Unix domain socket. 57 uc, ok := c.(*net.UnixConn) 58 if !ok { 59 return nil, fmt.Errorf("Expected UnixConn, got %T", c) 60 } 61 62 // Read a message. 63 buf := make([]byte, 32) // expect 1 byte 64 oob := make([]byte, 32) // expect 24 bytes 65 _, oobn, _, _, err := uc.ReadMsgUnix(buf, oob) 66 if err != nil { 67 return nil, fmt.Errorf("ReadMsgUnix: %v", err) 68 } 69 70 // Parse the message. 71 scms, err := syscall.ParseSocketControlMessage(oob[:oobn]) 72 if err != nil { 73 return nil, fmt.Errorf("ParseSocketControlMessage: %v", err) 74 } 75 76 // We expect one message. 77 if len(scms) != 1 { 78 return nil, fmt.Errorf("expected 1 SocketControlMessage; got scms = %#v", scms) 79 } 80 81 scm := scms[0] 82 83 // Pull out the FD returned by fusermount 84 gotFds, err := syscall.ParseUnixRights(&scm) 85 if err != nil { 86 return nil, fmt.Errorf("syscall.ParseUnixRights: %v", err) 87 } 88 89 if len(gotFds) != 1 { 90 return nil, fmt.Errorf("wanted 1 fd; got %#v", gotFds) 91 } 92 93 // Turn the FD into an os.File. 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": disableFunc(unix.MS_RDONLY), 112 "ro": enableFunc(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 fstype := "fuse" 153 if subtype, ok := opts["subtype"]; ok { 154 fstype += "." + subtype 155 } 156 delete(opts, "subtype") 157 data += "," + mapToOptionsString(opts) 158 if err := unix.Mount( 159 cfg.FSName, // source 160 dir, // target 161 fstype, // fstype 162 mountflag, // mountflag 163 data, // data 164 ); err != nil { 165 if err == syscall.EPERM { 166 return nil, errFallback 167 168 } 169 return nil, err 170 } 171 return dev, nil 172 } 173 174 // Begin the process of mounting at the given directory, returning a connection 175 // to the kernel. Mounting continues in the background, and is complete when an 176 // error is written to the supplied channel. The file system may need to 177 // service the connection in order for mounting to complete. 178 func mount(dir string, cfg *MountConfig, ready chan<- error) (*os.File, error) { 179 // On linux, mounting is never delayed. 180 ready <- nil 181 182 // Try mounting without fusermount(1) first: we might be running as root or 183 // have the CAP_SYS_ADMIN capability. 184 dev, err := directmount(dir, cfg) 185 if err == errFallback { 186 return fusermount(dir, cfg) 187 } 188 return dev, err 189 }