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 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"
}
2024-07-02 11:39:48 +02:00
// 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 : "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 } )
2020-07-23 18:17:01 +02:00
}
2024-07-02 11:39:48 +02:00
// 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" `
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" `
2020-07-23 18:17:01 +02:00
}
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
)
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 ) {
2024-07-02 11:39:48 +02:00
flags . AddFlagsFromOptions ( flagSet , "" , OptionsInfo )
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
}
2024-07-03 12:34:29 +02:00
mnt := NewMountPoint ( mount , args [ 1 ] , cmd . NewFsDir ( args ) , & Opt , & vfscommon . 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" )
} )
2024-07-02 11:26:15 +02:00
err = WaitMountReady ( mnt . MountPoint , time . Duration ( 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 ( )
}