Skip to content

Commit 656c792

Browse files
authored
[unsafe] Replace fs::File with libc::FILE (#135)
Currently we translate `FILE*` as `*mut std::fs:FILE`. This makes it difficult to translate functions such as feof and ferrror. feof and ferror both peek the internal state of the FILE object. std::fs::File doet not keep an internal state, it propagates the error right at the point of the usage, for example when performing a read or a write. On the other side, `*mut libc::FILE`, which is the direct FFI equivalent of `FILE*`, keeps the internal state, making the translation of feof and ferror easy. Another advantage of switching to `*mut libc::FILE` is that it simplifies all stdio.h rules. For example fwrites becomes a simple call to `libc::fwrite`, instead of the hand-rolled implementation that we have now for writing a buffer into `std::fs::File`.
1 parent 60deba8 commit 656c792

15 files changed

Lines changed: 586 additions & 1560 deletions

libcc2rs/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ edition = "2024"
55

66
[dependencies]
77
libcc2rs-macros = { path = "../libcc2rs-macros", version = "0.1.0" }
8+
libc = "0.2"

libcc2rs/src/io.rs

Lines changed: 50 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -165,95 +165,70 @@ pub fn fwrite_refcount(a0: AnyPtr, a1: u64, a2: u64, a3: Ptr<::std::fs::File>) -
165165
(written_bytes / a1 as usize) as u64
166166
}
167167

168+
unsafe extern "C" {
169+
#[cfg(target_os = "linux")]
170+
#[link_name = "stdin"]
171+
static mut LIBC_STDIN: *mut libc::FILE;
172+
#[cfg(target_os = "linux")]
173+
#[link_name = "stdout"]
174+
static mut LIBC_STDOUT: *mut libc::FILE;
175+
#[cfg(target_os = "linux")]
176+
#[link_name = "stderr"]
177+
static mut LIBC_STDERR: *mut libc::FILE;
178+
179+
#[cfg(target_os = "macos")]
180+
#[link_name = "__stdinp"]
181+
static mut LIBC_STDIN: *mut libc::FILE;
182+
#[cfg(target_os = "macos")]
183+
#[link_name = "__stdoutp"]
184+
static mut LIBC_STDOUT: *mut libc::FILE;
185+
#[cfg(target_os = "macos")]
186+
#[link_name = "__stderrp"]
187+
static mut LIBC_STDERR: *mut libc::FILE;
188+
}
189+
190+
/// # Safety
191+
///
192+
/// Returns the libc `stdin` handle. The pointer is valid for the process
193+
/// lifetime.
194+
pub unsafe fn stdin_unsafe() -> *mut libc::FILE {
195+
unsafe { LIBC_STDIN }
196+
}
197+
198+
/// # Safety
199+
///
200+
/// Returns the libc `stdout` handle.
201+
pub unsafe fn stdout_unsafe() -> *mut libc::FILE {
202+
unsafe { LIBC_STDOUT }
203+
}
204+
168205
/// # Safety
169206
///
170-
/// `a0` must point to a readable buffer of at least `a1 * a2` bytes, and `a3`
171-
/// must point to a valid, open `std::fs::File`.
207+
/// Returns the libc `stderr` handle.
208+
pub unsafe fn stderr_unsafe() -> *mut libc::FILE {
209+
unsafe { LIBC_STDERR }
210+
}
211+
212+
/// # Safety
213+
///
214+
/// Same contract as C's `fwrite`.
172215
pub unsafe fn fwrite_unsafe(
173216
a0: *const ::std::ffi::c_void,
174217
a1: u64,
175218
a2: u64,
176-
a3: *mut ::std::fs::File,
219+
a3: *mut libc::FILE,
177220
) -> u64 {
178-
let total = a1.saturating_mul(a2) as usize;
179-
let mut src = a0 as *const u8;
180-
181-
let f = unsafe { (*a3).try_clone().expect("try_clone failed") };
182-
let mut writer = std::io::BufWriter::with_capacity(64 * 1024, f);
183-
184-
let mut written_bytes: usize = 0;
185-
let mut buffer: [u8; 8192] = [0; 8192];
186-
187-
while written_bytes < total {
188-
let remaining = total - written_bytes;
189-
let to_fill = std::cmp::min(buffer.len(), remaining);
190-
191-
for b in buffer.iter_mut().take(to_fill) {
192-
unsafe {
193-
*b = *src;
194-
src = src.offset(1);
195-
}
196-
}
197-
198-
let mut off = 0;
199-
while off < to_fill {
200-
match std::io::Write::write(&mut writer, &buffer[off..to_fill]) {
201-
Ok(0) => break,
202-
Ok(n) => off += n,
203-
Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
204-
Err(e) => panic!("Unhandled error in fwrite: {e}"),
205-
}
206-
}
207-
208-
if off == 0 {
209-
break;
210-
}
211-
212-
written_bytes += off;
213-
}
214-
215-
(written_bytes / a1 as usize) as u64
221+
unsafe { libc::fwrite(a0, a1 as libc::size_t, a2 as libc::size_t, a3) as u64 }
216222
}
217223

218224
/// # Safety
219225
///
220-
/// `a0` must point to a writable buffer of at least `a1 * a2` bytes, and `a3`
221-
/// must point to a valid, open `std::fs::File`.
226+
/// Same contract as C's `fread`.
222227
pub unsafe fn fread_unsafe(
223228
a0: *mut ::std::ffi::c_void,
224229
a1: u64,
225230
a2: u64,
226-
a3: *mut ::std::fs::File,
231+
a3: *mut libc::FILE,
227232
) -> u64 {
228-
let total = a1.saturating_mul(a2) as usize;
229-
let mut dst = a0 as *mut u8;
230-
231-
let f = unsafe { (*a3).try_clone().expect("try_clone failed") };
232-
let mut reader = std::io::BufReader::with_capacity(64 * 1024, f);
233-
234-
let mut read_bytes: usize = 0;
235-
let mut buffer: [u8; 8192] = [0; 8192];
236-
237-
while read_bytes < total {
238-
let remaining = total - read_bytes;
239-
let to_read = std::cmp::min(buffer.len(), remaining);
240-
241-
let n = match std::io::Read::read(&mut reader, &mut buffer[..to_read]) {
242-
Ok(0) => break,
243-
Ok(n) => n,
244-
Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
245-
Err(e) => panic!("Unhandled error in fread: {e}"),
246-
};
247-
248-
for &byte in &buffer[..n] {
249-
unsafe {
250-
*dst = byte;
251-
dst = dst.offset(1);
252-
}
253-
}
254-
255-
read_bytes += n;
256-
}
257-
258-
(read_bytes / a1 as usize) as u64
233+
unsafe { libc::fread(a0, a1 as libc::size_t, a2 as libc::size_t, a3) as u64 }
259234
}

0 commit comments

Comments
 (0)