github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/cmd/cmount/mount.go (about) 1 //go:build cmount && ((linux && cgo) || (darwin && cgo) || (freebsd && cgo) || windows) 2 3 // Package cmount implements a FUSE mounting system for rclone remotes. 4 // 5 // This uses the cgo based cgofuse library 6 package cmount 7 8 import ( 9 "errors" 10 "fmt" 11 "os" 12 "runtime" 13 "strings" 14 "time" 15 16 "github.com/rclone/rclone/cmd/mountlib" 17 "github.com/rclone/rclone/fs" 18 "github.com/rclone/rclone/lib/atexit" 19 "github.com/rclone/rclone/lib/buildinfo" 20 "github.com/rclone/rclone/vfs" 21 "github.com/winfsp/cgofuse/fuse" 22 ) 23 24 func init() { 25 name := "cmount" 26 cmountOnly := runtime.GOOS != "linux" // rclone mount only works for linux 27 if cmountOnly { 28 name = "mount" 29 } 30 cmd := mountlib.NewMountCommand(name, false, mount) 31 if cmountOnly { 32 cmd.Aliases = append(cmd.Aliases, "cmount") 33 } 34 mountlib.AddRc("cmount", mount) 35 buildinfo.Tags = append(buildinfo.Tags, "cmount") 36 } 37 38 // Find the option string in the current options 39 func findOption(name string, options []string) (found bool) { 40 for _, option := range options { 41 if option == "-o" { 42 continue 43 } 44 if strings.Contains(option, name) { 45 return true 46 } 47 } 48 return false 49 } 50 51 // mountOptions configures the options from the command line flags 52 func mountOptions(VFS *vfs.VFS, device string, mountpoint string, opt *mountlib.Options) (options []string) { 53 // Options 54 options = []string{ 55 "-o", fmt.Sprintf("attr_timeout=%g", opt.AttrTimeout.Seconds()), 56 } 57 if opt.DebugFUSE { 58 options = append(options, "-o", "debug") 59 } 60 61 if runtime.GOOS == "windows" { 62 options = append(options, "-o", "uid=-1") 63 options = append(options, "-o", "gid=-1") 64 options = append(options, "--FileSystemName=rclone") 65 if opt.VolumeName != "" { 66 if opt.NetworkMode { 67 options = append(options, "--VolumePrefix="+opt.VolumeName) 68 } else { 69 options = append(options, "-o", "volname="+opt.VolumeName) 70 } 71 } 72 } else { 73 options = append(options, "-o", "fsname="+device) 74 options = append(options, "-o", "subtype=rclone") 75 options = append(options, "-o", fmt.Sprintf("max_readahead=%d", opt.MaxReadAhead)) 76 // This causes FUSE to supply O_TRUNC with the Open 77 // call which is more efficient for cmount. However 78 // it does not work with cgofuse on Windows with 79 // WinFSP so cmount must work with or without it. 80 options = append(options, "-o", "atomic_o_trunc") 81 if opt.DaemonTimeout != 0 { 82 options = append(options, "-o", fmt.Sprintf("daemon_timeout=%d", int(opt.DaemonTimeout.Seconds()))) 83 } 84 if opt.AllowOther { 85 options = append(options, "-o", "allow_other") 86 } 87 if opt.AllowRoot { 88 options = append(options, "-o", "allow_root") 89 } 90 if opt.DefaultPermissions { 91 options = append(options, "-o", "default_permissions") 92 } 93 if VFS.Opt.ReadOnly { 94 options = append(options, "-o", "ro") 95 } 96 if opt.WritebackCache { 97 // FIXME? options = append(options, "-o", WritebackCache()) 98 } 99 if runtime.GOOS == "darwin" { 100 if opt.VolumeName != "" { 101 options = append(options, "-o", "volname="+opt.VolumeName) 102 } 103 if opt.NoAppleDouble { 104 options = append(options, "-o", "noappledouble") 105 } 106 if opt.NoAppleXattr { 107 options = append(options, "-o", "noapplexattr") 108 } 109 } 110 } 111 for _, option := range opt.ExtraOptions { 112 options = append(options, "-o", option) 113 } 114 for _, option := range opt.ExtraFlags { 115 options = append(options, option) 116 } 117 return options 118 } 119 120 // waitFor runs fn() until it returns true or the timeout expires 121 func waitFor(fn func() bool) (ok bool) { 122 const totalWait = 10 * time.Second 123 const individualWait = 10 * time.Millisecond 124 for i := 0; i < int(totalWait/individualWait); i++ { 125 ok = fn() 126 if ok { 127 return ok 128 } 129 time.Sleep(individualWait) 130 } 131 return false 132 } 133 134 // mount the file system 135 // 136 // The mount point will be ready when this returns. 137 // 138 // returns an error, and an error channel for the serve process to 139 // report an error when fusermount is called. 140 func mount(VFS *vfs.VFS, mountPath string, opt *mountlib.Options) (<-chan error, func() error, error) { 141 // Get mountpoint using OS specific logic 142 f := VFS.Fs() 143 mountpoint, err := getMountpoint(f, mountPath, opt) 144 if err != nil { 145 return nil, nil, err 146 } 147 fs.Debugf(nil, "Mounting on %q (%q)", mountpoint, opt.VolumeName) 148 149 // Create underlying FS 150 fsys := NewFS(VFS, opt) 151 host := fuse.NewFileSystemHost(fsys) 152 host.SetCapReaddirPlus(true) // only works on Windows 153 if opt.CaseInsensitive.Valid { 154 host.SetCapCaseInsensitive(opt.CaseInsensitive.Value) 155 } else { 156 host.SetCapCaseInsensitive(f.Features().CaseInsensitive) 157 } 158 159 // Create options 160 options := mountOptions(VFS, opt.DeviceName, mountpoint, opt) 161 fs.Debugf(f, "Mounting with options: %q", options) 162 163 // Serve the mount point in the background returning error to errChan 164 errChan := make(chan error, 1) 165 go func() { 166 defer func() { 167 if r := recover(); r != nil { 168 errChan <- fmt.Errorf("mount failed: %v", r) 169 } 170 }() 171 var err error 172 ok := host.Mount(mountpoint, options) 173 if !ok { 174 err = errors.New("mount failed") 175 fs.Errorf(f, "Mount failed") 176 } 177 errChan <- err 178 }() 179 180 // unmount 181 unmount := func() error { 182 // Shutdown the VFS 183 fsys.VFS.Shutdown() 184 var umountOK bool 185 if fsys.destroyed.Load() != 0 { 186 fs.Debugf(nil, "Not calling host.Unmount as mount already Destroyed") 187 umountOK = true 188 } else if atexit.Signalled() { 189 // If we have received a signal then FUSE will be shutting down already 190 fs.Debugf(nil, "Not calling host.Unmount as signal received") 191 umountOK = true 192 } else { 193 fs.Debugf(nil, "Calling host.Unmount") 194 umountOK = host.Unmount() 195 } 196 if umountOK { 197 fs.Debugf(nil, "Unmounted successfully") 198 if runtime.GOOS == "windows" { 199 if !waitFor(func() bool { 200 _, err := os.Stat(mountpoint) 201 return err != nil 202 }) { 203 fs.Errorf(nil, "mountpoint %q didn't disappear after unmount - continuing anyway", mountpoint) 204 } 205 } 206 return nil 207 } 208 fs.Debugf(nil, "host.Unmount failed") 209 return errors.New("host unmount failed") 210 } 211 212 // Wait for the filesystem to become ready, checking the file 213 // system didn't blow up before starting 214 select { 215 case err := <-errChan: 216 err = fmt.Errorf("mount stopped before calling Init: %w", err) 217 return nil, nil, err 218 case <-fsys.ready: 219 } 220 221 // Wait for the mount point to be available on Windows 222 // On Windows the Init signal comes slightly before the mount is ready 223 if runtime.GOOS == "windows" { 224 if !waitFor(func() bool { 225 _, err := os.Stat(mountpoint) 226 return err == nil 227 }) { 228 fs.Errorf(nil, "mountpoint %q didn't became available on mount - continuing anyway", mountpoint) 229 } 230 } 231 232 return errChan, unmount, nil 233 }