Newer
Older
//! stdio implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/stdio.h.html
#![no_std]
Tom Almeida
committed
// For Vec
Tom Almeida
committed
#[macro_use]
Tom Almeida
committed
#[macro_use]
extern crate lazy_static;
use core::fmt::Write as WriteFmt;
use core::sync::atomic::{AtomicBool, Ordering};
Tom Almeida
committed
use errno::STR_ERROR;
Tom Almeida
committed
///
/// This struct gets exposed to the C API.
///
flags: i32,
read: Option<(usize, usize)>,
write: Option<(usize, usize, usize)>,
fd: c_int,
buf: Vec<u8>,
Tom Almeida
committed
}
impl FILE {
pub fn can_read(&mut self) -> bool {
Tom Almeida
committed
/*
Tom Almeida
committed
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;
}
Tom Almeida
committed
*/
Tom Almeida
committed
if let Some(_) = self.read {
return true;
}
Tom Almeida
committed
if let Some(_) = self.write {
Tom Almeida
committed
self.write(&[]);
}
Tom Almeida
committed
self.write = None;
Tom Almeida
committed
if self.flags & constants::F_NORD > 0 {
self.flags |= constants::F_ERR;
return false;
}
Tom Almeida
committed
self.read = Some((self.buf.len() - 1, self.buf.len() - 1));
Tom Almeida
committed
if self.flags & constants::F_EOF > 0 {
false
} else {
true
}
}
pub fn can_write(&mut self) -> bool {
Tom Almeida
committed
/*
Tom Almeida
committed
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;
}
Tom Almeida
committed
*/
Tom Almeida
committed
if self.flags & constants::F_NOWR > 0 {
self.flags &= constants::F_ERR;
return false;
}
// Buffer repositioning
if let Some(_) = self.write {
return true;
}
Tom Almeida
committed
self.read = None;
self.write = Some((self.unget, self.unget, self.buf.len() - 1));
Tom Almeida
committed
return true;
}
pub fn write(&mut self, to_write: &[u8]) -> usize {
Tom Almeida
committed
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
if let Some((wbase, wpos, _)) = self.write {
let len = wpos - wbase;
let mut advance = 0;
let mut f_buf = &self.buf[wbase..wpos];
let mut f_filled = false;
let mut rem = f_buf.len() + to_write.len();
loop {
let mut count = if f_filled {
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.write = Some((self.unget, self.unget, self.buf.len() - 1));
return to_write.len();
}
if count < 0 {
self.write = None;
self.flags |= constants::F_ERR;
return 0;
}
rem -= count as usize;
if count as usize > len {
count -= len as isize;
f_buf = to_write;
f_filled = true;
advance = 0;
}
advance += count as usize;
Tom Almeida
committed
}
}
Tom Almeida
committed
// self.can_write() should always be called before self.write()
// and should automatically fill self.write if it returns true.
// Thus, we should never reach this
// -- Tommoa (20/6/2018)
unreachable!()
Tom Almeida
committed
}
pub fn read(&mut self, buf: &mut [u8]) -> usize {
Tom Almeida
committed
let mut file_buf = &mut self.buf[self.unget..];
Tom Almeida
committed
let count = if buf.len() <= 1 + adj {
Tom Almeida
committed
platform::read(self.fd, &mut file_buf)
Tom Almeida
committed
} else {
Tom Almeida
committed
platform::read(self.fd, buf) + platform::read(self.fd, &mut file_buf)
Tom Almeida
committed
};
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;
}
Tom Almeida
committed
// Adjust pointers
if buf.len() > 0 {
self.read = Some((self.unget + 1, self.unget + (count as usize)));
buf[buf.len() - 1] = file_buf[0];
} else {
self.read = Some((self.unget, self.unget + (count as usize)));
}
Tom Almeida
committed
buf.len()
}
pub fn seek(&self, off: off_t, whence: c_int) -> off_t {
Tom Almeida
committed
platform::lseek(self.fd, off, whence)
Tom Almeida
committed
}
}
impl fmt::Write for FILE {
Tom Almeida
committed
fn write_str(&mut self, s: &str) -> Result {
if !self.can_write() {
return Err(Error);
}
Tom Almeida
committed
let s = s.as_bytes();
if self.write(s) != s.len() {
Err(Error)
} else {
Ok(())
}
}
impl Write for FILE {
fn write_u8(&mut self, byte: u8) -> Result {
if !self.can_write() {
return Err(Error);
}
if self.write(&[byte]) != 1 {
Err(Error)
} else {
Ok(())
}
}
}
impl Read for FILE {
fn read_u8(&mut self, byte: &mut u8) -> bool {
let mut buf = [*byte];
let n = self.read(&mut buf);
*byte = buf[0];
n > 0
}
}
impl Drop for FILE {
fn drop(&mut self) {
// Flush
if let Some(_) = self.write {
self.write(&[]);
}
}
}
Tom Almeida
committed
pub extern "C" fn clearerr(stream: &mut FILE) {
Tom Almeida
committed
pub extern "C" fn ctermid(_s: *mut c_char) -> *mut c_char {
Tom Almeida
committed
pub extern "C" fn cuserid(_s: *mut c_char) -> *mut c_char {
/// 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.
Tom Almeida
committed
pub extern "C" fn fclose(stream: &mut FILE) -> c_int {
Tom Almeida
committed
let r = helpers::fflush_unlocked(stream) | platform::close(stream.fd);
if stream.flags & constants::F_PERM == 0 {
Tom Almeida
committed
unsafe {
Tom Almeida
committed
}
Tom Almeida
committed
} else {
funlockfile(stream);
Tom Almeida
committed
pub extern "C" fn fdopen(fildes: c_int, mode: *const c_char) -> *mut FILE {
Tom Almeida
committed
use core::ptr;
Tom Almeida
committed
if let Some(f) = unsafe { helpers::_fdopen(fildes, mode) } {
f
Tom Almeida
committed
} else {
ptr::null_mut()
}
Tom Almeida
committed
pub extern "C" fn feof(stream: &mut FILE) -> c_int {
Tom Almeida
committed
let ret = stream.flags & F_EOF;
Tom Almeida
committed
pub extern "C" fn ferror(stream: &mut FILE) -> c_int {
Tom Almeida
committed
let ret = stream.flags & F_ERR;
/// 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.
Tom Almeida
committed
pub unsafe extern "C" fn fflush(stream: &mut FILE) -> c_int {
let ret = helpers::fflush_unlocked(stream);
funlockfile(stream);
ret
Tom Almeida
committed
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
Tom Almeida
committed
pub extern "C" fn fgetpos(stream: &mut FILE, pos: Option<&mut fpos_t>) -> c_int {
let off = internal::ftello(stream);
if off < 0 {
return -1;
}
Tom Almeida
committed
if let Some(pos) = pos {
*pos = off;
0
} else {
-1
Tom Almeida
committed
}
}
/// Get a string from the stream
#[no_mangle]
Tom Almeida
committed
pub extern "C" fn fgets(s: *mut c_char, n: c_int, stream: &mut FILE) -> *mut c_char {
let st = unsafe { slice::from_raw_parts_mut(s, n as usize) };
Tom Almeida
committed
Tom Almeida
committed
// We can only fit one or less chars in
Tom Almeida
committed
unsafe {
(*s) = b'\0' as i8;
}
Tom Almeida
committed
// Scope this so we can reuse stream mutably
{
// We can't read from this stream
if !stream.can_read() {
return ptr::null_mut();
Tom Almeida
committed
// TODO: Look at this later to determine correctness and efficiency
'outer: while stream.read(&mut []) == 0 && stream.flags & F_ERR == 0 {
if let Some((rpos, rend)) = stream.read {
let mut idiff = 0usize;
for _ in (0..(len - 1) as usize).take_while(|x| rpos + x < rend) {
let pos = (n - len) as usize;
st[pos] = stream.buf[rpos + idiff] as i8;
idiff += 1;
len -= 1;
if st[pos] == b'\n' as i8 || st[pos] == stream.buf_char {
break 'outer;
// We can read, there's been no errors. We should have stream.read setbuf
// -- Tommoa (3/7/2018)
unreachable!()
Tom Almeida
committed
Tom Almeida
committed
pub extern "C" fn fileno(stream: &mut FILE) -> c_int {
Tom Almeida
committed
stream.fd
/// Lock the file
/// Do not call any functions other than those with the `_unlocked` postfix while the file is
/// locked
Tom Almeida
committed
pub extern "C" fn flockfile(file: &mut FILE) {
Tom Almeida
committed
pub extern "C" fn fopen(filename: *const c_char, mode: *const c_char) -> *mut FILE {
Tom Almeida
committed
let initial_mode = unsafe { *mode };
if initial_mode != b'r' as i8 && initial_mode != b'w' as i8 && initial_mode != b'a' as i8 {
Tom Almeida
committed
unsafe { platform::errno = errno::EINVAL };
Tom Almeida
committed
let flags = unsafe { 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);
}
Tom Almeida
committed
if let Some(f) = unsafe { helpers::_fdopen(fd, mode) } {
f
Tom Almeida
committed
} else {
Tom Almeida
committed
ptr::null_mut()
Tom Almeida
committed
pub extern "C" fn fputc(c: c_int, stream: &mut FILE) -> c_int {
flockfile(stream);
let c = putc_unlocked(c, stream);
funlockfile(stream);
Tom Almeida
committed
pub extern "C" fn fputs(s: *const c_char, stream: &mut FILE) -> c_int {
extern "C" {
fn strlen(s: *const c_char) -> size_t;
}
Tom Almeida
committed
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`
Tom Almeida
committed
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);
Tom Almeida
committed
if !stream.can_read() {
return 0;
Tom Almeida
committed
if let Some((rpos, rend)) = stream.read {
if rend > rpos {
// We have some buffered data that can be read
let diff = rend - rpos;
let k = if diff < l as usize { diff } else { l as usize };
unsafe {
// Copy data
copy_nonoverlapping(&stream.buf[rpos..] as *const _ as *const u8, dest, k);
// Reposition pointers
dest = dest.add(k);
}
stream.read = Some((rpos + k, rend));
l -= k as isize;
Tom Almeida
committed
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);
}
Tom Almeida
committed
}
Tom Almeida
committed
funlockfile(stream);
nitems
} else {
unreachable!()
}
Tom Almeida
committed
pub extern "C" fn freopen(
filename: *const c_char,
mode: *const c_char,
Tom Almeida
committed
stream: &mut FILE,
Tom Almeida
committed
let mut flags = unsafe { helpers::parse_mode_flags(mode) };
flockfile(stream);
helpers::fflush_unlocked(stream);
if filename.is_null() {
// Reopen stream in new mode
Tom Almeida
committed
fcntl::sys_fcntl(stream.fd, fcntl::F_SETFD, fcntl::FD_CLOEXEC);
}
flags &= !(fcntl::O_CREAT | fcntl::O_EXCL | fcntl::O_CLOEXEC);
Tom Almeida
committed
if fcntl::sys_fcntl(stream.fd, fcntl::F_SETFL, flags) < 0 {
funlockfile(stream);
fclose(stream);
return ptr::null_mut();
}
} else {
Tom Almeida
committed
let new = fopen(filename, mode);
if new.is_null() {
funlockfile(stream);
fclose(stream);
return ptr::null_mut();
}
Tom Almeida
committed
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();
}
Tom Almeida
committed
stream.flags = (stream.flags & constants::F_PERM) | new.flags;
fclose(new);
}
funlockfile(stream);
stream
Tom Almeida
committed
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
Tom Almeida
committed
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
Tom Almeida
committed
let rdiff = if let Some((rpos, rend)) = stream.read {
rend - rpos
} else {
0
};
Tom Almeida
committed
off -= (rdiff) as i64;
Tom Almeida
committed
if let Some(_) = stream.write {
Tom Almeida
committed
stream.write(&[]);
}
Tom Almeida
committed
stream.write = None;
Tom Almeida
committed
if stream.seek(off, whence) < 0 {
Tom Almeida
committed
stream.read = None;
Tom Almeida
committed
stream.flags &= !F_EOF;
/// Seek to a position `pos` in the file from the beginning of the file
Tom Almeida
committed
pub unsafe extern "C" fn fsetpos(stream: &mut FILE, pos: Option<&fpos_t>) -> c_int {
fseek(
stream,
*pos.expect("You must specify a valid position"),
SEEK_SET,
)
/// Get the current position of the cursor in the file
Tom Almeida
committed
pub extern "C" fn ftell(stream: &mut FILE) -> c_long {
/// Get the current position of the cursor in the file
Tom Almeida
committed
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
Tom Almeida
committed
pub extern "C" fn ftrylockfile(file: &mut FILE) -> c_int {
file.lock.compare_and_swap(false, true, Ordering::Acquire) as c_int
Tom Almeida
committed
pub extern "C" fn funlockfile(file: &mut FILE) {
file.lock.store(false, Ordering::Release);
/// Write `nitems` of size `size` from `ptr` to `stream`
Tom Almeida
committed
pub extern "C" fn fwrite(
ptr: *const c_void,
size: usize,
nitems: usize,
Tom Almeida
committed
stream: &mut FILE,
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
}
Tom Almeida
committed
pub extern "C" fn getc(stream: &mut FILE) -> c_int {
flockfile(stream);
let c = getc_unlocked(stream);
funlockfile(stream);
c
Tom Almeida
committed
pub extern "C" fn getchar() -> c_int {
/// Get a char from a stream without locking the stream
Tom Almeida
committed
pub extern "C" fn getc_unlocked(stream: &mut FILE) -> c_int {
Tom Almeida
committed
if !stream.can_read() {
return -1;
}
if let Some((rpos, rend)) = stream.read {
if rpos < rend {
let ret = stream.buf[rpos] as c_int;
stream.read = Some((rpos + 1, rend));
Tom Almeida
committed
ret
Tom Almeida
committed
let mut c = [0u8; 1];
if stream.read(&mut c) == 1 {
c[0] as c_int
} else {
-1
}
Tom Almeida
committed
} else {
// We made a prior call to can_read() and are checking it, therefore we
// should never be in a case where stream.read is None
// -- Tommoa (20/6/2018)
unreachable!()
/// Get a char from `stdin` without locking `stdin`
Tom Almeida
committed
pub extern "C" fn getchar_unlocked() -> c_int {
getc_unlocked(unsafe { &mut *__stdin() })
Tom Almeida
committed
pub extern "C" fn gets(s: *mut c_char) -> *mut c_char {
fgets(s, i32::MAX, unsafe { &mut *__stdin() })
Tom Almeida
committed
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
}
Tom Almeida
committed
pub extern "C" fn pclose(_stream: &mut FILE) -> c_int {
if errno >= 0 && errno < STR_ERROR.len() as c_int {
Tom Almeida
committed
w.write_fmt(format_args!("{}: {}\n", s_str, STR_ERROR[errno as usize]))
.unwrap();
Tom Almeida
committed
w.write_fmt(format_args!("{}: Unknown error {}\n", s_str, errno))
.unwrap();
Tom Almeida
committed
pub extern "C" fn popen(_command: *const c_char, _mode: *const c_char) -> *mut FILE {
Tom Almeida
committed
pub extern "C" fn putc(c: c_int, stream: &mut FILE) -> c_int {
flockfile(stream);
let ret = putc_unlocked(c, stream);
funlockfile(stream);
ret
Tom Almeida
committed
pub extern "C" fn putchar(c: c_int) -> c_int {
fputc(c, unsafe { &mut *__stdout() })
/// Put a character `c` into `stream` without locking `stream`
Tom Almeida
committed
pub extern "C" fn putc_unlocked(c: c_int, stream: &mut FILE) -> c_int {
Tom Almeida
committed
if stream.can_write() {
if let Some((wbase, wpos, wend)) = stream.write {
if c as i8 != stream.buf_char {
stream.buf[wpos] = c as u8;
stream.write = Some((wbase, wpos + 1, wend));
Tom Almeida
committed
} else if stream.write(&[c as u8]) == 1 {
c
} else {
-1
Tom Almeida
committed
} else {
Tom Almeida
committed
-1
Tom Almeida
committed
} else {
-1
/// Put a character `c` into `stdout` without locking `stdout`
Tom Almeida
committed
pub extern "C" fn putchar_unlocked(c: c_int) -> c_int {
putc_unlocked(c, unsafe { &mut *__stdout() })
Tom Almeida
committed
pub extern "C" fn puts(s: *const c_char) -> c_int {
let ret = (fputs(s, unsafe { &mut *__stdout() }) > 0) || (putchar_unlocked(b'\n' as c_int) > 0);
Tom Almeida
committed
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
pub extern "C" fn remove(path: *const c_char) -> c_int {
let r = platform::unlink(path);
if r == -errno::EISDIR {
platform::rmdir(path)
} else {
r
}
pub extern "C" fn rename(oldpath: *const c_char, newpath: *const c_char) -> c_int {
platform::rename(oldpath, newpath)
/// Rewind `stream` back to the beginning of it
Tom Almeida
committed
pub extern "C" fn rewind(stream: &mut FILE) {
fseeko(stream, 0, SEEK_SET);
flockfile(stream);
Tom Almeida
committed
stream.flags &= !F_ERR;
/// Reset `stream` to use buffer `buf`. Buffer must be `BUFSIZ` in length
Tom Almeida
committed
pub extern "C" fn setbuf(stream: &mut FILE, buf: *mut c_char) {
Tom Almeida
committed
setvbuf(
stream,
buf,
if buf.is_null() { _IONBF } else { _IOFBF },
BUFSIZ as usize,
);
/// Reset `stream` to use buffer `buf` of size `size`
Tom Almeida
committed
/// If this isn't the meaning of unsafe, idk what is
Tom Almeida
committed
pub extern "C" fn setvbuf(stream: &mut FILE, buf: *mut c_char, mode: c_int, size: usize) -> c_int {
// Set a buffer of size `size` if no buffer is given
let buf = if buf.is_null() {
if mode != _IONBF {
vec![0u8; 1]
} else {
Vec::new()
}
} else {
// We trust the user on this one
// -- Tommoa (20/6/2018)
unsafe { Vec::from_raw_parts(buf as *mut u8, size, size) }
};
stream.buf_char = -1;
if mode == _IOLBF {
stream.buf_char = b'\n' as i8;
}
stream.flags |= F_SVB;
stream.buf = buf;
Tom Almeida
committed
pub extern "C" fn tempnam(_dir: *const c_char, _pfx: *const c_char) -> *mut c_char {
extern "C" {
fn mkstemp(name: *mut c_char) -> c_int;
}
let mut file_name = *b"/tmp/tmpfileXXXXXX";
let file_name = file_name.as_mut_ptr() as *mut c_char;
let fd = unsafe { mkstemp(file_name) };
if fd < 0 {
return ptr::null_mut();
}
let fp = fdopen(fd, b"w+".as_ptr() as *const i8);
platform::unlink(file_name);
if fp == ptr::null_mut() {
platform::close(fd);
}
fp
Tom Almeida
committed
pub extern "C" fn tmpnam(_s: *mut c_char) -> *mut c_char {
/// Push character `c` back onto `stream` so it'll be read next
Tom Almeida
committed
pub extern "C" fn ungetc(c: c_int, stream: &mut FILE) -> c_int {
if c < 0 {
c
} else {
flockfile(stream);
Tom Almeida
committed
if stream.read.is_none() {
Tom Almeida
committed
stream.can_read();
Tom Almeida
committed
if let Some((rpos, rend)) = stream.read {
if rpos == 0 {
funlockfile(stream);
return -1;
}
stream.read = Some((rpos - 1, rend));
stream.buf[rpos - 1] = c as u8;
stream.flags &= !F_EOF;
Tom Almeida
committed
c
} else {
funlockfile(stream);
-1
Tom Almeida
committed
}
Tom Almeida
committed
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 {
pub unsafe extern "C" fn vsnprintf(
s: *mut c_char,
n: usize,
format: *const c_char,
ap: va_list,
) -> c_int {
Tom Almeida
committed
printf::printf(
&mut platform::StringWriter(s as *mut u8, n as usize),
format,
ap,
)
pub unsafe extern "C" fn vsprintf(s: *mut c_char, format: *const c_char, ap: va_list) -> c_int {
Tom Almeida
committed
printf::printf(&mut platform::UnsafeStringWriter(s as *mut u8), format, ap)
#[no_mangle]
pub unsafe extern "C" fn vfscanf(file: &mut FILE, format: *const c_char, ap: va_list) -> c_int {
scanf::scanf(file, format, ap)
}
#[no_mangle]
pub unsafe extern "C" fn vscanf(format: *const c_char, ap: va_list) -> c_int {
}
#[no_mangle]
pub unsafe extern "C" fn vsscanf(s: *const c_char, format: *const c_char, ap: va_list) -> c_int {
scanf::scanf(
&mut platform::UnsafeStringReader(s as *const u8),
format,
ap,
)