2022-04-24 09:27:28 +00:00
|
|
|
//go:build cmount && ((linux && cgo) || (darwin && cgo) || (freebsd && cgo) || windows)
|
2017-05-19 14:46:13 +00:00
|
|
|
// +build cmount
|
2022-04-24 09:27:28 +00:00
|
|
|
// +build linux,cgo darwin,cgo freebsd,cgo windows
|
2017-05-02 21:36:11 +00:00
|
|
|
|
2022-08-28 11:21:57 +00:00
|
|
|
// Package cmount implements a FUSE mounting system for rclone remotes.
|
|
|
|
//
|
|
|
|
// This uses the cgo based cgofuse library
|
2017-05-02 21:36:11 +00:00
|
|
|
package cmount
|
|
|
|
|
|
|
|
import (
|
2021-11-04 10:12:57 +00:00
|
|
|
"errors"
|
2017-05-02 21:36:11 +00:00
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"runtime"
|
2021-02-10 16:49:35 +00:00
|
|
|
"strings"
|
2017-05-02 21:36:11 +00:00
|
|
|
"time"
|
|
|
|
|
2019-07-28 17:47:38 +00:00
|
|
|
"github.com/rclone/rclone/cmd/mountlib"
|
|
|
|
"github.com/rclone/rclone/fs"
|
2020-11-27 10:50:36 +00:00
|
|
|
"github.com/rclone/rclone/lib/atexit"
|
2021-04-03 11:13:29 +00:00
|
|
|
"github.com/rclone/rclone/lib/buildinfo"
|
2019-07-28 17:47:38 +00:00
|
|
|
"github.com/rclone/rclone/vfs"
|
2022-04-24 09:27:28 +00:00
|
|
|
"github.com/winfsp/cgofuse/fuse"
|
2017-05-02 21:36:11 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
2017-06-19 12:44:49 +00:00
|
|
|
name := "cmount"
|
2021-03-21 12:14:10 +00:00
|
|
|
cmountOnly := ProvidedBy(runtime.GOOS)
|
2020-10-29 12:19:48 +00:00
|
|
|
if cmountOnly {
|
2017-06-19 12:44:49 +00:00
|
|
|
name = "mount"
|
|
|
|
}
|
2020-10-29 12:19:48 +00:00
|
|
|
cmd := mountlib.NewMountCommand(name, false, mount)
|
|
|
|
if cmountOnly {
|
|
|
|
cmd.Aliases = append(cmd.Aliases, "cmount")
|
|
|
|
}
|
2020-04-25 05:03:07 +00:00
|
|
|
mountlib.AddRc("cmount", mount)
|
2021-04-03 11:13:29 +00:00
|
|
|
buildinfo.Tags = append(buildinfo.Tags, "cmount")
|
2017-05-02 21:36:11 +00:00
|
|
|
}
|
|
|
|
|
2021-02-10 16:49:35 +00: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 21:36:11 +00:00
|
|
|
// mountOptions configures the options from the command line flags
|
2020-07-23 16:17:01 +00:00
|
|
|
func mountOptions(VFS *vfs.VFS, device string, mountpoint string, opt *mountlib.Options) (options []string) {
|
2017-05-02 21:36:11 +00:00
|
|
|
// Options
|
|
|
|
options = []string{
|
2020-07-23 16:17:01 +00:00
|
|
|
"-o", fmt.Sprintf("attr_timeout=%g", opt.AttrTimeout.Seconds()),
|
2017-05-02 21:36:11 +00:00
|
|
|
}
|
2020-07-23 16:17:01 +00:00
|
|
|
if opt.DebugFUSE {
|
2017-05-02 21:36:11 +00:00
|
|
|
options = append(options, "-o", "debug")
|
|
|
|
}
|
|
|
|
|
2017-05-09 13:03:37 +00:00
|
|
|
if runtime.GOOS == "windows" {
|
2020-11-15 00:29:10 +00:00
|
|
|
options = append(options, "-o", "uid=-1")
|
|
|
|
options = append(options, "-o", "gid=-1")
|
2017-05-10 08:20:09 +00:00
|
|
|
options = append(options, "--FileSystemName=rclone")
|
2020-11-06 13:21:38 +00:00
|
|
|
if opt.VolumeName != "" {
|
|
|
|
if opt.NetworkMode {
|
|
|
|
options = append(options, "--VolumePrefix="+opt.VolumeName)
|
|
|
|
} else {
|
|
|
|
options = append(options, "-o", "volname="+opt.VolumeName)
|
|
|
|
}
|
|
|
|
}
|
2020-11-10 23:17:25 +00: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 20:41:05 +00:00
|
|
|
}
|
2018-07-18 15:21:35 +00:00
|
|
|
}
|
2020-07-23 16:17:01 +00:00
|
|
|
for _, option := range opt.ExtraOptions {
|
2017-05-09 13:24:07 +00:00
|
|
|
options = append(options, "-o", option)
|
|
|
|
}
|
2020-07-23 16:17:01 +00:00
|
|
|
for _, option := range opt.ExtraFlags {
|
2017-05-10 08:13:46 +00:00
|
|
|
options = append(options, option)
|
|
|
|
}
|
2021-02-10 16:49:35 +00:00
|
|
|
if runtime.GOOS == "darwin" {
|
|
|
|
if !findOption("modules=iconv", options) {
|
|
|
|
iconv := "modules=iconv,from_code=UTF-8,to_code=UTF-8-MAC"
|
|
|
|
options = append(options, "-o", iconv)
|
|
|
|
fs.Debugf(nil, "Adding \"-o %s\" for macOS", iconv)
|
|
|
|
}
|
|
|
|
}
|
2017-05-02 21:36:11 +00:00
|
|
|
return options
|
|
|
|
}
|
|
|
|
|
2017-11-17 10:31:23 +00: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 21:36:11 +00: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 13:21:38 +00: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 14:18:48 +00:00
|
|
|
f := VFS.Fs()
|
|
|
|
mountpoint, err := getMountpoint(f, mountPath, opt)
|
2020-11-06 13:21:38 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
2017-05-02 21:36:11 +00:00
|
|
|
}
|
2020-11-06 13:21:38 +00:00
|
|
|
fs.Debugf(nil, "Mounting on %q (%q)", mountpoint, opt.VolumeName)
|
2017-05-02 21:36:11 +00:00
|
|
|
|
|
|
|
// Create underlying FS
|
2020-07-22 16:58:49 +00:00
|
|
|
fsys := NewFS(VFS)
|
2017-05-02 21:36:11 +00:00
|
|
|
host := fuse.NewFileSystemHost(fsys)
|
2020-07-05 10:28:30 +00:00
|
|
|
host.SetCapReaddirPlus(true) // only works on Windows
|
2023-03-13 11:28:26 +00:00
|
|
|
if opt.CaseInsensitive.Valid {
|
|
|
|
host.SetCapCaseInsensitive(opt.CaseInsensitive.Value)
|
|
|
|
} else {
|
|
|
|
host.SetCapCaseInsensitive(f.Features().CaseInsensitive)
|
|
|
|
}
|
2017-05-02 21:36:11 +00:00
|
|
|
|
|
|
|
// Create options
|
2022-02-09 11:56:43 +00:00
|
|
|
options := mountOptions(VFS, opt.DeviceName, mountpoint, opt)
|
2017-05-02 21:36:11 +00: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 17:54:21 +00:00
|
|
|
defer func() {
|
|
|
|
if r := recover(); r != nil {
|
2021-11-04 10:12:57 +00:00
|
|
|
errChan <- fmt.Errorf("mount failed: %v", r)
|
2020-07-04 17:54:21 +00:00
|
|
|
}
|
|
|
|
}()
|
2017-05-02 21:36:11 +00: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 18:03:23 +00:00
|
|
|
// Shutdown the VFS
|
|
|
|
fsys.VFS.Shutdown()
|
2020-11-27 10:50:36 +00:00
|
|
|
var umountOK bool
|
2023-08-18 14:44:23 +00:00
|
|
|
if fsys.destroyed.Load() != 0 {
|
2021-01-28 18:17:47 +00:00
|
|
|
fs.Debugf(nil, "Not calling host.Unmount as mount already Destroyed")
|
|
|
|
umountOK = true
|
|
|
|
} else if atexit.Signalled() {
|
2020-11-27 10:50:36 +00: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 10:31:23 +00: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 21:36:11 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
fs.Debugf(nil, "host.Unmount failed")
|
|
|
|
return errors.New("host unmount failed")
|
|
|
|
}
|
|
|
|
|
2017-05-10 08:34:01 +00: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 10:12:57 +00:00
|
|
|
err = fmt.Errorf("mount stopped before calling Init: %w", err)
|
2020-07-22 16:58:49 +00:00
|
|
|
return nil, nil, err
|
2017-05-10 08:34:01 +00:00
|
|
|
case <-fsys.ready:
|
|
|
|
}
|
|
|
|
|
2017-05-10 10:16:53 +00: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 10:31:23 +00:00
|
|
|
if !waitFor(func() bool {
|
2017-05-10 10:16:53 +00:00
|
|
|
_, err := os.Stat(mountpoint)
|
2017-11-17 10:31:23 +00:00
|
|
|
return err == nil
|
|
|
|
}) {
|
|
|
|
fs.Errorf(nil, "mountpoint %q didn't became available on mount - continuing anyway", mountpoint)
|
2017-05-10 10:16:53 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-22 16:58:49 +00:00
|
|
|
return errChan, unmount, nil
|
2017-05-02 21:36:11 +00:00
|
|
|
}
|