From c9b296f1174b96b5277a608867d9936d4ff292c0 Mon Sep 17 00:00:00 2001 From: Wolfgang Bumiller Date: Wed, 20 Feb 2019 12:08:58 +0100 Subject: [PATCH] tools: add tty helper module with read_password() and stdin_isatty() functions Signed-off-by: Wolfgang Bumiller --- src/tools.rs | 1 + src/tools/tty.rs | 84 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 src/tools/tty.rs diff --git a/src/tools.rs b/src/tools.rs index 55657599..2ee7a7bd 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -26,6 +26,7 @@ pub mod common_regex; pub mod ticket; pub mod borrow; pub mod fs; +pub mod tty; /// Macro to write error-handling blocks (like perl eval {}) /// diff --git a/src/tools/tty.rs b/src/tools/tty.rs new file mode 100644 index 00000000..bbcc8e9e --- /dev/null +++ b/src/tools/tty.rs @@ -0,0 +1,84 @@ +//! Helpers for terminal interaction + +use std::io::{Read, Write}; +use std::os::unix::io::AsRawFd; + +use failure::*; + +use crate::try_block; + +/// Returns whether the current stdin is a tty . +pub fn stdin_isatty() -> bool { + unsafe { libc::isatty(std::io::stdin().as_raw_fd()) == 1 } +} + +/// Read a password from stdin, masking the echoed output with asterisks and writing a query first. +pub fn read_password(query: &str) -> Result, Error> { + let input = std::io::stdin(); + if unsafe { libc::isatty(input.as_raw_fd()) } != 1 { + let mut out = String::new(); + input.read_line(&mut out)?; + return Ok(out.into_bytes()); + } + + let mut out = std::io::stdout(); + let _ignore_error = out.write_all(query.as_bytes()); + let _ignore_error = out.flush(); + + let infd = input.as_raw_fd(); + let mut termios: libc::termios = unsafe { std::mem::uninitialized() }; + if unsafe { libc::tcgetattr(infd, &mut termios) } != 0 { + bail!("tcgetattr() failed"); + } + let old_termios = termios.clone(); + unsafe { + libc::cfmakeraw(&mut termios); + } + if unsafe { libc::tcsetattr(infd, libc::TCSANOW, &termios) } != 0 { + bail!("tcsetattr() failed"); + } + + let mut password = Vec::::new(); + let mut asterisks = true; + + let ok: Result<(), Error> = try_block!({ + for byte in input.bytes() { + let byte = byte?; + match byte { + 3 => bail!("cancelled"), // ^C + 4 => break, // ^D / EOF + 9 => asterisks = false, // tab disables echo + 0xA | 0xD => { // newline, we're done + let _ignore_error = out.write_all("\r\n".as_bytes()); + let _ignore_error = out.flush(); + break; + } + 0x7F => { // backspace + if password.len() > 0{ + password.pop(); + if asterisks { + let _ignore_error = out.write_all("\x08 \x08".as_bytes()); + let _ignore_error = out.flush(); + } + } + } + other => { + password.push(other); + if asterisks { + let _ignore_error = out.write_all("*".as_bytes()); + let _ignore_error = out.flush(); + } + } + } + } + Ok(()) + }); + if unsafe { libc::tcsetattr(infd, libc::TCSANOW, &old_termios) } != 0 { + // not fatal... + eprintln!("failed to reset terminal attributes!"); + } + match ok { + Ok(_) => Ok(password), + Err(e) => Err(e), + } +}