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  }