diff --git a/Cargo.toml b/Cargo.toml index f3c801e..f903127 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ crate-type = ["staticlib"] [dependencies] yggdrasil-rt = { git = "https://git.alnyan.me/yggdrasil/yggdrasil-rt.git" } libyalloc = { git = "https://git.alnyan.me/yggdrasil/libyalloc.git" } +bitflags = { version = "2.4.1" } [build-dependencies] -cbindgen = "0.26.0" +cbindgen = { git = "https://git.alnyan.me/yggdrasil/cbindgen.git", branch = "master" } diff --git a/include/bits/stdio.h b/include/bits/stdio.h index 6a939aa..0103f3e 100644 --- a/include/bits/stdio.h +++ b/include/bits/stdio.h @@ -3,5 +3,12 @@ int printf(const char *format, ...); int fprintf(FILE *stream, const char *format, ...); +int sprintf(FILE *stream, const char *format, ...); +int snprintf(FILE *stream, size_t len, const char *format, ...); +int dprintf(int fd, const char *format, ...); + +int scanf(const char *format, ...); +int sscanf(const char *str, const char *format, ...); +int fscanf(FILE *stream, const char *format, ...); #endif diff --git a/src/error.rs b/src/error.rs index b6d90a0..b227d78 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,16 +4,21 @@ use core::{ ops::{ControlFlow, FromResidual, Try}, }; -use crate::header::errno::Errno; +use crate::header::{errno::Errno, stdio::EOF}; pub trait CZeroResult { fn into_zero_status(self) -> c_int; + fn into_eof_status(self) -> c_int; } pub trait CSizeResult { fn into_size_status(self) -> usize; } +pub trait TryFromExt: Sized { + fn e_try_from(value: T) -> EResult; +} + pub trait OptionExt { fn e_ok_or(self, err: yggdrasil_rt::Error) -> EResult; } @@ -60,6 +65,13 @@ impl CZeroResult for Result<(), Errno> { Self::Err(_) => -1, } } + + fn into_eof_status(self) -> c_int { + match self { + Self::Ok(_) => 0, + Self::Err(_) => EOF, + } + } } impl CSizeResult for Result { diff --git a/src/file.rs b/src/file.rs index bda0b46..3ddfdb9 100644 --- a/src/file.rs +++ b/src/file.rs @@ -1,3 +1,139 @@ -pub trait FileImpl {} +use yggdrasil_rt::{ + io::{FileMode, OpenOptions, RawFd, SeekFrom}, + path::Path, + sys as syscall, +}; -pub struct File {} +use crate::{ + error::EResult, + header::errno::Errno, + io::{Read, Seek, Write}, +}; + +pub enum FileBacking { + File(File), +} + +pub struct File { + fd: RawFd, + reference: bool, +} + +impl File { + pub unsafe fn new(fd: RawFd) -> Self { + Self { + fd, + reference: false, + } + } + + pub fn from_raw(fd: RawFd) -> Self { + Self { + fd, + reference: false, + } + } + + pub fn open_at>( + at: Option, + pathname: P, + opts: OpenOptions, + mode: FileMode, + ) -> Result { + let fd = + EResult::from(unsafe { syscall::open(at, pathname.as_ref().as_str(), opts, mode) })?; + Ok(Self { + fd, + reference: false, + }) + } + + pub unsafe fn close(&self) -> Result<(), Errno> { + yggdrasil_rt::debug_trace!("Close fd {:?}", self.fd); + EResult::from(unsafe { syscall::close(self.fd) })?; + Ok(()) + } +} + +impl Write for File { + fn write(&mut self, data: &[u8]) -> Result { + let count = EResult::from(unsafe { syscall::write(self.fd, data) })?; + Ok(count) + } + + fn flush(&mut self) -> Result<(), Errno> { + // TODO fsync + Ok(()) + } +} + +impl Read for File { + fn read(&mut self, data: &mut [u8]) -> Result { + let count = EResult::from(unsafe { syscall::read(self.fd, data) })?; + Ok(count) + } +} + +impl Seek for File { + fn seek(&mut self, off: SeekFrom) -> Result { + let pos = EResult::from(unsafe { syscall::seek(self.fd, off) })?; + Ok(pos) + } +} + +impl Drop for File { + fn drop(&mut self) { + if !self.reference { + unsafe { + self.close().ok(); + } + } + } +} + +impl FileBacking { + pub unsafe fn make_ref(&self) -> Self { + match self { + Self::File(file) => Self::File(File { + fd: file.fd, + reference: true, + }), + } + } + + pub fn as_raw_fd(&self) -> EResult { + match self { + Self::File(file) => EResult::Ok(file.fd), + } + } +} + +impl Write for FileBacking { + fn write(&mut self, data: &[u8]) -> Result { + match self { + Self::File(file) => file.write(data), + } + } + + fn flush(&mut self) -> Result<(), Errno> { + match self { + Self::File(file) => file.flush(), + } + } +} + +impl Read for FileBacking { + fn read(&mut self, data: &mut [u8]) -> Result { + match self { + Self::File(file) => file.read(data), + } + } +} + +impl Seek for FileBacking { + fn seek(&mut self, off: SeekFrom) -> Result { + match self { + Self::File(file) => file.seek(off), + } + } +} diff --git a/src/header/locale/cbindgen.toml b/src/header/locale/cbindgen.toml index 2b3b191..aae38ca 100644 --- a/src/header/locale/cbindgen.toml +++ b/src/header/locale/cbindgen.toml @@ -7,7 +7,8 @@ no_includes = true include_guard = "_LOCALE_H" trailer = "#include " -usize_is_size_t = true +usize_type = "size_t" +isize_type = "ssize_t" [export] include = ["lconv", "locale_t"] diff --git a/src/header/math/cbindgen.toml b/src/header/math/cbindgen.toml index 4e5c28a..d5aa13d 100644 --- a/src/header/math/cbindgen.toml +++ b/src/header/math/cbindgen.toml @@ -7,6 +7,7 @@ no_includes = true include_guard = "_MATH_H" trailer = "#include " -usize_is_size_t = true +usize_type = "size_t" +isize_type = "ssize_t" [export] diff --git a/src/header/mod.rs b/src/header/mod.rs index 583dcf8..6c9bbeb 100644 --- a/src/header/mod.rs +++ b/src/header/mod.rs @@ -1,4 +1,4 @@ -#![allow(nonstandard_style)] +#![allow(nonstandard_style, unused_variables)] pub mod sys_types; pub mod sys_wait; diff --git a/src/header/stdio/cbindgen.toml b/src/header/stdio/cbindgen.toml index 4e51ea4..bc66c65 100644 --- a/src/header/stdio/cbindgen.toml +++ b/src/header/stdio/cbindgen.toml @@ -1,14 +1,25 @@ language = "C" style = "Type" -sys_includes = ["stdarg.h", "stddef.h", "stdint.h"] +sys_includes = ["stdarg.h", "stddef.h", "stdint.h", "sys/types.h"] no_includes = true include_guard = "_STDIO_H" trailer = "#include " -usize_is_size_t = true +usize_type = "size_t" +isize_type = "ssize_t" [export] +include = ["fpos_t"] # Varargs are broken for these -exclude = ["printf", "fprintf"] +exclude = [ + "printf", + "fprintf", + "sprintf", + "snprintf", + "dprintf", + "scanf", + "fscanf", + "sscanf" +] diff --git a/src/header/stdio/file.rs b/src/header/stdio/file.rs index 78599e6..700ac71 100644 --- a/src/header/stdio/file.rs +++ b/src/header/stdio/file.rs @@ -1,15 +1,101 @@ -use core::ffi::{c_char, c_void}; - -use crate::{ - error::CSizeResult, - traits::{Read, Write}, +use core::{ + ffi::{c_char, c_int, c_long, c_void, CStr}, + ptr::null_mut, }; -use super::FILE; +use alloc::boxed::Box; +use yggdrasil_rt::{ + io::{OpenOptions, RawFd, SeekFrom}, + path::Path, +}; + +use crate::{ + error::{CSizeResult, CZeroResult}, + header::{errno::Errno, sys_types::off_t}, + io::{Read, Seek, Write}, +}; + +use super::{ + fpos_t, FileFlags, FileOpenSource, FILE, SEEK_CUR, SEEK_END, SEEK_SET, _IOFBF, _IOLBF, _IONBF, +}; + +fn open_inner(source: O, mode_str: &[u8]) -> Result<*mut FILE, Errno> { + let opts = match mode_str { + b"r" | b"rb" => OpenOptions::READ, + b"r+" | b"rb+" => OpenOptions::READ | OpenOptions::WRITE, + b"w" | b"wb" => OpenOptions::TRUNCATE | OpenOptions::WRITE, + b"w+" | b"wb+" => OpenOptions::TRUNCATE | OpenOptions::READ | OpenOptions::WRITE, + b"a" | b"ab" => OpenOptions::APPEND | OpenOptions::READ | OpenOptions::WRITE, + b"a+" | b"ab+" => OpenOptions::APPEND | OpenOptions::READ | OpenOptions::WRITE, + // TODO: errno and fail + _ => todo!(), + }; + + match source.open_with(opts) { + Ok(file) => { + let file = Box::into_raw(Box::new(file)); + unsafe { + super::register_file(file); + } + Ok(file) + } + Err(err) => Err(err), + } +} #[no_mangle] pub unsafe extern "C" fn fopen(pathname: *const c_char, mode: *const c_char) -> *mut FILE { - todo!() + if pathname.is_null() || mode.is_null() { + panic!(); + } + let pathname = CStr::from_ptr(pathname); + let pathname = pathname.to_str().unwrap(); + let mode = CStr::from_ptr(mode); + + match open_inner(Path::from_str(pathname), mode.to_bytes()) { + Ok(file) => file, + Err(_) => null_mut(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn feof(stream: *mut FILE) -> c_int { + let stream = stream.as_mut().unwrap(); + stream.has_flag(FileFlags::EOF) as _ +} + +#[no_mangle] +pub unsafe extern "C" fn ferror(stream: *mut FILE) -> c_int { + let stream = stream.as_mut().unwrap(); + stream.has_flag(FileFlags::ERROR) as _ +} + +#[no_mangle] +pub unsafe extern "C" fn clearerr(stream: *mut FILE) { + let stream = stream.as_mut().unwrap(); + stream.clear_error(); +} + +#[no_mangle] +pub unsafe extern "C" fn fclose(stream: *mut FILE) -> c_int { + if stream.is_null() { + panic!(); + } + + if !super::deregister_file(stream) { + yggdrasil_rt::debug_trace!("fclose() non-registered file: {:p}", stream); + } + + stream.close().into_eof_status() +} + +#[no_mangle] +pub unsafe extern "C" fn fileno(stream: *mut FILE) -> c_int { + let stream = stream.as_mut().unwrap(); + match stream.as_raw_fd() { + Ok(RawFd(fd)) => fd as _, + Err(_) => -1, + } } #[no_mangle] @@ -43,3 +129,103 @@ pub unsafe extern "C" fn fread( stream.read(data).into_size_status() / size } + +#[no_mangle] +pub unsafe extern "C" fn fflush(stream: *mut FILE) -> c_int { + if let Some(stream) = stream.as_mut() { + stream.flush().into_eof_status() + } else { + super::fflush_all().into_eof_status() + } +} + +#[no_mangle] +pub unsafe extern "C" fn fseek(stream: *mut FILE, offset: c_long, whence: c_int) -> c_int { + fseeko(stream, offset.try_into().unwrap(), whence) +} + +#[no_mangle] +pub unsafe extern "C" fn fseeko(stream: *mut FILE, offset: off_t, whence: c_int) -> c_int { + let stream = stream.as_mut().unwrap(); + let off = match whence { + SEEK_SET => SeekFrom::Start(offset.try_into().unwrap()), + SEEK_CUR => SeekFrom::Current(offset), + SEEK_END => SeekFrom::End(offset), + // TODO set errno and fail + _ => todo!(), + }; + + match stream.seek(off) { + Ok(_) => 0, + Err(_) => -1, + } +} + +#[no_mangle] +pub unsafe extern "C" fn ftell(stream: *mut FILE) -> c_long { + ftello(stream).try_into().unwrap() +} + +#[no_mangle] +pub unsafe extern "C" fn ftello(stream: *mut FILE) -> off_t { + let stream = stream.as_mut().unwrap(); + match stream.stream_position() { + Ok(p) => p.try_into().unwrap(), + Err(_) => -1, + } +} + +#[no_mangle] +pub unsafe extern "C" fn rewind(stream: *mut FILE) { + fseek(stream, 0, SEEK_SET); +} + +#[no_mangle] +pub unsafe extern "C" fn fgetpos(stream: *mut FILE, pos: *mut fpos_t) -> c_int { + let stream = stream.as_mut().unwrap(); + let pos = pos.as_mut().unwrap(); + match stream.stream_position() { + Ok(p) => { + *pos = p; + 0 + } + Err(_) => -1, + } +} + +#[no_mangle] +pub unsafe extern "C" fn fsetpos(stream: *mut FILE, pos: *const fpos_t) -> c_int { + let stream = stream.as_mut().unwrap(); + let pos = pos.as_ref().unwrap(); + match stream.seek(SeekFrom::Start(*pos as _)) { + Ok(_) => 0, + Err(_) => -1, + } +} + +#[no_mangle] +pub unsafe extern "C" fn setvbuf( + stream: *mut FILE, + buf: *mut c_char, + mode: c_int, + size: usize, +) -> c_int { + let stream = stream.as_mut().unwrap(); + stream.setvbuf(mode, buf, size).into_eof_status() +} + +#[no_mangle] +pub unsafe extern "C" fn setbuf(stream: *mut FILE, buf: *mut c_char) { + setbuffer(stream, buf, 0); +} + +#[no_mangle] +pub unsafe extern "C" fn setbuffer(stream: *mut FILE, buf: *mut c_char, size: usize) { + let mode = if buf.is_null() { _IONBF } else { _IOFBF }; + setvbuf(stream, buf, mode, size); +} + +#[no_mangle] +pub unsafe extern "C" fn setlinebuf(stream: *mut FILE) { + setvbuf(stream, null_mut(), _IOLBF, 0); +} diff --git a/src/header/stdio/get_put.rs b/src/header/stdio/get_put.rs index d6ed374..27278b6 100644 --- a/src/header/stdio/get_put.rs +++ b/src/header/stdio/get_put.rs @@ -1,8 +1,11 @@ -use core::ffi::{c_char, c_int, CStr}; +use core::{ + ffi::{c_char, c_int, CStr}, + ptr::null_mut, +}; -use crate::{error::CZeroResult, traits::Write}; +use crate::io::{Read, Write}; -use super::{stdout, FILE}; +use super::{stdin, stdout, EOF, FILE}; // Chars @@ -11,10 +14,9 @@ unsafe extern "C" fn fputc(c: c_int, stream: *mut FILE) -> c_int { let stream = stream.as_mut().unwrap(); let c = c as u8; - match stream.putc(c) { + match stream.write(&[c]) { Ok(_) => c as c_int, - // TODO EOF - Err(_) => -1, + Err(_) => EOF, } } @@ -32,16 +34,115 @@ unsafe extern "C" fn putchar(c: c_int) -> c_int { #[no_mangle] unsafe extern "C" fn puts(s: *const c_char) -> c_int { - fputs(s, stdout) + let r = fputs(s, stdout); + if r < 0 { + return r; + } + fputc(b'\n' as _, stdout) } #[no_mangle] unsafe extern "C" fn fputs(s: *const c_char, stream: *mut FILE) -> c_int { let stream = stream.as_mut().unwrap(); if s.is_null() { - return stream.puts(b"(nil)").into_zero_status(); + panic!(); } let s = CStr::from_ptr(s); - stream.puts(s.to_bytes()).into_zero_status() + match stream.write(s.to_bytes()) { + Ok(_) => 0, + Err(_) => EOF, + } +} + +#[no_mangle] +unsafe extern "C" fn fgetc(stream: *mut FILE) -> c_int { + let stream = stream.as_mut().unwrap(); + let mut buf = [0]; + match stream.read(&mut buf) { + Ok(1) => buf[0] as c_int, + Ok(_) => EOF, + Err(_) => EOF, + } +} + +#[no_mangle] +unsafe extern "C" fn getc(stream: *mut FILE) -> c_int { + fgetc(stream) +} + +#[no_mangle] +unsafe extern "C" fn getchar() -> c_int { + fgetc(stdin) +} + +#[no_mangle] +unsafe extern "C" fn fgets(s: *mut c_char, size: c_int, stream: *mut FILE) -> *mut c_char { + let stream = stream.as_mut().unwrap(); + + if size <= 0 { + return null_mut(); + } + // Nothing to read + if size == 1 { + *s = 0; + return s; + } + + let size = size as usize; + let mut pos = 0; + let mut buf = [0]; + + while pos < size - 1 { + let ch = match stream.read(&mut buf) { + Ok(1) => buf[0], + Ok(_) => { + break; + } + Err(err) => { + return null_mut(); + } + }; + + *s.add(pos) = ch as _; + pos += 1; + + if ch == b'\n' { + break; + } + } + + if pos == 0 { + null_mut() + } else { + *s.add(pos) = 0; + s + } +} + +#[no_mangle] +unsafe extern "C" fn ungetc(c: c_int, stream: *mut FILE) -> c_int { + let stream = stream.as_mut().unwrap(); + if stream.ungetc(c as _) { + c + } else { + EOF + } +} + +// Line + +#[no_mangle] +unsafe extern "C" fn getdelim( + lineptr: *mut *mut c_char, + n: *mut usize, + delim: c_int, + stream: *mut FILE, +) -> isize { + todo!() +} + +#[no_mangle] +unsafe extern "C" fn getline(lineptr: *mut *mut c_char, n: *mut usize, stream: *mut FILE) -> isize { + todo!() } diff --git a/src/header/stdio/mod.rs b/src/header/stdio/mod.rs index 3eaace1..70bd238 100644 --- a/src/header/stdio/mod.rs +++ b/src/header/stdio/mod.rs @@ -1,30 +1,31 @@ -use core::{fmt, mem::MaybeUninit, ptr}; +use core::{ + ffi::{c_char, c_int}, + fmt, ptr, +}; -use alloc::{boxed::Box, vec, vec::Vec}; -use yggdrasil_rt::{io::RawFd, sys as syscall}; +use alloc::{boxed::Box, collections::BTreeSet, vec::Vec}; +use bitflags::bitflags; +use yggdrasil_rt::{ + io::{FileMode, OpenOptions, RawFd, SeekFrom}, + path::Path, +}; use crate::{ - error::EResult, + error::{EResult, TryFromExt}, + file::{File, FileBacking}, + io::{ + buffered::{BufWriter, FileWriter, LineWriter, ReadBuffer, UnbufferedWriter}, + BufRead, Read, Seek, Write, + }, sync::{Mutex, RawMutex}, - traits::{Read, Write}, }; use super::errno::Errno; // TODO: -// -// fpos_t -// off_t -> sys/types.h -// ssize_t -> sys/types.h -// -// BUFSIZ // L_ctermid // L_tmpnam // -// _IOFBF -// _IOLBF -// _IONBF -// // SEEK_CUR // SEEK_END // SEEK_SET @@ -38,196 +39,427 @@ use super::errno::Errno; // P_tmpdir /* -void clearerr(FILE *); char *ctermid(char *); -int dprintf(int, const char *restrict, ...) -int fclose(FILE *); FILE *fdopen(int, const char *); -int feof(FILE *); -int ferror(FILE *); -int fflush(FILE *); -int fgetc(FILE *); -int fgetpos(FILE *restrict, fpos_t *restrict); -char *fgets(char *restrict, int, FILE *restrict); -int fileno(FILE *); void flockfile(FILE *); FILE *fmemopen(void *restrict, size_t, const char *restrict); -FILE *fopen(const char *restrict, const char *restrict); -int fprintf(FILE *restrict, const char *restrict, ...); -int fputc(int, FILE *); -size_t fread(void *restrict, size_t, size_t, FILE *restrict); FILE *freopen(const char *restrict, const char *restrict, FILE *restrict); -int fscanf(FILE *restrict, const char *restrict, ...); -int fseek(FILE *, long, int); -int fseeko(FILE *, off_t, int); -int fsetpos(FILE *, const fpos_t *); -long ftell(FILE *); -off_t ftello(FILE *); int ftrylockfile(FILE *); void funlockfile(FILE *); -int getc(FILE *); -int getchar(void); -int getc_unlocked(FILE *); -int getchar_unlocked(void); -ssize_t getdelim(char **restrict, size_t *restrict, int, - FILE *restrict); -ssize_t getline(char **restrict, size_t *restrict, FILE *restrict); -char *gets(char *); FILE *open_memstream(char **, size_t *); int pclose(FILE *); void perror(const char *); FILE *popen(const char *, const char *); -int putc(int, FILE *); -int putchar(int); -int putc_unlocked(int, FILE *); -int putchar_unlocked(int); int remove(const char *); int rename(const char *, const char *); int renameat(int, const char *, int, const char *); -void rewind(FILE *); -int scanf(const char *restrict, ...); -void setbuf(FILE *restrict, char *restrict); -int setvbuf(FILE *restrict, char *restrict, int, size_t); -int snprintf(char *restrict, size_t, const char *restrict, ...); -int sprintf(char *restrict, const char *restrict, ...); -int sscanf(const char *restrict, const char *restrict, ...); char *tempnam(const char *, const char *); FILE *tmpfile(void); char *tmpnam(char *); -int ungetc(int, FILE *); -int vdprintf(int, const char *restrict, va_list); -int vfscanf(FILE *restrict, const char *restrict, va_list); -int vscanf(const char *restrict, va_list); -int vsnprintf(char *restrict, size_t, const char *restrict, - va_list); -int vsprintf(char *restrict, const char *restrict, va_list); -int vsscanf(const char *restrict, const char *restrict, va_list); + + + + + */ +macro_rules! locked_op { + ($self:expr, $op:expr) => {{ + $self.lock.lock(); + let result = unsafe { $op }; + unsafe { $self.lock.release() } + result + }}; +} + mod file; mod get_put; mod printf; +mod scanf; +mod unlocked; -struct FileInner(RawFd); +pub trait FileOpenSource { + fn open_with(self, opts: OpenOptions) -> Result; +} + +pub enum BufferingMode { + Full, + Line, + None, +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum Direction { + Read, + Write, +} + +bitflags! { + pub struct FileFlags: u32 { + const ERROR = 1 << 0; + const EOF = 1 << 1; + const READ = 1 << 2; + const WRITE = 1 << 3; + const BUILTIN = 1 << 4; + } +} pub struct FILE { lock: RawMutex, - file: FileInner, + inner: FileBacking, + output: Box, + read_buffer: Option>, - error: bool, - eof: bool, + flags: FileFlags, - buffer: Vec, - unget_buffer: Vec, - read_pos: usize, - read_size: usize, -} + ungetc: Vec, -impl FileInner { - unsafe fn read(&mut self, data: &mut [u8]) -> Result { - let count = EResult::from(syscall::read(self.0, data))?; - Ok(count) - } + // NOTE: + // man setvbuf(3): + // The setvbuf() function may be used only after opening a stream and + // before any other operations have been performed on it. + last_operation: Option, } impl FILE { - unsafe fn new_builtin(fd: RawFd) -> Self { + pub unsafe fn from_c_file(file: File, flags: FileFlags) -> Self { + assert!(!flags.contains(FileFlags::BUILTIN)); + let inner = FileBacking::File(file); + // TODO run is_terminal on file and check for desired buffering mode Self { lock: RawMutex::new(), - file: FileInner(fd), + output: Box::new(BufWriter::with_capacity(inner.make_ref(), BUFSIZ)), + inner, + read_buffer: Some(ReadBuffer::owned(BUFSIZ)), - eof: false, - error: false, + flags, - buffer: vec![0; 1024], - unget_buffer: vec![], - read_pos: 0, - read_size: 0, + ungetc: Vec::new(), + + last_operation: None, } } - pub fn puts(&mut self, data: &[u8]) -> Result<(), Errno> { - self.write(data)?; - self.write(b"\n")?; - Ok(()) - } + pub unsafe fn new_builtin(fd: RawFd, flags: FileFlags) -> Self { + let inner = FileBacking::File(File::new(fd)); + Self { + lock: RawMutex::new(), - unsafe fn fill_buf(&mut self) -> Result<&[u8], Errno> { - if self.read_pos == self.read_size { - self.read_size = match self.file.read(&mut self.buffer) { - Ok(0) => { - self.eof = true; - 0 - } - Ok(n) => n, - Err(err) => { - self.error = true; - return Err(err); - } - }; - self.read_pos = 0; + output: Box::new(LineWriter::with_capacity(inner.make_ref(), BUFSIZ)), + inner, + read_buffer: Some(ReadBuffer::owned(BUFSIZ)), + + flags: FileFlags::BUILTIN | flags, + + ungetc: Vec::new(), + + last_operation: None, } - - Ok(&self.buffer[self.read_pos..self.read_size]) } - unsafe fn consume(&mut self, len: usize) { - self.read_pos = (self.read_pos + len).min(self.read_size); + pub unsafe fn close(self: *mut Self) -> Result<(), Errno> { + // TODO lock needed? + let r = self.as_mut().unwrap(); + r.flush()?; + if r.flags.contains(FileFlags::BUILTIN) { + // NOTE The OS will close the file for us + Ok(()) + } else { + // Drop the file + drop(Box::from_raw(self)); + Ok(()) + } + } + + pub unsafe fn as_raw_fd_unlocked(&mut self) -> Result { + let fd = self.inner.as_raw_fd()?; + Ok(fd) + } + + pub fn as_raw_fd(&mut self) -> Result { + locked_op!(self, self.as_raw_fd_unlocked()) + } + + pub unsafe fn clear_error_unlocked(&mut self) { + self.flags &= !FileFlags::ERROR; + } + + pub fn clear_error(&mut self) { + locked_op!(self, self.clear_error_unlocked()); + } + + pub fn has_flag(&mut self, flag: FileFlags) -> bool { + locked_op!(self, self.has_flag_unlocked(flag)) + } + + pub unsafe fn has_flag_unlocked(&self, flag: FileFlags) -> bool { + self.flags.contains(flag) + } + + pub fn setvbuf( + &mut self, + mode: c_int, + buffer: *mut c_char, + capacity: usize, + ) -> Result<(), Errno> { + locked_op!(self, self.setvbuf_unlocked(mode, buffer, capacity)) + } + + pub unsafe fn write_unlocked(&mut self, data: &[u8]) -> Result { + self.set_direction(Direction::Write)?; + + match self.output.write(data) { + Ok(amount) => Ok(amount), + Err(err) => { + self.flags |= FileFlags::ERROR; + Err(err) + } + } + } + + pub unsafe fn flush_unlocked(&mut self) -> Result<(), Errno> { + match self.output.flush() { + Ok(()) => { + self.last_operation = None; + Ok(()) + } + Err(err) => { + self.flags |= FileFlags::ERROR; + Err(err) + } + } } pub unsafe fn read_unlocked(&mut self, data: &mut [u8]) -> Result { - let unget_len = core::cmp::min(self.unget_buffer.len(), data.len()); - for i in 0..unget_len { - data[i] = self.unget_buffer.pop().unwrap(); - } - if unget_len != 0 { - return Ok(unget_len); - } - let len = { - let buf = unsafe { self.fill_buf()? }; - let len = buf.len().min(data.len()); + self.set_direction(Direction::Read)?; + if !self.ungetc.is_empty() { + // Read from ungetc + let amount = core::cmp::min(self.ungetc.len(), data.len()); + data[..amount].copy_from_slice(&self.ungetc[..amount]); + self.ungetc.drain(..amount); + return Ok(amount); + } + + if self.read_buffer.is_some() { + let buf = self.fill_buf()?; + let len = core::cmp::min(data.len(), buf.len()); data[..len].copy_from_slice(&buf[..len]); - len - }; - unsafe { self.consume(len); + Ok(len) + } else { + match self.inner.read(data) { + Ok(0) => { + self.flags |= FileFlags::EOF; + Ok(0) + } + Ok(n) => Ok(n), + Err(err) => { + self.flags |= FileFlags::ERROR; + Err(err) + } + } } - Ok(len) } -} -impl Read for FILE { - fn read(&mut self, data: &mut [u8]) -> Result { - self.lock.lock(); - let result = unsafe { self.read_unlocked(data) }; - unsafe { - self.lock.release(); + unsafe fn ungetc_unlocked(&mut self, ch: u8) -> bool { + // ungetc() for write doesn't make any sense + if self.set_direction(Direction::Read).is_err() { + return false; } - result + if self.ungetc.len() == UNGETC_MAX { + return false; + } + self.ungetc.push(ch); + true } -} -impl Write for FILE { - fn write(&mut self, data: &[u8]) -> Result { - // TODO - self.lock.lock(); - let count = EResult::from(unsafe { syscall::write(self.file.0, data) })?; - unsafe { - self.lock.release(); + pub fn ungetc(&mut self, ch: u8) -> bool { + locked_op!(self, self.ungetc_unlocked(ch)) + } + + fn reset(&mut self) { + if let Some(read_buffer) = self.read_buffer.as_mut() { + read_buffer.reset(); + } + self.output.reset(); + self.ungetc.clear(); + self.last_operation = None; + self.flags &= !(FileFlags::ERROR | FileFlags::EOF); + } + + unsafe fn setvbuf_unlocked( + &mut self, + mode: c_int, + buffer: *mut c_char, + capacity: usize, + ) -> Result<(), Errno> { + let mode = BufferingMode::e_try_from(mode)?; + + if self.last_operation.is_some() { + // TODO EINVAL? + todo!(); + } + + let mut read_capacity = capacity; + let mut write_capacity = capacity; + + // Set read buffer + match mode { + BufferingMode::None => self.read_buffer = None, + BufferingMode::Full | BufferingMode::Line => { + if buffer.is_null() || read_capacity == 0 { + if read_capacity == 0 { + read_capacity = BUFSIZ; + } + + self.read_buffer = Some(ReadBuffer::owned(read_capacity)); + } else { + let slice = + unsafe { core::slice::from_raw_parts_mut(buffer as *mut _, capacity) }; + self.read_buffer = Some(ReadBuffer::borrowed(slice)); + } + } + } + + // Set write buffer + let file_ref = unsafe { self.inner.make_ref() }; + match mode { + BufferingMode::None => self.output = Box::new(UnbufferedWriter::new(file_ref)), + BufferingMode::Line => { + if write_capacity == 0 { + write_capacity = BUFSIZ; + } + self.output = Box::new(LineWriter::with_capacity(file_ref, write_capacity)) + } + BufferingMode::Full => { + if write_capacity == 0 { + write_capacity = BUFSIZ; + } + self.output = Box::new(LineWriter::with_capacity(file_ref, write_capacity)) + } + } + + Ok(()) + } + + unsafe fn seek_unlocked(&mut self, off: SeekFrom) -> Result { + self.flush_unlocked()?; + self.ungetc.clear(); + + match self.inner.seek(off) { + Ok(pos) => { + self.flags &= !FileFlags::EOF; + Ok(pos) + } + Err(err) => { + self.flags |= FileFlags::ERROR; + Err(err) + } + } + } + + unsafe fn set_direction(&mut self, direction: Direction) -> Result<(), Errno> { + match self.last_operation.replace(direction) { + Some(dir) if dir != direction => { + self.flags |= FileFlags::ERROR; + todo!() + } + _ => Ok(()), } - Ok(count) } } impl fmt::Write for FILE { fn write_str(&mut self, s: &str) -> fmt::Result { - match self.write(s.as_bytes()) { - Ok(_) => Ok(()), - Err(_) => Err(fmt::Error), + self.write_all(s.as_bytes()).map_err(|_| fmt::Error) + } +} + +impl Write for FILE { + fn write(&mut self, data: &[u8]) -> Result { + locked_op!(self, self.write_unlocked(data)) + } + + fn flush(&mut self) -> Result<(), Errno> { + locked_op!(self, self.flush_unlocked()) + } +} + +impl Read for FILE { + fn read(&mut self, data: &mut [u8]) -> Result { + locked_op!(self, self.read_unlocked(data)) + } +} + +// NOTE assumes file is locked +impl BufRead for FILE { + fn fill_buf(&mut self) -> Result<&[u8], Errno> { + let buffer = self.read_buffer.as_mut().unwrap(); + match buffer.fill_from(&mut self.inner) { + Ok(slice) => { + if slice.len() == 0 { + self.flags |= FileFlags::EOF; + } + Ok(slice) + } + Err(err) => { + self.flags |= FileFlags::ERROR; + Err(err) + } + } + } + + fn consume(&mut self, amount: usize) { + let buffer = self.read_buffer.as_mut().unwrap(); + buffer.consume(amount) + } +} + +impl Seek for FILE { + fn seek(&mut self, off: SeekFrom) -> Result { + locked_op!(self, self.seek_unlocked(off)) + } +} + +impl FileOpenSource for &Path { + fn open_with(self, opts: OpenOptions) -> Result { + let f = File::open_at(None, self, opts, FileMode::default_file())?; + let mut flags = FileFlags::empty(); + if opts.contains(OpenOptions::READ) { + flags |= FileFlags::READ; + } + if opts.contains(OpenOptions::WRITE) { + flags |= FileFlags::WRITE; + } + Ok(unsafe { FILE::from_c_file(f, flags) }) + } +} + +impl FileOpenSource for RawFd { + fn open_with(self, opts: OpenOptions) -> Result { + let f = File::from_raw(self); + let mut flags = FileFlags::empty(); + if opts.contains(OpenOptions::READ) { + flags |= FileFlags::READ; + } + if opts.contains(OpenOptions::WRITE) { + flags |= FileFlags::WRITE; + } + Ok(unsafe { FILE::from_c_file(f, flags) }) + } +} + +impl TryFromExt for BufferingMode { + fn e_try_from(value: c_int) -> EResult { + match value { + _IOFBF => EResult::Ok(Self::Full), + _IOLBF => EResult::Ok(Self::Line), + _IONBF => EResult::Ok(Self::None), + _ => EResult::Err(yggdrasil_rt::Error::InvalidArgument), } } } @@ -239,12 +471,68 @@ pub static mut stdout: *mut FILE = ptr::null_mut(); #[no_mangle] pub static mut stderr: *mut FILE = ptr::null_mut(); +pub const _IOFBF: c_int = 0; +pub const _IOLBF: c_int = 1; +pub const _IONBF: c_int = 2; + +pub const SEEK_SET: c_int = 0; +pub const SEEK_CUR: c_int = 1; +pub const SEEK_END: c_int = 2; + +pub const EOF: c_int = -1; + +pub type fpos_t = u64; + +pub const BUFSIZ: usize = 8192; + +const UNGETC_MAX: usize = 128; + +pub static OPEN_FILES: Mutex> = Mutex::new(BTreeSet::new()); + +pub unsafe fn register_file(file: *mut FILE) { + OPEN_FILES.lock().insert(file); +} + +pub unsafe fn deregister_file(file: *mut FILE) -> bool { + OPEN_FILES.lock().remove(&file) +} + pub unsafe fn setup_default_files() { - let stdin_ = Box::into_raw(Box::new(FILE::new_builtin(RawFd::STDIN))); - let stdout_ = Box::into_raw(Box::new(FILE::new_builtin(RawFd::STDOUT))); - let stderr_ = Box::into_raw(Box::new(FILE::new_builtin(RawFd::STDERR))); + let stdin_ = Box::into_raw(Box::new(FILE::new_builtin(RawFd::STDIN, FileFlags::READ))); + let stdout_ = Box::into_raw(Box::new(FILE::new_builtin(RawFd::STDOUT, FileFlags::WRITE))); + let stderr_ = Box::into_raw(Box::new(FILE::new_builtin(RawFd::STDERR, FileFlags::WRITE))); stdin = stdin_; stdout = stdout_; stderr = stderr_; + + let mut open_files = OPEN_FILES.lock(); + open_files.insert(stdin); + open_files.insert(stdout); + open_files.insert(stderr); +} + +pub fn fflush_all() -> Result<(), Errno> { + let open_files = OPEN_FILES.lock(); + for &file in open_files.iter() { + unsafe { + file.as_mut().unwrap().flush()?; + } + } + Ok(()) +} + +pub unsafe fn fflush_all_unlocked() -> Result<(), Errno> { + let open_files = OPEN_FILES.lock(); + for &file in open_files.iter() { + file.as_mut().unwrap().flush_unlocked()?; + } + Ok(()) +} + +pub unsafe fn cleanup() { + let mut open_files = OPEN_FILES.lock(); + while let Some(file) = open_files.pop_first() { + file.close().ok(); + } } diff --git a/src/header/stdio/printf/float.rs b/src/header/stdio/printf/float.rs index daeb0df..fafdd88 100644 --- a/src/header/stdio/printf/float.rs +++ b/src/header/stdio/printf/float.rs @@ -1,10 +1,10 @@ -use core::{ffi::c_double, num::FpCategory}; +use core::{ffi::c_double, fmt, num::FpCategory}; use alloc::{format, string::String}; use crate::{ header::errno::{Errno, EINVAL}, - traits::Write, + io::Write, }; use super::format::FmtOpts; @@ -30,7 +30,7 @@ fn float_exp(mut val: c_double) -> (c_double, isize) { (val, exp) } -fn fmt_float_exp( +fn fmt_float_exp( val: c_double, exp: isize, exp_fmt: u8, @@ -38,9 +38,6 @@ fn fmt_float_exp( precision: usize, opts: &FmtOpts, ) -> Result { - // TODO padding - use core::fmt::Write as FW; - let mut exp2 = exp; let mut exp_len = 1; while exp2 >= 10 { @@ -65,7 +62,7 @@ fn fmt_float_string(val: c_double, precision: usize) -> String { string } -fn fmt_float_finite( +fn fmt_float_finite( val: c_double, output: &mut W, precision: usize, @@ -78,7 +75,7 @@ fn fmt_float_finite( Ok(lpad + flen + rpad) } -fn fmt_float_nonfinite( +fn fmt_float_nonfinite( val: c_double, output: &mut W, upper: bool, @@ -108,7 +105,7 @@ fn fmt_float_nonfinite( Ok(len + lpad + flen + rpad) } -pub fn fmt_float( +pub fn fmt_float( val: c_double, output: &mut W, upper: bool, @@ -121,7 +118,7 @@ pub fn fmt_float( } } -pub fn fmt_float_scientific( +pub fn fmt_float_scientific( val: c_double, output: &mut W, upper: bool, @@ -141,7 +138,7 @@ pub fn fmt_float_scientific( } } -pub fn fmt_float_any( +pub fn fmt_float_any( val: c_double, output: &mut W, upper: bool, diff --git a/src/header/stdio/printf/format.rs b/src/header/stdio/printf/format.rs index 7192901..5901eb2 100644 --- a/src/header/stdio/printf/format.rs +++ b/src/header/stdio/printf/format.rs @@ -1,11 +1,14 @@ -use core::ffi::{ - c_char, c_double, c_int, c_long, c_longlong, c_short, c_uchar, c_uint, c_ulong, c_ulonglong, - c_ushort, CStr, VaList, +use core::{ + ffi::{ + c_char, c_double, c_int, c_long, c_longlong, c_short, c_uchar, c_uint, c_ulong, + c_ulonglong, c_ushort, CStr, VaList, + }, + fmt, }; use alloc::string::String; -use crate::{header::errno::Errno, traits::Write, types::wchar_t}; +use crate::{header::errno::Errno, io::Write, types::wchar_t}; use super::float::{fmt_float, fmt_float_any, fmt_float_scientific}; @@ -140,7 +143,7 @@ impl FmtSize { } impl FmtOpts { - fn pad(&self, output: &mut W, len: usize) -> Result { + fn pad(&self, output: &mut W, len: usize) -> Result { let pad = self.width - core::cmp::min(self.width, len); for _ in 0..pad { output.write(&[self.pad_char])?; @@ -148,7 +151,11 @@ impl FmtOpts { Ok(pad) } - pub fn left_pad(&self, output: &mut W, len: usize) -> Result { + pub fn left_pad( + &self, + output: &mut W, + len: usize, + ) -> Result { if !self.left_adjust { self.pad(output, len) } else { @@ -156,7 +163,11 @@ impl FmtOpts { } } - pub fn right_pad(&self, output: &mut W, len: usize) -> Result { + pub fn right_pad( + &self, + output: &mut W, + len: usize, + ) -> Result { if self.left_adjust { self.pad(output, len) } else { @@ -164,7 +175,7 @@ impl FmtOpts { } } - pub fn fmt( + pub fn fmt( &self, spec: FmtSpec, output: &mut W, diff --git a/src/header/stdio/printf/mod.rs b/src/header/stdio/printf/mod.rs index 36f89ad..8ec712f 100644 --- a/src/header/stdio/printf/mod.rs +++ b/src/header/stdio/printf/mod.rs @@ -1,6 +1,11 @@ -use core::ffi::{c_char, c_int, CStr, VaList}; +use core::{ + ffi::{c_char, c_int, CStr, VaList}, + fmt, +}; -use crate::{header::errno::Errno, traits::Write}; +use yggdrasil_rt::{io::RawFd, sys as syscall}; + +use crate::{error::EResult, header::errno::Errno, io::Write}; use self::format::{FmtOpts, FmtRadix, FmtSign, FmtSize, FmtSpec}; @@ -9,14 +14,86 @@ use super::{stdout, FILE}; mod float; mod format; -fn printf_inner(output: &mut W, format: &[u8], mut ap: VaList) -> Result { +struct StringWriter { + buffer: *mut c_char, + position: usize, + capacity: usize, +} + +struct FdWriter { + fd: RawFd, +} + +impl StringWriter { + pub fn new(buffer: *mut c_char, capacity: usize) -> Self { + Self { + buffer, + capacity, + position: 0, + } + } +} + +impl Write for StringWriter { + fn write(&mut self, data: &[u8]) -> Result { + let count = core::cmp::min(self.capacity - self.position, data.len()); + if count > 0 { + let dst = unsafe { + core::slice::from_raw_parts_mut(self.buffer.add(self.position) as *mut u8, count) + }; + dst.copy_from_slice(&data[..count]); + self.position += count; + } + Ok(count) + } + + fn flush(&mut self) -> Result<(), Errno> { + todo!() + } +} + +impl fmt::Write for StringWriter { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.write(s.as_bytes()).map_err(|_| fmt::Error)?; + Ok(()) + } +} + +impl FdWriter { + pub fn new(fd: RawFd) -> Self { + Self { fd } + } +} + +impl Write for FdWriter { + fn write(&mut self, data: &[u8]) -> Result { + let count = EResult::from(unsafe { syscall::write(self.fd, data) })?; + Ok(count) + } + + fn flush(&mut self) -> Result<(), Errno> { + todo!() + } +} + +impl fmt::Write for FdWriter { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.write(s.as_bytes()).map_err(|_| fmt::Error)?; + Ok(()) + } +} + +fn printf_inner( + output: &mut W, + format: &[u8], + mut ap: VaList, +) -> Result { let mut fmt = format.into_iter(); let mut count = 0; while let Some(&c) = fmt.next() { if c != b'%' { - output.putc(c)?; - count += 1; + count += output.write(&[c])?; continue; } @@ -131,3 +208,74 @@ unsafe extern "C" fn vfprintf(stream: *mut FILE, format: *const c_char, ap: VaLi Err(_) => -1, } } + +#[no_mangle] +unsafe extern "C" fn sprintf(str: *mut c_char, format: *const c_char, mut args: ...) -> c_int { + vsnprintf(str, usize::MAX, format, args.as_va_list()) +} + +#[no_mangle] +unsafe extern "C" fn snprintf( + str: *mut c_char, + len: usize, + format: *const c_char, + mut args: ... +) -> c_int { + vsnprintf(str, len, format, args.as_va_list()) +} + +#[no_mangle] +unsafe extern "C" fn vsprintf(str: *mut c_char, format: *const c_char, ap: VaList) -> c_int { + vsnprintf(str, usize::MAX, format, ap) +} + +#[no_mangle] +unsafe extern "C" fn vsnprintf( + str: *mut c_char, + len: usize, + format: *const c_char, + ap: VaList, +) -> c_int { + if format.is_null() { + panic!(); + } + if len == 0 { + return 0; + } + if str.is_null() { + // TODO output the length + todo!() + } + let mut writer = StringWriter::new(str, len); + let format = CStr::from_ptr(format); + + match printf_inner(&mut writer, format.to_bytes(), ap) { + // TODO handle this + Ok(count) => count as c_int, + Err(_) => -1, + } +} + +#[no_mangle] +unsafe extern "C" fn dprintf(fd: c_int, format: *const c_char, mut args: ...) -> c_int { + vdprintf(fd, format, args.as_va_list()) +} + +#[no_mangle] +unsafe extern "C" fn vdprintf(fd: c_int, format: *const c_char, ap: VaList) -> c_int { + if format.is_null() { + panic!(); + } + if fd < 0 { + // TODO set errno and return + todo!(); + } + let mut writer = FdWriter::new(RawFd(fd as u32)); + let format = CStr::from_ptr(format); + + match printf_inner(&mut writer, format.to_bytes(), ap) { + // TODO handle this + Ok(count) => count as c_int, + Err(_) => -1, + } +} diff --git a/src/header/stdio/scanf.rs b/src/header/stdio/scanf.rs new file mode 100644 index 0000000..906ae63 --- /dev/null +++ b/src/header/stdio/scanf.rs @@ -0,0 +1,42 @@ +/* +int fscanf(FILE *restrict, const char *restrict, ...); +int scanf(const char *restrict, ...); +int sscanf(const char *restrict, const char *restrict, ...); +int vfscanf(FILE *restrict, const char *restrict, va_list); +int vscanf(const char *restrict, va_list); +int vsscanf(const char *restrict, const char *restrict, va_list); + */ + +use core::ffi::{c_char, c_int, VaList}; + +use super::{stdin, FILE}; + +#[no_mangle] +unsafe extern "C" fn fscanf(stream: *mut FILE, format: *const c_char, mut args: ...) -> c_int { + vfscanf(stream, format, args.as_va_list()) +} + +#[no_mangle] +unsafe extern "C" fn scanf(format: *const c_char, mut args: ...) -> c_int { + vfscanf(stdin, format, args.as_va_list()) +} + +#[no_mangle] +unsafe extern "C" fn sscanf(str: *const c_char, format: *const c_char, mut args: ...) -> c_int { + vsscanf(str, format, args.as_va_list()) +} + +#[no_mangle] +unsafe extern "C" fn vfscanf(stream: *mut FILE, format: *const c_char, ap: VaList) -> c_int { + todo!() +} + +#[no_mangle] +unsafe extern "C" fn vscanf(format: *const c_char, ap: VaList) -> c_int { + vfscanf(stdin, format, ap) +} + +#[no_mangle] +unsafe extern "C" fn vsscanf(str: *const c_char, format: *const c_char, ap: VaList) -> c_int { + todo!() +} diff --git a/src/header/stdio/unlocked.rs b/src/header/stdio/unlocked.rs new file mode 100644 index 0000000..d57b8b6 --- /dev/null +++ b/src/header/stdio/unlocked.rs @@ -0,0 +1,90 @@ +use core::ffi::{c_int, c_void}; + +use yggdrasil_rt::io::RawFd; + +use crate::error::{CSizeResult, CZeroResult}; + +use super::{FileFlags, FILE}; + +#[no_mangle] +pub unsafe extern "C" fn feof_unlocked(stream: *mut FILE) -> c_int { + let stream = stream.as_mut().unwrap(); + stream.has_flag_unlocked(FileFlags::EOF) as _ +} + +#[no_mangle] +pub unsafe extern "C" fn ferror_unlocked(stream: *mut FILE) -> c_int { + let stream = stream.as_mut().unwrap(); + stream.has_flag_unlocked(FileFlags::ERROR) as _ +} + +#[no_mangle] +pub unsafe extern "C" fn clearerr_unlocked(stream: *mut FILE) { + let stream = stream.as_mut().unwrap(); + stream.clear_error_unlocked(); +} + +#[no_mangle] +pub unsafe extern "C" fn fileno_unlocked(stream: *mut FILE) -> c_int { + let stream = stream.as_mut().unwrap(); + match stream.as_raw_fd_unlocked() { + Ok(RawFd(fd)) => fd as _, + Err(_) => -1, + } +} + +#[no_mangle] +pub unsafe extern "C" fn fwrite_unlocked( + ptr: *mut c_void, + size: usize, + nmemb: usize, + stream: *mut FILE, +) -> usize { + if ptr.is_null() { + panic!(); + } + let stream = stream.as_mut().unwrap(); + let data = core::slice::from_raw_parts(ptr as *const u8, size * nmemb); + + stream.write_unlocked(data).into_size_status() / size +} + +#[no_mangle] +pub unsafe extern "C" fn fread_unlocked( + ptr: *mut c_void, + size: usize, + nmemb: usize, + stream: *mut FILE, +) -> usize { + if ptr.is_null() { + panic!(); + } + let stream = stream.as_mut().unwrap(); + let data = core::slice::from_raw_parts_mut(ptr as *mut u8, size * nmemb); + + stream.read_unlocked(data).into_size_status() / size +} + +#[no_mangle] +pub unsafe extern "C" fn fflush_unlocked(stream: *mut FILE) -> c_int { + if let Some(stream) = stream.as_mut() { + stream.flush_unlocked().into_eof_status() + } else { + super::fflush_all_unlocked().into_eof_status() + } +} + +/* +int getc_unlocked(FILE *stream); +int getchar_unlocked(void); +int putc_unlocked(int c, FILE *stream); +int putchar_unlocked(int c); + +int fgetc_unlocked(FILE *stream); +int fputc_unlocked(int c, FILE *stream); +*/ + +#[no_mangle] +pub unsafe extern "C" fn getc_unlocked(stream: *mut FILE) -> c_int { + todo!() +} diff --git a/src/header/stdlib/cbindgen.toml b/src/header/stdlib/cbindgen.toml index 4758ec7..1667693 100644 --- a/src/header/stdlib/cbindgen.toml +++ b/src/header/stdlib/cbindgen.toml @@ -6,7 +6,8 @@ no_includes = true include_guard = "_STDLIB_H" -usize_is_size_t = true +usize_type = "size_t" +isize_type = "ssize_t" [export] exclude = [] diff --git a/src/header/string/cbindgen.toml b/src/header/string/cbindgen.toml index f78711c..5220d5e 100644 --- a/src/header/string/cbindgen.toml +++ b/src/header/string/cbindgen.toml @@ -6,6 +6,7 @@ no_includes = true include_guard = "_STRING_H" -usize_is_size_t = true +usize_type = "size_t" +isize_type = "ssize_t" [export] diff --git a/src/header/sys_types/cbindgen.toml b/src/header/sys_types/cbindgen.toml index 4d45124..a298792 100644 --- a/src/header/sys_types/cbindgen.toml +++ b/src/header/sys_types/cbindgen.toml @@ -1,10 +1,30 @@ language = "C" style = "Type" -sys_includes = ["stddef.h"] +sys_includes = ["stdint.h", "stddef.h"] no_includes = true include_guard = "_SYS_TYPES_H" [export] -include = ["pid_t"] +include = [ + "blkcnt_t", + "blksize_t", + "clock_t", + "clockid_t", + "dev_t", + "fsblkcnt_t", + "fsfilcnt_t", + "gid_t", + "id_t", + "ino_t", + "mode_t", + "nlink_t", + "off_t", + "pid_t", + "ssize_t", + "suseconds_t", + "time_t", + "timer_t", + "uid_t" +] diff --git a/src/header/sys_types/mod.rs b/src/header/sys_types/mod.rs index bd17fce..bf1b57c 100644 --- a/src/header/sys_types/mod.rs +++ b/src/header/sys_types/mod.rs @@ -1,3 +1,31 @@ -use core::ffi::c_int; +use core::ffi::c_ulong; pub type pid_t = i32; +pub type uid_t = i32; +pub type gid_t = i32; +pub type id_t = i32; + +pub type dev_t = u32; + +pub type clockid_t = i32; +pub type timer_t = i32; + +pub type mode_t = u32; + +#[cfg(target_pointer_width = "64")] +pub type ssize_t = i64; + +pub type blkcnt_t = i64; +pub type blksize_t = c_ulong; +pub type nlink_t = u64; + +pub type fsblkcnt_t = u64; +pub type fsfilcnt_t = u64; +pub type ino_t = u64; + +pub type clock_t = u64; +pub type time_t = u64; + +pub type off_t = i64; + +pub type suseconds_t = c_ulong; diff --git a/src/header/sys_wait/cbindgen.toml b/src/header/sys_wait/cbindgen.toml index aa6e2e6..381cc16 100644 --- a/src/header/sys_wait/cbindgen.toml +++ b/src/header/sys_wait/cbindgen.toml @@ -6,7 +6,8 @@ no_includes = true include_guard = "_SYS_WAIT_H" -usize_is_size_t = true +usize_type = "size_t" +isize_type = "ssize_t" [export] exclude = [] diff --git a/src/header/unistd/cbindgen.toml b/src/header/unistd/cbindgen.toml index 97dcd36..58054db 100644 --- a/src/header/unistd/cbindgen.toml +++ b/src/header/unistd/cbindgen.toml @@ -7,7 +7,8 @@ no_includes = true include_guard = "_UNISTD_H" trailer = "#include " -usize_is_size_t = true +usize_type = "size_t" +isize_type = "ssize_t" [export] exclude = [] diff --git a/src/io/buffered.rs b/src/io/buffered.rs new file mode 100644 index 0000000..6c329ed --- /dev/null +++ b/src/io/buffered.rs @@ -0,0 +1,231 @@ +use core::{ + mem::MaybeUninit, + ops::{Deref, DerefMut}, + slice::memchr, +}; + +use alloc::{boxed::Box, vec::Vec}; + +use crate::header::errno::Errno; + +use super::{Read, Write}; + +// Read + +enum ReadBufferBacking<'a> { + Owned(Box<[MaybeUninit]>), + Borrowed(&'a mut [MaybeUninit]), +} + +pub struct ReadBuffer<'a> { + data: ReadBufferBacking<'a>, + position: usize, + len: usize, +} + +// Write + +pub trait FileWriter: Write { + fn reset(&mut self); +} + +pub struct UnbufferedWriter { + inner: W, +} + +pub struct BufWriter { + inner: W, + buffer: Vec, +} + +pub struct LineWriter { + inner: BufWriter, + need_flush: bool, +} + +impl<'a> ReadBuffer<'a> { + pub fn owned(capacity: usize) -> Self { + Self { + data: ReadBufferBacking::Owned(Box::new_uninit_slice(capacity)), + position: 0, + len: 0, + } + } + + pub fn borrowed(data: &'a mut [MaybeUninit]) -> Self { + Self { + data: ReadBufferBacking::Borrowed(data), + position: 0, + len: 0, + } + } + + pub fn reset(&mut self) { + self.position = 0; + self.len = 0; + } + + pub fn fill_from(&mut self, source: &mut R) -> Result<&[u8], Errno> { + let buffer = unsafe { self.data.as_slice_mut() }; + if self.position == self.len { + let amount = match source.read(buffer) { + Ok(0) => return Ok(&buffer[..0]), + Ok(n) => n, + Err(err) => return Err(err), + }; + + self.position = 0; + self.len = amount; + } + + Ok(&mut buffer[self.position..self.len]) + } + + pub fn consume(&mut self, amount: usize) { + self.position = (self.position + amount).min(self.len); + } +} + +impl<'a> ReadBufferBacking<'a> { + unsafe fn as_slice_mut(&mut self) -> &mut [u8] { + MaybeUninit::slice_assume_init_mut(self.deref_mut()) + } +} + +impl<'a> Deref for ReadBufferBacking<'a> { + type Target = [MaybeUninit]; + + fn deref(&self) -> &Self::Target { + match self { + Self::Owned(owned) => owned.deref(), + Self::Borrowed(borrowed) => borrowed.deref(), + } + } +} + +impl<'a> DerefMut for ReadBufferBacking<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + match self { + Self::Owned(owned) => owned.deref_mut(), + Self::Borrowed(borrowed) => borrowed.deref_mut(), + } + } +} + +impl UnbufferedWriter { + pub fn new(inner: W) -> Self { + Self { inner } + } +} + +impl BufWriter { + pub fn with_capacity(inner: W, capacity: usize) -> Self { + Self { + inner, + buffer: Vec::with_capacity(capacity), + } + } +} + +impl LineWriter { + pub fn with_capacity(inner: W, capacity: usize) -> Self { + Self { + inner: BufWriter::with_capacity(inner, capacity), + need_flush: false, + } + } +} + +impl Write for UnbufferedWriter { + fn write(&mut self, data: &[u8]) -> Result { + self.inner.write(data) + } + + fn flush(&mut self) -> Result<(), Errno> { + Ok(()) + } +} + +impl Write for BufWriter { + fn write(&mut self, mut data: &[u8]) -> Result { + if data.len() + self.buffer.len() > self.buffer.capacity() { + self.flush()?; + } + + let len = data.len(); + + if len >= self.buffer.capacity() { + // Write directly from `data` until it can be fit into the buffer + let (amount, result) = self + .inner + .write_chunked(&data[..len - self.buffer.capacity()]); + + // Fail if write fails + result?; + + data = &data[amount..]; + } + + // Store the data in the buffer + assert!(data.len() < self.buffer.capacity()); + self.buffer.extend_from_slice(data); + Ok(len) + } + + fn flush(&mut self) -> Result<(), Errno> { + let (amount, result) = self.inner.write_chunked(&self.buffer); + if amount != 0 { + self.buffer.drain(..amount); + } + result + } +} + +impl Write for LineWriter { + fn write(&mut self, data: &[u8]) -> Result { + if self.need_flush { + self.flush()?; + } + + let Some(nl_pos) = memchr::memrchr(b'\n', data) else { + return self.inner.write(data); + }; + + // Write up to the last newline and flush + let amount = self.inner.write(&data[..nl_pos + 1])?; + self.need_flush = true; + if self.flush().is_err() || amount != nl_pos + 1 { + return Ok(amount); + } + + // Write the rest to the buffer + match self.inner.write(&data[nl_pos + 1..]) { + Ok(amount_to_buffer) => Ok(amount_to_buffer + amount), + Err(_) => Ok(amount), + } + } + + fn flush(&mut self) -> Result<(), Errno> { + self.inner.flush()?; + self.need_flush = false; + Ok(()) + } +} + +impl FileWriter for UnbufferedWriter { + fn reset(&mut self) { + // Do nothing + } +} + +impl FileWriter for BufWriter { + fn reset(&mut self) { + self.buffer.clear(); + } +} + +impl FileWriter for LineWriter { + fn reset(&mut self) { + self.inner.reset(); + } +} diff --git a/src/io/mod.rs b/src/io/mod.rs new file mode 100644 index 0000000..d489978 --- /dev/null +++ b/src/io/mod.rs @@ -0,0 +1,51 @@ +use yggdrasil_rt::io::SeekFrom; + +use crate::header::errno::{Errno, EINTR}; + +pub mod buffered; + +pub trait Write { + fn write(&mut self, data: &[u8]) -> Result; + fn flush(&mut self) -> Result<(), Errno>; + + fn write_chunked(&mut self, data: &[u8]) -> (usize, Result<(), Errno>) { + let mut pos = 0; + let len = data.len(); + + while pos < len { + match self.write(data) { + Ok(0) => todo!(), + Ok(n) => pos += n, + Err(err) if err == EINTR => todo!(), + Err(err) => return (pos, Err(err)), + } + } + + (pos, Ok(())) + } + + fn write_all(&mut self, data: &[u8]) -> Result<(), Errno> { + match self.write(data) { + Ok(n) if n == data.len() => Ok(()), + Ok(_) => todo!(), // TODO handle partial writes in write_all + Err(err) => Err(err), + } + } +} + +pub trait Read { + fn read(&mut self, data: &mut [u8]) -> Result; +} + +pub trait BufRead { + fn fill_buf(&mut self) -> Result<&[u8], Errno>; + fn consume(&mut self, amount: usize); +} + +pub trait Seek { + fn seek(&mut self, off: SeekFrom) -> Result; + + fn stream_position(&mut self) -> Result { + self.seek(SeekFrom::Current(0)) + } +} diff --git a/src/lib.rs b/src/lib.rs index 2e00e84..5e8f4a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,12 @@ -#![feature(type_alias_impl_trait, c_variadic, try_trait_v2)] +#![feature( + type_alias_impl_trait, + c_variadic, + try_trait_v2, + slice_internals, + arbitrary_self_types, + new_uninit, + maybe_uninit_slice +)] #![no_std] use core::ffi::{c_char, c_int}; @@ -7,6 +15,8 @@ use alloc::{ffi::CString, vec::Vec}; use header::unistd::environ; use yggdrasil_rt::process::{ExitCode, ProgramArgumentInner}; +use crate::header::stdio; + extern crate alloc; extern crate yggdrasil_rt; @@ -14,10 +24,11 @@ pub mod header; pub mod allocator; pub mod error; +pub mod file; +pub mod io; pub mod path; pub mod process; pub mod sync; -pub mod traits; pub mod types; pub mod util; @@ -42,6 +53,10 @@ unsafe fn setup_env(arg: usize) { environ = C_ENVS.as_mut_ptr(); } +unsafe fn cleanup() { + stdio::cleanup(); +} + #[no_mangle] unsafe extern "C" fn _start(arg: usize) -> ! { extern "C" { @@ -49,12 +64,14 @@ unsafe extern "C" fn _start(arg: usize) -> ! { } setup_env(arg); - header::stdio::setup_default_files(); + stdio::setup_default_files(); // TODO setup signals, allocator, etc. let code = main(C_ARGS.len() as _, C_ARGS.as_ptr()); + cleanup(); + process::exit(code) } diff --git a/src/sync.rs b/src/sync.rs index 4072fec..dd78b57 100644 --- a/src/sync.rs +++ b/src/sync.rs @@ -87,6 +87,8 @@ impl Mutex { } } +unsafe impl Sync for Mutex {} + impl Deref for MutexGuard<'_, T> { type Target = T; diff --git a/src/traits.rs b/src/traits.rs deleted file mode 100644 index 62d9013..0000000 --- a/src/traits.rs +++ /dev/null @@ -1,16 +0,0 @@ -use core::fmt; - -use crate::header::errno::Errno; - -pub trait Write: fmt::Write { - fn write(&mut self, data: &[u8]) -> Result; - - fn putc(&mut self, ch: u8) -> Result<(), Errno> { - self.write(&[ch])?; - Ok(()) - } -} - -pub trait Read { - fn read(&mut self, data: &mut [u8]) -> Result; -} diff --git a/src/types.rs b/src/types.rs index e4cf0b2..747a85c 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,5 +1,3 @@ #![allow(non_camel_case_types)] -use core::ffi::c_int; - pub type wchar_t = i32;