2017-06-19 14:44:49 +02:00
package mountlib
import (
"log"
2017-11-09 01:37:27 +01:00
"os"
2019-01-10 15:18:00 +01:00
"path/filepath"
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"
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
sysdnotify "github.com/iguanesolutions/go-systemd/v5/notify"
"github.com/pkg/errors"
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
)
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
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
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
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
}
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 ( ) {
2019-10-12 13:41:36 +02:00
// DaemonTimeout defaults to non zero for macOS
if runtime . GOOS == "darwin" {
2021-02-21 13:56:19 +01:00
DefaultOpt . DaemonTimeout = 10 * time . Minute
2020-07-23 18:17:01 +02:00
}
}
// 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." )
2020-11-11 00:17:25 +01:00
// 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" )
2019-06-24 12:54:38 +02:00
}
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. ` ,
2021-01-03 01:05:52 +01:00
Long : strings . ReplaceAll ( strings . ReplaceAll ( mountHelp , "|" , "`" ) , "@" , commandName ) + vfs . Help ,
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-01-03 01:05:52 +01:00
if Opt . Daemon {
2018-08-21 10:41:16 +02:00
config . PassConfigKeyForDaemonization = true
}
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
}
2021-01-03 01:05:52 +01:00
mnt := & MountPoint {
MountFn : mount ,
MountPoint : args [ 1 ] ,
Fs : cmd . NewFsDir ( args ) ,
MountOpt : Opt ,
VFSOpt : vfsflags . Opt ,
2020-03-03 19:59:47 +01:00
}
2018-05-03 10:34:07 +02:00
2021-07-26 12:44:02 +02:00
daemonized , err := mnt . Mount ( )
if ! daemonized && err == nil {
2021-01-03 01:05:52 +01:00
err = mnt . Wait ( )
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
2021-07-26 12:44:02 +02:00
func ( m * MountPoint ) Mount ( ) ( daemonized bool , err error ) {
2021-01-03 01:05:52 +01:00
if err = m . CheckOverlap ( ) ; err != nil {
2021-07-26 12:44:02 +02:00
return false , err
2018-06-26 10:26:34 +02:00
}
2021-01-03 01:05:52 +01:00
if err = m . CheckAllowings ( ) ; err != nil {
2021-07-26 12:44:02 +02:00
return false , err
2018-06-26 10:26:34 +02:00
}
2021-01-03 01:05:52 +01:00
m . SetVolumeName ( m . MountOpt . VolumeName )
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 {
2021-07-26 12:44:02 +02:00
daemonized = startBackgroundMode ( )
2021-01-03 01:05:52 +01:00
if daemonized {
2021-07-26 12:44:02 +02:00
return true , nil
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-07-26 12:44:02 +02:00
return false , errors . Wrap ( err , "failed to mount FUSE fs" )
2020-07-23 14:08:38 +02:00
}
2021-07-26 12:44:02 +02:00
return false , nil
2021-01-03 01:05:52 +01:00
}
// CheckOverlap checks that root doesn't overlap with mountpoint
func ( m * MountPoint ) CheckOverlap ( ) error {
name := m . Fs . Name ( )
if name != "" && name != "local" {
return nil
}
rootAbs := absPath ( m . Fs . Root ( ) )
mountpointAbs := absPath ( m . MountPoint )
if strings . HasPrefix ( rootAbs , mountpointAbs ) || strings . HasPrefix ( mountpointAbs , rootAbs ) {
const msg = "mount point %q and directory to be mounted %q mustn't overlap"
return errors . Errorf ( msg , m . MountPoint , m . Fs . Root ( ) )
}
return nil
}
// absPath is a helper function for MountPoint.CheckOverlap
func absPath ( path string ) string {
if abs , err := filepath . EvalSymlinks ( path ) ; err == nil {
path = abs
}
if abs , err := filepath . Abs ( path ) ; err == nil {
path = abs
}
path = filepath . ToSlash ( path )
if ! strings . HasSuffix ( path , "/" ) {
path += "/"
}
return path
}
// CheckAllowings informs about ignored flags on Windows. If not on Windows
// and not --allow-non-empty flag is used, verify that mountpoint is empty.
func ( m * MountPoint ) CheckAllowings ( ) error {
opt := & m . MountOpt
if runtime . GOOS == "windows" {
if opt . AllowNonEmpty {
fs . Logf ( nil , "--allow-non-empty flag does nothing on Windows" )
}
if opt . AllowRoot {
fs . Logf ( nil , "--allow-root flag does nothing on Windows" )
}
if opt . AllowOther {
fs . Logf ( nil , "--allow-other flag does nothing on Windows" )
}
return nil
}
if ! opt . AllowNonEmpty {
return CheckMountEmpty ( m . MountPoint )
}
return nil
}
2020-07-23 14:08:38 +02:00
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 ( ) {
_ = sysdnotify . Stopping ( )
2021-01-03 01:05:52 +01:00
_ = m . UnmountFn ( )
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
2020-09-18 17:37:54 +02:00
if err := sysdnotify . Ready ( ) ; err != nil {
2020-07-23 14:08:38 +02:00
return errors . Wrap ( err , "failed to notify systemd" )
}
// 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 {
return errors . Wrap ( err , "failed to umount FUSE fs" )
}
return nil
}
2021-01-03 01:05:52 +01:00
// Unmount the specified mountpoint
func ( m * MountPoint ) Unmount ( ) ( err error ) {
return m . UnmountFn ( )
}
// SetVolumeName with sensible default
func ( m * MountPoint ) SetVolumeName ( vol string ) {
if vol == "" {
vol = m . Fs . Name ( ) + ":" + m . Fs . Root ( )
}
m . MountOpt . SetVolumeName ( vol )
}
// SetVolumeName removes special characters from volume name if necessary
func ( opt * Options ) SetVolumeName ( vol string ) {
vol = strings . ReplaceAll ( vol , ":" , " " )
vol = strings . ReplaceAll ( vol , "/" , " " )
vol = strings . TrimSpace ( vol )
if runtime . GOOS == "windows" && len ( vol ) > 32 {
vol = vol [ : 32 ]
}
opt . VolumeName = vol
}