diff --git a/go/oasis-node/cmd/common/common.go b/go/oasis-node/cmd/common/common.go index 378f8fb2aa2..26a011f21aa 100644 --- a/go/oasis-node/cmd/common/common.go +++ b/go/oasis-node/cmd/common/common.go @@ -278,24 +278,34 @@ func ExportEntity(signerBackend, entityDir string) error { return err } -// GetUserConfirmation scans the input for user's confirmation. +// GetUserConfirmation displays the prompt, and scans the input for +// the user's confirmation, until the user either explicitly confirms +// or rejects the prompt. // -// If the user's response is not recognized, it prompts the user again. -func GetUserConfirmation() bool { - var response string - - _, err := fmt.Scanln(&response) - if err != nil && err.Error() != "unexpected newline" { - rootLog.Error("Error reading from line", "err", err) +// Note: If standard input is not a tty, this will omit displaying +// the prompt, and assume the user entered yes. +func GetUserConfirmation(prompt string) bool { + if !Isatty(os.Stdin.Fd()) { + return true } - switch strings.ToLower(response) { - case "y", "yes": - return true - case "n", "no": - return false - default: - fmt.Printf("Unrecognized response: '%s'. Please, type (y)es or (n)o: ", response) - return GetUserConfirmation() + fmt.Printf("%s", prompt) + + var response string + for { + _, err := fmt.Scanln(&response) + if err != nil && err.Error() != "unexpected newline" { + rootLog.Error("Error reading from line", "err", err) + continue + } + + switch strings.ToLower(response) { + case "y", "yes": + return true + case "n", "no": + return false + default: + fmt.Printf("Unrecognized response: '%s'. Please, type (y)es or (n)o: ", response) + } } } diff --git a/go/oasis-node/cmd/common/consensus/consensus.go b/go/oasis-node/cmd/common/consensus/consensus.go index 7608195836e..2201f146d67 100644 --- a/go/oasis-node/cmd/common/consensus/consensus.go +++ b/go/oasis-node/cmd/common/consensus/consensus.go @@ -124,8 +124,7 @@ func SignAndSaveTx(ctx context.Context, tx *transaction.Transaction) { tx.PrettyPrint(ctx, " ", os.Stdout) if !cmdFlags.AssumeYes() && cmdSigner.Backend() == signerFile.SignerName { - fmt.Printf("\nAre you sure you want to continue? (y)es/(n)o ") - if !cmdCommon.GetUserConfirmation() { + if !cmdCommon.GetUserConfirmation("\nAre you sure you want to continue? (y)es/(n)o: ") { os.Exit(1) } } diff --git a/go/oasis-node/cmd/common/isatty.go b/go/oasis-node/cmd/common/isatty.go new file mode 100644 index 00000000000..c043be72d4d --- /dev/null +++ b/go/oasis-node/cmd/common/isatty.go @@ -0,0 +1,31 @@ +package common + +import ( + "syscall" + "unsafe" +) + +// Isatty returns true iff the provided file descriptor is a terminal. +func Isatty(fd uintptr) bool { + var attrs syscall.Termios + + // This could examine the error more specifically to see if + // something really strange is going on since we expect it + // to return 0 or ENOTTY all the time, but, "the messed up + // thing the user passed in that makes it complain" also + // is not a tty. + // + // And yes, this is the standard way of doing this, see your + // libc implementation of choice. + _, _, errno := syscall.Syscall6( + syscall.SYS_IOCTL, + fd, + syscall.TCGETS, + uintptr(unsafe.Pointer(&attrs)), + 0, + 0, + 0, + ) + + return errno == 0 +}