tiny_std/linux/
get_pass.rs

1use crate::error::Error;
2use core::ops::{BitAndAssign, BitOrAssign};
3use rusl::platform::{SetAction, STDIN};
4use rusl::termios::{tcgetattr, tcsetattr};
5
6/// Sets the terminal to no echo and waits for a line to be entered.
7/// The raw input, including the newline, will be put into the buffer and converted to a &str.
8/// If the buffer is too small to fit the input, an attempt will be made to drain stdin and then
9/// return an error.
10/// # Errors
11/// Buffer undersized.
12/// Stdin unreadable.
13/// Fail to set terminal attributes.
14pub fn get_pass(buf: &mut [u8]) -> crate::error::Result<&str> {
15    /// If the user supplied a buffer that's too small we'd still like to drain stdin
16    /// so that it doesn't leak.
17    /// # Safety
18    /// Safe if the buffer is not empty
19    #[cold]
20    unsafe fn drain_newline(buf: &mut [u8]) -> Result<(), Error> {
21        loop {
22            let res = rusl::unistd::read(STDIN, buf)?;
23            // EOF or full line read
24            if res == 0 || res < buf.len() {
25                return Ok(());
26            }
27            let last = *buf.last().unwrap_unchecked();
28            if last == b'\n' {
29                return Ok(());
30            }
31        }
32    }
33    if buf.is_empty() {
34        return Err(Error::Uncategorized(
35            "Supplied a zero-length buffer to getpass, need an initialized buffer to populate",
36        ));
37    }
38    let mut stdin_term = tcgetattr(STDIN)?;
39    let orig_flags = stdin_term;
40    let iflag = &mut stdin_term.0.c_lflag;
41    iflag.bitand_assign(!(rusl::platform::ECHO as u32));
42    iflag.bitor_assign(rusl::platform::ECHONL as u32);
43    tcsetattr(STDIN, SetAction::NOW, &stdin_term)?;
44    let read = rusl::unistd::read(STDIN, buf)?;
45    unsafe {
46        // Safety, we know the buffer is not empty.
47        if read == buf.len() && *buf.last().unwrap_unchecked() != b'\n' {
48            drain_newline(buf)?;
49            tcsetattr(STDIN, SetAction::NOW, &orig_flags)?;
50            return Err(Error::Uncategorized(
51                "Supplied a buffer that was too small to getpass, buffer overrun.",
52            ));
53        }
54    }
55    tcsetattr(STDIN, SetAction::NOW, &orig_flags)?;
56    core::str::from_utf8(&buf[..read])
57        .map_err(|_| Error::no_code("Failed to convert read password to utf8.  "))
58}