0.3.0: converge relibc to upstream 0.6.0 + Red Bear patches

This commit is contained in:
2026-07-06 19:13:08 +03:00
parent 1a0edd8eeb
commit 4ef7e57571
1466 changed files with 75236 additions and 13644 deletions
+21 -2
View File
@@ -1,6 +1,25 @@
sys_includes = ["stdarg.h", "stddef.h", "stdint.h", "sys/types.h"]
sys_includes = ["stdarg.h", "stddef.h", "stdint.h", "sys/types.h", "features.h"]
include_guard = "_RELIBC_STDIO_H"
trailer = "#include <bits/stdio.h>"
trailer = """
#ifndef _RELIBC_BITS_STDIO_H
#define _RELIBC_BITS_STDIO_H
// XXX: this is only here because cbindgen can't handle string constants
#define P_tmpdir "/tmp"
// TODO: figure out why this duplicate is needed to compile libiconv
typedef struct FILE FILE;
// A typedef doesn't suffice, because libgmp uses this definition to check if
// STDIO was loaded.
#define FILE FILE
// Likewise, stdin, stdout, and stderr are expected to be macros.
#define stdin stdin
#define stdout stdout
#define stderr stderr
#endif // _RELIBC_BITS_STDIO_H
"""
language = "C"
style = "Type"
no_includes = true
+15 -2
View File
@@ -1,4 +1,4 @@
use crate::platform::types::*;
use crate::platform::types::{c_int, c_uint, int32_t, off_t};
pub const EOF: c_int = -1;
pub const BUFSIZ: c_int = 1024;
@@ -24,13 +24,26 @@ pub const _IOFBF: c_int = 0;
pub const _IOLBF: c_int = 1;
pub const _IONBF: c_int = 2;
// renameat2
// (uint instead of int is intentional)
/// Rename but don't replace the target if it exists.
pub const RENAME_NOREPLACE: c_uint = 0x01;
/// Atomically swap two files.
#[cfg(target_os = "linux")]
pub const RENAME_EXCHANGE: c_uint = 0x02;
#[cfg(target_os = "linux")]
pub const RENAME_WHITEOUT: c_uint = 0x04;
// /dev/tty + nul
pub const L_ctermid: usize = 9;
// form of name is /XXXXXX, so 7
pub const L_tmpnam: c_int = 7;
// 36^6 (26 letters + 10 digits) is larger than i32::MAX, so just set to that
// for now
pub const TMP_MAX: int32_t = 2_147_483_647;
// XXX: defined manually in bits/stdio.h as well because cbindgen can't handle
// XXX: defined manually in cbindgen as well because it can't handle
// string constants in any form AFAICT
/// cbindgen:ignore
pub const P_tmpdir: &[u8; 5] = b"/tmp\0";
#[allow(non_camel_case_types)]
+26 -16
View File
@@ -1,16 +1,22 @@
use super::{constants, Buffer, BUFSIZ, FILE};
use super::{BUFSIZ, Buffer, FILE, constants};
use core::{cell::UnsafeCell, ptr};
use crate::{fs::File, io::LineWriter, platform::types::*, sync::Mutex};
use alloc::vec::Vec;
use crate::{fs::File, header::pthread, io::LineWriter, platform::types::c_int, sync::Once};
use alloc::{boxed::Box, vec::Vec};
// TODO: Change FILE to allow const fn initialization?
pub struct GlobalFile(UnsafeCell<FILE>);
impl GlobalFile {
fn new(file: c_int, flags: c_int) -> Self {
let file = File::new(file);
let writer = LineWriter::new(unsafe { file.get_ref() });
let writer = Box::new(LineWriter::new(unsafe { file.get_ref() }));
let mutex_attr = pthread::RlctMutexAttr {
ty: pthread::PTHREAD_MUTEX_RECURSIVE,
..Default::default()
};
GlobalFile(UnsafeCell::new(FILE {
lock: Mutex::new(()),
lock: pthread::RlctMutex::new(&mutex_attr).unwrap(),
file,
flags: constants::F_PERM | flags,
@@ -32,20 +38,24 @@ impl GlobalFile {
// statics need to be Sync
unsafe impl Sync for GlobalFile {}
lazy_static! {
#[allow(non_upper_case_globals)]
pub static ref default_stdin: GlobalFile = GlobalFile::new(0, constants::F_NOWR);
// TODO: Allow const fn initialization of FILE
static DEFAULT_STDIN: Once<GlobalFile> = Once::new();
static DEFAULT_STDOUT: Once<GlobalFile> = Once::new();
static DEFAULT_STDERR: Once<GlobalFile> = Once::new();
#[allow(non_upper_case_globals)]
pub static ref default_stdout: GlobalFile = GlobalFile::new(1, constants::F_NORD);
#[allow(non_upper_case_globals)]
pub static ref default_stderr: GlobalFile = GlobalFile::new(2, constants::F_NORD);
pub fn default_stdin() -> &'static GlobalFile {
DEFAULT_STDIN.call_once(|| GlobalFile::new(0, constants::F_NOWR))
}
pub fn default_stdout() -> &'static GlobalFile {
DEFAULT_STDOUT.call_once(|| GlobalFile::new(1, constants::F_NORD))
}
pub fn default_stderr() -> &'static GlobalFile {
DEFAULT_STDERR.call_once(|| GlobalFile::new(2, constants::F_NORD))
}
#[no_mangle]
#[unsafe(no_mangle)]
pub static mut stdin: *mut FILE = ptr::null_mut();
#[no_mangle]
#[unsafe(no_mangle)]
pub static mut stdout: *mut FILE = ptr::null_mut();
#[no_mangle]
#[unsafe(no_mangle)]
pub static mut stderr: *mut FILE = ptr::null_mut();
+26 -12
View File
@@ -1,41 +1,55 @@
use crate::{
header::stdio::{FILE, F_NORD, F_NOWR},
platform::types::*,
header::stdio::{F_ERR, F_NORD, F_NOWR, FILE},
platform::types::{c_int, size_t},
};
#[no_mangle]
#[unsafe(no_mangle)]
pub extern "C" fn __freadahead(stream: *mut FILE) -> size_t {
let stream = unsafe { &mut *stream }.lock();
(stream.read_size.saturating_sub(stream.read_pos) + stream.unget.len()) as size_t
}
#[unsafe(no_mangle)]
pub extern "C" fn __fpending(stream: *mut FILE) -> size_t {
let stream = unsafe { &mut *stream }.lock();
stream.writer.inner.buf.len() as size_t
stream.writer.pending()
}
#[no_mangle]
#[unsafe(no_mangle)]
pub extern "C" fn __freadable(stream: *mut FILE) -> c_int {
let stream = unsafe { &mut *stream }.lock();
(stream.flags & F_NORD == 0) as c_int
c_int::from(stream.flags & F_NORD == 0)
}
#[no_mangle]
#[unsafe(no_mangle)]
pub extern "C" fn __fwritable(stream: *mut FILE) -> c_int {
let stream = unsafe { &mut *stream }.lock();
(stream.flags & F_NOWR == 0) as c_int
c_int::from(stream.flags & F_NOWR == 0)
}
#[unsafe(no_mangle)]
pub extern "C" fn __fseterr(stream: *mut FILE) {
let mut stream = unsafe { &mut *stream }.lock();
stream.flags |= F_ERR;
}
//TODO: Check last operation when read-write
#[no_mangle]
#[unsafe(no_mangle)]
pub extern "C" fn __freading(stream: *mut FILE) -> c_int {
let stream = unsafe { &mut *stream }.lock();
(stream.flags & F_NORD == 0) as c_int
c_int::from(stream.flags & F_NORD == 0)
}
//TODO: Check last operation when read-write
#[no_mangle]
#[unsafe(no_mangle)]
pub extern "C" fn __fwriting(stream: *mut FILE) -> c_int {
let stream = unsafe { &mut *stream }.lock();
(stream.flags & F_NOWR == 0) as c_int
c_int::from(stream.flags & F_NOWR == 0)
}
+107 -17
View File
@@ -1,31 +1,80 @@
// See <https://pubs.opengroup.org/onlinepubs/9799919799/functions/getline.html>.
use alloc::vec::Vec;
use core::ptr;
use core::{intrinsics::unlikely, ptr};
use crate::{
header::{stdio::FILE, stdlib},
header::{
errno::{EINVAL, ENOMEM, EOVERFLOW},
stdio::FILE,
stdlib,
},
io::BufRead,
platform::types::*,
platform::types::{c_char, c_int, c_void, size_t, ssize_t},
};
#[no_mangle]
pub unsafe extern "C" fn __getline(
use crate::{
header::stdio::{F_EOF, F_ERR, feof, ferror},
platform::ERRNO,
};
/// see getdelim (getline is a special case of getdelim with delim == '\n')
#[unsafe(no_mangle)]
pub unsafe extern "C" fn getline(
lineptr: *mut *mut c_char,
n: *mut size_t,
stream: *mut FILE,
) -> ssize_t {
__getdelim(lineptr, n, b'\n' as c_int, stream)
unsafe { getdelim(lineptr, n, c_int::from(b'\n'), stream) }
}
#[no_mangle]
pub unsafe extern "C" fn __getdelim(
// One *could* read the standard as 'getdelim sets the stream error flag on *any* error, though
// since glibc doesn't seem to do this, I won't either
/// <https://pubs.opengroup.org/onlinepubs/9799919799/functions/getline.html>
///
/// # Safety
/// - `lineptr, *lineptr, `n`, `stream` pointers must be valid and have to be aligned.
/// - `stream` has to be a valid file handle returned by fopen and likes.
///
/// # Deviation from POSIX
/// - **EINVAL is set on stream being NULL or delim not fitting into char** (POSIX allows UB)
/// - **`*n` can contain invalid data.** The buffer size `n` is not read, instead realloc is called each time. That is in principle
/// inefficent since the buffer is reallocated in memory for every call, but if `n` is by mistake
/// bigger than the number of bytes allocated for the buffer, there can be no out-of-bounds write.
/// - On non-stream-related errors, the error indicator of the stream is *not* set. Posix states
/// "If an error occurs, the error indicator for the stream shall be set, and the function shall
/// return -1 and set errno to indicate the error." but in cases that produce EINVAL even glibc
/// doesn't seem to set the error indicator, so we also don't.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn getdelim(
lineptr: *mut *mut c_char,
n: *mut size_t,
delim: c_int,
stream: *mut FILE,
) -> ssize_t {
let lineptr = &mut *lineptr;
let n = &mut *n;
let delim = delim as u8;
let (lineptr, n, stream) = if let (Some(ptr), Some(n), Some(file)) =
(unsafe { lineptr.as_mut() }, unsafe { n.as_mut() }, unsafe {
stream.as_mut()
}) {
(ptr, n, file)
} else {
ERRNO.set(EINVAL);
return -1 as ssize_t;
};
if unsafe { feof(stream) } != 0 || unsafe { ferror(stream) } != 0 {
return -1 as ssize_t;
}
// POSIX specifies UB but we test anyway
// returning EINVAL in that case
let delim: u8 = if let Ok(delim) = delim.try_into() {
delim
} else {
ERRNO.set(EINVAL);
return -1;
};
//TODO: More efficient algorithm using lineptr and n instead of this vec
let mut buf = Vec::new();
@@ -33,23 +82,64 @@ pub unsafe extern "C" fn __getdelim(
let mut stream = (*stream).lock();
match stream.read_until(delim, &mut buf) {
Ok(ok) => ok,
Err(err) => return -1,
Err(err) => {
stream.flags &= F_ERR;
return -1;
}
}
};
// "[EOVERFLOW]
// The number of bytes to be written into the buffer, including the delimiter character (if encountered), would exceed {SSIZE_MAX}."
if unlikely(count > ssize_t::MAX as usize) {
ERRNO.set(EOVERFLOW);
return -1;
}
// we reached EOF if either
// - we have no last elem (because vec is empty), or
// - the last elem doesn't match the delimiter
let eof_reached = if let Some(last) = buf.last() {
*last == delim
} else {
true
};
// "If the end-of-file indicator for the stream is set, or if no characters were read and the
// stream is at end-of-file, the end-of-file indicator for the stream shall be set and the
// function shall return -1."
if eof_reached {
stream.flags &= F_EOF;
if count == 0 {
return -1;
}
}
//TODO: Check errors and improve safety
{
// Allocate lineptr to size of buf and set n to size of lineptr
// Allocate lineptr to size of buf plus NUL byte and set n to size of lineptr
*n = count + 1;
*lineptr = stdlib::realloc(*lineptr as *mut c_void, *n) as *mut c_char;
// The advantage in always realloc'ing is that even if the user supplies a wrong n, this
// doesn't break
*lineptr = unsafe { stdlib::realloc((*lineptr).cast::<c_void>(), *n) }.cast::<c_char>();
if unlikely(lineptr.is_null() && *n != 0usize) {
// memory error; realloc returns NULL on alloc'ing 0 bytes
ERRNO.set(ENOMEM);
return -1;
}
// Copy buf to lineptr
ptr::copy(buf.as_ptr(), *lineptr as *mut u8, count);
unsafe { ptr::copy(buf.as_ptr(), (*lineptr).cast::<u8>(), count) };
// NUL terminate lineptr
*lineptr.offset(count as isize) = 0;
unsafe { *lineptr.add(count) = 0 };
// TODO remove
/*eprintln!(
"[DBG]{}: {}, {:?}, {:?}, {:?}", line!(),
String::from_utf8(buf).unwrap(), count, *n, *lineptr
);*/
// Return allocated size
*n as ssize_t
count as ssize_t
}
}
+44 -29
View File
@@ -1,36 +1,47 @@
use alloc::boxed::Box;
use super::{constants::*, Buffer, FILE};
use super::{
Buffer, FILE,
constants::{BUFSIZ, F_APP, F_NORD, F_NOWR},
};
use crate::{
c_str::CStr,
error::Errno,
fs::File,
header::{errno, fcntl::*, string::strchr},
io::LineWriter,
platform::{self, types::*},
sync::Mutex,
header::{
errno::EINVAL,
fcntl::{
F_GETFL, F_SETFD, F_SETFL, FD_CLOEXEC, O_APPEND, O_CLOEXEC, O_CREAT, O_EXCL, O_RDONLY,
O_RDWR, O_TRUNC, O_WRONLY, fcntl,
},
pthread,
},
io::BufWriter,
platform::types::{c_int, c_ulonglong},
};
use alloc::vec::Vec;
/// Parse mode flags as a string and output a mode flags integer
pub unsafe fn parse_mode_flags(mode_str: *const c_char) -> i32 {
let mut flags = if !strchr(mode_str, b'+' as i32).is_null() {
pub fn parse_mode_flags(mode_str: CStr) -> i32 {
let mut flags = if mode_str.contains(b'+') {
O_RDWR
} else if (*mode_str) == b'r' as i8 {
} else if mode_str.first() == b'r' {
O_RDONLY
} else {
O_WRONLY
};
if !strchr(mode_str, b'x' as i32).is_null() {
if mode_str.contains(b'x') {
flags |= O_EXCL;
}
if !strchr(mode_str, b'e' as i32).is_null() {
if mode_str.contains(b'e') {
flags |= O_CLOEXEC;
}
if (*mode_str) != b'r' as i8 {
if mode_str.first() != b'r' {
flags |= O_CREAT;
}
if (*mode_str) == b'w' as i8 {
if mode_str.first() == b'w' {
flags |= O_TRUNC;
} else if (*mode_str) == b'a' as i8 {
} else if mode_str.first() == b'a' {
flags |= O_APPEND;
}
@@ -38,34 +49,38 @@ pub unsafe fn parse_mode_flags(mode_str: *const c_char) -> i32 {
}
/// Open a file with the file descriptor `fd` in the mode `mode`
pub unsafe fn _fdopen(fd: c_int, mode: *const c_char) -> Option<*mut FILE> {
if *mode != b'r' as i8 && *mode != b'w' as i8 && *mode != b'a' as i8 {
platform::errno = errno::EINVAL;
return None;
pub fn _fdopen(fd: c_int, mode: CStr) -> Result<Box<FILE>, Errno> {
if mode.first() != b'r' && mode.first() != b'w' && mode.first() != b'a' {
return Err(Errno(EINVAL));
}
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 !mode.contains(b'+') {
flags |= if mode.first() == b'r' { F_NOWR } else { F_NORD };
}
if !strchr(mode, b'e' as i32).is_null() {
sys_fcntl(fd, F_SETFD, FD_CLOEXEC);
if mode.contains(b'e') {
unsafe {
fcntl(fd, F_SETFD, FD_CLOEXEC as c_ulonglong);
}
}
if *mode == 'a' as i8 {
let f = sys_fcntl(fd, F_GETFL, 0);
if mode.first() == b'a' {
let f = unsafe { fcntl(fd, F_GETFL, 0) };
if (f & O_APPEND) == 0 {
sys_fcntl(fd, F_SETFL, f | O_APPEND);
unsafe { fcntl(fd, F_SETFL, (f | O_APPEND) as c_ulonglong) };
}
flags |= F_APP;
}
let file = File::new(fd);
let writer = LineWriter::new(file.get_ref());
Some(Box::into_raw(Box::new(FILE {
lock: Mutex::new(()),
let writer = Box::new(BufWriter::new(unsafe { file.get_ref() }));
let mutex_attr = pthread::RlctMutexAttr {
ty: pthread::PTHREAD_MUTEX_RECURSIVE,
..Default::default()
};
Ok(Box::new(FILE {
lock: pthread::RlctMutex::new(&mutex_attr).unwrap(),
file,
flags,
@@ -78,5 +93,5 @@ pub unsafe fn _fdopen(fd: c_int, mode: *const c_char) -> Option<*mut FILE> {
pid: None,
orientation: 0,
})))
}))
}
-99
View File
@@ -1,99 +0,0 @@
use super::{fseek_locked, ftell_locked, FILE, SEEK_SET};
use crate::core_io::Read;
struct LookAheadBuffer {
buf: *const u8,
pos: isize,
look_ahead: isize,
}
impl LookAheadBuffer {
fn look_ahead(&mut self) -> Result<Option<u8>, i32> {
let byte = unsafe { *self.buf.offset(self.look_ahead) };
if byte == 0 {
Ok(None)
} else {
self.look_ahead += 1;
Ok(Some(byte))
}
}
fn commit(&mut self) {
self.pos = self.look_ahead;
}
}
impl From<*const u8> for LookAheadBuffer {
fn from(buff: *const u8) -> LookAheadBuffer {
LookAheadBuffer {
buf: buff,
pos: 0,
look_ahead: 0,
}
}
}
struct LookAheadFile<'a> {
f: &'a mut FILE,
look_ahead: i64,
}
impl<'a> LookAheadFile<'a> {
fn look_ahead(&mut self) -> Result<Option<u8>, i32> {
let buf = &mut [0];
let seek = unsafe { ftell_locked(self.f) };
unsafe { fseek_locked(self.f, self.look_ahead, SEEK_SET) };
let ret = match self.f.read(buf) {
Ok(0) => Ok(None),
Ok(_) => Ok(Some(buf[0])),
Err(_) => Err(-1),
};
unsafe { fseek_locked(self.f, seek, SEEK_SET) };
self.look_ahead += 1;
ret
}
fn commit(&mut self) {
unsafe { fseek_locked(self.f, self.look_ahead, SEEK_SET) };
}
}
impl<'a> From<&'a mut FILE> for LookAheadFile<'a> {
fn from(f: &'a mut FILE) -> LookAheadFile<'a> {
let look_ahead = unsafe { ftell_locked(f) } as i64;
LookAheadFile { f, look_ahead }
}
}
enum LookAheadReaderEnum<'a> {
FILE(LookAheadFile<'a>),
// (buffer, location)
BUFFER(LookAheadBuffer),
}
pub struct LookAheadReader<'a>(LookAheadReaderEnum<'a>);
impl<'a> LookAheadReader<'a> {
pub fn lookahead1(&mut self) -> Result<Option<u8>, i32> {
match &mut self.0 {
LookAheadReaderEnum::FILE(f) => f.look_ahead(),
LookAheadReaderEnum::BUFFER(b) => b.look_ahead(),
}
}
pub fn commit(&mut self) {
match &mut self.0 {
LookAheadReaderEnum::FILE(f) => f.commit(),
LookAheadReaderEnum::BUFFER(b) => b.commit(),
}
}
}
impl<'a> From<&'a mut FILE> for LookAheadReader<'a> {
fn from(f: &'a mut FILE) -> LookAheadReader {
LookAheadReader(LookAheadReaderEnum::FILE(f.into()))
}
}
impl<'a> From<*const u8> for LookAheadReader<'a> {
fn from(buff: *const u8) -> LookAheadReader<'a> {
LookAheadReader(LookAheadReaderEnum::BUFFER(buff.into()))
}
}
+779 -319
View File
File diff suppressed because it is too large Load Diff
+124
View File
@@ -0,0 +1,124 @@
use alloc::{boxed::Box, vec, vec::Vec};
use core::ptr;
use super::{
Buffer, FILE,
constants::{BUFSIZ, F_NORD},
};
use crate::{
error::{Errno, ResultExtPtrMut},
fs::File,
header::{
errno::{EFAULT, ENOMEM},
fcntl, pthread, stdlib, unistd,
},
io::{self, BufWriter, Write},
platform::{
ERRNO,
types::{c_char, size_t},
},
};
struct MemstreamWriter {
bufp: *mut *mut c_char,
sizep: *mut size_t,
current: *mut c_char,
buffer: Vec<u8>,
}
unsafe impl Send for MemstreamWriter {}
impl MemstreamWriter {
fn new(bufp: *mut *mut c_char, sizep: *mut size_t) -> Self {
Self {
bufp,
sizep,
current: ptr::null_mut(),
buffer: Vec::new(),
}
}
fn sync_output(&mut self) -> io::Result<()> {
let size = self.buffer.len();
let alloc_size = size
.checked_add(1)
.ok_or_else(|| io::Error::from_raw_os_error(ENOMEM))?;
let raw = if self.current.is_null() {
unsafe { stdlib::malloc(alloc_size) }
} else {
unsafe { stdlib::realloc(self.current.cast(), alloc_size) }
};
if raw.is_null() {
return Err(io::Error::from_raw_os_error(ENOMEM));
}
let raw = raw.cast::<c_char>();
if size != 0 {
unsafe { ptr::copy_nonoverlapping(self.buffer.as_ptr(), raw.cast::<u8>(), size) };
}
unsafe {
*raw.add(size) = 0;
*self.bufp = raw;
*self.sizep = size;
}
self.current = raw;
Ok(())
}
}
impl Write for MemstreamWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.buffer
.try_reserve(buf.len())
.map_err(|_| io::Error::from_raw_os_error(ENOMEM))?;
self.buffer.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
self.sync_output()
}
}
fn create_memstream(bufp: *mut *mut c_char, sizep: *mut size_t) -> Result<Box<FILE>, Errno> {
if bufp.is_null() || sizep.is_null() {
return Err(Errno(EFAULT));
}
unsafe {
*bufp = ptr::null_mut();
*sizep = 0;
}
let mut fds = [0; 2];
if unsafe { unistd::pipe2(fds.as_mut_ptr(), fcntl::O_CLOEXEC) } != 0 {
return Err(Errno(ERRNO.get()));
}
let _ = unistd::close(fds[0]);
let file = File::new(fds[1]);
let writer = Box::new(BufWriter::new(MemstreamWriter::new(bufp, sizep)));
let mutex_attr = pthread::RlctMutexAttr {
ty: pthread::PTHREAD_MUTEX_RECURSIVE,
..Default::default()
};
Ok(Box::new(FILE {
lock: pthread::RlctMutex::new(&mutex_attr).unwrap(),
file,
flags: F_NORD,
read_buf: Buffer::Owned(vec![0; BUFSIZ as usize]),
read_pos: 0,
read_size: 0,
unget: Vec::new(),
writer,
pid: None,
orientation: 0,
}))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn open_memstream(bufp: *mut *mut c_char, sizep: *mut size_t) -> *mut FILE {
create_memstream(bufp, sizep).or_errno_null_mut()
}
+765 -304
View File
File diff suppressed because it is too large Load Diff
+180
View File
@@ -0,0 +1,180 @@
use super::{FILE, SEEK_SET, fseek_locked, ftell_locked};
use crate::{
c_str::{CStr, Kind, NulStr, Thin, WStr, Wide},
header::{
errno::EILSEQ,
stdlib::MB_CUR_MAX,
wchar::{get_char_encoded_length, mbrtowc},
wctype::WEOF,
},
io::Read,
platform::{
ERRNO,
types::{c_char, off_t, wchar_t, wint_t},
},
};
use core::{
iter::Iterator,
marker::PhantomData,
ptr::{self},
};
pub struct BufferReader<'a, T: Kind> {
buf: NulStr<'a, T>,
}
impl<'a> From<WStr<'a>> for BufferReader<'a, Wide> {
fn from(buff: WStr<'a>) -> Self {
BufferReader { buf: buff }
}
}
impl<'a> From<CStr<'a>> for BufferReader<'a, Thin> {
fn from(buff: CStr<'a>) -> Self {
BufferReader { buf: buff }
}
}
impl<'a, T: Kind> Iterator for BufferReader<'a, T> {
type Item = Result<T::Char, i32>;
fn next(&mut self) -> Option<Self::Item> {
self.buf.split_first().map(|(c, r)| {
self.buf = r;
Ok(c)
})
}
}
pub struct FileReader<'a, T: Kind> {
f: &'a mut FILE,
position: off_t,
phantom: PhantomData<T>,
}
impl<'a, T: Kind> FileReader<'a, T> {
// Gets the wchar at the current position
#[inline]
fn get_curret_char(&mut self) -> Result<Option<(T::Char, usize)>, i32> {
if T::IS_THIN_NOT_WIDE {
let mut buf: [u8; 1] = [0];
match self.f.read(&mut buf) {
Ok(0) => Ok(None),
Ok(n) => Ok(Some((T::Char::from(buf[0]), n))),
Err(_) => Err(-1),
}
} else {
let buf = &mut [0; MB_CUR_MAX as usize];
let mut encoded_length = 0;
let mut bytes_read = 0;
loop {
match self.f.read(&mut buf[bytes_read..bytes_read + 1]) {
Ok(0) => return Ok(None),
Ok(_) => {}
Err(_) => return Err(-1),
}
bytes_read += 1;
if bytes_read == 1 {
encoded_length = if let Some(el) = get_char_encoded_length(buf[0]) {
el
} else {
ERRNO.set(EILSEQ);
return Self::get_char_from_wint(WEOF).map(|c| Some((c, 0)));
};
}
if bytes_read >= encoded_length {
break;
}
}
let mut wc: wchar_t = 0;
unsafe {
mbrtowc(
&raw mut wc,
buf.as_ptr().cast::<c_char>(),
encoded_length,
ptr::null_mut(),
);
}
Self::get_char_from_wint(wc as wint_t).map(|c| Some((c, encoded_length)))
}
}
fn get_char_from_wint(wc: wint_t) -> Result<T::Char, i32> {
if let Some(wc_char) = T::chars_from_bytes(&wc.to_be_bytes())
&& wc_char.len() == 1
{
Ok(wc_char[0])
} else {
Err(-1)
}
}
}
impl<'a, T: Kind> Iterator for FileReader<'a, T> {
type Item = Result<T::Char, i32>;
fn next(&mut self) -> Option<Self::Item> {
unsafe { fseek_locked(self.f, self.position, SEEK_SET) };
match self.get_curret_char() {
Ok(Some((wc, encoded_length))) => {
unsafe { fseek_locked(self.f, self.position, SEEK_SET) };
self.position += encoded_length as off_t;
Some(Ok(wc))
}
Ok(None) => None,
Err(e) => Some(Err(e)),
}
}
}
impl<'a, T: Kind> From<&'a mut FILE> for FileReader<'a, T> {
fn from(f: &'a mut FILE) -> Self {
let position = unsafe { ftell_locked(f) } as off_t;
FileReader {
f,
position,
phantom: PhantomData::<T>,
}
}
}
pub enum Reader<'a, T: Kind> {
FILE(FileReader<'a, T>, PhantomData<T>),
BUFFER(BufferReader<'a, T>),
}
impl<'a, T: Kind> Iterator for Reader<'a, T> {
type Item = Result<T::Char, i32>;
fn next(&mut self) -> Option<Self::Item> {
match self {
Self::FILE(r, _) => r.next(),
Self::BUFFER(r) => r.next(),
}
}
}
impl<'a, T: Kind> From<&'a mut FILE> for Reader<'a, T> {
fn from(f: &'a mut FILE) -> Self {
Self::FILE(f.into(), PhantomData::<T>)
}
}
impl<'a> From<WStr<'a>> for Reader<'a, Wide> {
fn from(buff: WStr<'a>) -> Self {
Self::BUFFER(buff.into())
}
}
impl<'a> From<CStr<'a>> for Reader<'a, Thin> {
fn from(buff: CStr<'a>) -> Self {
Self::BUFFER(buff.into())
}
}
+196 -145
View File
@@ -1,51 +1,56 @@
use super::lookaheadreader::LookAheadReader;
use crate::platform::types::*;
use super::reader::Reader;
use crate::{
c_str::Kind,
header::stdio::printf::IntKind,
platform::types::{
c_char, c_double, c_float, c_int, c_long, c_longlong, c_short, c_uchar, c_uint, c_ulong,
c_ulonglong, c_ushort, c_void, intmax_t, ptrdiff_t, size_t, ssize_t, uintmax_t, wchar_t,
},
};
use alloc::{string::String, vec::Vec};
use core::ffi::VaList as va_list;
use core::{ffi::VaList as va_list, iter::Peekable};
#[derive(PartialEq, Eq)]
enum IntKind {
Byte,
Short,
Int,
Long,
LongLong,
IntMax,
PtrDiff,
Size,
enum CharKind {
Ascii,
Wide,
}
/// Helper function for progressing a C string
unsafe fn next_byte(string: &mut *const c_char) -> Result<u8, c_int> {
let c = **string as u8;
*string = string.offset(1);
if c == 0 {
Err(-1)
fn next_char<T: Kind>(lar: &mut Peekable<Reader<'_, T>>) -> Result<char, c_int> {
if let Some(c) = lar.next().transpose()? {
char::try_from(c.into()).map_err(|_| -1)
} else {
Ok(c)
Ok('\0')
}
}
unsafe fn inner_scanf(
mut r: LookAheadReader,
mut format: *const c_char,
macro_rules! wc_as_char {
($c:ident) => {
char::try_from($c.into()).map_err(|_| -1)?
};
}
pub unsafe fn inner_scanf<T: Kind>(
mut r: Reader<T>,
format: Reader<T>,
mut ap: va_list,
) -> Result<c_int, c_int> {
let mut matched = 0;
let mut byte = 0;
let mut character: char = '\0';
let mut skip_read = false;
let mut count = 0;
let mut format = format.peekable();
macro_rules! read {
() => {{
match r.lookahead1() {
Ok(None) => false,
Ok(Some(b)) => {
byte = b;
match r.next() {
None => false,
Some(Ok(b)) => {
character = wc_as_char!(b);
count += 1;
true
}
Err(x) => return Err(x),
Some(Err(x)) => return Err(x),
}
}};
}
@@ -71,38 +76,37 @@ unsafe fn inner_scanf(
}
}
while *format != 0 {
let mut c = next_byte(&mut format)?;
while format.peek().is_some() {
let mut c = next_char(&mut format)?;
if c == b' ' {
if c == ' ' {
maybe_read!(noreset);
while (byte as char).is_whitespace() {
while (character).is_whitespace() {
if !read!() {
return Ok(matched);
}
}
skip_read = true;
} else if c != b'%' {
} else if c != '%' {
maybe_read!();
if c != byte {
if c != character {
return Ok(matched);
}
r.commit();
} else {
c = next_byte(&mut format)?;
c = next_char(&mut format)?;
let mut ignore = false;
if c == b'*' {
if c == '*' {
ignore = true;
c = next_byte(&mut format)?;
c = next_char(&mut format)?;
}
let mut width = String::new();
while c >= b'0' && c <= b'9' {
width.push(c as char);
c = next_byte(&mut format)?;
while c.is_ascii_digit() {
width.push(c);
c = next_char(&mut format)?;
}
let mut width = if width.is_empty() {
None
@@ -118,65 +122,72 @@ unsafe fn inner_scanf(
let mut eof = false;
let mut kind = IntKind::Int;
let mut c_kind = CharKind::Ascii;
loop {
kind = match c {
b'h' => {
match c {
'h' => {
if kind == IntKind::Short || kind == IntKind::Byte {
IntKind::Byte
kind = IntKind::Byte;
} else {
IntKind::Short
kind = IntKind::Short;
}
}
b'j' => IntKind::IntMax,
b'l' => {
'j' => kind = IntKind::IntMax,
'l' => {
if kind == IntKind::Long || kind == IntKind::LongLong {
IntKind::LongLong
kind = IntKind::LongLong;
} else {
IntKind::Long
kind = IntKind::Long;
}
}
b'q' | b'L' => IntKind::LongLong,
b't' => IntKind::PtrDiff,
b'z' => IntKind::Size,
'q' | 'L' => kind = IntKind::LongLong,
't' => kind = IntKind::PtrDiff,
'z' => kind = IntKind::Size,
// If kind is Long, means we found a 'l' before finding 'c' or 's'. In this
// case the format corresponds to a wide char/string
'c' | 's' if kind == IntKind::Long && !T::IS_THIN_NOT_WIDE => {
c_kind = CharKind::Wide;
break;
}
_ => break,
};
}
c = next_byte(&mut format)?;
c = next_char(&mut format)?;
}
if c != b'n' {
if c != 'n' {
maybe_read!(noreset);
}
match c {
b'%' => {
while (byte as char).is_whitespace() {
'%' => {
while (character).is_whitespace() {
if !read!() {
return Ok(matched);
}
}
if byte != b'%' {
if character != '%' {
return Err(matched);
} else if !read!() {
return Ok(matched);
}
}
b'd' | b'i' | b'o' | b'u' | b'x' | b'X' | b'f' | b'e' | b'g' | b'E' | b'a'
| b'p' => {
while (byte as char).is_whitespace() {
'd' | 'i' | 'o' | 'u' | 'x' | 'X' | 'f' | 'e' | 'g' | 'E' | 'a' | 'p' => {
while character.is_whitespace() {
if !read!() {
return Ok(matched);
}
}
let pointer = c == b'p';
let pointer = c == 'p';
// Pointers aren't automatic, but we do want to parse "0x"
let auto = c == b'i' || pointer;
let float = c == b'f' || c == b'e' || c == b'g' || c == b'E' || c == b'a';
let auto = c == 'i' || pointer;
let float = c == 'f' || c == 'e' || c == 'g' || c == 'E' || c == 'a';
let mut radix = match c {
b'o' => 8,
b'x' | b'X' | b'p' => 16,
'o' => 8,
'x' | 'X' | 'p' => 16,
_ => 10,
};
@@ -184,16 +195,16 @@ unsafe fn inner_scanf(
let mut dot = false;
while width.map(|w| w > 0).unwrap_or(true)
&& ((byte >= b'0' && byte <= b'7')
|| (radix >= 10 && (byte >= b'8' && byte <= b'9'))
|| (float && !dot && byte == b'.')
&& (('0'..='7').contains(&character)
|| (radix >= 10 && ('8'..='9').contains(&character))
|| (float && !dot && character == '.')
|| (radix == 16
&& ((byte >= b'a' && byte <= b'f')
|| (byte >= b'A' && byte <= b'F'))))
&& (('a'..='f').contains(&character)
|| ('A'..='F').contains(&character))))
{
if auto
&& n.is_empty()
&& byte == b'0'
&& character == '0'
&& width.map(|w| w > 0).unwrap_or(true)
{
if !pointer {
@@ -204,7 +215,7 @@ unsafe fn inner_scanf(
return Ok(matched);
}
if width.map(|w| w > 0).unwrap_or(true)
&& (byte == b'x' || byte == b'X')
&& (character == 'x' || character == 'X')
{
radix = 16;
width = width.map(|w| w - 1);
@@ -214,15 +225,14 @@ unsafe fn inner_scanf(
}
continue;
}
if byte == b'.' {
if character == '.' {
// Don't allow another dot
dot = true;
}
n.push(byte as char);
r.commit();
n.push(character);
width = width.map(|w| w - 1);
if width.map(|w| w > 0).unwrap_or(true) && !read!() {
return Ok(matched);
break;
}
}
@@ -234,18 +244,18 @@ unsafe fn inner_scanf(
n.parse::<$type>().map_err(|_| 0)?
};
if !ignore {
*ap.arg::<*mut $type>() = n;
unsafe { *ap.next_arg::<*mut $type>() = n };
matched += 1;
}
}};
(c_double) => {
parse_type!(noformat c_double);
parse_type!(noformat c_double)
};
(c_float) => {
parse_type!(noformat c_float);
parse_type!(noformat c_float)
};
($type:ident) => {
parse_type!($type, $type);
parse_type!($type, $type)
};
($type:ident, $final:ty) => {{
let n = if n.is_empty() {
@@ -254,7 +264,7 @@ unsafe fn inner_scanf(
$type::from_str_radix(&n, radix).map_err(|_| 0)?
};
if !ignore {
*ap.arg::<*mut $final>() = n as $final;
unsafe { *ap.next_arg::<*mut $final>() = n as $final };
matched += 1;
}
}};
@@ -266,10 +276,10 @@ unsafe fn inner_scanf(
} else {
parse_type!(c_float);
}
} else if c == b'p' {
} else if c == 'p' {
parse_type!(size_t, *mut c_void);
} else {
let unsigned = c == b'o' || c == b'u' || c == b'x' || c == b'X';
let unsigned = c == 'o' || c == 'u' || c == 'x' || c == 'X';
match kind {
IntKind::Byte => {
@@ -325,99 +335,140 @@ unsafe fn inner_scanf(
}
}
}
b's' => {
while (byte as char).is_whitespace() {
if !read!() {
return Ok(matched);
}
's' => {
macro_rules! parse_string_type {
($type:ident) => {
while character.is_whitespace() {
if !read!() {
return Ok(matched);
}
}
let mut ptr: Option<*mut $type> =
if ignore { None } else { Some(ap.next_arg()) };
while width.map(|w| w > 0).unwrap_or(true) && !character.is_whitespace()
{
if let Some(ref mut ptr) = ptr {
**ptr = character as $type;
*ptr = ptr.offset(1);
}
width = width.map(|w| w - 1);
if width.map(|w| w > 0).unwrap_or(true) && !read!() {
eof = true;
break;
}
}
// If we read the width, we end up in the last character of was
// intended to be read, so we advance one
if let Some(0) = width {
read!();
}
if let Some(ptr) = ptr {
*ptr = 0;
matched += 1;
}
};
}
let mut ptr: Option<*mut c_char> = if ignore { None } else { Some(ap.arg()) };
while width.map(|w| w > 0).unwrap_or(true) && !(byte as char).is_whitespace() {
if let Some(ref mut ptr) = ptr {
**ptr = byte as c_char;
*ptr = ptr.offset(1);
if c_kind == CharKind::Ascii {
unsafe {
parse_string_type!(c_char);
}
width = width.map(|w| w - 1);
if width.map(|w| w > 0).unwrap_or(true) && !read!() {
eof = true;
break;
} else {
unsafe {
parse_string_type!(wchar_t);
}
}
if let Some(ptr) = ptr {
*ptr = 0;
matched += 1;
r.commit();
}
}
b'c' => {
let ptr: Option<*mut c_char> = if ignore { None } else { Some(ap.arg()) };
for i in 0..width.unwrap_or(1) {
if let Some(ptr) = ptr {
*ptr.add(i) = byte as c_char;
}
width = width.map(|w| w - 1);
if width.map(|w| w > 0).unwrap_or(true) && !read!() {
eof = true;
break;
}
'c' => {
macro_rules! parse_char_type {
($type:ident) => {
let ptr: Option<*mut $type> = if ignore {
None
} else {
Some(unsafe { ap.next_arg() })
};
for i in 0..width.unwrap_or(1) {
if let Some(ptr) = ptr {
unsafe { *ptr.add(i) = character as $type };
}
width = width.map(|w| w - 1);
if width.map(|w| w > 0).unwrap_or(true) && !read!() {
eof = true;
break;
}
}
if ptr.is_some() {
matched += 1;
}
};
}
if ptr.is_some() {
matched += 1;
r.commit();
if c_kind == CharKind::Ascii {
parse_char_type!(c_char);
} else {
parse_char_type!(wchar_t);
}
}
b'[' => {
c = next_byte(&mut format)?;
'[' => {
c = next_char(&mut format)?;
let mut matches = Vec::new();
let invert = if c == b'^' {
c = next_byte(&mut format)?;
let invert = if c == '^' {
c = next_char(&mut format)?;
true
} else {
false
};
let mut prev;
let mut prev: u32;
loop {
matches.push(c);
prev = c;
c = next_byte(&mut format)?;
if c == b'-' {
if prev == b']' {
prev = c.into();
c = next_char(&mut format)?;
if c == '-' {
if prev as u8 == b']' {
continue;
}
c = next_byte(&mut format)?;
if c == b']' {
matches.push(b'-');
c = next_char(&mut format)?;
if c == ']' {
matches.push('-');
break;
}
prev += 1;
while prev < c {
matches.push(prev);
while prev < c.into() {
matches.push(char::try_from(prev).map_err(|_| -1)?);
prev += 1;
}
} else if c == b']' {
} else if c == ']' {
break;
}
}
let mut ptr: Option<*mut c_char> = if ignore { None } else { Some(ap.arg()) };
let mut ptr: Option<*mut c_char> = if ignore {
None
} else {
Some(unsafe { ap.next_arg() })
};
// While we haven't used up all the width, and it matches
let mut data_stored = false;
while width.map(|w| w > 0).unwrap_or(true) && !invert == matches.contains(&byte)
while width.map(|w| w > 0).unwrap_or(true)
&& invert != matches.contains(&character)
{
if let Some(ref mut ptr) = ptr {
**ptr = byte as c_char;
*ptr = ptr.offset(1);
unsafe { **ptr = character as c_char };
*ptr = unsafe { ptr.offset(1) };
data_stored = true;
}
r.commit();
// Decrease the width, and read a new character unless the width is 0
width = width.map(|w| w - 1);
if width.map(|w| w > 0).unwrap_or(true) && !read!() {
@@ -429,13 +480,13 @@ unsafe fn inner_scanf(
}
if data_stored {
*ptr.unwrap() = 0;
unsafe { *ptr.unwrap() = 0 };
matched += 1;
}
}
b'n' => {
'n' => {
if !ignore {
*ap.arg::<*mut c_int>() = count as c_int;
unsafe { *ap.next_arg::<*mut c_int>() = count as c_int };
}
}
_ => return Err(-1),
@@ -445,7 +496,7 @@ unsafe fn inner_scanf(
return Ok(matched);
}
if width != Some(0) && c != b'n' {
if width != Some(0) && c != 'n' {
// It didn't hit the width, so an extra character was read and matched.
// But this character did not match so let's reuse it.
skip_read = true;
@@ -455,8 +506,8 @@ unsafe fn inner_scanf(
Ok(matched)
}
pub unsafe fn scanf(r: LookAheadReader, format: *const c_char, ap: va_list) -> c_int {
match inner_scanf(r, format, ap) {
pub unsafe fn scanf<T: Kind>(r: Reader<T>, format: Reader<T>, ap: va_list) -> c_int {
match unsafe { inner_scanf(r, format, ap) } {
Ok(n) => n,
Err(n) => n,
}