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  }