From 7f2b720962f9fbabdfda651af4fa59c914021f20 Mon Sep 17 00:00:00 2001 From: Tom Almeida Date: Thu, 15 Mar 2018 15:27:07 +0800 Subject: [PATCH] Implemented stdio functions --- src/stdio/Cargo.toml | 4 + src/stdio/src/constants.rs | 27 ++ src/stdio/src/default.rs | 85 ++++++ src/stdio/src/helpers.rs | 159 ++++++++++ src/stdio/src/internal.rs | 143 +++++++++ src/stdio/src/lib.rs | 596 +++++++++++++++++++++++++++++-------- 6 files changed, 898 insertions(+), 116 deletions(-) create mode 100644 src/stdio/src/constants.rs create mode 100644 src/stdio/src/default.rs create mode 100644 src/stdio/src/helpers.rs create mode 100644 src/stdio/src/internal.rs diff --git a/src/stdio/Cargo.toml b/src/stdio/Cargo.toml index 2f53e58dc7..1a920812a6 100644 --- a/src/stdio/Cargo.toml +++ b/src/stdio/Cargo.toml @@ -8,6 +8,10 @@ build = "build.rs" cbindgen = { path = "../../cbindgen" } [dependencies] +compiler_builtins = { git = "https://github.com/rust-lang-nursery/compiler-builtins.git", default-features = false, features = ["mem"] } platform = { path = "../platform" } va_list = { path = "../../va_list", features = ["no_std"] } +fcntl = { path = "../fcntl" } +string = { path = "../string" } +stdlib = { path = "../stdlib" } errno = { path = "../errno"} diff --git a/src/stdio/src/constants.rs b/src/stdio/src/constants.rs new file mode 100644 index 0000000000..c601cbc85e --- /dev/null +++ b/src/stdio/src/constants.rs @@ -0,0 +1,27 @@ +use platform::types::*; + +pub const BUFSIZ: size_t = 1024; + +pub const UNGET: size_t = 8; + +pub const FILENAME_MAX: c_int = 4096; + +pub const F_PERM: c_int = 1; +pub const F_NORD: c_int = 4; +pub const F_NOWR: c_int = 8; +pub const F_EOF: c_int = 16; +pub const F_ERR: c_int = 32; +pub const F_SVB: c_int = 64; +pub const F_APP: c_int = 128; +pub const F_BADJ: c_int = 256; + +pub const SEEK_SET: c_int = 0; +pub const SEEK_CUR: c_int = 1; +pub const SEEK_END: c_int = 2; + +pub const _IOFBF: c_int = 0; +pub const _IOLBF: c_int = 1; +pub const _IONBF: c_int = 2; + +#[allow(non_camel_case_types)] +pub type fpos_t = off_t; diff --git a/src/stdio/src/default.rs b/src/stdio/src/default.rs new file mode 100644 index 0000000000..b3c1d71f67 --- /dev/null +++ b/src/stdio/src/default.rs @@ -0,0 +1,85 @@ +use core::sync::atomic::AtomicBool; +use core::ptr; +use super::{internal, BUFSIZ, FILE, UNGET, constants}; + +#[allow(non_upper_case_globals)] +static mut default_stdin_buf: [u8; BUFSIZ as usize + UNGET] = [0; BUFSIZ as usize + UNGET]; + +#[allow(non_upper_case_globals)] +static mut default_stdin: FILE = FILE { + flags: constants::F_PERM | constants::F_NOWR | constants::F_BADJ, + rpos: ptr::null_mut(), + rend: ptr::null_mut(), + wend: ptr::null_mut(), + wpos: ptr::null_mut(), + wbase: ptr::null_mut(), + fd: 0, + buf: unsafe { &mut default_stdin_buf as *mut [u8] as *mut u8 }, + buf_size: BUFSIZ as usize, + buf_char: -1, + unget: UNGET, + lock: AtomicBool::new(false), + write: None, + read: Some(&internal::stdio_read), + seek: None, +}; + +#[allow(non_upper_case_globals)] +static mut default_stdout_buf: [u8; BUFSIZ as usize] = [0; BUFSIZ as usize]; + +#[allow(non_upper_case_globals)] +static mut default_stdout: FILE = FILE { + flags: constants::F_PERM | constants::F_NORD | constants::F_BADJ, + rpos: ptr::null_mut(), + rend: ptr::null_mut(), + wend: ptr::null_mut(), + wpos: ptr::null_mut(), + wbase: ptr::null_mut(), + fd: 1, + buf: unsafe { &mut default_stdout_buf } as *mut [u8] as *mut u8, + buf_size: BUFSIZ as usize, + buf_char: b'\n' as i8, + unget: 0, + lock: AtomicBool::new(false), + write: Some(&internal::stdio_write), + read: None, + seek: None, +}; + +#[allow(non_upper_case_globals)] +static mut default_stderr_buf: [u8; BUFSIZ as usize] = [0; BUFSIZ as usize]; + +#[allow(non_upper_case_globals)] +static mut default_stderr: FILE = FILE { + flags: constants::F_PERM | constants::F_NORD | constants::F_BADJ, + rpos: ptr::null_mut(), + rend: ptr::null_mut(), + wend: ptr::null_mut(), + wpos: ptr::null_mut(), + wbase: ptr::null_mut(), + fd: 2, + buf: unsafe { &mut default_stderr_buf } as *mut [u8] as *mut u8, + buf_size: BUFSIZ as usize, + buf_char: -1, + unget: 0, + lock: AtomicBool::new(false), + write: Some(&internal::stdio_write), + read: None, + seek: None, +}; + +// Don't ask me how the casting below works, I have no idea +// " as *const FILE as *mut FILE" rust pls +// +// -- Tommoa +#[allow(non_upper_case_globals)] +#[no_mangle] +pub static mut stdin: *mut FILE = unsafe { &default_stdin } as *const FILE as *mut FILE; + +#[allow(non_upper_case_globals)] +#[no_mangle] +pub static mut stdout: *mut FILE = unsafe { &default_stdout } as *const FILE as *mut FILE; + +#[allow(non_upper_case_globals)] +#[no_mangle] +pub static mut stderr: *mut FILE = unsafe { &default_stderr } as *const FILE as *mut FILE; diff --git a/src/stdio/src/helpers.rs b/src/stdio/src/helpers.rs new file mode 100644 index 0000000000..4bc84ac375 --- /dev/null +++ b/src/stdio/src/helpers.rs @@ -0,0 +1,159 @@ +use super::{internal, BUFSIZ, FILE, UNGET}; +use compiler_builtins::mem::memset; +use stdlib::calloc; +use core::{mem, ptr}; +use core::sync::atomic::AtomicBool; +use platform::types::*; +use super::constants::*; +use fcntl::*; +use platform; +use errno; + +/// Parse mode flags as a string and output a mode flags integer +pub unsafe fn parse_mode_flags(mode_str: *const c_char) -> i32 { + use string::strchr; + let mut flags = if !strchr(mode_str, b'+' as i32).is_null() { + O_RDWR + } else if (*mode_str) == b'r' as i8 { + O_RDONLY + } else { + O_WRONLY + }; + if !strchr(mode_str, b'x' as i32).is_null() { + flags |= O_EXCL; + } + if !strchr(mode_str, b'e' as i32).is_null() { + flags |= O_CLOEXEC; + } + if (*mode_str) != b'r' as i8 { + flags |= O_CREAT; + } + if (*mode_str) == b'w' as i8 { + flags |= O_TRUNC; + } + if (*mode_str) != b'a' as i8 { + flags |= O_APPEND; + } + + flags +} + +/// Open a file with the file descriptor `fd` in the mode `mode` +pub unsafe fn _fdopen(fd: c_int, mode: *const c_char) -> *mut FILE { + use string::strchr; + if *mode != b'r' as i8 && *mode != b'w' as i8 && *mode != b'a' as i8 { + platform::errno = errno::EINVAL; + return ptr::null_mut(); + } + + let mut flags = 0; + if strchr(mode, b'+' as i32).is_null() { + flags |= if *mode == b'r' as i8 { F_NOWR } else { F_NORD }; + } + + if !strchr(mode, b'e' as i32).is_null() { + sys_fcntl(fd, F_SETFD, FD_CLOEXEC); + } + + if *mode == 'a' as i8 { + let f = sys_fcntl(fd, F_GETFL, 0); + if (f & O_APPEND) == 0 { + sys_fcntl(fd, F_SETFL, f | O_APPEND); + } + flags |= F_APP; + } + + let file = calloc(mem::size_of::() + BUFSIZ + UNGET, 1) as *mut FILE; + // Allocate the file + (*file) = FILE { + flags: flags, + rpos: ptr::null_mut(), + rend: ptr::null_mut(), + wend: ptr::null_mut(), + wpos: ptr::null_mut(), + wbase: ptr::null_mut(), + fd: fd, + buf: (file as *mut u8).add(mem::size_of::() + UNGET), + buf_size: BUFSIZ, + buf_char: -1, + unget: UNGET, + lock: AtomicBool::new(false), + write: Some(&internal::stdio_write), + read: Some(&internal::stdio_read), + seek: Some(&internal::stdio_seek), + }; + file +} + +/// Write buffer `buf` of length `l` into `stream` +pub unsafe fn fwritex(buf: *const u8, l: size_t, stream: *mut FILE) -> size_t { + use compiler_builtins::mem::memcpy; + let mut buf = buf; + let mut l = l; + if let Some(stream_write) = (*stream).write { + if (*stream).wend.is_null() && !internal::to_write(stream) { + // We can't write to this stream + return 0; + } + if l > (*stream).wend as usize - (*stream).wpos as usize { + // We can't fit all of buf in the buffer + return (*stream_write)(stream, buf, l); + } + + let i = if (*stream).buf_char >= 0 { + let mut i = l; + while i > 0 && *buf.offset(i as isize - 1) != b'\n' { + i -= 1; + } + if i > 0 { + let n = (*stream_write)(stream, buf, i); + if n < i { + return n; + } + buf = buf.add(i); + l -= i; + } + i + } else { + 0 + }; + + memcpy((*stream).wpos, buf, l); + (*stream).wpos = (*stream).wpos.add(l); + l + i + } else { + // We can't write to this stream + 0 + } +} + +/// Flush `stream` without locking it. +pub unsafe fn fflush_unlocked(stream: *mut FILE) -> c_int { + if (*stream).wpos > (*stream).wbase { + if let Some(f) = (*stream).write { + (*f)(stream, ptr::null(), 0); + if (*stream).wpos.is_null() { + return -1; + } + } else { + return -1; + } + } + + if (*stream).rpos < (*stream).rend { + if let Some(s) = (*stream).seek { + (*s)( + stream, + (*stream).rpos as i64 - (*stream).rend as i64, + SEEK_CUR, + ); + } + } + + (*stream).wpos = ptr::null_mut(); + (*stream).wend = ptr::null_mut(); + (*stream).wbase = ptr::null_mut(); + (*stream).rpos = ptr::null_mut(); + (*stream).rend = ptr::null_mut(); + 0 +} diff --git a/src/stdio/src/internal.rs b/src/stdio/src/internal.rs new file mode 100644 index 0000000000..5330eb269d --- /dev/null +++ b/src/stdio/src/internal.rs @@ -0,0 +1,143 @@ +use super::{FILE, constants}; +use platform; +use platform::types::*; +use core::{mem, ptr, slice}; + +pub fn stdio_read(stream: *mut FILE, buf: *mut u8, size: usize) -> usize { + unsafe { + let mut buff = slice::from_raw_parts_mut(buf, size - !((*stream).buf_size == 0) as usize); + let mut file_buf = slice::from_raw_parts_mut((*stream).buf, (*stream).buf_size); + let mut file = platform::FileReader((*stream).fd); + let count = if buff.len() == 0 { + file.read(&mut file_buf) + } else { + file.read(&mut buff) + file.read(&mut file_buf) + }; + mem::forget(buff); + mem::forget(file_buf); + if count <= 0 { + (*stream).flags |= if count == 0 { constants::F_EOF } else { constants::F_ERR }; + return 0; + } + if count as usize <= size { + return count as usize; + } + (*stream).rpos = (*stream).buf; + (*stream).rend = (*stream).buf.offset(count); + *buf.offset(size as isize - 1) = *(*stream).rpos; + (*stream).rpos = (*stream).rpos.add(1); + } + size +} + +pub fn stdio_write(stream: *mut FILE, buf: *const u8, size: usize) -> usize { + unsafe { + let len = (*stream).wpos as usize - (*stream).wbase as usize; + let mut advance = 0; + let mut f_buf = slice::from_raw_parts((*stream).wbase, len); + let mut buff = slice::from_raw_parts(buf, size); + let mut f_filled = false; + let mut rem = f_buf.len() + buff.len(); + let mut file = platform::FileWriter((*stream).fd); + loop { + let mut count = if f_filled { + file.write(&f_buf[advance..]) + } else { + file.write(&f_buf[advance..]) + file.write(buff) + }; + if count == rem as isize { + (*stream).wend = (*stream).buf.add((*stream).buf_size -1); + (*stream).wpos = (*stream).buf; + (*stream).wbase = (*stream).buf; + return size; + } + if count < 0 { + (*stream).wpos = ptr::null_mut(); + (*stream).wbase = ptr::null_mut(); + (*stream).wend = ptr::null_mut(); + (*stream).flags |= constants::F_ERR; + return 0; + } + rem -= count as usize; + if count as usize > len { + count -= len as isize; + f_buf = buff; + f_filled = true; + advance = 0; + } + advance += count as usize; + } + } +} + +pub unsafe fn to_read(stream: *mut FILE) -> bool { + if (*stream).flags & constants::F_BADJ > 0 { + // Static and needs unget region + (*stream).buf = (*stream).buf.add((*stream).unget); + (*stream).flags &= !constants::F_BADJ; + } + + if (*stream).wpos > (*stream).wbase { + if let Some(f) = (*stream).write { + (*f)(stream, ptr::null(), 0); + } + } + (*stream).wpos = ptr::null_mut(); + (*stream).wbase = ptr::null_mut(); + (*stream).wend = ptr::null_mut(); + if (*stream).flags & constants::F_NORD > 0 { + (*stream).flags |= constants::F_ERR; + return true; + } + (*stream).rpos = (*stream).buf.offset((*stream).buf_size as isize -1); + (*stream).rend = (*stream).buf.offset((*stream).buf_size as isize -1); + if (*stream).flags & constants::F_EOF > 0 { + true + } else { + false + } +} + +pub unsafe fn to_write(stream: *mut FILE) -> bool { + if (*stream).flags & constants::F_BADJ > 0 { + // Static and needs unget region + (*stream).buf = (*stream).buf.add((*stream).unget); + (*stream).flags &= !constants::F_BADJ; + } + + if (*stream).flags & constants::F_NOWR > 0 { + (*stream).flags &= constants::F_ERR; + return false; + } + (*stream).rpos = ptr::null_mut(); + (*stream).rend = ptr::null_mut(); + (*stream).wpos = (*stream).buf; + (*stream).wbase = (*stream).buf; + (*stream).wend = (*stream).buf.offset((*stream).buf_size as isize -1); + return true; +} + +pub unsafe fn ftello(stream: *mut FILE) -> off_t { + if let Some(s) = (*stream).seek { + let pos = (*s)( + stream, + 0, + if ((*stream).flags & constants::F_APP > 0) && (*stream).wpos > (*stream).wbase { + constants::SEEK_END + } else { + constants::SEEK_CUR + }, + ); + if pos < 0 { + return pos; + } + pos - ((*stream).rend as i64 - (*stream).rpos as i64) + + ((*stream).wpos as i64 - (*stream).wbase as i64) + } else { + -1 + } +} + +pub fn stdio_seek(stream: *mut FILE, off: off_t, whence: c_int) -> off_t { + unsafe { platform::lseek((*stream).fd, off, whence) } +} diff --git a/src/stdio/src/lib.rs b/src/stdio/src/lib.rs index e99351639b..e77b86eaa8 100644 --- a/src/stdio/src/lib.rs +++ b/src/stdio/src/lib.rs @@ -1,41 +1,64 @@ //! stdio implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/stdio.h.html +#![feature(compiler_builtins_lib)] #![no_std] +extern crate compiler_builtins; extern crate errno; +extern crate fcntl; extern crate platform; +extern crate stdlib; +extern crate string; extern crate va_list as vl; use core::str; +use core::ptr; use core::fmt::Write; +use core::sync::atomic::{AtomicBool, Ordering}; use platform::types::*; -use platform::c_str; -use platform::errno; +use platform::{c_str, errno}; use errno::STR_ERROR; use vl::VaList as va_list; mod printf; -pub const BUFSIZ: c_int = 4096; +mod default; +pub use default::*; -pub const FILENAME_MAX: c_int = 4096; +mod constants; +pub use constants::*; -pub type fpos_t = off_t; +mod helpers; -pub struct FILE; +mod internal; -#[allow(non_upper_case_globals)] +/// The structure of this struct is likely to change. +/// all of the _pos pointers are likely to change to isize offsets and its possible that the +/// function pointers will be removed +#[repr(C)] +pub struct FILE { + flags: c_int, + rpos: *mut u8, + rend: *mut u8, + wend: *mut u8, + wpos: *mut u8, + wbase: *mut u8, + fd: c_int, + buf: *mut u8, + buf_size: size_t, + buf_char: i8, + lock: AtomicBool, + unget: size_t, + write: Option<*const (Fn(*mut FILE, *const u8, usize) -> size_t)>, + read: Option<*const (Fn(*mut FILE, *mut u8, usize) -> size_t)>, + seek: Option<*const (Fn(*mut FILE, off_t, c_int) -> off_t)>, +} + +/// Clears EOF and ERR indicators on a stream #[no_mangle] -pub static mut stdout: *mut FILE = 1 as *mut FILE; - -#[allow(non_upper_case_globals)] -#[no_mangle] -pub static mut stderr: *mut FILE = 2 as *mut FILE; - -#[no_mangle] -pub extern "C" fn clearerr(stream: *mut FILE) { - unimplemented!(); +pub unsafe extern "C" fn clearerr(stream: *mut FILE) { + (*stream).flags &= !(F_EOF & F_ERR); } #[no_mangle] @@ -48,88 +71,244 @@ pub extern "C" fn cuserid(s: *mut c_char) -> *mut c_char { unimplemented!(); } +/// Close a file +/// This function does not guarentee that the file buffer will be flushed or that the file +/// 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 { - unimplemented!(); +pub unsafe extern "C" fn fclose(stream: *mut FILE) -> c_int { + use stdlib::free; + flockfile(stream); + let r = helpers::fflush_unlocked(stream) | platform::close((*stream).fd); + if (*stream).flags & constants::F_PERM == 0 { + // Not one of stdin, stdout or stderr + free(stream as *mut _); + } + r } +/// Open a file from a file descriptor #[no_mangle] -pub extern "C" fn fdopen(fildes: c_int, mode: *const c_char) -> *mut FILE { - unimplemented!(); +pub unsafe extern "C" fn fdopen(fildes: c_int, mode: *const c_char) -> *mut FILE { + helpers::_fdopen(fildes, mode) } +/// Check for EOF #[no_mangle] -pub extern "C" fn feof(stream: *mut FILE) -> c_int { - unimplemented!(); +pub unsafe extern "C" fn feof(stream: *mut FILE) -> c_int { + flockfile(stream); + let ret = (*stream).flags & F_EOF; + funlockfile(stream); + ret } +/// Check for ERR #[no_mangle] -pub extern "C" fn ferror(stream: *mut FILE) -> c_int { - unimplemented!(); +pub unsafe extern "C" fn ferror(stream: *mut FILE) -> c_int { + flockfile(stream); + let ret = (*stream).flags & F_ERR; + funlockfile(stream); + ret } +/// Flush output to stream, or sync read position +/// Ensure the file is unlocked before calling this function, as it will attempt to lock the file +/// itself. #[no_mangle] -pub extern "C" fn fflush(stream: *mut FILE) -> c_int { - unimplemented!(); +pub unsafe extern "C" fn fflush(stream: *mut FILE) -> c_int { + flockfile(stream); + + let ret = helpers::fflush_unlocked(stream); + + funlockfile(stream); + ret } +/// Get a single char from a stream #[no_mangle] -pub extern "C" fn fgetc(stream: *mut FILE) -> c_int { - unimplemented!(); -} - -#[no_mangle] -pub extern "C" fn fgetpos(stream: *mut FILE, pos: *mut fpos_t) -> c_int { - unimplemented!(); -} - -#[no_mangle] -pub extern "C" fn fgets(s: *mut c_char, n: c_int, stream: *mut FILE) -> *mut c_char { - unimplemented!(); -} - -#[no_mangle] -pub extern "C" fn fileno(stream: *mut FILE) -> c_int { - unimplemented!(); -} - -#[no_mangle] -pub extern "C" fn flockfile(file: *mut FILE) { - unimplemented!(); -} - -#[no_mangle] -pub extern "C" fn fopen(filename: *const c_char, mode: *const c_char) -> *mut FILE { - unimplemented!(); -} - -#[no_mangle] -pub extern "C" fn fputc(c: c_int, stream: *mut FILE) -> c_int { - platform::FileWriter(stream as c_int) - .write_char(c as u8 as char) - .map_err(|_| return -1); +pub unsafe extern "C" fn fgetc(stream: *mut FILE) -> c_int { + flockfile(stream); + let c = getc_unlocked(stream); + funlockfile(stream); c } +/// Get the position of the stream and store it in pos +#[no_mangle] +pub unsafe extern "C" fn fgetpos(stream: *mut FILE, pos: *mut fpos_t) -> c_int { + let off = internal::ftello(stream); + if off < 0 { + return -1; + } + (*pos) = off; + 0 +} + +/// Get a string from the stream +#[no_mangle] +pub unsafe extern "C" fn fgets(s: *mut c_char, n: c_int, stream: *mut FILE) -> *mut c_char { + use string::memchr; + use compiler_builtins::mem::memcpy; + flockfile(stream); + let mut ptr = s as *mut u8; + let mut n = n; + if n <= 1 { + funlockfile(stream); + if n == 0 { + return ptr::null_mut(); + } + (*s) = b'\0' as i8; + return s; + } + while n > 0 { + let z = memchr( + (*stream).rpos as *const c_void, + b'\n' as c_int, + (*stream).rend as usize - (*stream).rpos as usize, + ) as *mut u8; + let k = if z.is_null() { + (*stream).rend as usize - (*stream).rpos as usize + } else { + z as usize - (*stream).rpos as usize + 1 + }; + let k = if k as i32 > n { n as usize } else { k }; + memcpy(ptr, (*stream).rpos, k); + (*stream).rpos = (*stream).rpos.add(k); + ptr = ptr.add(k); + n -= k as i32; + if !z.is_null() || n < 1 { + break; + } + let c = getc_unlocked(stream); + if c < 0 { + break; + } + n -= 1; + *ptr = c as u8; + ptr = ptr.add(1); + if c as u8 == b'\n' { + break; + } + } + if !s.is_null() { + *ptr = 0; + } + funlockfile(stream); + s +} + +/// Get the underlying file descriptor +#[no_mangle] +pub unsafe extern "C" fn fileno(stream: *mut FILE) -> c_int { + flockfile(stream); + funlockfile(stream); + (*stream).fd +} + +/// Lock the file +/// Do not call any functions other than those with the `_unlocked` postfix while the file is +/// locked +#[no_mangle] +pub unsafe extern "C" fn flockfile(file: *mut FILE) { + while ftrylockfile(file) != 0 {} +} + +/// Open the file in mode `mode` +#[no_mangle] +pub unsafe extern "C" fn fopen(filename: *const c_char, mode: *const c_char) -> *mut FILE { + use core::ptr; + let initial_mode = *mode; + if initial_mode != b'r' as i8 && initial_mode != b'w' as i8 && initial_mode != b'a' as i8 { + platform::errno = errno::EINVAL; + return ptr::null_mut(); + } + + let flags = helpers::parse_mode_flags(mode); + + let fd = fcntl::sys_open(filename, flags, 0o666); + if fd < 0 { + return ptr::null_mut(); + } + + if flags & fcntl::O_CLOEXEC > 0 { + fcntl::sys_fcntl(fd, fcntl::F_SETFD, fcntl::FD_CLOEXEC); + } + + let f = helpers::_fdopen(fd, mode); + if f.is_null() { + platform::close(fd); + return ptr::null_mut(); + } + f +} + +/// Insert a character into the stream +#[no_mangle] +pub unsafe extern "C" fn fputc(c: c_int, stream: *mut FILE) -> c_int { + flockfile(stream); + let c = putc_unlocked(c, stream); + funlockfile(stream); + c +} + +/// Insert a string into a stream #[no_mangle] pub unsafe extern "C" fn fputs(s: *const c_char, stream: *mut FILE) -> c_int { extern "C" { fn strlen(s: *const c_char) -> size_t; } - use core::{slice, str}; let len = strlen(s); - platform::FileWriter(stream as c_int) - .write_str(str::from_utf8_unchecked(slice::from_raw_parts( - s as *const u8, - len, - ))) - .map_err(|_| return -1); - len as i32 + (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 { - unimplemented!(); +pub unsafe extern "C" fn fread( + ptr: *mut c_void, + size: usize, + nitems: usize, + stream: *mut FILE, +) -> usize { + use compiler_builtins::mem::memcpy; + let mut dest = ptr as *mut u8; + let len = size * nitems; + let mut l = len as isize; + + flockfile(stream); + + if (*stream).rend > (*stream).rpos { + // We have some buffered data that can be read + let diff = (*stream).rend as usize - (*stream).rpos as usize; + let k = if diff < l as usize { diff } else { l as usize }; + memcpy(dest, (*stream).rpos, k); + (*stream).rpos = (*stream).rpos.add(k); + dest = dest.add(k); + l -= k as isize; + } + + while l > 0 { + let k = if internal::to_read(stream) { + 0 + } else { + if let Some(f) = (*stream).read { + (*f)(stream, dest, l as usize) + } + else { + 0 + } + }; + + if k == 0 { + funlockfile(stream); + return (len - l as usize) / 2; + } + + l -= k as isize; + dest = dest.add(k); + } + + funlockfile(stream); + nitems } #[no_mangle] @@ -141,79 +320,169 @@ pub extern "C" fn freopen( unimplemented!(); } +/// 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 { - unimplemented!(); +pub unsafe 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 } +/// 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 { - unimplemented!(); +pub unsafe extern "C" fn fseeko(stream: *mut FILE, offset: off_t, whence: c_int) -> c_int { + let mut off = offset; + flockfile(stream); + // Adjust for what is currently in the buffer + if whence == SEEK_CUR { + off -= ((*stream).rend as usize - (*stream).rpos as usize) as i64; + } + if (*stream).wpos > (*stream).wbase { + if let Some(f) = (*stream).write { + (*f)(stream, ptr::null(), 0); + if (*stream).wpos.is_null() { + return -1; + } + } + } + (*stream).wpos = ptr::null_mut(); + (*stream).wend = ptr::null_mut(); + (*stream).wbase = ptr::null_mut(); + if let Some(s) = (*stream).seek { + if (*s)(stream, off, whence) < 0 { + return -1; + } + } else { + return -1; + } + (*stream).rpos = ptr::null_mut(); + (*stream).rend = ptr::null_mut(); + (*stream).flags &= !F_EOF; + funlockfile(stream); + 0 } +/// Seek to a position `pos` in the file from the beginning of the file #[no_mangle] -pub extern "C" fn fsetpos(stream: *mut FILE, pos: *const fpos_t) -> c_int { - unimplemented!(); +pub unsafe extern "C" fn fsetpos(stream: *mut FILE, pos: *const fpos_t) -> c_int { + fseek(stream, *pos as off_t, SEEK_SET) } +/// Get the current position of the cursor in the file #[no_mangle] -pub extern "C" fn ftell(stream: *mut FILE) -> c_long { - unimplemented!(); +pub unsafe 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 { - unimplemented!(); +pub unsafe extern "C" fn ftello(stream: *mut FILE) -> off_t { + flockfile(stream); + let pos = internal::ftello(stream); + funlockfile(stream); + pos } +/// Try to lock the file. Returns 0 for success, 1 for failure #[no_mangle] -pub extern "C" fn ftrylockfile(file: *mut FILE) -> c_int { - unimplemented!(); +pub unsafe extern "C" fn ftrylockfile(file: *mut FILE) -> c_int { + (*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) { - unimplemented!(); +pub unsafe extern "C" fn funlockfile(file: *mut FILE) { + (*file).lock.store(false, Ordering::Release); } +/// Write `nitems` of size `size` from `ptr` to `stream` #[no_mangle] -pub extern "C" fn fwrite( +pub unsafe extern "C" fn fwrite( ptr: *const c_void, size: usize, nitems: usize, stream: *mut FILE, ) -> usize { - unimplemented!(); + let l = size * nitems; + let nitems = if size == 0 { 0 } else { nitems }; + flockfile(stream); + let k = helpers::fwritex(ptr as *const u8, l, stream); + funlockfile(stream); + if k == l { + nitems + } else { + k / size + } } +/// Get a single char from a stream #[no_mangle] -pub extern "C" fn getc(stream: *mut FILE) -> c_int { - unimplemented!(); +pub unsafe extern "C" fn getc(stream: *mut FILE) -> c_int { + flockfile(stream); + let c = getc_unlocked(stream); + funlockfile(stream); + c } +/// Get a single char from `stdin` #[no_mangle] -pub extern "C" fn getchar() -> c_int { - unimplemented!(); +pub unsafe extern "C" fn getchar() -> c_int { + fgetc(stdin) } +/// Get a char from a stream without locking the stream #[no_mangle] -pub extern "C" fn getc_unlocked(stream: *mut FILE) -> c_int { - unimplemented!(); +pub unsafe extern "C" fn getc_unlocked(stream: *mut FILE) -> c_int { + if (*stream).rpos < (*stream).rend { + let ret = *(*stream).rpos as c_int; + (*stream).rpos = (*stream).rpos.add(1); + ret + } else { + if let Some(read) = (*stream).read { + let mut c = 0u8; + if !internal::to_read(stream) && (*read)(stream, &mut c, 1) == 1 { + c as c_int + } else { + -1 + } + } else { + -1 + } + } } +/// Get a char from `stdin` without locking `stdin` #[no_mangle] -pub extern "C" fn getchar_unlocked() -> c_int { - unimplemented!(); +pub unsafe extern "C" fn getchar_unlocked() -> c_int { + getc_unlocked(stdin) } +/// Get a string from `stdin` #[no_mangle] -pub extern "C" fn gets(s: *mut c_char) -> *mut c_char { - unimplemented!(); +pub unsafe extern "C" fn gets(s: *mut c_char) -> *mut c_char { + use core::i32; + fgets(s, i32::MAX, stdin) } +/// Get an integer from `stream` #[no_mangle] -pub extern "C" fn getw(stream: *mut FILE) -> c_int { - unimplemented!(); +pub unsafe 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, + mem::size_of_val(&ret), + 1, + stream, + ) > 0 + { + ret + } else { + -1 + } } #[no_mangle] @@ -238,60 +507,134 @@ pub extern "C" fn popen(command: *const c_char, mode: *const c_char) -> *mut FIL unimplemented!(); } +/// Put a character `c` into `stream` #[no_mangle] -pub extern "C" fn putc(c: c_int, stream: *mut FILE) -> c_int { - fputc(c, stream) +pub unsafe extern "C" fn putc(c: c_int, stream: *mut FILE) -> c_int { + flockfile(stream); + let ret = putc_unlocked(c, stream); + funlockfile(stream); + ret } +/// Put a character `c` into `stdout` #[no_mangle] pub unsafe extern "C" fn putchar(c: c_int) -> c_int { - putc(c, stdout) + fputc(c, stdout) } +/// 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 { - unimplemented!(); +pub unsafe extern "C" fn putc_unlocked(c: c_int, stream: *mut FILE) -> c_int { + if c as i8 != (*stream).buf_char && (*stream).wpos < (*stream).wend { + *(*stream).wpos = c as u8; + (*stream).wpos = (*stream).wpos.add(1); + c + } else { + if let Some(write) = (*stream).write { + if (*stream).wend.is_null() && internal::to_write(stream) { + -1 + } else if c as i8 != (*stream).buf_char && (*stream).wpos < (*stream).wend { + *(*stream).wpos = c as u8; + (*stream).wpos = (*stream).wpos.add(1); + c + } else if (*write)(stream, &c as *const i32 as *const _, 1) != 1 { + -1 + } else { + c + } + } else { + -1 + } + } } +/// Put a character `c` into `stdout` without locking `stdout` #[no_mangle] pub unsafe extern "C" fn putchar_unlocked(c: c_int) -> c_int { putc_unlocked(c, stdout) } +/// Put a string `s` into `stdout` #[no_mangle] pub unsafe extern "C" fn puts(s: *const c_char) -> c_int { - fputs(s, stdout); - putchar(b'\n' as c_int) + let ret = (fputs(s, stdout) > 0) || (putchar_unlocked(b'\n' as c_int) > 0); + if ret { + 0 + } else { + -1 + } } +/// Put an integer `w` into `stream` #[no_mangle] -pub extern "C" fn putw(w: c_int, stream: *mut FILE) -> c_int { - unimplemented!(); +pub unsafe 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 } +/// Delete file or directory `path` #[no_mangle] pub extern "C" fn remove(path: *const c_char) -> c_int { - unimplemented!(); + let r = platform::unlink(path); + if r == -errno::EISDIR { + platform::rmdir(path) + } else { + r + } } #[no_mangle] pub extern "C" fn rename(old: *const c_char, new: *const c_char) -> c_int { + // This function requires a rename syscall, which currently is not in platform. unimplemented!(); } +/// Rewind `stream` back to the beginning of it #[no_mangle] -pub extern "C" fn rewind(stream: *mut FILE) { - unimplemented!(); +pub unsafe extern "C" fn rewind(stream: *mut FILE) { + fseeko(stream, 0, SEEK_SET); + flockfile(stream); + (*stream).flags &= !F_ERR; + funlockfile(stream); } +/// 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) { - unimplemented!(); + unsafe { + setvbuf( + stream, + buf, + if buf.is_null() { _IONBF } else { _IOFBF }, + BUFSIZ as usize, + ) + }; } +/// Reset `stream` to use buffer `buf` of size `size` #[no_mangle] -pub extern "C" fn setvbuf(stream: *mut FILE, buf: *mut c_char, mode: c_int, size: usize) -> c_int { - unimplemented!(); +pub unsafe extern "C" fn setvbuf( + stream: *mut FILE, + buf: *mut c_char, + mode: c_int, + size: usize, +) -> c_int { + // TODO: Check correctness + use stdlib::calloc; + let mut buf = buf; + if buf.is_null() && mode != _IONBF { + buf = calloc(size, 1) as *mut c_char; + } + (*stream).buf_size = size; + (*stream).buf_char = -1; + if mode == _IONBF { + (*stream).buf_size = 0; + } else if mode == _IOLBF { + (*stream).buf_char = b'\n' as i8; + } + (*stream).flags |= F_SVB; + (*stream).buf = buf as *mut u8; + 0 } #[no_mangle] @@ -309,14 +652,35 @@ pub extern "C" fn tmpnam(s: *mut c_char) -> *mut c_char { unimplemented!(); } +/// 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 { - unimplemented!(); +pub unsafe extern "C" fn ungetc(c: c_int, stream: *mut FILE) -> c_int { + if c < 0 { + c + } else { + flockfile(stream); + if (*stream).rpos.is_null() { + internal::to_read(stream); + } + if (*stream).rpos.is_null() + || (*stream).rpos <= (*stream).buf.sub((*stream).unget) + { + funlockfile(stream); + return -1; + } + + (*stream).rpos = (*stream).rpos.sub(1); + *(*stream).rpos = c as u8; + (*stream).flags &= !F_EOF; + + funlockfile(stream); + c + } } #[no_mangle] pub unsafe extern "C" fn vfprintf(file: *mut FILE, format: *const c_char, ap: va_list) -> c_int { - printf::printf(platform::FileWriter(file as c_int), format, ap) + printf::printf(platform::FileWriter((*file).fd), format, ap) } #[no_mangle]