2022-04-24 11:27:28 +02:00
|
|
|
//go:build cmount && ((linux && cgo) || (darwin && cgo) || (freebsd && cgo) || windows)
|
2017-05-02 23:36:11 +02:00
|
|
|
|
2022-08-28 13:21:57 +02:00
|
|
|
// Package cmount implements a FUSE mounting system for rclone remotes.
|
|
|
|
//
|
|
|
|
// This uses the cgo based cgofuse library
|
2017-05-02 23:36:11 +02:00
|
|
|
package cmount
|
|
|
|
|
|
|
|
import (
|
2021-11-04 11:12:57 +01:00
|
|
|
"errors"
|
2017-05-02 23:36:11 +02:00
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"runtime"
|
2021-02-10 17:49:35 +01:00
|
|
|
"strings"
|
2017-05-02 23:36:11 +02:00
|
|
|
"time"
|
|
|
|
|
2019-07-28 19:47:38 +02:00
|
|
|
"github.com/rclone/rclone/cmd/mountlib"
|
|
|
|
"github.com/rclone/rclone/fs"
|
2020-11-27 11:50:36 +01:00
|
|
|
"github.com/rclone/rclone/lib/atexit"
|
2021-04-03 13:13:29 +02:00
|
|
|
"github.com/rclone/rclone/lib/buildinfo"
|
2019-07-28 19:47:38 +02:00
|
|
|
"github.com/rclone/rclone/vfs"
|
2022-04-24 11:27:28 +02:00
|
|
|
"github.com/winfsp/cgofuse/fuse"
|
2017-05-02 23:36:11 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
2017-06-19 14:44:49 +02:00
|
|
|
name := "cmount"
|
2023-10-29 15:15:34 +01:00
|
|
|
cmountOnly := runtime.GOOS != "linux" // rclone mount only works for linux
|
2020-10-29 13:19:48 +01:00
|
|
|
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)
|
2021-04-03 13:13:29 +02:00
|
|
|
buildinfo.Tags = append(buildinfo.Tags, "cmount")
|
2017-05-02 23:36:11 +02:00
|
|
|
}
|
|
|
|
|
2021-02-10 17:49:35 +01:00
|
|
|
// Find the option string in the current options
|
|
|
|
func findOption(name string, options []string) (found bool) {
|
|
|
|
for _, option := range options {
|
|
|
|
if option == "-o" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if strings.Contains(option, name) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
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{
|
2020-07-23 18:17:01 +02:00
|
|
|
"-o", fmt.Sprintf("attr_timeout=%g", opt.AttrTimeout.Seconds()),
|
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")
|
|
|
|
}
|
|
|
|
|
2017-05-09 15:03:37 +02:00
|
|
|
if runtime.GOOS == "windows" {
|
2020-11-15 01:29:10 +01:00
|
|
|
options = append(options, "-o", "uid=-1")
|
|
|
|
options = append(options, "-o", "gid=-1")
|
2017-05-10 10:20:09 +02:00
|
|
|
options = append(options, "--FileSystemName=rclone")
|
2020-11-06 14:21:38 +01:00
|
|
|
if opt.VolumeName != "" {
|
|
|
|
if opt.NetworkMode {
|
|
|
|
options = append(options, "--VolumePrefix="+opt.VolumeName)
|
|
|
|
} else {
|
|
|
|
options = append(options, "-o", "volname="+opt.VolumeName)
|
|
|
|
}
|
|
|
|
}
|
2020-11-11 00:17:25 +01:00
|
|
|
} else {
|
|
|
|
options = append(options, "-o", "fsname="+device)
|
|
|
|
options = append(options, "-o", "subtype=rclone")
|
|
|
|
options = append(options, "-o", fmt.Sprintf("max_readahead=%d", opt.MaxReadAhead))
|
|
|
|
// 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.
|
|
|
|
options = append(options, "-o", "atomic_o_trunc")
|
|
|
|
if opt.DaemonTimeout != 0 {
|
|
|
|
options = append(options, "-o", fmt.Sprintf("daemon_timeout=%d", int(opt.DaemonTimeout.Seconds())))
|
|
|
|
}
|
|
|
|
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 runtime.GOOS == "darwin" {
|
|
|
|
if opt.VolumeName != "" {
|
|
|
|
options = append(options, "-o", "volname="+opt.VolumeName)
|
|
|
|
}
|
|
|
|
if opt.NoAppleDouble {
|
|
|
|
options = append(options, "-o", "noappledouble")
|
|
|
|
}
|
|
|
|
if opt.NoAppleXattr {
|
|
|
|
options = append(options, "-o", "noapplexattr")
|
|
|
|
}
|
2018-11-22 21:41:05 +01:00
|
|
|
}
|
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-11-06 14:21:38 +01:00
|
|
|
func mount(VFS *vfs.VFS, mountPath string, opt *mountlib.Options) (<-chan error, func() error, error) {
|
|
|
|
// Get mountpoint using OS specific logic
|
2022-06-11 16:18:48 +02:00
|
|
|
f := VFS.Fs()
|
|
|
|
mountpoint, err := getMountpoint(f, mountPath, opt)
|
2020-11-06 14:21:38 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
2017-05-02 23:36:11 +02:00
|
|
|
}
|
2020-11-06 14:21:38 +01:00
|
|
|
fs.Debugf(nil, "Mounting on %q (%q)", mountpoint, opt.VolumeName)
|
2017-05-02 23:36:11 +02:00
|
|
|
|
|
|
|
// Create underlying FS
|
2024-03-04 12:37:18 +01:00
|
|
|
fsys := NewFS(VFS, opt)
|
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
|
2023-03-13 12:28:26 +01:00
|
|
|
if opt.CaseInsensitive.Valid {
|
|
|
|
host.SetCapCaseInsensitive(opt.CaseInsensitive.Value)
|
|
|
|
} else {
|
|
|
|
host.SetCapCaseInsensitive(f.Features().CaseInsensitive)
|
|
|
|
}
|
2017-05-02 23:36:11 +02:00
|
|
|
|
|
|
|
// Create options
|
2022-02-09 12:56:43 +01:00
|
|
|
options := mountOptions(VFS, opt.DeviceName, 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 {
|
2021-11-04 11:12:57 +01:00
|
|
|
errChan <- fmt.Errorf("mount failed: %v", r)
|
2020-07-04 19:54:21 +02:00
|
|
|
}
|
|
|
|
}()
|
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()
|
2020-11-27 11:50:36 +01:00
|
|
|
var umountOK bool
|
2023-08-18 16:44:23 +02:00
|
|
|
if fsys.destroyed.Load() != 0 {
|
2021-01-28 19:17:47 +01:00
|
|
|
fs.Debugf(nil, "Not calling host.Unmount as mount already Destroyed")
|
|
|
|
umountOK = true
|
|
|
|
} else if atexit.Signalled() {
|
2020-11-27 11:50:36 +01:00
|
|
|
// 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")
|
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:
|
2021-11-04 11:12:57 +01:00
|
|
|
err = fmt.Errorf("mount stopped before calling Init: %w", err)
|
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
|
|
|
}
|