github.com/ncw/rclone@v1.48.1-0.20190724201158-a35aa1360e3e/cmd/mountlib/mount.go (about)

     1  package mountlib
     2  
     3  import (
     4  	"io"
     5  	"log"
     6  	"os"
     7  	"path/filepath"
     8  	"runtime"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/ncw/rclone/cmd"
    13  	"github.com/ncw/rclone/fs"
    14  	"github.com/ncw/rclone/fs/config"
    15  	"github.com/ncw/rclone/fs/config/flags"
    16  	"github.com/ncw/rclone/vfs"
    17  	"github.com/ncw/rclone/vfs/vfsflags"
    18  	"github.com/pkg/errors"
    19  	"github.com/spf13/cobra"
    20  )
    21  
    22  // Options set by command line flags
    23  var (
    24  	DebugFUSE                        = false
    25  	AllowNonEmpty                    = false
    26  	AllowRoot                        = false
    27  	AllowOther                       = false
    28  	DefaultPermissions               = false
    29  	WritebackCache                   = false
    30  	Daemon                           = false
    31  	MaxReadAhead       fs.SizeSuffix = 128 * 1024
    32  	ExtraOptions       []string
    33  	ExtraFlags         []string
    34  	AttrTimeout        = 1 * time.Second // how long the kernel caches attribute for
    35  	VolumeName         string
    36  	NoAppleDouble      = true        // use noappledouble by default
    37  	NoAppleXattr       = false       // do not use noapplexattr by default
    38  	DaemonTimeout      time.Duration // OSXFUSE only
    39  )
    40  
    41  func init() {
    42  	// DaemonTimeout defaults to non zero for macOS and freebsd
    43  	if runtime.GOOS == "darwin" || runtime.GOOS == "freebsd" {
    44  		DaemonTimeout = 15 * time.Minute
    45  	}
    46  }
    47  
    48  // Check is folder is empty
    49  func checkMountEmpty(mountpoint string) error {
    50  	fp, fpErr := os.Open(mountpoint)
    51  
    52  	if fpErr != nil {
    53  		return errors.Wrap(fpErr, "Can not open: "+mountpoint)
    54  	}
    55  	defer fs.CheckClose(fp, &fpErr)
    56  
    57  	_, fpErr = fp.Readdirnames(1)
    58  
    59  	// directory is not empty
    60  	if fpErr != io.EOF {
    61  		var e error
    62  		var errorMsg = "Directory is not empty: " + mountpoint + " If you want to mount it anyway use: --allow-non-empty option"
    63  		if fpErr == nil {
    64  			e = errors.New(errorMsg)
    65  		} else {
    66  			e = errors.Wrap(fpErr, errorMsg)
    67  		}
    68  		return e
    69  	}
    70  	return nil
    71  }
    72  
    73  // Check the root doesn't overlap the mountpoint
    74  func checkMountpointOverlap(root, mountpoint string) error {
    75  	abs := func(x string) string {
    76  		if absX, err := filepath.EvalSymlinks(x); err == nil {
    77  			x = absX
    78  		}
    79  		if absX, err := filepath.Abs(x); err == nil {
    80  			x = absX
    81  		}
    82  		x = filepath.ToSlash(x)
    83  		if !strings.HasSuffix(x, "/") {
    84  			x += "/"
    85  		}
    86  		return x
    87  	}
    88  	rootAbs, mountpointAbs := abs(root), abs(mountpoint)
    89  	if strings.HasPrefix(rootAbs, mountpointAbs) || strings.HasPrefix(mountpointAbs, rootAbs) {
    90  		return errors.Errorf("mount point %q and directory to be mounted %q mustn't overlap", mountpoint, root)
    91  	}
    92  	return nil
    93  }
    94  
    95  // NewMountCommand makes a mount command with the given name and Mount function
    96  func NewMountCommand(commandName string, Mount func(f fs.Fs, mountpoint string) error) *cobra.Command {
    97  	var commandDefintion = &cobra.Command{
    98  		Use:   commandName + " remote:path /path/to/mountpoint",
    99  		Short: `Mount the remote as file system on a mountpoint.`,
   100  		Long: `
   101  rclone ` + commandName + ` allows Linux, FreeBSD, macOS and Windows to
   102  mount any of Rclone's cloud storage systems as a file system with
   103  FUSE.
   104  
   105  First set up your remote using ` + "`rclone config`" + `.  Check it works with ` + "`rclone ls`" + ` etc.
   106  
   107  Start the mount like this
   108  
   109      rclone ` + commandName + ` remote:path/to/files /path/to/local/mount
   110  
   111  Or on Windows like this where X: is an unused drive letter
   112  
   113      rclone ` + commandName + ` remote:path/to/files X:
   114  
   115  When the program ends, either via Ctrl+C or receiving a SIGINT or SIGTERM signal,
   116  the mount is automatically stopped.
   117  
   118  The umount operation can fail, for example when the mountpoint is busy.
   119  When that happens, it is the user's responsibility to stop the mount manually with
   120  
   121      # Linux
   122      fusermount -u /path/to/local/mount
   123      # OS X
   124      umount /path/to/local/mount
   125  
   126  ### Installing on Windows
   127  
   128  To run rclone ` + commandName + ` on Windows, you will need to
   129  download and install [WinFsp](http://www.secfs.net/winfsp/).
   130  
   131  WinFsp is an [open source](https://github.com/billziss-gh/winfsp)
   132  Windows File System Proxy which makes it easy to write user space file
   133  systems for Windows.  It provides a FUSE emulation layer which rclone
   134  uses combination with
   135  [cgofuse](https://github.com/billziss-gh/cgofuse).  Both of these
   136  packages are by Bill Zissimopoulos who was very helpful during the
   137  implementation of rclone ` + commandName + ` for Windows.
   138  
   139  #### Windows caveats
   140  
   141  Note that drives created as Administrator are not visible by other
   142  accounts (including the account that was elevated as
   143  Administrator). So if you start a Windows drive from an Administrative
   144  Command Prompt and then try to access the same drive from Explorer
   145  (which does not run as Administrator), you will not be able to see the
   146  new drive.
   147  
   148  The easiest way around this is to start the drive from a normal
   149  command prompt. It is also possible to start a drive from the SYSTEM
   150  account (using [the WinFsp.Launcher
   151  infrastructure](https://github.com/billziss-gh/winfsp/wiki/WinFsp-Service-Architecture))
   152  which creates drives accessible for everyone on the system or
   153  alternatively using [the nssm service manager](https://nssm.cc/usage).
   154  
   155  ### Limitations
   156  
   157  Without the use of "--vfs-cache-mode" this can only write files
   158  sequentially, it can only seek when reading.  This means that many
   159  applications won't work with their files on an rclone mount without
   160  "--vfs-cache-mode writes" or "--vfs-cache-mode full".  See the [File
   161  Caching](#file-caching) section for more info.
   162  
   163  The bucket based remotes (eg Swift, S3, Google Compute Storage, B2,
   164  Hubic) won't work from the root - you will need to specify a bucket,
   165  or a path within the bucket.  So ` + "`swift:`" + ` won't work whereas
   166  ` + "`swift:bucket`" + ` will as will ` + "`swift:bucket/path`" + `.
   167  None of these support the concept of directories, so empty
   168  directories will have a tendency to disappear once they fall out of
   169  the directory cache.
   170  
   171  Only supported on Linux, FreeBSD, OS X and Windows at the moment.
   172  
   173  ### rclone ` + commandName + ` vs rclone sync/copy
   174  
   175  File systems expect things to be 100% reliable, whereas cloud storage
   176  systems are a long way from 100% reliable. The rclone sync/copy
   177  commands cope with this with lots of retries.  However rclone ` + commandName + `
   178  can't use retries in the same way without making local copies of the
   179  uploads. Look at the [file caching](#file-caching)
   180  for solutions to make ` + commandName + ` more reliable.
   181  
   182  ### Attribute caching
   183  
   184  You can use the flag --attr-timeout to set the time the kernel caches
   185  the attributes (size, modification time etc) for directory entries.
   186  
   187  The default is "1s" which caches files just long enough to avoid
   188  too many callbacks to rclone from the kernel.
   189  
   190  In theory 0s should be the correct value for filesystems which can
   191  change outside the control of the kernel. However this causes quite a
   192  few problems such as
   193  [rclone using too much memory](https://github.com/ncw/rclone/issues/2157),
   194  [rclone not serving files to samba](https://forum.rclone.org/t/rclone-1-39-vs-1-40-mount-issue/5112)
   195  and [excessive time listing directories](https://github.com/ncw/rclone/issues/2095#issuecomment-371141147).
   196  
   197  The kernel can cache the info about a file for the time given by
   198  "--attr-timeout". You may see corruption if the remote file changes
   199  length during this window.  It will show up as either a truncated file
   200  or a file with garbage on the end.  With "--attr-timeout 1s" this is
   201  very unlikely but not impossible.  The higher you set "--attr-timeout"
   202  the more likely it is.  The default setting of "1s" is the lowest
   203  setting which mitigates the problems above.
   204  
   205  If you set it higher ('10s' or '1m' say) then the kernel will call
   206  back to rclone less often making it more efficient, however there is
   207  more chance of the corruption issue above.
   208  
   209  If files don't change on the remote outside of the control of rclone
   210  then there is no chance of corruption.
   211  
   212  This is the same as setting the attr_timeout option in mount.fuse.
   213  
   214  ### Filters
   215  
   216  Note that all the rclone filters can be used to select a subset of the
   217  files to be visible in the mount.
   218  
   219  ### systemd
   220  
   221  When running rclone ` + commandName + ` as a systemd service, it is possible
   222  to use Type=notify. In this case the service will enter the started state
   223  after the mountpoint has been successfully set up.
   224  Units having the rclone ` + commandName + ` service specified as a requirement
   225  will see all files and folders immediately in this mode.
   226  
   227  ### chunked reading ###
   228  
   229  --vfs-read-chunk-size will enable reading the source objects in parts.
   230  This can reduce the used download quota for some remotes by requesting only chunks
   231  from the remote that are actually read at the cost of an increased number of requests.
   232  
   233  When --vfs-read-chunk-size-limit is also specified and greater than --vfs-read-chunk-size,
   234  the chunk size for each open file will get doubled for each chunk read, until the
   235  specified value is reached. A value of -1 will disable the limit and the chunk size will
   236  grow indefinitely.
   237  
   238  With --vfs-read-chunk-size 100M and --vfs-read-chunk-size-limit 0 the following
   239  parts will be downloaded: 0-100M, 100M-200M, 200M-300M, 300M-400M and so on.
   240  When --vfs-read-chunk-size-limit 500M is specified, the result would be
   241  0-100M, 100M-300M, 300M-700M, 700M-1200M, 1200M-1700M and so on.
   242  
   243  Chunked reading will only work with --vfs-cache-mode < full, as the file will always
   244  be copied to the vfs cache before opening with --vfs-cache-mode full.
   245  ` + vfs.Help,
   246  		Run: func(command *cobra.Command, args []string) {
   247  			cmd.CheckArgs(2, 2, command, args)
   248  
   249  			if Daemon {
   250  				config.PassConfigKeyForDaemonization = true
   251  			}
   252  
   253  			mountpoint := args[1]
   254  			fdst := cmd.NewFsDir(args)
   255  			if fdst.Name() == "" || fdst.Name() == "local" {
   256  				err := checkMountpointOverlap(fdst.Root(), mountpoint)
   257  				if err != nil {
   258  					log.Fatalf("Fatal error: %v", err)
   259  				}
   260  			}
   261  
   262  			// Show stats if the user has specifically requested them
   263  			if cmd.ShowStats() {
   264  				defer cmd.StartStats()()
   265  			}
   266  
   267  			// Skip checkMountEmpty if --allow-non-empty flag is used or if
   268  			// the Operating System is Windows
   269  			if !AllowNonEmpty && runtime.GOOS != "windows" {
   270  				err := checkMountEmpty(mountpoint)
   271  				if err != nil {
   272  					log.Fatalf("Fatal error: %v", err)
   273  				}
   274  			}
   275  
   276  			// Work out the volume name, removing special
   277  			// characters from it if necessary
   278  			if VolumeName == "" {
   279  				VolumeName = fdst.Name() + ":" + fdst.Root()
   280  			}
   281  			VolumeName = strings.Replace(VolumeName, ":", " ", -1)
   282  			VolumeName = strings.Replace(VolumeName, "/", " ", -1)
   283  			VolumeName = strings.TrimSpace(VolumeName)
   284  
   285  			// Start background task if --background is specified
   286  			if Daemon {
   287  				daemonized := startBackgroundMode()
   288  				if daemonized {
   289  					return
   290  				}
   291  			}
   292  
   293  			err := Mount(fdst, mountpoint)
   294  			if err != nil {
   295  				log.Fatalf("Fatal error: %v", err)
   296  			}
   297  		},
   298  	}
   299  
   300  	// Register the command
   301  	cmd.Root.AddCommand(commandDefintion)
   302  
   303  	// Add flags
   304  	flagSet := commandDefintion.Flags()
   305  	flags.BoolVarP(flagSet, &DebugFUSE, "debug-fuse", "", DebugFUSE, "Debug the FUSE internals - needs -v.")
   306  	// mount options
   307  	flags.BoolVarP(flagSet, &AllowNonEmpty, "allow-non-empty", "", AllowNonEmpty, "Allow mounting over a non-empty directory.")
   308  	flags.BoolVarP(flagSet, &AllowRoot, "allow-root", "", AllowRoot, "Allow access to root user.")
   309  	flags.BoolVarP(flagSet, &AllowOther, "allow-other", "", AllowOther, "Allow access to other users.")
   310  	flags.BoolVarP(flagSet, &DefaultPermissions, "default-permissions", "", DefaultPermissions, "Makes kernel enforce access control based on the file mode.")
   311  	flags.BoolVarP(flagSet, &WritebackCache, "write-back-cache", "", WritebackCache, "Makes kernel buffer writes before sending them to rclone. Without this, writethrough caching is used.")
   312  	flags.FVarP(flagSet, &MaxReadAhead, "max-read-ahead", "", "The number of bytes that can be prefetched for sequential reads.")
   313  	flags.DurationVarP(flagSet, &AttrTimeout, "attr-timeout", "", AttrTimeout, "Time for which file/directory attributes are cached.")
   314  	flags.StringArrayVarP(flagSet, &ExtraOptions, "option", "o", []string{}, "Option for libfuse/WinFsp. Repeat if required.")
   315  	flags.StringArrayVarP(flagSet, &ExtraFlags, "fuse-flag", "", []string{}, "Flags or arguments to be passed direct to libfuse/WinFsp. Repeat if required.")
   316  	flags.BoolVarP(flagSet, &Daemon, "daemon", "", Daemon, "Run mount as a daemon (background mode).")
   317  	flags.StringVarP(flagSet, &VolumeName, "volname", "", VolumeName, "Set the volume name (not supported by all OSes).")
   318  	flags.DurationVarP(flagSet, &DaemonTimeout, "daemon-timeout", "", DaemonTimeout, "Time limit for rclone to respond to kernel (not supported by all OSes).")
   319  
   320  	if runtime.GOOS == "darwin" {
   321  		flags.BoolVarP(flagSet, &NoAppleDouble, "noappledouble", "", NoAppleDouble, "Sets the OSXFUSE option noappledouble.")
   322  		flags.BoolVarP(flagSet, &NoAppleXattr, "noapplexattr", "", NoAppleXattr, "Sets the OSXFUSE option noapplexattr.")
   323  	}
   324  
   325  	// Add in the generic flags
   326  	vfsflags.AddFlags(flagSet)
   327  
   328  	return commandDefintion
   329  }
   330  
   331  // ClipBlocks clips the blocks pointed to to the OS max
   332  func ClipBlocks(b *uint64) {
   333  	var max uint64
   334  	switch runtime.GOOS {
   335  	case "windows":
   336  		if runtime.GOARCH == "386" {
   337  			max = (1 << 32) - 1
   338  		} else {
   339  			max = (1 << 43) - 1
   340  		}
   341  	case "darwin":
   342  		// OSX FUSE only supports 32 bit number of blocks
   343  		// https://github.com/osxfuse/osxfuse/issues/396
   344  		max = (1 << 32) - 1
   345  	default:
   346  		// no clipping
   347  		return
   348  	}
   349  	if *b > max {
   350  		*b = max
   351  	}
   352  }