//go:build unix // Package daemonize provides daemonization interface for Unix platforms. package daemonize import ( "fmt" "os" "strings" "syscall" "github.com/rclone/rclone/fs" "golang.org/x/sys/unix" ) // 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] } // os.Executable might have resolved symbolic link to the executable // so we run the background process with pre-converted CLI arguments. // Double conversion is still probable but isn't a problem as it should // preserve the converted command line. if len(args) != 0 { args[0] = me } if fs.PassDaemonArgsAsEnviron { args, env = argsToEnv(args, env) } 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 } // Processed command line flags of mount helper have simple structure: // `--flag` or `--flag=value` but never `--flag value` or `-x` // so we can easily pass them as environment variables. func argsToEnv(origArgs, origEnv []string) (args, env []string) { env = origEnv if len(origArgs) == 0 { return } args = []string{origArgs[0]} for _, arg := range origArgs[1:] { if !strings.HasPrefix(arg, "--") { args = append(args, arg) continue } arg = arg[2:] var key, val string var ok bool if key, val, ok = strings.Cut(arg, "="); !ok { val = "true" } name := "RCLONE_" + strings.ToUpper(strings.ReplaceAll(key, "-", "_")) pref := name + "=" line := name + "=" + val found := false for i, s := range env { if strings.HasPrefix(s, pref) { env[i] = line found = true } } if !found { env = append(env, line) } } return } // Check returns non nil if the daemon process has died func Check(daemon *os.Process) error { var status unix.WaitStatus wpid, err := unix.Wait4(daemon.Pid, &status, unix.WNOHANG, nil) // fs.Debugf(nil, "wait4 returned wpid=%d, err=%v, status=%d", wpid, err, status) if err != nil { return err } if wpid == 0 { return nil } if status.Exited() { return fmt.Errorf("daemon exited with error code %d", status.ExitStatus()) } return nil }