mirror of
https://github.com/rclone/rclone.git
synced 2024-11-22 12:05:05 +08:00
mountlib: correctly daemonize for compatibility with automount - #5593
This patch will: - add --daemon-wait flag to control the time to wait for background mount - remove dependency on sevlyar/go-daemon and implement backgrounding directly - avoid setsid during backgrounding as it can result in race under Automount - provide a fallback PATH to correctly run `fusermount` under systemd as it runs mount units without standard environment variables - correctly handle ^C pressed while background process is being setting up
This commit is contained in:
parent
8c10dee510
commit
8b8a943dd8
|
@ -1,16 +0,0 @@
|
|||
// Daemonization interface for non-Unix variants only
|
||||
|
||||
//go:build windows || plan9 || js
|
||||
// +build windows plan9 js
|
||||
|
||||
package mountlib
|
||||
|
||||
import (
|
||||
"log"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func startBackgroundMode() bool {
|
||||
log.Fatalf("background mode not supported on %s platform", runtime.GOOS)
|
||||
return false
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
// Daemonization interface for Unix variants only
|
||||
|
||||
//go:build !windows && !plan9 && !js
|
||||
// +build !windows,!plan9,!js
|
||||
|
||||
package mountlib
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
daemon "github.com/sevlyar/go-daemon"
|
||||
)
|
||||
|
||||
func startBackgroundMode() bool {
|
||||
cntxt := &daemon.Context{}
|
||||
d, err := cntxt.Reborn()
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
if d != nil {
|
||||
return true
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := cntxt.Release(); err != nil {
|
||||
log.Printf("error encountered while killing daemon: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
return false
|
||||
}
|
|
@ -15,6 +15,7 @@ import (
|
|||
"github.com/rclone/rclone/fs/config/flags"
|
||||
"github.com/rclone/rclone/fs/rc"
|
||||
"github.com/rclone/rclone/lib/atexit"
|
||||
"github.com/rclone/rclone/lib/daemonize"
|
||||
"github.com/rclone/rclone/vfs"
|
||||
"github.com/rclone/rclone/vfs/vfscommon"
|
||||
"github.com/rclone/rclone/vfs/vfsflags"
|
||||
|
@ -34,6 +35,7 @@ type Options struct {
|
|||
DefaultPermissions bool
|
||||
WritebackCache bool
|
||||
Daemon bool
|
||||
DaemonWait time.Duration // time to wait for ready mount from daemon, maximum on Linux or constant on macOS/BSD
|
||||
MaxReadAhead fs.SizeSuffix
|
||||
ExtraOptions []string
|
||||
ExtraFlags []string
|
||||
|
@ -81,16 +83,30 @@ const (
|
|||
)
|
||||
|
||||
func init() {
|
||||
// DaemonTimeout defaults to non zero for macOS
|
||||
if runtime.GOOS == "darwin" {
|
||||
switch runtime.GOOS {
|
||||
case "darwin":
|
||||
// DaemonTimeout defaults to non-zero for macOS
|
||||
// (this is a macOS specific kernel option unrelated to DaemonWait)
|
||||
DefaultOpt.DaemonTimeout = 10 * time.Minute
|
||||
}
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
// Linux provides /proc/mounts to check mount status
|
||||
// so --daemon-wait means *maximum* time to wait
|
||||
DefaultOpt.DaemonWait = 60 * time.Second
|
||||
case "darwin", "openbsd", "freebsd", "netbsd":
|
||||
// On BSD we can't check mount status yet
|
||||
// so --daemon-wait is just a *constant* delay
|
||||
DefaultOpt.DaemonWait = 5 * time.Second
|
||||
}
|
||||
|
||||
// Opt must be assigned in the init block to ensure changes really get in
|
||||
Opt = DefaultOpt
|
||||
}
|
||||
|
||||
// Options set by command line flags
|
||||
var (
|
||||
Opt = DefaultOpt
|
||||
)
|
||||
// Opt contains options set by command line flags
|
||||
var Opt Options
|
||||
|
||||
// AddFlags adds the non filing system specific flags to the command
|
||||
func AddFlags(flagSet *pflag.FlagSet) {
|
||||
|
@ -100,7 +116,7 @@ func AddFlags(flagSet *pflag.FlagSet) {
|
|||
flags.StringArrayVarP(flagSet, &Opt.ExtraOptions, "option", "o", []string{}, "Option for libfuse/WinFsp. Repeat if required.")
|
||||
flags.StringArrayVarP(flagSet, &Opt.ExtraFlags, "fuse-flag", "", []string{}, "Flags or arguments to be passed direct to libfuse/WinFsp. Repeat if required.")
|
||||
// Non-Windows only
|
||||
flags.BoolVarP(flagSet, &Opt.Daemon, "daemon", "", Opt.Daemon, "Run mount as a daemon (background mode). Not supported on Windows.")
|
||||
flags.BoolVarP(flagSet, &Opt.Daemon, "daemon", "", Opt.Daemon, "Run mount in background and exit parent process. Not supported on Windows. As background output is suppressed, use --log-file with --log-format=pid,... to monitor.")
|
||||
flags.DurationVarP(flagSet, &Opt.DaemonTimeout, "daemon-timeout", "", Opt.DaemonTimeout, "Time limit for rclone to respond to kernel. Not supported on Windows.")
|
||||
flags.BoolVarP(flagSet, &Opt.DefaultPermissions, "default-permissions", "", Opt.DefaultPermissions, "Makes kernel enforce access control based on the file mode. Not supported on Windows.")
|
||||
flags.BoolVarP(flagSet, &Opt.AllowNonEmpty, "allow-non-empty", "", Opt.AllowNonEmpty, "Allow mounting over a non-empty directory. Not supported on Windows.")
|
||||
|
@ -116,6 +132,8 @@ func AddFlags(flagSet *pflag.FlagSet) {
|
|||
flags.BoolVarP(flagSet, &Opt.NoAppleXattr, "noapplexattr", "", Opt.NoAppleXattr, "Ignore all \"com.apple.*\" extended attributes. Supported on OSX only.")
|
||||
// Windows only
|
||||
flags.BoolVarP(flagSet, &Opt.NetworkMode, "network-mode", "", Opt.NetworkMode, "Mount as remote network drive, instead of fixed disk drive. Supported on Windows only")
|
||||
// Unix only
|
||||
flags.DurationVarP(flagSet, &Opt.DaemonWait, "daemon-wait", "", Opt.DaemonWait, "Time to wait for ready mount from daemon (maximum time on Linux, constant sleep time on OSX/BSD). Ignored on Windows.")
|
||||
}
|
||||
|
||||
// NewMountCommand makes a mount command with the given name and Mount function
|
||||
|
@ -136,6 +154,12 @@ func NewMountCommand(commandName string, hidden bool, mount MountFn) *cobra.Comm
|
|||
config.PassConfigKeyForDaemonization = true
|
||||
}
|
||||
|
||||
if os.Getenv("PATH") == "" && runtime.GOOS != "windows" {
|
||||
// PATH can be unset when running under Autofs or Systemd mount
|
||||
fs.Debugf(nil, "Using fallback PATH to run fusermount")
|
||||
_ = os.Setenv("PATH", "/bin:/usr/bin")
|
||||
}
|
||||
|
||||
// Show stats if the user has specifically requested them
|
||||
if cmd.ShowStats() {
|
||||
defer cmd.StartStats()()
|
||||
|
@ -149,9 +173,40 @@ func NewMountCommand(commandName string, hidden bool, mount MountFn) *cobra.Comm
|
|||
VFSOpt: vfsflags.Opt,
|
||||
}
|
||||
|
||||
daemonized, err := mnt.Mount()
|
||||
if !daemonized && err == nil {
|
||||
err = mnt.Wait()
|
||||
daemon, err := mnt.Mount()
|
||||
|
||||
// Wait for foreground mount, if any...
|
||||
if daemon == nil {
|
||||
if err == nil {
|
||||
err = mnt.Wait()
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatalf("Fatal error: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Wait for daemon, if any...
|
||||
killOnce := sync.Once{}
|
||||
killDaemon := func(reason string) {
|
||||
killOnce.Do(func() {
|
||||
if err := daemon.Signal(os.Interrupt); err != nil {
|
||||
fs.Errorf(nil, "%s. Failed to terminate daemon pid %d: %v", reason, daemon.Pid, err)
|
||||
return
|
||||
}
|
||||
fs.Debugf(nil, "%s. Terminating daemon pid %d", reason, daemon.Pid)
|
||||
})
|
||||
}
|
||||
|
||||
if err == nil && Opt.DaemonWait > 0 {
|
||||
handle := atexit.Register(func() {
|
||||
killDaemon("Got interrupt")
|
||||
})
|
||||
err = WaitMountReady(mnt.MountPoint, Opt.DaemonWait)
|
||||
if err != nil {
|
||||
killDaemon("Daemon timed out")
|
||||
}
|
||||
atexit.Unregister(handle)
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatalf("Fatal error: %v", err)
|
||||
|
@ -171,21 +226,21 @@ func NewMountCommand(commandName string, hidden bool, mount MountFn) *cobra.Comm
|
|||
}
|
||||
|
||||
// Mount the remote at mountpoint
|
||||
func (m *MountPoint) Mount() (daemonized bool, err error) {
|
||||
func (m *MountPoint) Mount() (daemon *os.Process, err error) {
|
||||
if err = m.CheckOverlap(); err != nil {
|
||||
return false, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = m.CheckAllowings(); err != nil {
|
||||
return false, err
|
||||
return nil, err
|
||||
}
|
||||
m.SetVolumeName(m.MountOpt.VolumeName)
|
||||
|
||||
// Start background task if --daemon is specified
|
||||
if m.MountOpt.Daemon {
|
||||
daemonized = startBackgroundMode()
|
||||
if daemonized {
|
||||
return true, nil
|
||||
daemon, err = daemonize.StartDaemon(os.Args)
|
||||
if daemon != nil || err != nil {
|
||||
return daemon, err
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -193,9 +248,9 @@ func (m *MountPoint) Mount() (daemonized bool, err error) {
|
|||
|
||||
m.ErrChan, m.UnmountFn, err = m.MountFn(m.VFS, m.MountPoint, &m.MountOpt)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to mount FUSE fs")
|
||||
return nil, errors.Wrap(err, "failed to mount FUSE fs")
|
||||
}
|
||||
return false, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Wait for mount end
|
||||
|
@ -205,7 +260,16 @@ func (m *MountPoint) Wait() error {
|
|||
finalise := func() {
|
||||
finaliseOnce.Do(func() {
|
||||
_ = sysdnotify.Stopping()
|
||||
_ = m.UnmountFn()
|
||||
// Unmount only if directory was mounted by rclone, e.g. don't unmount autofs hooks.
|
||||
if err := CheckMountReady(m.MountPoint); err != nil {
|
||||
fs.Debugf(m.MountPoint, "Unmounted externally. Just exit now.")
|
||||
return
|
||||
}
|
||||
if err := m.Unmount(); err != nil {
|
||||
fs.Errorf(m.MountPoint, "Failed to unmount: %v", err)
|
||||
} else {
|
||||
fs.Errorf(m.MountPoint, "Unmounted rclone mount")
|
||||
}
|
||||
})
|
||||
}
|
||||
fnHandle := atexit.Register(finalise)
|
||||
|
|
11
fs/daemon_other.go
Normal file
11
fs/daemon_other.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
// Daemonization stub for non-Unix platforms (common definitions)
|
||||
|
||||
//go:build windows || plan9 || js
|
||||
// +build windows plan9 js
|
||||
|
||||
package fs
|
||||
|
||||
// IsDaemon returns true if this process runs in background
|
||||
func IsDaemon() bool {
|
||||
return false
|
||||
}
|
21
fs/daemon_unix.go
Normal file
21
fs/daemon_unix.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
// Daemonization interface for Unix platforms (common definitions)
|
||||
|
||||
//go:build !windows && !plan9 && !js
|
||||
// +build !windows,!plan9,!js
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// We use a special environment variable to let the child process know its role.
|
||||
const (
|
||||
DaemonMarkVar = "_RCLONE_DAEMON_"
|
||||
DaemonMarkChild = "_rclone_daemon_"
|
||||
)
|
||||
|
||||
// IsDaemon returns true if this process runs in background
|
||||
func IsDaemon() bool {
|
||||
return os.Getenv(DaemonMarkVar) == DaemonMarkChild
|
||||
}
|
2
go.mod
2
go.mod
|
@ -37,7 +37,6 @@ require (
|
|||
github.com/jcmturner/gokrb5/v8 v8.4.2
|
||||
github.com/jlaffaye/ftp v0.0.0-20210307004419-5d4190119067
|
||||
github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
|
||||
github.com/klauspost/compress v1.13.4
|
||||
github.com/koofr/go-httpclient v0.0.0-20200420163713-93aa7c75b348
|
||||
github.com/koofr/go-koofrclient v0.0.0-20190724113126-8e5366da203a
|
||||
|
@ -56,7 +55,6 @@ require (
|
|||
github.com/prometheus/procfs v0.7.3 // indirect
|
||||
github.com/putdotio/go-putio/putio v0.0.0-20200123120452-16d982cac2b8
|
||||
github.com/rfjakob/eme v1.1.2
|
||||
github.com/sevlyar/go-daemon v0.1.5
|
||||
github.com/shirou/gopsutil/v3 v3.21.8
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
||||
|
|
5
go.sum
5
go.sum
|
@ -391,8 +391,6 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
|
|||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004 h1:G+9t9cEtnC9jFiTxyptEKuNIAbiN5ZCQzX2a74lj3xg=
|
||||
github.com/jzelinskie/whirlpool v0.0.0-20201016144138-0675e54bb004/go.mod h1:KmHnJWQrgEvbuy0vcvj00gtMqbvNn1L+3YUZLK/B92c=
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
|
||||
|
@ -548,8 +546,6 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb
|
|||
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46/go.mod h1:uAQ5PCi+MFsC7HjREoAz1BU+Mq60+05gifQSsHSDG/8=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/sevlyar/go-daemon v0.1.5 h1:Zy/6jLbM8CfqJ4x4RPr7MJlSKt90f00kNM1D401C+Qk=
|
||||
github.com/sevlyar/go-daemon v0.1.5/go.mod h1:6dJpPatBT9eUwM5VCw9Bt6CdX9Tk6UWvhW3MebLDRKE=
|
||||
github.com/shirou/gopsutil/v3 v3.21.8 h1:nKct+uP0TV8DjjNiHanKf8SAuub+GNsbrOtM9Nl9biA=
|
||||
github.com/shirou/gopsutil/v3 v3.21.8/go.mod h1:YWp/H8Qs5fVmf17v7JNZzA0mPJ+mS2e9JdiUF9LlKzQ=
|
||||
github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY=
|
||||
|
@ -1156,7 +1152,6 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
|
18
lib/daemonize/daemon_other.go
Normal file
18
lib/daemonize/daemon_other.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
// Daemonization stub for non-Unix platforms (implementation)
|
||||
|
||||
//go:build windows || plan9 || js
|
||||
// +build windows plan9 js
|
||||
|
||||
package daemonize
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// StartDaemon runs background twin of current process.
|
||||
func StartDaemon(args []string) (*os.Process, error) {
|
||||
return nil, errors.Errorf("background mode is not supported on %s platform", runtime.GOOS)
|
||||
}
|
59
lib/daemonize/daemon_unix.go
Normal file
59
lib/daemonize/daemon_unix.go
Normal file
|
@ -0,0 +1,59 @@
|
|||
// Daemonization interface for Unix platforms (implementation)
|
||||
|
||||
//go:build !windows && !plan9 && !js
|
||||
// +build !windows,!plan9,!js
|
||||
|
||||
package daemonize
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/rclone/rclone/fs"
|
||||
)
|
||||
|
||||
// StartDaemon runs background twin of current process.
|
||||
// It executes separate parts of code in child and parent processes.
|
||||
// Returns child process pid in the parent or nil in the child.
|
||||
// The method looks like a fork but safe for goroutines.
|
||||
func StartDaemon(args []string) (*os.Process, error) {
|
||||
if fs.IsDaemon() {
|
||||
// This process is already daemonized
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
env := append(os.Environ(), fs.DaemonMarkVar+"="+fs.DaemonMarkChild)
|
||||
|
||||
me, err := os.Executable()
|
||||
if err != nil {
|
||||
me = os.Args[0]
|
||||
}
|
||||
|
||||
null, err := os.Open(os.DevNull)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
files := []*os.File{
|
||||
null, // (0) stdin
|
||||
null, // (1) stdout
|
||||
null, // (2) stderr
|
||||
}
|
||||
sysAttr := &syscall.SysProcAttr{
|
||||
// setsid (https://linux.die.net/man/2/setsid) in the child process will reset
|
||||
// its process group id (PGID) to its PID thus detaching it from parent.
|
||||
// This would make autofs fail because it detects mounting process by its PGID.
|
||||
Setsid: false,
|
||||
}
|
||||
attr := &os.ProcAttr{
|
||||
Env: env,
|
||||
Files: files,
|
||||
Sys: sysAttr,
|
||||
}
|
||||
|
||||
daemon, err := os.StartProcess(me, args, attr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return daemon, nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user