terminal: move reading password from terminal here

This commit is contained in:
Michael Eischer
2025-09-07 14:30:04 +02:00
parent 43b5166de8
commit d10bd1d321
2 changed files with 56 additions and 49 deletions

View File

@@ -38,8 +38,6 @@ import (
"github.com/spf13/pflag"
"github.com/restic/restic/internal/errors"
"golang.org/x/term"
)
// 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())
}
// 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
// variable RESTIC_PASSWORD or prompts the user. If the context is canceled,
// the function leaks the password reading goroutine.
@@ -378,7 +330,7 @@ func ReadPassword(ctx context.Context, opts GlobalOptions, prompt string) (strin
)
if terminal.StdinIsTerminal() {
password, err = readPasswordTerminal(ctx, os.Stdin, os.Stderr, prompt)
password, err = terminal.ReadPassword(ctx, os.Stdin, os.Stderr, prompt)
} else {
password, err = readPassword(os.Stdin)
if terminal.StdoutIsTerminal() {

View 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
}