pub mod abi;
pub mod guest;
#[cfg(feature = "kvm")]
pub mod kvm;
pub mod loader;
pub mod mem;
use std::collections::BTreeMap;
use crate::emulator::context::FileAccess;
use crate::emulator::emulator::Perm;
use crate::emulator::fsmount::{MountTable, NodeKind};
use abi::{LinuxAbi, StatLayout, Sysno};
use guest::GuestCpu;
use mem::GuestMem;
const ENOENT: i64 = -2;
const EBADF: i64 = -9;
const EFAULT: i64 = -14;
const EINVAL: i64 = -22;
const ENOTTY: i64 = -25;
const ENOSYS: i64 = -38;
const ENOTDIR: i64 = -20;
const EISDIR: i64 = -21;
const AT_FDCWD: i32 = -100;
const PAGE: u32 = 0x1000;
const MMAP_BASE: u32 = 0x4000_0000;
const MMAP_LIMIT: u32 = 0x8000_0000;
#[derive(Debug, Clone)]
enum Fd {
Stdin,
Stdout,
Stderr,
Vfs {
h: u32,
path: String,
},
Dir {
path: String,
pos: usize,
},
PipeRead(usize),
PipeWrite(usize),
Socket(usize),
}
#[derive(Debug, Default)]
enum SocketState {
Pending {
ty: i32,
},
Tcp(std::net::TcpStream),
Udp(std::net::UdpSocket),
#[default]
Closed,
}
#[derive(Debug, Default)]
pub struct LinuxKernel {
brk: u32,
brk_mapped: u32,
mmap_top: u32,
fds: BTreeMap<i32, Fd>,
next_fd: i32,
pub stdout: Vec<u8>,
pub stderr: Vec<u8>,
pub exit_code: Option<i32>,
pub unsupported: BTreeMap<String, u64>,
ptr64: bool,
rng: u64,
pub current_tid: i32,
file_maps: Vec<FileMapping>,
cwd: String,
umask: u32,
pipes: Vec<Pipe>,
sockets: Vec<SocketState>,
sock_nonblock: Vec<bool>,
pub net_enabled: bool,
pub interactive: bool,
termios: Termios,
stdin_buf: std::collections::VecDeque<u8>,
pub term_size: (u16, u16),
sigactions: BTreeMap<i32, SigAction>,
sig_mask: u64,
pub pending_signal: Option<i32>,
pub input: Option<std::sync::Arc<InteractiveInput>>,
pub scheduler: bool,
pub pid: i32,
pub ppid: i32,
pub block_request: Option<BlockReason>,
line_accum: Vec<u8>,
}
#[derive(Debug, Clone)]
pub enum BlockReason {
Stdin,
Timer(std::time::Instant),
Pipe(usize),
}
#[derive(Debug, Default)]
struct Pipe {
buf: std::collections::VecDeque<u8>,
readers: i32,
writers: i32,
}
#[derive(Debug, Default)]
pub struct InteractiveInput {
state: std::sync::Mutex<InputState>,
cv: std::sync::Condvar,
pub isig: std::sync::atomic::AtomicBool,
pub vintr: std::sync::atomic::AtomicU8,
pub vquit: std::sync::atomic::AtomicU8,
pub pending: std::sync::atomic::AtomicU64,
pub win_rows: std::sync::atomic::AtomicU16,
pub win_cols: std::sync::atomic::AtomicU16,
pub stop: std::sync::atomic::AtomicBool,
}
#[derive(Debug, Default)]
struct InputState {
buf: std::collections::VecDeque<u8>,
closed: bool,
}
enum InByte {
Byte(u8),
Closed,
Interrupted,
}
impl InteractiveInput {
pub fn push(&self, b: u8) {
self.state.lock().unwrap().buf.push_back(b);
self.cv.notify_all();
}
pub fn close(&self) {
self.state.lock().unwrap().closed = true;
self.cv.notify_all();
}
pub fn raise(&self, sig: i32) {
let _g = self.state.lock().unwrap();
self.pending.fetch_or(
1u64 << ((sig - 1) as u64),
std::sync::atomic::Ordering::SeqCst,
);
self.cv.notify_all();
}
pub fn take(&self) -> Option<i32> {
use std::sync::atomic::Ordering::SeqCst;
loop {
let cur = self.pending.load(SeqCst);
if cur == 0 {
return None;
}
let sig = cur.trailing_zeros() as i32 + 1;
let next = cur & !(1u64 << (sig - 1) as u64);
if self
.pending
.compare_exchange(cur, next, SeqCst, SeqCst)
.is_ok()
{
return Some(sig);
}
}
}
fn next_byte(&self) -> InByte {
let mut st = self.state.lock().unwrap();
loop {
if self.pending.load(std::sync::atomic::Ordering::SeqCst) != 0 {
return InByte::Interrupted;
}
if let Some(b) = st.buf.pop_front() {
return InByte::Byte(b);
}
if st.closed {
return InByte::Closed;
}
st = self.cv.wait(st).unwrap();
}
}
fn try_byte(&self) -> Option<u8> {
self.state.lock().unwrap().buf.pop_front()
}
fn at_eof(&self) -> bool {
let st = self.state.lock().unwrap();
st.buf.is_empty() && st.closed
}
pub fn has_input(&self) -> bool {
if self.pending.load(std::sync::atomic::Ordering::SeqCst) != 0 {
return true;
}
let st = self.state.lock().unwrap();
!st.buf.is_empty() || st.closed
}
pub fn wait_for_input(&self, timeout: Option<std::time::Duration>) {
let st = self.state.lock().unwrap();
if self.pending.load(std::sync::atomic::Ordering::SeqCst) != 0
|| !st.buf.is_empty()
|| st.closed
{
return;
}
match timeout {
Some(d) => {
let (_g, _) = self.cv.wait_timeout(st, d).unwrap();
}
None => {
let _g = self.cv.wait(st).unwrap();
}
}
}
}
#[derive(Debug, Clone, Copy)]
struct Termios {
iflag: u32,
oflag: u32,
cflag: u32,
lflag: u32,
line: u8,
cc: [u8; 19],
}
impl Default for Termios {
fn default() -> Self {
let mut cc = [0u8; 19];
cc[0] = 0x03; cc[1] = 0x1c; cc[2] = 0x7f; cc[4] = 0x04; cc[6] = 0x01; Self {
iflag: 0x100 | 0x400 | 0x4000, oflag: 0x1 | 0x4, cflag: 0xbf, lflag: 0x8a3b,
line: 0,
cc,
}
}
}
#[derive(Debug, Clone, Copy)]
struct SigAction {
handler: u64,
flags: u64,
restorer: u64,
mask: u64,
}
#[derive(Clone)]
pub struct ProcState {
fds: BTreeMap<i32, Fd>,
next_fd: i32,
brk: u32,
brk_mapped: u32,
mmap_top: u32,
file_maps: Vec<FileMapping>,
cwd: String,
umask: u32,
sigactions: BTreeMap<i32, SigAction>,
sig_mask: u64,
pending_signal: Option<i32>,
current_tid: i32,
}
#[derive(Debug, Clone)]
struct FileMapping {
base: u32,
len: u32,
path: String,
offset: u64,
file_len: u32,
writeback: bool,
}
impl LinuxKernel {
pub fn init(&mut self, brk: u32) {
self.brk = brk;
self.brk_mapped = brk;
self.mmap_top = MMAP_BASE;
self.fds.clear();
self.fds.insert(0, Fd::Stdin);
self.fds.insert(1, Fd::Stdout);
self.fds.insert(2, Fd::Stderr);
self.next_fd = 3;
self.current_tid = 1;
self.pid = 1;
self.ppid = 0;
self.cwd = "/".to_string();
self.umask = 0o022;
self.pipes.clear();
self.sockets.clear();
}
pub fn proc_snapshot(&self) -> ProcState {
ProcState {
fds: self.fds.clone(),
next_fd: self.next_fd,
brk: self.brk,
brk_mapped: self.brk_mapped,
mmap_top: self.mmap_top,
file_maps: self.file_maps.clone(),
cwd: self.cwd.clone(),
umask: self.umask,
sigactions: self.sigactions.clone(),
sig_mask: self.sig_mask,
pending_signal: self.pending_signal,
current_tid: self.current_tid,
}
}
pub fn proc_restore(&mut self, s: ProcState) {
self.fds = s.fds;
self.next_fd = s.next_fd;
self.brk = s.brk;
self.brk_mapped = s.brk_mapped;
self.mmap_top = s.mmap_top;
self.file_maps = s.file_maps;
self.cwd = s.cwd;
self.umask = s.umask;
self.sigactions = s.sigactions;
self.sig_mask = s.sig_mask;
self.pending_signal = s.pending_signal;
self.current_tid = s.current_tid;
}
pub fn exec_reset(&mut self, brk: u32) {
self.brk = brk;
self.brk_mapped = brk;
self.mmap_top = MMAP_BASE;
self.file_maps.clear();
}
pub fn set_ids(&mut self, pid: i32, ppid: i32) {
self.pid = pid;
self.ppid = ppid;
self.current_tid = pid;
}
pub fn fork_dup_pipes(&mut self) {
let (mut rs, mut ws) = (Vec::new(), Vec::new());
for fd in self.fds.values() {
match fd {
Fd::PipeRead(i) => rs.push(*i),
Fd::PipeWrite(i) => ws.push(*i),
_ => {}
}
}
for i in rs {
self.pipes[i].readers += 1;
}
for i in ws {
self.pipes[i].writers += 1;
}
}
pub fn exit_close_pipes(&mut self) {
let (mut rs, mut ws) = (Vec::new(), Vec::new());
for fd in self.fds.values() {
match fd {
Fd::PipeRead(i) => rs.push(*i),
Fd::PipeWrite(i) => ws.push(*i),
_ => {}
}
}
for i in rs {
self.pipes[i].readers -= 1;
}
for i in ws {
self.pipes[i].writers -= 1;
}
}
pub fn pipe_readable(&self, i: usize) -> bool {
self.pipes
.get(i)
.is_some_and(|p| !p.buf.is_empty() || p.writers <= 0)
}
pub fn attach_input(&mut self, input: std::sync::Arc<InteractiveInput>) {
use std::sync::atomic::Ordering::SeqCst;
input.isig.store(self.termios.lflag & 0x1 != 0, SeqCst);
input.vintr.store(self.termios.cc[0], SeqCst);
input.vquit.store(self.termios.cc[1], SeqCst);
input.win_rows.store(self.term_size.0, SeqCst);
input.win_cols.store(self.term_size.1, SeqCst);
self.input = Some(input);
}
fn sync_input_termios(&self) {
use std::sync::atomic::Ordering::SeqCst;
if let Some(input) = &self.input {
input.isig.store(self.termios.lflag & 0x1 != 0, SeqCst);
input.vintr.store(self.termios.cc[0], SeqCst);
input.vquit.store(self.termios.cc[1], SeqCst);
}
}
pub fn take_pending_signal(&mut self) -> Option<i32> {
if let Some(input) = &self.input {
if let Some(sig) = input.take() {
return Some(sig);
}
}
self.pending_signal.take()
}
pub fn read_str_array(&self, mmu: &dyn GuestMem, addr: u32) -> Vec<String> {
let mut out = Vec::new();
let step = if self.ptr64 { 8u32 } else { 4 };
let mut p = addr;
for _ in 0..4096 {
let ptr = if self.ptr64 {
mmu.load64(p).ok().map(|v| v as u32)
} else {
mmu.load32(p).ok()
};
match ptr {
Some(0) | None => break,
Some(sp) => {
if let Some(s) = read_cstr(mmu, sp, 4096) {
out.push(s);
}
}
}
p = p.wrapping_add(step);
}
out
}
pub fn dispatch(
&mut self,
abi: &dyn LinuxAbi,
cpu: &mut dyn GuestCpu,
mmu: &mut dyn GuestMem,
vfs: &mut MountTable,
) {
self.ptr64 = abi.ptr_bits() == 64;
let nr = abi.syscall_nr(cpu);
let a = abi.syscall_args(cpu);
let Some(sys) = abi.map_syscall(nr) else {
*self.unsupported.entry(format!("nr {nr}")).or_default() += 1;
abi.set_return(cpu, ENOSYS);
return;
};
let trace = std::env::var("UD_LINUX_TRACE").is_ok();
if sys == Sysno::ArchPrctl {
if a[0] == 0x1002 {
cpu.set_tls(a[1]);
}
if trace {
eprintln!("syscall arch_prctl({:#x}, {:#x}) = 0", a[0], a[1]);
}
abi.set_return(cpu, 0);
return;
}
let ret = self.run(sys, &a, mmu, vfs, abi.stat_layout());
if trace {
eprintln!(
"syscall {sys:?}({:#x}, {:#x}, {:#x}) = {ret:#x}",
a[0], a[1], a[2]
);
}
abi.set_return(cpu, ret);
}
#[allow(clippy::too_many_lines)]
fn run(
&mut self,
sys: Sysno,
a: &[u64; 6],
mmu: &mut dyn GuestMem,
vfs: &mut MountTable,
stat_layout: StatLayout,
) -> i64 {
let (a0, a1, a2) = (a[0] as u32, a[1] as u32, a[2] as u32);
match sys {
Sysno::Write => self.sys_write(a0 as i32, a1, a2, mmu, vfs),
Sysno::Writev => self.sys_writev(a0 as i32, a1, a2, mmu, vfs),
Sysno::Read => self.sys_read(a0 as i32, a1, a2, mmu, vfs),
Sysno::Open => self.sys_openat(AT_FDCWD, a0, a1, mmu, vfs),
Sysno::Openat => self.sys_openat(a0 as i32, a1, a2, mmu, vfs),
Sysno::Close => self.sys_close(a0 as i32, vfs),
Sysno::Lseek => self.sys_lseek(a0 as i32, a1 as i32, a2, vfs),
Sysno::Brk => self.sys_brk(a0, mmu),
Sysno::Mmap => {
let off = if self.ptr64 {
a[5]
} else {
a[5].wrapping_mul(u64::from(PAGE))
};
self.sys_mmap(a0, a1, a2, a[3] as u32, a[4] as i32, off, mmu, vfs)
}
Sysno::Munmap => self.sys_munmap(a0, a1, mmu, vfs),
Sysno::Mremap => self.sys_mremap(a0, a1, a2, a[3] as u32, mmu),
Sysno::Mprotect => self.sys_mprotect(a0, a1, a2, mmu),
Sysno::Exit | Sysno::ExitGroup => {
self.exit_code = Some(a0 as i32);
0
}
Sysno::Fstat | Sysno::Stat | Sysno::Lstat | Sysno::Newfstatat => {
self.sys_stat(sys, a, mmu, vfs, stat_layout)
}
Sysno::Statx => self.sys_statx(a0 as i32, a1, a2, a[4] as u32, mmu, vfs),
Sysno::Getdents64 => self.sys_getdents64(a0 as i32, a1, a2, mmu, vfs),
Sysno::Mkdir => self.sys_mkdirat(AT_FDCWD, a0, mmu, vfs),
Sysno::Mkdirat => self.sys_mkdirat(a0 as i32, a1, mmu, vfs),
Sysno::Unlink => self.sys_unlinkat(AT_FDCWD, a0, false, mmu, vfs),
Sysno::Rmdir => self.sys_unlinkat(AT_FDCWD, a0, true, mmu, vfs),
Sysno::Unlinkat => self.sys_unlinkat(a0 as i32, a1, a2 & 0x200 != 0, mmu, vfs),
Sysno::Symlink => self.sys_symlinkat(a0, AT_FDCWD, a1, mmu, vfs),
Sysno::Symlinkat => self.sys_symlinkat(a0, a1 as i32, a2, mmu, vfs),
Sysno::Link => self.sys_linkat(AT_FDCWD, a0, AT_FDCWD, a1, mmu, vfs),
Sysno::Linkat => self.sys_linkat(a0 as i32, a1, a2 as i32, a[3] as u32, mmu, vfs),
Sysno::Truncate => self.sys_truncate(a0, a[1], mmu, vfs),
Sysno::Ftruncate => self.sys_ftruncate(a0 as i32, a[1], vfs),
Sysno::Readlink => self.sys_readlinkat(AT_FDCWD, a0, a1, a2, mmu, vfs),
Sysno::Readlinkat => self.sys_readlinkat(a0 as i32, a1, a2, a[3] as u32, mmu, vfs),
Sysno::Uname => self.sys_uname(a0, mmu),
Sysno::Getpid => i64::from(self.pid),
Sysno::Gettid => i64::from(self.pid),
Sysno::Getppid => i64::from(self.ppid),
Sysno::Getuid | Sysno::Geteuid | Sysno::Getgid | Sysno::Getegid => 0,
Sysno::Time => 0,
Sysno::ClockGettime => self.sys_clock_gettime(a1, mmu),
Sysno::Gettimeofday => self.sys_gettimeofday(a0, mmu),
Sysno::Futex => 0, Sysno::Getrandom => self.sys_getrandom(a0, a1, mmu),
Sysno::SchedYield => 0,
Sysno::Nanosleep => self.sys_nanosleep(a0, a2, mmu),
Sysno::Poll => self.sys_poll(a0, a1, i64::from(a2 as i32), mmu),
Sysno::Ppoll => {
let tmo = self.poll_timeout_from_timespec(a2, mmu);
self.sys_poll(a0, a1, tmo, mmu)
}
Sysno::Select => {
let tmo = self.poll_timeout_from_timeval(a[4] as u32, mmu);
self.sys_select(a0, a1, a2, a[3] as u32, tmo, mmu)
}
Sysno::Pselect6 => {
let tmo = self.poll_timeout_from_timespec(a[4] as u32, mmu);
self.sys_select(a0, a1, a2, a[3] as u32, tmo, mmu)
}
Sysno::Clone => ENOSYS,
Sysno::Access => self.sys_faccessat(AT_FDCWD, a0, mmu, vfs),
Sysno::Faccessat => self.sys_faccessat(a0 as i32, a1, mmu, vfs),
Sysno::Chown => 0,
Sysno::Rename => self.sys_renameat(AT_FDCWD, a0, AT_FDCWD, a1, mmu, vfs),
Sysno::Renameat | Sysno::Renameat2 => {
self.sys_renameat(a0 as i32, a1, a2 as i32, a[3] as u32, mmu, vfs)
}
Sysno::Fcntl => self.sys_fcntl(a0 as i32, a1, a2),
Sysno::Dup => self.sys_dup(a0 as i32),
Sysno::Dup2 => self.sys_dup2(a0 as i32, a1 as i32, vfs),
Sysno::Dup3 => self.sys_dup2(a0 as i32, a1 as i32, vfs),
Sysno::Chdir => self.sys_chdir(a0, mmu, vfs),
Sysno::Fchdir => self.sys_fchdir(a0 as i32),
Sysno::Umask => {
let old = self.umask;
self.umask = a0 & 0o777;
i64::from(old)
}
Sysno::Fchmod | Sysno::Fchmodat | Sysno::Utimensat => 0,
Sysno::Pread64 => self.sys_pread64(a0 as i32, a1, a2, a[3], vfs, mmu),
Sysno::Pwrite64 => self.sys_pwrite64(a0 as i32, a1, a2, a[3], vfs, mmu),
Sysno::Fallocate => self.sys_fallocate(a0 as i32, a[2], a[3], vfs),
Sysno::CopyFileRange => self.sys_copy_file_range(
a0 as i32,
a1,
a2 as i32,
a[3] as u32,
a[4] as u32,
mmu,
vfs,
),
Sysno::Readv => self.sys_readv(a0 as i32, a1, a2, mmu, vfs),
Sysno::Pipe | Sysno::Pipe2 => self.sys_pipe(a0, mmu),
Sysno::Flock => 0, Sysno::Statfs | Sysno::Fstatfs => self.sys_statfs(a1, mmu),
Sysno::Socket => self.sys_socket(a0 as i32, a1 as i32),
Sysno::Connect => self.sys_connect(a0 as i32, a1, a2, mmu),
Sysno::Sendto => self.sys_sendto(a0 as i32, a1, a2, a[4] as u32, a[5] as u32, mmu),
Sysno::Recvfrom => self.sys_recvfrom(a0 as i32, a1, a2, a[4] as u32, a[5] as u32, mmu),
Sysno::Sendmsg => self.sys_sendmsg(a0 as i32, a1, mmu),
Sysno::Recvmsg => self.sys_recvmsg(a0 as i32, a1, mmu),
Sysno::Setsockopt => 0, Sysno::Getsockopt => self.sys_getsockopt(a[3] as u32, a[4] as u32, mmu),
Sysno::Getsockname | Sysno::Getpeername => self.sys_getsockname(a1, a2, mmu),
Sysno::Shutdown => self.sys_shutdown(a0 as i32),
Sysno::Bind => 0, Sysno::Listen => ENOSYS, Sysno::Accept => ENOSYS,
Sysno::Getcwd => self.sys_getcwd(a0, a1, mmu),
Sysno::Ioctl => self.sys_ioctl(a0 as i32, a1, a2, mmu),
Sysno::ArchPrctl => 0,
Sysno::SetTidAddress => 1,
Sysno::RtSigaction => self.sys_rt_sigaction(a0 as i32, a1, a2, mmu),
Sysno::RtSigprocmask => self.sys_rt_sigprocmask(a0 as i32, a1, a2, mmu),
Sysno::RtSigreturn => 0,
Sysno::SetRobustList | Sysno::Ignored => 0,
#[allow(unreachable_patterns)]
_ => {
*self.unsupported.entry(format!("{sys:?}")).or_default() += 1;
ENOSYS
}
}
}
fn sys_write(
&mut self,
fd: i32,
buf: u32,
len: u32,
mmu: &dyn GuestMem,
vfs: &mut MountTable,
) -> i64 {
let data = match read_mem(mmu, buf, len as usize) {
Some(d) => d,
None => return EFAULT,
};
match self.fds.get(&fd) {
Some(Fd::Stdout) => {
if self.interactive {
self.write_host(false, &data);
} else {
self.stdout.extend_from_slice(&data);
}
data.len() as i64
}
Some(Fd::Stderr) => {
if self.interactive {
self.write_host(true, &data);
} else {
self.stderr.extend_from_slice(&data);
}
data.len() as i64
}
Some(Fd::Vfs { h, .. }) => vfs.write_handle(*h, &data).map_or(EBADF, |n| n as i64),
Some(&Fd::PipeWrite(i)) => {
self.pipes[i].buf.extend(data.iter().copied());
data.len() as i64
}
Some(&Fd::Socket(i)) => self.socket_send(i, &data),
Some(Fd::Stdin | Fd::Dir { .. } | Fd::PipeRead(_)) | None => EBADF,
}
}
fn read_iovec(&self, iov: u32, i: u32, mmu: &dyn GuestMem) -> Option<(u32, u32)> {
if self.ptr64 {
let base = iov.wrapping_add(i.wrapping_mul(16));
let ptr = mmu.load64(base).ok()?;
let len = mmu.load64(base.wrapping_add(8)).ok()?;
Some((ptr as u32, len as u32))
} else {
let base = iov.wrapping_add(i.wrapping_mul(8));
let ptr = mmu.load32(base).ok()?;
let len = mmu.load32(base.wrapping_add(4)).ok()?;
Some((ptr, len))
}
}
fn read_msghdr(&self, mmu: &dyn GuestMem, msg: u32) -> Option<(u32, u32, u32, u32, u32)> {
if self.ptr64 {
let name = mmu.load64(msg).ok()? as u32;
let namelen_addr = msg.wrapping_add(8);
let namelen = mmu.load32(namelen_addr).ok()?;
let iov = mmu.load64(msg.wrapping_add(16)).ok()? as u32;
let iovlen = mmu.load64(msg.wrapping_add(24)).ok()? as u32;
Some((name, namelen, namelen_addr, iov, iovlen))
} else {
let name = mmu.load32(msg).ok()?;
let namelen_addr = msg.wrapping_add(4);
let namelen = mmu.load32(namelen_addr).ok()?;
let iov = mmu.load32(msg.wrapping_add(8)).ok()?;
let iovlen = mmu.load32(msg.wrapping_add(12)).ok()?;
Some((name, namelen, namelen_addr, iov, iovlen))
}
}
fn sys_writev(
&mut self,
fd: i32,
iov: u32,
cnt: u32,
mmu: &dyn GuestMem,
vfs: &mut MountTable,
) -> i64 {
let mut total = 0i64;
for i in 0..cnt {
let Some((ptr, len)) = self.read_iovec(iov, i, mmu) else {
return EFAULT;
};
let r = self.sys_write(fd, ptr, len, mmu, vfs);
if r < 0 {
return if total > 0 { total } else { r };
}
total += r;
}
total
}
fn sys_ioctl(&mut self, fd: i32, req: u32, arg: u32, mmu: &mut dyn GuestMem) -> i64 {
let is_std = matches!(self.fds.get(&fd), Some(Fd::Stdin | Fd::Stdout | Fd::Stderr));
if !self.interactive || !is_std {
return ENOTTY;
}
match req {
0x5401 => {
let t = self.termios;
let mut b = [0u8; 36];
b[0..4].copy_from_slice(&t.iflag.to_le_bytes());
b[4..8].copy_from_slice(&t.oflag.to_le_bytes());
b[8..12].copy_from_slice(&t.cflag.to_le_bytes());
b[12..16].copy_from_slice(&t.lflag.to_le_bytes());
b[16] = t.line;
b[17..36].copy_from_slice(&t.cc);
if write_mem(mmu, arg, &b).is_none() {
return EFAULT;
}
0
}
0x5402 | 0x5403 | 0x5404 => {
let Some(b) = read_mem(mmu, arg, 36) else {
return EFAULT;
};
let rd = |o: usize| u32::from_le_bytes([b[o], b[o + 1], b[o + 2], b[o + 3]]);
self.termios.iflag = rd(0);
self.termios.oflag = rd(4);
self.termios.cflag = rd(8);
self.termios.lflag = rd(12);
self.termios.line = b[16];
self.termios.cc.copy_from_slice(&b[17..36]);
self.sync_input_termios();
0
}
0x5413 => {
let (mut rows, mut cols) = self.input.as_ref().map_or(self.term_size, |i| {
use std::sync::atomic::Ordering::SeqCst;
(i.win_rows.load(SeqCst), i.win_cols.load(SeqCst))
});
if rows == 0 {
rows = 24;
}
if cols == 0 {
cols = 80;
}
let mut b = [0u8; 8];
b[0..2].copy_from_slice(&rows.to_le_bytes());
b[2..4].copy_from_slice(&cols.to_le_bytes());
if write_mem(mmu, arg, &b).is_none() {
return EFAULT;
}
0
}
0x540f => {
let _ = write_mem(mmu, arg, &1i32.to_le_bytes());
0
}
0x541b => {
let n = self.stdin_buf.len() as i32;
let _ = write_mem(mmu, arg, &n.to_le_bytes());
0
}
0x5410 | 0x5414 | 0x5409 | 0x540b | 0x540e => 0,
_ => 0, }
}
fn sys_rt_sigaction(&mut self, sig: i32, act: u32, oldact: u32, mmu: &mut dyn GuestMem) -> i64 {
if !self.ptr64 {
return 0;
}
if oldact != 0 {
let prev = self.sigactions.get(&sig).copied().unwrap_or(SigAction {
handler: 0,
flags: 0,
restorer: 0,
mask: 0,
});
let mut b = [0u8; 32];
b[0..8].copy_from_slice(&prev.handler.to_le_bytes());
b[8..16].copy_from_slice(&prev.flags.to_le_bytes());
b[16..24].copy_from_slice(&prev.restorer.to_le_bytes());
b[24..32].copy_from_slice(&prev.mask.to_le_bytes());
let _ = write_mem(mmu, oldact, &b);
}
if act != 0 {
let Some(b) = read_mem(mmu, act, 32) else {
return EFAULT;
};
let rd = |o: usize| u64::from_le_bytes(b[o..o + 8].try_into().unwrap());
self.sigactions.insert(
sig,
SigAction {
handler: rd(0),
flags: rd(8),
restorer: rd(16),
mask: rd(24),
},
);
}
0
}
fn sys_rt_sigprocmask(
&mut self,
how: i32,
set: u32,
oldset: u32,
mmu: &mut dyn GuestMem,
) -> i64 {
if oldset != 0 {
let _ = write_mem(mmu, oldset, &self.sig_mask.to_le_bytes());
}
if set != 0 {
if let Some(b) = read_mem(mmu, set, 8) {
let m = u64::from_le_bytes(b.try_into().unwrap());
match how {
0 => self.sig_mask |= m, 1 => self.sig_mask &= !m, 2 => self.sig_mask = m, _ => {}
}
}
}
0
}
pub fn signal_disposition(&self, sig: i32) -> Option<(u64, u64, u64, u64)> {
self.sigactions
.get(&sig)
.map(|s| (s.handler, s.flags, s.restorer, s.mask))
}
pub fn sig_mask(&self) -> u64 {
self.sig_mask
}
pub fn set_sig_mask(&mut self, mask: u64) {
self.sig_mask = mask;
}
fn write_host(&self, is_err: bool, data: &[u8]) {
use std::io::Write;
let onlcr = self.termios.oflag & 0x1 != 0 && self.termios.oflag & 0x4 != 0;
let buf: std::borrow::Cow<[u8]> = if onlcr && data.contains(&b'\n') {
let mut v = Vec::with_capacity(data.len() + 8);
for &b in data {
if b == b'\n' {
v.push(b'\r');
}
v.push(b);
}
std::borrow::Cow::Owned(v)
} else {
std::borrow::Cow::Borrowed(data)
};
if is_err {
let mut e = std::io::stderr().lock();
let _ = e.write_all(&buf);
let _ = e.flush();
} else {
let mut o = std::io::stdout().lock();
let _ = o.write_all(&buf);
let _ = o.flush();
}
}
fn sys_nanosleep(&mut self, a0: u32, a2: u32, mmu: &dyn GuestMem) -> i64 {
if !self.scheduler {
return 0;
}
let req = if a0 >= 0x1000 { a0 } else { a2 };
let secs = mmu.load64(req).unwrap_or(0).min(1 << 32);
let nanos = u32::try_from(mmu.load64(req.wrapping_add(8)).unwrap_or(0))
.unwrap_or(0)
.min(999_999_999);
let dur = std::time::Duration::new(secs, nanos);
if dur.is_zero() {
return 0;
}
self.block_request = Some(BlockReason::Timer(std::time::Instant::now() + dur));
0
}
fn read_host_stdin(&mut self, max: usize) -> Option<Vec<u8>> {
use std::io::{Read, Write};
let cooked = self.termios.lflag & 0x2 != 0; let echo = self.termios.lflag & 0x8 != 0; let isig = self.termios.lflag & 0x1 != 0;
if let Some(input) = self.input.clone() {
let mut out = std::io::stdout().lock();
if !cooked {
if self.scheduler {
let mut v = Vec::new();
while v.len() < max.max(1) {
match input.try_byte() {
Some(b) => v.push(b),
None => break,
}
}
if !v.is_empty() {
return Some(v);
}
if input.at_eof() {
return Some(Vec::new());
}
self.block_request = Some(BlockReason::Stdin);
return None;
}
let first = match input.next_byte() {
InByte::Byte(b) => b,
InByte::Closed => return Some(Vec::new()),
InByte::Interrupted => return None,
};
let mut v = vec![first];
while v.len() < max.max(1) {
match input.try_byte() {
Some(b) => v.push(b),
None => break,
}
}
return Some(v);
}
loop {
let b = if self.scheduler {
match input.try_byte() {
Some(b) => b,
None => {
if input.at_eof() {
return Some(std::mem::take(&mut self.line_accum));
}
self.block_request = Some(BlockReason::Stdin);
return None; }
}
} else {
match input.next_byte() {
InByte::Byte(b) => b,
InByte::Closed => return Some(std::mem::take(&mut self.line_accum)),
InByte::Interrupted => {
self.line_accum.clear();
return None;
}
}
};
match b {
0x04 => return Some(std::mem::take(&mut self.line_accum)), b'\r' | b'\n' => {
self.line_accum.push(b'\n');
if echo {
let _ = out.write_all(b"\r\n");
let _ = out.flush();
}
return Some(std::mem::take(&mut self.line_accum));
}
0x7f | 0x08 => {
if self.line_accum.pop().is_some() && echo {
let _ = out.write_all(b"\x08 \x08");
let _ = out.flush();
}
}
c => {
self.line_accum.push(c);
if echo {
let _ = out.write_all(&[c]);
let _ = out.flush();
}
if self.line_accum.len() >= max {
return Some(std::mem::take(&mut self.line_accum));
}
}
}
}
}
let mut stdin = std::io::stdin().lock();
let mut out = std::io::stdout().lock();
if !cooked {
let mut tmp = vec![0u8; max.max(1)];
let n = stdin.read(&mut tmp).unwrap_or(0);
if isig && tmp[..n].contains(&0x03) {
self.pending_signal = Some(2); return None;
}
return Some(tmp[..n].to_vec());
}
let mut line: Vec<u8> = Vec::new();
loop {
let mut b = [0u8; 1];
match stdin.read(&mut b) {
Ok(0) | Err(_) => return Some(line), Ok(_) => {}
}
match b[0] {
0x03 if isig => {
let _ = out.write_all(b"^C\r\n");
let _ = out.flush();
self.pending_signal = Some(2); return None;
}
0x04 => {
return Some(line);
}
b'\r' | b'\n' => {
line.push(b'\n');
if echo {
let _ = out.write_all(b"\r\n");
let _ = out.flush();
}
return Some(line);
}
0x7f | 0x08 => {
if line.pop().is_some() && echo {
let _ = out.write_all(b"\x08 \x08");
let _ = out.flush();
}
}
c => {
line.push(c);
if echo {
let _ = out.write_all(&[c]);
let _ = out.flush();
}
if line.len() >= max {
return Some(line);
}
}
}
}
}
fn sys_read(
&mut self,
fd: i32,
buf: u32,
len: u32,
mmu: &mut dyn GuestMem,
vfs: &mut MountTable,
) -> i64 {
match self.fds.get(&fd) {
Some(Fd::Stdin) => {
if !self.interactive {
return 0; }
if self.stdin_buf.is_empty() {
match self.read_host_stdin(len as usize) {
None => return if self.block_request.is_some() { 0 } else { -4 },
Some(d) if d.is_empty() => return 0, Some(d) => self.stdin_buf.extend(d),
}
}
let n = (len as usize).min(self.stdin_buf.len());
let bytes: Vec<u8> = self.stdin_buf.drain(..n).collect();
if write_mem(mmu, buf, &bytes).is_none() {
return EFAULT;
}
n as i64
}
Some(Fd::Vfs { h, .. }) => {
let mut tmp = vec![0u8; len as usize];
let n = vfs.read_handle(*h, &mut tmp).unwrap_or(0);
if write_mem(mmu, buf, &tmp[..n]).is_none() {
return EFAULT;
}
n as i64
}
Some(&Fd::PipeRead(i)) => {
if self.pipes[i].buf.is_empty() {
if self.scheduler && self.pipes[i].writers > 0 {
self.block_request = Some(BlockReason::Pipe(i));
return 0; }
return 0; }
let n = (len as usize).min(self.pipes[i].buf.len());
let bytes: Vec<u8> = self.pipes[i].buf.drain(..n).collect();
if write_mem(mmu, buf, &bytes).is_none() {
return EFAULT;
}
n as i64
}
Some(&Fd::Socket(i)) => {
let mut tmp = vec![0u8; len as usize];
let n = self.socket_recv(i, &mut tmp);
if n < 0 {
return n;
}
if write_mem(mmu, buf, &tmp[..n as usize]).is_none() {
return EFAULT;
}
n
}
Some(Fd::Dir { .. }) => EISDIR,
Some(Fd::Stdout | Fd::Stderr | Fd::PipeWrite(_)) | None => EBADF,
}
}
fn resolve_at(&self, dirfd: i32, path_ptr: u32, mmu: &dyn GuestMem) -> Option<String> {
let p = read_cstr(mmu, path_ptr, 4096)?;
if p.starts_with('/') {
return Some(canonicalize("/", &p));
}
let base = if dirfd == AT_FDCWD {
self.cwd.clone()
} else {
match self.fds.get(&dirfd) {
Some(Fd::Dir { path, .. } | Fd::Vfs { path, .. }) => path.clone(),
_ => return None,
}
};
Some(canonicalize(&base, &p))
}
fn sys_renameat(
&mut self,
olddirfd: i32,
oldp: u32,
newdirfd: i32,
newp: u32,
mmu: &dyn GuestMem,
vfs: &mut MountTable,
) -> i64 {
let (Some(old), Some(new)) = (
self.resolve_at(olddirfd, oldp, mmu),
self.resolve_at(newdirfd, newp, mmu),
) else {
return EBADF;
};
if old == new {
return 0;
}
if vfs.rename_path(&old, &new).is_ok() {
return 0;
}
let Some(data) = vfs.read_file(&old) else {
return -2; };
let _ = vfs.truncate_path(&new, 0); let Some(h) = vfs.open(&new, FileAccess::Write) else {
return -13; };
vfs.write_handle(h, &data);
vfs.close(h);
let _ = vfs.unlink_path(&old);
0
}
fn sys_linkat(
&mut self,
olddirfd: i32,
oldp: u32,
newdirfd: i32,
newp: u32,
mmu: &dyn GuestMem,
vfs: &mut MountTable,
) -> i64 {
let (Some(old), Some(new)) = (
self.resolve_at(olddirfd, oldp, mmu),
self.resolve_at(newdirfd, newp, mmu),
) else {
return EBADF;
};
if vfs.hardlink_path(&old, &new).is_ok() {
return 0;
}
let Some(data) = vfs.read_file(&old) else {
return -2; };
let _ = vfs.truncate_path(&new, 0);
let Some(h) = vfs.open(&new, FileAccess::Write) else {
return -13; };
vfs.write_handle(h, &data);
vfs.close(h);
0
}
fn sys_openat(
&mut self,
dirfd: i32,
path: u32,
flags: u32,
mmu: &dyn GuestMem,
vfs: &mut MountTable,
) -> i64 {
let Some(p) = self.resolve_at(dirfd, path, mmu) else {
return EBADF;
};
let p = if flags & 0x2_0000 == 0 {
vfs.resolve_symlinks(&p)
} else {
p
};
let access = match flags & 0x3 {
0 => FileAccess::Read,
1 => FileAccess::Write,
_ => FileAccess::ReadWrite,
};
let is_dir = flags & 0x1_0000 != 0
|| vfs
.stat_path(&p)
.is_some_and(|att| matches!(att.kind, NodeKind::Dir));
if is_dir {
let fd = self.next_fd;
self.next_fd += 1;
self.fds.insert(fd, Fd::Dir { path: p, pos: 0 });
return i64::from(fd);
}
if flags & 0x40 != 0 {
vfs.write_path(&p, Vec::new()); }
match vfs.open(&p, access) {
Some(h) => {
let fd = self.next_fd;
self.next_fd += 1;
self.fds.insert(fd, Fd::Vfs { h, path: p });
i64::from(fd)
}
None => ENOENT,
}
}
fn sys_close(&mut self, fd: i32, vfs: &mut MountTable) -> i64 {
match self.fds.remove(&fd) {
Some(Fd::Vfs { h, .. }) => {
vfs.close(h);
0
}
Some(Fd::Socket(i)) => {
self.sockets[i] = SocketState::Closed;
0
}
Some(Fd::PipeRead(i)) => {
self.pipes[i].readers -= 1;
0
}
Some(Fd::PipeWrite(i)) => {
self.pipes[i].writers -= 1;
0
}
Some(_) => 0, None => EBADF,
}
}
fn sys_lseek(&mut self, fd: i32, off: i32, whence: u32, vfs: &mut MountTable) -> i64 {
match self.fds.get(&fd) {
Some(Fd::Vfs { h, .. }) => vfs
.seek_handle(*h, off, whence as u8)
.map_or(EINVAL, |p| p as i64),
_ => EINVAL,
}
}
fn sys_brk(&mut self, new: u32, mmu: &mut dyn GuestMem) -> i64 {
if new == 0 || new < self.brk {
return i64::from(self.brk);
}
let want = page_up(new);
if want > self.brk_mapped {
mmu.map_zeroed(
self.brk_mapped,
want.wrapping_sub(self.brk_mapped),
Perm::R | Perm::W,
);
self.brk_mapped = want;
}
self.brk = new;
i64::from(self.brk)
}
#[allow(clippy::too_many_arguments)]
fn sys_mmap(
&mut self,
addr: u32,
len: u32,
prot: u32,
flags: u32,
fd: i32,
offset: u64,
mmu: &mut dyn GuestMem,
vfs: &mut MountTable,
) -> i64 {
const MAP_SHARED: u32 = 0x1;
const MAP_FIXED: u32 = 0x10;
const MAP_ANONYMOUS: u32 = 0x20;
let size = page_up(len.max(1));
let base = if flags & MAP_FIXED != 0 {
addr & !(PAGE - 1)
} else {
let b = self.mmap_top;
if b.checked_add(size).is_none_or(|end| end > MMAP_LIMIT) {
return -12; }
self.mmap_top = b.wrapping_add(size);
b
};
if flags & MAP_ANONYMOUS != 0 || fd < 0 {
mmu.map_zeroed(base, size, Perm::R | Perm::W | Perm::X);
return i64::from(base);
}
let path = match self.fds.get(&fd) {
Some(Fd::Vfs { path, .. }) => path.clone(),
_ => return EBADF,
};
mmu.map_zeroed(base, size, Perm::R | Perm::W);
let data = read_file_region(vfs, &path, offset, len as usize);
let file_len = data.len() as u32;
if write_mem(mmu, base, &data).is_none() {
return EFAULT;
}
mmu.map(base, size, prot_to_perm(prot));
let writeback = (flags & MAP_SHARED != 0) && (prot & 0x2 != 0); self.file_maps.push(FileMapping {
base,
len: size,
path,
offset,
file_len,
writeback,
});
i64::from(base)
}
fn sys_mremap(
&mut self,
old_addr: u32,
old_size: u32,
new_size: u32,
flags: u32,
mmu: &mut dyn GuestMem,
) -> i64 {
const MREMAP_MAYMOVE: u32 = 1;
let rwx = Perm::R | Perm::W | Perm::X;
let old_sz = page_up(old_size.max(1));
let new_sz = page_up(new_size.max(1));
if new_sz <= old_sz {
return i64::from(old_addr);
}
let extra = new_sz - old_sz;
if old_addr.wrapping_add(old_sz) == self.mmap_top
&& self
.mmap_top
.checked_add(extra)
.is_some_and(|e| e <= MMAP_LIMIT)
{
mmu.map_zeroed(self.mmap_top, extra, rwx);
self.mmap_top = self.mmap_top.wrapping_add(extra);
return i64::from(old_addr);
}
if flags & MREMAP_MAYMOVE == 0 {
return -12; }
let base = self.mmap_top;
if base.checked_add(new_sz).is_none_or(|e| e > MMAP_LIMIT) {
return -12;
}
self.mmap_top = base.wrapping_add(new_sz);
mmu.map_zeroed(base, new_sz, rwx);
if let Some(data) = read_mem(mmu, old_addr, old_size as usize) {
let _ = write_mem(mmu, base, &data);
}
i64::from(base)
}
fn sys_munmap(&mut self, addr: u32, len: u32, mmu: &dyn GuestMem, vfs: &mut MountTable) -> i64 {
let end = addr.wrapping_add(page_up(len));
let overlapping: Vec<FileMapping> = self
.file_maps
.iter()
.filter(|m| m.base < end && m.base.wrapping_add(m.len) > addr)
.cloned()
.collect();
for m in &overlapping {
self.flush_file_mapping(m, mmu, vfs);
}
self.file_maps
.retain(|m| !(m.base < end && m.base.wrapping_add(m.len) > addr));
0
}
fn sys_mprotect(&mut self, addr: u32, len: u32, prot: u32, mmu: &mut dyn GuestMem) -> i64 {
let start = addr & !(PAGE - 1);
let size = page_up(addr.wrapping_add(len)).wrapping_sub(start);
mmu.map(start, size, prot_to_perm(prot));
0
}
fn flush_file_mapping(&self, m: &FileMapping, mmu: &dyn GuestMem, vfs: &mut MountTable) {
if !m.writeback || m.file_len == 0 {
return;
}
let Some(bytes) = read_mem(mmu, m.base, m.file_len as usize) else {
return;
};
if let Some(h) = vfs.open(&m.path, FileAccess::ReadWrite) {
vfs.seek(h, m.offset);
let _ = vfs.write_handle(h, &bytes);
vfs.close(h);
}
}
fn sys_uname(&self, buf: u32, mmu: &mut dyn GuestMem) -> i64 {
let fields = [
"Linux",
"univdreams",
"5.15.0",
"#1 univdreams",
"i686",
"(none)",
];
for (i, s) in fields.iter().enumerate() {
let off = buf.wrapping_add((i * 65) as u32);
let mut bytes = s.as_bytes().to_vec();
bytes.resize(65, 0);
if write_mem(mmu, off, &bytes).is_none() {
return EFAULT;
}
}
0
}
fn sys_getcwd(&self, buf: u32, size: u32, mmu: &mut dyn GuestMem) -> i64 {
let mut cwd = self.cwd.clone().into_bytes();
cwd.push(0);
if (size as usize) < cwd.len() {
return -34; }
if write_mem(mmu, buf, &cwd).is_none() {
return EFAULT;
}
cwd.len() as i64
}
fn sys_chdir(&mut self, path: u32, mmu: &dyn GuestMem, vfs: &mut MountTable) -> i64 {
let Some(p) = self.resolve_at(AT_FDCWD, path, mmu) else {
return EFAULT;
};
match vfs.stat_path(&p) {
Some(a) if matches!(a.kind, NodeKind::Dir) => {
self.cwd = p;
0
}
Some(_) => ENOTDIR,
None => ENOENT,
}
}
fn sys_fchdir(&mut self, fd: i32) -> i64 {
match self.fds.get(&fd) {
Some(Fd::Dir { path, .. }) => {
self.cwd = path.clone();
0
}
Some(_) => ENOTDIR,
None => EBADF,
}
}
fn sys_clock_gettime(&self, ts: u32, mmu: &mut dyn GuestMem) -> i64 {
self.write_time_pair(ts, 0, 0, mmu)
}
fn sys_gettimeofday(&self, tv: u32, mmu: &mut dyn GuestMem) -> i64 {
if tv == 0 {
return 0;
}
self.write_time_pair(tv, 0, 0, mmu)
}
fn write_time_pair(&self, addr: u32, sec: u64, frac: u64, mmu: &mut dyn GuestMem) -> i64 {
let mut buf = Vec::new();
if self.ptr64 {
buf.extend_from_slice(&sec.to_le_bytes());
buf.extend_from_slice(&frac.to_le_bytes());
} else {
buf.extend_from_slice(&(sec as u32).to_le_bytes());
buf.extend_from_slice(&(frac as u32).to_le_bytes());
}
if write_mem(mmu, addr, &buf).is_none() {
return EFAULT;
}
0
}
fn sys_getrandom(&mut self, buf: u32, len: u32, mmu: &mut dyn GuestMem) -> i64 {
let mut out = Vec::with_capacity(len as usize);
for _ in 0..len {
self.rng = self.rng.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = self.rng;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
out.push((z ^ (z >> 31)) as u8);
}
if write_mem(mmu, buf, &out).is_none() {
return EFAULT;
}
i64::from(len)
}
fn sys_stat(
&self,
sys: Sysno,
a: &[u64; 6],
mmu: &mut dyn GuestMem,
vfs: &mut MountTable,
layout: StatLayout,
) -> i64 {
let (kind, size, perm, ino, buf) = match sys {
Sysno::Fstat => {
let fd = a[0] as i32;
let buf = a[1] as u32;
match self.fds.get(&fd) {
Some(
Fd::Stdin
| Fd::Stdout
| Fd::Stderr
| Fd::PipeRead(_)
| Fd::PipeWrite(_)
| Fd::Socket(_),
) => (NodeKind::CharDevice, 0, 0o666, u64::from(fd as u32), buf),
Some(Fd::Vfs { path, .. } | Fd::Dir { path, .. }) => {
let path = path.clone();
let att = vfs.stat_path(&path);
(
att.map_or(NodeKind::File, |x| x.kind),
att.map_or(0, |x| x.size),
att.map_or(0, |x| x.mode),
inode_of(att, &path),
buf,
)
}
None => return EBADF,
}
}
sys => {
let (dirfd, path_ptr, buf, nofollow) = if sys == Sysno::Newfstatat {
(a[0] as i32, a[1] as u32, a[2] as u32, a[3] & 0x100 != 0)
} else {
(AT_FDCWD, a[0] as u32, a[1] as u32, sys == Sysno::Lstat)
};
let Some(p) = self.resolve_at(dirfd, path_ptr, mmu) else {
return EBADF;
};
let target = if nofollow {
p.clone()
} else {
vfs.resolve_symlinks(&p)
};
let att = vfs.stat_path(&target);
match att {
Some(att) => (
att.kind,
att.size,
att.mode,
inode_of(Some(att), &target),
buf,
),
None => return ENOENT,
}
}
};
self.write_stat(buf, kind, size, perm, ino, layout, mmu)
}
fn sys_statx(
&self,
dirfd: i32,
path: u32,
flags: u32,
buf: u32,
mmu: &mut dyn GuestMem,
vfs: &mut MountTable,
) -> i64 {
let Some(p) = self.resolve_at(dirfd, path, mmu) else {
return EBADF;
};
let target = if flags & 0x100 != 0 {
p.clone()
} else {
vfs.resolve_symlinks(&p)
};
let Some(att) = vfs.stat_path(&target) else {
return ENOENT;
};
let mode = stat_mode(att.kind, att.mode) as u16;
let mut b = [0u8; 256];
b[0..4].copy_from_slice(&0x7ffu32.to_le_bytes()); b[4..8].copy_from_slice(&512u32.to_le_bytes()); b[16..20].copy_from_slice(&1u32.to_le_bytes()); b[28..30].copy_from_slice(&mode.to_le_bytes()); b[32..40].copy_from_slice(&inode_of(Some(att), &target).to_le_bytes()); b[40..48].copy_from_slice(&att.size.to_le_bytes()); b[48..56].copy_from_slice(&att.size.div_ceil(512).to_le_bytes()); if write_mem(mmu, buf, &b).is_none() {
return EFAULT;
}
0
}
fn sys_faccessat(
&self,
dirfd: i32,
path: u32,
mmu: &dyn GuestMem,
vfs: &mut MountTable,
) -> i64 {
let Some(p) = self.resolve_at(dirfd, path, mmu) else {
return EBADF;
};
if vfs.stat_path(&p).is_some() {
0
} else {
ENOENT
}
}
#[allow(clippy::too_many_arguments)]
fn write_stat(
&self,
buf: u32,
kind: NodeKind,
size: u64,
perm: u32,
ino: u64,
layout: StatLayout,
mmu: &mut dyn GuestMem,
) -> i64 {
let mode = stat_mode(kind, perm);
let mut b = vec![0u8; layout.size];
let put32 = |b: &mut [u8], off: usize, v: u32| {
b[off..off + 4].copy_from_slice(&v.to_le_bytes());
};
let put64 = |b: &mut [u8], off: usize, v: u64| {
b[off..off + 8].copy_from_slice(&v.to_le_bytes());
};
put64(&mut b, layout.dev_off, 1); put64(&mut b, layout.ino_off, ino);
put32(&mut b, layout.mode_off, mode);
put32(&mut b, layout.nlink_off, 1);
put64(&mut b, layout.size_off, size);
put32(&mut b, layout.blksize_off, 512);
put64(&mut b, layout.blocks_off, size.div_ceil(512));
if write_mem(mmu, buf, &b).is_none() {
return EFAULT;
}
0
}
fn sys_getdents64(
&mut self,
fd: i32,
dirp: u32,
count: u32,
mmu: &mut dyn GuestMem,
vfs: &mut MountTable,
) -> i64 {
let Some(Fd::Dir { path, pos }) = self.fds.get(&fd) else {
return ENOTDIR;
};
let (path, start) = (path.clone(), *pos);
let entries = match vfs.readdir_path(&path) {
Ok(e) => e,
Err(_) => return ENOTDIR,
};
let mut all: Vec<(String, NodeKind)> = vec![
(".".to_string(), NodeKind::Dir),
("..".to_string(), NodeKind::Dir),
];
all.extend(entries.into_iter().map(|e| (e.name, e.kind)));
let mut off = 0u32;
let mut idx = start;
while idx < all.len() {
let (name, kind) = &all[idx];
let namelen = name.len() + 1; let reclen = (((19 + namelen) + 7) & !7) as u32; if off + reclen > count {
break;
}
let rec_base = dirp.wrapping_add(off);
let d_type: u8 = match kind {
NodeKind::Dir => 4,
NodeKind::CharDevice => 2,
NodeKind::Symlink => 10,
NodeKind::File => 8,
};
let mut rec = Vec::with_capacity(reclen as usize);
rec.extend_from_slice(&((idx as u64) + 1).to_le_bytes());
rec.extend_from_slice(&((idx as u64) + 1).to_le_bytes());
rec.extend_from_slice(&(reclen as u16).to_le_bytes());
rec.push(d_type);
rec.extend_from_slice(name.as_bytes());
rec.resize(reclen as usize, 0); if write_mem(mmu, rec_base, &rec).is_none() {
return EFAULT;
}
off += reclen;
idx += 1;
}
if off == 0 && start < all.len() {
return EINVAL;
}
if let Some(Fd::Dir { pos, .. }) = self.fds.get_mut(&fd) {
*pos = idx;
}
i64::from(off)
}
fn sys_mkdirat(
&mut self,
dirfd: i32,
path: u32,
mmu: &dyn GuestMem,
vfs: &mut MountTable,
) -> i64 {
let Some(p) = self.resolve_at(dirfd, path, mmu) else {
return EBADF;
};
vfs.mkdir_path(&p, 0o755).map_or(EINVAL, |()| 0)
}
fn sys_unlinkat(
&mut self,
dirfd: i32,
path: u32,
is_rmdir: bool,
mmu: &dyn GuestMem,
vfs: &mut MountTable,
) -> i64 {
let Some(p) = self.resolve_at(dirfd, path, mmu) else {
return EBADF;
};
let r = if is_rmdir {
vfs.rmdir_path(&p)
} else {
vfs.unlink_path(&p)
};
r.map_or(ENOENT, |()| 0)
}
fn sys_symlinkat(
&mut self,
target: u32,
dirfd: i32,
linkpath: u32,
mmu: &dyn GuestMem,
vfs: &mut MountTable,
) -> i64 {
let (Some(t), Some(l)) = (
read_cstr(mmu, target, 4096),
self.resolve_at(dirfd, linkpath, mmu),
) else {
return EFAULT;
};
vfs.symlink_path(&t, &l).map_or(EINVAL, |()| 0)
}
fn sys_truncate(
&mut self,
path: u32,
len: u64,
mmu: &dyn GuestMem,
vfs: &mut MountTable,
) -> i64 {
let Some(p) = read_cstr(mmu, path, 4096) else {
return EFAULT;
};
vfs.truncate_path(&p, len).map_or(ENOENT, |()| 0)
}
fn sys_ftruncate(&mut self, fd: i32, len: u64, vfs: &mut MountTable) -> i64 {
match self.fds.get(&fd) {
Some(Fd::Vfs { path, .. }) => {
let path = path.clone();
vfs.truncate_path(&path, len).map_or(EINVAL, |()| 0)
}
Some(Fd::Dir { .. }) => EISDIR,
_ => EBADF,
}
}
fn sys_readlinkat(
&self,
dirfd: i32,
path: u32,
buf: u32,
size: u32,
mmu: &mut dyn GuestMem,
vfs: &mut MountTable,
) -> i64 {
let Some(p) = self.resolve_at(dirfd, path, mmu) else {
return EBADF;
};
let target = match vfs.readlink_path(&p) {
Ok(t) => t,
Err(e) if e.kind() == std::io::ErrorKind::InvalidInput => return EINVAL,
Err(_) => return ENOENT,
};
let bytes = target.as_bytes();
let n = bytes.len().min(size as usize);
if write_mem(mmu, buf, &bytes[..n]).is_none() {
return EFAULT;
}
n as i64
}
fn sys_fcntl(&mut self, fd: i32, cmd: u32, arg: u32) -> i64 {
const O_NONBLOCK: u32 = 0x800;
let sock_idx = match self.fds.get(&fd) {
Some(&Fd::Socket(i)) => Some(i),
Some(_) => None,
None => return EBADF,
};
match cmd {
0 | 1030 => self.sys_dup(fd), 1 => 0, 3 => {
match sock_idx {
Some(i) if self.sock_nonblock[i] => 2 | i64::from(O_NONBLOCK),
Some(_) => 2, None => 0,
}
}
2 => 0, 4 => {
if let Some(i) = sock_idx {
let nb = arg & O_NONBLOCK != 0;
self.sock_nonblock[i] = nb;
if let SocketState::Tcp(s) = &self.sockets[i] {
let _ = s.set_nonblocking(nb);
}
}
0
}
_ => 0, }
}
fn install_fd(&mut self, value: Fd) -> i32 {
let fd = self.next_fd;
self.next_fd += 1;
self.fds.insert(fd, value);
fd
}
fn sys_pipe(&mut self, fds_ptr: u32, mmu: &mut dyn GuestMem) -> i64 {
let idx = self.pipes.len();
self.pipes.push(Pipe {
buf: std::collections::VecDeque::new(),
readers: 1,
writers: 1,
});
let rfd = self.install_fd(Fd::PipeRead(idx));
let wfd = self.install_fd(Fd::PipeWrite(idx));
if write_mem(mmu, fds_ptr, &(rfd as u32).to_le_bytes()).is_none()
|| write_mem(mmu, fds_ptr.wrapping_add(4), &(wfd as u32).to_le_bytes()).is_none()
{
return EFAULT;
}
0
}
fn sys_socket(&mut self, domain: i32, ty: i32) -> i64 {
if !self.net_enabled {
return -13; }
if domain != 2 && domain != 10 {
return -97; }
let idx = self.sockets.len();
self.sockets.push(SocketState::Pending { ty: ty & 0xff });
self.sock_nonblock.push(ty & 0x800 != 0);
i64::from(self.install_fd(Fd::Socket(idx)))
}
fn sys_connect(&mut self, fd: i32, addr: u32, len: u32, mmu: &dyn GuestMem) -> i64 {
let idx = match self.fds.get(&fd) {
Some(&Fd::Socket(i)) => i,
_ => return EBADF,
};
let Some(sa) = read_sockaddr(mmu, addr, len) else {
return EINVAL;
};
let ty = match &self.sockets[idx] {
SocketState::Pending { ty, .. } => *ty,
_ => return -56, };
if ty == 2 {
match std::net::UdpSocket::bind("0.0.0.0:0").and_then(|s| s.connect(sa).map(|()| s)) {
Ok(s) => {
self.sockets[idx] = SocketState::Udp(s);
0
}
Err(_) => -111, }
} else {
match std::net::TcpStream::connect(sa) {
Ok(s) => {
let nb = self.sock_nonblock[idx];
let _ = s.set_nonblocking(nb);
self.sockets[idx] = SocketState::Tcp(s);
if nb {
-115 } else {
0
}
}
Err(_) => -111,
}
}
}
fn socket_send(&mut self, idx: usize, data: &[u8]) -> i64 {
use std::io::Write;
match &mut self.sockets[idx] {
SocketState::Tcp(s) => s.write(data).map_or_else(io_errno, |n| n as i64),
SocketState::Udp(s) => s.send(data).map_or_else(io_errno, |n| n as i64),
_ => -107, }
}
fn socket_recv(&mut self, idx: usize, buf: &mut [u8]) -> i64 {
use std::io::Read;
match &mut self.sockets[idx] {
SocketState::Tcp(s) => s.read(buf).map_or_else(io_errno, |n| n as i64),
SocketState::Udp(s) => s.recv(buf).map_or_else(io_errno, |n| n as i64),
_ => -107,
}
}
fn socket_pollin(&self, idx: usize) -> (bool, bool) {
use std::io::ErrorKind::WouldBlock;
let restore = self.sock_nonblock[idx]; let mut probe = [0u8; 1];
match &self.sockets[idx] {
SocketState::Tcp(s) => {
let _ = s.set_nonblocking(true);
let r = s.peek(&mut probe);
let _ = s.set_nonblocking(restore);
match r {
Ok(0) => (true, true), Ok(_) => (true, false), Err(e) if e.kind() == WouldBlock => (false, false),
Err(_) => (true, true), }
}
SocketState::Udp(s) => {
let _ = s.set_nonblocking(true);
let r = s.peek_from(&mut probe);
let _ = s.set_nonblocking(restore);
match r {
Ok(_) => (true, false),
Err(e) if e.kind() == WouldBlock => (false, false),
Err(_) => (true, true),
}
}
_ => (false, false),
}
}
fn sys_sendto(
&mut self,
fd: i32,
buf: u32,
len: u32,
dest: u32,
destlen: u32,
mmu: &dyn GuestMem,
) -> i64 {
let idx = match self.fds.get(&fd) {
Some(&Fd::Socket(i)) => i,
_ => return EBADF,
};
let Some(data) = read_mem(mmu, buf, len as usize) else {
return EFAULT;
};
if dest != 0 && destlen != 0 {
let Some(sa) = read_sockaddr(mmu, dest, destlen) else {
return EINVAL;
};
if matches!(self.sockets[idx], SocketState::Pending { ty: 2, .. }) {
match std::net::UdpSocket::bind("0.0.0.0:0") {
Ok(s) => self.sockets[idx] = SocketState::Udp(s),
Err(_) => return -111,
}
}
if let SocketState::Udp(s) = &self.sockets[idx] {
return s.send_to(&data, sa).map_or(-111, |n| n as i64);
}
}
self.socket_send(idx, &data)
}
fn sys_sendmsg(&mut self, fd: i32, msg: u32, mmu: &mut dyn GuestMem) -> i64 {
let Some((name, namelen, _, iov, iovlen)) = self.read_msghdr(mmu, msg) else {
return EFAULT;
};
let mut data = Vec::new();
for i in 0..iovlen {
if let Some((p, l)) = self.read_iovec(iov, i, mmu) {
if let Some(chunk) = read_mem(mmu, p, l as usize) {
data.extend_from_slice(&chunk);
}
}
}
let idx = match self.fds.get(&fd) {
Some(&Fd::Socket(i)) => i,
_ => return EBADF,
};
if name != 0 && namelen != 0 {
if let Some(sa) = read_sockaddr(mmu, name, namelen) {
if matches!(self.sockets[idx], SocketState::Pending { ty: 2, .. }) {
if let Ok(s) = std::net::UdpSocket::bind("0.0.0.0:0") {
self.sockets[idx] = SocketState::Udp(s);
}
}
if let SocketState::Udp(s) = &self.sockets[idx] {
return s.send_to(&data, sa).map_or(-111, |n| n as i64);
}
}
}
self.socket_send(idx, &data)
}
fn sys_recvmsg(&mut self, fd: i32, msg: u32, mmu: &mut dyn GuestMem) -> i64 {
let Some((name, _, namelen_ptr, iov, _iovlen)) = self.read_msghdr(mmu, msg) else {
return EFAULT;
};
let idx = match self.fds.get(&fd) {
Some(&Fd::Socket(i)) => i,
_ => return EBADF,
};
let Some((ptr, cap)) = self.read_iovec(iov, 0, mmu) else {
return EFAULT;
};
let mut tmp = vec![0u8; cap as usize];
let n = if let SocketState::Udp(s) = &self.sockets[idx] {
match s.recv_from(&mut tmp) {
Ok((n, peer)) => {
if name != 0 {
write_sockaddr(mmu, name, namelen_ptr, peer);
}
n as i64
}
Err(_) => -111,
}
} else {
self.socket_recv(idx, &mut tmp)
};
if n < 0 {
return n;
}
if write_mem(mmu, ptr, &tmp[..n as usize]).is_none() {
return EFAULT;
}
n
}
fn sys_recvfrom(
&mut self,
fd: i32,
buf: u32,
len: u32,
src: u32,
srclen: u32,
mmu: &mut dyn GuestMem,
) -> i64 {
let idx = match self.fds.get(&fd) {
Some(&Fd::Socket(i)) => i,
_ => return EBADF,
};
let mut tmp = vec![0u8; len as usize];
let n = if let SocketState::Udp(s) = &self.sockets[idx] {
match s.recv_from(&mut tmp) {
Ok((n, peer)) => {
if src != 0 && srclen != 0 {
write_sockaddr(mmu, src, srclen, peer);
}
n as i64
}
Err(_) => -111,
}
} else {
self.socket_recv(idx, &mut tmp)
};
if n < 0 {
return n;
}
if write_mem(mmu, buf, &tmp[..n as usize]).is_none() {
return EFAULT;
}
n
}
fn sys_getsockopt(&self, optval: u32, optlen: u32, mmu: &mut dyn GuestMem) -> i64 {
if optval != 0 && optlen != 0 {
let _ = write_mem(mmu, optval, &0u32.to_le_bytes());
}
0
}
fn sys_getsockname(&self, addr: u32, addrlen: u32, mmu: &mut dyn GuestMem) -> i64 {
if addr != 0 && addrlen != 0 {
let mut sa = [0u8; 16];
sa[0] = 2; let _ = write_mem(mmu, addr, &sa);
let _ = write_mem(mmu, addrlen, &16u32.to_le_bytes());
}
0
}
fn sys_shutdown(&mut self, fd: i32) -> i64 {
match self.fds.get(&fd) {
Some(&Fd::Socket(i)) => {
if let SocketState::Tcp(s) = &self.sockets[i] {
let _ = s.shutdown(std::net::Shutdown::Both);
}
0
}
_ => EBADF,
}
}
fn sys_statfs(&self, buf: u32, mmu: &mut dyn GuestMem) -> i64 {
let mut b = [0u8; 120];
let put =
|b: &mut [u8], off: usize, v: u64| b[off..off + 8].copy_from_slice(&v.to_le_bytes());
put(&mut b, 0, 0xEF53); put(&mut b, 8, 4096); put(&mut b, 16, 1 << 20); put(&mut b, 24, 1 << 19); put(&mut b, 32, 1 << 19); put(&mut b, 40, 1 << 18); put(&mut b, 48, 1 << 17); put(&mut b, 64, 255); put(&mut b, 72, 4096); if write_mem(mmu, buf, &b).is_none() {
return EFAULT;
}
0
}
fn pipe_ref(&mut self, fd: &Fd, delta: i32) {
match *fd {
Fd::PipeRead(i) => self.pipes[i].readers += delta,
Fd::PipeWrite(i) => self.pipes[i].writers += delta,
_ => {}
}
}
fn sys_dup(&mut self, oldfd: i32) -> i64 {
match self.fds.get(&oldfd).cloned() {
Some(v) => {
let fd = self.install_fd(v.clone());
self.pipe_ref(&v, 1);
i64::from(fd)
}
None => EBADF,
}
}
fn sys_dup2(&mut self, oldfd: i32, newfd: i32, vfs: &mut MountTable) -> i64 {
let Some(v) = self.fds.get(&oldfd).cloned() else {
return EBADF;
};
if oldfd == newfd {
return i64::from(newfd);
}
match self.fds.remove(&newfd) {
Some(Fd::Vfs { h, .. }) => {
vfs.close(h);
}
Some(Fd::PipeRead(i)) => self.pipes[i].readers -= 1,
Some(Fd::PipeWrite(i)) => self.pipes[i].writers -= 1,
_ => {}
}
self.fds.insert(newfd, v.clone());
self.pipe_ref(&v, 1);
if newfd >= self.next_fd {
self.next_fd = newfd + 1;
}
i64::from(newfd)
}
fn poll_timeout_from_timespec(&self, ts: u32, mmu: &dyn GuestMem) -> i64 {
if ts == 0 {
return -1;
}
let (sec, nsec) = if self.ptr64 {
(
mmu.load64(ts).unwrap_or(0),
mmu.load64(ts.wrapping_add(8)).unwrap_or(0),
)
} else {
(
u64::from(mmu.load32(ts).unwrap_or(0)),
u64::from(mmu.load32(ts.wrapping_add(4)).unwrap_or(0)),
)
};
(sec.saturating_mul(1000) + nsec / 1_000_000) as i64
}
fn poll_timeout_from_timeval(&self, tv: u32, mmu: &dyn GuestMem) -> i64 {
if tv == 0 {
return -1;
}
let (sec, usec) = if self.ptr64 {
(
mmu.load64(tv).unwrap_or(0),
mmu.load64(tv.wrapping_add(8)).unwrap_or(0),
)
} else {
(
u64::from(mmu.load32(tv).unwrap_or(0)),
u64::from(mmu.load32(tv.wrapping_add(4)).unwrap_or(0)),
)
};
(sec.saturating_mul(1000) + usec / 1000) as i64
}
fn select_ready(&self, fd: i32, want_r: bool, want_w: bool) -> (bool, bool) {
match self.fds.get(&fd) {
Some(&Fd::Socket(idx)) => {
let w = want_w
&& matches!(self.sockets[idx], SocketState::Tcp(_) | SocketState::Udp(_));
let r = want_r && self.socket_pollin(idx).0;
(r, w)
}
Some(_) => (want_r, want_w), None => (false, false),
}
}
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
fn sys_select(
&self,
nfds: u32,
readfds: u32,
writefds: u32,
exceptfds: u32,
timeout_ms: i64,
mmu: &mut dyn GuestMem,
) -> i64 {
let nfds = nfds.min(1024);
let word_bytes: u32 = if self.ptr64 { 8 } else { 4 };
let start = std::time::Instant::now();
let deadline_ms: u128 = if timeout_ms < 0 {
120_000
} else {
timeout_ms as u128
};
loop {
let mut ready_r: Vec<u32> = Vec::new();
let mut ready_w: Vec<u32> = Vec::new();
let mut count = 0i64;
for fd in 0..nfds {
let want_r = readfds != 0 && fdset_test(mmu, readfds, fd, word_bytes);
let want_w = writefds != 0 && fdset_test(mmu, writefds, fd, word_bytes);
if !want_r && !want_w {
continue;
}
let (rok, wok) = self.select_ready(fd as i32, want_r, want_w);
if rok {
ready_r.push(fd);
count += 1;
}
if wok {
ready_w.push(fd);
count += 1;
}
}
if count > 0 || timeout_ms == 0 || start.elapsed().as_millis() >= deadline_ms {
if readfds != 0 {
fdset_write(mmu, readfds, nfds, &ready_r, word_bytes);
}
if writefds != 0 {
fdset_write(mmu, writefds, nfds, &ready_w, word_bytes);
}
if exceptfds != 0 {
fdset_write(mmu, exceptfds, nfds, &[], word_bytes); }
return count;
}
std::thread::sleep(std::time::Duration::from_millis(2));
}
}
fn sys_poll(&self, fds: u32, nfds: u32, timeout_ms: i64, mmu: &mut dyn GuestMem) -> i64 {
const POLLIN: u16 = 0x001;
const POLLOUT: u16 = 0x004;
const POLLHUP: u16 = 0x010;
const POLLNVAL: u16 = 0x020;
let start = std::time::Instant::now();
let deadline_ms: u128 = if timeout_ms < 0 {
120_000
} else {
timeout_ms as u128
};
loop {
let mut ready = 0i64;
for i in 0..nfds {
let base = fds.wrapping_add(i.wrapping_mul(8));
let (Ok(fd_raw), Ok(events)) = (mmu.load32(base), mmu.load16(base.wrapping_add(4)))
else {
return EFAULT;
};
let fd = fd_raw as i32;
let mut revents = 0u16;
match self.fds.get(&fd) {
Some(&Fd::Socket(idx)) => {
if events & POLLOUT != 0
&& matches!(
self.sockets[idx],
SocketState::Tcp(_) | SocketState::Udp(_)
)
{
revents |= POLLOUT;
}
if events & POLLIN != 0 {
let (readable, hup) = self.socket_pollin(idx);
if readable {
revents |= POLLIN;
}
if hup {
revents |= POLLHUP;
}
}
}
Some(_) => revents = events, None => revents = POLLNVAL,
}
if write_mem(mmu, base.wrapping_add(6), &revents.to_le_bytes()).is_none() {
return EFAULT;
}
if revents != 0 {
ready += 1;
}
}
if ready > 0 || timeout_ms == 0 {
return ready;
}
if start.elapsed().as_millis() >= deadline_ms {
return 0;
}
std::thread::sleep(std::time::Duration::from_millis(2));
}
}
fn sys_pread64(
&self,
fd: i32,
buf: u32,
len: u32,
offset: u64,
vfs: &mut MountTable,
mmu: &mut dyn GuestMem,
) -> i64 {
let path = match self.fds.get(&fd) {
Some(Fd::Vfs { path, .. }) => path.clone(),
Some(Fd::Dir { .. }) => return EISDIR,
_ => return EBADF,
};
let data = read_file_region(vfs, &path, offset, len as usize);
if write_mem(mmu, buf, &data).is_none() {
return EFAULT;
}
data.len() as i64
}
fn sys_pwrite64(
&self,
fd: i32,
buf: u32,
len: u32,
offset: u64,
vfs: &mut MountTable,
mmu: &dyn GuestMem,
) -> i64 {
let path = match self.fds.get(&fd) {
Some(Fd::Vfs { path, .. }) => path.clone(),
Some(Fd::Dir { .. }) => return EISDIR,
_ => return EBADF,
};
let Some(data) = read_mem(mmu, buf, len as usize) else {
return EFAULT;
};
if let Some(h) = vfs.open(&path, FileAccess::ReadWrite) {
vfs.seek(h, offset);
let n = vfs.write_handle(h, &data).unwrap_or(0);
vfs.close(h);
n as i64
} else {
EBADF
}
}
fn sys_fallocate(&mut self, fd: i32, offset: u64, len: u64, vfs: &mut MountTable) -> i64 {
let path = match self.fds.get(&fd) {
Some(Fd::Vfs { path, .. }) => path.clone(),
Some(Fd::Dir { .. }) => return EISDIR,
_ => return EBADF,
};
let want = offset.saturating_add(len);
let cur = vfs.stat_path(&path).map_or(0, |a| a.size);
if want > cur {
let _ = vfs.truncate_path(&path, want);
}
0
}
fn sys_copy_file_range(
&mut self,
fd_in: i32,
off_in: u32,
fd_out: i32,
off_out: u32,
len: u32,
mmu: &mut dyn GuestMem,
vfs: &mut MountTable,
) -> i64 {
let (in_h, in_path) = match self.fds.get(&fd_in) {
Some(Fd::Vfs { h, path }) => (*h, path.clone()),
_ => return EBADF,
};
let (out_h, out_path) = match self.fds.get(&fd_out) {
Some(Fd::Vfs { h, path }) => (*h, path.clone()),
_ => return EBADF,
};
let len = (len as usize).min(64 << 20);
let data = if off_in == 0 {
let mut buf = vec![0u8; len];
let n = vfs.read_handle(in_h, &mut buf).unwrap_or(0);
buf.truncate(n);
buf
} else {
let off = mmu.load64(off_in).unwrap_or(0);
let d = read_file_region(vfs, &in_path, off, len);
let _ = mmu.store64(off_in, off + d.len() as u64);
d
};
if data.is_empty() {
return 0;
}
let n = if off_out == 0 {
vfs.write_handle(out_h, &data).unwrap_or(0)
} else if let Some(h) = vfs.open(&out_path, FileAccess::ReadWrite) {
let off = mmu.load64(off_out).unwrap_or(0);
vfs.seek(h, off);
let w = vfs.write_handle(h, &data).unwrap_or(0);
vfs.close(h);
let _ = mmu.store64(off_out, off + w as u64);
w
} else {
return EBADF;
};
n as i64
}
fn sys_readv(
&mut self,
fd: i32,
iov: u32,
cnt: u32,
mmu: &mut dyn GuestMem,
vfs: &mut MountTable,
) -> i64 {
let mut total = 0i64;
for i in 0..cnt {
let Some((ptr, len)) = self.read_iovec(iov, i, mmu) else {
return EFAULT;
};
let r = self.sys_read(fd, ptr, len, mmu, vfs);
if r < 0 {
return if total > 0 { total } else { r };
}
total += r;
if (r as u32) < len {
break; }
}
total
}
}
fn fdset_test(mmu: &dyn GuestMem, set: u32, fd: u32, word_bytes: u32) -> bool {
let bits = word_bytes * 8;
let waddr = set.wrapping_add((fd / bits) * word_bytes);
let word = if word_bytes == 8 {
mmu.load64(waddr).unwrap_or(0)
} else {
u64::from(mmu.load32(waddr).unwrap_or(0))
};
word & (1u64 << (fd % bits)) != 0
}
fn fdset_write(mmu: &mut dyn GuestMem, set: u32, nfds: u32, ready: &[u32], word_bytes: u32) {
let bits = word_bytes * 8;
let nwords = nfds.div_ceil(bits);
let mut words = vec![0u64; nwords as usize];
for &fd in ready {
words[(fd / bits) as usize] |= 1u64 << (fd % bits);
}
for (i, w) in words.iter().enumerate() {
let waddr = set.wrapping_add(i as u32 * word_bytes);
let _ = write_mem(mmu, waddr, &w.to_le_bytes()[..word_bytes as usize]);
}
}
fn io_errno(e: std::io::Error) -> i64 {
use std::io::ErrorKind::{BrokenPipe, ConnectionReset, WouldBlock};
match e.kind() {
WouldBlock => -11, BrokenPipe => -32, ConnectionReset => -104, _ => -111, }
}
fn read_sockaddr(mmu: &dyn GuestMem, addr: u32, len: u32) -> Option<std::net::SocketAddr> {
let buf = read_mem(mmu, addr, (len as usize).clamp(8, 28))?;
let family = u16::from_le_bytes([buf[0], buf[1]]);
let port = u16::from_be_bytes([buf[2], buf[3]]);
match family {
2 => {
let ip = std::net::Ipv4Addr::new(buf[4], buf[5], buf[6], buf[7]);
Some(std::net::SocketAddr::new(ip.into(), port))
}
10 if buf.len() >= 24 => {
let mut o = [0u8; 16];
o.copy_from_slice(&buf[8..24]);
Some(std::net::SocketAddr::new(
std::net::Ipv6Addr::from(o).into(),
port,
))
}
_ => None,
}
}
fn write_sockaddr(mmu: &mut dyn GuestMem, addr: u32, addrlen_ptr: u32, peer: std::net::SocketAddr) {
let mut buf = vec![0u8; if peer.is_ipv6() { 28 } else { 16 }];
buf[2..4].copy_from_slice(&peer.port().to_be_bytes());
match peer {
std::net::SocketAddr::V4(a) => {
buf[0] = 2;
buf[4..8].copy_from_slice(&a.ip().octets());
}
std::net::SocketAddr::V6(a) => {
buf[0] = 10;
buf[8..24].copy_from_slice(&a.ip().octets());
}
}
let _ = write_mem(mmu, addr, &buf);
let _ = write_mem(mmu, addrlen_ptr, &(buf.len() as u32).to_le_bytes());
}
fn path_inode(path: &str) -> u64 {
use std::hash::{Hash, Hasher};
let mut h = std::collections::hash_map::DefaultHasher::new();
path.hash(&mut h);
h.finish() | 1 }
fn inode_of(att: Option<crate::emulator::fsmount::Attrs>, path: &str) -> u64 {
match att {
Some(a) if a.inode != 0 => a.inode,
_ => path_inode(path),
}
}
fn stat_mode(kind: NodeKind, perm: u32) -> u32 {
let type_bits = match kind {
NodeKind::Dir => 0o040_000,
NodeKind::Symlink => 0o120_000,
NodeKind::CharDevice => 0o020_000,
NodeKind::File => 0o100_000,
};
let perm = if perm & 0o7777 != 0 {
perm & 0o7777
} else {
match kind {
NodeKind::Dir => 0o755,
NodeKind::Symlink => 0o777,
_ => 0o644,
}
};
type_bits | perm
}
fn canonicalize(base: &str, path: &str) -> String {
let mut comps: Vec<&str> = Vec::new();
let first = if path.starts_with('/') { "" } else { base };
for seg in first.split('/').chain(path.split('/')) {
match seg {
"" | "." => {}
".." => {
comps.pop();
}
other => comps.push(other),
}
}
if comps.is_empty() {
return "/".to_string();
}
let mut s = String::with_capacity(path.len() + base.len() + 1);
for c in &comps {
s.push('/');
s.push_str(c);
}
s
}
fn read_mem(mmu: &dyn GuestMem, addr: u32, len: usize) -> Option<Vec<u8>> {
let mut out = Vec::with_capacity(len);
for i in 0..len {
out.push(mmu.load8(addr.wrapping_add(i as u32)).ok()?);
}
Some(out)
}
fn write_mem(mmu: &mut dyn GuestMem, addr: u32, data: &[u8]) -> Option<()> {
for (i, &b) in data.iter().enumerate() {
mmu.store8(addr.wrapping_add(i as u32), b).ok()?;
}
Some(())
}
fn read_cstr(mmu: &dyn GuestMem, addr: u32, max: usize) -> Option<String> {
let mut out = Vec::new();
for i in 0..max as u32 {
match mmu.load8(addr.wrapping_add(i)) {
Ok(0) => return Some(String::from_utf8_lossy(&out).into_owned()),
Ok(b) => out.push(b),
Err(_) => return None,
}
}
Some(String::from_utf8_lossy(&out).into_owned())
}
const fn page_up(addr: u32) -> u32 {
addr.wrapping_add(PAGE - 1) & !(PAGE - 1)
}
fn prot_to_perm(prot: u32) -> Perm {
let mut p = Perm::R;
if prot & 0x2 != 0 {
p = p | Perm::W;
}
if prot & 0x4 != 0 {
p = p | Perm::X;
}
p
}
fn read_file_region(vfs: &mut MountTable, path: &str, offset: u64, len: usize) -> Vec<u8> {
let Some(h) = vfs.open(path, FileAccess::Read) else {
return Vec::new();
};
vfs.seek(h, offset);
let mut out = Vec::with_capacity(len);
let mut buf = vec![0u8; 64 * 1024];
while out.len() < len {
let want = (len - out.len()).min(buf.len());
match vfs.read_handle(h, &mut buf[..want]) {
Some(0) | None => break,
Some(n) => out.extend_from_slice(&buf[..n]),
}
}
vfs.close(h);
out
}
#[cfg(test)]
mod tests {
use super::canonicalize;
#[test]
fn canonicalize_resolves_dots_and_relatives() {
assert_eq!(canonicalize("/", "."), "/");
assert_eq!(canonicalize("/", "etc"), "/etc");
assert_eq!(canonicalize("/usr", "bin"), "/usr/bin");
assert_eq!(canonicalize("/usr/bin", ".."), "/usr");
assert_eq!(canonicalize("/usr/bin", "../lib"), "/usr/lib");
assert_eq!(canonicalize("/a/b/c", "../../x"), "/a/x");
assert_eq!(canonicalize("/usr", "/etc/hosts"), "/etc/hosts");
assert_eq!(canonicalize("/", "a//b/./c"), "/a/b/c");
assert_eq!(canonicalize("/", "../.."), "/");
}
}