From 05958caf6e858aeff1d2e8e6f8a9a2940f818659 Mon Sep 17 00:00:00 2001 From: Alexander Neumann Date: Wed, 17 Jan 2018 23:02:47 +0100 Subject: [PATCH] sftp: Prompt for password, don't terminate on SIGINT This is a follow-up on fb9729fdb9bf1e15dc6c90383df85a8968f6ddb0, which runs the `ssh` in its own process group and selects that process group as the foreground group. After the sftp connection is established, restic switches back to the previous foreground process group. This allows `ssh` to prompt for the password, but it won't receive the interrupt signal (SIGINT, ^C) later on, because it is not in the foreground process group any more, allowing a clean tear down. --- internal/backend/sftp/foreground_unix.go | 73 +++++++++++++++++++++ internal/backend/sftp/foreground_windows.go | 21 ++++++ internal/backend/sftp/sftp.go | 9 ++- 3 files changed, 101 insertions(+), 2 deletions(-) create mode 100644 internal/backend/sftp/foreground_unix.go create mode 100644 internal/backend/sftp/foreground_windows.go 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 }