restic/internal/terminal/password.go
2025-10-03 18:55:46 +02:00

54 lines
1.1 KiB
Go

package terminal
import (
"context"
"fmt"
"io"
"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, inFd int, out io.Writer, prompt string) (password string, err error) {
state, err := term.GetState(inFd)
if err != nil {
_, _ = fmt.Fprintf(out, "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(inFd)
if err != nil {
return
}
_, err = fmt.Fprintln(out)
}()
select {
case <-ctx.Done():
err := term.Restore(inFd, state)
if err != nil {
_, _ = fmt.Fprintf(out, "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
}