#![allow(non_snake_case, non_upper_case_globals, dead_code)]
use nix::libc;
pub const ARENA_BLOCK_SIZE: usize = 4096;
pub const kFileReadOnly: i32 = 1;
pub const kFileCreate: i32 = 2;
pub const kFileWriteOnly: i32 = 4;
pub const kFileNoSymlink: i32 = 8;
pub const kFileCreateOnly: i32 = 16;
pub const kFileTruncate: i32 = 32;
pub const kFileAppend: i32 = 64;
pub const kFileNonBlocking: i32 = 128;
pub const kFileMkDir: i32 = 256;
const UV_EAGAIN: i32 = -libc::EAGAIN;
const UV_EINTR: i32 = -libc::EINTR;
const UV_EIO: i32 = -libc::EIO;
const UV_EINVAL: i32 = -libc::EINVAL;
const UV_EROFS: i32 = -libc::EROFS;
const UV_ENOTSUP: i32 = -libc::ENOTSUP;
const UV_UNKNOWN: i32 = -4094;
fn os_translate_sys_error() -> i32 {
-std::io::Error::last_os_error().raw_os_error().unwrap_or(0)
}
#[derive(Default)]
pub struct FileDescriptor {
pub fd: i32,
pub buffer: Vec<u8>,
pub read_pos: usize,
pub write_pos: usize,
pub wr: bool,
pub eof: bool,
pub non_blocking: bool,
pub bytes_read: u64,
}
fn file_space(fp: &FileDescriptor) -> usize {
ARENA_BLOCK_SIZE - fp.write_pos
}
pub fn file_open(ret_fp: &mut FileDescriptor, fname: &str, flags: i32, mode: i32) -> i32 {
let mut os_open_flags = 0i32;
if flags & kFileWriteOnly != 0 {
os_open_flags |= libc::O_WRONLY;
}
if flags & kFileCreateOnly != 0 {
os_open_flags |= libc::O_CREAT | libc::O_EXCL | libc::O_WRONLY;
}
if flags & kFileCreate != 0 {
os_open_flags |= libc::O_CREAT | libc::O_WRONLY;
}
if flags & kFileTruncate != 0 {
os_open_flags |= libc::O_TRUNC | libc::O_WRONLY;
}
if flags & kFileAppend != 0 {
os_open_flags |= libc::O_APPEND | libc::O_WRONLY;
}
if flags & kFileReadOnly != 0 {
os_open_flags |= libc::O_RDONLY;
}
if flags & kFileNoSymlink != 0 {
os_open_flags |= libc::O_NOFOLLOW;
}
if flags & kFileMkDir != 0 {
os_open_flags |= libc::O_CREAT | libc::O_WRONLY;
}
if flags & kFileMkDir != 0 {
let mkdir_ret = os_file_mkdir(fname, 0o755);
if mkdir_ret < 0 {
return mkdir_ret;
}
}
let fd = os_open(fname, os_open_flags, mode);
if fd < 0 {
return fd;
}
file_open_fd(ret_fp, fd, flags)
}
pub fn file_open_fd(ret_fp: &mut FileDescriptor, fd: i32, flags: i32) -> i32 {
ret_fp.wr = (flags
& (kFileCreate | kFileCreateOnly | kFileTruncate | kFileAppend | kFileWriteOnly))
!= 0;
ret_fp.non_blocking = (flags & kFileNonBlocking) != 0;
debug_assert!(!ret_fp.wr || !ret_fp.non_blocking);
ret_fp.fd = fd;
ret_fp.eof = false;
ret_fp.buffer = alloc_block();
ret_fp.read_pos = 0;
ret_fp.write_pos = 0;
ret_fp.bytes_read = 0;
0
}
pub fn file_open_stdin(fp: &mut FileDescriptor) -> i32 {
let error = file_open_fd(fp, os_open_stdin_fd(), kFileReadOnly | kFileNonBlocking);
error
}
pub fn file_open_buffer(ret_fp: &mut FileDescriptor, data: &[u8]) {
ret_fp.wr = false;
ret_fp.non_blocking = false;
ret_fp.fd = -1;
ret_fp.eof = true;
ret_fp.buffer = data.to_vec();
ret_fp.read_pos = 0;
ret_fp.write_pos = data.len();
ret_fp.bytes_read = 0;
}
pub fn file_close(fp: &mut FileDescriptor, do_fsync: bool) -> i32 {
if fp.fd < 0 {
return 0;
}
let flush_error = if do_fsync {
file_fsync(fp)
} else {
file_flush(fp)
};
let close_error = os_close(fp.fd);
free_block(&mut fp.buffer);
if close_error != 0 {
return close_error;
}
flush_error
}
pub fn file_fsync(fp: &mut FileDescriptor) -> i32 {
if !fp.wr {
return 0;
}
let flush_error = file_flush(fp);
if flush_error != 0 {
return flush_error;
}
let fsync_error = os_fsync(fp.fd);
if fsync_error != UV_EINVAL
&& fsync_error != UV_EROFS
&& fsync_error != UV_ENOTSUP
{
return fsync_error;
}
0
}
pub fn file_flush(fp: &mut FileDescriptor) -> i32 {
if !fp.wr {
return 0;
}
let to_write = (fp.write_pos - fp.read_pos) as isize;
if to_write == 0 {
return 0;
}
let wres = os_write(
fp.fd,
&fp.buffer[fp.read_pos..fp.write_pos],
to_write as usize,
fp.non_blocking,
);
fp.read_pos = 0;
fp.write_pos = 0;
if wres != to_write {
return if wres >= 0 { UV_EIO } else { wres as i32 };
}
0
}
pub fn file_read(fp: &mut FileDescriptor, ret_buf: &mut [u8], size: usize) -> isize {
debug_assert!(!fp.wr);
let from_buffer = std::cmp::min(fp.write_pos - fp.read_pos, size);
ret_buf[..from_buffer].copy_from_slice(&fp.buffer[fp.read_pos..fp.read_pos + from_buffer]);
let bufpos = from_buffer; let mut read_remaining = size - from_buffer;
if read_remaining == 0 {
fp.bytes_read += from_buffer as u64;
fp.read_pos += from_buffer;
return from_buffer as isize;
}
fp.read_pos = 0;
fp.write_pos = 0;
if fp.eof {
} else if read_remaining >= ARENA_BLOCK_SIZE {
let r_ret = os_read(
fp.fd,
&mut fp.eof,
&mut ret_buf[bufpos..bufpos + read_remaining],
read_remaining,
fp.non_blocking,
);
if r_ret >= 0 {
read_remaining -= r_ret as usize;
} else {
return r_ret;
}
} else {
let r_ret = os_read(
fp.fd,
&mut fp.eof,
&mut fp.buffer[fp.write_pos..fp.write_pos + ARENA_BLOCK_SIZE],
ARENA_BLOCK_SIZE,
fp.non_blocking,
);
if r_ret < 0 {
return r_ret;
} else {
fp.write_pos += r_ret as usize;
let to_copy = std::cmp::min(r_ret as usize, read_remaining);
ret_buf[bufpos..bufpos + to_copy]
.copy_from_slice(&fp.buffer[fp.read_pos..fp.read_pos + to_copy]);
fp.read_pos += to_copy;
read_remaining -= to_copy;
}
}
fp.bytes_read += (size - read_remaining) as u64;
(size - read_remaining) as isize
}
pub fn file_write(fp: &mut FileDescriptor, buf: &[u8], size: usize) -> isize {
debug_assert!(fp.wr);
if size < file_space(fp) {
fp.buffer[fp.write_pos..fp.write_pos + size].copy_from_slice(&buf[..size]);
fp.write_pos += size;
return size as isize;
}
let status = file_flush(fp);
if status < 0 {
return status as isize;
}
if size < ARENA_BLOCK_SIZE {
fp.buffer[fp.write_pos..fp.write_pos + size].copy_from_slice(&buf[..size]);
fp.write_pos += size;
return size as isize;
}
let wres = os_write(fp.fd, buf, size, fp.non_blocking);
if wres != size as isize && wres >= 0 {
UV_EIO as isize
} else {
wres
}
}
fn os_open(path: &str, flags: i32, mode: i32) -> i32 {
let cpath = match std::ffi::CString::new(path) {
Ok(c) => c,
Err(_) => return UV_EINVAL,
};
let r = unsafe { libc::open(cpath.as_ptr(), flags, mode as libc::c_uint) };
if r < 0 {
os_translate_sys_error()
} else {
r
}
}
fn os_close(fd: i32) -> i32 {
let r = unsafe { libc::close(fd) };
if r < 0 {
os_translate_sys_error()
} else {
r
}
}
fn os_dup(fd: i32) -> i32 {
loop {
let ret = unsafe { libc::dup(fd) };
if ret < 0 {
let error = os_translate_sys_error();
if error == UV_EINTR {
continue;
} else {
return error;
}
}
return ret;
}
}
fn os_open_stdin_fd() -> i32 {
os_dup(libc::STDIN_FILENO)
}
fn os_read(
fd: i32,
ret_eof: &mut bool,
ret_buf: &mut [u8],
size: usize,
non_blocking: bool,
) -> isize {
*ret_eof = false;
let mut read_bytes = 0usize;
while read_bytes != size {
debug_assert!(size >= read_bytes);
let cur_read_bytes = unsafe {
libc::read(
fd,
ret_buf[read_bytes..].as_mut_ptr() as *mut libc::c_void,
size - read_bytes,
)
};
if cur_read_bytes > 0 {
read_bytes += cur_read_bytes as usize;
}
if cur_read_bytes < 0 {
let error = os_translate_sys_error();
if non_blocking && error == UV_EAGAIN {
break;
} else if error == UV_EINTR || error == UV_EAGAIN {
continue;
} else {
return error as isize;
}
}
if cur_read_bytes == 0 {
*ret_eof = true;
break;
}
}
read_bytes as isize
}
fn os_write(fd: i32, buf: &[u8], size: usize, non_blocking: bool) -> isize {
let mut written_bytes = 0usize;
while written_bytes != size {
debug_assert!(size >= written_bytes);
let cur_written_bytes = unsafe {
libc::write(
fd,
buf[written_bytes..].as_ptr() as *const libc::c_void,
size - written_bytes,
)
};
if cur_written_bytes > 0 {
written_bytes += cur_written_bytes as usize;
}
if cur_written_bytes < 0 {
let error = os_translate_sys_error();
if non_blocking && error == UV_EAGAIN {
break;
} else if error == UV_EINTR || error == UV_EAGAIN {
continue;
} else {
return error as isize;
}
}
if cur_written_bytes == 0 {
return UV_UNKNOWN as isize;
}
}
written_bytes as isize
}
fn os_fsync(fd: i32) -> i32 {
let r = unsafe { libc::fsync(fd) };
if r < 0 {
os_translate_sys_error()
} else {
r
}
}
fn os_file_mkdir(fname: &str, _mode: i32) -> i32 {
let parent = match std::path::Path::new(fname).parent() {
Some(p) if !p.as_os_str().is_empty() => p,
_ => return 0,
};
if parent.exists() {
return 0;
}
match std::fs::create_dir_all(parent) {
Ok(()) => 0,
Err(e) => -e.raw_os_error().unwrap_or(0),
}
}
pub fn os_strerror(error: i32) -> String {
std::io::Error::from_raw_os_error(-error).to_string()
}
fn alloc_block() -> Vec<u8> {
vec![0u8; ARENA_BLOCK_SIZE]
}
fn free_block(block: &mut Vec<u8>) {
*block = Vec::new();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn open_write_read_roundtrip() {
let dir = std::env::temp_dir();
let path = dir
.join(format!("vimlrs_fileio_{}.tmp", std::process::id()))
.to_string_lossy()
.into_owned();
let mut fp = FileDescriptor::default();
let err = file_open(
&mut fp,
&path,
kFileCreate | kFileTruncate | kFileWriteOnly,
0o644,
);
assert_eq!(err, 0, "file_open: {}", os_strerror(err));
assert!(fp.wr);
let payload = b"hello\nworld";
assert_eq!(
file_write(&mut fp, payload, payload.len()),
payload.len() as isize
);
assert_eq!(file_close(&mut fp, false), 0);
assert!(fp.buffer.is_empty(), "buffer freed on close");
let mut rp = FileDescriptor::default();
assert_eq!(file_open(&mut rp, &path, kFileReadOnly, 0), 0);
let mut buf = [0u8; 64];
let n = file_read(&mut rp, &mut buf, payload.len());
assert_eq!(n, payload.len() as isize);
assert_eq!(&buf[..payload.len()], payload);
assert_eq!(file_close(&mut rp, false), 0);
let _ = std::fs::remove_file(&path);
}
#[test]
fn large_write_flushes_through_syscall() {
let dir = std::env::temp_dir();
let path = dir
.join(format!("vimlrs_fileio_big_{}.tmp", std::process::id()))
.to_string_lossy()
.into_owned();
let mut fp = FileDescriptor::default();
assert_eq!(
file_open(
&mut fp,
&path,
kFileCreate | kFileTruncate | kFileWriteOnly,
0o644
),
0
);
let big = vec![7u8; ARENA_BLOCK_SIZE * 3 + 5];
assert_eq!(file_write(&mut fp, &big, big.len()), big.len() as isize);
assert_eq!(file_close(&mut fp, true), 0);
assert_eq!(std::fs::metadata(&path).unwrap().len(), big.len() as u64);
let _ = std::fs::remove_file(&path);
}
}