diff --git a/include/bits/stdio.h b/include/bits/stdio.h
index 10d1314d8b5c20709eeb86a7952f713e9455e99e..23d913d779806803bc35c21af2ba88249039dab3 100644
--- a/include/bits/stdio.h
+++ b/include/bits/stdio.h
@@ -1,9 +1,6 @@
 #ifndef _BITS_STDIO_H
 #define _BITS_STDIO_H
 
-#define EOF (-1)
-#define BUFSIZ 1024
-
 typedef struct FILE FILE;
 
 int fprintf(FILE * stream, const char * fmt, ...);
diff --git a/src/fs.rs b/src/fs.rs
index 097c40ca2d1f2a96c1e4e274967127a7a2c86735..672081a4efa3b317de28ea29dcd1df683320e8fb 100644
--- a/src/fs.rs
+++ b/src/fs.rs
@@ -1,4 +1,5 @@
 use c_str::CStr;
+use core::ops::Deref;
 use header::fcntl::O_CREAT;
 use header::unistd::{SEEK_SET, SEEK_CUR, SEEK_END};
 use io;
@@ -11,48 +12,70 @@ fn last_os_error() -> io::Error {
     io::Error::from_raw_os_error(errno)
 }
 
-pub struct File(c_int);
+pub struct File {
+    pub fd: c_int,
+    /// To avoid self referential FILE struct that needs both a reader and a writer,
+    /// make "reference" files that share fd but don't close on drop.
+    pub reference: bool
+}
 
 impl File {
+    pub fn new(fd: c_int) -> Self {
+        Self {
+            fd,
+            reference: false
+        }
+    }
+
     pub fn open(path: &CStr, oflag: c_int) -> io::Result<Self> {
         match Sys::open(path, oflag, 0) {
             -1 => Err(last_os_error()),
-            ok => Ok(File(ok)),
+            ok => Ok(Self::new(ok)),
         }
     }
 
     pub fn create(path: &CStr, oflag: c_int, mode: mode_t) -> io::Result<Self> {
         match Sys::open(path, oflag | O_CREAT, mode) {
             -1 => Err(last_os_error()),
-            ok => Ok(File(ok)),
+            ok => Ok(Self::new(ok)),
         }
     }
 
     pub fn sync_all(&self) -> io::Result<()> {
-        match Sys::fsync(self.0) {
+        match Sys::fsync(self.fd) {
             -1 => Err(last_os_error()),
             _ok => Ok(()),
         }
     }
 
     pub fn set_len(&self, size: u64) -> io::Result<()> {
-        match Sys::ftruncate(self.0, size as off_t) {
+        match Sys::ftruncate(self.fd, size as off_t) {
             -1 => Err(last_os_error()),
             _ok => Ok(()),
         }
     }
 
     pub fn try_clone(&self) -> io::Result<Self> {
-        match Sys::dup(self.0) {
+        match Sys::dup(self.fd) {
             -1 => Err(last_os_error()),
-            ok => Ok(File(ok)),
+            ok => Ok(Self::new(ok)),
+        }
+    }
+
+    /// Create a new file pointing to the same underlying descriptor. This file
+    /// will know it's a "reference" and won't close the fd. It will, however,
+    /// not prevent the original file from closing the fd.
+    pub unsafe fn get_ref(&self) -> Self {
+        Self {
+            fd: self.fd,
+            reference: false
         }
     }
 }
 
 impl io::Read for File {
     fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
-        match Sys::read(self.0, buf) {
+        match Sys::read(self.fd, buf) {
             -1 => Err(last_os_error()),
             ok => Ok(ok as usize),
         }
@@ -61,7 +84,7 @@ impl io::Read for File {
 
 impl io::Write for File {
     fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
-        match Sys::write(self.0, buf) {
+        match Sys::write(self.fd, buf) {
             -1 => Err(last_os_error()),
             ok => Ok(ok as usize),
         }
@@ -80,9 +103,25 @@ impl io::Seek for File {
             io::SeekFrom::End(end) => (end as off_t, SEEK_END),
         };
 
-        match Sys::lseek(self.0, offset, whence) {
+        match Sys::lseek(self.fd, offset, whence) {
             -1 => Err(last_os_error()),
             ok => Ok(ok as u64),
         }
     }
 }
+
+impl Deref for File {
+    type Target = c_int;
+
+    fn deref(&self) -> &Self::Target {
+        &self.fd
+    }
+}
+
+impl Drop for File {
+    fn drop(&mut self) {
+        if !self.reference {
+            let _ = Sys::close(self.fd);
+        }
+    }
+}
diff --git a/src/header/stdio/constants.rs b/src/header/stdio/constants.rs
index c601cbc85e3d07206b6dfdaa0314dfe870b58771..2adea19182e650b87265dbf2b69de99808d44d1f 100644
--- a/src/header/stdio/constants.rs
+++ b/src/header/stdio/constants.rs
@@ -1,8 +1,9 @@
 use platform::types::*;
 
-pub const BUFSIZ: size_t = 1024;
+pub const EOF: c_int = -1;
+pub const BUFSIZ: c_int = 1024;
 
-pub const UNGET: size_t = 8;
+pub const UNGET: c_int = 8;
 
 pub const FILENAME_MAX: c_int = 4096;
 
diff --git a/src/header/stdio/default.rs b/src/header/stdio/default.rs
index 5928254178e5e64c90978b81c2c28c0e59486578..315d5f7505a4ef2eedc57139b10f5f06ec9105ea 100644
--- a/src/header/stdio/default.rs
+++ b/src/header/stdio/default.rs
@@ -1,12 +1,28 @@
-use super::{constants, BUFSIZ, FILE, UNGET};
+use super::{constants, Buffer, BUFSIZ, FILE, UNGET};
 use core::cell::UnsafeCell;
 use core::ptr;
 use core::sync::atomic::AtomicBool;
 
+use fs::File;
+use io::LineWriter;
+use platform::types::*;
+
 pub struct GlobalFile(UnsafeCell<FILE>);
 impl GlobalFile {
-    const fn new(file: FILE) -> Self {
-        GlobalFile(UnsafeCell::new(file))
+    fn new(file: c_int, flags: c_int) -> Self {
+        let file = File::new(file);
+        let writer = LineWriter::new(unsafe { file.get_ref() });
+        GlobalFile(UnsafeCell::new(FILE {
+            lock: AtomicBool::new(false),
+
+            file,
+            flags: constants::F_PERM | flags,
+            read_buf: Buffer::Owned(vec![0; BUFSIZ as usize]),
+            read_pos: 0,
+            read_size: 0,
+            unget: None,
+            writer
+        }))
     }
     pub fn get(&self) -> *mut FILE {
         self.0.get()
@@ -17,40 +33,13 @@ unsafe impl Sync for GlobalFile {}
 
 lazy_static! {
     #[allow(non_upper_case_globals)]
-    pub static ref default_stdin: GlobalFile = GlobalFile::new(FILE {
-        flags: constants::F_PERM | constants::F_NOWR,
-        read: None,
-        write: None,
-        fd: 0,
-        buf: vec![0u8;(BUFSIZ + UNGET) as usize],
-        buf_char: -1,
-        unget: UNGET,
-        lock: AtomicBool::new(false),
-    });
+    pub static ref default_stdin: GlobalFile = GlobalFile::new(0, constants::F_NOWR);
 
     #[allow(non_upper_case_globals)]
-    pub static ref default_stdout: GlobalFile = GlobalFile::new(FILE {
-        flags: constants::F_PERM | constants::F_NORD,
-        read: None,
-        write: None,
-        fd: 1,
-        buf: vec![0u8;(BUFSIZ + UNGET) as usize],
-        buf_char: b'\n' as i8,
-        unget: 0,
-        lock: AtomicBool::new(false),
-    });
+    pub static ref default_stdout: GlobalFile = GlobalFile::new(1, constants::F_NORD);
 
     #[allow(non_upper_case_globals)]
-    pub static ref default_stderr: GlobalFile = GlobalFile::new(FILE {
-        flags: constants::F_PERM | constants::F_NORD,
-        read: None,
-        write: None,
-        fd: 2,
-        buf: vec![0u8;(BUFSIZ + UNGET) as usize],
-        buf_char: -1,
-        unget: 0,
-        lock: AtomicBool::new(false),
-    });
+    pub static ref default_stderr: GlobalFile = GlobalFile::new(2, constants::F_NORD);
 }
 
 #[no_mangle]
diff --git a/src/header/stdio/helpers.rs b/src/header/stdio/helpers.rs
index 3d1629fdd4f65e12e17fc892b71c14208c3f5c7d..1544462f06f2772a468ec2f6b69bac18c1219ff2 100644
--- a/src/header/stdio/helpers.rs
+++ b/src/header/stdio/helpers.rs
@@ -1,14 +1,17 @@
+use alloc::boxed::Box;
 use core::sync::atomic::AtomicBool;
 use core::{mem, ptr};
 
+use fs::File;
 use header::errno;
 use header::fcntl::*;
 use header::string::strchr;
-use platform;
+use io::LineWriter;
 use platform::types::*;
+use platform;
 
 use super::constants::*;
-use super::{BUFSIZ, FILE, UNGET};
+use super::{Buffer, FILE};
 
 /// Parse mode flags as a string and output a mode flags integer
 pub unsafe fn parse_mode_flags(mode_str: *const c_char) -> i32 {
@@ -61,104 +64,18 @@ pub unsafe fn _fdopen(fd: c_int, mode: *const c_char) -> Option<*mut FILE> {
         flags |= F_APP;
     }
 
-    let f = platform::alloc(mem::size_of::<FILE>()) as *mut FILE;
-    // Allocate the file
-    if f.is_null() {
-        None
-    } else {
-        ptr::write(
-            f,
-            FILE {
-                flags: flags,
-                read: None,
-                write: None,
-                fd: fd,
-                buf: vec![0u8; BUFSIZ + UNGET],
-                buf_char: -1,
-                unget: UNGET,
-                lock: AtomicBool::new(false),
-            },
-        );
-        Some(f)
-    }
-}
-
-/// Write buffer `buf` of length `l` into `stream`
-pub fn fwritex(buf: *const u8, l: size_t, stream: &mut FILE) -> size_t {
-    use core::ptr::copy_nonoverlapping;
-    use core::slice;
-
-    let buf: &'static [u8] = unsafe { slice::from_raw_parts(buf, l) };
-    let mut l = l;
-    let mut advance = 0;
-
-    if stream.write.is_none() && !stream.can_write() {
-        // We can't write to this stream
-        return 0;
-    }
-    if let Some((wbase, mut wpos, wend)) = stream.write {
-        if l > wend - wpos {
-            // We can't fit all of buf in the buffer
-            return stream.write(buf);
-        }
-
-        let i = if stream.buf_char >= 0 {
-            let mut i = l;
-            while i > 0 && buf[i - 1] != b'\n' {
-                i -= 1;
-            }
-            if i > 0 {
-                let n = stream.write(buf);
-                match stream.write {
-                    Some((_, new_wpos, _)) => wpos = new_wpos,
-                    None => unreachable!("stream.write should never be None after a write call")
-                }
-
-                if n < i {
-                    return n;
-                }
-                advance += i;
-                l -= i;
-            }
-            i
-        } else {
-            0
-        };
+    let file = File::new(fd);
+    let writer = LineWriter::new(file.get_ref());
 
-        unsafe {
-            copy_nonoverlapping(
-                &buf[advance..] as *const _ as *const u8,
-                &mut stream.buf[wpos..] as *mut _ as *mut u8,
-                l,
-            );
-        }
-        stream.write = Some((wbase, wpos + l, wend));
-        l + i
-    } else {
-        0
-    }
-}
-
-/// Flush `stream` without locking it.
-pub fn fflush_unlocked(stream: &mut FILE) -> c_int {
-    if let Some((wbase, wpos, _)) = stream.write {
-        if wpos > wbase {
-            stream.write(&[]);
-            /*
-            if stream.wpos.is_null() {
-            return -1;
-        }
-             */
-        }
-    }
-
-    if let Some((rpos, rend)) = stream.read {
-        if rpos < rend {
-            stream.seek(rpos as i64 - rend as i64, SEEK_CUR);
-        }
-    }
+    Some(Box::into_raw(Box::new(FILE {
+        lock: AtomicBool::new(false),
 
-    stream.write = None;
-    stream.read = None;
-    0
+        file,
+        flags,
+        read_buf: Buffer::Owned(vec![0; BUFSIZ as usize]),
+        read_pos: 0,
+        read_size: 0,
+        unget: None,
+        writer
+    })))
 }
diff --git a/src/header/stdio/internal.rs b/src/header/stdio/internal.rs
deleted file mode 100644
index 60ad4f3ebfd9b02a6220a45c4ddb82067ab9425e..0000000000000000000000000000000000000000
--- a/src/header/stdio/internal.rs
+++ /dev/null
@@ -1,31 +0,0 @@
-use super::{constants, FILE};
-use platform::types::*;
-
-pub fn ftello(stream: &mut FILE) -> off_t {
-    let pos = stream.seek(
-        0,
-        if let Some((wbase, wpos, _)) = stream.write {
-            if (stream.flags & constants::F_APP > 0) && wpos > wbase {
-                constants::SEEK_END
-            } else {
-                constants::SEEK_CUR
-            }
-        } else {
-            constants::SEEK_CUR
-        },
-    );
-    if pos < 0 {
-        return pos;
-    }
-    let rdiff = if let Some((rpos, rend)) = stream.read {
-        rend - rpos
-    } else {
-        0
-    };
-    let wdiff = if let Some((wbase, wpos, _)) = stream.write {
-        wpos - wbase
-    } else {
-        0
-    };
-    pos - rdiff as i64 + wdiff as i64
-}
diff --git a/src/header/stdio/mod.rs b/src/header/stdio/mod.rs
index 6a8867d727db16ffb138b1ddb58b27de34721442..8c20ba6e0d83403b42b358bace3a10832d6b1b9b 100644
--- a/src/header/stdio/mod.rs
+++ b/src/header/stdio/mod.rs
@@ -1,22 +1,26 @@
 //! stdio implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/stdio.h.html
 
+use alloc::borrow::{Borrow, BorrowMut};
+use alloc::boxed::Box;
 use alloc::vec::Vec;
 use core::fmt::Write as WriteFmt;
 use core::fmt::{self, Error};
 use core::ops::{Deref, DerefMut};
 use core::sync::atomic::{self, AtomicBool, Ordering};
-use core::{ptr, str};
+use core::{ptr, str, slice};
 use va_list::VaList as va_list;
 
 use c_str::CStr;
+use fs::File;
 use header::errno::{self, STR_ERROR};
 use header::fcntl;
 use header::stdlib::mkstemp;
 use header::string::strlen;
-use platform;
+use io::{self, BufRead, LineWriter, SeekFrom, Read, Write};
 use platform::types::*;
-use platform::{errno, ReadByte, WriteByte};
 use platform::{Pal, Sys};
+use platform::{errno, WriteByte};
+use platform;
 
 mod printf;
 mod scanf;
@@ -29,158 +33,97 @@ mod constants;
 
 mod helpers;
 
-mod internal;
-
-///
-/// This struct gets exposed to the C API.
-///
-pub struct FILE {
-    flags: i32,
-    read: Option<(usize, usize)>,
-    write: Option<(usize, usize, usize)>,
-    fd: c_int,
-    buf: Vec<u8>,
-    buf_char: i8,
-    lock: AtomicBool,
-    unget: usize,
+enum Buffer<'a> {
+    Borrowed(&'a mut [u8]),
+    Owned(Vec<u8>)
 }
+impl<'a> Deref for Buffer<'a> {
+    type Target = [u8];
 
-impl FILE {
-    pub fn can_read(&mut self) -> bool {
-        /*
-        if self.flags & constants::F_BADJ > 0 {
-            // Static and needs unget region
-            self.buf = unsafe { self.buf.add(self.unget) };
-            self.flags &= !constants::F_BADJ;
-        }
-        */
-
-        if let Some(_) = self.read {
-            return true;
-        }
-        if let Some(_) = self.write {
-            self.write(&[]);
-        }
-        self.write = None;
-        if self.flags & constants::F_NORD > 0 {
-            self.flags |= constants::F_ERR;
-            return false;
-        }
-        self.read = if self.buf.len() == 0 {
-            Some((0, 0))
-        } else {
-            Some((self.buf.len() - 1, self.buf.len() - 1))
-        };
-        if self.flags & constants::F_EOF > 0 {
-            false
-        } else {
-            true
+    fn deref(&self) -> &Self::Target {
+        match self {
+            Buffer::Borrowed(inner) => inner,
+            Buffer::Owned(inner) => inner.borrow()
         }
     }
-    pub fn can_write(&mut self) -> bool {
-        /*
-        if self.flags & constants::F_BADJ > 0 {
-            // Static and needs unget region
-            self.buf = unsafe { self.buf.add(self.unget) };
-            self.flags &= !constants::F_BADJ;
+}
+impl<'a> DerefMut for Buffer<'a> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        match self {
+            Buffer::Borrowed(inner) => inner,
+            Buffer::Owned(inner) => inner.borrow_mut()
         }
-        */
+    }
+}
 
-        if self.flags & constants::F_NOWR > 0 {
-            self.flags &= constants::F_ERR;
-            return false;
-        }
-        // Buffer repositioning
-        if let Some(_) = self.write {
-            return true;
-        }
-        self.read = None;
-        self.write = if self.buf.len() == 0 {
-            Some((0, 0, 0))
-        } else {
-            Some((self.unget, self.unget, self.buf.len() - 1))
-        };
-        return true;
-    }
-
-    pub fn write(&mut self, to_write: &[u8]) -> usize {
-        if let Some((wbase, wpos, _)) = self.write {
-            let len = wpos - wbase;
-            let mut advance = 0;
-            let mut f_buf = &self.buf[wbase..wpos];
-            let mut f_filled = false;
-            let mut rem = f_buf.len() + to_write.len();
-            loop {
-                let mut count = if f_filled {
-                    Sys::write(self.fd, &f_buf[advance..])
-                } else {
-                    Sys::write(self.fd, &f_buf[advance..]) + Sys::write(self.fd, to_write)
-                };
-                if count == rem as isize {
-                    self.write = if self.buf.len() == 0 {
-                        Some((0, 0, 0))
-                    } else {
-                        Some((self.unget, self.unget, self.buf.len() - 1))
-                    };
-                    return to_write.len();
-                }
-                if count < 0 {
-                    self.write = None;
-                    self.flags |= constants::F_ERR;
-                    return 0;
-                }
-                rem -= count as usize;
-                if count as usize > len {
-                    count -= len as isize;
-                    f_buf = to_write;
-                    f_filled = true;
-                    advance = 0;
-                }
-                advance += count as usize;
+/// This struct gets exposed to the C API.
+pub struct FILE {
+    // Can't use spin crate because *_unlocked functions are things in C :(
+    lock: AtomicBool,
+
+    file: File,
+    flags: c_int,
+    read_buf: Buffer<'static>,
+    read_pos: usize,
+    read_size: usize,
+    unget: Option<u8>,
+    writer: LineWriter<File>
+}
+
+impl Read for FILE {
+    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+        if !buf.is_empty() {
+            if let Some(c) = self.unget.take() {
+                buf[0] = c;
+                return Ok(1);
             }
         }
-        // self.can_write() should always be called before self.write()
-        // and should automatically fill self.write if it returns true.
-        // Thus, we should never reach this
-        //            -- Tommoa (20/6/2018)
-        unreachable!()
-    }
-
-    pub fn read(&mut self, buf: &mut [u8]) -> usize {
-        let adj = if self.buf.len() > 0 { 0 } else { 1 };
-        let mut file_buf = &mut self.buf[self.unget..];
-        let count = if buf.len() <= 1 - adj {
-            Sys::read(self.fd, &mut file_buf)
-        } else {
-            Sys::read(self.fd, buf) + Sys::read(self.fd, &mut file_buf)
-        };
-        if count <= 0 {
-            self.flags |= if count == 0 {
-                constants::F_EOF
-            } else {
-                constants::F_ERR
-            };
-            return 0;
-        }
-        if count as usize <= buf.len() - adj {
-            return count as usize;
+
+        if self.read_pos == self.read_size {
+            self.fill_buf()?;
         }
-        // Adjust pointers
-        if file_buf.len() == 0 {
-            self.read = Some((0, 0));
-        } else if buf.len() > 0 {
-            self.read = Some((self.unget + 1, self.unget + (count as usize)));
-            buf[buf.len() - 1] = file_buf[0];
-        } else {
-            self.read = Some((self.unget, self.unget + (count as usize)));
+
+        let len = buf.len().min(self.read_size);
+        buf[..len].copy_from_slice(&mut self.read_buf[..len]);
+        self.consume(len);
+        Ok(len)
+    }
+}
+impl BufRead for FILE {
+    fn fill_buf(&mut self) -> io::Result<&[u8]> {
+        if self.read_pos == self.read_size {
+            self.read_size = self.file.read(&mut self.read_buf)?;
+            self.read_pos = 0;
         }
-        buf.len()
+        Ok(&self.read_buf[self.read_pos..self.read_size])
     }
-
-    pub fn seek(&self, off: off_t, whence: c_int) -> off_t {
-        Sys::lseek(self.fd, off, whence)
+    fn consume(&mut self, i: usize) {
+        self.read_pos = (self.read_pos + i).min(self.read_size);
     }
-
+}
+impl Write for FILE {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        self.writer.write(buf)
+    }
+    fn flush(&mut self) -> io::Result<()> {
+        self.writer.flush()
+    }
+}
+impl WriteFmt for FILE {
+    fn write_str(&mut self, s: &str) -> fmt::Result {
+        self.write_all(s.as_bytes())
+            .map(|_| ())
+            .map_err(|_| fmt::Error)
+    }
+}
+impl WriteByte for FILE {
+    fn write_u8(&mut self, c: u8) -> fmt::Result {
+        self.write_all(&[c])
+            .map(|_| ())
+            .map_err(|_| fmt::Error)
+    }
+}
+impl FILE {
     pub fn lock(&mut self) -> LockGuard {
         flockfile(self);
         LockGuard(self)
@@ -189,7 +132,7 @@ impl FILE {
 
 pub struct LockGuard<'a>(&'a mut FILE);
 impl<'a> Deref for LockGuard<'a> {
-    type Target = &'a mut FILE;
+    type Target = FILE;
 
     fn deref(&self) -> &Self::Target {
         &self.0
@@ -197,7 +140,7 @@ impl<'a> Deref for LockGuard<'a> {
 }
 impl<'a> DerefMut for LockGuard<'a> {
     fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.0
+        self.0
     }
 }
 impl<'a> Drop for LockGuard<'a> {
@@ -206,50 +149,6 @@ impl<'a> Drop for LockGuard<'a> {
     }
 }
 
-impl<'a> fmt::Write for LockGuard<'a> {
-    fn write_str(&mut self, s: &str) -> fmt::Result {
-        if !self.0.can_write() {
-            return Err(Error);
-        }
-        let s = s.as_bytes();
-        if self.0.write(s) != s.len() {
-            Err(Error)
-        } else {
-            Ok(())
-        }
-    }
-}
-impl<'a> WriteByte for LockGuard<'a> {
-    fn write_u8(&mut self, byte: u8) -> fmt::Result {
-        if !self.0.can_write() {
-            return Err(Error);
-        }
-        if self.0.write(&[byte]) != 1 {
-            Err(Error)
-        } else {
-            Ok(())
-        }
-    }
-}
-impl<'a> ReadByte for LockGuard<'a> {
-    fn read_u8(&mut self) -> Result<Option<u8>, ()> {
-        let mut buf = [0];
-        match self.0.read(&mut buf) {
-            0 => Ok(None),
-            _ => Ok(Some(buf[0])),
-        }
-    }
-}
-
-impl Drop for FILE {
-    fn drop(&mut self) {
-        // Flush
-        if let Some(_) = self.write {
-            self.write(&[]);
-        }
-    }
-}
-
 /// Clears EOF and ERR indicators on a stream
 #[no_mangle]
 pub extern "C" fn clearerr(stream: &mut FILE) {
@@ -271,18 +170,24 @@ pub extern "C" fn cuserid(_s: *mut c_char) -> *mut c_char {
 /// descriptor will be closed, so if it is important that the file be written to, use `fflush()`
 /// prior to using this function.
 #[no_mangle]
-pub extern "C" fn fclose(stream: &mut FILE) -> c_int {
+pub extern "C" fn fclose(stream: *mut FILE) -> c_int {
+    let stream = unsafe { &mut *stream };
     flockfile(stream);
-    let r = helpers::fflush_unlocked(stream) | Sys::close(stream.fd);
+
+    let mut r = stream.flush().is_err();
+    let close = Sys::close(*stream.file) < 0;
+    r = r || close;
+
     if stream.flags & constants::F_PERM == 0 {
         // Not one of stdin, stdout or stderr
-        unsafe {
-            platform::free(stream as *mut FILE as *mut c_void);
-        }
+        let mut stream = unsafe { Box::from_raw(stream) };
+        // Reference files aren't closed on drop, so pretend to be a reference
+        stream.file.reference = true;
     } else {
         funlockfile(stream);
     }
-    r
+
+    r as c_int
 }
 
 /// Open a file from a file descriptor
@@ -298,15 +203,15 @@ pub extern "C" fn fdopen(fildes: c_int, mode: *const c_char) -> *mut FILE {
 
 /// Check for EOF
 #[no_mangle]
-pub extern "C" fn feof(stream: &mut FILE) -> c_int {
-    let stream = stream.lock();
+pub extern "C" fn feof(stream: *mut FILE) -> c_int {
+    let stream = unsafe { &mut *stream }.lock();
     stream.flags & F_EOF
 }
 
 /// Check for ERR
 #[no_mangle]
-pub extern "C" fn ferror(stream: &mut FILE) -> c_int {
-    let stream = stream.lock();
+pub extern "C" fn ferror(stream: *mut FILE) -> c_int {
+    let stream = unsafe { &mut *stream }.lock();
     stream.flags & F_ERR
 }
 
@@ -314,109 +219,116 @@ pub extern "C" fn ferror(stream: &mut FILE) -> c_int {
 /// Ensure the file is unlocked before calling this function, as it will attempt to lock the file
 /// itself.
 #[no_mangle]
-pub unsafe extern "C" fn fflush(stream: &mut FILE) -> c_int {
-    let mut stream = stream.lock();
-    helpers::fflush_unlocked(&mut stream)
+pub unsafe extern "C" fn fflush(stream: *mut FILE) -> c_int {
+    unsafe { &mut *stream }.lock().flush().is_err() as c_int
 }
 
 /// Get a single char from a stream
 #[no_mangle]
-pub extern "C" fn fgetc(stream: &mut FILE) -> c_int {
-    let mut stream = stream.lock();
-    getc_unlocked(&mut stream)
+pub extern "C" fn fgetc(stream: *mut FILE) -> c_int {
+    let mut stream = unsafe { &mut *stream }.lock();
+    getc_unlocked(&mut *stream)
 }
 
 /// Get the position of the stream and store it in pos
 #[no_mangle]
-pub extern "C" fn fgetpos(stream: &mut FILE, pos: Option<&mut fpos_t>) -> c_int {
-    let off = internal::ftello(stream);
+pub extern "C" fn fgetpos(stream: *mut FILE, pos: *mut fpos_t) -> c_int {
+    let off = ftello(stream);
     if off < 0 {
         return -1;
     }
-    if let Some(pos) = pos {
+    unsafe {
         *pos = off;
-        0
-    } else {
-        -1
     }
+    0
 }
 
 /// Get a string from the stream
 #[no_mangle]
-pub extern "C" fn fgets(s: *mut c_char, n: c_int, stream: &mut FILE) -> *mut c_char {
-    use core::slice;
-    let mut stream = stream.lock();
-    let st = unsafe { slice::from_raw_parts_mut(s as *mut u8, n as usize) };
-
-    let mut len = n;
+pub extern "C" fn fgets(original: *mut c_char, max: c_int, stream: *mut FILE) -> *mut c_char {
+    let mut stream = unsafe { &mut *stream }.lock();
+    let mut out = original;
+    let max = max as usize;
+    let mut left = max.saturating_sub(1); // Make space for the terminating NUL-byte
+    let mut wrote = false;
 
-    // We can only fit one or less chars in
-    if n <= 1 {
-        if n <= 0 {
-            return ptr::null_mut();
-        }
-        unsafe {
-            (*s) = b'\0' as i8;
+    if left >= 1 {
+        if let Some(c) = stream.unget.take() {
+            unsafe {
+                *out = c as c_char;
+                out = out.offset(1);
+            }
+            left -= 1;
         }
-        return s;
     }
-    // Scope this so we can reuse stream mutably
-    {
-        // We can't read from this stream
-        if !stream.can_read() {
-            return ptr::null_mut();
+
+    loop {
+        if left == 0 {
+            break;
         }
-    }
 
-    // TODO: Look at this later to determine correctness and efficiency
-    'outer: while stream.read(&mut []) == 0 && stream.flags & F_ERR == 0 {
-        if let Some((rpos, rend)) = stream.read {
-            let mut idiff = 0usize;
-            for _ in (0..(len - 1) as usize).take_while(|x| rpos + x < rend) {
-                let pos = (n - len) as usize;
-                st[pos] = stream.buf[rpos + idiff];
-                idiff += 1;
-                len -= 1;
-                if st[pos] == b'\n' || st[pos] as i8 == stream.buf_char {
-                    stream.read = Some((rpos + idiff, rend));
-                    break 'outer;
-                }
-            }
-            stream.read = Some((rpos + idiff, rend));
-            if rend - rpos == 0 {
-                match stream.read(&mut st[((n - len) as usize)..]) as i32 {
-                    0 if idiff == 0 => return ptr::null_mut(),
-                    n => {
-                        len -= n.max(0);
-                        break;
+        // TODO: When NLL is a thing, this block can be flattened out
+        let (read, exit) = {
+            let mut buf = match stream.fill_buf() {
+                Ok(buf) => buf,
+                Err(err) => {
+                    unsafe {
+                        platform::errno = errno::EIO;
                     }
+                    return ptr::null_mut();
                 }
-            }
-            if len <= 1 {
+            };
+            if buf.is_empty() {
                 break;
             }
+            wrote = true;
+            let len = buf.len().min(left);
+
+            let newline = buf[..len].iter().position(|&c| c == b'\n');
+            let len = newline.map(|i| i + 1).unwrap_or(len);
+
+            unsafe {
+                ptr::copy_nonoverlapping(buf.as_ptr(), out as *mut u8, len);
+            }
+
+            (len, newline.is_some())
+        };
+
+        stream.consume(read);
+
+        out = unsafe { out.offset(read as isize) };
+        left -= read;
+
+        if exit {
+            break;
         }
-        // We can read, there's been no errors. We should have stream.read setbuf
-        //            -- Tommoa (3/7/2018)
-        unreachable!()
     }
 
-    st[(n - len) as usize] = 0;
-    s
+    if max >= 1 {
+        // Write the NUL byte
+        unsafe {
+            *out = 0;
+        }
+    }
+    if wrote {
+        original
+    } else {
+        ptr::null_mut()
+    }
 }
 
 /// Get the underlying file descriptor
 #[no_mangle]
-pub extern "C" fn fileno(stream: &mut FILE) -> c_int {
-    let stream = stream.lock();
-    stream.fd
+pub extern "C" fn fileno(stream: *mut FILE) -> c_int {
+    let stream = unsafe { &mut *stream }.lock();
+    *stream.file
 }
 
 /// Lock the file
 /// Do not call any functions other than those with the `_unlocked` postfix while the file is
 /// locked
 #[no_mangle]
-pub extern "C" fn flockfile(file: &mut FILE) {
+pub extern "C" fn flockfile(file: *mut FILE) {
     while ftrylockfile(file) != 0 {
         atomic::spin_loop_hint();
     }
@@ -459,69 +371,34 @@ pub extern "C" fn fopen(filename: *const c_char, mode: *const c_char) -> *mut FI
 
 /// Insert a character into the stream
 #[no_mangle]
-pub extern "C" fn fputc(c: c_int, stream: &mut FILE) -> c_int {
-    let mut stream = stream.lock();
-    putc_unlocked(c, &mut stream)
+pub extern "C" fn fputc(c: c_int, stream: *mut FILE) -> c_int {
+    let mut stream = unsafe { &mut *stream }.lock();
+    putc_unlocked(c, &mut *stream)
 }
 
 /// Insert a string into a stream
 #[no_mangle]
-pub extern "C" fn fputs(s: *const c_char, stream: &mut FILE) -> c_int {
+pub extern "C" fn fputs(s: *const c_char, stream: *mut FILE) -> c_int {
     let len = unsafe { strlen(s) };
     (fwrite(s as *const c_void, 1, len, stream) == len) as c_int - 1
 }
 
 /// Read `nitems` of size `size` into `ptr` from `stream`
 #[no_mangle]
-pub extern "C" fn fread(ptr: *mut c_void, size: usize, nitems: usize, stream: &mut FILE) -> usize {
-    use core::ptr::copy_nonoverlapping;
-    use core::slice;
-    let mut dest = ptr as *mut u8;
-    let len = size * nitems;
-    let mut l = len as isize;
-
-    let mut stream = stream.lock();
-
-    if !stream.can_read() {
-        return 0;
-    }
-
-    if let Some((rpos, rend)) = stream.read {
-        if rend > rpos {
-            // We have some buffered data that can be read
-            let diff = rend - rpos;
-            let k = if diff < l as usize { diff } else { l as usize };
+pub extern "C" fn fread(ptr: *mut c_void, size: size_t, count: size_t, stream: *mut FILE) -> size_t {
+    let mut stream = unsafe { &mut *stream }.lock();
+    let buf = unsafe { slice::from_raw_parts_mut(
+        ptr as *mut u8,
+        size as usize * count as usize
+    ) };
+    match stream.read(buf) {
+        Ok(bytes) => (bytes as usize / size as usize) as size_t,
+        Err(err) => {
             unsafe {
-                // Copy data
-                copy_nonoverlapping(&stream.buf[rpos..] as *const _ as *const u8, dest, k);
-                // Reposition pointers
-                dest = dest.add(k);
+                platform::errno = errno::EIO;
             }
-            stream.read = Some((rpos + k, rend));
-            l -= k as isize;
+            0
         }
-
-        while l > 0 {
-            let k = if !stream.can_read() {
-                0
-            } else {
-                stream.read(unsafe { slice::from_raw_parts_mut(dest, l as usize) })
-            };
-
-            if k == 0 {
-                return (len - l as usize) / 2;
-            }
-
-            l -= k as isize;
-            unsafe {
-                // Reposition
-                dest = dest.add(k);
-            }
-        }
-
-        nitems
-    } else {
-        unreachable!()
     }
 }
 
@@ -534,14 +411,14 @@ pub extern "C" fn freopen(
     let mut flags = unsafe { helpers::parse_mode_flags(mode) };
     flockfile(stream);
 
-    helpers::fflush_unlocked(stream);
+    let _ = stream.flush();
     if filename.is_null() {
         // Reopen stream in new mode
         if flags & fcntl::O_CLOEXEC > 0 {
-            fcntl::sys_fcntl(stream.fd, fcntl::F_SETFD, fcntl::FD_CLOEXEC);
+            fcntl::sys_fcntl(*stream.file, fcntl::F_SETFD, fcntl::FD_CLOEXEC);
         }
         flags &= !(fcntl::O_CREAT | fcntl::O_EXCL | fcntl::O_CLOEXEC);
-        if fcntl::sys_fcntl(stream.fd, fcntl::F_SETFL, flags) < 0 {
+        if fcntl::sys_fcntl(*stream.file, fcntl::F_SETFL, flags) < 0 {
             funlockfile(stream);
             fclose(stream);
             return ptr::null_mut();
@@ -554,10 +431,10 @@ pub extern "C" fn freopen(
             return ptr::null_mut();
         }
         let new = unsafe { &mut *new }; // Should be safe, new is not null
-        if new.fd == stream.fd {
-            new.fd = -1;
-        } else if Sys::dup2(new.fd, stream.fd) < 0
-            || fcntl::sys_fcntl(stream.fd, fcntl::F_SETFL, flags & fcntl::O_CLOEXEC) < 0
+        if *new.file == *stream.file {
+            new.file.fd = -1;
+        } else if Sys::dup2(*new.file, *stream.file) < 0
+            || fcntl::sys_fcntl(*stream.file, fcntl::F_SETFL, flags & fcntl::O_CLOEXEC) < 0
         {
             funlockfile(stream);
             fclose(new);
@@ -573,99 +450,93 @@ pub extern "C" fn freopen(
 
 /// Seek to an offset `offset` from `whence`
 #[no_mangle]
-pub extern "C" fn fseek(stream: &mut FILE, offset: c_long, whence: c_int) -> c_int {
-    if fseeko(stream, offset as off_t, whence) != -1 {
-        return 0;
-    }
-    -1
+pub extern "C" fn fseek(stream: *mut FILE, offset: c_long, whence: c_int) -> c_int {
+    fseeko(stream, offset as off_t, whence)
 }
 
 /// Seek to an offset `offset` from `whence`
 #[no_mangle]
-pub extern "C" fn fseeko(stream: &mut FILE, offset: off_t, whence: c_int) -> c_int {
-    let mut off = offset;
-    let mut stream = stream.lock();
+pub extern "C" fn fseeko(stream: *mut FILE, mut off: off_t, whence: c_int) -> c_int {
+    let mut stream = unsafe { &mut *stream }.lock();
 
-    // Adjust for what is currently in the buffer
-    let rdiff = if let Some((rpos, rend)) = stream.read {
-        rend - rpos
-    } else {
-        0
-    };
     if whence == SEEK_CUR {
-        off -= (rdiff) as i64;
+        // Since it's a buffered writer, our actual cursor isn't where the user
+        // thinks
+        off -= (stream.read_size - stream.read_pos) as off_t;
     }
-    if let Some(_) = stream.write {
-        stream.write(&[]);
-    }
-    stream.write = None;
-    if stream.seek(off, whence) < 0 {
-        return -1;
+
+    let err = Sys::lseek(*stream.file, off, whence);
+    if err < 0 {
+        return err as c_int;
     }
-    stream.read = None;
+
     stream.flags &= !F_EOF;
+    stream.read_pos = 0;
+    stream.read_size = 0;
+    stream.unget = None;
     0
 }
 
 /// Seek to a position `pos` in the file from the beginning of the file
 #[no_mangle]
-pub unsafe extern "C" fn fsetpos(stream: &mut FILE, pos: Option<&fpos_t>) -> c_int {
-    fseek(
-        stream,
-        *pos.expect("You must specify a valid position"),
-        SEEK_SET,
-    )
+pub unsafe extern "C" fn fsetpos(stream: *mut FILE, pos: *const fpos_t) -> c_int {
+    fseek(stream, *pos, SEEK_SET)
 }
 
 /// Get the current position of the cursor in the file
 #[no_mangle]
-pub extern "C" fn ftell(stream: &mut FILE) -> c_long {
+pub extern "C" fn ftell(stream: *mut FILE) -> c_long {
     ftello(stream) as c_long
 }
 
 /// Get the current position of the cursor in the file
 #[no_mangle]
-pub extern "C" fn ftello(stream: &mut FILE) -> off_t {
-    let mut stream = stream.lock();
-    internal::ftello(&mut stream)
+pub extern "C" fn ftello(stream: *mut FILE) -> off_t {
+    let mut stream = unsafe { &mut *stream }.lock();
+    let pos = Sys::lseek(*stream.file, 0, SEEK_CUR);
+    if pos < 0 {
+        return -1;
+    }
+
+    pos - (stream.read_size - stream.read_pos) as off_t
 }
 
 /// Try to lock the file. Returns 0 for success, 1 for failure
 #[no_mangle]
-pub extern "C" fn ftrylockfile(file: &mut FILE) -> c_int {
-    file.lock.compare_and_swap(false, true, Ordering::Acquire) as c_int
+pub extern "C" fn ftrylockfile(file: *mut FILE) -> c_int {
+    unsafe { &mut *file }.lock.compare_and_swap(false, true, Ordering::Acquire) as c_int
 }
 
 /// Unlock the file
 #[no_mangle]
-pub extern "C" fn funlockfile(file: &mut FILE) {
-    file.lock.store(false, Ordering::Release);
+pub extern "C" fn funlockfile(file: *mut FILE) {
+    unsafe { &mut *file }.lock.store(false, Ordering::Release);
 }
 
 /// Write `nitems` of size `size` from `ptr` to `stream`
 #[no_mangle]
-pub extern "C" fn fwrite(
-    ptr: *const c_void,
-    size: usize,
-    nitems: usize,
-    stream: &mut FILE,
-) -> usize {
-    let l = size * nitems;
-    let nitems = if size == 0 { 0 } else { nitems };
-    let mut stream = stream.lock();
-    let k = helpers::fwritex(ptr as *const u8, l, &mut stream);
-    if k == l {
-        nitems
-    } else {
-        k / size
+pub extern "C" fn fwrite(ptr: *const c_void, size: usize, count: usize, stream: *mut FILE) -> usize {
+    let mut stream = unsafe { &mut *stream }.lock();
+    let buf = unsafe { slice::from_raw_parts_mut(
+        ptr as *mut u8,
+        size as usize * count as usize
+    ) };
+    match stream.write(buf) {
+        Ok(bytes) => (bytes as usize / size as usize) as size_t,
+        Err(err) => {
+            unsafe {
+                platform::errno = errno::EIO;
+            }
+            0
+        }
     }
 }
 
 /// Get a single char from a stream
 #[no_mangle]
-pub extern "C" fn getc(stream: &mut FILE) -> c_int {
-    let mut stream = stream.lock();
-    getc_unlocked(&mut stream)
+pub extern "C" fn getc(stream: *mut FILE) -> c_int {
+    let mut stream = unsafe { &mut *stream }.lock();
+    getc_unlocked(&mut *stream)
 }
 
 /// Get a single char from `stdin`
@@ -676,28 +547,16 @@ pub extern "C" fn getchar() -> c_int {
 
 /// Get a char from a stream without locking the stream
 #[no_mangle]
-pub extern "C" fn getc_unlocked(stream: &mut FILE) -> c_int {
-    if !stream.can_read() {
-        return -1;
-    }
-    if let Some((rpos, rend)) = stream.read {
-        if rpos < rend {
-            let ret = stream.buf[rpos] as c_int;
-            stream.read = Some((rpos + 1, rend));
-            ret
-        } else {
-            let mut c = [0u8; 1];
-            if stream.read(&mut c) == 1 {
-                c[0] as c_int
-            } else {
-                -1
-            }
+pub extern "C" fn getc_unlocked(stream: *mut FILE) -> c_int {
+    let mut buf = [0];
+
+    match unsafe { &mut *stream }.read(&mut buf) {
+        Ok(0) => EOF,
+        Ok(_) => buf[0] as c_int,
+        Err(err) => unsafe {
+            platform::errno = errno::EIO;
+            EOF
         }
-    } else {
-        // We made a prior call to can_read() and are checking it, therefore we
-        // should never be in a case where stream.read is None
-        //            -- Tommoa (20/6/2018)
-        unreachable!()
     }
 }
 
@@ -710,20 +569,19 @@ pub extern "C" fn getchar_unlocked() -> c_int {
 /// Get a string from `stdin`
 #[no_mangle]
 pub extern "C" fn gets(s: *mut c_char) -> *mut c_char {
-    use core::i32;
-    fgets(s, i32::MAX, unsafe { &mut *stdin })
+    fgets(s, c_int::max_value(), unsafe { &mut *stdin })
 }
 
 /// Get an integer from `stream`
 #[no_mangle]
-pub extern "C" fn getw(stream: &mut FILE) -> c_int {
+pub extern "C" fn getw(stream: *mut FILE) -> c_int {
     use core::mem;
     let mut ret: c_int = 0;
     if fread(
-        &mut ret as *mut c_int as *mut c_void,
+        &mut ret as *mut _ as *mut c_void,
         mem::size_of_val(&ret),
         1,
-        stream,
+        stream
     ) > 0
     {
         ret
@@ -733,7 +591,7 @@ pub extern "C" fn getw(stream: &mut FILE) -> c_int {
 }
 
 // #[no_mangle]
-pub extern "C" fn pclose(_stream: &mut FILE) -> c_int {
+pub extern "C" fn pclose(_stream: *mut FILE) -> c_int {
     unimplemented!();
 }
 
@@ -759,9 +617,9 @@ pub extern "C" fn popen(_command: *const c_char, _mode: *const c_char) -> *mut F
 
 /// Put a character `c` into `stream`
 #[no_mangle]
-pub extern "C" fn putc(c: c_int, stream: &mut FILE) -> c_int {
-    let mut stream = stream.lock();
-    putc_unlocked(c, &mut stream)
+pub extern "C" fn putc(c: c_int, stream: *mut FILE) -> c_int {
+    let mut stream = unsafe { &mut *stream }.lock();
+    putc_unlocked(c, &mut *stream)
 }
 
 /// Put a character `c` into `stdout`
@@ -772,36 +630,27 @@ pub extern "C" fn putchar(c: c_int) -> c_int {
 
 /// Put a character `c` into `stream` without locking `stream`
 #[no_mangle]
-pub extern "C" fn putc_unlocked(c: c_int, stream: &mut FILE) -> c_int {
-    if stream.can_write() {
-        if let Some((wbase, wpos, wend)) = stream.write {
-            if c as i8 != stream.buf_char {
-                stream.buf[wpos] = c as u8;
-                stream.write = Some((wbase, wpos + 1, wend));
-                c
-            } else if stream.write(&[c as u8]) == 1 {
-                c
-            } else {
-                -1
-            }
-        } else {
-            -1
+pub extern "C" fn putc_unlocked(c: c_int, stream: *mut FILE) -> c_int {
+    match unsafe { &mut *stream }.write(&[c as u8]) {
+        Ok(0) => EOF,
+        Ok(_) => c,
+        Err(_) => unsafe {
+            platform::errno = errno::EIO;
+            EOF
         }
-    } else {
-        -1
     }
 }
 
 /// Put a character `c` into `stdout` without locking `stdout`
 #[no_mangle]
 pub extern "C" fn putchar_unlocked(c: c_int) -> c_int {
-    putc_unlocked(c, unsafe { &mut *stdout })
+    putc_unlocked(c, unsafe { stdout })
 }
 
 /// Put a string `s` into `stdout`
 #[no_mangle]
 pub extern "C" fn puts(s: *const c_char) -> c_int {
-    let ret = (fputs(s, unsafe { &mut *stdout }) > 0) || (putchar_unlocked(b'\n' as c_int) > 0);
+    let ret = (fputs(s, unsafe { stdout }) > 0) || (putchar_unlocked(b'\n' as c_int) > 0);
     if ret {
         0
     } else {
@@ -811,9 +660,9 @@ pub extern "C" fn puts(s: *const c_char) -> c_int {
 
 /// Put an integer `w` into `stream`
 #[no_mangle]
-pub extern "C" fn putw(w: c_int, stream: &mut FILE) -> c_int {
+pub extern "C" fn putw(w: c_int, stream: *mut FILE) -> c_int {
     use core::mem;
-    fwrite(&w as *const i32 as _, mem::size_of_val(&w), 1, stream) as i32 - 1
+    fwrite(&w as *const c_int as _, mem::size_of_val(&w), 1, stream) as i32 - 1
 }
 
 /// Delete file or directory `path`
@@ -837,15 +686,13 @@ pub extern "C" fn rename(oldpath: *const c_char, newpath: *const c_char) -> c_in
 
 /// Rewind `stream` back to the beginning of it
 #[no_mangle]
-pub extern "C" fn rewind(stream: &mut FILE) {
+pub extern "C" fn rewind(stream: *mut FILE) {
     fseeko(stream, 0, SEEK_SET);
-    let mut stream = stream.lock();
-    stream.flags &= !F_ERR;
 }
 
 /// Reset `stream` to use buffer `buf`. Buffer must be `BUFSIZ` in length
 #[no_mangle]
-pub extern "C" fn setbuf(stream: &mut FILE, buf: *mut c_char) {
+pub extern "C" fn setbuf(stream: *mut FILE, buf: *mut c_char) {
     setvbuf(
         stream,
         buf,
@@ -857,31 +704,27 @@ pub extern "C" fn setbuf(stream: &mut FILE, buf: *mut c_char) {
 /// Reset `stream` to use buffer `buf` of size `size`
 /// If this isn't the meaning of unsafe, idk what is
 #[no_mangle]
-pub extern "C" fn setvbuf(stream: &mut FILE, buf: *mut c_char, mode: c_int, size: usize) -> c_int {
+pub extern "C" fn setvbuf(stream: *mut FILE, buf: *mut c_char, mode: c_int, mut size: size_t) -> c_int {
+    let mut stream = unsafe { &mut *stream }.lock();
     // Set a buffer of size `size` if no buffer is given
-    let buf = if buf.is_null() {
-        if mode != _IONBF {
-            vec![0u8; 1]
-        } else {
-            stream.unget = 0;
-            if let Some(_) = stream.write {
-                stream.write = Some((0, 0, 0));
-            } else if let Some(_) = stream.read {
-                stream.read = Some((0, 0));
-            }
-            Vec::new()
-        }
+    stream.read_buf = if buf.is_null() || size == 0 {
+        if size == 0 {
+            size = BUFSIZ as usize;
+        }
+        // TODO: Make it unbuffered if _IONBF
+        // if mode == _IONBF {
+        // } else {
+        Buffer::Owned(vec![0; size as usize])
+        // }
     } else {
-        // We trust the user on this one
-        //        -- Tommoa (20/6/2018)
-        unsafe { Vec::from_raw_parts(buf as *mut u8, size, size) }
+        unsafe {
+            Buffer::Borrowed(slice::from_raw_parts_mut(
+                buf as *mut u8,
+                size
+            ))
+        }
     };
-    stream.buf_char = -1;
-    if mode == _IOLBF {
-        stream.buf_char = b'\n' as i8;
-    }
     stream.flags |= F_SVB;
-    stream.buf = buf;
     0
 }
 
@@ -920,32 +763,22 @@ pub extern "C" fn tmpnam(_s: *mut c_char) -> *mut c_char {
 
 /// Push character `c` back onto `stream` so it'll be read next
 #[no_mangle]
-pub extern "C" fn ungetc(c: c_int, stream: &mut FILE) -> c_int {
-    if c < 0 {
-        c
-    } else {
-        let mut stream = stream.lock();
-
-        if stream.read.is_none() {
-            stream.can_read();
-        }
-        if let Some((rpos, rend)) = stream.read {
-            if rpos == 0 {
-                return -1;
-            }
-            stream.read = Some((rpos - 1, rend));
-            stream.buf[rpos - 1] = c as u8;
-            stream.flags &= !F_EOF;
-            c
-        } else {
-            -1
+pub extern "C" fn ungetc(c: c_int, stream: *mut FILE) -> c_int {
+    let mut stream = unsafe { &mut *stream }.lock();
+    if stream.unget.is_some() {
+        unsafe {
+            platform::errno = errno::EIO;
+            return EOF;
         }
     }
+    stream.unget = Some(c as u8);
+    c
 }
 
 #[no_mangle]
-pub unsafe extern "C" fn vfprintf(file: &mut FILE, format: *const c_char, ap: va_list) -> c_int {
-    printf::printf(file.lock(), format, ap)
+pub unsafe extern "C" fn vfprintf(file: *mut FILE, format: *const c_char, ap: va_list) -> c_int {
+    let mut file = (*file).lock();
+    printf::printf(&mut *file, format, ap)
 }
 
 #[no_mangle]
@@ -973,8 +806,9 @@ pub unsafe extern "C" fn vsprintf(s: *mut c_char, format: *const c_char, ap: va_
 }
 
 #[no_mangle]
-pub unsafe extern "C" fn vfscanf(file: &mut FILE, format: *const c_char, ap: va_list) -> c_int {
-    scanf::scanf(file.lock(), format, ap)
+pub unsafe extern "C" fn vfscanf(file: *mut FILE, format: *const c_char, ap: va_list) -> c_int {
+    let mut file = (*file).lock();
+    scanf::scanf(&mut *file, format, ap)
 }
 
 #[no_mangle]
diff --git a/src/header/stdio/scanf.rs b/src/header/stdio/scanf.rs
index 97864af620e7cf3c9816cbb5ef708fe81f1f85ea..c92ff7721efc56a97e4b89a5d89d4ba785151e10 100644
--- a/src/header/stdio/scanf.rs
+++ b/src/header/stdio/scanf.rs
@@ -1,7 +1,7 @@
 use alloc::String;
 use alloc::Vec;
+use io::Read;
 use platform::types::*;
-use platform::ReadByte;
 use va_list::VaList;
 
 #[derive(PartialEq, Eq)]
@@ -27,7 +27,7 @@ unsafe fn next_byte(string: &mut *const c_char) -> Result<u8, c_int> {
     }
 }
 
-unsafe fn inner_scanf<R: ReadByte>(
+unsafe fn inner_scanf<R: Read>(
     mut r: R,
     mut format: *const c_char,
     mut ap: VaList,
@@ -39,14 +39,15 @@ unsafe fn inner_scanf<R: ReadByte>(
 
     macro_rules! read {
         () => {{
-            match r.read_u8() {
-                Ok(Some(b)) => {
-                    byte = b;
+            let mut buf = &mut [byte];
+            match r.read(buf) {
+                Ok(0) => false,
+                Ok(_) => {
+                    byte = buf[0];
                     count += 1;
                     true
                 }
-                Ok(None) => false,
-                Err(()) => return Err(-1),
+                Err(_) => return Err(-1),
             }
         }};
     }
@@ -420,7 +421,7 @@ unsafe fn inner_scanf<R: ReadByte>(
     }
     Ok(matched)
 }
-pub unsafe fn scanf<R: ReadByte>(r: R, format: *const c_char, ap: VaList) -> c_int {
+pub unsafe fn scanf<R: Read>(r: R, format: *const c_char, ap: VaList) -> c_int {
     match inner_scanf(r, format, ap) {
         Ok(n) => n,
         Err(n) => n,
diff --git a/src/platform/linux/mod.rs b/src/platform/linux/mod.rs
index 08a6f4d1dc50097d2ec99fb0c0fb911e18fae335..5b3d8957335a5d1199dd1b72342ff0db1183fdde 100644
--- a/src/platform/linux/mod.rs
+++ b/src/platform/linux/mod.rs
@@ -194,32 +194,31 @@ impl Pal for Sys {
         e(unsafe { syscall!(GETGID) }) as gid_t
     }
 
-    unsafe fn gethostname(mut name: *mut c_char, len: size_t) -> c_int {
-        // len only needs to be mutable on linux
-        let mut len = len;
-
-        let mut uts = mem::uninitialized();
-        let err = Sys::uname(&mut uts);
-        if err < 0 {
-            mem::forget(uts);
-            return err;
-        }
-        for c in uts.nodename.iter() {
-            if len == 0 {
-                break;
+    fn gethostname(mut name: *mut c_char, mut len: size_t) -> c_int {
+        unsafe {
+            let mut uts = mem::uninitialized();
+            let err = Sys::uname(&mut uts);
+            if err < 0 {
+                mem::forget(uts);
+                return err;
             }
-            len -= 1;
+            for c in uts.nodename.iter() {
+                if len == 0 {
+                    break;
+                }
+                len -= 1;
 
-            *name = *c;
+                *name = *c;
 
-            if *name == 0 {
-                // We do want to copy the zero also, so we check this after the copying.
-                break;
-            }
+                if *name == 0 {
+                    // We do want to copy the zero also, so we check this after the copying.
+                    break;
+                }
 
-            name = name.offset(1);
+                name = name.offset(1);
+            }
+            0
         }
-        0
     }
 
     fn getpgid(pid: pid_t) -> pid_t {
diff --git a/src/platform/mod.rs b/src/platform/mod.rs
index e492bc6338a900586da913e823e3ae4f501de243..ca964759ab7038c05a8f43d5e968b5db80625264 100644
--- a/src/platform/mod.rs
+++ b/src/platform/mod.rs
@@ -1,5 +1,6 @@
 use alloc::vec::Vec;
 use core::{fmt, ptr};
+use io::{self, Read};
 
 pub use self::allocator::*;
 
@@ -70,16 +71,6 @@ impl<'a, W: WriteByte> WriteByte for &'a mut W {
     }
 }
 
-pub trait ReadByte {
-    fn read_u8(&mut self) -> Result<Option<u8>, ()>;
-}
-
-impl<'a, R: ReadByte> ReadByte for &'a mut R {
-    fn read_u8(&mut self) -> Result<Option<u8>, ()> {
-        (**self).read_u8()
-    }
-}
-
 pub struct FileWriter(pub c_int);
 
 impl FileWriter {
@@ -110,13 +101,13 @@ impl FileReader {
     }
 }
 
-impl ReadByte for FileReader {
-    fn read_u8(&mut self) -> Result<Option<u8>, ()> {
-        let mut buf = [0];
-        match self.read(&mut buf) {
-            0 => Ok(None),
-            n if n < 0 => Err(()),
-            _ => Ok(Some(buf[0])),
+impl Read for FileReader {
+    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+        let i = Sys::read(self.0, buf);
+        if i >= 0 {
+            Ok(i as usize)
+        } else {
+            Err(io::Error::from_raw_os_error(-i as i32))
         }
     }
 }
@@ -182,32 +173,20 @@ impl WriteByte for UnsafeStringWriter {
     }
 }
 
-pub struct StringReader<'a>(pub &'a [u8]);
-
-impl<'a> ReadByte for StringReader<'a> {
-    fn read_u8(&mut self) -> Result<Option<u8>, ()> {
-        if self.0.is_empty() {
-            Ok(None)
-        } else {
-            let byte = self.0[0];
-            self.0 = &self.0[1..];
-            Ok(Some(byte))
-        }
-    }
-}
-
 pub struct UnsafeStringReader(pub *const u8);
 
-impl ReadByte for UnsafeStringReader {
-    fn read_u8(&mut self) -> Result<Option<u8>, ()> {
+impl Read for UnsafeStringReader {
+    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
         unsafe {
-            if *self.0 == 0 {
-                Ok(None)
-            } else {
-                let byte = *self.0;
+            for i in 0..buf.len() {
+                if *self.0 == 0 {
+                    return Ok(i);
+                }
+
+                buf[i] = *self.0;
                 self.0 = self.0.offset(1);
-                Ok(Some(byte))
             }
+            Ok(buf.len())
         }
     }
 }
diff --git a/src/platform/pal/mod.rs b/src/platform/pal/mod.rs
index 9a3a2030dc193ef37d48060b63cbd379b9f816ce..bd18cbabe1f8af8c066de3488eab114e632ba29c 100644
--- a/src/platform/pal/mod.rs
+++ b/src/platform/pal/mod.rs
@@ -71,7 +71,7 @@ pub trait Pal {
 
     fn getgid() -> gid_t;
 
-    unsafe fn gethostname(name: *mut c_char, len: size_t) -> c_int;
+    fn gethostname(name: *mut c_char, len: size_t) -> c_int;
 
     fn getpgid(pid: pid_t) -> pid_t;
 
diff --git a/src/platform/redox/mod.rs b/src/platform/redox/mod.rs
index 59a5edcecf414835fead726ecb5be0a321f5cce7..42cb72b974d2a1c49b20a4c54aa376bf48a19824 100644
--- a/src/platform/redox/mod.rs
+++ b/src/platform/redox/mod.rs
@@ -11,8 +11,10 @@ use syscall::flag::*;
 use syscall::{self, Result};
 
 use c_str::{CStr, CString};
+use fs::File;
+use io;
 use header::dirent::dirent;
-use header::errno::{EINVAL, ENOSYS};
+use header::errno::{EIO, EINVAL, ENOSYS};
 use header::fcntl;
 const MAP_ANON: c_int = 1;
 //use header::sys_mman::MAP_ANON;
@@ -423,31 +425,30 @@ impl Pal for Sys {
         e(syscall::getgid()) as gid_t
     }
 
-    unsafe fn gethostname(mut name: *mut c_char, len: size_t) -> c_int {
-        let fd = match RawFile::open(
-            &CString::new("/etc/hostname").unwrap(),
-            fcntl::O_RDONLY | fcntl::O_CLOEXEC,
-            0,
-        ) {
-            Ok(fd) => fd,
-            Err(_) => return -1,
-        };
+    fn gethostname(name: *mut c_char, len: size_t) -> c_int {
+        fn inner(name: &mut [u8]) -> io::Result<()> {
+            let mut file = File::open(
+                &CString::new("/etc/hostname").unwrap(),
+                fcntl::O_RDONLY | fcntl::O_CLOEXEC
+            )?;
 
-        let mut reader = FileReader(*fd);
-        for _ in 0..len {
-            match reader.read_u8() {
-                Ok(Some(b)) => {
-                    *name = b as c_char;
-                    name = name.offset(1);
-                }
-                Ok(None) => {
-                    *name = 0;
-                    break;
+            let mut read = 0;
+            loop {
+                match file.read(&mut name[read..])? {
+                    0 => break,
+                    n => read += n
                 }
-                Err(()) => return -1,
+            }
+            Ok(())
+        }
+
+        match inner(unsafe { slice::from_raw_parts_mut(name as *mut u8, len as usize) }) {
+            Ok(()) => 0,
+            Err(_) => unsafe {
+                errno = EIO;
+                -1
             }
         }
-        0
     }
 
     fn getpgid(pid: pid_t) -> pid_t {
diff --git a/tests/Makefile b/tests/Makefile
index ab914b7798a27b164e823cc06924171fd5d50977..34bc799f3d5b1a3bd804bc3246aa7d78589c151d 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -16,7 +16,9 @@ EXPECT_BINS=\
 	signal \
 	stdio/all \
 	stdio/buffer \
+	stdio/fgets \
 	stdio/freopen \
+	stdio/fseek \
 	stdio/fwrite \
 	stdio/getc_unget \
 	stdio/printf \
diff --git a/tests/expected/stdio/buffer.stderr b/tests/expected/stdio/buffer.stderr
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/tests/expected/stdio/buffer.stdout b/tests/expected/stdio/buffer.stdout
new file mode 100644
index 0000000000000000000000000000000000000000..4b124bc7c01363ec010f1d727b8fb106838b6883
--- /dev/null
+++ b/tests/expected/stdio/buffer.stdout
@@ -0,0 +1,5 @@
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaTest
+Hello
+World
+It works
+No buffering issues here
diff --git a/tests/expected/stdio/fgets.stderr b/tests/expected/stdio/fgets.stderr
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/tests/expected/stdio/fgets.stdout b/tests/expected/stdio/fgets.stdout
new file mode 100644
index 0000000000000000000000000000000000000000..a856e2de524d031649f2f5c84fdc43cbd6decbc8
--- /dev/null
+++ b/tests/expected/stdio/fgets.stdout
@@ -0,0 +1,31 @@
+Hello World!
+
+Line 2
+
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
+eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
+fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
+hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
+iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
+jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj
+kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk
+lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll
+mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
+nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
+ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
+ppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
+qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq
+rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr
+sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss
+ttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt
+uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
+vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
+wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
+EOF
diff --git a/tests/expected/stdio/fseek.stderr b/tests/expected/stdio/fseek.stderr
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/tests/expected/stdio/fseek.stdout b/tests/expected/stdio/fseek.stdout
new file mode 100644
index 0000000000000000000000000000000000000000..574d89ae9e30f294a172cf0192722281134d4c12
--- /dev/null
+++ b/tests/expected/stdio/fseek.stdout
@@ -0,0 +1,2 @@
+Line 2
+ftell: 21
diff --git a/tests/expected/stdio/setvbuf.stdout b/tests/expected/stdio/setvbuf.stdout
index 7f04b787f7dc9dab3d29ca26572878533525395d..ebda98d01a2738e3b1f5ea75d3163815195255e3 100644
--- a/tests/expected/stdio/setvbuf.stdout
+++ b/tests/expected/stdio/setvbuf.stdout
@@ -1,6 +1,4 @@
 H
-ello World!
-
-Line 2
+Hello World!
 
 Hello
diff --git a/tests/stdio/fgets.c b/tests/stdio/fgets.c
new file mode 100644
index 0000000000000000000000000000000000000000..34877e9089821a5e3f987f2dcde113fc203c5634
--- /dev/null
+++ b/tests/stdio/fgets.c
@@ -0,0 +1,16 @@
+#include <stdio.h>
+
+int main() {
+    //FILE *f = fopen("/etc/ssl/certs/ca-certificates.crt", "r");
+    FILE *f = fopen("stdio/stdio.in", "r");
+    char line[256];
+
+    while (1) {
+        if (fgets(line, 256, f)) {
+            fputs(line, stdout);
+        } else {
+            puts("EOF");
+            break;
+        }
+    }
+}
diff --git a/tests/stdio/fseek.c b/tests/stdio/fseek.c
new file mode 100644
index 0000000000000000000000000000000000000000..4bed8733c5efa84db9916a163304a6e09356d377
--- /dev/null
+++ b/tests/stdio/fseek.c
@@ -0,0 +1,12 @@
+#include <stdio.h>
+
+int main() {
+	FILE *f = fopen("stdio/stdio.in", "r");
+    if (fseek(f, 14, SEEK_CUR) < 0) {
+        puts("fseek error");
+        return 1;
+    }
+    char buffer[256];
+    printf("%s", fgets(buffer, 256, f));
+    printf("ftell: %d\n", ftello(f));
+}
diff --git a/tests/stdio/stdio.in b/tests/stdio/stdio.in
index c50d87bb17331bbf91d4b38a48453dc1aee539f1..e4d49013909e51e9cdf5b076bd7c170d11510d38 100644
--- a/tests/stdio/stdio.in
+++ b/tests/stdio/stdio.in
@@ -1,3 +1,30 @@
 Hello World!
 
 Line 2
+
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
+ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
+ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
+eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee
+fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
+ggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg
+hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
+iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii
+jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj
+kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk
+lllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll
+mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
+nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
+ooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
+ppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppppp
+qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq
+rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr
+sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss
+ttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttttt
+uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
+vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
+wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
+zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz