embed_crypto: add embed_read_passphrase (ring-backed termios prompt)

ober

93996b185260db049065ffd4f1f9a93f3410755b

diff --git a/jerboa-native-rs/src/embed_crypto.rs b/jerboa-native-rs/src/embed_crypto.rs
index 326da3d..2752413 100644
--- a/jerboa-native-rs/src/embed_crypto.rs
+++ b/jerboa-native-rs/src/embed_crypto.rs
@@ -168,3 +168,82 @@ pub extern "C" fn embed_random_bytes(buf: *mut u8, len: usize) -> i32 {
         }
     })
 }
+
+/// Read a passphrase from /dev/tty with echo disabled. Writes up to
+/// buflen-1 bytes plus a trailing NUL into buf, returns the number of
+/// bytes read (excluding the NUL) or -1 on error. Mirrors the legacy
+/// embed_read_passphrase from embed-crypto.c so gen-embed.ss can load
+/// libjerboa_native.{so,dylib} and drop the hand-rolled C entirely.
+#[no_mangle]
+pub extern "C" fn embed_read_passphrase(
+    prompt: *const u8,
+    buf: *mut u8,
+    buflen: i32,
+) -> i32 {
+    use libc::{c_int, c_void, close, open, read, tcgetattr, tcsetattr, termios, write, ECHO,
+               EINTR, O_RDWR, TCSANOW};
+    fn last_errno() -> i32 {
+        std::io::Error::last_os_error().raw_os_error().unwrap_or(0)
+    }
+    if buf.is_null() || buflen <= 1 {
+        return -1;
+    }
+    unsafe {
+        let path = b"/dev/tty\0".as_ptr() as *const i8;
+        let fd: c_int = open(path, O_RDWR);
+        if fd < 0 {
+            return -1;
+        }
+
+        let mut old_term: termios = std::mem::zeroed();
+        if tcgetattr(fd, &mut old_term) != 0 {
+            close(fd);
+            return -1;
+        }
+        let mut new_term = old_term;
+        new_term.c_lflag &= !ECHO;
+        tcsetattr(fd, TCSANOW, &new_term);
+
+        if !prompt.is_null() {
+            // strlen on the C string
+            let mut plen = 0usize;
+            while *prompt.add(plen) != 0 {
+                plen += 1;
+                if plen > 4096 { break; }
+            }
+            let mut written = 0usize;
+            while written < plen {
+                let w = write(fd, prompt.add(written) as *const c_void, plen - written);
+                if w < 0 {
+                    if last_errno() == EINTR { continue; }
+                    break;
+                }
+                written += w as usize;
+            }
+        }
+
+        let mut len: i32 = 0;
+        while len < buflen - 1 {
+            let mut c: u8 = 0;
+            let n = read(fd, (&mut c) as *mut u8 as *mut c_void, 1);
+            if n < 0 {
+                if *libc::__error() == EINTR { continue; }
+                break;
+            }
+            if n <= 0 { break; }
+            if c == b'\n' || c == b'\r' { break; }
+            *buf.add(len as usize) = c;
+            len += 1;
+        }
+        *buf.add(len as usize) = 0;
+
+        tcsetattr(fd, TCSANOW, &old_term);
+        loop {
+            let n = write(fd, b"\n\0".as_ptr() as *const c_void, 1);
+            if n < 0 && last_errno() == EINTR { continue; }
+            break;
+        }
+        close(fd);
+        len
+    }
+}