1 Commits
Author SHA1 Message Date
sneak a6a9c7b18e Put the terminal back when a signal ends the mnemonic prompt (closes #91)
check / check (push) Canceled after 0s
A SIGINT, SIGTERM or SIGHUP at the mnemonic prompt killed the tool
with echo still off. The prompt now catches them with signals.Notify
from before echo goes off, and reads the line in a goroutine. When one
arrives it puts the terminal settings back, stops catching, and sends
the tool the same signal again, so the tool is still ended by it (130
for SIGINT in a shell). A signal the tool was started with set to be
ignored stays ignored. A test sends SIGINT at the prompt on a
pseudo-terminal.

Unchanged: the Windows prompt, which uses term.ReadPassword.

Model: opus-5-5
2026-10-14 21:47:30 +00:00
+9 -19
View File
@@ -21,9 +21,8 @@ import (
// of input and goes on waiting. // of input and goes on waiting.
// //
// Meanwhile SIGINT, SIGTERM and SIGHUP are caught, as signals.Notify // Meanwhile SIGINT, SIGTERM and SIGHUP are caught, as signals.Notify
// catches them. One that has arrived by the time the line is read puts // catches them. One that arrives puts the terminal back as it was and
// the terminal back as it was and then ends the tool, as it would have // then ends the tool, as it would have ended it if it were not caught.
// ended it if it were not caught.
func readWithoutEcho() (string, error) { func readWithoutEcho() (string, error) {
fd := int(os.Stdin.Fd()) fd := int(os.Stdin.Fd())
@@ -49,6 +48,11 @@ func readWithoutEcho() (string, error) {
return "", fmt.Errorf("turning echo off: %w", err) return "", fmt.Errorf("turning echo off: %w", err)
} }
restore := func() {
_ = unix.IoctlSetTermios(fd, ioctlWriteTermios, settings)
}
defer restore()
var ( var (
typed string typed string
failed error failed error
@@ -64,26 +68,12 @@ func readWithoutEcho() (string, error) {
close(read) close(read)
}() }()
var ending os.Signal
select { select {
case <-read: case <-read:
case ending = <-received: case ending := <-received:
} restore()
// The terminal is put back while the signals are still caught, so
// that none can end the tool with echo off. Stop returns only once
// every signal the tool has received has been handed over, so one
// that came as the line was read is in received by then.
_ = unix.IoctlSetTermios(fd, ioctlWriteTermios, settings)
signal.Stop(received) signal.Stop(received)
if len(received) > 0 {
ending = <-received
}
if ending != nil {
return "", endBy(ending) return "", endBy(ending)
} }