1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
  
     | 
    
      package fuse
import (
	"errors"
	"fmt"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"syscall"
	"golang.org/x/sys/unix"
)
func findFusermount() (string, error) {
	path, err := exec.LookPath("fusermount3")
	if err != nil {
		path, err = exec.LookPath("fusermount")
	}
	if err != nil {
		return "", err
	}
	return path, nil
}
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":      disableFunc(unix.MS_RDONLY),
	"ro":      enableFunc(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) {
	if cfg.DebugLogger != nil {
		cfg.DebugLogger.Println("Preparing for direct mounting")
	}
	// 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")
	if cfg.DebugLogger != nil {
		cfg.DebugLogger.Println("Successfully opened the /dev/fuse in blocking mode")
	}
	// 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)
	}
	fsname := opts["fsname"]
	delete(opts, "fsname") // handled via fstype mount(2) parameter
	fstype := "fuse"
	if subtype, ok := opts["subtype"]; ok {
		fstype += "." + subtype
	}
	delete(opts, "subtype")
	data += "," + mapToOptionsString(opts)
	if cfg.DebugLogger != nil {
		cfg.DebugLogger.Println("Starting the unix mounting")
	}
	if err := unix.Mount(
		fsname,    // source
		dir,       // target
		fstype,    // fstype
		mountflag, // mountflag
		data,      // data
	); err != nil {
		if err == syscall.EPERM {
			return nil, errFallback
		}
		return nil, err
	}
	if cfg.DebugLogger != nil {
		cfg.DebugLogger.Println("Unix mounting completed successfully")
	}
	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
	if cfg.DebugLogger != nil {
		cfg.DebugLogger.Println("Parsing fuse file descriptor")
	}
	// If the mountpoint is /dev/fd/N, assume that the file descriptor N is an
	// already open FUSE channel. Parse it, cast it to an fd, and don't do any
	// other part of the mount dance.
	if fd, err := parseFuseFd(dir); err == nil {
		dev := os.NewFile(uintptr(fd), "/dev/fuse")
		return dev, 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 {
		if cfg.DebugLogger != nil {
			cfg.DebugLogger.Println("Directmount failed. Trying fallback.")
		}
		fusermountPath, err := findFusermount()
		if err != nil {
			return nil, err
		}
		argv := []string{
			"-o", cfg.toOptionsString(),
			"--",
			dir,
		}
		dev, err := fusermount(fusermountPath, argv, []string{}, true, cfg.DebugLogger)
		if err == nil {
			return dev, nil
		}
		// fusermount requires the mount user to have write access to the directory.
		// However, it doesn't give a useful error on mount-failure if the access is missing.
		// So, add this check and return a useful error if the user doesn't have write-access.
		if err2 := unix.Access(dir, unix.W_OK); err2 != nil {
			return nil, errors.Join(err, fmt.Errorf("the user doesn't have write-access on the mount point: %w", err2))
		}
		return dev, err
	}
	return dev, err
}
func parseFuseFd(dir string) (int, error) {
	if !strings.HasPrefix(dir, "/dev/fd/") {
		return -1, fmt.Errorf("not a /dev/fd path")
	}
	fd, err := strconv.ParseUint(strings.TrimPrefix(dir, "/dev/fd/"), 10, 32)
	if err != nil {
		return -1, fmt.Errorf("invalid /dev/fd/N path: N must be a positive integer")
	}
	return int(fd), nil
}
 
     |