mirror of
https://github.com/restic/restic.git
synced 2025-12-11 18:47:50 +00:00
backend/util: extract background handling code
This commit is contained in:
@@ -21,9 +21,9 @@ import (
|
||||
"github.com/restic/restic/internal/backend/limiter"
|
||||
"github.com/restic/restic/internal/backend/location"
|
||||
"github.com/restic/restic/internal/backend/rest"
|
||||
"github.com/restic/restic/internal/backend/util"
|
||||
"github.com/restic/restic/internal/debug"
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/terminal"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
@@ -82,7 +82,7 @@ func run(command string, args ...string) (*StdioConn, *sync.WaitGroup, chan stru
|
||||
cmd.Stdin = r
|
||||
cmd.Stdout = w
|
||||
|
||||
bg, err := util.StartForeground(cmd)
|
||||
bg, err := terminal.StartForeground(cmd)
|
||||
// close rclone side of pipes
|
||||
errR := r.Close()
|
||||
errW := w.Close()
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/restic/restic/internal/debug"
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/feature"
|
||||
"github.com/restic/restic/internal/terminal"
|
||||
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/pkg/sftp"
|
||||
@@ -84,7 +85,7 @@ func startClient(cfg Config) (*SFTP, error) {
|
||||
return nil, errors.Wrap(err, "cmd.StdoutPipe")
|
||||
}
|
||||
|
||||
bg, err := util.StartForeground(cmd)
|
||||
bg, err := terminal.StartForeground(cmd)
|
||||
if err != nil {
|
||||
if errors.Is(err, exec.ErrDot) {
|
||||
return nil, errors.Errorf("cannot implicitly run relative executable %v found in current directory, use -o sftp.command=./<command> to override", cmd.Path)
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// StartForeground runs cmd in the foreground, by temporarily switching to the
|
||||
// new process group created for cmd. The returned function `bg` switches back
|
||||
// to the previous process group.
|
||||
//
|
||||
// The command's environment has all RESTIC_* variables removed.
|
||||
//
|
||||
// Return exec.ErrDot if it would implicitly run an executable from the current
|
||||
// directory.
|
||||
func StartForeground(cmd *exec.Cmd) (bg func() error, err error) {
|
||||
env := os.Environ() // Returns a copy that we can modify.
|
||||
|
||||
cmd.Env = env[:0]
|
||||
for _, kv := range env {
|
||||
if strings.HasPrefix(kv, "RESTIC_") {
|
||||
continue
|
||||
}
|
||||
cmd.Env = append(cmd.Env, kv)
|
||||
}
|
||||
|
||||
return startForeground(cmd)
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package util_test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/restic/restic/internal/backend/util"
|
||||
rtest "github.com/restic/restic/internal/test"
|
||||
)
|
||||
|
||||
func TestForeground(t *testing.T) {
|
||||
err := os.Setenv("RESTIC_PASSWORD", "supersecret")
|
||||
rtest.OK(t, err)
|
||||
|
||||
cmd := exec.Command("env")
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
rtest.OK(t, err)
|
||||
|
||||
bg, err := util.StartForeground(cmd)
|
||||
rtest.OK(t, err)
|
||||
defer func() {
|
||||
rtest.OK(t, cmd.Wait())
|
||||
}()
|
||||
|
||||
err = bg()
|
||||
rtest.OK(t, err)
|
||||
|
||||
sc := bufio.NewScanner(stdout)
|
||||
for sc.Scan() {
|
||||
if strings.HasPrefix(sc.Text(), "RESTIC_PASSWORD=") {
|
||||
t.Error("subprocess got to see the password")
|
||||
}
|
||||
}
|
||||
rtest.OK(t, err)
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
//go:build unix
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
|
||||
"github.com/restic/restic/internal/debug"
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"github.com/restic/restic/internal/ui/termstatus"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
func startForeground(cmd *exec.Cmd) (bg func() error, err error) {
|
||||
// run the command in its own process group
|
||||
// this ensures that sending ctrl-c to restic will not immediately stop the backend process.
|
||||
cmd.SysProcAttr = &unix.SysProcAttr{
|
||||
Setpgid: true,
|
||||
}
|
||||
|
||||
// open the TTY, we need the file descriptor
|
||||
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
debug.Log("unable to open tty: %v", err)
|
||||
return startFallback(cmd)
|
||||
}
|
||||
|
||||
// only move child process to foreground if restic is in the foreground
|
||||
prev, err := termstatus.Tcgetpgrp(int(tty.Fd()))
|
||||
if err != nil {
|
||||
_ = tty.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
self := termstatus.Getpgrp()
|
||||
if prev != self {
|
||||
debug.Log("restic is not controlling the tty; err = %v", err)
|
||||
if err := tty.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return startFallback(cmd)
|
||||
}
|
||||
|
||||
// Prevent getting suspended when interacting with the tty
|
||||
signal.Ignore(unix.SIGTTIN)
|
||||
signal.Ignore(unix.SIGTTOU)
|
||||
|
||||
// start the process
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
_ = tty.Close()
|
||||
return nil, errors.Wrap(err, "cmd.Start")
|
||||
}
|
||||
|
||||
// move the command's process group into the foreground
|
||||
err = termstatus.Tcsetpgrp(int(tty.Fd()), cmd.Process.Pid)
|
||||
if err != nil {
|
||||
_ = tty.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bg = func() error {
|
||||
signal.Reset(unix.SIGTTIN)
|
||||
signal.Reset(unix.SIGTTOU)
|
||||
|
||||
// reset the foreground process group
|
||||
err = termstatus.Tcsetpgrp(int(tty.Fd()), prev)
|
||||
if err != nil {
|
||||
_ = tty.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
return tty.Close()
|
||||
}
|
||||
|
||||
return bg, nil
|
||||
}
|
||||
|
||||
func startFallback(cmd *exec.Cmd) (bg func() error, err error) {
|
||||
bg = func() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return bg, cmd.Start()
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"syscall"
|
||||
|
||||
"github.com/restic/restic/internal/errors"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func startForeground(cmd *exec.Cmd) (bg func() error, err error) {
|
||||
// just start the process and hope for the best
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||
cmd.SysProcAttr.CreationFlags = windows.CREATE_NEW_PROCESS_GROUP
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cmd.Start")
|
||||
}
|
||||
|
||||
bg = func() error { return nil }
|
||||
return bg, nil
|
||||
}
|
||||
Reference in New Issue
Block a user