diff --git a/internal/backend/sftp/foreground_unix.go b/internal/backend/sftp/foreground_unix.go new file mode 100644 index 000000000..bdcb52a34 --- /dev/null +++ b/internal/backend/sftp/foreground_unix.go @@ -0,0 +1,73 @@ +// +build !windows + +package sftp + +import ( + "os" + "os/exec" + "os/signal" + "syscall" + "unsafe" + + "github.com/restic/restic/internal/errors" +) + +func tcsetpgrp(fd int, pid int) error { + _, _, errno := syscall.RawSyscall(syscall.SYS_IOCTL, uintptr(fd), + uintptr(syscall.TIOCSPGRP), uintptr(unsafe.Pointer(&pid))) + if errno == 0 { + return nil + } + + return errno +} + +// 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. +func startForeground(cmd *exec.Cmd) (bg func() error, err error) { + // open the TTY, we need the file descriptor + tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0) + if err != nil { + return nil, errors.Wrap(err, "open TTY") + } + + signal.Ignore(syscall.SIGTTIN) + signal.Ignore(syscall.SIGTTOU) + + // run the command in its own process group + cmd.SysProcAttr = &syscall.SysProcAttr{ + Setpgid: true, + } + + // 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 + prev := syscall.Getpgrp() + err = tcsetpgrp(int(tty.Fd()), cmd.Process.Pid) + if err != nil { + _ = tty.Close() + return nil, err + } + + bg = func() error { + signal.Reset(syscall.SIGTTIN) + signal.Reset(syscall.SIGTTOU) + + // reset the foreground process group + err = tcsetpgrp(int(tty.Fd()), prev) + if err != nil { + _ = tty.Close() + return err + } + + return tty.Close() + } + + return bg, nil +} diff --git a/internal/backend/sftp/foreground_windows.go b/internal/backend/sftp/foreground_windows.go new file mode 100644 index 000000000..e57b4d3a4 --- /dev/null +++ b/internal/backend/sftp/foreground_windows.go @@ -0,0 +1,21 @@ +package sftp + +import ( + "os/exec" + + "github.com/restic/restic/internal/errors" +) + +// 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. +func startForeground(cmd *exec.Cmd) (bg func() error, err error) { + // just start the process and hope for the best + err = cmd.Start() + if err != nil { + return nil, errors.Wrap(err, "cmd.Start") + } + + bg = func() error { return nil } + return bg, nil +} diff --git a/internal/backend/sftp/sftp.go b/internal/backend/sftp/sftp.go index 911e56293..243258ff3 100644 --- a/internal/backend/sftp/sftp.go +++ b/internal/backend/sftp/sftp.go @@ -69,8 +69,8 @@ func startClient(preExec, postExec func(), program string, args ...string) (*SFT preExec() } - // start the process - if err := cmd.Start(); err != nil { + bg, err := startForeground(cmd) + if err != nil { return nil, errors.Wrap(err, "cmd.Start") } @@ -92,6 +92,11 @@ func startClient(preExec, postExec func(), program string, args ...string) (*SFT return nil, errors.Errorf("unable to start the sftp session, error: %v", err) } + err = bg() + if err != nil { + return nil, errors.Wrap(err, "bg") + } + return &SFTP{c: client, cmd: cmd, result: ch}, nil }