2017-06-19 14:44:49 +02:00
package mountlib
import (
2017-11-09 01:37:27 +01:00
"io"
2017-06-19 14:44:49 +02:00
"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"
2018-03-02 17:39:42 +01:00
"time"
2017-06-19 14:44:49 +02:00
"github.com/ncw/rclone/cmd"
"github.com/ncw/rclone/fs"
2018-08-21 10:41:16 +02:00
"github.com/ncw/rclone/fs/config"
2018-01-12 17:30:54 +01:00
"github.com/ncw/rclone/fs/config/flags"
2017-11-13 18:54:21 +01:00
"github.com/ncw/rclone/vfs"
2017-10-29 12:00:56 +01:00
"github.com/ncw/rclone/vfs/vfsflags"
2017-11-09 01:37:27 +01:00
"github.com/pkg/errors"
2017-06-19 14:44:49 +02:00
"github.com/spf13/cobra"
)
// Options set by command line flags
var (
2017-10-28 21:01:34 +02:00
DebugFUSE = false
2017-06-19 14:44:49 +02:00
AllowNonEmpty = false
AllowRoot = false
AllowOther = false
DefaultPermissions = false
WritebackCache = false
2018-03-02 14:30:04 +01:00
Daemon = false
2017-06-19 14:44:49 +02:00
MaxReadAhead fs . SizeSuffix = 128 * 1024
2017-11-07 18:09:08 +01:00
ExtraOptions [ ] string
ExtraFlags [ ] string
2018-03-23 23:42:51 +01:00
AttrTimeout = 1 * time . Second // how long the kernel caches attribute for
2018-05-03 10:34:07 +02:00
VolumeName string
2018-07-18 17:21:35 +02:00
NoAppleDouble = true // use noappledouble by default
NoAppleXattr = false // do not use noapplexattr by default
DaemonTimeout time . Duration // OSXFUSE only
2017-06-19 14:44:49 +02:00
)
2017-11-09 01:37:27 +01:00
// Check is folder is empty
func checkMountEmpty ( mountpoint string ) error {
fp , fpErr := os . Open ( mountpoint )
if fpErr != nil {
return errors . Wrap ( fpErr , "Can not open: " + mountpoint )
}
defer fs . CheckClose ( fp , & fpErr )
_ , fpErr = fp . Readdirnames ( 1 )
// directory is not empty
if fpErr != io . EOF {
var e error
var errorMsg = "Directory is not empty: " + mountpoint + " If you want to mount it anyway use: --allow-non-empty option"
if fpErr == nil {
e = errors . New ( errorMsg )
} else {
e = errors . Wrap ( fpErr , errorMsg )
}
return e
}
return nil
}
2019-01-10 15:18:00 +01:00
// Check the root doesn't overlap the mountpoint
func checkMountpointOverlap ( root , mountpoint string ) error {
abs := func ( x string ) string {
if absX , err := filepath . EvalSymlinks ( x ) ; err == nil {
x = absX
}
if absX , err := filepath . Abs ( x ) ; err == nil {
x = absX
}
x = filepath . ToSlash ( x )
if ! strings . HasSuffix ( x , "/" ) {
x += "/"
}
return x
}
rootAbs , mountpointAbs := abs ( root ) , abs ( mountpoint )
if strings . HasPrefix ( rootAbs , mountpointAbs ) || strings . HasPrefix ( mountpointAbs , rootAbs ) {
return errors . Errorf ( "mount point %q and directory to be mounted %q mustn't overlap" , mountpoint , root )
}
return nil
}
2017-06-19 14:44:49 +02:00
// NewMountCommand makes a mount command with the given name and Mount function
func NewMountCommand ( commandName string , Mount func ( f fs . Fs , mountpoint string ) error ) * cobra . Command {
var commandDefintion = & cobra . Command {
Use : commandName + " remote:path /path/to/mountpoint" ,
2018-10-04 12:37:42 +02:00
Short : ` Mount the remote as file system on a mountpoint. ` ,
2017-06-19 14:44:49 +02:00
Long : `
rclone ` + commandName + ` allows Linux , FreeBSD , macOS and Windows to
mount any of Rclone ' s cloud storage systems as a file system with
FUSE .
First set up your remote using ` + " ` rclone config ` " + ` . Check it works with ` + " ` rclone ls ` " + ` etc .
Start the mount like this
rclone ` + commandName + ` remote : path / to / files / path / to / local / mount
Or on Windows like this where X : is an unused drive letter
rclone ` + commandName + ` remote : path / to / files X :
When the program ends , either via Ctrl + C or receiving a SIGINT or SIGTERM signal ,
the mount is automatically stopped .
The umount operation can fail , for example when the mountpoint is busy .
When that happens , it is the user ' s responsibility to stop the mount manually with
# Linux
fusermount - u / path / to / local / mount
# OS X
umount / path / to / local / mount
2018-01-18 20:16:21 +01:00
# # # Installing on Windows
2017-07-23 13:13:29 +02:00
To run rclone ` + commandName + ` on Windows , you will need to
download and install [ WinFsp ] ( http : //www.secfs.net/winfsp/).
WinFsp is an [ open source ] ( https : //github.com/billziss-gh/winfsp)
Windows File System Proxy which makes it easy to write user space file
systems for Windows . It provides a FUSE emulation layer which rclone
uses combination with
[ cgofuse ] ( https : //github.com/billziss-gh/cgofuse). Both of these
packages are by Bill Zissimopoulos who was very helpful during the
implementation of rclone ` + commandName + ` for Windows .
2018-01-18 20:16:21 +01:00
# # # # Windows caveats
2017-07-26 22:08:24 +02:00
Note that drives created as Administrator are not visible by other
accounts ( including the account that was elevated as
Administrator ) . So if you start a Windows drive from an Administrative
Command Prompt and then try to access the same drive from Explorer
( which does not run as Administrator ) , you will not be able to see the
new drive .
The easiest way around this is to start the drive from a normal
command prompt . It is also possible to start a drive from the SYSTEM
account ( using [ the WinFsp . Launcher
infrastructure ] ( https : //github.com/billziss-gh/winfsp/wiki/WinFsp-Service-Architecture))
2018-02-11 18:37:47 +01:00
which creates drives accessible for everyone on the system or
alternatively using [ the nssm service manager ] ( https : //nssm.cc/usage).
2017-07-26 22:08:24 +02:00
2018-01-18 20:16:21 +01:00
# # # Limitations
2017-06-19 14:44:49 +02:00
2018-02-10 10:28:20 +01:00
Without the use of "--vfs-cache-mode" this can only write files
sequentially , it can only seek when reading . This means that many
applications won ' t work with their files on an rclone mount without
"--vfs-cache-mode writes" or "--vfs-cache-mode full" . See the [ File
Caching ] ( # file - caching ) section for more info .
2017-06-19 14:44:49 +02:00
The bucket based remotes ( eg Swift , S3 , Google Compute Storage , B2 ,
Hubic ) won ' t work from the root - you will need to specify a bucket ,
or a path within the bucket . So ` + " ` swift : ` " + ` won ' t work whereas
` + " ` swift : bucket ` " + ` will as will ` + " ` swift : bucket / path ` " + ` .
None of these support the concept of directories , so empty
directories will have a tendency to disappear once they fall out of
the directory cache .
Only supported on Linux , FreeBSD , OS X and Windows at the moment .
2018-01-18 20:16:21 +01:00
# # # rclone ` + commandName + ` vs rclone sync / copy
2017-06-19 14:44:49 +02:00
File systems expect things to be 100 % reliable , whereas cloud storage
systems are a long way from 100 % reliable . The rclone sync / copy
commands cope with this with lots of retries . However rclone ` + commandName + `
can ' t use retries in the same way without making local copies of the
2018-10-04 12:37:42 +02:00
uploads . Look at the [ file caching ] ( # file - caching )
2018-11-14 17:11:58 +01:00
for solutions to make ` + commandName + ` more reliable .
2017-06-19 14:44:49 +02:00
2018-03-02 17:39:42 +01:00
# # # Attribute caching
You can use the flag -- attr - timeout to set the time the kernel caches
the attributes ( size , modification time etc ) for directory entries .
2018-03-23 23:42:51 +01:00
The default is "1s" which caches files just long enough to avoid
too many callbacks to rclone from the kernel .
In theory 0 s should be the correct value for filesystems which can
change outside the control of the kernel . However this causes quite a
few problems such as
[ rclone using too much memory ] ( https : //github.com/ncw/rclone/issues/2157),
[ rclone not serving files to samba ] ( https : //forum.rclone.org/t/rclone-1-39-vs-1-40-mount-issue/5112)
and [ excessive time listing directories ] ( https : //github.com/ncw/rclone/issues/2095#issuecomment-371141147).
The kernel can cache the info about a file for the time given by
"--attr-timeout" . You may see corruption if the remote file changes
length during this window . It will show up as either a truncated file
or a file with garbage on the end . With "--attr-timeout 1s" this is
very unlikely but not impossible . The higher you set "--attr-timeout"
the more likely it is . The default setting of "1s" is the lowest
setting which mitigates the problems above .
If you set it higher ( ' 10 s ' or ' 1 m ' say ) then the kernel will call
back to rclone less often making it more efficient , however there is
more chance of the corruption issue above .
If files don ' t change on the remote outside of the control of rclone
then there is no chance of corruption .
2018-03-02 17:39:42 +01:00
This is the same as setting the attr_timeout option in mount . fuse .
2018-01-18 20:16:21 +01:00
# # # Filters
2017-06-19 14:44:49 +02:00
Note that all the rclone filters can be used to select a subset of the
files to be visible in the mount .
2018-01-18 20:16:21 +01:00
# # # systemd
2017-11-19 23:03:49 +01:00
When running rclone ` + commandName + ` as a systemd service , it is possible
2018-01-18 20:16:21 +01:00
to use Type = notify . In this case the service will enter the started state
2017-11-19 23:03:49 +01:00
after the mountpoint has been successfully set up .
Units having the rclone ` + commandName + ` service specified as a requirement
will see all files and folders immediately in this mode .
2018-02-18 15:18:12 +01:00
# # # chunked reading # # #
-- vfs - read - chunk - size will enable reading the source objects in parts .
This can reduce the used download quota for some remotes by requesting only chunks
from the remote that are actually read at the cost of an increased number of requests .
When -- vfs - read - chunk - size - limit is also specified and greater than -- vfs - read - chunk - size ,
the chunk size for each open file will get doubled for each chunk read , until the
specified value is reached . A value of - 1 will disable the limit and the chunk size will
grow indefinitely .
With -- vfs - read - chunk - size 100 M and -- vfs - read - chunk - size - limit 0 the following
parts will be downloaded : 0 - 100 M , 100 M - 200 M , 200 M - 300 M , 300 M - 400 M and so on .
When -- vfs - read - chunk - size - limit 500 M is specified , the result would be
0 - 100 M , 100 M - 300 M , 300 M - 700 M , 700 M - 1200 M , 1200 M - 1700 M and so on .
Chunked reading will only work with -- vfs - cache - mode < full , as the file will always
be copied to the vfs cache before opening with -- vfs - cache - mode full .
2017-11-13 18:54:21 +01:00
` + 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
if Daemon {
config . PassConfigKeyForDaemonization = true
}
2019-01-10 15:18:00 +01:00
mountpoint := args [ 1 ]
2018-05-07 18:58:16 +02:00
fdst := cmd . NewFsDir ( args )
2019-01-10 15:18:00 +01:00
if fdst . Name ( ) == "" || fdst . Name ( ) == "local" {
err := checkMountpointOverlap ( fdst . Root ( ) , mountpoint )
if err != nil {
log . Fatalf ( "Fatal error: %v" , err )
}
}
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
}
2017-11-16 13:20:53 +01:00
// Skip checkMountEmpty if --allow-non-empty flag is used or if
// the Operating System is Windows
if ! AllowNonEmpty && runtime . GOOS != "windows" {
2019-01-10 15:18:00 +01:00
err := checkMountEmpty ( mountpoint )
2017-11-09 01:37:27 +01:00
if err != nil {
log . Fatalf ( "Fatal error: %v" , err )
}
}
2018-05-03 10:34:07 +02:00
// Work out the volume name, removing special
// characters from it if necessary
if VolumeName == "" {
VolumeName = fdst . Name ( ) + ":" + fdst . Root ( )
}
VolumeName = strings . Replace ( VolumeName , ":" , " " , - 1 )
VolumeName = strings . Replace ( VolumeName , "/" , " " , - 1 )
VolumeName = strings . TrimSpace ( VolumeName )
2018-03-02 14:30:04 +01:00
// Start background task if --background is specified
if Daemon {
daemonized := startBackgroundMode ( )
if daemonized {
return
}
}
2019-01-10 15:18:00 +01:00
err := Mount ( fdst , mountpoint )
2017-06-19 14:44:49 +02:00
if err != nil {
log . Fatalf ( "Fatal error: %v" , err )
}
} ,
}
// Register the command
cmd . Root . AddCommand ( commandDefintion )
// Add flags
2018-01-12 17:30:54 +01:00
flagSet := commandDefintion . Flags ( )
flags . BoolVarP ( flagSet , & DebugFUSE , "debug-fuse" , "" , DebugFUSE , "Debug the FUSE internals - needs -v." )
2017-06-19 14:44:49 +02:00
// mount options
2018-01-12 17:30:54 +01:00
flags . BoolVarP ( flagSet , & AllowNonEmpty , "allow-non-empty" , "" , AllowNonEmpty , "Allow mounting over a non-empty directory." )
flags . BoolVarP ( flagSet , & AllowRoot , "allow-root" , "" , AllowRoot , "Allow access to root user." )
flags . BoolVarP ( flagSet , & AllowOther , "allow-other" , "" , AllowOther , "Allow access to other users." )
flags . BoolVarP ( flagSet , & DefaultPermissions , "default-permissions" , "" , DefaultPermissions , "Makes kernel enforce access control based on the file mode." )
flags . BoolVarP ( flagSet , & WritebackCache , "write-back-cache" , "" , WritebackCache , "Makes kernel buffer writes before sending them to rclone. Without this, writethrough caching is used." )
flags . FVarP ( flagSet , & MaxReadAhead , "max-read-ahead" , "" , "The number of bytes that can be prefetched for sequential reads." )
2018-03-02 17:39:42 +01:00
flags . DurationVarP ( flagSet , & AttrTimeout , "attr-timeout" , "" , AttrTimeout , "Time for which file/directory attributes are cached." )
2018-01-12 17:30:54 +01:00
flags . StringArrayVarP ( flagSet , & ExtraOptions , "option" , "o" , [ ] string { } , "Option for libfuse/WinFsp. Repeat if required." )
flags . StringArrayVarP ( flagSet , & ExtraFlags , "fuse-flag" , "" , [ ] string { } , "Flags or arguments to be passed direct to libfuse/WinFsp. Repeat if required." )
2018-03-02 14:30:04 +01:00
flags . BoolVarP ( flagSet , & Daemon , "daemon" , "" , Daemon , "Run mount as a daemon (background mode)." )
2018-05-03 10:34:07 +02:00
flags . StringVarP ( flagSet , & VolumeName , "volname" , "" , VolumeName , "Set the volume name (not supported by all OSes)." )
2018-07-18 17:21:35 +02:00
flags . DurationVarP ( flagSet , & DaemonTimeout , "daemon-timeout" , "" , DaemonTimeout , "Time limit for rclone to respond to kernel (not supported by all OSes)." )
2017-06-19 14:44:49 +02:00
2018-05-03 10:45:24 +02:00
if runtime . GOOS == "darwin" {
flags . BoolVarP ( flagSet , & NoAppleDouble , "noappledouble" , "" , NoAppleDouble , "Sets the OSXFUSE option noappledouble." )
flags . BoolVarP ( flagSet , & NoAppleXattr , "noapplexattr" , "" , NoAppleXattr , "Sets the OSXFUSE option noapplexattr." )
}
2017-10-24 22:06:06 +02:00
// Add in the generic flags
2018-01-12 17:30:54 +01:00
vfsflags . AddFlags ( flagSet )
2017-10-24 22:06:06 +02:00
2017-06-19 14:44:49 +02:00
return commandDefintion
}
2018-06-26 10:26:34 +02:00
// ClipBlocks clips the blocks pointed to to the OS max
func ClipBlocks ( b * uint64 ) {
var max uint64
switch runtime . GOOS {
case "windows" :
2019-01-25 18:31:56 +01:00
if runtime . GOARCH == "386" {
max = ( 1 << 32 ) - 1
} else {
max = ( 1 << 43 ) - 1
}
2018-06-26 10:26:34 +02:00
case "darwin" :
// OSX FUSE only supports 32 bit number of blocks
// https://github.com/osxfuse/osxfuse/issues/396
max = ( 1 << 32 ) - 1
default :
// no clipping
return
}
if * b > max {
* b = max
}
}