// Package mountlib provides the mount command.package mountlib import ( "context" _ "embed" "fmt" "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/lib/atexit" "github.com/rclone/rclone/lib/daemonize" "github.com/rclone/rclone/lib/systemd" "github.com/rclone/rclone/vfs" "github.com/rclone/rclone/vfs/vfscommon" "github.com/rclone/rclone/vfs/vfsflags" "github.com/spf13/cobra" "github.com/spf13/pflag") //go:embed mount.mdvar mountHelp string // help returns the help string cleaned up to simplify appendingfunc help(commandName string) string { return strings.TrimSpace(strings.ReplaceAll(mountHelp, "@", commandName)) + "\n\n"} // OptionsInfo describes the Options in usevar OptionsInfo = fs.Options{{ Name: "debug_fuse", Default: false, Help: "Debug the FUSE internals - needs -v", Groups: "Mount",}, { Name: "attr_timeout", Default: fs.Duration(1 * time.Second), Help: "Time for which file/directory attributes are cached", Groups: "Mount",}, { Name: "option", Default: []string{}, Help: "Option for libfuse/WinFsp (repeat if required)", Groups: "Mount", ShortOpt: "o",}, { Name: "fuse_flag", Default: []string{}, Help: "Flags or arguments to be passed direct to libfuse/WinFsp (repeat if required)", Groups: "Mount",}, { Name: "daemon", Default: false, Help: "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)", Groups: "Mount",}, { Name: "daemon_timeout", Default: func() fs.Duration { if runtime.GOOS == "darwin" { // DaemonTimeout defaults to non-zero for macOS // (this is a macOS specific kernel option unrelated to DaemonWait) return fs.Duration(10 * time.Minute) } return 0 }(), Help: "Time limit for rclone to respond to kernel (not supported on Windows)", Groups: "Mount",}, { Name: "default_permissions", Default: false, Help: "Makes kernel enforce access control based on the file mode (not supported on Windows)", Groups: "Mount",}, { Name: "allow_non_empty", Default: false, Help: "Allow mounting over a non-empty directory (not supported on Windows)", Groups: "Mount",}, { Name: "allow_root", Default: false, Help: "Allow access to root user (not supported on Windows)", Groups: "Mount",}, { Name: "allow_other", Default: false, Help: "Allow access to other users (not supported on Windows)", Groups: "Mount",}, { Name: "allow_idmap", Default: false, Help: "Allow id-mapped mounts (Linux 6.12+, mount2 only)", Groups: "Mount",}, { Name: "async_read", Default: true, Help: "Use asynchronous reads (not supported on Windows)", Groups: "Mount",}, { Name: "max_read_ahead", Default: fs.SizeSuffix(128 * 1024), Help: "The number of bytes that can be prefetched for sequential reads (not supported on Windows)", Groups: "Mount",}, { Name: "write_back_cache", Default: false, Help: "Makes kernel buffer writes before sending them to rclone (without this, writethrough caching is used) (not supported on Windows)", Groups: "Mount",}, { Name: "devname", Default: "", Help: "Set the device name - default is remote:path", Groups: "Mount",}, { Name: "mount_case_insensitive", Default: fs.Tristate{}, Help: "Tell the OS the mount is case insensitive (true) or sensitive (false) regardless of the backend (auto)", Groups: "Mount",}, { Name: "direct_io", Default: false, Help: "Use Direct IO, disables caching of data", Groups: "Mount",}, { Name: "volname", Default: "", Help: "Set the volume name (supported on Windows and OSX only)", Groups: "Mount",}, { Name: "noappledouble", Default: true, Help: "Ignore Apple Double (._) and .DS_Store files (supported on OSX only)", Groups: "Mount",}, { Name: "noapplexattr", Default: false, Help: "Ignore all \"com.apple.*\" extended attributes (supported on OSX only)", Groups: "Mount",}, { Name: "network_mode", Default: false, Help: "Mount as remote network drive, instead of fixed disk drive (supported on Windows only)", Groups: "Mount",}, { Name: "daemon_wait", Default: func() fs.Duration { switch runtime.GOOS { case "linux": // Linux provides /proc/mounts to check mount status // so --daemon-wait means *maximum* time to wait return fs.Duration(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 return fs.Duration(5 * time.Second) } return 0 }(), Help: "Time to wait for ready mount from daemon (maximum time on Linux, constant sleep time on OSX/BSD) (not supported on Windows)", Groups: "Mount",}} func init() { fs.RegisterGlobalOptions(fs.OptionsInfo{Name: "mount", Opt: &Opt, Options: OptionsInfo})} // Options for creating the mounttype Options struct { DebugFUSE bool `config:"debug_fuse"` AllowNonEmpty bool `config:"allow_non_empty"` AllowRoot bool `config:"allow_root"` AllowOther bool `config:"allow_other"` AllowIDMap bool `config:"allow_idmap"` DefaultPermissions bool `config:"default_permissions"` WritebackCache bool `config:"write_back_cache"` Daemon bool `config:"daemon"` DaemonWait fs.Duration `config:"daemon_wait"` // time to wait for ready mount from daemon, maximum on Linux or constant on macOS/BSD MaxReadAhead fs.SizeSuffix `config:"max_read_ahead"` ExtraOptions []string `config:"option"` ExtraFlags []string `config:"fuse_flag"` AttrTimeout fs.Duration `config:"attr_timeout"` // how long the kernel caches attribute for DeviceName string `config:"devname"` VolumeName string `config:"volname"` NoAppleDouble bool `config:"noappledouble"` NoAppleXattr bool `config:"noapplexattr"` DaemonTimeout fs.Duration `config:"daemon_timeout"` // OSXFUSE only AsyncRead bool `config:"async_read"` NetworkMode bool `config:"network_mode"` // Windows only DirectIO bool `config:"direct_io"` // use Direct IO for file access CaseInsensitive fs.Tristate `config:"mount_case_insensitive"`} type ( // UnmountFn is called to unmount the file system UnmountFn func() error // MountFn is called to mount the file system // // It returns the errChan, unmount function, the actual mountpoint // (which may differ from the input, e.g. on Windows when "*" is // used to auto-assign a drive letter) and an error. MountFn func(VFS *vfs.VFS, mountpoint string, opt *Options) (<-chan error, func() error, string, error)) // MountPoint represents a mount with options and runtime statetype MountPoint struct { Ctx context.Context MountPoint string MountedOn time.Time MountOpt Options VFSOpt vfscommon.Options Fs fs.Fs VFS *vfs.VFS MountFn MountFn UnmountFn UnmountFn ErrChan <-chan error vfsShutdownOnce sync.Once} // shutdownVFS shuts down the VFS instance exactly oncefunc (m *MountPoint) shutdownVFS() { m.vfsShutdownOnce.Do(func() { if m.VFS != nil { m.VFS.Shutdown() } })} // NewMountPoint makes a new mounting structurefunc NewMountPoint(mount MountFn, mountPoint string, f fs.Fs, mountOpt *Options, vfsOpt *vfscommon.Options) *MountPoint { return &MountPoint{ Ctx: context.Background(), MountFn: mount, MountPoint: mountPoint, Fs: f, MountOpt: *mountOpt, VFSOpt: *vfsOpt, }} // Global constantsconst ( MaxLeafSize = 1024 // don't pass file names longer than this) // 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) { flags.AddFlagsFromOptions(flagSet, "", OptionsInfo)} const ( pollInterval = 100 * time.Millisecond) // WaitMountReady waits until mountpoint is mounted by rclone.//// If the mount daemon dies prematurely it will notice too.func WaitMountReady(mountpoint string, timeout time.Duration, daemon *os.Process) (err error) { endTime := time.Now().Add(timeout) for { if CanCheckMountReady { err = CheckMountReady(mountpoint) if err == nil { break } } err = daemonize.Check(daemon) if err != nil { return err } delay := time.Until(endTime) if delay <= 0 { break } if delay > pollInterval { delay = pollInterval } time.Sleep(delay) } return} // 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: help(commandName) + strings.TrimSpace(vfs.Help()), Annotations: map[string]string{ "versionIntroduced": "v1.33", "groups": "Filter", }, 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, &vfscommon.Opt) mountDaemon, err := mnt.Mount() // Wait for foreground mount, if any... if mountDaemon == nil { if err == nil { defer systemd.Notify()() err = mnt.Wait() } if err != nil { fs.Fatalf(nil, "Fatal error: %v", err) } return } // Wait for mountDaemon, if any... killOnce := sync.Once{} killDaemon := func(reason string) { killOnce.Do(func() { if err := mountDaemon.Signal(os.Interrupt); err != nil { fs.Errorf(nil, "%s. Failed to terminate daemon pid %d: %v", reason, mountDaemon.Pid, err) return } fs.Debugf(nil, "%s. Terminating daemon pid %d", reason, mountDaemon.Pid) }) } if err == nil && Opt.DaemonWait > 0 { handle := atexit.Register(func() { killDaemon("Got interrupt") }) err = WaitMountReady(mnt.MountPoint, time.Duration(Opt.DaemonWait), mountDaemon) if err != nil { killDaemon("Daemon timed out") } atexit.Unregister(handle) } if err != nil { fs.Fatalf(nil, "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() (mountDaemon *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 { mountDaemon, err = daemonize.StartDaemon(os.Args) if mountDaemon != nil || err != nil { return mountDaemon, err } } m.VFS = vfs.New(m.Ctx, m.Fs, &m.VFSOpt) var actualMountpoint string m.ErrChan, m.UnmountFn, actualMountpoint, err = m.MountFn(m.VFS, m.MountPoint, &m.MountOpt) if err != nil { m.shutdownVFS() if len(os.Args) > 0 && strings.HasPrefix(os.Args[0], "/snap/") { return nil, fmt.Errorf("mounting is not supported when running from snap") } return nil, fmt.Errorf("failed to mount FUSE fs: %w", err) } if actualMountpoint != "" { m.MountPoint = actualMountpoint } 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() { defer m.shutdownVFS() // 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.Logf(m.MountPoint, "Unmounted rclone mount") } }) } fnHandle := atexit.Register(finalise) defer atexit.Unregister(fnHandle) err := <-m.ErrChan 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) { defer m.shutdownVFS() if m.UnmountFn != nil { return m.UnmountFn() } return nil}
// Package mountlib provides the mount command.
package mountlib
import (
"context"
_ "embed"
"fmt"
"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/lib/atexit"
"github.com/rclone/rclone/lib/daemonize"
"github.com/rclone/rclone/lib/systemd"
"github.com/rclone/rclone/vfs"
"github.com/rclone/rclone/vfs/vfscommon"
"github.com/rclone/rclone/vfs/vfsflags"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
//go:embed mount.md
var mountHelp string
// help returns the help string cleaned up to simplify appending
func help(commandName string) string {
return strings.TrimSpace(strings.ReplaceAll(mountHelp, "@", commandName)) + "\n\n"
}
// OptionsInfo describes the Options in use
var OptionsInfo = fs.Options{{
Name: "debug_fuse",
Default: false,
Help: "Debug the FUSE internals - needs -v",
Groups: "Mount",
}, {
Name: "attr_timeout",
Default: fs.Duration(1 * time.Second),
Help: "Time for which file/directory attributes are cached",
Groups: "Mount",
}, {
Name: "option",
Default: []string{},
Help: "Option for libfuse/WinFsp (repeat if required)",
Groups: "Mount",
ShortOpt: "o",
}, {
Name: "fuse_flag",
Default: []string{},
Help: "Flags or arguments to be passed direct to libfuse/WinFsp (repeat if required)",
Groups: "Mount",
}, {
Name: "daemon",
Default: false,
Help: "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)",
Groups: "Mount",
}, {
Name: "daemon_timeout",
Default: func() fs.Duration {
if runtime.GOOS == "darwin" {
// DaemonTimeout defaults to non-zero for macOS
// (this is a macOS specific kernel option unrelated to DaemonWait)
return fs.Duration(10 * time.Minute)
}
return 0
}(),
Help: "Time limit for rclone to respond to kernel (not supported on Windows)",
Groups: "Mount",
}, {
Name: "default_permissions",
Default: false,
Help: "Makes kernel enforce access control based on the file mode (not supported on Windows)",
Groups: "Mount",
}, {
Name: "allow_non_empty",
Default: false,
Help: "Allow mounting over a non-empty directory (not supported on Windows)",
Groups: "Mount",
}, {
Name: "allow_root",
Default: false,
Help: "Allow access to root user (not supported on Windows)",
Groups: "Mount",
}, {
Name: "allow_other",
Default: false,
Help: "Allow access to other users (not supported on Windows)",
Groups: "Mount",
}, {
Name: "allow_idmap",
Default: false,
Help: "Allow id-mapped mounts (Linux 6.12+, mount2 only)",
Groups: "Mount",
}, {
Name: "async_read",
Default: true,
Help: "Use asynchronous reads (not supported on Windows)",
Groups: "Mount",
}, {
Name: "max_read_ahead",
Default: fs.SizeSuffix(128 * 1024),
Help: "The number of bytes that can be prefetched for sequential reads (not supported on Windows)",
Groups: "Mount",
}, {
Name: "write_back_cache",
Default: false,
Help: "Makes kernel buffer writes before sending them to rclone (without this, writethrough caching is used) (not supported on Windows)",
Groups: "Mount",
}, {
Name: "devname",
Default: "",
Help: "Set the device name - default is remote:path",
Groups: "Mount",
}, {
Name: "mount_case_insensitive",
Default: fs.Tristate{},
Help: "Tell the OS the mount is case insensitive (true) or sensitive (false) regardless of the backend (auto)",
Groups: "Mount",
}, {
Name: "direct_io",
Default: false,
Help: "Use Direct IO, disables caching of data",
Groups: "Mount",
}, {
Name: "volname",
Default: "",
Help: "Set the volume name (supported on Windows and OSX only)",
Groups: "Mount",
}, {
Name: "noappledouble",
Default: true,
Help: "Ignore Apple Double (._) and .DS_Store files (supported on OSX only)",
Groups: "Mount",
}, {
Name: "noapplexattr",
Default: false,
Help: "Ignore all \"com.apple.*\" extended attributes (supported on OSX only)",
Groups: "Mount",
}, {
Name: "network_mode",
Default: false,
Help: "Mount as remote network drive, instead of fixed disk drive (supported on Windows only)",
Groups: "Mount",
}, {
Name: "daemon_wait",
Default: func() fs.Duration {
switch runtime.GOOS {
case "linux":
// Linux provides /proc/mounts to check mount status
// so --daemon-wait means *maximum* time to wait
return fs.Duration(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
return fs.Duration(5 * time.Second)
}
return 0
}(),
Help: "Time to wait for ready mount from daemon (maximum time on Linux, constant sleep time on OSX/BSD) (not supported on Windows)",
Groups: "Mount",
}}
func init() {
fs.RegisterGlobalOptions(fs.OptionsInfo{Name: "mount", Opt: &Opt, Options: OptionsInfo})
}
// Options for creating the mount
type Options struct {
DebugFUSE bool `config:"debug_fuse"`
AllowNonEmpty bool `config:"allow_non_empty"`
AllowRoot bool `config:"allow_root"`
AllowOther bool `config:"allow_other"`
AllowIDMap bool `config:"allow_idmap"`
DefaultPermissions bool `config:"default_permissions"`
WritebackCache bool `config:"write_back_cache"`
Daemon bool `config:"daemon"`
DaemonWait fs.Duration `config:"daemon_wait"` // time to wait for ready mount from daemon, maximum on Linux or constant on macOS/BSD
MaxReadAhead fs.SizeSuffix `config:"max_read_ahead"`
ExtraOptions []string `config:"option"`
ExtraFlags []string `config:"fuse_flag"`
AttrTimeout fs.Duration `config:"attr_timeout"` // how long the kernel caches attribute for
DeviceName string `config:"devname"`
VolumeName string `config:"volname"`
NoAppleDouble bool `config:"noappledouble"`
NoAppleXattr bool `config:"noapplexattr"`
DaemonTimeout fs.Duration `config:"daemon_timeout"` // OSXFUSE only
AsyncRead bool `config:"async_read"`
NetworkMode bool `config:"network_mode"` // Windows only
DirectIO bool `config:"direct_io"` // use Direct IO for file access
CaseInsensitive fs.Tristate `config:"mount_case_insensitive"`
}
type (
// UnmountFn is called to unmount the file system
UnmountFn func() error
// MountFn is called to mount the file system
//
// It returns the errChan, unmount function, the actual mountpoint
// (which may differ from the input, e.g. on Windows when "*" is
// used to auto-assign a drive letter) and an error.
MountFn func(VFS *vfs.VFS, mountpoint string, opt *Options) (<-chan error, func() error, string, error)
)
// MountPoint represents a mount with options and runtime state
type MountPoint struct {
Ctx context.Context
MountPoint string
MountedOn time.Time
MountOpt Options
VFSOpt vfscommon.Options
Fs fs.Fs
VFS *vfs.VFS
MountFn MountFn
UnmountFn UnmountFn
ErrChan <-chan error
vfsShutdownOnce sync.Once
}
// shutdownVFS shuts down the VFS instance exactly once
func (m *MountPoint) shutdownVFS() {
m.vfsShutdownOnce.Do(func() {
if m.VFS != nil {
m.VFS.Shutdown()
}
})
}
// NewMountPoint makes a new mounting structure
func NewMountPoint(mount MountFn, mountPoint string, f fs.Fs, mountOpt *Options, vfsOpt *vfscommon.Options) *MountPoint {
return &MountPoint{
Ctx: context.Background(),
MountFn: mount,
MountPoint: mountPoint,
Fs: f,
MountOpt: *mountOpt,
VFSOpt: *vfsOpt,
}
}
// Global constants
const (
MaxLeafSize = 1024 // don't pass file names longer than this
)
// Opt contains options set by command line flags
var Opt Options
// AddFlags adds the non filing system specific flags to the command
func AddFlags(flagSet *pflag.FlagSet) {
flags.AddFlagsFromOptions(flagSet, "", OptionsInfo)
}
const (
pollInterval = 100 * time.Millisecond
)
// WaitMountReady waits until mountpoint is mounted by rclone.
//
// If the mount daemon dies prematurely it will notice too.
func WaitMountReady(mountpoint string, timeout time.Duration, daemon *os.Process) (err error) {
endTime := time.Now().Add(timeout)
for {
if CanCheckMountReady {
err = CheckMountReady(mountpoint)
if err == nil {
break
}
}
err = daemonize.Check(daemon)
if err != nil {
return err
}
delay := time.Until(endTime)
if delay <= 0 {
break
}
if delay > pollInterval {
delay = pollInterval
}
time.Sleep(delay)
}
return
}
// NewMountCommand makes a mount command with the given name and Mount function
func 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: help(commandName) + strings.TrimSpace(vfs.Help()),
Annotations: map[string]string{
"versionIntroduced": "v1.33",
"groups": "Filter",
},
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, &vfscommon.Opt)
mountDaemon, err := mnt.Mount()
// Wait for foreground mount, if any...
if mountDaemon == nil {
if err == nil {
defer systemd.Notify()()
err = mnt.Wait()
}
if err != nil {
fs.Fatalf(nil, "Fatal error: %v", err)
}
return
}
// Wait for mountDaemon, if any...
killOnce := sync.Once{}
killDaemon := func(reason string) {
killOnce.Do(func() {
if err := mountDaemon.Signal(os.Interrupt); err != nil {
fs.Errorf(nil, "%s. Failed to terminate daemon pid %d: %v", reason, mountDaemon.Pid, err)
return
}
fs.Debugf(nil, "%s. Terminating daemon pid %d", reason, mountDaemon.Pid)
})
}
if err == nil && Opt.DaemonWait > 0 {
handle := atexit.Register(func() {
killDaemon("Got interrupt")
})
err = WaitMountReady(mnt.MountPoint, time.Duration(Opt.DaemonWait), mountDaemon)
if err != nil {
killDaemon("Daemon timed out")
}
atexit.Unregister(handle)
}
if err != nil {
fs.Fatalf(nil, "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 mountpoint
func (m *MountPoint) Mount() (mountDaemon *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 {
mountDaemon, err = daemonize.StartDaemon(os.Args)
if mountDaemon != nil || err != nil {
return mountDaemon, err
}
}
m.VFS = vfs.New(m.Ctx, m.Fs, &m.VFSOpt)
var actualMountpoint string
m.ErrChan, m.UnmountFn, actualMountpoint, err = m.MountFn(m.VFS, m.MountPoint, &m.MountOpt)
if err != nil {
m.shutdownVFS()
if len(os.Args) > 0 && strings.HasPrefix(os.Args[0], "/snap/") {
return nil, fmt.Errorf("mounting is not supported when running from snap")
}
return nil, fmt.Errorf("failed to mount FUSE fs: %w", err)
}
if actualMountpoint != "" {
m.MountPoint = actualMountpoint
}
m.MountedOn = time.Now()
return nil, nil
}
// Wait for mount end
func (m *MountPoint) Wait() error {
// Unmount on exit
var finaliseOnce sync.Once
finalise := func() {
finaliseOnce.Do(func() {
defer m.shutdownVFS()
// 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.Logf(m.MountPoint, "Unmounted rclone mount")
}
})
}
fnHandle := atexit.Register(finalise)
defer atexit.Unregister(fnHandle)
err := <-m.ErrChan
finalise()
if err != nil {
return fmt.Errorf("failed to umount FUSE fs: %w", err)
}
return nil
}
// Unmount the specified mountpoint
func (m *MountPoint) Unmount() (err error) {
defer m.shutdownVFS()
if m.UnmountFn != nil {
return m.UnmountFn()
}
return nil
}