From 545e9e7b2925b369a7c5125a812b6ebc26f59ff2 Mon Sep 17 00:00:00 2001
From: Agoston Szepessy <agoston.the.dev@gmail.com>
Date: Tue, 23 Jul 2024 16:06:14 +0200
Subject: [PATCH] Implement getpass using File API

Returns a Result for better error handling.
---
 src/header/unistd/getpass.rs | 81 ++++++++++++++++++++++++++++++++++++
 src/header/unistd/mod.rs     | 55 ++----------------------
 2 files changed, 85 insertions(+), 51 deletions(-)
 create mode 100644 src/header/unistd/getpass.rs

diff --git a/src/header/unistd/getpass.rs b/src/header/unistd/getpass.rs
new file mode 100644
index 00000000..24e7c14c
--- /dev/null
+++ b/src/header/unistd/getpass.rs
@@ -0,0 +1,81 @@
+use core::ptr;
+
+use alloc::vec::Vec;
+
+use crate::{
+    fs::File,
+    header::{
+        fcntl::{O_CLOEXEC, O_RDWR},
+        limits::PASS_MAX,
+        stdio, termios,
+    },
+    io::{self, Read},
+};
+
+use crate::platform::types::*;
+
+#[derive(Debug)]
+enum Error {
+    Io(io::Error),
+    CannotConvertFd,
+}
+
+impl From<io::Error> for Error {
+    fn from(value: io::Error) -> Self {
+        Error::Io(value)
+    }
+}
+
+unsafe fn getpass_rs(prompt: *const c_char) -> Result<*mut c_char, Error> {
+    let mut f = File::open(c_str!("/dev/tty"), O_RDWR | O_CLOEXEC)?;
+
+    let mut term = termios::termios::default();
+    termios::tcgetattr(f.fd, &mut term as *mut termios::termios);
+    let old_term = term.clone();
+
+    term.c_iflag &= !(termios::IGNCR | termios::INLCR) as u32;
+    term.c_iflag |= termios::ICRNL as u32;
+    term.c_lflag &= !(termios::ECHO | termios::ISIG) as u32;
+    term.c_lflag |= termios::ICANON as u32;
+
+    let cfile = stdio::fdopen(f.fd, c_str!("w+e").as_ptr());
+
+    if cfile.is_null() {
+        return Err(Error::CannotConvertFd);
+    }
+
+    termios::tcsetattr(f.fd, termios::TCSAFLUSH, &term as *const termios::termios);
+    stdio::fputs(prompt, cfile);
+    stdio::fflush(cfile);
+
+    let mut buff = Vec::new();
+    let mut len = f.read(&mut buff)?;
+
+    static mut PASSBUFF: [c_char; PASS_MAX] = [0; PASS_MAX];
+
+    for (dst, src) in PASSBUFF.iter_mut().zip(&buff) {
+        *dst = *src as c_char;
+    }
+
+    if len > 0 {
+        if PASSBUFF[len - 1] == b'\n' as c_char || PASSBUFF.len() == len {
+            len -= 1;
+        }
+    }
+
+    PASSBUFF[len] = 0;
+
+    termios::tcsetattr(
+        f.fd,
+        termios::TCSAFLUSH,
+        &old_term as *const termios::termios,
+    );
+    stdio::fputs(c_str!("\n").as_ptr(), cfile);
+
+    Ok(PASSBUFF.as_mut_ptr())
+}
+
+#[no_mangle]
+pub extern "C" fn getpass(prompt: *const c_char) -> *mut c_char {
+    unsafe { getpass_rs(prompt).unwrap_or(ptr::null_mut()) }
+}
diff --git a/src/header/unistd/mod.rs b/src/header/unistd/mod.rs
index 1bc169d9..da7977e9 100644
--- a/src/header/unistd/mod.rs
+++ b/src/header/unistd/mod.rs
@@ -11,9 +11,7 @@ use crate::{
     c_str::CStr,
     header::{
         crypt::{crypt_data, crypt_r},
-        errno, fcntl,
-        limits::{self, PASS_MAX},
-        stdio,
+        errno, fcntl, limits,
         stdlib::getenv,
         sys_ioctl, sys_resource, sys_time, sys_utsname, termios,
         time::timespec,
@@ -21,14 +19,16 @@ use crate::{
     platform::{self, types::*, Pal, Sys},
     pthread::ResultExt,
 };
+
 use alloc::collections::LinkedList;
 
-pub use self::{brk::*, getopt::*, pathconf::*, sysconf::*};
+pub use self::{brk::*, getopt::*, getpass::getpass, pathconf::*, sysconf::*};
 
 use super::errno::{E2BIG, ENOMEM};
 
 mod brk;
 mod getopt;
+mod getpass;
 mod pathconf;
 mod sysconf;
 
@@ -452,53 +452,6 @@ pub extern "C" fn getpagesize() -> c_int {
         .expect("page size not representable as type `int`")
 }
 
-#[no_mangle]
-pub unsafe extern "C" fn getpass(prompt: *const c_char) -> *mut c_char {
-    let tty = stdio::fopen(c_str!("/dev/tty").as_ptr(), c_str!("w+e").as_ptr());
-
-    if tty.is_null() {
-        return ptr::null_mut();
-    }
-
-    let fd = stdio::fileno(tty);
-
-    let mut term = termios::termios::default();
-    termios::tcgetattr(fd, &mut term as *mut termios::termios);
-    let old_temr = term.clone();
-
-    term.c_iflag &= !(termios::IGNCR | termios::INLCR) as u32;
-    term.c_iflag |= termios::ICRNL as u32;
-    term.c_lflag &= !(termios::ECHO | termios::ISIG) as u32;
-    term.c_lflag |= termios::ICANON as u32;
-
-    termios::tcsetattr(fd, termios::TCSAFLUSH, &term as *const termios::termios);
-    stdio::fputs(prompt, tty);
-    stdio::fflush(tty);
-
-    static mut PASSBUFF: [c_char; PASS_MAX] = [0; PASS_MAX];
-
-    let len = read(fd, PASSBUFF.as_mut_ptr() as *const c_void, PASSBUFF.len());
-
-    if len >= 0 {
-        let mut l = len as usize;
-        if PASSBUFF[l - 1] == b'\n' as c_char || PASSBUFF.len() == l {
-            l -= 1;
-        }
-
-        PASSBUFF[l] = 0;
-    }
-
-    termios::tcsetattr(fd, termios::TCSAFLUSH, &old_temr as *const termios::termios);
-    stdio::fputs(c_str!("\n").as_ptr(), tty);
-    stdio::fclose(tty);
-
-    if len < 0 {
-        return ptr::null_mut();
-    }
-
-    PASSBUFF.as_mut_ptr()
-}
-
 #[no_mangle]
 pub extern "C" fn getpgid(pid: pid_t) -> pid_t {
     Sys::getpgid(pid)
-- 
GitLab