use std::io::{Read, Write};
use std::sync::atomic::{AtomicBool, Ordering};
static REQUESTED: AtomicBool = AtomicBool::new(false);
pub fn request_prompt_missing() {
REQUESTED.store(true, Ordering::Relaxed);
}
pub fn prompt_missing_requested() -> bool {
REQUESTED.load(Ordering::Relaxed)
}
pub fn read_secret_from_tty(label: &str) -> Result<String, String> {
let mut tty = std::fs::OpenOptions::new()
.read(true)
.write(true)
.open("/dev/tty")
.map_err(|e| format!("no controlling terminal ({e}) — --prompt-missing needs one"))?;
write!(tty, "{label}: ").map_err(|e| e.to_string())?;
tty.flush().ok();
let fd = std::os::fd::AsRawFd::as_raw_fd(&tty);
let mut term: libc::termios = unsafe { std::mem::zeroed() };
let had_termios = unsafe { libc::tcgetattr(fd, &mut term) } == 0;
let saved = term;
if had_termios {
term.c_lflag &= !libc::ECHO;
unsafe { libc::tcsetattr(fd, libc::TCSANOW, &term) };
}
let mut value = Vec::new();
let mut byte = [0u8; 1];
let read = loop {
match tty.read(&mut byte) {
Ok(0) => break Err("EOF before a value was entered".to_string()),
Ok(_) if byte[0] == b'\n' => break Ok(()),
Ok(_) => value.push(byte[0]),
Err(e) => break Err(e.to_string()),
}
};
if had_termios {
unsafe { libc::tcsetattr(fd, libc::TCSANOW, &saved) };
}
let _ = writeln!(tty);
read?;
let s = String::from_utf8_lossy(&value);
let s = s.trim_end_matches('\r').trim();
if s.is_empty() {
return Err("empty value".to_string());
}
Ok(s.to_string())
}