2020-05-20 12:39:20 +02:00
|
|
|
// Package cmount implements a FUSE mounting system for rclone remotes.
|
2017-05-02 23:36:11 +02:00
|
|
|
//
|
|
|
|
// This uses the cgo based cgofuse library
|
|
|
|
|
2017-05-19 16:46:13 +02:00
|
|
|
// +build cmount
|
2017-05-02 23:36:11 +02:00
|
|
|
// +build cgo
|
|
|
|
// +build linux darwin freebsd windows
|
|
|
|
|
|
|
|
package cmount
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"runtime"
|
2020-03-07 17:32:37 +01:00
|
|
|
"strings"
|
2017-05-02 23:36:11 +02:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/billziss-gh/cgofuse/fuse"
|
|
|
|
"github.com/pkg/errors"
|
2019-07-28 19:47:38 +02:00
|
|
|
"github.com/rclone/rclone/cmd/mountlib"
|
|
|
|
"github.com/rclone/rclone/fs"
|
|
|
|
"github.com/rclone/rclone/vfs"
|
2017-05-02 23:36:11 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
2017-06-19 14:44:49 +02:00
|
|
|
name := "cmount"
|
2020-10-29 13:19:48 +01:00
|
|
|
cmountOnly := runtime.GOOS == "windows" || runtime.GOOS == "darwin"
|
|
|
|
if cmountOnly {
|
2017-06-19 14:44:49 +02:00
|
|
|
name = "mount"
|
|
|
|
}
|
2020-10-29 13:19:48 +01:00
|
|
|
cmd := mountlib.NewMountCommand(name, false, mount)
|
|
|
|
if cmountOnly {
|
|
|
|
cmd.Aliases = append(cmd.Aliases, "cmount")
|
|
|
|
}
|
2020-04-25 07:03:07 +02:00
|
|
|
mountlib.AddRc("cmount", mount)
|
2017-05-02 23:36:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// mountOptions configures the options from the command line flags
|
2020-07-23 18:17:01 +02:00
|
|
|
func mountOptions(VFS *vfs.VFS, device string, mountpoint string, opt *mountlib.Options) (options []string) {
|
2017-05-02 23:36:11 +02:00
|
|
|
// Options
|
|
|
|
options = []string{
|
|
|
|
"-o", "fsname=" + device,
|
|
|
|
"-o", "subtype=rclone",
|
2020-07-23 18:17:01 +02:00
|
|
|
"-o", fmt.Sprintf("max_readahead=%d", opt.MaxReadAhead),
|
|
|
|
"-o", fmt.Sprintf("attr_timeout=%g", opt.AttrTimeout.Seconds()),
|
2017-11-13 18:18:47 +01:00
|
|
|
// This causes FUSE to supply O_TRUNC with the Open
|
|
|
|
// call which is more efficient for cmount. However
|
|
|
|
// it does not work with cgofuse on Windows with
|
|
|
|
// WinFSP so cmount must work with or without it.
|
|
|
|
"-o", "atomic_o_trunc",
|
2017-05-02 23:36:11 +02:00
|
|
|
}
|
2020-07-23 18:17:01 +02:00
|
|
|
if opt.DebugFUSE {
|
2017-05-02 23:36:11 +02:00
|
|
|
options = append(options, "-o", "debug")
|
|
|
|
}
|
|
|
|
|
|
|
|
// OSX options
|
|
|
|
if runtime.GOOS == "darwin" {
|
2020-07-23 18:17:01 +02:00
|
|
|
if opt.NoAppleDouble {
|
2018-05-03 10:45:24 +02:00
|
|
|
options = append(options, "-o", "noappledouble")
|
|
|
|
}
|
2020-07-23 18:17:01 +02:00
|
|
|
if opt.NoAppleXattr {
|
2018-05-03 10:45:24 +02:00
|
|
|
options = append(options, "-o", "noapplexattr")
|
|
|
|
}
|
2017-05-02 23:36:11 +02:00
|
|
|
}
|
|
|
|
|
2020-03-07 17:32:37 +01:00
|
|
|
// determine if ExtraOptions already has an opt in
|
|
|
|
hasOption := func(optionName string) bool {
|
|
|
|
optionName += "="
|
|
|
|
for _, option := range opt.ExtraOptions {
|
|
|
|
if strings.HasPrefix(option, optionName) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2017-05-09 15:03:37 +02:00
|
|
|
// Windows options
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
// These cause WinFsp to mean the current user
|
2020-03-07 17:32:37 +01:00
|
|
|
if !hasOption("uid") {
|
|
|
|
options = append(options, "-o", "uid=-1")
|
|
|
|
}
|
|
|
|
if !hasOption("gid") {
|
|
|
|
options = append(options, "-o", "gid=-1")
|
|
|
|
}
|
2017-05-10 10:20:09 +02:00
|
|
|
options = append(options, "--FileSystemName=rclone")
|
2017-05-09 15:03:37 +02:00
|
|
|
}
|
|
|
|
|
2018-11-22 21:41:05 +01:00
|
|
|
if runtime.GOOS == "darwin" || runtime.GOOS == "windows" {
|
2020-07-23 18:17:01 +02:00
|
|
|
if opt.VolumeName != "" {
|
|
|
|
options = append(options, "-o", "volname="+opt.VolumeName)
|
2018-11-22 21:41:05 +01:00
|
|
|
}
|
2018-10-21 12:05:20 +02:00
|
|
|
}
|
2020-07-23 18:17:01 +02:00
|
|
|
if opt.AllowNonEmpty {
|
2017-05-02 23:36:11 +02:00
|
|
|
options = append(options, "-o", "nonempty")
|
|
|
|
}
|
2020-07-23 18:17:01 +02:00
|
|
|
if opt.AllowOther {
|
2017-05-02 23:36:11 +02:00
|
|
|
options = append(options, "-o", "allow_other")
|
|
|
|
}
|
2020-07-23 18:17:01 +02:00
|
|
|
if opt.AllowRoot {
|
2017-05-02 23:36:11 +02:00
|
|
|
options = append(options, "-o", "allow_root")
|
|
|
|
}
|
2020-07-23 18:17:01 +02:00
|
|
|
if opt.DefaultPermissions {
|
2017-05-02 23:36:11 +02:00
|
|
|
options = append(options, "-o", "default_permissions")
|
|
|
|
}
|
2020-07-22 18:58:49 +02:00
|
|
|
if VFS.Opt.ReadOnly {
|
2017-05-02 23:36:11 +02:00
|
|
|
options = append(options, "-o", "ro")
|
|
|
|
}
|
2020-07-23 18:17:01 +02:00
|
|
|
if opt.WritebackCache {
|
2017-05-02 23:36:11 +02:00
|
|
|
// FIXME? options = append(options, "-o", WritebackCache())
|
|
|
|
}
|
2020-07-23 18:17:01 +02:00
|
|
|
if opt.DaemonTimeout != 0 {
|
|
|
|
options = append(options, "-o", fmt.Sprintf("daemon_timeout=%d", int(opt.DaemonTimeout.Seconds())))
|
2018-07-18 17:21:35 +02:00
|
|
|
}
|
2020-07-23 18:17:01 +02:00
|
|
|
for _, option := range opt.ExtraOptions {
|
2017-05-09 15:24:07 +02:00
|
|
|
options = append(options, "-o", option)
|
|
|
|
}
|
2020-07-23 18:17:01 +02:00
|
|
|
for _, option := range opt.ExtraFlags {
|
2017-05-10 10:13:46 +02:00
|
|
|
options = append(options, option)
|
|
|
|
}
|
2017-05-02 23:36:11 +02:00
|
|
|
return options
|
|
|
|
}
|
|
|
|
|
2017-11-17 11:31:23 +01:00
|
|
|
// waitFor runs fn() until it returns true or the timeout expires
|
|
|
|
func waitFor(fn func() bool) (ok bool) {
|
|
|
|
const totalWait = 10 * time.Second
|
|
|
|
const individualWait = 10 * time.Millisecond
|
|
|
|
for i := 0; i < int(totalWait/individualWait); i++ {
|
|
|
|
ok = fn()
|
|
|
|
if ok {
|
|
|
|
return ok
|
|
|
|
}
|
|
|
|
time.Sleep(individualWait)
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2017-05-02 23:36:11 +02:00
|
|
|
// mount the file system
|
|
|
|
//
|
|
|
|
// The mount point will be ready when this returns.
|
|
|
|
//
|
|
|
|
// returns an error, and an error channel for the serve process to
|
|
|
|
// report an error when fusermount is called.
|
2020-07-23 18:17:01 +02:00
|
|
|
func mount(VFS *vfs.VFS, mountpoint string, opt *mountlib.Options) (<-chan error, func() error, error) {
|
2020-07-22 18:58:49 +02:00
|
|
|
f := VFS.Fs()
|
2017-05-02 23:36:11 +02:00
|
|
|
fs.Debugf(f, "Mounting on %q", mountpoint)
|
|
|
|
|
2019-04-30 14:06:24 +02:00
|
|
|
// Check the mountpoint - in Windows the mountpoint mustn't exist before the mount
|
2017-05-07 21:48:17 +02:00
|
|
|
if runtime.GOOS != "windows" {
|
|
|
|
fi, err := os.Stat(mountpoint)
|
|
|
|
if err != nil {
|
2020-07-22 18:58:49 +02:00
|
|
|
return nil, nil, errors.Wrap(err, "mountpoint")
|
2017-05-07 21:48:17 +02:00
|
|
|
}
|
|
|
|
if !fi.IsDir() {
|
2020-07-22 18:58:49 +02:00
|
|
|
return nil, nil, errors.New("mountpoint is not a directory")
|
2017-05-07 21:48:17 +02:00
|
|
|
}
|
2017-05-02 23:36:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Create underlying FS
|
2020-07-22 18:58:49 +02:00
|
|
|
fsys := NewFS(VFS)
|
2017-05-02 23:36:11 +02:00
|
|
|
host := fuse.NewFileSystemHost(fsys)
|
2020-07-05 12:28:30 +02:00
|
|
|
host.SetCapReaddirPlus(true) // only works on Windows
|
2020-05-01 19:35:32 +02:00
|
|
|
host.SetCapCaseInsensitive(f.Features().CaseInsensitive)
|
2017-05-02 23:36:11 +02:00
|
|
|
|
|
|
|
// Create options
|
2020-07-23 18:17:01 +02:00
|
|
|
options := mountOptions(VFS, f.Name()+":"+f.Root(), mountpoint, opt)
|
2017-05-02 23:36:11 +02:00
|
|
|
fs.Debugf(f, "Mounting with options: %q", options)
|
|
|
|
|
|
|
|
// Serve the mount point in the background returning error to errChan
|
|
|
|
errChan := make(chan error, 1)
|
|
|
|
go func() {
|
2020-07-04 19:54:21 +02:00
|
|
|
defer func() {
|
|
|
|
if r := recover(); r != nil {
|
|
|
|
errChan <- errors.Errorf("mount failed: %v", r)
|
|
|
|
}
|
|
|
|
}()
|
2017-05-02 23:36:11 +02:00
|
|
|
var err error
|
|
|
|
ok := host.Mount(mountpoint, options)
|
|
|
|
if !ok {
|
|
|
|
err = errors.New("mount failed")
|
|
|
|
fs.Errorf(f, "Mount failed")
|
|
|
|
}
|
|
|
|
errChan <- err
|
|
|
|
}()
|
|
|
|
|
|
|
|
// unmount
|
|
|
|
unmount := func() error {
|
2017-11-07 19:03:23 +01:00
|
|
|
// Shutdown the VFS
|
|
|
|
fsys.VFS.Shutdown()
|
2017-05-02 23:36:11 +02:00
|
|
|
fs.Debugf(nil, "Calling host.Unmount")
|
|
|
|
if host.Unmount() {
|
|
|
|
fs.Debugf(nil, "host.Unmount succeeded")
|
2017-11-17 11:31:23 +01:00
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
if !waitFor(func() bool {
|
|
|
|
_, err := os.Stat(mountpoint)
|
|
|
|
return err != nil
|
|
|
|
}) {
|
|
|
|
fs.Errorf(nil, "mountpoint %q didn't disappear after unmount - continuing anyway", mountpoint)
|
|
|
|
}
|
|
|
|
}
|
2017-05-02 23:36:11 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
fs.Debugf(nil, "host.Unmount failed")
|
|
|
|
return errors.New("host unmount failed")
|
|
|
|
}
|
|
|
|
|
2017-05-10 10:34:01 +02:00
|
|
|
// Wait for the filesystem to become ready, checking the file
|
|
|
|
// system didn't blow up before starting
|
|
|
|
select {
|
|
|
|
case err := <-errChan:
|
|
|
|
err = errors.Wrap(err, "mount stopped before calling Init")
|
2020-07-22 18:58:49 +02:00
|
|
|
return nil, nil, err
|
2017-05-10 10:34:01 +02:00
|
|
|
case <-fsys.ready:
|
|
|
|
}
|
|
|
|
|
2017-05-10 12:16:53 +02:00
|
|
|
// Wait for the mount point to be available on Windows
|
|
|
|
// On Windows the Init signal comes slightly before the mount is ready
|
|
|
|
if runtime.GOOS == "windows" {
|
2017-11-17 11:31:23 +01:00
|
|
|
if !waitFor(func() bool {
|
2017-05-10 12:16:53 +02:00
|
|
|
_, err := os.Stat(mountpoint)
|
2017-11-17 11:31:23 +01:00
|
|
|
return err == nil
|
|
|
|
}) {
|
|
|
|
fs.Errorf(nil, "mountpoint %q didn't became available on mount - continuing anyway", mountpoint)
|
2017-05-10 12:16:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-22 18:58:49 +02:00
|
|
|
return errChan, unmount, nil
|
2017-05-02 23:36:11 +02:00
|
|
|
}
|