rclone/cmd/cmount/mount.go
albertony e92cb9e8f8 mount: more user friendly mounting as network drive on windows
Add --network-mode option to activate mounting as network drive without having to set volume prefix.
Add support for automatic drive letter assignment (not specific to network drive mounting).
Allow full network share unc path in --volname, which will also implicitely activate network drive mounting.
Allow full network share unc path as mountpoint, which will also implicitely activate network drive mounting, and the specified path will be used as volume prefix and the remote will be mounted on an automatically assigned drive letter instead.
2020-12-28 13:59:34 +00:00

236 lines
6.2 KiB
Go

// Package cmount implements a FUSE mounting system for rclone remotes.
//
// This uses the cgo based cgofuse library
// +build cmount
// +build cgo
// +build linux darwin freebsd windows
package cmount
import (
"fmt"
"os"
"runtime"
"strings"
"time"
"github.com/billziss-gh/cgofuse/fuse"
"github.com/pkg/errors"
"github.com/rclone/rclone/cmd/mountlib"
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/lib/atexit"
"github.com/rclone/rclone/vfs"
)
func init() {
name := "cmount"
cmountOnly := runtime.GOOS == "windows" || runtime.GOOS == "darwin"
if cmountOnly {
name = "mount"
}
cmd := mountlib.NewMountCommand(name, false, mount)
if cmountOnly {
cmd.Aliases = append(cmd.Aliases, "cmount")
}
mountlib.AddRc("cmount", mount)
}
// mountOptions configures the options from the command line flags
func mountOptions(VFS *vfs.VFS, device string, mountpoint string, opt *mountlib.Options) (options []string) {
// Options
options = []string{
"-o", "fsname=" + device,
"-o", "subtype=rclone",
"-o", fmt.Sprintf("max_readahead=%d", opt.MaxReadAhead),
"-o", fmt.Sprintf("attr_timeout=%g", opt.AttrTimeout.Seconds()),
// 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",
}
if opt.DebugFUSE {
options = append(options, "-o", "debug")
}
// OSX options
if runtime.GOOS == "darwin" {
if opt.NoAppleDouble {
options = append(options, "-o", "noappledouble")
}
if opt.NoAppleXattr {
options = append(options, "-o", "noapplexattr")
}
}
// 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
}
// Windows options
if runtime.GOOS == "windows" {
// These cause WinFsp to mean the current user
if !hasOption("uid") {
options = append(options, "-o", "uid=-1")
}
if !hasOption("gid") {
options = append(options, "-o", "gid=-1")
}
options = append(options, "--FileSystemName=rclone")
if opt.VolumeName != "" {
if opt.NetworkMode {
options = append(options, "--VolumePrefix="+opt.VolumeName)
} else {
options = append(options, "-o", "volname="+opt.VolumeName)
}
}
} else if runtime.GOOS == "darwin" {
if opt.VolumeName != "" {
options = append(options, "-o", "volname="+opt.VolumeName)
}
}
if opt.AllowNonEmpty {
options = append(options, "-o", "nonempty")
}
if opt.AllowOther {
options = append(options, "-o", "allow_other")
}
if opt.AllowRoot {
options = append(options, "-o", "allow_root")
}
if opt.DefaultPermissions {
options = append(options, "-o", "default_permissions")
}
if VFS.Opt.ReadOnly {
options = append(options, "-o", "ro")
}
if opt.WritebackCache {
// FIXME? options = append(options, "-o", WritebackCache())
}
if opt.DaemonTimeout != 0 {
options = append(options, "-o", fmt.Sprintf("daemon_timeout=%d", int(opt.DaemonTimeout.Seconds())))
}
for _, option := range opt.ExtraOptions {
options = append(options, "-o", option)
}
for _, option := range opt.ExtraFlags {
options = append(options, option)
}
return options
}
// 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
}
// 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.
func mount(VFS *vfs.VFS, mountPath string, opt *mountlib.Options) (<-chan error, func() error, error) {
// Get mountpoint using OS specific logic
mountpoint, err := getMountpoint(mountPath, opt)
if err != nil {
return nil, nil, err
}
fs.Debugf(nil, "Mounting on %q (%q)", mountpoint, opt.VolumeName)
// Create underlying FS
f := VFS.Fs()
fsys := NewFS(VFS)
host := fuse.NewFileSystemHost(fsys)
host.SetCapReaddirPlus(true) // only works on Windows
host.SetCapCaseInsensitive(f.Features().CaseInsensitive)
// Create options
options := mountOptions(VFS, f.Name()+":"+f.Root(), mountpoint, opt)
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() {
defer func() {
if r := recover(); r != nil {
errChan <- errors.Errorf("mount failed: %v", r)
}
}()
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 {
// Shutdown the VFS
fsys.VFS.Shutdown()
var umountOK bool
if atexit.Signalled() {
// If we have received a signal then FUSE will be shutting down already
fs.Debugf(nil, "Not calling host.Unmount as signal received")
umountOK = true
} else {
fs.Debugf(nil, "Calling host.Unmount")
umountOK = host.Unmount()
}
if umountOK {
fs.Debugf(nil, "Unmounted successfully")
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)
}
}
return nil
}
fs.Debugf(nil, "host.Unmount failed")
return errors.New("host unmount failed")
}
// 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")
return nil, nil, err
case <-fsys.ready:
}
// 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" {
if !waitFor(func() bool {
_, err := os.Stat(mountpoint)
return err == nil
}) {
fs.Errorf(nil, "mountpoint %q didn't became available on mount - continuing anyway", mountpoint)
}
}
return errChan, unmount, nil
}