diff --git a/src/fcntl/src/linux.rs b/src/fcntl/src/linux.rs index fd68ffcf14c01e6adfcfaf4f34bb9412e65bd7d2..ce9499aab713cf36142986d781e8d9ef4b4cc076 100644 --- a/src/fcntl/src/linux.rs +++ b/src/fcntl/src/linux.rs @@ -6,3 +6,6 @@ pub const O_RDWR: c_int = 0x0002; pub const O_CREAT: c_int = 0x0040; pub const O_TRUNC: c_int = 0x0200; pub const O_ACCMODE: c_int = O_RDONLY | O_WRONLY | O_RDWR; +pub const O_APPEND: c_int = 0o2000; +pub const O_CLOEXEC: c_int = 0o2_000_000; +pub const O_EXCL: c_int = 0o200; diff --git a/src/platform/src/lib.rs b/src/platform/src/lib.rs index 5f57298db7e1faacb1908029f3449b491e7d674e..a9b6d25c46ee2c5d70584908eb9bb8e9942de4c4 100644 --- a/src/platform/src/lib.rs +++ b/src/platform/src/lib.rs @@ -57,8 +57,8 @@ pub unsafe fn c_str_n(s: *const c_char, n: usize) -> &'static [u8] { pub struct FileWriter(pub c_int); impl FileWriter { - pub fn write(&mut self, buf: &[u8]) { - write(self.0, buf); + pub fn write(&mut self, buf: &[u8]) -> isize { + write(self.0, buf) } } @@ -69,6 +69,14 @@ impl fmt::Write for FileWriter { } } +pub struct FileReader(pub c_int); + +impl FileReader { + pub fn read(&mut self, buf: &mut [u8]) -> isize { + read(self.0, buf) + } +} + pub struct StringWriter(pub *mut u8, pub usize); impl StringWriter { diff --git a/src/platform/src/linux/mod.rs b/src/platform/src/linux/mod.rs index f26076e6c14157a6f70b6278513c1f241730c17c..44d7ed2cfb8dcecfe9d5f972e53409aa3f9a9799 100644 --- a/src/platform/src/linux/mod.rs +++ b/src/platform/src/linux/mod.rs @@ -119,6 +119,10 @@ pub fn link(path1: *const c_char, path2: *const c_char) -> c_int { e(unsafe { syscall!(LINKAT, AT_FDCWD, path1, AT_FDCWD, path2, 0) }) as c_int } +pub fn lseek(fildes: c_int, offset: off_t, whence: c_int) -> off_t { + e(unsafe { syscall!(LSEEK, fildes, offset, whence) }) as off_t +} + pub fn mkdir(path: *const c_char, mode: mode_t) -> c_int { e(unsafe { syscall!(MKDIRAT, AT_FDCWD, path, mode) }) as c_int } diff --git a/src/platform/src/redox/mod.rs b/src/platform/src/redox/mod.rs index f125166fa920eeb68a6696a13d6298f765519c96..3e03bbde9005b19ce4cec9c9bc2835b0975ba24e 100644 --- a/src/platform/src/redox/mod.rs +++ b/src/platform/src/redox/mod.rs @@ -125,6 +125,14 @@ pub fn link(path1: *const c_char, path2: *const c_char) -> c_int { e(unsafe { syscall::link(path1.as_ptr(), path2.as_ptr()) }) as c_int } +pub fn lseek(fd: c_int, offset: off_t, whence: c_int) -> off_t { + e(syscall::lseek( + fd as usize, + offset as isize, + whence as usize, + )) as off_t +} + pub fn mkdir(path: *const c_char, mode: mode_t) -> c_int { let flags = O_CREAT | O_EXCL | O_CLOEXEC | O_DIRECTORY | mode as usize & 0o777; let path = unsafe { c_str(path) }; diff --git a/src/stdio/Cargo.toml b/src/stdio/Cargo.toml index 2f53e58dc70578c5c48c0a96c5bb6fb83b1c62ca..1a920812a6a730a62b6a53efa6b8ca42faa0ed44 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/cbindgen.toml b/src/stdio/cbindgen.toml index 58483d52ac51ae265ff8f156bb26a8800dc8250e..3f13ef55cb66b092e669e76578cce789ee7899ab 100644 --- a/src/stdio/cbindgen.toml +++ b/src/stdio/cbindgen.toml @@ -5,3 +5,6 @@ language = "C" [enum] prefix_with_name = true + +[export.rename] +"AtomicBool" = "volatile char" diff --git a/src/stdio/src/constants.rs b/src/stdio/src/constants.rs new file mode 100644 index 0000000000000000000000000000000000000000..c601cbc85e3d07206b6dfdaa0314dfe870b58771 --- /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 0000000000000000000000000000000000000000..840a6fa07ff1ef76d945ff7997403e6b153d1f40 --- /dev/null +++ b/src/stdio/src/default.rs @@ -0,0 +1,76 @@ +use core::sync::atomic::AtomicBool; +use core::ptr; +use super::{constants, internal, BUFSIZ, FILE, UNGET}; + +#[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), +}; + +#[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), +}; + +#[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), +}; + +// 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 0000000000000000000000000000000000000000..378d2af3b6cbdbc4d45b430b318a0abdcb7f59ca --- /dev/null +++ b/src/stdio/src/helpers.rs @@ -0,0 +1,147 @@ +use super::{internal, BUFSIZ, FILE, UNGET}; +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::<FILE>() + 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::<FILE>() + UNGET), + buf_size: BUFSIZ, + buf_char: -1, + unget: UNGET, + lock: AtomicBool::new(false), + }; + file +} + +/// 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.wend.is_null() && !stream.can_write() { + // 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(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); + if n < i { + return n; + } + advance += i; + l -= i; + } + i + } else { + 0 + }; + + unsafe { + // Copy and reposition + copy_nonoverlapping(&buf[advance..] as *const _ as *const u8, stream.wpos, l); + stream.wpos = stream.wpos.add(l); + } + l + i +} + +/// Flush `stream` without locking it. +pub fn fflush_unlocked(stream: &mut FILE) -> c_int { + if stream.wpos > stream.wbase { + stream.write(&[]); + if stream.wpos.is_null() { + return -1; + } + } + + if stream.rpos < stream.rend { + stream.seek(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 0000000000000000000000000000000000000000..35eb5f47e8896126ae57e64a41d383842659d189 --- /dev/null +++ b/src/stdio/src/internal.rs @@ -0,0 +1,19 @@ +use super::{constants, FILE}; +use platform; +use platform::types::*; +use core::{mem, ptr, slice}; + +pub fn ftello(stream: &mut FILE) -> off_t { + let pos = stream.seek( + 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) +} diff --git a/src/stdio/src/lib.rs b/src/stdio/src/lib.rs index e99351639b22ed0de4e5c8d09eacbb288d8bea53..98e3a3913f180b08bf6671e1c095d133f7c8b7d3 100644 --- a/src/stdio/src/lib.rs +++ b/src/stdio/src/lib.rs @@ -1,41 +1,186 @@ //! 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::fmt::Write; +use core::ptr; +use core::fmt::{Error, Result, 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; - -pub const FILENAME_MAX: c_int = 4096; - -pub type fpos_t = off_t; - -pub struct FILE; - -#[allow(non_upper_case_globals)] -#[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; +mod default; +pub use default::*; + +mod constants; +pub use constants::*; + +mod helpers; + +mod internal; + +#[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, +} + +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 self.wpos > self.wbase { + self.write(&[]); + } + self.wpos = ptr::null_mut(); + self.wbase = ptr::null_mut(); + self.wend = ptr::null_mut(); + if self.flags & constants::F_NORD > 0 { + self.flags |= constants::F_ERR; + return false; + } + self.rpos = unsafe { self.buf.offset(self.buf_size as isize - 1) }; + self.rend = unsafe { self.buf.offset(self.buf_size as isize - 1) }; + if self.flags & constants::F_EOF > 0 { + false + } else { + true + } + } + 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; + } + + if self.flags & constants::F_NOWR > 0 { + self.flags &= constants::F_ERR; + return false; + } + // Buffer repositioning + self.rpos = ptr::null_mut(); + self.rend = ptr::null_mut(); + self.wpos = self.buf; + self.wbase = self.buf; + self.wend = unsafe { self.buf.offset(self.buf_size as isize - 1) }; + return true; + } + pub fn write(&mut self, to_write: &[u8]) -> usize { + use core::slice; + use core::mem; + let len = self.wpos as usize - self.wbase as usize; + let mut advance = 0; + let mut f_buf: &'static _ = unsafe { slice::from_raw_parts(self.wbase, len) }; + let mut f_filled = false; + let mut rem = f_buf.len() + to_write.len(); + loop { + let mut count = if f_filled { + platform::write(self.fd, &f_buf[advance..]) + } else { + platform::write(self.fd, &f_buf[advance..]) + platform::write(self.fd, to_write) + }; + if count == rem as isize { + self.wend = unsafe { self.buf.add(self.buf_size - 1) }; + self.wpos = self.buf; + self.wbase = self.buf; + return to_write.len(); + } + if count < 0 { + self.wpos = ptr::null_mut(); + self.wbase = ptr::null_mut(); + self.wend = ptr::null_mut(); + self.flags |= constants::F_ERR; + return 0; + } + rem -= count as usize; + if count as usize > len { + count -= len as isize; + f_buf = unsafe { mem::transmute(to_write) }; + f_filled = true; + advance = 0; + } + advance += count as usize; + } + } + pub fn read(&mut self, buf: &mut [u8]) -> usize { + use core::slice; + // let buff = slice::from_raw_parts_mut(buf, size - !((*stream).buf_size == 0) as usize); + let adj = !(self.buf_size == 0) as usize; + let mut file_buf: &'static mut _ = + unsafe { slice::from_raw_parts_mut(self.buf, self.buf_size) }; + let count = if buf.len() <= 1 + adj { + platform::read(self.fd, file_buf) + } else { + platform::read(self.fd, buf) + platform::read(self.fd, 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; + } + unsafe { + // Adjust pointers + self.rpos = self.buf; + self.rend = self.buf.offset(count); + buf[buf.len() - 1] = *self.rpos; + self.rpos = self.rpos.add(1); + } + buf.len() + } + pub fn seek(&self, off: off_t, whence: c_int) -> off_t { + unsafe { platform::lseek(self.fd, off, whence) } + } +} +impl Write for FILE { + fn write_str(&mut self, s: &str) -> Result { + let s = s.as_bytes(); + if self.write(s) != s.len() { + Err(Error) + } else { + Ok(()) + } + } +} +/// Clears EOF and ERR indicators on a stream #[no_mangle] -pub extern "C" fn clearerr(stream: *mut FILE) { - unimplemented!(); +pub extern "C" fn clearerr(stream: &mut FILE) { + stream.flags &= !(F_EOF | F_ERR); } #[no_mangle] @@ -48,176 +193,468 @@ 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 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 + unsafe { + free(stream as *mut _ 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!(); + unsafe { helpers::_fdopen(fildes, mode) } } +/// Check for EOF #[no_mangle] -pub extern "C" fn feof(stream: *mut FILE) -> c_int { - unimplemented!(); +pub 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 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!(); +pub 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 extern "C" fn fgetpos(stream: *mut FILE, pos: *mut fpos_t) -> c_int { - unimplemented!(); +pub extern "C" fn fgetpos(stream: &mut FILE, pos: *mut fpos_t) -> c_int { + let off = internal::ftello(stream); + if off < 0 { + return -1; + } + unsafe { + (*pos) = off; + } + 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 { - unimplemented!(); +pub extern "C" fn fgets(s: *mut c_char, n: c_int, stream: &mut FILE) -> *mut c_char { + use string::memchr; + use core::ptr::copy_nonoverlapping; + + 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(); + } + unsafe { + (*s) = b'\0' as i8; + } + return s; + } + while n > 0 { + let z = unsafe { + 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 }; + unsafe { + // Copy + copy_nonoverlapping(stream.rpos, ptr, k); + // Reposition pointers + 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; + + unsafe { + // Pointer stuff + *ptr = c as u8; + ptr = ptr.add(1); + } + + if c as u8 == b'\n' { + break; + } + } + if !s.is_null() { + unsafe { + *ptr = 0; + } + } + funlockfile(stream); + s } +/// Get the underlying file descriptor #[no_mangle] -pub extern "C" fn fileno(stream: *mut FILE) -> c_int { - unimplemented!(); +pub 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 extern "C" fn flockfile(file: *mut FILE) { - unimplemented!(); +pub extern "C" fn flockfile(file: &mut FILE) { + while ftrylockfile(file) != 0 {} } +/// Open the file in mode `mode` #[no_mangle] -pub extern "C" fn fopen(filename: *const c_char, mode: *const c_char) -> *mut FILE { - unimplemented!(); +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 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 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 { +pub 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 -} + 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; + + 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 }; + unsafe { + // Copy data + copy_nonoverlapping(stream.rpos, dest, k); + // Reposition pointers + stream.rpos = stream.rpos.add(k); + dest = dest.add(k); + } + l -= k as isize; + } -#[no_mangle] -pub extern "C" fn fread(ptr: *mut c_void, size: usize, nitems: usize, stream: *mut FILE) -> usize { - unimplemented!(); + 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 { + funlockfile(stream); + return (len - l as usize) / 2; + } + + l -= k as isize; + unsafe { + // Reposition + dest = dest.add(k); + } + } + + funlockfile(stream); + nitems } #[no_mangle] pub extern "C" fn freopen( filename: *const c_char, mode: *const c_char, - stream: *mut FILE, + stream: &mut FILE, ) -> *mut FILE { - unimplemented!(); + let mut flags = unsafe { helpers::parse_mode_flags(mode) }; + flockfile(stream); + + helpers::fflush_unlocked(stream); + 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); + } + flags &= !(fcntl::O_CREAT | fcntl::O_EXCL | fcntl::O_CLOEXEC); + if fcntl::sys_fcntl(stream.fd, fcntl::F_SETFL, flags) < 0 { + funlockfile(stream); + fclose(stream); + return ptr::null_mut(); + } + } else { + let new = unsafe { fopen(filename, mode) }; + if new.is_null() { + funlockfile(stream); + fclose(stream); + 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 platform::dup2(new.fd, stream.fd) < 0 + || fcntl::sys_fcntl(stream.fd, fcntl::F_SETFL, flags & fcntl::O_CLOEXEC) < 0 + { + fclose(new); + funlockfile(stream); + fclose(stream); + return ptr::null_mut(); + } + stream.flags = (stream.flags & constants::F_PERM) | new.flags; + fclose(new); + } + funlockfile(stream); + stream } +/// 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 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 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 { + stream.write(&[]); + if stream.wpos.is_null() { + return -1; + } + } + stream.wpos = ptr::null_mut(); + stream.wend = ptr::null_mut(); + stream.wbase = ptr::null_mut(); + if stream.seek(off, whence) < 0 { + 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 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 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 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( ptr: *const c_void, size: usize, nitems: usize, - stream: *mut FILE, + 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 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(&mut *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 extern "C" fn getc_unlocked(stream: &mut FILE) -> c_int { + if stream.rpos < stream.rend { + unsafe { + let ret = *stream.rpos as c_int; + stream.rpos = stream.rpos.add(1); + ret + } + } else { + let mut c = [0u8; 1]; + if stream.can_read() && stream.read(&mut c) == 1 { + c[0] as c_int + } 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(&mut *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, &mut *stdin) } +/// Get an integer from `stream` #[no_mangle] -pub extern "C" fn getw(stream: *mut FILE) -> c_int { - unimplemented!(); +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, + mem::size_of_val(&ret), + 1, + stream, + ) > 0 + { + ret + } else { + -1 + } } #[no_mangle] -pub extern "C" fn pclose(stream: *mut FILE) -> c_int { +pub extern "C" fn pclose(stream: &mut FILE) -> c_int { unimplemented!(); } @@ -238,60 +675,135 @@ 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 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, &mut *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 extern "C" fn putc_unlocked(c: c_int, stream: &mut FILE) -> c_int { + if c as i8 != stream.buf_char && stream.wpos < stream.wend { + unsafe { + *stream.wpos = c as u8; + stream.wpos = stream.wpos.add(1); + c + } + } else { + if stream.wend.is_null() && stream.can_write() { + -1 + } else if c as i8 != stream.buf_char && stream.wpos < stream.wend { + unsafe { + *stream.wpos = c as u8; + stream.wpos = stream.wpos.add(1); + c + } + } else if stream.write(&[c as u8]) != 1 { + -1 + } else { + c + } + } } +/// 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) + putc_unlocked(c, &mut *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, &mut *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 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 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!(); +pub extern "C" fn setbuf(stream: &mut FILE, buf: *mut c_char) { + unsafe { + setvbuf( + stream, + buf, + if buf.is_null() { _IONBF } else { _IOFBF }, + BUFSIZ as usize, + ) + }; } +/// 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 { - 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,19 +821,40 @@ 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 extern "C" fn ungetc(c: c_int, stream: &mut FILE) -> c_int { + if c < 0 { + c + } else { + flockfile(stream); + if stream.rpos.is_null() { + stream.can_read(); + } + if stream.rpos.is_null() || stream.rpos <= unsafe { stream.buf.sub(stream.unget) } { + funlockfile(stream); + return -1; + } + + unsafe { + 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) +pub unsafe extern "C" fn vfprintf(file: &mut FILE, format: *const c_char, ap: va_list) -> c_int { + printf::printf(file, format, ap) } #[no_mangle] pub unsafe extern "C" fn vprintf(format: *const c_char, ap: va_list) -> c_int { - vfprintf(stdout, format, ap) + vfprintf(&mut *stdout, format, ap) } #[no_mangle] @@ -331,12 +864,16 @@ pub unsafe extern "C" fn vsnprintf( format: *const c_char, ap: va_list, ) -> c_int { - printf::printf(platform::StringWriter(s as *mut u8, n as usize), format, ap) + printf::printf( + &mut platform::StringWriter(s as *mut u8, n as usize), + format, + ap, + ) } #[no_mangle] pub unsafe extern "C" fn vsprintf(s: *mut c_char, format: *const c_char, ap: va_list) -> c_int { - printf::printf(platform::UnsafeStringWriter(s as *mut u8), format, ap) + printf::printf(&mut platform::UnsafeStringWriter(s as *mut u8), format, ap) } /* diff --git a/src/stdio/src/printf.rs b/src/stdio/src/printf.rs index 093ee82c122d6f902e00d8f9c05f2653dcdbc432..fee1c1156d60af5d615a08f828dbf22253af1c63 100644 --- a/src/stdio/src/printf.rs +++ b/src/stdio/src/printf.rs @@ -1,4 +1,4 @@ -use core::{fmt, mem, slice, str}; +use core::{fmt, slice, str}; use platform::types::*; use vl::VaList; diff --git a/src/unistd/src/getopt.rs b/src/unistd/src/getopt.rs index 2790529a359767db20d5199335e9aa5db7249d4e..c93a5487b94d3be9ca9e0522811ab028a6b316fb 100644 --- a/src/unistd/src/getopt.rs +++ b/src/unistd/src/getopt.rs @@ -69,10 +69,10 @@ unsafe fn parse_arg( let print_error = |desc: &[u8]| { // NOTE: we don't use fprintf to get around the usage of va_list - stdio::fputs(*argv as _, stdio::stderr); - stdio::fputs(desc.as_ptr() as _, stdio::stderr); - stdio::fputc(*current_arg as _, stdio::stderr); - stdio::fputc(b'\n' as _, stdio::stderr); + stdio::fputs(*argv as _, &mut *stdio::stderr); + stdio::fputs(desc.as_ptr() as _, &mut *stdio::stderr); + stdio::fputc(*current_arg as _, &mut *stdio::stderr); + stdio::fputc(b'\n' as _, &mut *stdio::stderr); }; match find_option(*current_arg, optstring) { diff --git a/tests/.gitignore b/tests/.gitignore index 28ee3728268142cc236e2e28c53481e466481a5b..433d1e3fd62605107422820c97b35c3c6a9f4803 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -15,6 +15,7 @@ /fsync /ftruncate /getid +/getc_unget /link /math /mem @@ -29,6 +30,10 @@ /stdlib/bsearch /stdlib/rand /stdlib/strtol +/stdlib/a64l +/stdio/fwrite +/stdio/all +/stdio/freopen /string/strchr /string/strcspn /string/strncmp diff --git a/tests/Makefile b/tests/Makefile index 513235e8f899ee8a8e06faf171bd6bce13039ec4..e788930ed0991bc915a77887c341ba0f35b8400d 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -12,6 +12,8 @@ EXPECT_BINS=\ fcntl \ fsync \ ftruncate \ + getid \ + getc_unget \ link \ math \ mem \ @@ -20,6 +22,9 @@ EXPECT_BINS=\ rmdir \ sleep \ sprintf \ + stdio/fwrite \ + stdio/all \ + stdio/freopen \ stdlib/bsearch \ stdlib/strtol \ stdlib/a64l \ diff --git a/tests/getc_unget.c b/tests/getc_unget.c new file mode 100644 index 0000000000000000000000000000000000000000..f8008534fb0c4718f961e58c3c4a9f2dd50539a8 --- /dev/null +++ b/tests/getc_unget.c @@ -0,0 +1,12 @@ +#include <stdio.h> + +int main(int argc, char ** argv) { + ungetc('h', stdin); + char c; + if ((c = getchar()) == 'h') { + printf("Worked!\n"); + return 0; + } + printf("failed :( %c\n", c); + return 0; +} diff --git a/tests/stdio/all.c b/tests/stdio/all.c new file mode 100644 index 0000000000000000000000000000000000000000..450829364ef1384a6636be504b8c89a55fbc7709 --- /dev/null +++ b/tests/stdio/all.c @@ -0,0 +1,11 @@ +#include <stdio.h> +#include <stdlib.h> + +int main(int argc, char ** argv) { + FILE *f = fopen("stdio/stdio.in", "r"); + printf("%c\n", fgetc(f)); + ungetc('H', f); + char *in = malloc(30); + printf("%s\n", fgets(in, 30, f)); + return 0; +} diff --git a/tests/stdio/freopen.c b/tests/stdio/freopen.c new file mode 100644 index 0000000000000000000000000000000000000000..7bf3b64f20c3fcdfe351b9465c60eae7692d9a9a --- /dev/null +++ b/tests/stdio/freopen.c @@ -0,0 +1,9 @@ +#include <stdio.h> + +int main(int argc, char ** argv) { + freopen("stdio/stdio.in", "r", stdin); + char in[6]; + fgets(in, 6, stdin); + printf("%s\n", in); // should print Hello + return 0; +} diff --git a/tests/stdio/fwrite.c b/tests/stdio/fwrite.c new file mode 100644 index 0000000000000000000000000000000000000000..da172d5b58d643ecdb8cdfe22d0151dff848375d --- /dev/null +++ b/tests/stdio/fwrite.c @@ -0,0 +1,11 @@ +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> + +int main(int argc, char ** argv) { + FILE *f = fopen("stdio/fwrite.out", "w"); + char *in = "Hello World!"; + fputs(in, f); // calls fwrite, helpers::fwritex, internal::to_write and internal::stdio_write + fclose(f); + return 0; +} diff --git a/tests/stdio/fwrite.out b/tests/stdio/fwrite.out new file mode 100644 index 0000000000000000000000000000000000000000..c57eff55ebc0c54973903af5f72bac72762cf4f4 --- /dev/null +++ b/tests/stdio/fwrite.out @@ -0,0 +1 @@ +Hello World! \ No newline at end of file diff --git a/tests/stdio/stdio.in b/tests/stdio/stdio.in new file mode 100644 index 0000000000000000000000000000000000000000..c50d87bb17331bbf91d4b38a48453dc1aee539f1 --- /dev/null +++ b/tests/stdio/stdio.in @@ -0,0 +1,3 @@ +Hello World! + +Line 2