Files
vault/oracle/go/internal/term/term.go
T
Andy DixonandClaude 69f4b90777 Freeze the Go implementation as a test-only oracle, with vectors
oracle/go is the Go code at 4c1507c, moved unchanged, plus test-only
generators that record what it computes. tests/vectors holds the
deterministic crypto/store vectors and synthetic fixtures they produce.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-10-08 09:55:17 +01:00

109 lines
3.2 KiB
Go

package term
import (
"bufio"
"fmt"
"os"
"runtime"
"strings"
"golang.org/x/term"
)
// enableVirtualTerminalProcessing tries to enable virtual terminal processing
// on Windows. If it fails, avoid using escape sequences to prevent weird
// characters being printed to the console.
var enableVirtualTerminalProcessing func(out *os.File) error
// clearLine clears the current line on the terminal, or opens a new line if
// terminal escape codes don't work.
func clearLine(out *os.File) {
const (
CUI = "\033[" // Control Sequence Introducer
CPL = CUI + "F" // Cursor Previous Line
EL = CUI + "K" // Erase in Line
)
// First, open a new line, which is guaranteed to work everywhere. Then, try
// to erase the line above with escape codes, if possible.
//
// (We use CRLF instead of LF to work around an apparent bug in WSL2's
// handling of CONOUT$. Only when running a Windows binary from WSL2, the
// cursor would not go back to the start of the line with a simple LF.
// Honestly, it's impressive CONIN$ and CONOUT$ work at all inside WSL2.)
fmt.Fprintf(out, "\r\n")
if enableVirtualTerminalProcessing == nil || enableVirtualTerminalProcessing(out) == nil {
fmt.Fprintf(out, CPL+EL)
}
}
// WithTerminal runs f with the terminal input and output files, if available.
// WithTerminal does not open a non-terminal stdin, so the caller does not need
// to check if stdin is in use.
func WithTerminal(f func(in, out *os.File) error) error {
if runtime.GOOS == "windows" {
in, err := os.OpenFile("CONIN$", os.O_RDWR, 0)
if err != nil {
return err
}
defer in.Close()
out, err := os.OpenFile("CONOUT$", os.O_WRONLY, 0)
if err != nil {
return err
}
defer out.Close()
return f(in, out)
} else if tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0); err == nil {
defer tty.Close()
return f(tty, tty)
} else if term.IsTerminal(int(os.Stdin.Fd())) {
return f(os.Stdin, os.Stdin)
} else {
return fmt.Errorf("standard input is not a terminal, and /dev/tty is not available: %v", err)
}
}
// ReadSecret reads a value from the terminal with no echo. The prompt is ephemeral.
func ReadSecret(prompt string) (s []byte, err error) {
err = WithTerminal(func(in, out *os.File) error {
fmt.Fprintf(out, "%s ", prompt)
defer clearLine(out)
s, err = term.ReadPassword(int(in.Fd()))
return err
})
return
}
// Confirm asks a yes/no question on the terminal. Only "y" or "yes" is yes.
func Confirm(prompt string) (yes bool, err error) {
err = WithTerminal(func(in, out *os.File) error {
fmt.Fprintf(out, "%s ", prompt)
line, err := bufio.NewReader(in).ReadString('\n')
if err != nil && line == "" {
return err
}
switch strings.ToLower(strings.TrimSpace(line)) {
case "y", "yes":
yes = true
}
return nil
})
return
}
// ReadPassphrase returns the passphrase from the environment variable env if
// it is set, otherwise prompts on the terminal.
func ReadPassphrase(prompt, env string) (string, error) {
if p, ok := os.LookupEnv(env); ok {
if p == "" {
return "", fmt.Errorf("%s is set but empty", env)
}
return p, nil
}
p, err := ReadSecret(prompt)
if err != nil {
return "", fmt.Errorf("could not read passphrase (set %s for non-interactive use): %v", env, err)
}
return string(p), nil
}