2022-08-28 13:21:57 +02:00
// Package mountlib provides the mount command.
2017-06-19 14:44:49 +02:00
package mountlib
import (
2021-08-13 19:42:33 +02:00
"context"
2023-10-05 16:32:50 +02:00
_ "embed"
2021-11-04 11:12:57 +01:00
"fmt"
2017-06-19 14:44:49 +02:00
"log"
2017-11-09 01:37:27 +01:00
"os"
2017-11-16 13:20:53 +01:00
"runtime"
2018-05-03 10:34:07 +02:00
"strings"
2020-11-27 11:50:10 +01:00
"sync"
2018-03-02 17:39:42 +01:00
"time"
2017-06-19 14:44:49 +02:00
2019-07-28 19:47:38 +02:00
"github.com/rclone/rclone/cmd"
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/config"
"github.com/rclone/rclone/fs/config/flags"
2020-07-23 18:17:01 +02:00
"github.com/rclone/rclone/fs/rc"
2020-07-23 14:08:38 +02:00
"github.com/rclone/rclone/lib/atexit"
2021-08-18 13:07:09 +02:00
"github.com/rclone/rclone/lib/daemonize"
2019-07-28 19:47:38 +02:00
"github.com/rclone/rclone/vfs"
2021-01-03 01:05:52 +01:00
"github.com/rclone/rclone/vfs/vfscommon"
2019-07-28 19:47:38 +02:00
"github.com/rclone/rclone/vfs/vfsflags"
2021-01-03 01:05:52 +01:00
2023-11-29 10:25:30 +01:00
"github.com/coreos/go-systemd/v22/daemon"
2017-06-19 14:44:49 +02:00
"github.com/spf13/cobra"
2020-07-23 18:17:01 +02:00
"github.com/spf13/pflag"
2017-06-19 14:44:49 +02:00
)
2023-10-05 16:32:50 +02:00
//go:embed mount.md
var mountHelp string
2024-04-05 13:27:33 +02:00
// help returns the help string cleaned up to simplify appending
func help ( commandName string ) string {
return strings . TrimSpace ( strings . ReplaceAll ( mountHelp , "@" , commandName ) ) + "\n\n"
}
2020-07-23 18:17:01 +02:00
// Options for creating the mount
type Options struct {
DebugFUSE bool
AllowNonEmpty bool
AllowRoot bool
AllowOther bool
DefaultPermissions bool
WritebackCache bool
Daemon bool
2021-08-18 13:07:09 +02:00
DaemonWait time . Duration // time to wait for ready mount from daemon, maximum on Linux or constant on macOS/BSD
2020-07-23 18:17:01 +02:00
MaxReadAhead fs . SizeSuffix
2017-11-07 18:09:08 +01:00
ExtraOptions [ ] string
ExtraFlags [ ] string
2020-07-23 18:17:01 +02:00
AttrTimeout time . Duration // how long the kernel caches attribute for
2022-02-09 12:56:43 +01:00
DeviceName string
2018-05-03 10:34:07 +02:00
VolumeName string
2020-07-23 18:17:01 +02:00
NoAppleDouble bool
NoAppleXattr bool
2018-07-18 17:21:35 +02:00
DaemonTimeout time . Duration // OSXFUSE only
2020-07-23 18:17:01 +02:00
AsyncRead bool
2020-11-06 14:21:38 +01:00
NetworkMode bool // Windows only
2024-03-04 12:37:18 +01:00
DirectIO bool // use Direct IO for file access
2023-03-13 12:28:26 +01:00
CaseInsensitive fs . Tristate
2020-07-23 18:17:01 +02:00
}
// 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
}
2017-06-19 14:44:49 +02:00
2020-04-25 07:03:07 +02:00
type (
// UnmountFn is called to unmount the file system
UnmountFn func ( ) error
// MountFn is called to mount the file system
2020-07-23 18:17:01 +02:00
MountFn func ( VFS * vfs . VFS , mountpoint string , opt * Options ) ( <- chan error , func ( ) error , error )
2020-04-25 07:03:07 +02:00
)
2021-01-03 01:05:52 +01:00
// 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
}
2022-04-21 21:28:09 +02:00
// NewMountPoint makes a new mounting structure
func 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 ,
}
}
2019-10-18 11:53:07 +02:00
// Global constants
const (
2020-01-19 15:54:55 +01:00
MaxLeafSize = 1024 // don't pass file names longer than this
2019-10-18 11:53:07 +02:00
)
2019-06-24 12:54:38 +02:00
func init ( ) {
2021-08-18 13:07:09 +02:00
switch runtime . GOOS {
case "darwin" :
// DaemonTimeout defaults to non-zero for macOS
// (this is a macOS specific kernel option unrelated to DaemonWait)
2021-02-21 13:56:19 +01:00
DefaultOpt . DaemonTimeout = 10 * time . Minute
2020-07-23 18:17:01 +02:00
}
2021-08-18 13:07:09 +02:00
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
2020-07-23 18:17:01 +02:00
Opt = DefaultOpt
2021-08-18 13:07:09 +02:00
}
// Opt contains options set by command line flags
var Opt Options
2020-07-23 18:17:01 +02:00
// AddFlags adds the non filing system specific flags to the command
func AddFlags ( flagSet * pflag . FlagSet ) {
rc . AddOption ( "mount" , & Opt )
2023-07-10 19:34:10 +02:00
flags . BoolVarP ( flagSet , & Opt . DebugFUSE , "debug-fuse" , "" , Opt . DebugFUSE , "Debug the FUSE internals - needs -v" , "Mount" )
flags . DurationVarP ( flagSet , & Opt . AttrTimeout , "attr-timeout" , "" , Opt . AttrTimeout , "Time for which file/directory attributes are cached" , "Mount" )
flags . StringArrayVarP ( flagSet , & Opt . ExtraOptions , "option" , "o" , [ ] string { } , "Option for libfuse/WinFsp (repeat if required)" , "Mount" )
flags . StringArrayVarP ( flagSet , & Opt . ExtraFlags , "fuse-flag" , "" , [ ] string { } , "Flags or arguments to be passed direct to libfuse/WinFsp (repeat if required)" , "Mount" )
2020-11-11 00:17:25 +01:00
// Non-Windows only
2023-07-10 19:34:10 +02:00
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)" , "Mount" )
flags . DurationVarP ( flagSet , & Opt . DaemonTimeout , "daemon-timeout" , "" , Opt . DaemonTimeout , "Time limit for rclone to respond to kernel (not supported on Windows)" , "Mount" )
flags . BoolVarP ( flagSet , & Opt . DefaultPermissions , "default-permissions" , "" , Opt . DefaultPermissions , "Makes kernel enforce access control based on the file mode (not supported on Windows)" , "Mount" )
flags . BoolVarP ( flagSet , & Opt . AllowNonEmpty , "allow-non-empty" , "" , Opt . AllowNonEmpty , "Allow mounting over a non-empty directory (not supported on Windows)" , "Mount" )
flags . BoolVarP ( flagSet , & Opt . AllowRoot , "allow-root" , "" , Opt . AllowRoot , "Allow access to root user (not supported on Windows)" , "Mount" )
flags . BoolVarP ( flagSet , & Opt . AllowOther , "allow-other" , "" , Opt . AllowOther , "Allow access to other users (not supported on Windows)" , "Mount" )
flags . BoolVarP ( flagSet , & Opt . AsyncRead , "async-read" , "" , Opt . AsyncRead , "Use asynchronous reads (not supported on Windows)" , "Mount" )
flags . FVarP ( flagSet , & Opt . MaxReadAhead , "max-read-ahead" , "" , "The number of bytes that can be prefetched for sequential reads (not supported on Windows)" , "Mount" )
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)" , "Mount" )
flags . StringVarP ( flagSet , & Opt . DeviceName , "devname" , "" , Opt . DeviceName , "Set the device name - default is remote:path" , "Mount" )
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)" , "Mount" )
2024-03-04 12:37:18 +01:00
flags . BoolVarP ( flagSet , & Opt . DirectIO , "direct-io" , "" , Opt . DirectIO , "Use Direct IO, disables caching of data" , "Mount" )
2020-11-11 00:17:25 +01:00
// Windows and OSX
2023-07-10 19:34:10 +02:00
flags . StringVarP ( flagSet , & Opt . VolumeName , "volname" , "" , Opt . VolumeName , "Set the volume name (supported on Windows and OSX only)" , "Mount" )
2020-11-11 00:17:25 +01:00
// OSX only
2023-07-10 19:34:10 +02:00
flags . BoolVarP ( flagSet , & Opt . NoAppleDouble , "noappledouble" , "" , Opt . NoAppleDouble , "Ignore Apple Double (._) and .DS_Store files (supported on OSX only)" , "Mount" )
flags . BoolVarP ( flagSet , & Opt . NoAppleXattr , "noapplexattr" , "" , Opt . NoAppleXattr , "Ignore all \"com.apple.*\" extended attributes (supported on OSX only)" , "Mount" )
2020-11-11 00:17:25 +01:00
// Windows only
2023-07-10 19:34:10 +02:00
flags . BoolVarP ( flagSet , & Opt . NetworkMode , "network-mode" , "" , Opt . NetworkMode , "Mount as remote network drive, instead of fixed disk drive (supported on Windows only)" , "Mount" )
2021-08-18 13:07:09 +02:00
// Unix only
2023-07-10 19:34:10 +02:00
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)" , "Mount" )
2019-06-24 12:54:38 +02:00
}
2023-11-29 16:11:11 +01:00
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
}
2017-06-19 14:44:49 +02:00
// NewMountCommand makes a mount command with the given name and Mount function
2020-07-23 14:08:38 +02:00
func NewMountCommand ( commandName string , hidden bool , mount MountFn ) * cobra . Command {
2019-10-11 17:58:11 +02:00
var commandDefinition = & cobra . Command {
2020-02-11 13:05:43 +01:00
Use : commandName + " remote:path /path/to/mountpoint" ,
Hidden : hidden ,
Short : ` Mount the remote as file system on a mountpoint. ` ,
2024-04-05 13:27:33 +02:00
Long : help ( commandName ) + vfs . Help ( ) ,
2022-11-26 23:40:49 +01:00
Annotations : map [ string ] string {
"versionIntroduced" : "v1.33" ,
2023-07-10 19:34:10 +02:00
"groups" : "Filter" ,
2022-11-26 23:40:49 +01:00
} ,
2017-06-19 14:44:49 +02:00
Run : func ( command * cobra . Command , args [ ] string ) {
cmd . CheckArgs ( 2 , 2 , command , args )
2018-08-21 10:41:16 +02:00
2021-08-13 19:42:33 +02:00
if fs . GetConfig ( context . Background ( ) ) . UseListR {
fs . Logf ( nil , "--fast-list does nothing on a mount" )
}
2021-01-03 01:05:52 +01:00
if Opt . Daemon {
2018-08-21 10:41:16 +02:00
config . PassConfigKeyForDaemonization = true
}
2021-08-18 13:07:09 +02:00
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" )
}
2017-06-19 14:44:49 +02:00
// Show stats if the user has specifically requested them
if cmd . ShowStats ( ) {
2018-10-03 22:46:18 +02:00
defer cmd . StartStats ( ) ( )
2017-06-19 14:44:49 +02:00
}
2022-04-21 21:28:09 +02:00
mnt := NewMountPoint ( mount , args [ 1 ] , cmd . NewFsDir ( args ) , & Opt , & vfsflags . Opt )
2023-11-29 10:25:30 +01:00
mountDaemon , err := mnt . Mount ( )
2021-08-18 13:07:09 +02:00
// Wait for foreground mount, if any...
2023-11-29 10:25:30 +01:00
if mountDaemon == nil {
2021-08-18 13:07:09 +02:00
if err == nil {
err = mnt . Wait ( )
}
if err != nil {
log . Fatalf ( "Fatal error: %v" , err )
}
return
}
2023-11-29 10:25:30 +01:00
// Wait for mountDaemon, if any...
2021-08-18 13:07:09 +02:00
killOnce := sync . Once { }
killDaemon := func ( reason string ) {
killOnce . Do ( func ( ) {
2023-11-29 10:25:30 +01:00
if err := mountDaemon . Signal ( os . Interrupt ) ; err != nil {
fs . Errorf ( nil , "%s. Failed to terminate daemon pid %d: %v" , reason , mountDaemon . Pid , err )
2021-08-18 13:07:09 +02:00
return
}
2023-11-29 10:25:30 +01:00
fs . Debugf ( nil , "%s. Terminating daemon pid %d" , reason , mountDaemon . Pid )
2021-08-18 13:07:09 +02:00
} )
}
if err == nil && Opt . DaemonWait > 0 {
handle := atexit . Register ( func ( ) {
killDaemon ( "Got interrupt" )
} )
2023-11-29 10:25:30 +01:00
err = WaitMountReady ( mnt . MountPoint , Opt . DaemonWait , mountDaemon )
2021-08-18 13:07:09 +02:00
if err != nil {
killDaemon ( "Daemon timed out" )
}
atexit . Unregister ( handle )
2018-03-02 14:30:04 +01:00
}
2017-06-19 14:44:49 +02:00
if err != nil {
log . Fatalf ( "Fatal error: %v" , err )
}
} ,
}
// Register the command
2019-10-11 17:58:11 +02:00
cmd . Root . AddCommand ( commandDefinition )
2017-06-19 14:44:49 +02:00
// Add flags
2019-10-11 17:55:04 +02:00
cmdFlags := commandDefinition . Flags ( )
2020-07-23 18:17:01 +02:00
AddFlags ( cmdFlags )
2019-10-11 17:55:04 +02:00
vfsflags . AddFlags ( cmdFlags )
2017-10-24 22:06:06 +02:00
2019-10-11 17:58:11 +02:00
return commandDefinition
2017-06-19 14:44:49 +02:00
}
2018-06-26 10:26:34 +02:00
2021-01-03 01:05:52 +01:00
// Mount the remote at mountpoint
2023-11-29 10:25:30 +01:00
func ( m * MountPoint ) Mount ( ) ( mountDaemon * os . Process , err error ) {
2021-01-03 01:05:52 +01:00
2022-06-11 16:18:48 +02:00
// Ensure sensible defaults
2021-01-03 01:05:52 +01:00
m . SetVolumeName ( m . MountOpt . VolumeName )
2022-02-09 12:56:43 +01:00
m . SetDeviceName ( m . MountOpt . DeviceName )
2020-07-23 14:08:38 +02:00
2021-07-26 12:44:02 +02:00
// Start background task if --daemon is specified
2021-01-03 01:05:52 +01:00
if m . MountOpt . Daemon {
2023-11-29 10:25:30 +01:00
mountDaemon , err = daemonize . StartDaemon ( os . Args )
if mountDaemon != nil || err != nil {
return mountDaemon , err
2021-01-03 01:05:52 +01:00
}
2020-07-23 18:17:01 +02:00
}
2021-01-03 01:05:52 +01:00
m . VFS = vfs . New ( m . Fs , & m . VFSOpt )
m . ErrChan , m . UnmountFn , err = m . MountFn ( m . VFS , m . MountPoint , & m . MountOpt )
2020-07-23 14:08:38 +02:00
if err != nil {
2021-11-04 11:12:57 +01:00
return nil , fmt . Errorf ( "failed to mount FUSE fs: %w" , err )
2020-07-23 14:08:38 +02:00
}
2022-04-21 21:28:09 +02:00
m . MountedOn = time . Now ( )
2021-08-18 13:07:09 +02:00
return nil , nil
2021-01-03 01:05:52 +01:00
}
// Wait for mount end
func ( m * MountPoint ) Wait ( ) error {
2020-07-23 14:08:38 +02:00
// Unmount on exit
2020-11-27 11:50:10 +01:00
var finaliseOnce sync . Once
finalise := func ( ) {
finaliseOnce . Do ( func ( ) {
2023-11-29 10:25:30 +01:00
_ , _ = daemon . SdNotify ( false , daemon . SdNotifyStopping )
2021-08-18 13:07:09 +02:00
// 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" )
}
2020-11-27 11:50:10 +01:00
} )
}
fnHandle := atexit . Register ( finalise )
2020-07-23 14:08:38 +02:00
defer atexit . Unregister ( fnHandle )
// Notify systemd
2023-11-29 10:25:30 +01:00
if _ , err := daemon . SdNotify ( false , daemon . SdNotifyReady ) ; err != nil {
2021-11-04 11:12:57 +01:00
return fmt . Errorf ( "failed to notify systemd: %w" , err )
2020-07-23 14:08:38 +02:00
}
// Reload VFS cache on SIGHUP
sigHup := make ( chan os . Signal , 1 )
2021-01-03 01:05:52 +01:00
NotifyOnSigHup ( sigHup )
var err error
2020-07-23 14:08:38 +02:00
2021-01-03 01:05:52 +01:00
waiting := true
for waiting {
2020-07-23 14:08:38 +02:00
select {
// umount triggered outside the app
2021-01-03 01:05:52 +01:00
case err = <- m . ErrChan :
waiting = false
2020-07-23 14:08:38 +02:00
// user sent SIGHUP to clear the cache
case <- sigHup :
2021-01-03 01:05:52 +01:00
root , err := m . VFS . Root ( )
2020-07-23 14:08:38 +02:00
if err != nil {
2021-01-03 01:05:52 +01:00
fs . Errorf ( m . VFS . Fs ( ) , "Error reading root: %v" , err )
2020-07-23 14:08:38 +02:00
} else {
root . ForgetAll ( )
}
}
}
2020-11-27 11:50:10 +01:00
finalise ( )
2020-07-23 14:08:38 +02:00
if err != nil {
2021-11-04 11:12:57 +01:00
return fmt . Errorf ( "failed to umount FUSE fs: %w" , err )
2020-07-23 14:08:38 +02:00
}
return nil
}
2021-01-03 01:05:52 +01:00
// Unmount the specified mountpoint
func ( m * MountPoint ) Unmount ( ) ( err error ) {
return m . UnmountFn ( )
}