mirror of
https://github.com/restic/restic.git
synced 2025-12-13 12:52:34 +00:00
terminal: move reading password from terminal here
This commit is contained in:
@@ -38,8 +38,6 @@ import (
|
|||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
|
|
||||||
"golang.org/x/term"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrNoRepository is used to report if opening a repository failed due
|
// ErrNoRepository is used to report if opening a repository failed due
|
||||||
@@ -311,52 +309,6 @@ func readPassword(in io.Reader) (password string, err error) {
|
|||||||
return sc.Text(), errors.WithStack(sc.Err())
|
return sc.Text(), errors.WithStack(sc.Err())
|
||||||
}
|
}
|
||||||
|
|
||||||
// readPasswordTerminal reads the password from the given reader which must be a
|
|
||||||
// tty. Prompt is printed on the writer out before attempting to read the
|
|
||||||
// password. If the context is canceled, the function leaks the password reading
|
|
||||||
// goroutine.
|
|
||||||
func readPasswordTerminal(ctx context.Context, in *os.File, out *os.File, prompt string) (password string, err error) {
|
|
||||||
fd := int(out.Fd())
|
|
||||||
state, err := term.GetState(fd)
|
|
||||||
if err != nil {
|
|
||||||
_, _ = fmt.Fprintf(os.Stderr, "unable to get terminal state: %v\n", err)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
done := make(chan struct{})
|
|
||||||
var buf []byte
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer close(done)
|
|
||||||
_, err = fmt.Fprint(out, prompt)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
buf, err = term.ReadPassword(int(in.Fd()))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_, err = fmt.Fprintln(out)
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
err := term.Restore(fd, state)
|
|
||||||
if err != nil {
|
|
||||||
_, _ = fmt.Fprintf(os.Stderr, "unable to restore terminal state: %v\n", err)
|
|
||||||
}
|
|
||||||
return "", ctx.Err()
|
|
||||||
case <-done:
|
|
||||||
// clean shutdown, nothing to do
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return "", errors.Wrap(err, "ReadPassword")
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(buf), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadPassword reads the password from a password file, the environment
|
// ReadPassword reads the password from a password file, the environment
|
||||||
// variable RESTIC_PASSWORD or prompts the user. If the context is canceled,
|
// variable RESTIC_PASSWORD or prompts the user. If the context is canceled,
|
||||||
// the function leaks the password reading goroutine.
|
// the function leaks the password reading goroutine.
|
||||||
@@ -378,7 +330,7 @@ func ReadPassword(ctx context.Context, opts GlobalOptions, prompt string) (strin
|
|||||||
)
|
)
|
||||||
|
|
||||||
if terminal.StdinIsTerminal() {
|
if terminal.StdinIsTerminal() {
|
||||||
password, err = readPasswordTerminal(ctx, os.Stdin, os.Stderr, prompt)
|
password, err = terminal.ReadPassword(ctx, os.Stdin, os.Stderr, prompt)
|
||||||
} else {
|
} else {
|
||||||
password, err = readPassword(os.Stdin)
|
password, err = readPassword(os.Stdin)
|
||||||
if terminal.StdoutIsTerminal() {
|
if terminal.StdoutIsTerminal() {
|
||||||
|
|||||||
55
internal/terminal/password.go
Normal file
55
internal/terminal/password.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
package terminal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/term"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReadPassword reads the password from the given reader which must be a
|
||||||
|
// tty. Prompt is printed on the writer out before attempting to read the
|
||||||
|
// password. If the context is canceled, the function leaks the password reading
|
||||||
|
// goroutine.
|
||||||
|
func ReadPassword(ctx context.Context, in *os.File, out *os.File, prompt string) (password string, err error) {
|
||||||
|
fd := int(out.Fd())
|
||||||
|
state, err := term.GetState(fd)
|
||||||
|
if err != nil {
|
||||||
|
_, _ = fmt.Fprintf(os.Stderr, "unable to get terminal state: %v\n", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
|
var buf []byte
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
_, err = fmt.Fprint(out, prompt)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buf, err = term.ReadPassword(int(in.Fd()))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = fmt.Fprintln(out)
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
err := term.Restore(fd, state)
|
||||||
|
if err != nil {
|
||||||
|
_, _ = fmt.Fprintf(os.Stderr, "unable to restore terminal state: %v\n", err)
|
||||||
|
}
|
||||||
|
return "", ctx.Err()
|
||||||
|
case <-done:
|
||||||
|
// clean shutdown, nothing to do
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("ReadPassword: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(buf), nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user