vmbase: Add support for fputs(), stdout, stderr

Support printing to stdout and stderr from C by wrapping the
corresponding crate::console functions.

Implement fwrite() for when Clang substitutes fputs() for it, even if
both symbols are undefined in the translation unit ... See LLVM's
SimplifyLibCalls.

Bug: 256148034
Test: -
Change-Id: I9e77925f8003985ccbf1c625358cc81f7be11371
diff --git a/vmbase/src/bionic.rs b/vmbase/src/bionic.rs
index b4a2f7b..6f88cf6 100644
--- a/vmbase/src/bionic.rs
+++ b/vmbase/src/bionic.rs
@@ -16,11 +16,17 @@
 
 use core::ffi::c_char;
 use core::ffi::c_int;
+use core::ffi::c_void;
 use core::ffi::CStr;
+use core::slice;
+use core::str;
 
+use crate::console;
 use crate::eprintln;
 use crate::linker;
 
+const EOF: c_int = -1;
+
 /// Reference to __stack_chk_guard.
 pub static STACK_CHK_GUARD: &u64 = unsafe { &linker::__stack_chk_guard };
 
@@ -43,6 +49,11 @@
     &mut ERRNO as *mut _
 }
 
+fn set_errno(value: c_int) {
+    // SAFETY - vmbase is currently single-threaded.
+    unsafe { ERRNO = value };
+}
+
 /// Reports a fatal error detected by Bionic.
 ///
 /// # Safety
@@ -62,3 +73,56 @@
         eprintln!("FATAL BIONIC ERROR: {prefix}: \"{format}\" (unformatted)");
     }
 }
+
+#[repr(usize)]
+/// Arbitrary token FILE pseudo-pointers used by C to refer to the default streams.
+enum File {
+    Stdout = 0x7670cf00,
+    Stderr = 0x9d118200,
+}
+
+impl TryFrom<usize> for File {
+    type Error = &'static str;
+
+    fn try_from(value: usize) -> Result<Self, Self::Error> {
+        match value {
+            x if x == File::Stdout as _ => Ok(File::Stdout),
+            x if x == File::Stderr as _ => Ok(File::Stderr),
+            _ => Err("Received Invalid FILE* from C"),
+        }
+    }
+}
+
+#[no_mangle]
+static stdout: File = File::Stdout;
+#[no_mangle]
+static stderr: File = File::Stderr;
+
+#[no_mangle]
+extern "C" fn fputs(c_str: *const c_char, stream: usize) -> c_int {
+    // SAFETY - Just like libc, we need to assume that `s` is a valid NULL-terminated string.
+    let c_str = unsafe { CStr::from_ptr(c_str) };
+
+    if let (Ok(s), Ok(_)) = (c_str.to_str(), File::try_from(stream)) {
+        console::write_str(s);
+        0
+    } else {
+        set_errno(EOF);
+        EOF
+    }
+}
+
+#[no_mangle]
+extern "C" fn fwrite(ptr: *const c_void, size: usize, nmemb: usize, stream: usize) -> usize {
+    let length = size.saturating_mul(nmemb);
+
+    // SAFETY - Just like libc, we need to assume that `ptr` is valid.
+    let bytes = unsafe { slice::from_raw_parts(ptr as *const u8, length) };
+
+    if let (Ok(s), Ok(_)) = (str::from_utf8(bytes), File::try_from(stream)) {
+        console::write_str(s);
+        length
+    } else {
+        0
+    }
+}