325 lines · 11.1 KB · Go
RawDownload
// Package mountlib provides the mount command.package mountlib import (	"context"	"fmt"	"log"	"os"	"runtime"	"strings"	"sync"	"time" 	"github.com/rclone/rclone/cmd"	"github.com/rclone/rclone/fs"	"github.com/rclone/rclone/fs/config"	"github.com/rclone/rclone/fs/config/flags"	"github.com/rclone/rclone/fs/rc"	"github.com/rclone/rclone/lib/atexit"	"github.com/rclone/rclone/lib/daemonize"	"github.com/rclone/rclone/vfs"	"github.com/rclone/rclone/vfs/vfscommon"	"github.com/rclone/rclone/vfs/vfsflags" 	sysdnotify "github.com/iguanesolutions/go-systemd/v5/notify"	"github.com/spf13/cobra"	"github.com/spf13/pflag") // Options for creating the mounttype Options struct {	DebugFUSE          bool	AllowNonEmpty      bool	AllowRoot          bool	AllowOther         bool	DefaultPermissions bool	WritebackCache     bool	Daemon             bool	DaemonWait         time.Duration // time to wait for ready mount from daemon, maximum on Linux or constant on macOS/BSD	MaxReadAhead       fs.SizeSuffix	ExtraOptions       []string	ExtraFlags         []string	AttrTimeout        time.Duration // how long the kernel caches attribute for	DeviceName         string	VolumeName         string	NoAppleDouble      bool	NoAppleXattr       bool	DaemonTimeout      time.Duration // OSXFUSE only	AsyncRead          bool	NetworkMode        bool // Windows only	CaseInsensitive    fs.Tristate} // DefaultOpt is the default values for creating the mountvar DefaultOpt = Options{	MaxReadAhead:  128 * 1024,	AttrTimeout:   1 * time.Second, // how long the kernel caches attribute for	NoAppleDouble: true,            // use noappledouble by default	NoAppleXattr:  false,           // do not use noapplexattr by default	AsyncRead:     true,            // do async reads by default} type (	// UnmountFn is called to unmount the file system	UnmountFn func() error	// MountFn is called to mount the file system	MountFn func(VFS *vfs.VFS, mountpoint string, opt *Options) (<-chan error, func() error, error)) // MountPoint represents a mount with options and runtime statetype MountPoint struct {	MountPoint string	MountedOn  time.Time	MountOpt   Options	VFSOpt     vfscommon.Options	Fs         fs.Fs	VFS        *vfs.VFS	MountFn    MountFn	UnmountFn  UnmountFn	ErrChan    <-chan error} // NewMountPoint makes a new mounting structurefunc NewMountPoint(mount MountFn, mountPoint string, f fs.Fs, mountOpt *Options, vfsOpt *vfscommon.Options) *MountPoint {	return &MountPoint{		MountFn:    mount,		MountPoint: mountPoint,		Fs:         f,		MountOpt:   *mountOpt,		VFSOpt:     *vfsOpt,	}} // Global constantsconst (	MaxLeafSize = 1024 // don't pass file names longer than this) func init() {	switch runtime.GOOS {	case "darwin":		// DaemonTimeout defaults to non-zero for macOS		// (this is a macOS specific kernel option unrelated to DaemonWait)		DefaultOpt.DaemonTimeout = 10 * time.Minute	} 	switch runtime.GOOS {	case "linux":		// Linux provides /proc/mounts to check mount status		// so --daemon-wait means *maximum* time to wait		DefaultOpt.DaemonWait = 60 * time.Second	case "darwin", "openbsd", "freebsd", "netbsd":		// On BSD we can't check mount status yet		// so --daemon-wait is just a *constant* delay		DefaultOpt.DaemonWait = 5 * time.Second	} 	// Opt must be assigned in the init block to ensure changes really get in	Opt = DefaultOpt} // Opt contains options set by command line flagsvar Opt Options // AddFlags adds the non filing system specific flags to the commandfunc AddFlags(flagSet *pflag.FlagSet) {	rc.AddOption("mount", &Opt)	flags.BoolVarP(flagSet, &Opt.DebugFUSE, "debug-fuse", "", Opt.DebugFUSE, "Debug the FUSE internals - needs -v")	flags.DurationVarP(flagSet, &Opt.AttrTimeout, "attr-timeout", "", Opt.AttrTimeout, "Time for which file/directory attributes are cached")	flags.StringArrayVarP(flagSet, &Opt.ExtraOptions, "option", "o", []string{}, "Option for libfuse/WinFsp (repeat if required)")	flags.StringArrayVarP(flagSet, &Opt.ExtraFlags, "fuse-flag", "", []string{}, "Flags or arguments to be passed direct to libfuse/WinFsp (repeat if required)")	// Non-Windows only	flags.BoolVarP(flagSet, &Opt.Daemon, "daemon", "", Opt.Daemon, "Run mount in background and exit parent process (as background output is suppressed, use --log-file with --log-format=pid,... to monitor) (not supported on Windows)")	flags.DurationVarP(flagSet, &Opt.DaemonTimeout, "daemon-timeout", "", Opt.DaemonTimeout, "Time limit for rclone to respond to kernel (not supported on Windows)")	flags.BoolVarP(flagSet, &Opt.DefaultPermissions, "default-permissions", "", Opt.DefaultPermissions, "Makes kernel enforce access control based on the file mode (not supported on Windows)")	flags.BoolVarP(flagSet, &Opt.AllowNonEmpty, "allow-non-empty", "", Opt.AllowNonEmpty, "Allow mounting over a non-empty directory (not supported on Windows)")	flags.BoolVarP(flagSet, &Opt.AllowRoot, "allow-root", "", Opt.AllowRoot, "Allow access to root user (not supported on Windows)")	flags.BoolVarP(flagSet, &Opt.AllowOther, "allow-other", "", Opt.AllowOther, "Allow access to other users (not supported on Windows)")	flags.BoolVarP(flagSet, &Opt.AsyncRead, "async-read", "", Opt.AsyncRead, "Use asynchronous reads (not supported on Windows)")	flags.FVarP(flagSet, &Opt.MaxReadAhead, "max-read-ahead", "", "The number of bytes that can be prefetched for sequential reads (not supported on Windows)")	flags.BoolVarP(flagSet, &Opt.WritebackCache, "write-back-cache", "", Opt.WritebackCache, "Makes kernel buffer writes before sending them to rclone (without this, writethrough caching is used) (not supported on Windows)")	flags.StringVarP(flagSet, &Opt.DeviceName, "devname", "", Opt.DeviceName, "Set the device name - default is remote:path")	flags.FVarP(flagSet, &Opt.CaseInsensitive, "mount-case-insensitive", "", "Tell the OS the mount is case insensitive (true) or sensitive (false) regardless of the backend (auto)")	// Windows and OSX	flags.StringVarP(flagSet, &Opt.VolumeName, "volname", "", Opt.VolumeName, "Set the volume name (supported on Windows and OSX only)")	// OSX only	flags.BoolVarP(flagSet, &Opt.NoAppleDouble, "noappledouble", "", Opt.NoAppleDouble, "Ignore Apple Double (._) and .DS_Store files (supported on OSX only)")	flags.BoolVarP(flagSet, &Opt.NoAppleXattr, "noapplexattr", "", Opt.NoAppleXattr, "Ignore all \"com.apple.*\" extended attributes (supported on OSX only)")	// Windows only	flags.BoolVarP(flagSet, &Opt.NetworkMode, "network-mode", "", Opt.NetworkMode, "Mount as remote network drive, instead of fixed disk drive (supported on Windows only)")	// Unix only	flags.DurationVarP(flagSet, &Opt.DaemonWait, "daemon-wait", "", Opt.DaemonWait, "Time to wait for ready mount from daemon (maximum time on Linux, constant sleep time on OSX/BSD) (not supported on Windows)")} // NewMountCommand makes a mount command with the given name and Mount functionfunc NewMountCommand(commandName string, hidden bool, mount MountFn) *cobra.Command {	var commandDefinition = &cobra.Command{		Use:    commandName + " remote:path /path/to/mountpoint",		Hidden: hidden,		Short:  `Mount the remote as file system on a mountpoint.`,		Long:   strings.ReplaceAll(strings.ReplaceAll(mountHelp, "|", "`"), "@", commandName) + vfs.Help,		Annotations: map[string]string{			"versionIntroduced": "v1.33",		},		Run: func(command *cobra.Command, args []string) {			cmd.CheckArgs(2, 2, command, args) 			if fs.GetConfig(context.Background()).UseListR {				fs.Logf(nil, "--fast-list does nothing on a mount")			} 			if Opt.Daemon {				config.PassConfigKeyForDaemonization = true			} 			if os.Getenv("PATH") == "" && runtime.GOOS != "windows" {				// PATH can be unset when running under Autofs or Systemd mount				fs.Debugf(nil, "Using fallback PATH to run fusermount")				_ = os.Setenv("PATH", "/bin:/usr/bin")			} 			// Show stats if the user has specifically requested them			if cmd.ShowStats() {				defer cmd.StartStats()()			} 			mnt := NewMountPoint(mount, args[1], cmd.NewFsDir(args), &Opt, &vfsflags.Opt)			daemon, err := mnt.Mount() 			// Wait for foreground mount, if any...			if daemon == nil {				if err == nil {					err = mnt.Wait()				}				if err != nil {					log.Fatalf("Fatal error: %v", err)				}				return			} 			// Wait for daemon, if any...			killOnce := sync.Once{}			killDaemon := func(reason string) {				killOnce.Do(func() {					if err := daemon.Signal(os.Interrupt); err != nil {						fs.Errorf(nil, "%s. Failed to terminate daemon pid %d: %v", reason, daemon.Pid, err)						return					}					fs.Debugf(nil, "%s. Terminating daemon pid %d", reason, daemon.Pid)				})			} 			if err == nil && Opt.DaemonWait > 0 {				handle := atexit.Register(func() {					killDaemon("Got interrupt")				})				err = WaitMountReady(mnt.MountPoint, Opt.DaemonWait)				if err != nil {					killDaemon("Daemon timed out")				}				atexit.Unregister(handle)			}			if err != nil {				log.Fatalf("Fatal error: %v", err)			}		},	} 	// Register the command	cmd.Root.AddCommand(commandDefinition) 	// Add flags	cmdFlags := commandDefinition.Flags()	AddFlags(cmdFlags)	vfsflags.AddFlags(cmdFlags) 	return commandDefinition} // Mount the remote at mountpointfunc (m *MountPoint) Mount() (daemon *os.Process, err error) { 	// Ensure sensible defaults	m.SetVolumeName(m.MountOpt.VolumeName)	m.SetDeviceName(m.MountOpt.DeviceName) 	// Start background task if --daemon is specified	if m.MountOpt.Daemon {		daemon, err = daemonize.StartDaemon(os.Args)		if daemon != nil || err != nil {			return daemon, err		}	} 	m.VFS = vfs.New(m.Fs, &m.VFSOpt) 	m.ErrChan, m.UnmountFn, err = m.MountFn(m.VFS, m.MountPoint, &m.MountOpt)	if err != nil {		return nil, fmt.Errorf("failed to mount FUSE fs: %w", err)	}	m.MountedOn = time.Now()	return nil, nil} // Wait for mount endfunc (m *MountPoint) Wait() error {	// Unmount on exit	var finaliseOnce sync.Once	finalise := func() {		finaliseOnce.Do(func() {			_ = sysdnotify.Stopping()			// Unmount only if directory was mounted by rclone, e.g. don't unmount autofs hooks.			if err := CheckMountReady(m.MountPoint); err != nil {				fs.Debugf(m.MountPoint, "Unmounted externally. Just exit now.")				return			}			if err := m.Unmount(); err != nil {				fs.Errorf(m.MountPoint, "Failed to unmount: %v", err)			} else {				fs.Errorf(m.MountPoint, "Unmounted rclone mount")			}		})	}	fnHandle := atexit.Register(finalise)	defer atexit.Unregister(fnHandle) 	// Notify systemd	if err := sysdnotify.Ready(); err != nil {		return fmt.Errorf("failed to notify systemd: %w", err)	} 	// Reload VFS cache on SIGHUP	sigHup := make(chan os.Signal, 1)	NotifyOnSigHup(sigHup)	var err error 	waiting := true	for waiting {		select {		// umount triggered outside the app		case err = <-m.ErrChan:			waiting = false		// user sent SIGHUP to clear the cache		case <-sigHup:			root, err := m.VFS.Root()			if err != nil {				fs.Errorf(m.VFS.Fs(), "Error reading root: %v", err)			} else {				root.ForgetAll()			}		}	} 	finalise() 	if err != nil {		return fmt.Errorf("failed to umount FUSE fs: %w", err)	}	return nil} // Unmount the specified mountpointfunc (m *MountPoint) Unmount() (err error) {	return m.UnmountFn()}

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t