From 0a904a21fef178c37672089f0af212220cb5c76c Mon Sep 17 00:00:00 2001
From: Mark Poliakov <mark@alnyan.me>
Date: Tue, 12 Nov 2024 11:01:39 +0200
Subject: [PATCH] libc: ctype.h, most of stdio.h, string.h

---
 userspace/lib/ygglibc/include/bits/ctype.h    |   7 +
 userspace/lib/ygglibc/src/allocator.rs        |  76 +++--
 userspace/lib/ygglibc/src/error.rs            |  15 +-
 .../ygglibc/src/headers/ctype/cbindgen.toml   |  15 +
 .../lib/ygglibc/src/headers/ctype/mod.rs      | 161 ++++++++++
 .../lib/ygglibc/src/headers/errno/mod.rs      |   5 +-
 userspace/lib/ygglibc/src/headers/mod.rs      |   9 +-
 .../lib/ygglibc/src/headers/stdio/file.rs     | 148 ++++++---
 .../lib/ygglibc/src/headers/stdio/get_put.rs  | 280 ++++++++++++++++++
 userspace/lib/ygglibc/src/headers/stdio/io.rs |  99 +------
 .../lib/ygglibc/src/headers/stdio/mod.rs      |  17 +-
 .../lib/ygglibc/src/headers/stdio/util.rs     |  47 ++-
 .../lib/ygglibc/src/headers/string/str.rs     |  59 ++--
 userspace/lib/ygglibc/src/io/managed.rs       | 268 +++++++++++++++--
 userspace/lib/ygglibc/src/io/mod.rs           |  19 +-
 15 files changed, 980 insertions(+), 245 deletions(-)
 create mode 100644 userspace/lib/ygglibc/include/bits/ctype.h
 create mode 100644 userspace/lib/ygglibc/src/headers/ctype/cbindgen.toml
 create mode 100644 userspace/lib/ygglibc/src/headers/ctype/mod.rs
 create mode 100644 userspace/lib/ygglibc/src/headers/stdio/get_put.rs

diff --git a/userspace/lib/ygglibc/include/bits/ctype.h b/userspace/lib/ygglibc/include/bits/ctype.h
new file mode 100644
index 00000000..bf667a49
--- /dev/null
+++ b/userspace/lib/ygglibc/include/bits/ctype.h
@@ -0,0 +1,7 @@
+#ifndef _YGGDRASIL_CTYPE_H
+#define _YGGDRASIL_CTYPE_H 1
+
+#define _tolower(ch) tolower(ch)
+#define _toupper(ch) toupper(ch)
+
+#endif
diff --git a/userspace/lib/ygglibc/src/allocator.rs b/userspace/lib/ygglibc/src/allocator.rs
index 32e68751..3c8164f6 100644
--- a/userspace/lib/ygglibc/src/allocator.rs
+++ b/userspace/lib/ygglibc/src/allocator.rs
@@ -1,18 +1,26 @@
 use core::{
-    alloc::{GlobalAlloc, Layout}, ffi::c_void, ptr::{self, null, null_mut, NonNull}
+    alloc::{GlobalAlloc, Layout},
+    ffi::c_void,
+    ptr::{self, null_mut, NonNull},
 };
 
 use libyalloc::{allocator::BucketAllocator, sys::PageProvider};
 use yggdrasil_rt::{mem::MappingSource, sys as syscall};
 
-use crate::{error::EResult, headers::errno};
+use crate::{
+    error::EResult,
+    headers::{
+        errno,
+        string::{memcpy, memset},
+    }, sync::Mutex,
+};
 
 struct Allocator;
 struct PageProviderImpl;
 
 unsafe impl GlobalAlloc for Allocator {
     unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
-        let pointer = YALLOC.allocate(layout);
+        let pointer = YALLOC.lock().allocate(layout);
         match pointer {
             Some(ptr) => ptr.as_ptr(),
             None => null_mut(),
@@ -21,7 +29,7 @@ unsafe impl GlobalAlloc for Allocator {
 
     unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
         let pointer = NonNull::new(ptr).expect("NULL pointer passed to dealloc()");
-        YALLOC.free(pointer, layout);
+        YALLOC.lock().free(pointer, layout);
     }
 }
 
@@ -43,10 +51,10 @@ impl PageProvider for PageProviderImpl {
     }
 }
 
-const MALLOC_HEADER_SIZE: usize = size_of::<usize>();
+const MALLOC_HEADER_SIZE: usize = 16;
 
 unsafe fn get_allocation(ptr: NonNull<u8>) -> (NonNull<u8>, Layout) {
-    assert!(usize::from(ptr.addr()) > 0x10);
+    assert!(usize::from(ptr.addr()) > MALLOC_HEADER_SIZE);
     let real_ptr = ptr.sub(MALLOC_HEADER_SIZE);
     let size = *real_ptr.cast::<usize>().as_ref();
     let layout = Layout::from_size_align(size, 16).unwrap();
@@ -54,49 +62,69 @@ unsafe fn get_allocation(ptr: NonNull<u8>) -> (NonNull<u8>, Layout) {
 }
 
 pub fn c_alloc(size: usize, mut align: usize, zero: bool) -> EResult<NonNull<c_void>> {
-    assert!(align.is_power_of_two());
-    if align < align_of::<usize>() {
-        align = align_of::<usize>();
+    if align < 16 {
+        align = 16;
     }
     if align > 16 {
         todo!()
     }
-    let offset = (MALLOC_HEADER_SIZE + align - 1) & !(align - 1);
-    let size = offset + size;
+    let size = size + MALLOC_HEADER_SIZE;
     let layout = Layout::from_size_align(size, align).unwrap();
 
-    let ptr = match unsafe { YALLOC.allocate(layout) } {
+    let ptr = match YALLOC.lock().allocate(layout) {
         Some(value) => value,
         None => return EResult::Err(errno::ENOMEM),
     };
 
     if zero {
-        // TODO
+        unsafe {
+            memset(ptr.as_ptr().cast(), 0, size);
+        }
     }
 
     unsafe {
         // Write the size right below the pointer
-        ptr.add(offset - size_of::<usize>()).cast::<usize>().write(size);
+        ptr.cast::<usize>().write(size);
     }
 
-    unsafe { EResult::Ok(ptr.cast::<c_void>().add(offset)) }
+    unsafe { EResult::Ok(ptr.cast::<c_void>().add(MALLOC_HEADER_SIZE)) }
+}
+
+pub unsafe fn c_realloc(old_ptr: Option<NonNull<c_void>>, size: usize) -> EResult<NonNull<c_void>> {
+    match old_ptr {
+        Some(old_ptr) => {
+            // TODO libyalloc realloc
+            let (real_old_ptr, old_layout) = get_allocation(old_ptr.cast());
+            let new_ptr = c_alloc(size, 16, false)?;
+
+            memcpy(
+                new_ptr.cast().as_ptr(),
+                old_ptr.cast().as_ptr(),
+                old_layout.size() - MALLOC_HEADER_SIZE,
+            );
+
+            YALLOC.lock().free(real_old_ptr, old_layout);
+
+            EResult::Ok(new_ptr)
+        }
+        None => c_alloc(size, 16, false),
+    }
+}
+
+pub unsafe fn c_free(ptr: NonNull<c_void>) {
+    let (real_ptr, layout) = get_allocation(ptr.cast());
+    YALLOC.lock().free(real_ptr, layout);
 }
 
 pub unsafe fn malloc(size: usize) -> EResult<NonNull<c_void>> {
-    todo!()
-    // // TODO errno setting
-    // match c_alloc(size, 16, false) {
-    //     Some(ptr) => ptr.as_ptr(),
-    //     None => null_mut()
-    // }
+    c_alloc(size, 16, false)
 }
 
 pub unsafe fn free(ptr: NonNull<c_void>) {
-    todo!()
+    c_free(ptr)
 }
 
 #[global_allocator]
 static ALLOCATOR: Allocator = Allocator;
 
-// TODO locking for multithreaded binaries
-static mut YALLOC: BucketAllocator<PageProviderImpl> = BucketAllocator::new();
+static YALLOC: Mutex<BucketAllocator<PageProviderImpl>> = Mutex::new(BucketAllocator::new());
diff --git a/userspace/lib/ygglibc/src/error.rs b/userspace/lib/ygglibc/src/error.rs
index 58a6bc54..199c32f6 100644
--- a/userspace/lib/ygglibc/src/error.rs
+++ b/userspace/lib/ygglibc/src/error.rs
@@ -27,6 +27,10 @@ macro impl_from_residual($($ty:ty),+) {
 #[allow(non_upper_case_globals)]
 pub static mut errno: Errno = Errno(0);
 
+pub trait ResultExt<T, E> {
+    fn e_map_err<F: FnOnce(E) -> Errno>(self, map: F) -> EResult<T>;
+}
+
 pub trait CResult {
     const ERROR: Self;
 }
@@ -65,7 +69,7 @@ pub struct CFdResult(c_int);
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 #[repr(transparent)]
-pub struct COffsetResult(off_t);
+pub struct COffsetResult(pub(crate) off_t);
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 #[repr(transparent)]
@@ -124,6 +128,15 @@ impl<T> EResult<T> {
     }
 }
 
+impl<T, E> ResultExt<T, E> for Result<T, E> {
+    fn e_map_err<F: FnOnce(E) -> Errno>(self, map: F) -> EResult<T> {
+        match self {
+            Ok(value) => EResult::Ok(value),
+            Err(value) => EResult::Err(map(value))
+        }
+    }
+}
+
 impl<T> Try for EResult<T> {
     type Output = T;
     type Residual = EResult<Infallible>;
diff --git a/userspace/lib/ygglibc/src/headers/ctype/cbindgen.toml b/userspace/lib/ygglibc/src/headers/ctype/cbindgen.toml
new file mode 100644
index 00000000..23e42008
--- /dev/null
+++ b/userspace/lib/ygglibc/src/headers/ctype/cbindgen.toml
@@ -0,0 +1,15 @@
+language = "C"
+style = "Type"
+
+sys_includes = [
+    "locale.h"
+]
+no_includes = true
+
+include_guard = "_CTYPE_H"
+trailer = "#include <bits/ctype.h>"
+
+usize_type = "size_t"
+isize_type = "ssize_t"
+
+[export]
diff --git a/userspace/lib/ygglibc/src/headers/ctype/mod.rs b/userspace/lib/ygglibc/src/headers/ctype/mod.rs
new file mode 100644
index 00000000..62f913be
--- /dev/null
+++ b/userspace/lib/ygglibc/src/headers/ctype/mod.rs
@@ -0,0 +1,161 @@
+use core::ffi::c_int;
+
+use super::locale::locale_t;
+
+#[no_mangle]
+unsafe extern "C" fn isalnum(ch: c_int) -> c_int {
+    (ch < 0xFF && (ch as u8).is_ascii_alphanumeric()) as _
+}
+
+#[no_mangle]
+unsafe extern "C" fn isalpha(ch: c_int) -> c_int {
+    (ch < 0xFF && (ch as u8).is_ascii_alphabetic()) as _
+}
+
+#[no_mangle]
+unsafe extern "C" fn isascii(ch: c_int) -> c_int {
+    (ch < 0xFF && (ch as u8).is_ascii()) as _
+}
+
+#[no_mangle]
+unsafe extern "C" fn isblank(ch: c_int) -> c_int {
+    (ch == b' ' as c_int || ch == b'\t' as c_int) as _
+}
+
+#[no_mangle]
+unsafe extern "C" fn iscntrl(ch: c_int) -> c_int {
+    (ch < 0xFF && (ch as u8).is_ascii_control()) as _
+}
+
+#[no_mangle]
+unsafe extern "C" fn isdigit(ch: c_int) -> c_int {
+    (ch < 0xFF && (ch as u8).is_ascii_digit()) as _
+}
+
+#[no_mangle]
+unsafe extern "C" fn isgraph(ch: c_int) -> c_int {
+    (ch < 0xFF && (ch as u8).is_ascii_graphic()) as _
+}
+
+#[no_mangle]
+unsafe extern "C" fn islower(ch: c_int) -> c_int {
+    (ch < 0xFF && (ch as u8).is_ascii_lowercase()) as _
+}
+
+#[no_mangle]
+unsafe extern "C" fn isprint(ch: c_int) -> c_int {
+    (ch < 0xFF && ((ch as u8).is_ascii_graphic() || (ch as u8).is_ascii_whitespace())) as _
+}
+
+#[no_mangle]
+unsafe extern "C" fn ispunct(ch: c_int) -> c_int {
+    (ch < 0xFF && (ch as u8).is_ascii_punctuation()) as _
+}
+
+#[no_mangle]
+unsafe extern "C" fn isspace(ch: c_int) -> c_int {
+    (ch < 0xFF && (ch as u8).is_ascii_whitespace()) as _
+}
+
+#[no_mangle]
+unsafe extern "C" fn isupper(ch: c_int) -> c_int {
+    (ch < 0xFF && (ch as u8).is_ascii_uppercase()) as _
+}
+
+#[no_mangle]
+unsafe extern "C" fn isxdigit(ch: c_int) -> c_int {
+    (ch < 0xFF && (ch as u8).is_ascii_hexdigit()) as _
+}
+
+#[no_mangle]
+unsafe extern "C" fn toascii(ch: c_int) -> c_int {
+    ch & 0x7F
+}
+
+#[no_mangle]
+unsafe extern "C" fn tolower(ch: c_int) -> c_int {
+    if ch < 0xFF {
+        (ch as u8).to_ascii_lowercase() as _
+    } else {
+        ch
+    }
+}
+
+#[no_mangle]
+unsafe extern "C" fn toupper(ch: c_int) -> c_int {
+    if ch < 0xFF {
+        (ch as u8).to_ascii_uppercase() as _
+    } else {
+        ch
+    }
+}
+
+#[no_mangle]
+unsafe extern "C" fn isalnum_l(_ch: c_int, _locale: locale_t) -> c_int {
+    unimplemented!()
+}
+
+#[no_mangle]
+unsafe extern "C" fn isalpha_l(_ch: c_int, _locale: locale_t) -> c_int {
+    unimplemented!()
+}
+
+#[no_mangle]
+unsafe extern "C" fn isblank_l(_ch: c_int, _locale: locale_t) -> c_int {
+    unimplemented!()
+}
+
+#[no_mangle]
+unsafe extern "C" fn iscntrl_l(_ch: c_int, _locale: locale_t) -> c_int {
+    unimplemented!()
+}
+
+#[no_mangle]
+unsafe extern "C" fn isdigit_l(_ch: c_int, _locale: locale_t) -> c_int {
+    unimplemented!()
+}
+
+#[no_mangle]
+unsafe extern "C" fn isgraph_l(_ch: c_int, _locale: locale_t) -> c_int {
+    unimplemented!()
+}
+
+#[no_mangle]
+unsafe extern "C" fn islower_l(_ch: c_int, _locale: locale_t) -> c_int {
+    unimplemented!()
+}
+
+#[no_mangle]
+unsafe extern "C" fn isprint_l(_ch: c_int, _locale: locale_t) -> c_int {
+    unimplemented!()
+}
+
+#[no_mangle]
+unsafe extern "C" fn ispunct_l(_ch: c_int, _locale: locale_t) -> c_int {
+    unimplemented!()
+}
+
+#[no_mangle]
+unsafe extern "C" fn isspace_l(_ch: c_int, _locale: locale_t) -> c_int {
+    unimplemented!()
+}
+
+#[no_mangle]
+unsafe extern "C" fn isupper_l(_ch: c_int, _locale: locale_t) -> c_int {
+    unimplemented!()
+}
+
+#[no_mangle]
+unsafe extern "C" fn isxdigit_l(_ch: c_int, _locale: locale_t) -> c_int {
+    unimplemented!()
+}
+
+#[no_mangle]
+unsafe extern "C" fn tolower_l(_ch: c_int, _locale: locale_t) -> c_int {
+    unimplemented!()
+}
+
+#[no_mangle]
+unsafe extern "C" fn toupper_l(_ch: c_int, _locale: locale_t) -> c_int {
+    unimplemented!()
+}
diff --git a/userspace/lib/ygglibc/src/headers/errno/mod.rs b/userspace/lib/ygglibc/src/headers/errno/mod.rs
index b8b68b02..34a238ac 100644
--- a/userspace/lib/ygglibc/src/headers/errno/mod.rs
+++ b/userspace/lib/ygglibc/src/headers/errno/mod.rs
@@ -1,4 +1,4 @@
-use core::ffi::{c_char, c_int, CStr};
+use core::ffi::{c_int, CStr};
 
 macro_rules! static_cstr {
     ($string:expr) => {
@@ -136,12 +136,11 @@ static ERRNO_STRINGS: &[&CStr] = &[
 ];
 
 impl Errno {
-    pub fn to_c_str(&self) -> *const c_char {
+    pub fn to_c_str(&self) -> &CStr {
         ERRNO_STRINGS
             .get(self.0 as usize)
             .copied()
             .unwrap_or(SUCCESS)
-            .as_ptr() as *const _
     }
 
     pub fn from_c_int(v: c_int) -> Option<Self> {
diff --git a/userspace/lib/ygglibc/src/headers/mod.rs b/userspace/lib/ygglibc/src/headers/mod.rs
index 0fa3a635..8b5efa7a 100644
--- a/userspace/lib/ygglibc/src/headers/mod.rs
+++ b/userspace/lib/ygglibc/src/headers/mod.rs
@@ -2,13 +2,13 @@
 
 // <aio.h>                  -
 // <arpa/inet.h>            -
-// <assert.h>               -
+// <assert.h>               +
 // <complex.h>              -
 // <cpio.h>                 -
-// <ctype.h>                -
+// <ctype.h>                +
 // <dirent.h>               -
 // <dlfcn.h>                -
-// <errno.h>                ~
+// <errno.h>                +
 // <fcntl.h>                -
 // <fenv.h>                 -
 // <float.h>                -
@@ -83,12 +83,13 @@
 // <wctype.h>				-
 // <wordexp.h>				-
 
+pub mod ctype;
 pub mod errno;
 pub mod locale;
 pub mod stdio;
 pub mod stdlib;
 pub mod string;
-pub mod unistd;
 pub mod strings;
+pub mod unistd;
 
 pub mod sys_types;
diff --git a/userspace/lib/ygglibc/src/headers/stdio/file.rs b/userspace/lib/ygglibc/src/headers/stdio/file.rs
index 5fb29fd8..a231f454 100644
--- a/userspace/lib/ygglibc/src/headers/stdio/file.rs
+++ b/userspace/lib/ygglibc/src/headers/stdio/file.rs
@@ -1,22 +1,30 @@
 use core::{
     ffi::{c_char, c_int, c_long, c_void},
-    ptr::NonNull,
+    ptr::{null_mut, NonNull},
 };
 
 use alloc::boxed::Box;
-use yggdrasil_rt::{debug_trace, io::OpenOptions, path::Path};
+use yggdrasil_rt::{
+    debug_trace,
+    io::{OpenOptions, RawFd, SeekFrom},
+    path::Path,
+};
 
 use crate::{
-    error::{CEofResult, CFdResult, CIntZeroResult, CPtrResult, EResult},
-    headers::sys_types::off_t,
+    error::{
+        CEofResult, CFdResult, CIntZeroResult, COffsetResult, CPtrResult, CResult, EResult,
+        ResultExt, TryFromExt,
+    },
+    headers::{errno, sys_types::off_t},
     io::{
         self,
         managed::{FileOpenSource, FILE},
+        Seek, Write,
     },
     util::{PointerExt, PointerStrExt},
 };
 
-use super::fpos_t;
+use super::{fpos_t, SEEK_SET, _IOFBF, _IOLBF, _IONBF};
 
 fn open_inner<O: FileOpenSource>(source: O, mode_str: &[u8]) -> EResult<NonNull<FILE>> {
     let opts = match mode_str {
@@ -43,7 +51,10 @@ fn open_inner<O: FileOpenSource>(source: O, mode_str: &[u8]) -> EResult<NonNull<
 }
 
 #[no_mangle]
-unsafe extern "C" fn clearerr(fp: *mut FILE) {}
+unsafe extern "C" fn clearerr(fp: *mut FILE) {
+    let fp = fp.ensure_mut();
+    fp.clear_error();
+}
 
 #[no_mangle]
 unsafe extern "C" fn fclose(fp: *mut FILE) -> CEofResult {
@@ -59,55 +70,72 @@ unsafe extern "C" fn fclose(fp: *mut FILE) -> CEofResult {
 
 #[no_mangle]
 unsafe extern "C" fn fdopen(fd: c_int, mode: *const c_char) -> CPtrResult<FILE> {
-    todo!()
+    let fd = RawFd::e_try_from(fd)?;
+    let mode = mode.ensure_cstr();
+    let file = open_inner(fd, mode.to_bytes())?;
+    CPtrResult::success(file)
 }
 
 #[no_mangle]
 unsafe extern "C" fn feof(fp: *mut FILE) -> c_int {
-    todo!()
+    let fp = fp.ensure_mut();
+    fp.is_eof() as _
 }
 
 #[no_mangle]
 unsafe extern "C" fn ferror(fp: *mut FILE) -> c_int {
-    todo!()
+    let fp = fp.ensure_mut();
+    fp.is_error() as _
 }
 
 #[no_mangle]
 unsafe extern "C" fn fflush(fp: *mut FILE) -> CIntZeroResult {
-    todo!()
+    let fp = fp.ensure_mut();
+    fp.flush()?;
+    CIntZeroResult::SUCCESS
 }
 
 #[no_mangle]
 unsafe extern "C" fn fileno(fp: *mut FILE) -> CFdResult {
-    todo!()
+    let fp = fp.ensure_mut();
+    let fd = fp.fd();
+    match fd {
+        Some(fd) => CFdResult::success(fd),
+        None => CFdResult::ERROR,
+    }
 }
 
 #[no_mangle]
 unsafe extern "C" fn flockfile(fp: *mut FILE) {
-    todo!()
+    let fp = fp.ensure_mut();
+    fp.lock();
 }
 
 #[no_mangle]
-unsafe extern "C" fn fgetpos(fp: *mut FILE, pos: *mut fpos_t) -> c_int {
-    todo!()
+unsafe extern "C" fn fgetpos(fp: *mut FILE, pos: *mut fpos_t) -> CIntZeroResult {
+    let fp = fp.ensure_mut();
+    let pos = pos.ensure_mut();
+    let p = fp.stream_position()?;
+    *pos = p;
+    CIntZeroResult::SUCCESS
 }
 
 #[no_mangle]
 unsafe extern "C" fn fmemopen(
-    buffer: *mut c_void,
-    size: usize,
-    mode: *const c_char,
+    _buffer: *mut c_void,
+    _size: usize,
+    _mode: *const c_char,
 ) -> CPtrResult<FILE> {
-    todo!()
+    unimplemented!()
 }
 
 #[no_mangle]
 unsafe extern "C" fn freopen(
-    path: *const c_char,
-    mode: *const c_char,
-    fp: *mut FILE,
+    _path: *const c_char,
+    _mode: *const c_char,
+    _fp: *mut FILE,
 ) -> CPtrResult<FILE> {
-    todo!()
+    unimplemented!()
 }
 
 #[no_mangle]
@@ -119,74 +147,100 @@ unsafe extern "C" fn fopen(path: *const c_char, mode: *const c_char) -> CPtrResu
 }
 
 #[no_mangle]
-unsafe extern "C" fn fseek(fp: *mut FILE, offset: c_long, whence: c_int) -> c_int {
-    todo!()
+unsafe extern "C" fn fseek(fp: *mut FILE, offset: c_long, whence: c_int) -> CIntZeroResult {
+    let offset: off_t = offset.try_into().e_map_err(|_| errno::EINVAL)?;
+    fseeko(fp, offset, whence)
 }
 
 #[no_mangle]
-unsafe extern "C" fn fseeko(fp: *mut FILE, offset: off_t, whence: c_int) -> c_int {
-    todo!()
+unsafe extern "C" fn fseeko(fp: *mut FILE, offset: off_t, whence: c_int) -> CIntZeroResult {
+    let seek = SeekFrom::e_try_from((offset, whence))?;
+    let fp = fp.ensure_mut();
+    fp.seek(seek)?;
+    CIntZeroResult::SUCCESS
 }
 
 #[no_mangle]
-unsafe extern "C" fn fsetpos(fp: *mut FILE, pos: *const fpos_t) -> c_int {
-    todo!()
+unsafe extern "C" fn fsetpos(fp: *mut FILE, pos: *const fpos_t) -> CIntZeroResult {
+    let fp = fp.ensure_mut();
+    let pos = *pos.ensure();
+    fp.seek(SeekFrom::Start(pos))?;
+    CIntZeroResult::SUCCESS
 }
 
 #[no_mangle]
 unsafe extern "C" fn ftell(fp: *mut FILE) -> c_long {
-    todo!()
+    match ftello(fp) {
+        COffsetResult::ERROR => -1,
+        COffsetResult(off) => off,
+    }
 }
 
 #[no_mangle]
-unsafe extern "C" fn ftello(fp: *mut FILE) -> off_t {
-    todo!()
+unsafe extern "C" fn ftello(fp: *mut FILE) -> COffsetResult {
+    let fp = fp.ensure_mut();
+    let pos = fp.stream_position()?;
+    COffsetResult::success(pos)
 }
 
 #[no_mangle]
-unsafe extern "C" fn ftrylockfile(fp: *mut FILE) -> c_int {
-    todo!()
+unsafe extern "C" fn ftrylockfile(_fp: *mut FILE) -> c_int {
+    unimplemented!()
 }
 
 #[no_mangle]
 unsafe extern "C" fn funlockfile(fp: *mut FILE) {
-    todo!()
+    let fp = fp.ensure_mut();
+    fp.unlock();
 }
 
 #[no_mangle]
 unsafe extern "C" fn open_memstream(
-    ptr: *mut *mut c_char,
-    sizeloc: *mut usize,
+    _ptr: *mut *mut c_char,
+    _sizeloc: *mut usize,
 ) -> CPtrResult<FILE> {
+    unimplemented!()
+}
+
+#[no_mangle]
+unsafe extern "C" fn pclose(_fp: *mut FILE) -> c_int {
     todo!()
 }
 
 #[no_mangle]
-unsafe extern "C" fn pclose(fp: *mut FILE) -> c_int {
-    todo!()
-}
-
-#[no_mangle]
-unsafe extern "C" fn popen(command: *const c_char, ty: *const c_char) -> CPtrResult<FILE> {
+unsafe extern "C" fn popen(_command: *const c_char, _ty: *const c_char) -> CPtrResult<FILE> {
     todo!()
 }
 
 #[no_mangle]
 unsafe extern "C" fn rewind(fp: *mut FILE) {
-    todo!()
+    fseek(fp, 0, SEEK_SET);
 }
 
 #[no_mangle]
 unsafe extern "C" fn setbuf(fp: *mut FILE, buf: *mut c_char) {
-    todo!()
+    setbuffer(fp, buf, 0);
+}
+
+#[no_mangle]
+unsafe extern "C" fn setbuffer(fp: *mut FILE, buf: *mut c_char, size: usize) {
+    let mode = if buf.is_null() { _IONBF } else { _IOFBF };
+    setvbuf(fp, buf, mode, size);
+}
+
+#[no_mangle]
+unsafe extern "C" fn setlinebuf(fp: *mut FILE) {
+    setvbuf(fp, null_mut(), _IOLBF, 0);
 }
 
 #[no_mangle]
 unsafe extern "C" fn setvbuf(
     fp: *mut FILE,
-    buffer: *mut c_void,
+    buffer: *mut c_char,
     mode: c_int,
     size: usize,
-) -> c_int {
-    todo!()
+) -> CIntZeroResult {
+    let fp = fp.ensure_mut();
+    fp.setvbuf(mode, buffer, size)?;
+    CIntZeroResult::SUCCESS
 }
diff --git a/userspace/lib/ygglibc/src/headers/stdio/get_put.rs b/userspace/lib/ygglibc/src/headers/stdio/get_put.rs
new file mode 100644
index 00000000..38cdba00
--- /dev/null
+++ b/userspace/lib/ygglibc/src/headers/stdio/get_put.rs
@@ -0,0 +1,280 @@
+// get*
+
+use core::{
+    ffi::{c_char, c_int},
+    ptr::{null_mut, NonNull},
+};
+
+use crate::{
+    allocator,
+    error::{CEofResult, CIsizeResult, CPtrResult, CResult, EResult},
+    headers::errno,
+    io::{
+        managed::{stdin, stdout, FILE},
+        Read, Write,
+    },
+    util::{PointerExt, PointerStrExt},
+};
+
+#[no_mangle]
+unsafe extern "C" fn fgetc(fp: *mut FILE) -> CEofResult {
+    let fp = fp.ensure_mut();
+    let mut buf = [0];
+    fp.read_exact(&mut buf)?;
+    CEofResult::success(buf[0] as _)
+}
+
+#[no_mangle]
+unsafe extern "C" fn fgets(s: *mut c_char, size: c_int, fp: *mut FILE) -> CPtrResult<c_char> {
+    let fp = fp.ensure_mut();
+    let mut s = NonNull::new(s).unwrap();
+
+    if size <= 0 {
+        return CPtrResult::ERROR;
+    }
+    // Nothing to read
+    if size == 1 {
+        *s.as_mut() = 0;
+        return CPtrResult::success(s);
+    }
+
+    let size = size as usize;
+    let mut pos = 0;
+    let mut buf = [0];
+    let slice = NonNull::slice_from_raw_parts(s, size).as_mut();
+
+    while pos < size - 1 {
+        let ch = match fp.read(&mut buf)? {
+            1 => buf[0],
+            _ => break,
+        };
+
+        slice[pos] = ch as _;
+        pos += 1;
+
+        if ch == b'\n' {
+            break;
+        }
+    }
+
+    if pos == 0 {
+        CPtrResult::ERROR
+    } else {
+        slice[pos] = 0;
+        CPtrResult::success(s)
+    }
+}
+
+#[no_mangle]
+unsafe extern "C" fn getc(fp: *mut FILE) -> CEofResult {
+    fgetc(fp)
+}
+
+#[no_mangle]
+unsafe extern "C" fn getchar() -> CEofResult {
+    fgetc(stdin)
+}
+
+#[no_mangle]
+unsafe extern "C" fn fgetc_unlocked(fp: *mut FILE) -> CEofResult {
+    let fp = fp.ensure_mut();
+    let mut buf = [0];
+    match fp.read_unlocked(&mut buf)? {
+        1 => CEofResult::success(buf[0] as _),
+        _ => CEofResult::ERROR,
+    }
+}
+
+#[no_mangle]
+unsafe extern "C" fn getc_unlocked(fp: *mut FILE) -> CEofResult {
+    fgetc_unlocked(fp)
+}
+
+#[no_mangle]
+unsafe extern "C" fn getchar_unlocked() -> CEofResult {
+    getc_unlocked(stdin)
+}
+
+// put*
+
+#[no_mangle]
+unsafe extern "C" fn fputc(ch: c_int, fp: *mut FILE) -> CEofResult {
+    let fp = fp.ensure_mut();
+    let ch = ch as u8;
+    fp.write_all(&[ch])?;
+    CEofResult::success(ch as c_int)
+}
+
+#[no_mangle]
+unsafe extern "C" fn fputs(str: *const c_char, fp: *mut FILE) -> CEofResult {
+    let fp = fp.ensure_mut();
+    let str = str.ensure_cstr();
+    fp.write_all(str.to_bytes())?;
+    CEofResult::success(0)
+}
+
+#[no_mangle]
+unsafe extern "C" fn putc(ch: c_int, fp: *mut FILE) -> CEofResult {
+    fputc(ch, fp)
+}
+
+#[no_mangle]
+unsafe extern "C" fn putchar(ch: c_int) -> CEofResult {
+    fputc(ch, stdout)
+}
+
+#[no_mangle]
+unsafe extern "C" fn putc_unlocked(ch: c_int, fp: *mut FILE) -> CEofResult {
+    let fp = fp.ensure_mut();
+    let ch = ch as u8;
+    match fp.write_unlocked(&[ch])? {
+        1 => CEofResult::success(ch as _),
+        _ => CEofResult::ERROR,
+    }
+}
+
+#[no_mangle]
+unsafe extern "C" fn putchar_unlocked(ch: c_int) -> CEofResult {
+    putc_unlocked(ch, stdout)
+}
+
+#[no_mangle]
+unsafe extern "C" fn puts(str: *const c_char) -> CEofResult {
+    let str = str.ensure_cstr();
+    let out = stdout.ensure_mut();
+    out.write_all(str.to_bytes())?;
+    out.write_all(b"\n")?;
+    CEofResult::success(0)
+}
+
+// ungetc
+
+#[no_mangle]
+unsafe extern "C" fn ungetc(ch: c_int, fp: *mut FILE) -> CEofResult {
+    let fp = fp.ensure_mut();
+    fp.ungetc(ch as _)?;
+    CEofResult::success(ch)
+}
+
+// getdelim
+
+struct MallocBufferWriter {
+    buffer: Option<NonNull<c_char>>,
+    capacity: usize,
+    position: usize,
+}
+
+impl MallocBufferWriter {
+    unsafe fn new(buffer: Option<NonNull<c_char>>, mut capacity: usize) -> Self {
+        if buffer.is_none() {
+            capacity = 0;
+        }
+        Self {
+            buffer,
+            capacity,
+            position: 0,
+        }
+    }
+
+    fn try_reserve(&mut self) -> EResult<&mut c_char> {
+        if self.position == self.capacity {
+            self.capacity = (self.capacity + 64) & !63;
+            self.buffer = Some(
+                unsafe { allocator::c_realloc(self.buffer.map(NonNull::cast), self.capacity) }?
+                    .cast(),
+            );
+        }
+        let buffer = self.buffer.unwrap();
+        EResult::Ok(unsafe { buffer.add(self.position).as_mut() })
+    }
+
+    fn putc(&mut self, ch: c_int) -> EResult<()> {
+        let item = self.try_reserve()?;
+        *item = ch as _;
+        self.position += 1;
+        EResult::Ok(())
+    }
+}
+
+fn getdelim_inner(
+    buffer: Option<NonNull<c_char>>,
+    capacity: usize,
+    delim: c_int,
+    stream: &mut FILE,
+) -> (MallocBufferWriter, EResult<()>) {
+    let mut writer = unsafe { MallocBufferWriter::new(buffer, capacity) };
+    let mut buf = [0];
+
+    loop {
+        let ch = match stream.read(&mut buf) {
+            EResult::Ok(1) => buf[0] as c_int,
+            EResult::Ok(_) => break,
+            EResult::Err(err) => return (writer, EResult::Err(err)),
+        };
+
+        match writer.putc(ch) {
+            EResult::Ok(()) => (),
+            EResult::Err(err) => {
+                return (writer, EResult::Err(err));
+            }
+        }
+
+        if ch == delim {
+            break;
+        }
+    }
+
+    if writer.position == 0 {
+        // EOF reached before anything could be read
+        return (writer, EResult::Err(errno::ESUCCESS));
+    }
+
+    match writer.putc(0) {
+        EResult::Ok(()) => (),
+        EResult::Err(err) => {
+            return (writer, EResult::Err(err));
+        }
+    }
+
+    (writer, EResult::Ok(()))
+}
+#[no_mangle]
+unsafe extern "C" fn getdelim(
+    lineptr: *mut *mut c_char,
+    n: *mut usize,
+    delim: c_int,
+    fp: *mut FILE,
+) -> CIsizeResult {
+    let lineptr = lineptr.ensure_mut();
+
+    let buffer = NonNull::new(*lineptr);
+    let n = n.ensure_mut();
+    let fp = fp.ensure_mut();
+
+    let (writer, result) = getdelim_inner(buffer, *n, delim, fp);
+
+    match writer.buffer {
+        Some(buffer) => *lineptr = buffer.as_ptr(),
+        None => *lineptr = null_mut(),
+    }
+    *n = writer.capacity;
+
+    result?;
+    assert_ne!(writer.position, 0);
+
+    CIsizeResult::success(writer.position - 1)
+}
+
+#[no_mangle]
+unsafe extern "C" fn getline(
+    lineptr: *mut *mut c_char,
+    size: *mut usize,
+    fp: *mut FILE,
+) -> CIsizeResult {
+    getdelim(lineptr, size, b'\n' as _, fp)
+}
+
+#[no_mangle]
+unsafe extern "C" fn gets(_buf: *mut c_char) -> *mut c_char {
+    unimplemented!("DO NOT USE")
+}
diff --git a/userspace/lib/ygglibc/src/headers/stdio/io.rs b/userspace/lib/ygglibc/src/headers/stdio/io.rs
index 2b7a95d2..130c8e9b 100644
--- a/userspace/lib/ygglibc/src/headers/stdio/io.rs
+++ b/userspace/lib/ygglibc/src/headers/stdio/io.rs
@@ -1,31 +1,11 @@
-use core::ffi::{c_char, c_int, c_void};
+use core::{ffi::{c_char, c_int, c_void}, ptr::NonNull};
 
 use crate::{
-    error::{CEofResult, CUsizeResult},
-    io::{managed::{stdout, FILE}, Read, Write},
+    error::{CEofResult, CPtrResult, CResult, CUsizeResult},
+    io::{managed::{stdin, stdout, FILE}, Read, Write},
     util::{PointerExt, PointerStrExt},
 };
 
-#[no_mangle]
-unsafe extern "C" fn fgetc(fp: *mut FILE) -> c_int {
-    todo!()
-}
-
-#[no_mangle]
-unsafe extern "C" fn fgets(buf: *mut c_char, size: c_int, fp: *mut FILE) -> *mut c_char {
-    todo!()
-}
-
-#[no_mangle]
-unsafe extern "C" fn fputc(ch: c_int, fp: *mut FILE) -> c_int {
-    todo!()
-}
-
-#[no_mangle]
-unsafe extern "C" fn fputs(str: *const c_char, fp: *mut FILE) -> c_int {
-    todo!()
-}
-
 #[no_mangle]
 unsafe extern "C" fn fread(
     buf: *mut c_void,
@@ -54,76 +34,3 @@ unsafe extern "C" fn fwrite(
     CUsizeResult::success(len)
 }
 
-#[no_mangle]
-unsafe extern "C" fn getc(fp: *mut FILE) -> c_int {
-    todo!()
-}
-
-#[no_mangle]
-unsafe extern "C" fn getchar() -> c_int {
-    todo!()
-}
-
-#[no_mangle]
-unsafe extern "C" fn getc_unlocked(fp: *mut FILE) -> c_int {
-    todo!()
-}
-
-#[no_mangle]
-unsafe extern "C" fn getchar_unlocked() -> c_int {
-    todo!()
-}
-
-#[no_mangle]
-unsafe extern "C" fn getdelim(
-    lineptr: *mut *mut c_char,
-    size: *mut usize,
-    delim: c_int,
-    fp: *mut FILE,
-) -> isize {
-    todo!()
-}
-
-#[no_mangle]
-unsafe extern "C" fn getline(lineptr: *mut *mut c_char, size: *mut usize, fp: *mut FILE) -> isize {
-    todo!()
-}
-
-#[no_mangle]
-unsafe extern "C" fn gets(buf: *mut c_char) -> *mut c_char {
-    todo!()
-}
-
-#[no_mangle]
-unsafe extern "C" fn putc(ch: c_int, fp: *mut FILE) -> c_int {
-    todo!()
-}
-
-#[no_mangle]
-unsafe extern "C" fn putchar(ch: c_int) -> c_int {
-    todo!()
-}
-
-#[no_mangle]
-unsafe extern "C" fn putc_unlocked(ch: c_int, fp: *mut FILE) -> c_int {
-    todo!()
-}
-
-#[no_mangle]
-unsafe extern "C" fn putchar_unlocked(ch: c_int) -> c_int {
-    todo!()
-}
-
-#[no_mangle]
-unsafe extern "C" fn puts(str: *const c_char) -> CEofResult {
-    let str = str.ensure_cstr();
-    let out = stdout.ensure_mut();
-    out.write_all(str.to_bytes())?;
-    out.write_all(b"\n")?;
-    CEofResult::success(0)
-}
-
-#[no_mangle]
-unsafe extern "C" fn ungetc(ch: c_int, fp: *mut FILE) -> c_int {
-    todo!()
-}
diff --git a/userspace/lib/ygglibc/src/headers/stdio/mod.rs b/userspace/lib/ygglibc/src/headers/stdio/mod.rs
index c8038629..1af41028 100644
--- a/userspace/lib/ygglibc/src/headers/stdio/mod.rs
+++ b/userspace/lib/ygglibc/src/headers/stdio/mod.rs
@@ -1,11 +1,20 @@
 use core::ffi::c_int;
 
+mod file;
+mod get_put;
+mod io;
 mod printf;
 mod scanf;
-mod file;
-mod io;
 mod util;
 
+// includes:
+// va_list from <stdarg.h>
+// FILE from <bits/stdio.h>
+// size_t from <stddef.h>
+// ssize_t from <sys/types.h>
+//
+// stdin, stdout, stderr as externs from <bits/stdio.h>
+
 pub const _IOFBF: c_int = 0;
 pub const _IOLBF: c_int = 1;
 pub const _IONBF: c_int = 2;
@@ -14,6 +23,8 @@ pub const SEEK_SET: c_int = 0;
 pub const SEEK_CUR: c_int = 1;
 pub const SEEK_END: c_int = 2;
 
+pub const FILENAME_MAX: usize = 4096;
+pub const FOPEN_MAX: usize = 4096;
 pub const TMP_MAX: usize = 216000;
 
 pub const EOF: c_int = -1;
@@ -22,4 +33,4 @@ pub type fpos_t = u64;
 
 pub const BUFSIZ: usize = 8192;
 
-const UNGETC_MAX: usize = 128;
+pub const UNGETC_MAX: usize = 128;
diff --git a/userspace/lib/ygglibc/src/headers/stdio/util.rs b/userspace/lib/ygglibc/src/headers/stdio/util.rs
index 0edd0e65..5d15c6ec 100644
--- a/userspace/lib/ygglibc/src/headers/stdio/util.rs
+++ b/userspace/lib/ygglibc/src/headers/stdio/util.rs
@@ -1,35 +1,50 @@
 use core::ffi::{c_char, c_int};
 
-use crate::{error::CPtrResult, io::managed::FILE};
-
+use crate::{
+    error::{self, CPtrResult},
+    io::managed::{stderr, FILE},
+    util::{PointerExt, PointerStrExt},
+};
 
 #[no_mangle]
 unsafe extern "C" fn perror(message: *const c_char) {
+    let out = stderr.ensure_mut();
+    out.lock();
+
+    if !message.is_null() {
+        let message = message.ensure_cstr();
+        out.write_unlocked(message.to_bytes()).ok();
+        out.write_unlocked(b": ").ok();
+    }
+
+    out.write_unlocked(error::errno.to_c_str().to_bytes()).ok();
+    out.write_unlocked(b"\n").ok();
+
+    out.unlock();
+}
+
+#[no_mangle]
+unsafe extern "C" fn ctermid(_buf: *mut c_char) -> *mut c_char {
+    unimplemented!()
+}
+
+#[no_mangle]
+unsafe extern "C" fn remove(_path: *const c_char) -> c_int {
     todo!()
 }
 
 #[no_mangle]
-unsafe extern "C" fn ctermid(buf: *mut c_char) -> *mut c_char {
+unsafe extern "C" fn rename(_src: *const c_char, _dst: *const c_char) -> c_int {
     todo!()
 }
 
 #[no_mangle]
-unsafe extern "C" fn remove(path: *const c_char) -> c_int {
+unsafe extern "C" fn renameat(_atfd: c_int, _src: *const c_char, _dst: *const c_char) -> c_int {
     todo!()
 }
 
 #[no_mangle]
-unsafe extern "C" fn rename(src: *const c_char, dst: *const c_char) -> c_int {
-    todo!()
-}
-
-#[no_mangle]
-unsafe extern "C" fn renameat(atfd: c_int, src: *const c_char, dst: *const c_char) -> c_int {
-    todo!()
-}
-
-#[no_mangle]
-unsafe extern "C" fn tempnam(dir: *const c_char, prefix: *const c_char) -> *mut c_char {
+unsafe extern "C" fn tempnam(_dir: *const c_char, _prefix: *const c_char) -> *mut c_char {
     todo!()
 }
 
@@ -39,6 +54,6 @@ unsafe extern "C" fn tmpfile() -> CPtrResult<FILE> {
 }
 
 #[no_mangle]
-unsafe extern "C" fn tmpnam(template: *mut c_char) -> *mut c_char {
+unsafe extern "C" fn tmpnam(_template: *mut c_char) -> *mut c_char {
     todo!()
 }
diff --git a/userspace/lib/ygglibc/src/headers/string/str.rs b/userspace/lib/ygglibc/src/headers/string/str.rs
index fa98fc2f..ea08135b 100644
--- a/userspace/lib/ygglibc/src/headers/string/str.rs
+++ b/userspace/lib/ygglibc/src/headers/string/str.rs
@@ -1,11 +1,11 @@
 use core::{
     cmp::Ordering,
     ffi::{c_char, c_int},
-    ptr::null_mut,
+    ptr::{null_mut, NonNull},
 };
 
 use crate::{
-    allocator,
+    allocator::{self, malloc},
     error::CPtrResult,
     headers::{
         errno::{self, Errno},
@@ -20,7 +20,7 @@ unsafe extern "C" fn stpcpy(dst: *mut c_char, src: *const c_char) -> *mut c_char
     if dst.is_null() || src.is_null() {
         panic!();
     }
-    let ptr = mempcpy(dst as _, src as _, strlen(src)) as *mut c_char;
+    let ptr = mempcpy(dst.cast(), src.cast(), strlen(src)).cast::<c_char>();
     *ptr = 0;
     ptr
 }
@@ -30,8 +30,8 @@ unsafe extern "C" fn stpncpy(dst: *mut c_char, src: *const c_char, n: usize) ->
     if dst.is_null() || src.is_null() {
         panic!();
     }
-    memset(dst as _, 0, n);
-    mempcpy(dst as _, src as _, strnlen(src, n)) as _
+    memset(dst.cast(), 0, n);
+    mempcpy(dst.cast(), src.cast(), strnlen(src, n)).cast()
 }
 
 #[no_mangle]
@@ -51,7 +51,7 @@ unsafe extern "C" fn strchr(mut s: *const c_char, c: c_int) -> *mut c_char {
 
     loop {
         if *s == c as _ {
-            return s as _;
+            return s.cast_mut();
         }
 
         if *s == 0 {
@@ -95,13 +95,13 @@ unsafe extern "C" fn strcspn(mut s: *const c_char, reject: *const c_char) -> usi
 unsafe extern "C" fn strdup(s: *const c_char) -> CPtrResult<c_char> {
     let len = strlen(s);
     let data = allocator::c_alloc(len + 1, 1, false)?;
-    memcpy(data.cast().as_ptr(), s as _, len + 1);
+    memcpy(data.cast().as_ptr(), s.cast(), len + 1);
     CPtrResult::success(data.cast())
 }
 
 unsafe fn strerror_inner(e: c_int) -> *const c_char {
     if let Some(errno) = Errno::from_c_int(e) {
-        errno.to_c_str()
+        errno.to_c_str().as_ptr()
     } else {
         errno::UNKNOWN_ERROR.as_ptr()
     }
@@ -127,7 +127,7 @@ unsafe extern "C" fn strncat(dst: *mut c_char, src: *const c_char, n: usize) ->
     }
     let len = strnlen(src, n);
     let ptr = dst.add(strlen(dst));
-    let ptr = mempcpy(ptr as _, src as _, len) as *mut c_char;
+    let ptr = mempcpy(ptr.cast(), src.cast(), len).cast::<c_char>();
     *ptr = 0;
 
     dst
@@ -171,8 +171,13 @@ unsafe extern "C" fn strncpy(dst: *mut c_char, src: *const c_char, n: usize) ->
 }
 
 #[no_mangle]
-unsafe extern "C" fn strndup(s: *const c_char, n: usize) -> *mut c_char {
-    todo!()
+unsafe extern "C" fn strndup(s: *const c_char, n: usize) -> CPtrResult<c_char> {
+    let src = NonNull::new(s.cast_mut()).unwrap();
+    let len = strnlen(s, n);
+    let dst = malloc(len + 1)?.cast();
+    dst.copy_from_nonoverlapping(src, len);
+    dst.add(len).write(0);
+    CPtrResult::success(dst)
 }
 
 #[no_mangle]
@@ -203,7 +208,7 @@ unsafe extern "C" fn strpbrk(mut a: *const c_char, b: *const c_char) -> *mut c_c
         }
 
         if !strchr(b, c as _).is_null() {
-            return a as _;
+            return a.cast_mut();
         }
 
         a = a.add(1);
@@ -221,7 +226,7 @@ unsafe extern "C" fn strrchr(a: *const c_char, c: c_int) -> *mut c_char {
     let n = strnlen(a, usize::MAX);
     for i in (0..n).rev() {
         if *a.add(i) == c as _ {
-            return a.add(i) as _;
+            return a.add(i).cast_mut();
         }
     }
 
@@ -229,7 +234,7 @@ unsafe extern "C" fn strrchr(a: *const c_char, c: c_int) -> *mut c_char {
 }
 
 #[no_mangle]
-unsafe extern "C" fn strsignal(signum: c_int) -> *mut c_char {
+unsafe extern "C" fn strsignal(_signum: c_int) -> *mut c_char {
     todo!()
 }
 
@@ -256,12 +261,12 @@ unsafe extern "C" fn strstr(mut a: *const c_char, b: *const c_char) -> *mut c_ch
     }
     let n = strnlen(b, usize::MAX);
     if *a == 0 && *b == 0 {
-        return a as _;
+        return a.cast_mut();
     }
 
     while *a != 0 {
         if strncmp(a, b, n) == 0 {
-            return a as _;
+            return a.cast_mut();
         }
         a = a.add(1);
     }
@@ -306,31 +311,31 @@ unsafe extern "C" fn strtok_r(
         *saveptr = null_mut();
     }
     *str.add(len) = 0;
-    str as _
+    str.cast()
 }
 
 // TODO locales
 #[no_mangle]
-unsafe extern "C" fn strcoll(a: *const c_char, b: *const c_char) -> c_int {
-    todo!()
+unsafe extern "C" fn strcoll(_a: *const c_char, _b: *const c_char) -> c_int {
+    unimplemented!()
 }
 
 #[no_mangle]
-unsafe extern "C" fn strcoll_l(a: *const c_char, b: *const c_char, l: locale_t) -> c_int {
-    todo!()
+unsafe extern "C" fn strcoll_l(_a: *const c_char, _b: *const c_char, _l: locale_t) -> c_int {
+    unimplemented!()
 }
 
 #[no_mangle]
-unsafe extern "C" fn strerror_l(e: c_int, l: locale_t) -> *mut c_char {
-    todo!()
+unsafe extern "C" fn strerror_l(_e: c_int, _l: locale_t) -> *mut c_char {
+    unimplemented!()
 }
 
 #[no_mangle]
-unsafe extern "C" fn strxfrm(a: *mut c_char, b: *const c_char, n: usize) -> usize {
-    todo!()
+unsafe extern "C" fn strxfrm(_a: *mut c_char, _b: *const c_char, _n: usize) -> usize {
+    unimplemented!()
 }
 
 #[no_mangle]
-unsafe extern "C" fn strxfrm_l(a: *mut c_char, b: *const c_char, n: usize, l: locale_t) -> usize {
-    todo!()
+unsafe extern "C" fn strxfrm_l(_a: *mut c_char, _b: *const c_char, _n: usize, _l: locale_t) -> usize {
+    unimplemented!()
 }
diff --git a/userspace/lib/ygglibc/src/io/managed.rs b/userspace/lib/ygglibc/src/io/managed.rs
index 3a80ec59..478a2ece 100644
--- a/userspace/lib/ygglibc/src/io/managed.rs
+++ b/userspace/lib/ygglibc/src/io/managed.rs
@@ -1,10 +1,13 @@
 #![allow(non_upper_case_globals)]
 
 use core::{
-    fmt, ops::{Deref, DerefMut}, ptr::{null_mut, NonNull}
+    ffi::{c_char, c_int},
+    fmt,
+    ops::{Deref, DerefMut},
+    ptr::{null_mut, NonNull},
 };
 
-use alloc::{boxed::Box, collections::btree_set::BTreeSet};
+use alloc::{boxed::Box, collections::btree_set::BTreeSet, vec::Vec};
 use bitflags::bitflags;
 use yggdrasil_rt::{
     io::{FileMode, OpenOptions, RawFd, SeekFrom},
@@ -12,15 +15,16 @@ use yggdrasil_rt::{
 };
 
 use crate::{
-    error::EResult,
-    headers::errno,
+    error::{EResult, TryFromExt},
+    headers::{
+        errno,
+        stdio::{BUFSIZ, UNGETC_MAX, _IOFBF, _IOLBF, _IONBF},
+    },
     sync::{Mutex, RawMutex},
 };
 
 use super::{
-    buffer::{FileWriter, FullBufferedWriter, LineBufferedWriter, ReadBuffer, UnbufferedWriter},
-    raw::RawFile,
-    AsRawFd, FromRawFd, Read, Seek, Write,
+    buffer::{FileWriter, FullBufferedWriter, LineBufferedWriter, ReadBuffer, UnbufferedWriter}, raw::RawFile, AsRawFd, BufRead, FromRawFd, Read, Seek, Write
 };
 
 macro locked_op($self:expr, $op:expr) {{
@@ -50,6 +54,12 @@ pub enum BufferingMode {
     None,
 }
 
+#[derive(Debug, PartialEq, Clone, Copy)]
+pub enum Direction {
+    Read,
+    Write
+}
+
 pub enum FileBacking {
     File(ManagedFile),
 }
@@ -67,6 +77,13 @@ pub struct FILE {
     write_buffer: Box<dyn FileWriter>,
 
     flags: FileFlags,
+
+    ungetc: Vec<u8>,
+    // NOTE:
+    // man setvbuf(3):
+    //  The setvbuf() function may be used only after opening a stream and
+    //  before any other  operations have been performed on it.
+    last_operation: Option<Direction>
 }
 
 // ManagedFile
@@ -191,8 +208,8 @@ impl FILE {
         let write_buffer = backing.make_ref();
         let write_buffer: Box<dyn FileWriter> = match buffering {
             BufferingMode::None => Box::new(UnbufferedWriter::new(write_buffer)),
-            BufferingMode::Line => Box::new(LineBufferedWriter::with_capacity(write_buffer, 1024)),
-            BufferingMode::Full => Box::new(FullBufferedWriter::with_capacity(write_buffer, 1024)),
+            BufferingMode::Line => Box::new(LineBufferedWriter::with_capacity(write_buffer, BUFSIZ)),
+            BufferingMode::Full => Box::new(FullBufferedWriter::with_capacity(write_buffer, BUFSIZ)),
         };
 
         Self {
@@ -203,6 +220,9 @@ impl FILE {
             read_buffer: None,
 
             flags,
+
+            ungetc: Vec::new(),
+            last_operation: None,
         }
     }
 
@@ -238,22 +258,71 @@ impl FILE {
         }
     }
 
-    pub fn reset(&mut self) {
-        if let Some(read_buffer) = self.read_buffer.as_mut() {
-            read_buffer.reset();
+    pub fn fd(&self) -> Option<RawFd> {
+        match &self.inner {
+            FileBacking::File(file) => Some(file.as_raw_fd()),
         }
-        self.write_buffer.reset();
-        self.flags &= !(FileFlags::ERROR | FileFlags::EOF);
     }
 
-    unsafe fn read_unlocked(&mut self, data: &mut [u8]) -> EResult<usize> {
+    pub fn is_eof(&self) -> bool {
+        self.flags.contains(FileFlags::EOF)
+    }
+
+    pub fn is_error(&self) -> bool {
+        self.flags.contains(FileFlags::ERROR)
+    }
+
+    pub fn clear_error(&mut self) {
+        self.flags.remove(FileFlags::ERROR);
+    }
+
+    pub unsafe fn lock(&mut self) {
+        self.lock.lock();
+    }
+
+    pub unsafe fn unlock(&mut self) {
+        self.lock.release();
+    }
+
+    // pub fn reset(&mut self) {
+    //     if let Some(read_buffer) = self.read_buffer.as_mut() {
+    //         read_buffer.reset();
+    //     }
+    //     self.write_buffer.reset();
+    //     self.flags &= !(FileFlags::ERROR | FileFlags::EOF);
+    //     self.ungetc.clear();
+    //     self.last_operation = None;
+    // }
+
+    pub fn setvbuf(&mut self, mode: c_int, buffer: *mut c_char, size: usize) -> EResult<()> {
+        locked_op!(self, self.setvbuf_unlocked(mode, buffer, size))
+    }
+
+    pub fn ungetc(&mut self, ch: u8) -> EResult<()> {
+        locked_op!(self, self.ungetc_unlocked(ch))
+    }
+
+    pub unsafe fn read_unlocked(&mut self, data: &mut [u8]) -> EResult<usize> {
         if !self.flags.contains(FileFlags::READ) {
             self.flags |= FileFlags::ERROR;
             return EResult::Err(errno::EBADF);
         }
 
+        self.set_direction(Direction::Read)?;
+
+        if !self.ungetc.is_empty() {
+            let amount = core::cmp::min(self.ungetc.len(), data.len());
+            data[..amount].copy_from_slice(&self.ungetc[..amount]);
+            self.ungetc.drain(..amount);
+            return EResult::Ok(amount);
+        }
+
         if self.read_buffer.is_some() {
-            todo!()
+            let buf = self.fill_buf()?;
+            let len = core::cmp::min(data.len(), buf.len());
+            data[..len].copy_from_slice(&buf[..len]);
+            self.consume(len);
+            EResult::Ok(len)
         } else {
             match self.inner.read(data) {
                 EResult::Ok(0) => {
@@ -269,12 +338,14 @@ impl FILE {
         }
     }
 
-    unsafe fn write_unlocked(&mut self, data: &[u8]) -> EResult<usize> {
+    pub unsafe fn write_unlocked(&mut self, data: &[u8]) -> EResult<usize> {
         if !self.flags.contains(FileFlags::WRITE) {
             self.flags |= FileFlags::ERROR;
             return EResult::Err(errno::EBADF);
         }
 
+        self.set_direction(Direction::Write)?;
+
         match self.write_buffer.write(data) {
             EResult::Ok(len) => EResult::Ok(len),
             EResult::Err(err) => {
@@ -285,19 +356,115 @@ impl FILE {
     }
 
     unsafe fn flush_unlocked(&mut self) -> EResult<()> {
-        if !self.flags.contains(FileFlags::WRITE) {
-            self.flags |= FileFlags::ERROR;
-            return EResult::Err(errno::EBADF);
-        }
+        self.ungetc.clear();
+        self.last_operation = None;
 
         match self.write_buffer.flush() {
-            EResult::Ok(()) => EResult::Ok(()),
+            EResult::Ok(()) => {
+                EResult::Ok(())
+            },
             EResult::Err(err) => {
                 self.flags |= FileFlags::ERROR;
                 EResult::Err(err)
             }
         }
     }
+
+    unsafe fn seek_unlocked(&mut self, off: SeekFrom) -> EResult<u64> {
+        self.flush_unlocked()?;
+
+        match self.inner.seek(off) {
+            EResult::Ok(pos) => {
+                self.flags &= !FileFlags::EOF;
+                EResult::Ok(pos)
+            }
+            EResult::Err(err) => {
+                self.flags |= FileFlags::ERROR;
+                EResult::Err(err)
+            }
+        }
+    }
+
+    unsafe fn ungetc_unlocked(&mut self, ch: u8) -> EResult<()> {
+        // ungetc() for write doesn't make any sense
+        self.set_direction(Direction::Read)?;
+        if self.ungetc.len() == UNGETC_MAX {
+            return EResult::Err(errno::ENOMEM);
+        }
+        self.ungetc.push(ch);
+        EResult::Ok(())
+    }
+
+    unsafe fn setvbuf_unlocked(
+        &mut self,
+        mode: c_int,
+        buffer: *mut c_char,
+        capacity: usize,
+    ) -> EResult<()> {
+        let mode = BufferingMode::e_try_from(mode)?;
+
+        if self.last_operation.is_some() {
+            self.flags |= FileFlags::ERROR;
+            return EResult::Err(errno::EINVAL);
+        }
+
+        self.ungetc.clear();
+
+        let mut read_capacity = capacity;
+        let mut write_capacity = capacity;
+
+        // Set read buffer
+        match mode {
+            BufferingMode::None => self.read_buffer = None,
+            BufferingMode::Line | BufferingMode::Full => {
+                if buffer.is_null() {
+                    if read_capacity == 0 {
+                        read_capacity = BUFSIZ;
+                    }
+
+                    self.read_buffer = Some(ReadBuffer::owned(read_capacity));
+                } else {
+                    assert_ne!(read_capacity, 0);
+
+                    let slice =
+                        unsafe { core::slice::from_raw_parts_mut(buffer.cast(), read_capacity) };
+                    self.read_buffer = Some(ReadBuffer::borrowed(slice));
+                }
+            }
+        }
+
+        // Set write buffer
+        let file_ref = unsafe { self.inner.make_ref() };
+        match mode {
+            BufferingMode::None => self.write_buffer = Box::new(UnbufferedWriter::new(file_ref)),
+            BufferingMode::Line => {
+                if write_capacity == 0 {
+                    write_capacity = BUFSIZ;
+                }
+                self.write_buffer =
+                    Box::new(LineBufferedWriter::with_capacity(file_ref, write_capacity));
+            }
+            BufferingMode::Full => {
+                if write_capacity == 0 {
+                    write_capacity = BUFSIZ;
+                }
+                self.write_buffer =
+                    Box::new(FullBufferedWriter::with_capacity(file_ref, write_capacity));
+            }
+        }
+
+        EResult::Ok(())
+    }
+
+    unsafe fn set_direction(&mut self, direction: Direction) -> EResult<()> {
+        match self.last_operation.replace(direction) {
+            Some(dir) if dir != direction => {
+                self.flags |= FileFlags::ERROR;
+                EResult::Err(errno::EINVAL)
+            },
+            _ => EResult::Ok(())
+        }
+    }
 }
 
 impl Read for FILE {
@@ -316,6 +483,35 @@ impl Write for FILE {
     }
 }
 
+impl Seek for FILE {
+    fn seek(&mut self, pos: SeekFrom) -> EResult<u64> {
+        locked_op!(self, self.seek_unlocked(pos))
+    }
+}
+
+impl BufRead for FILE {
+    fn fill_buf(&mut self) -> EResult<&[u8]> {
+        let buffer = self.read_buffer.as_mut().unwrap();
+        match buffer.fill_from(&mut self.inner) {
+            EResult::Ok(slice) => {
+                if slice.is_empty() {
+                    self.flags |= FileFlags::EOF;
+                }
+                EResult::Ok(slice)
+            }
+            EResult::Err(err) => {
+                self.flags |= FileFlags::ERROR;
+                EResult::Err(err)
+            }
+        }
+    }
+
+    fn consume(&mut self, amount: usize) {
+        let buffer = self.read_buffer.as_mut().unwrap();
+        buffer.consume(amount);
+    }
+}
+
 impl FileOpenSource for &Path {
     fn open_with(self, opts: OpenOptions) -> EResult<FILE> {
         let f = ManagedFile::open_at(None, self, opts, FileMode::default_file())?;
@@ -330,9 +526,35 @@ impl FileOpenSource for &Path {
     }
 }
 
+impl FileOpenSource for RawFd {
+    fn open_with(self, opts: OpenOptions) -> EResult<FILE> {
+        let f = unsafe { RawFile::from_raw_fd(self) };
+        let mut flags = FileFlags::empty();
+        if opts.contains(OpenOptions::READ) {
+            flags |= FileFlags::READ;
+        }
+        if opts.contains(OpenOptions::WRITE) {
+            flags |= FileFlags::WRITE;
+        }
+        EResult::Ok(unsafe { FILE::from_raw_file(f, flags) })
+    }
+}
+
 impl fmt::Write for FILE {
     fn write_str(&mut self, s: &str) -> fmt::Result {
-        self.write_all(s.as_bytes()).into_result(|_| fmt::Error, true)
+        self.write_all(s.as_bytes())
+            .into_result(|_| fmt::Error, true)
+    }
+}
+
+impl TryFromExt<c_int> for BufferingMode {
+    fn e_try_from(value: c_int) -> EResult<Self> {
+        match value {
+            _IOFBF => EResult::Ok(Self::Full),
+            _IOLBF => EResult::Ok(Self::Line),
+            _IONBF => EResult::Ok(Self::None),
+            _ => EResult::Err(errno::EINVAL),
+        }
     }
 }
 
diff --git a/userspace/lib/ygglibc/src/io/mod.rs b/userspace/lib/ygglibc/src/io/mod.rs
index 3cf99688..57fc9f18 100644
--- a/userspace/lib/ygglibc/src/io/mod.rs
+++ b/userspace/lib/ygglibc/src/io/mod.rs
@@ -1,4 +1,4 @@
-use core::{ffi::c_int, mem::MaybeUninit};
+use core::{ffi::{c_int, c_long}, mem::MaybeUninit};
 
 use yggdrasil_rt::{io::{PipeOptions, RawFd, SeekFrom}, sys as syscall};
 
@@ -46,10 +46,27 @@ pub trait Write {
 
 pub trait Read {
     fn read(&mut self, data: &mut [u8]) -> EResult<usize>;
+
+    fn read_exact(&mut self, buf: &mut [u8]) -> EResult<()> {
+        match self.read(buf) {
+            EResult::Ok(n) if n == buf.len() => EResult::Ok(()),
+            EResult::Ok(_) => todo!(),
+            EResult::Err(err) => EResult::Err(err)
+        }
+    }
+}
+
+pub trait BufRead {
+    fn fill_buf(&mut self) -> EResult<&[u8]>;
+    fn consume(&mut self, amount: usize);
 }
 
 pub trait Seek {
     fn seek(&mut self, pos: SeekFrom) -> EResult<u64>;
+
+    fn stream_position(&mut self) -> EResult<u64> {
+        self.seek(SeekFrom::Current(0))
+    }
 }
 
 pub trait FromRawFd {