use std::io::{BufRead as _, IsTerminal as _, Read as _, Write as _};
use anyhow::{Result, bail};
use scv_tools::stores::{MAX_FIELD_BYTES, validate_secret};
pub(crate) fn prompt_line(prompt: &str) -> Result<String> {
if std::io::stdin().is_terminal() {
eprint!("{prompt}: ");
std::io::stderr().flush().ok();
}
let mut line = String::new();
std::io::stdin().lock().read_line(&mut line)?;
Ok(line.trim().to_owned())
}
pub(crate) fn read_secret(prompt: &str) -> Result<String> {
let stdin = std::io::stdin();
let mut line = String::new();
if stdin.is_terminal() {
eprint!("{prompt}: ");
std::io::stderr().flush().ok();
let _echo = EchoOff::new()?;
stdin.lock().read_line(&mut line)?;
eprintln!();
} else {
stdin
.lock()
.take(MAX_FIELD_BYTES as u64 + 2)
.read_line(&mut line)?;
}
let secret = line.trim().to_owned();
validate_secret(&secret)?;
Ok(secret)
}
struct EchoOff(libc::termios);
impl EchoOff {
fn new() -> Result<Self> {
let mut termios = std::mem::MaybeUninit::<libc::termios>::uninit();
if unsafe { libc::tcgetattr(libc::STDIN_FILENO, termios.as_mut_ptr()) } != 0 {
bail!(
"read terminal settings: {}",
std::io::Error::last_os_error()
);
}
let original = unsafe { termios.assume_init() };
let mut silent = original;
silent.c_lflag &= !libc::ECHO;
if unsafe { libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, &raw const silent) } != 0 {
bail!("disable terminal echo: {}", std::io::Error::last_os_error());
}
Ok(Self(original))
}
}
impl Drop for EchoOff {
fn drop(&mut self) {
unsafe { libc::tcsetattr(libc::STDIN_FILENO, libc::TCSANOW, &raw const self.0) };
}
}