mirror of
https://github.com/rclone/rclone.git
synced 2025-12-10 13:23:21 +00:00
Current way of checking whether mountpoint has been already mounted (directory list) can result in race if rclone runs under Automount (classic or systemd). This patch adopts Linux ProcFS for the check. Note that mountpoint is considered empty if it's tagged as "mounted" by autofs. Also ProcFS is used to check whether rclone mount was successful (ie. tagged by a string containing "rclone"). On macOS/BSD where ProcFS is unavailable the old method is still used. This patch also moves a few utility functions unchanged to utils.go: CheckOverlap, CheckAllowings, SetVolumeName.
253 lines
8.0 KiB
Go
253 lines
8.0 KiB
Go
package mountlib
|
|
|
|
import (
|
|
"context"
|
|
"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/vfs"
|
|
"github.com/rclone/rclone/vfs/vfscommon"
|
|
"github.com/rclone/rclone/vfs/vfsflags"
|
|
|
|
sysdnotify "github.com/iguanesolutions/go-systemd/v5/notify"
|
|
"github.com/pkg/errors"
|
|
"github.com/spf13/cobra"
|
|
"github.com/spf13/pflag"
|
|
)
|
|
|
|
// Options for creating the mount
|
|
type Options struct {
|
|
DebugFUSE bool
|
|
AllowNonEmpty bool
|
|
AllowRoot bool
|
|
AllowOther bool
|
|
DefaultPermissions bool
|
|
WritebackCache bool
|
|
Daemon bool
|
|
MaxReadAhead fs.SizeSuffix
|
|
ExtraOptions []string
|
|
ExtraFlags []string
|
|
AttrTimeout time.Duration // how long the kernel caches attribute for
|
|
VolumeName string
|
|
NoAppleDouble bool
|
|
NoAppleXattr bool
|
|
DaemonTimeout time.Duration // OSXFUSE only
|
|
AsyncRead bool
|
|
NetworkMode bool // Windows only
|
|
}
|
|
|
|
// DefaultOpt is the default values for creating the mount
|
|
var 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 state
|
|
type 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
|
|
}
|
|
|
|
// Global constants
|
|
const (
|
|
MaxLeafSize = 1024 // don't pass file names longer than this
|
|
)
|
|
|
|
func init() {
|
|
// DaemonTimeout defaults to non zero for macOS
|
|
if runtime.GOOS == "darwin" {
|
|
DefaultOpt.DaemonTimeout = 10 * time.Minute
|
|
}
|
|
}
|
|
|
|
// Options set by command line flags
|
|
var (
|
|
Opt = DefaultOpt
|
|
)
|
|
|
|
// AddFlags adds the non filing system specific flags to the command
|
|
func 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 as a daemon (background mode). 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.")
|
|
// 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")
|
|
}
|
|
|
|
// 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: strings.ReplaceAll(strings.ReplaceAll(mountHelp, "|", "`"), "@", commandName) + vfs.Help,
|
|
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
|
|
}
|
|
|
|
// Show stats if the user has specifically requested them
|
|
if cmd.ShowStats() {
|
|
defer cmd.StartStats()()
|
|
}
|
|
|
|
mnt := &MountPoint{
|
|
MountFn: mount,
|
|
MountPoint: args[1],
|
|
Fs: cmd.NewFsDir(args),
|
|
MountOpt: Opt,
|
|
VFSOpt: vfsflags.Opt,
|
|
}
|
|
|
|
daemonized, err := mnt.Mount()
|
|
if !daemonized && err == nil {
|
|
err = mnt.Wait()
|
|
}
|
|
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 mountpoint
|
|
func (m *MountPoint) Mount() (daemonized bool, err error) {
|
|
if err = m.CheckOverlap(); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
if err = m.CheckAllowings(); err != nil {
|
|
return false, err
|
|
}
|
|
m.SetVolumeName(m.MountOpt.VolumeName)
|
|
|
|
// Start background task if --daemon is specified
|
|
if m.MountOpt.Daemon {
|
|
daemonized = startBackgroundMode()
|
|
if daemonized {
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
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 false, errors.Wrap(err, "failed to mount FUSE fs")
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
// Wait for mount end
|
|
func (m *MountPoint) Wait() error {
|
|
// Unmount on exit
|
|
var finaliseOnce sync.Once
|
|
finalise := func() {
|
|
finaliseOnce.Do(func() {
|
|
_ = sysdnotify.Stopping()
|
|
_ = m.UnmountFn()
|
|
})
|
|
}
|
|
fnHandle := atexit.Register(finalise)
|
|
defer atexit.Unregister(fnHandle)
|
|
|
|
// Notify systemd
|
|
if err := sysdnotify.Ready(); err != nil {
|
|
return errors.Wrap(err, "failed to notify systemd")
|
|
}
|
|
|
|
// 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 errors.Wrap(err, "failed to umount FUSE fs")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Unmount the specified mountpoint
|
|
func (m *MountPoint) Unmount() (err error) {
|
|
return m.UnmountFn()
|
|
}
|