mod file_system;
mod io;
mod process;
mod signal;
pub use self::file_system::*;
pub use self::io::*;
pub use self::process::*;
pub use self::signal::*;
use super::resource::LimitPair;
use super::resource::Resource;
use super::resource::RLIM_INFINITY;
use super::AtFlags;
use super::Dir;
use super::Errno;
use super::FdFlag;
use super::FdSet;
use super::FileStat;
use super::OFlag;
use super::Result;
use super::SigmaskHow;
use super::TimeSpec;
use super::Times;
use super::AT_FDCWD;
use crate::io::Fd;
use crate::job::Pid;
use crate::job::ProcessState;
use crate::system::ChildProcessStarter;
use crate::SignalHandling;
use crate::System;
use nix::sys::stat::SFlag;
use std::borrow::Cow;
use std::cell::Cell;
use std::cell::Ref;
use std::cell::RefCell;
use std::cell::RefMut;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::collections::VecDeque;
use std::convert::Infallible;
use std::convert::TryInto;
use std::ffi::c_int;
use std::ffi::CStr;
use std::ffi::CString;
use std::ffi::OsStr;
use std::ffi::OsString;
use std::fmt::Debug;
use std::future::poll_fn;
use std::future::Future;
use std::io::SeekFrom;
use std::mem::MaybeUninit;
use std::num::NonZeroI32;
use std::ops::DerefMut as _;
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
use std::path::PathBuf;
use std::pin::Pin;
use std::rc::Rc;
use std::task::Context;
use std::task::Poll;
use std::task::Waker;
use std::time::Duration;
use std::time::Instant;
#[derive(Clone, Debug)]
pub struct VirtualSystem {
pub state: Rc<RefCell<SystemState>>,
pub process_id: Pid,
}
impl VirtualSystem {
pub fn new() -> VirtualSystem {
let mut state = SystemState::default();
let mut process = Process::with_parent_and_group(Pid(1), Pid(1));
let mut set_std_fd = |path, fd| {
let file = Rc::new(RefCell::new(INode::new([])));
state.file_system.save(path, Rc::clone(&file)).unwrap();
let body = FdBody {
open_file_description: Rc::new(RefCell::new(OpenFileDescription {
file,
offset: 0,
is_readable: true,
is_writable: true,
is_appending: true,
})),
flag: FdFlag::empty(),
};
process.set_fd(fd, body).unwrap();
};
set_std_fd("/dev/stdin", Fd::STDIN);
set_std_fd("/dev/stdout", Fd::STDOUT);
set_std_fd("/dev/stderr", Fd::STDERR);
state
.file_system
.save(
"/tmp",
Rc::new(RefCell::new(INode {
body: FileBody::Directory {
files: Default::default(),
},
permissions: Mode(0o777),
})),
)
.unwrap();
let process_id = Pid(2);
state.processes.insert(process_id, process);
let state = Rc::new(RefCell::new(state));
VirtualSystem { state, process_id }
}
pub fn current_process(&self) -> Ref<'_, Process> {
Ref::map(self.state.borrow(), |state| {
&state.processes[&self.process_id]
})
}
pub fn current_process_mut(&mut self) -> RefMut<'_, Process> {
RefMut::map(self.state.borrow_mut(), |state| {
state.processes.get_mut(&self.process_id).unwrap()
})
}
pub fn with_open_file_description<F, R>(&self, fd: Fd, f: F) -> Result<R>
where
F: FnOnce(&OpenFileDescription) -> Result<R>,
{
let process = self.current_process();
let body = process.get_fd(fd).ok_or(Errno::EBADF)?;
let ofd = body.open_file_description.borrow();
f(&ofd)
}
pub fn with_open_file_description_mut<F, R>(&mut self, fd: Fd, f: F) -> Result<R>
where
F: FnOnce(&mut OpenFileDescription) -> Result<R>,
{
let mut process = self.current_process_mut();
let body = process.get_fd_mut(fd).ok_or(Errno::EBADF)?;
let mut ofd = body.open_file_description.borrow_mut();
f(&mut ofd)
}
fn resolve_relative_path<'a>(&self, path: &'a Path) -> Cow<'a, Path> {
if path.is_absolute() {
Cow::Borrowed(path)
} else {
Cow::Owned(self.current_process().cwd.join(path))
}
}
fn resolve_existing_file(
&self,
_dir_fd: Fd,
path: &Path,
flags: AtFlags,
) -> Result<Rc<RefCell<INode>>> {
const _POSIX_SYMLOOP_MAX: i32 = 8;
let mut path = Cow::Borrowed(path);
for _count in 0.._POSIX_SYMLOOP_MAX {
let resolved_path = self.resolve_relative_path(&path);
let inode = self.state.borrow().file_system.get(&resolved_path)?;
if flags.contains(AtFlags::AT_SYMLINK_NOFOLLOW) {
return Ok(inode);
}
let inode_ref = inode.borrow();
if let FileBody::Symlink { target } = &inode_ref.body {
let mut new_path = resolved_path.into_owned();
new_path.pop();
new_path.push(target);
path = Cow::Owned(new_path);
} else {
drop(inode_ref);
return Ok(inode);
}
}
Err(Errno::ELOOP)
}
async fn block_until_running(&self) {
let waker = Rc::new(Cell::new(None));
poll_fn(|cx| {
let mut state = self.state.borrow_mut();
let Some(process) = state.processes.get_mut(&self.process_id) else {
return Poll::Ready(());
};
match process.state {
ProcessState::Running => Poll::Ready(()),
ProcessState::Halted(result) => {
if result.is_stopped() {
waker.set(Some(cx.waker().clone()));
process.wake_on_resumption(Rc::downgrade(&waker));
}
Poll::Pending
}
}
})
.await
}
}
impl Default for VirtualSystem {
fn default() -> Self {
VirtualSystem::new()
}
}
fn stat(inode: &INode) -> Result<FileStat> {
let (type_flag, size) = match &inode.body {
FileBody::Regular { content, .. } => (SFlag::S_IFREG, content.len()),
FileBody::Directory { files } => (SFlag::S_IFDIR, files.len()),
FileBody::Fifo { content, .. } => (SFlag::S_IFIFO, content.len()),
FileBody::Symlink { target } => (SFlag::S_IFLNK, target.as_os_str().len()),
};
let mut result: FileStat = unsafe { MaybeUninit::zeroed().assume_init() };
result.st_mode = type_flag.bits() | inode.permissions.0;
result.st_size = size as _;
result.st_dev = 1;
result.st_ino = std::ptr::addr_of!(*inode) as nix::libc::ino_t;
Ok(result)
}
impl System for VirtualSystem {
fn fstat(&self, fd: Fd) -> Result<FileStat> {
self.with_open_file_description(fd, |ofd| stat(&ofd.file.borrow()))
}
fn fstatat(&self, dir_fd: Fd, path: &CStr, flags: AtFlags) -> Result<FileStat> {
let path = Path::new(OsStr::from_bytes(path.to_bytes()));
let inode = self.resolve_existing_file(dir_fd, path, flags)?;
let inode = inode.borrow();
stat(&inode)
}
fn is_executable_file(&self, path: &CStr) -> bool {
let path = Path::new(OsStr::from_bytes(path.to_bytes()));
let Ok(inode) = self.resolve_existing_file(AT_FDCWD, path, AtFlags::empty()) else {
return false;
};
let inode = inode.borrow();
inode.permissions.0 & 0o111 != 0
}
fn is_directory(&self, path: &CStr) -> bool {
let path = Path::new(OsStr::from_bytes(path.to_bytes()));
let Ok(inode) = self.resolve_existing_file(AT_FDCWD, path, AtFlags::empty()) else {
return false;
};
let inode = inode.borrow();
matches!(inode.body, FileBody::Directory { .. })
}
fn pipe(&mut self) -> Result<(Fd, Fd)> {
let file = Rc::new(RefCell::new(INode {
body: FileBody::Fifo {
content: VecDeque::new(),
readers: 1,
writers: 1,
},
permissions: Mode::default(),
}));
let reader = OpenFileDescription {
file: Rc::clone(&file),
offset: 0,
is_readable: true,
is_writable: false,
is_appending: false,
};
let writer = OpenFileDescription {
file: Rc::clone(&file),
offset: 0,
is_readable: false,
is_writable: true,
is_appending: false,
};
let reader = FdBody {
open_file_description: Rc::new(RefCell::new(reader)),
flag: FdFlag::empty(),
};
let writer = FdBody {
open_file_description: Rc::new(RefCell::new(writer)),
flag: FdFlag::empty(),
};
let mut process = self.current_process_mut();
let reader = process.open_fd(reader).map_err(|_| Errno::EMFILE)?;
let writer = process.open_fd(writer).map_err(|_| {
process.close_fd(reader);
Errno::EMFILE
})?;
Ok((reader, writer))
}
fn dup(&mut self, from: Fd, to_min: Fd, flags: FdFlag) -> Result<Fd> {
let mut process = self.current_process_mut();
let mut body = process.fds.get(&from).ok_or(Errno::EBADF)?.clone();
body.flag = flags;
process.open_fd_ge(to_min, body).map_err(|_| Errno::EMFILE)
}
fn dup2(&mut self, from: Fd, to: Fd) -> Result<Fd> {
let mut process = self.current_process_mut();
let mut body = process.fds.get(&from).ok_or(Errno::EBADF)?.clone();
body.flag = FdFlag::empty();
process.set_fd(to, body).map_err(|_| Errno::EBADF)?;
Ok(to)
}
fn open(&mut self, path: &CStr, option: OFlag, mode: nix::sys::stat::Mode) -> Result<Fd> {
let path = self.resolve_relative_path(Path::new(OsStr::from_bytes(path.to_bytes())));
let mut state = self.state.borrow_mut();
let file = match state.file_system.get(&path) {
Ok(inode) => {
if option.contains(OFlag::O_EXCL) {
return Err(Errno::EEXIST);
}
if option.contains(OFlag::O_DIRECTORY)
&& !matches!(inode.borrow().body, FileBody::Directory { .. })
{
return Err(Errno::ENOTDIR);
}
if option.contains(OFlag::O_TRUNC) {
if let FileBody::Regular { content, .. } = &mut inode.borrow_mut().body {
content.clear();
};
}
inode
}
Err(Errno::ENOENT) if option.contains(OFlag::O_CREAT) => {
let mut inode = INode::new([]);
inode.permissions = Mode(mode.bits());
let inode = Rc::new(RefCell::new(inode));
state.file_system.save(&path, Rc::clone(&inode))?;
inode
}
Err(errno) => return Err(errno),
};
let (is_readable, is_writable) = match option & OFlag::O_ACCMODE {
OFlag::O_RDONLY => (true, false),
OFlag::O_WRONLY => (false, true),
OFlag::O_RDWR => (true, true),
_ => (false, false),
};
if let FileBody::Fifo {
readers, writers, ..
} = &mut file.borrow_mut().body
{
if is_readable {
*readers += 1;
}
if is_writable {
*writers += 1;
}
}
let open_file_description = Rc::new(RefCell::new(OpenFileDescription {
file,
offset: 0,
is_readable,
is_writable,
is_appending: option.contains(OFlag::O_APPEND),
}));
let body = FdBody {
open_file_description,
flag: if option.contains(OFlag::O_CLOEXEC) {
FdFlag::FD_CLOEXEC
} else {
FdFlag::empty()
},
};
let process = state.processes.get_mut(&self.process_id).unwrap();
process.open_fd(body).map_err(|_| Errno::EMFILE)
}
fn open_tmpfile(&mut self, _parent_dir: &Path) -> Result<Fd> {
let file = Rc::new(RefCell::new(INode::new([])));
let open_file_description = Rc::new(RefCell::new(OpenFileDescription {
file,
offset: 0,
is_readable: true,
is_writable: true,
is_appending: false,
}));
let body = FdBody {
open_file_description,
flag: FdFlag::empty(),
};
let mut state = self.state.borrow_mut();
let process = state.processes.get_mut(&self.process_id).unwrap();
process.open_fd(body).map_err(|_| Errno::EMFILE)
}
fn close(&mut self, fd: Fd) -> Result<()> {
self.current_process_mut().close_fd(fd);
Ok(())
}
fn fcntl_getfl(&self, fd: Fd) -> Result<OFlag> {
self.with_open_file_description(fd, |ofd| {
let mut mode = match (ofd.is_readable, ofd.is_writable) {
(true, true) => OFlag::O_RDWR,
(true, false) => OFlag::O_RDONLY,
(false, true) => OFlag::O_WRONLY,
(false, false) => todo!("unsupported mode"),
};
if ofd.is_appending {
mode |= OFlag::O_APPEND;
}
Ok(mode)
})
}
fn fcntl_setfl(&mut self, _fd: Fd, _flags: OFlag) -> Result<()> {
Ok(())
}
fn fcntl_getfd(&self, fd: Fd) -> Result<FdFlag> {
let process = self.current_process();
let body = process.get_fd(fd).ok_or(Errno::EBADF)?;
Ok(body.flag)
}
fn fcntl_setfd(&mut self, fd: Fd, flags: FdFlag) -> Result<()> {
let mut process = self.current_process_mut();
let body = process.get_fd_mut(fd).ok_or(Errno::EBADF)?;
body.flag = flags;
Ok(())
}
fn isatty(&self, _fd: Fd) -> Result<bool> {
Ok(false)
}
fn read(&mut self, fd: Fd, buffer: &mut [u8]) -> Result<usize> {
self.with_open_file_description_mut(fd, |ofd| ofd.read(buffer))
}
fn write(&mut self, fd: Fd, buffer: &[u8]) -> Result<usize> {
self.with_open_file_description_mut(fd, |ofd| ofd.write(buffer))
}
fn lseek(&mut self, fd: Fd, position: SeekFrom) -> Result<u64> {
use nix::unistd::Whence::*;
let (offset, whence) = match position {
SeekFrom::Start(offset) => {
let offset = offset.try_into().map_err(|_| Errno::EOVERFLOW)?;
(offset, SeekSet)
}
SeekFrom::End(offset) => {
let offset = offset.try_into().map_err(|_| Errno::EOVERFLOW)?;
(offset, SeekEnd)
}
SeekFrom::Current(offset) => {
let offset = offset.try_into().map_err(|_| Errno::EOVERFLOW)?;
(offset, SeekCur)
}
};
self.with_open_file_description_mut(fd, |ofd| ofd.seek(offset, whence))
.and_then(|new_offset| new_offset.try_into().map_err(|_| Errno::EOVERFLOW))
}
fn fdopendir(&mut self, fd: Fd) -> Result<Box<dyn Dir>> {
self.with_open_file_description(fd, |ofd| {
let inode = ofd.i_node();
let dir = VirtualDir::try_from(&inode.borrow().body)?;
Ok(Box::new(dir) as Box<dyn Dir>)
})
}
fn opendir(&mut self, path: &CStr) -> Result<Box<dyn Dir>> {
let fd = self.open(
path,
OFlag::O_RDONLY | OFlag::O_DIRECTORY,
nix::sys::stat::Mode::empty(),
)?;
self.fdopendir(fd)
}
fn umask(&mut self, mask: nix::sys::stat::Mode) -> nix::sys::stat::Mode {
let new_mask = Mode(mask.bits());
let old_mask = std::mem::replace(&mut self.current_process_mut().umask, new_mask);
nix::sys::stat::Mode::from_bits_retain(old_mask.0)
}
fn now(&self) -> Instant {
self.state
.borrow()
.now
.expect("SystemState::now not assigned")
}
fn times(&self) -> Result<Times> {
Ok(self.state.borrow().times)
}
fn validate_signal(&self, number: signal::RawNumber) -> Option<(signal::Name, signal::Number)> {
let non_zero = NonZeroI32::new(number)?;
let name = signal::Name::try_from_raw_virtual(number)?;
Some((name, signal::Number::from_raw_unchecked(non_zero)))
}
#[inline(always)]
fn signal_number_from_name(&self, name: signal::Name) -> Option<signal::Number> {
name.to_raw_virtual()
}
fn sigmask(
&mut self,
op: Option<(SigmaskHow, &[signal::Number])>,
old_mask: Option<&mut Vec<signal::Number>>,
) -> Result<()> {
let mut state = self.state.borrow_mut();
let process = state
.processes
.get_mut(&self.process_id)
.expect("current process not found");
if let Some(old_mask) = old_mask {
old_mask.clear();
old_mask.extend(process.blocked_signals());
}
if let Some((how, mask)) = op {
let result = process.block_signals(how, mask);
if result.process_state_changed {
let parent_pid = process.ppid;
raise_sigchld(&mut state, parent_pid);
}
}
Ok(())
}
fn sigaction(
&mut self,
signal: signal::Number,
action: SignalHandling,
) -> Result<SignalHandling> {
let mut process = self.current_process_mut();
Ok(process.set_signal_handling(signal, action))
}
fn caught_signals(&mut self) -> Vec<signal::Number> {
std::mem::take(&mut self.current_process_mut().caught_signals)
}
fn kill(
&mut self,
target: Pid,
signal: Option<signal::Number>,
) -> Pin<Box<(dyn Future<Output = Result<()>>)>> {
let result = match target {
Pid::MY_PROCESS_GROUP => {
let target_pgid = self.current_process().pgid;
send_signal_to_processes(&mut self.state.borrow_mut(), Some(target_pgid), signal)
}
Pid::ALL => send_signal_to_processes(&mut self.state.borrow_mut(), None, signal),
Pid(raw_pid) if raw_pid >= 0 => {
let mut state = self.state.borrow_mut();
match state.processes.get_mut(&target) {
Some(process) => {
if let Some(signal) = signal {
let result = process.raise_signal(signal);
if result.process_state_changed {
let parent_pid = process.ppid;
raise_sigchld(&mut state, parent_pid);
}
}
Ok(())
}
None => Err(Errno::ESRCH),
}
}
Pid(negative_pgid) => {
let target_pgid = Pid(-negative_pgid);
send_signal_to_processes(&mut self.state.borrow_mut(), Some(target_pgid), signal)
}
};
let system = self.clone();
Box::pin(async move {
system.block_until_running().await;
result
})
}
fn select(
&mut self,
readers: &mut FdSet,
writers: &mut FdSet,
timeout: Option<&TimeSpec>,
signal_mask: Option<&[signal::Number]>,
) -> Result<c_int> {
let mut process = self.current_process_mut();
if let Some(signal_mask) = signal_mask {
let save_mask = process
.blocked_signals()
.iter()
.copied()
.collect::<Vec<signal::Number>>();
let result_1 = process.block_signals(SigmaskHow::SIG_SETMASK, signal_mask);
let result_2 = process.block_signals(SigmaskHow::SIG_SETMASK, &save_mask);
assert!(!result_2.delivered);
if result_1.caught {
return Err(Errno::EINTR);
}
}
for fd in &readers.clone() {
let body = process.fds().get(&fd).ok_or(Errno::EBADF)?;
let ofd = body.open_file_description.borrow();
if !ofd.is_readable() {
return Err(Errno::EBADF);
}
if !ofd.is_ready_for_reading() {
readers.remove(fd);
}
}
for fd in &writers.clone() {
let body = process.fds().get(&fd).ok_or(Errno::EBADF)?;
let ofd = body.open_file_description.borrow();
if !ofd.is_writable() {
return Err(Errno::EBADF);
}
if !ofd.is_ready_for_writing() {
writers.remove(fd);
}
}
drop(process);
let reader_count = readers.iter().count();
let writer_count = writers.iter().count();
let count = (reader_count + writer_count).try_into().unwrap();
if count == 0 {
if let Some(timeout) = timeout {
let duration = Duration::from(*timeout);
if !duration.is_zero() {
let mut state = self.state.borrow_mut();
let now = state.now.as_mut();
let now = now.expect("now time unspecified; cannot add timeout duration");
*now += duration;
}
}
}
Ok(count)
}
fn getpid(&self) -> Pid {
self.process_id
}
fn getppid(&self) -> Pid {
self.current_process().ppid
}
fn getpgrp(&self) -> Pid {
self.current_process().pgid
}
fn setpgid(&mut self, mut pid: Pid, mut pgid: Pid) -> Result<()> {
if pgid.0 < 0 {
return Err(Errno::EINVAL);
}
if pid.0 == 0 {
pid = self.process_id;
}
if pgid.0 == 0 {
pgid = pid;
}
let mut state = self.state.borrow_mut();
if pgid != pid && !state.processes.values().any(|p| p.pgid == pgid) {
return Err(Errno::EPERM);
}
let process = state.processes.get_mut(&pid).ok_or(Errno::ESRCH)?;
if pid != self.process_id && process.ppid != self.process_id {
return Err(Errno::ESRCH);
}
if process.last_exec.is_some() {
return Err(Errno::EACCES);
}
process.pgid = pgid;
Ok(())
}
fn tcgetpgrp(&self, fd: Fd) -> Result<Pid> {
self.with_open_file_description(fd, |_| Ok(()))?;
self.state.borrow().foreground.ok_or(Errno::ENOTTY)
}
fn tcsetpgrp(&mut self, fd: Fd, pgid: Pid) -> Result<()> {
self.with_open_file_description(fd, |_| Ok(()))?;
let mut state = self.state.borrow_mut();
if !state.processes.values().any(|p| p.pgid == pgid) {
return Err(Errno::EPERM);
}
state.foreground = Some(pgid);
Ok(())
}
fn new_child_process(&mut self) -> Result<ChildProcessStarter> {
let mut state = self.state.borrow_mut();
let executor = state.executor.clone().ok_or(Errno::ENOSYS)?;
let process_id = state
.processes
.keys()
.max()
.map_or(Pid(2), |pid| Pid(pid.0 + 1));
let parent_process = &state.processes[&self.process_id];
let child_process = Process::fork_from(self.process_id, parent_process);
state.processes.insert(process_id, child_process);
drop(state);
let state = Rc::clone(&self.state);
Ok(Box::new(move |parent_env, task| {
Box::pin(async move {
let mut system = VirtualSystem { state, process_id };
let mut child_env = parent_env.clone_with_system(Box::new(system.clone()));
{
let mut process = system.current_process_mut();
process.selector = Rc::downgrade(&child_env.system.0);
}
let run_task_and_set_exit_status = Box::pin(async move {
let mut runner = ProcessRunner {
task: task(&mut child_env),
system,
waker: Rc::new(Cell::new(None)),
};
(&mut runner).await;
let ProcessRunner { system, .. } = { runner };
let mut state = system.state.borrow_mut();
let process = state
.processes
.get_mut(&process_id)
.expect("missing child process");
if process.state == ProcessState::Running
&& process.set_state(ProcessState::exited(child_env.exit_status))
{
let ppid = process.ppid;
raise_sigchld(&mut state, ppid);
}
});
executor
.spawn(run_task_and_set_exit_status)
.expect("the executor failed to start the child process task");
process_id
})
}))
}
fn wait(&mut self, target: Pid) -> Result<Option<(Pid, ProcessState)>> {
let parent_pid = self.process_id;
let mut state = self.state.borrow_mut();
if let Some((pid, process)) = state.child_to_wait_for(parent_pid, target) {
if process.state_has_changed() {
Ok(Some((pid, process.take_state())))
} else if process.state().is_alive() {
Ok(None)
} else {
Err(Errno::ECHILD)
}
} else {
Err(Errno::ECHILD)
}
}
fn execve(&mut self, path: &CStr, args: &[CString], envs: &[CString]) -> Result<Infallible> {
let os_path = OsStr::from_bytes(path.to_bytes());
let mut state = self.state.borrow_mut();
let fs = &state.file_system;
let file = fs.get(os_path)?;
let is_executable = matches!(
&file.borrow().body,
FileBody::Regular {
is_native_executable: true,
..
}
);
if is_executable {
let process = state.processes.get_mut(&self.process_id).unwrap();
let path = path.to_owned();
let args = args.to_owned();
let envs = envs.to_owned();
process.last_exec = Some((path, args, envs));
Err(Errno::ENOSYS)
} else {
Err(Errno::ENOEXEC)
}
}
fn getcwd(&self) -> Result<PathBuf> {
Ok(self.current_process().cwd.clone())
}
fn chdir(&mut self, path: &CStr) -> Result<()> {
let path = Path::new(OsStr::from_bytes(path.to_bytes()));
let inode = self.resolve_existing_file(AT_FDCWD, path, AtFlags::empty())?;
if matches!(&inode.borrow().body, FileBody::Directory { .. }) {
let mut process = self.current_process_mut();
let new_path = process.cwd.join(path);
process.chdir(new_path);
Ok(())
} else {
Err(Errno::ENOTDIR)
}
}
fn getpwnam_dir(&self, name: &str) -> Result<Option<PathBuf>> {
let state = self.state.borrow();
Ok(state.home_dirs.get(name).cloned())
}
fn confstr_path(&self) -> Result<OsString> {
let path = self.state.borrow().path.clone();
if path.is_empty() {
Err(Errno::ENOSYS)
} else {
Ok(path)
}
}
fn shell_path(&self) -> CString {
c"/bin/sh".to_owned()
}
fn getrlimit(&self, resource: Resource) -> std::io::Result<LimitPair> {
let process = self.current_process();
Ok(process
.resource_limits
.get(&resource)
.copied()
.unwrap_or(LimitPair {
soft: RLIM_INFINITY,
hard: RLIM_INFINITY,
}))
}
fn setrlimit(&mut self, resource: Resource, limits: LimitPair) -> std::io::Result<()> {
if limits.soft_exceeds_hard() {
return Err(std::io::Error::from_raw_os_error(nix::libc::EINVAL));
}
let mut process = self.current_process_mut();
use std::collections::hash_map::Entry::{Occupied, Vacant};
match process.resource_limits.entry(resource) {
Occupied(occupied) => {
let occupied = occupied.into_mut();
if limits.hard > occupied.hard {
return Err(std::io::Error::from_raw_os_error(nix::libc::EPERM));
}
*occupied = limits;
}
Vacant(vacant) => {
vacant.insert(limits);
}
}
Ok(())
}
}
fn send_signal_to_processes(
state: &mut SystemState,
target_pgid: Option<Pid>,
signal: Option<signal::Number>,
) -> Result<()> {
let mut results = Vec::new();
if let Some(signal) = signal {
for (&_pid, process) in &mut state.processes {
if target_pgid.map_or(true, |target_pgid| process.pgid == target_pgid) {
let result = process.raise_signal(signal);
results.push((result, process.ppid));
}
}
}
if results.is_empty() {
Err(Errno::ESRCH)
} else {
for (result, ppid) in results {
if result.process_state_changed {
raise_sigchld(state, ppid);
}
}
Ok(())
}
}
fn raise_sigchld(state: &mut SystemState, target_pid: Pid) {
if let Some(target) = state.processes.get_mut(&target_pid) {
let result = target.raise_signal(signal::SIGCHLD);
assert!(!result.process_state_changed);
}
}
#[derive(Clone, Debug, Default)]
pub struct SystemState {
pub now: Option<Instant>,
pub times: Times,
pub executor: Option<Rc<dyn Executor>>,
pub processes: BTreeMap<Pid, Process>,
pub foreground: Option<Pid>,
pub file_system: FileSystem,
pub home_dirs: HashMap<String, PathBuf>,
pub path: OsString,
}
impl SystemState {
pub fn select_all(this: &RefCell<Self>) {
let mut selectors = Vec::new();
for process in this.borrow().processes.values() {
if let Some(selector) = process.selector.upgrade() {
selectors.push(selector);
}
}
for selector in selectors {
selector.borrow_mut().select(false).ok();
}
}
fn child_to_wait_for(&mut self, parent_pid: Pid, target: Pid) -> Option<(Pid, &mut Process)> {
match target.0 {
0 => todo!("wait target {}", target),
-1 => {
let mut result = None;
for (pid, process) in &mut self.processes {
if process.ppid == parent_pid {
let changed = process.state_has_changed();
result = Some((*pid, process));
if changed {
break;
}
}
}
result
}
raw if raw >= 0 => {
let process = self.processes.get_mut(&target)?;
if process.ppid == parent_pid {
Some((target, process))
} else {
None
}
}
_target => todo!("wait target {}", target),
}
}
}
pub trait Executor: Debug {
fn spawn(
&self,
task: Pin<Box<dyn Future<Output = ()>>>,
) -> std::result::Result<(), Box<dyn std::error::Error>>;
}
struct ProcessRunner<'a> {
task: Pin<Box<dyn Future<Output = ()> + 'a>>,
system: VirtualSystem,
waker: Rc<Cell<Option<Waker>>>,
}
impl Future for ProcessRunner<'_> {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
let this = self.deref_mut();
let process_state = this.system.current_process().state;
if process_state == ProcessState::Running {
let poll = this.task.as_mut().poll(cx);
if poll == Poll::Ready(()) {
return Poll::Ready(());
}
}
let mut process = this.system.current_process_mut();
match process.state {
ProcessState::Running => Poll::Pending,
ProcessState::Halted(result) => {
if result.is_stopped() {
this.waker.set(Some(cx.waker().clone()));
process.wake_on_resumption(Rc::downgrade(&this.waker));
Poll::Pending
} else {
Poll::Ready(())
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::job::ProcessResult;
use crate::semantics::ExitStatus;
use crate::Env;
use assert_matches::assert_matches;
use futures_executor::LocalPool;
use futures_util::FutureExt;
use std::ffi::CString;
use std::ffi::OsString;
use std::future::pending;
impl Executor for futures_executor::LocalSpawner {
fn spawn(
&self,
task: Pin<Box<dyn Future<Output = ()>>>,
) -> std::result::Result<(), Box<dyn std::error::Error>> {
use futures_util::task::LocalSpawnExt;
self.spawn_local(task)
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
}
}
#[test]
fn fstatat_non_existent_file() {
let system = VirtualSystem::new();
assert_matches!(
system.fstatat(Fd(0), c"/no/such/file", AtFlags::empty()),
Err(Errno::ENOENT)
);
}
#[test]
fn fstatat_regular_file() {
let system = VirtualSystem::new();
let path = "/some/file";
let content = Rc::new(RefCell::new(INode::new([1, 2, 3, 42, 100])));
let mut state = system.state.borrow_mut();
state.file_system.save(path, content).unwrap();
drop(state);
let stat = system
.fstatat(Fd(0), c"/some/file", AtFlags::empty())
.unwrap();
assert_eq!(stat.st_mode, SFlag::S_IFREG.bits() | Mode::default().0);
assert_eq!(stat.st_size, 5);
}
#[test]
fn fstatat_directory() {
let system = VirtualSystem::new();
let path = "/some/file";
let content = Rc::new(RefCell::new(INode::new([])));
let mut state = system.state.borrow_mut();
state.file_system.save(path, content).unwrap();
drop(state);
let stat = system.fstatat(Fd(0), c"/some/", AtFlags::empty()).unwrap();
assert_eq!(stat.st_mode, SFlag::S_IFDIR.bits() | 0o755);
}
#[test]
fn fstatat_fifo() {
let system = VirtualSystem::new();
let path = "/some/fifo";
let content = Rc::new(RefCell::new(INode {
body: FileBody::Fifo {
content: [17; 42].into(),
readers: 0,
writers: 0,
},
permissions: Mode::default(),
}));
let mut state = system.state.borrow_mut();
state.file_system.save(path, content).unwrap();
drop(state);
let stat = system
.fstatat(Fd(0), c"/some/fifo", AtFlags::empty())
.unwrap();
assert_eq!(stat.st_mode, SFlag::S_IFIFO.bits() | Mode::default().0);
assert_eq!(stat.st_size, 42);
}
fn system_with_symlink() -> VirtualSystem {
let system = VirtualSystem::new();
let mut state = system.state.borrow_mut();
state
.file_system
.save("/some/file", Rc::new(RefCell::new(INode::new([]))))
.unwrap();
state
.file_system
.save(
"/link",
Rc::new(RefCell::new(INode {
body: FileBody::Symlink {
target: "some/file".into(),
},
permissions: Mode::default(),
})),
)
.unwrap();
drop(state);
system
}
#[test]
fn fstatat_symlink_to_regular_file() {
let system = system_with_symlink();
let stat = system.fstatat(Fd(0), c"/link", AtFlags::empty()).unwrap();
assert_eq!(stat.st_mode, SFlag::S_IFREG.bits() | Mode::default().0);
}
#[test]
fn fstatat_symlink_no_follow() {
let system = system_with_symlink();
let stat = system
.fstatat(Fd(0), c"/link", AtFlags::AT_SYMLINK_NOFOLLOW)
.unwrap();
assert_eq!(stat.st_mode, SFlag::S_IFLNK.bits() | Mode::default().0);
}
#[test]
fn is_executable_file_non_existing_file() {
let system = VirtualSystem::new();
assert!(!system.is_executable_file(c"/no/such/file"));
}
#[test]
fn is_executable_file_existing_but_non_executable_file() {
let system = VirtualSystem::new();
let path = "/some/file";
let content = Rc::new(RefCell::new(INode::default()));
let mut state = system.state.borrow_mut();
state.file_system.save(path, content).unwrap();
drop(state);
assert!(!system.is_executable_file(c"/some/file"));
}
#[test]
fn is_executable_file_with_executable_file() {
let system = VirtualSystem::new();
let path = "/some/file";
let mut content = INode::default();
content.permissions.0 |= 0o100;
let content = Rc::new(RefCell::new(content));
let mut state = system.state.borrow_mut();
state.file_system.save(path, content).unwrap();
drop(state);
assert!(system.is_executable_file(c"/some/file"));
}
#[test]
fn pipe_read_write() {
let mut system = VirtualSystem::new();
let (reader, writer) = system.pipe().unwrap();
let result = system.write(writer, &[5, 42, 29]);
assert_eq!(result, Ok(3));
let mut buffer = [1; 4];
let result = system.read(reader, &mut buffer);
assert_eq!(result, Ok(3));
assert_eq!(buffer, [5, 42, 29, 1]);
let result = system.close(writer);
assert_eq!(result, Ok(()));
let result = system.read(reader, &mut buffer);
assert_eq!(result, Ok(0));
}
#[test]
fn dup_shares_open_file_description() {
let mut system = VirtualSystem::new();
let result = system.dup(Fd::STDOUT, Fd::STDERR, FdFlag::empty());
assert_eq!(result, Ok(Fd(3)));
let process = system.current_process();
let fd1 = process.fds.get(&Fd(1)).unwrap();
let fd3 = process.fds.get(&Fd(3)).unwrap();
assert_eq!(fd1, fd3);
}
#[test]
fn dup_can_set_cloexec() {
let mut system = VirtualSystem::new();
let result = system.dup(Fd::STDOUT, Fd::STDERR, FdFlag::FD_CLOEXEC);
assert_eq!(result, Ok(Fd(3)));
let process = system.current_process();
let fd3 = process.fds.get(&Fd(3)).unwrap();
assert_eq!(fd3.flag, FdFlag::FD_CLOEXEC);
}
#[test]
fn dup2_shares_open_file_description() {
let mut system = VirtualSystem::new();
let result = system.dup2(Fd::STDOUT, Fd(5));
assert_eq!(result, Ok(Fd(5)));
let process = system.current_process();
let fd1 = process.fds.get(&Fd(1)).unwrap();
let fd5 = process.fds.get(&Fd(5)).unwrap();
assert_eq!(fd1, fd5);
}
#[test]
fn dup2_clears_cloexec() {
let mut system = VirtualSystem::new();
let mut process = system.current_process_mut();
process.fds.get_mut(&Fd::STDOUT).unwrap().flag = FdFlag::FD_CLOEXEC;
drop(process);
let result = system.dup2(Fd::STDOUT, Fd(6));
assert_eq!(result, Ok(Fd(6)));
let process = system.current_process();
let fd6 = process.fds.get(&Fd(6)).unwrap();
assert_eq!(fd6.flag, FdFlag::empty());
}
#[test]
fn open_non_existing_file_no_creation() {
let mut system = VirtualSystem::new();
let result = system.open(
c"/no/such/file",
OFlag::O_RDONLY,
nix::sys::stat::Mode::empty(),
);
assert_eq!(result, Err(Errno::ENOENT));
}
#[test]
fn open_creating_non_existing_file() {
let mut system = VirtualSystem::new();
let result = system.open(
c"new_file",
OFlag::O_WRONLY | OFlag::O_CREAT,
nix::sys::stat::Mode::empty(),
);
assert_eq!(result, Ok(Fd(3)));
system.write(Fd(3), &[42, 123]).unwrap();
let file = system.state.borrow().file_system.get("new_file").unwrap();
let file = file.borrow();
assert_matches!(&file.body, FileBody::Regular { content, .. } => {
assert_eq!(content[..], [42, 123]);
});
}
#[test]
fn open_existing_file() {
let mut system = VirtualSystem::new();
let fd = system
.open(
c"file",
OFlag::O_WRONLY | OFlag::O_CREAT,
nix::sys::stat::Mode::all(),
)
.unwrap();
system.write(fd, &[75, 96, 133]).unwrap();
let result = system.open(c"file", OFlag::O_RDONLY, nix::sys::stat::Mode::empty());
assert_eq!(result, Ok(Fd(4)));
let mut buffer = [0; 5];
let count = system.read(Fd(4), &mut buffer).unwrap();
assert_eq!(count, 3);
assert_eq!(buffer, [75, 96, 133, 0, 0]);
let count = system.read(Fd(4), &mut buffer).unwrap();
assert_eq!(count, 0);
}
#[test]
fn open_existing_file_excl() {
let mut system = VirtualSystem::new();
let first = system.open(
c"my_file",
OFlag::O_WRONLY | OFlag::O_CREAT | OFlag::O_EXCL,
nix::sys::stat::Mode::empty(),
);
assert_eq!(first, Ok(Fd(3)));
let second = system.open(
c"my_file",
OFlag::O_WRONLY | OFlag::O_CREAT | OFlag::O_EXCL,
nix::sys::stat::Mode::empty(),
);
assert_eq!(second, Err(Errno::EEXIST));
}
#[test]
fn open_truncating() {
let mut system = VirtualSystem::new();
let fd = system
.open(
c"file",
OFlag::O_WRONLY | OFlag::O_CREAT,
nix::sys::stat::Mode::all(),
)
.unwrap();
system.write(fd, &[1, 2, 3]).unwrap();
let result = system.open(
c"file",
OFlag::O_WRONLY | OFlag::O_TRUNC,
nix::sys::stat::Mode::empty(),
);
assert_eq!(result, Ok(Fd(4)));
let reader = system
.open(c"file", OFlag::O_RDONLY, nix::sys::stat::Mode::empty())
.unwrap();
let count = system.read(reader, &mut [0; 1]).unwrap();
assert_eq!(count, 0);
}
#[test]
fn open_appending() {
let mut system = VirtualSystem::new();
let fd = system
.open(
c"file",
OFlag::O_WRONLY | OFlag::O_CREAT,
nix::sys::stat::Mode::all(),
)
.unwrap();
system.write(fd, &[1, 2, 3]).unwrap();
let result = system.open(
c"file",
OFlag::O_WRONLY | OFlag::O_APPEND,
nix::sys::stat::Mode::empty(),
);
assert_eq!(result, Ok(Fd(4)));
system.write(Fd(4), &[4, 5, 6]).unwrap();
let reader = system
.open(c"file", OFlag::O_RDONLY, nix::sys::stat::Mode::empty())
.unwrap();
let mut buffer = [0; 7];
let count = system.read(reader, &mut buffer).unwrap();
assert_eq!(count, 6);
assert_eq!(buffer, [1, 2, 3, 4, 5, 6, 0]);
}
#[test]
fn open_directory() {
let mut system = VirtualSystem::new();
let _ = system.open(
c"/dir/file",
OFlag::O_WRONLY | OFlag::O_CREAT,
nix::sys::stat::Mode::empty(),
);
let result = system.open(
c"/dir",
OFlag::O_RDONLY | OFlag::O_DIRECTORY,
nix::sys::stat::Mode::empty(),
);
assert_eq!(result, Ok(Fd(4)));
}
#[test]
fn open_non_directory_path_prefix() {
let mut system = VirtualSystem::new();
let _ = system.open(
c"/file",
OFlag::O_WRONLY | OFlag::O_CREAT,
nix::sys::stat::Mode::empty(),
);
let result = system.open(
c"/file/file",
OFlag::O_WRONLY | OFlag::O_CREAT,
nix::sys::stat::Mode::empty(),
);
assert_eq!(result, Err(Errno::ENOTDIR));
}
#[test]
fn open_non_directory_file() {
let mut system = VirtualSystem::new();
let _ = system.open(
c"/file",
OFlag::O_WRONLY | OFlag::O_CREAT,
nix::sys::stat::Mode::empty(),
);
let result = system.open(
c"/file",
OFlag::O_RDONLY | OFlag::O_DIRECTORY,
nix::sys::stat::Mode::empty(),
);
assert_eq!(result, Err(Errno::ENOTDIR));
}
#[test]
fn open_default_working_directory() {
let mut system = VirtualSystem::new();
let writer = system.open(
c"/dir/file",
OFlag::O_WRONLY | OFlag::O_CREAT,
nix::sys::stat::Mode::all(),
);
system.write(writer.unwrap(), &[1, 2, 3, 42]).unwrap();
let reader = system.open(
c"./dir/file",
OFlag::O_RDONLY,
nix::sys::stat::Mode::empty(),
);
let mut buffer = [0; 10];
let count = system.read(reader.unwrap(), &mut buffer).unwrap();
assert_eq!(count, 4);
assert_eq!(buffer[0..4], [1, 2, 3, 42]);
}
#[test]
fn open_tmpfile() {
let mut system = VirtualSystem::new();
let fd = system.open_tmpfile(Path::new("")).unwrap();
system.write(fd, &[42, 17, 75]).unwrap();
system.lseek(fd, SeekFrom::Start(0)).unwrap();
let mut buffer = [0; 4];
let count = system.read(fd, &mut buffer).unwrap();
assert_eq!(count, 3);
assert_eq!(buffer[..3], [42, 17, 75]);
}
#[test]
fn close() {
let mut system = VirtualSystem::new();
let result = system.close(Fd::STDERR);
assert_eq!(result, Ok(()));
assert_eq!(system.current_process().fds.get(&Fd::STDERR), None);
let result = system.close(Fd::STDERR);
assert_eq!(result, Ok(()));
}
#[test]
fn fcntl_getfl() {
let mut system = VirtualSystem::new();
let mode = nix::sys::stat::Mode::all();
let reader = system.open(c"/dev/stdin", OFlag::O_RDONLY, mode).unwrap();
let writer = system.open(c"/dev/stdout", OFlag::O_WRONLY, mode).unwrap();
let appender = system
.open(c"/dev/stdout", OFlag::O_RDWR | OFlag::O_APPEND, mode)
.unwrap();
assert_eq!(system.fcntl_getfl(reader), Ok(OFlag::O_RDONLY));
assert_eq!(system.fcntl_getfl(writer), Ok(OFlag::O_WRONLY));
assert_eq!(
system.fcntl_getfl(appender),
Ok(OFlag::O_RDWR | OFlag::O_APPEND)
);
assert_eq!(system.fcntl_getfl(Fd(100)), Err(Errno::EBADF));
}
#[test]
fn fcntl_getfd_and_setfd() {
let mut system = VirtualSystem::new();
let flags = system.fcntl_getfd(Fd::STDIN).unwrap();
assert_eq!(flags, FdFlag::empty());
system.fcntl_setfd(Fd::STDIN, FdFlag::FD_CLOEXEC).unwrap();
let flags = system.fcntl_getfd(Fd::STDIN).unwrap();
assert_eq!(flags, FdFlag::FD_CLOEXEC);
let flags = system.fcntl_getfd(Fd::STDOUT).unwrap();
assert_eq!(flags, FdFlag::empty());
system.fcntl_setfd(Fd::STDIN, FdFlag::empty()).unwrap();
let flags = system.fcntl_getfd(Fd::STDIN).unwrap();
assert_eq!(flags, FdFlag::empty());
}
#[test]
fn opendir_default_working_directory() {
let mut system = VirtualSystem::new();
let _ = system.open(
c"/dir/file",
OFlag::O_WRONLY | OFlag::O_CREAT,
nix::sys::stat::Mode::all(),
);
let mut dir = system.opendir(c"./dir").unwrap();
let mut files = Vec::new();
while let Some(entry) = dir.next().unwrap() {
files.push(entry.name.to_os_string());
}
files.sort_unstable();
assert_eq!(
files[..],
[
OsString::from("."),
OsString::from(".."),
OsString::from("file")
]
);
}
#[test]
fn kill_process() {
let mut system = VirtualSystem::new();
system
.kill(system.process_id, None)
.now_or_never()
.unwrap()
.unwrap();
assert_eq!(system.current_process().state(), ProcessState::Running);
let result = system.kill(system.process_id, Some(SIGINT)).now_or_never();
assert_eq!(result, None);
assert_eq!(
system.current_process().state(),
ProcessState::Halted(ProcessResult::Signaled {
signal: SIGINT,
core_dump: false
})
);
let mut system = VirtualSystem::new();
let state = system.state.borrow();
let max_pid = *state.processes.keys().max().unwrap();
drop(state);
let e = system
.kill(Pid(max_pid.0 + 1), Some(SIGINT))
.now_or_never()
.unwrap()
.unwrap_err();
assert_eq!(e, Errno::ESRCH);
}
#[test]
fn kill_all_processes() {
let mut system = VirtualSystem::new();
let pgid = system.current_process().pgid;
let mut state = system.state.borrow_mut();
state.processes.insert(
Pid(10),
Process::with_parent_and_group(system.process_id, pgid),
);
state.processes.insert(
Pid(11),
Process::with_parent_and_group(system.process_id, pgid),
);
state
.processes
.insert(Pid(21), Process::with_parent_and_group(Pid(10), Pid(21)));
drop(state);
let result = system.kill(Pid::ALL, Some(SIGTERM)).now_or_never();
assert_eq!(result, None);
let state = system.state.borrow();
for process in state.processes.values() {
assert_eq!(
process.state,
ProcessState::Halted(ProcessResult::Signaled {
signal: SIGTERM,
core_dump: false
})
);
}
}
#[test]
fn kill_processes_in_same_group() {
let mut system = VirtualSystem::new();
let pgid = system.current_process().pgid;
let mut state = system.state.borrow_mut();
state.processes.insert(
Pid(10),
Process::with_parent_and_group(system.process_id, pgid),
);
state.processes.insert(
Pid(11),
Process::with_parent_and_group(system.process_id, pgid),
);
state
.processes
.insert(Pid(21), Process::with_parent_and_group(Pid(10), Pid(21)));
drop(state);
let result = system
.kill(Pid::MY_PROCESS_GROUP, Some(SIGQUIT))
.now_or_never();
assert_eq!(result, None);
let state = system.state.borrow();
assert_eq!(
state.processes[&system.process_id].state,
ProcessState::Halted(ProcessResult::Signaled {
signal: SIGQUIT,
core_dump: true
})
);
assert_eq!(
state.processes[&Pid(10)].state,
ProcessState::Halted(ProcessResult::Signaled {
signal: SIGQUIT,
core_dump: true
})
);
assert_eq!(
state.processes[&Pid(11)].state,
ProcessState::Halted(ProcessResult::Signaled {
signal: SIGQUIT,
core_dump: true
})
);
assert_eq!(state.processes[&Pid(21)].state, ProcessState::Running);
}
#[test]
fn kill_process_group() {
let mut system = VirtualSystem::new();
let pgid = system.current_process().pgid;
let mut state = system.state.borrow_mut();
state.processes.insert(
Pid(10),
Process::with_parent_and_group(system.process_id, pgid),
);
state.processes.insert(
Pid(11),
Process::with_parent_and_group(system.process_id, Pid(21)),
);
state
.processes
.insert(Pid(21), Process::with_parent_and_group(Pid(10), Pid(21)));
drop(state);
system
.kill(Pid(-21), Some(SIGHUP))
.now_or_never()
.unwrap()
.unwrap();
let state = system.state.borrow();
assert_eq!(
state.processes[&system.process_id].state,
ProcessState::Running
);
assert_eq!(state.processes[&Pid(10)].state, ProcessState::Running);
assert_eq!(
state.processes[&Pid(11)].state,
ProcessState::Halted(ProcessResult::Signaled {
signal: SIGHUP,
core_dump: false
})
);
assert_eq!(
state.processes[&Pid(21)].state,
ProcessState::Halted(ProcessResult::Signaled {
signal: SIGHUP,
core_dump: false
})
);
}
#[test]
fn kill_returns_success_even_if_process_state_did_not_change() {
let mut system = VirtualSystem::new();
let pgid = system.current_process().pgid;
let mut state = system.state.borrow_mut();
state.processes.insert(
Pid(10),
Process::with_parent_and_group(system.process_id, pgid),
);
drop(state);
system
.kill(-pgid, Some(SIGCONT))
.now_or_never()
.unwrap()
.unwrap();
let state = system.state.borrow();
assert_eq!(state.processes[&Pid(10)].state, ProcessState::Running);
}
#[test]
fn select_regular_file_is_always_ready() {
let mut system = VirtualSystem::new();
let mut readers = FdSet::new();
readers.insert(Fd::STDIN).unwrap();
let mut writers = FdSet::new();
readers.insert(Fd::STDOUT).unwrap();
readers.insert(Fd::STDERR).unwrap();
let all_readers = readers;
let all_writers = writers;
let result = system.select(&mut readers, &mut writers, None, None);
assert_eq!(result, Ok(3));
assert_eq!(readers, all_readers);
assert_eq!(writers, all_writers);
}
#[test]
fn select_pipe_reader_is_ready_if_writer_is_closed() {
let mut system = VirtualSystem::new();
let (reader, writer) = system.pipe().unwrap();
system.close(writer).unwrap();
let mut readers = FdSet::new();
let mut writers = FdSet::new();
readers.insert(reader).unwrap();
let all_readers = readers;
let all_writers = writers;
let result = system.select(&mut readers, &mut writers, None, None);
assert_eq!(result, Ok(1));
assert_eq!(readers, all_readers);
assert_eq!(writers, all_writers);
}
#[test]
fn select_pipe_reader_is_ready_if_something_has_been_written() {
let mut system = VirtualSystem::new();
let (reader, writer) = system.pipe().unwrap();
system.write(writer, &[0]).unwrap();
let mut readers = FdSet::new();
let mut writers = FdSet::new();
readers.insert(reader).unwrap();
let all_readers = readers;
let all_writers = writers;
let result = system.select(&mut readers, &mut writers, None, None);
assert_eq!(result, Ok(1));
assert_eq!(readers, all_readers);
assert_eq!(writers, all_writers);
}
#[test]
fn select_pipe_reader_is_not_ready_if_writer_has_written_nothing() {
let mut system = VirtualSystem::new();
let (reader, _writer) = system.pipe().unwrap();
let mut readers = FdSet::new();
let mut writers = FdSet::new();
readers.insert(reader).unwrap();
let result = system.select(&mut readers, &mut writers, None, None);
assert_eq!(result, Ok(0));
assert_eq!(readers, FdSet::new());
assert_eq!(writers, FdSet::new());
}
#[test]
fn select_pipe_writer_is_ready_if_pipe_is_not_full() {
let mut system = VirtualSystem::new();
let (_reader, writer) = system.pipe().unwrap();
let mut readers = FdSet::new();
let mut writers = FdSet::new();
writers.insert(writer).unwrap();
let all_readers = readers;
let all_writers = writers;
let result = system.select(&mut readers, &mut writers, None, None);
assert_eq!(result, Ok(1));
assert_eq!(readers, all_readers);
assert_eq!(writers, all_writers);
}
#[test]
fn select_on_unreadable_fd() {
let mut system = VirtualSystem::new();
let (_reader, writer) = system.pipe().unwrap();
let mut fds = FdSet::new();
fds.insert(writer).unwrap();
let result = system.select(&mut fds, &mut FdSet::new(), None, None);
assert_eq!(result, Err(Errno::EBADF));
}
#[test]
fn select_on_unwritable_fd() {
let mut system = VirtualSystem::new();
let (reader, _writer) = system.pipe().unwrap();
let mut fds = FdSet::new();
fds.insert(reader).unwrap();
let result = system.select(&mut FdSet::new(), &mut fds, None, None);
assert_eq!(result, Err(Errno::EBADF));
}
#[test]
fn select_on_closed_fd() {
let mut system = VirtualSystem::new();
let mut fds = FdSet::new();
fds.insert(Fd(17)).unwrap();
let result = system.select(&mut fds, &mut FdSet::new(), None, None);
assert_eq!(result, Err(Errno::EBADF));
let result = system.select(&mut FdSet::new(), &mut fds, None, None);
assert_eq!(result, Err(Errno::EBADF));
}
fn system_for_catching_sigchld() -> VirtualSystem {
let mut system = VirtualSystem::new();
system
.sigmask(Some((SigmaskHow::SIG_BLOCK, &[SIGCHLD])), None)
.unwrap();
system.sigaction(SIGCHLD, SignalHandling::Catch).unwrap();
system
}
#[test]
fn select_on_non_pending_signal() {
let mut system = system_for_catching_sigchld();
let result = system.select(&mut FdSet::new(), &mut FdSet::new(), None, Some(&[]));
assert_eq!(result, Ok(0));
assert_eq!(system.caught_signals(), []);
}
#[test]
fn select_on_pending_signal() {
let mut system = system_for_catching_sigchld();
let _ = system.current_process_mut().raise_signal(SIGCHLD);
let result = system.select(&mut FdSet::new(), &mut FdSet::new(), None, Some(&[]));
assert_eq!(result, Err(Errno::EINTR));
assert_eq!(system.caught_signals(), [SIGCHLD]);
}
#[test]
fn select_timeout() {
let mut system = VirtualSystem::new();
let now = Instant::now();
system.state.borrow_mut().now = Some(now);
let (reader, _writer) = system.pipe().unwrap();
let mut readers = FdSet::new();
let mut writers = FdSet::new();
readers.insert(reader).unwrap();
let timeout = Duration::new(42, 195).into();
let result = system.select(&mut readers, &mut writers, Some(&timeout), None);
assert_eq!(result, Ok(0));
assert_eq!(readers, FdSet::new());
assert_eq!(writers, FdSet::new());
assert_eq!(
system.state.borrow().now,
Some(now + Duration::new(42, 195))
);
}
fn virtual_system_with_executor() -> (VirtualSystem, LocalPool) {
let system = VirtualSystem::new();
let executor = LocalPool::new();
system.state.borrow_mut().executor = Some(Rc::new(executor.spawner()));
(system, executor)
}
#[test]
fn setpgid_creating_new_group_from_parent() {
let (system, mut executor) = virtual_system_with_executor();
let state = Rc::clone(&system.state);
let mut env = Env::with_system(Box::new(system));
let child = env.system.new_child_process().unwrap();
let future = child(&mut env, Box::new(|_env| Box::pin(pending())));
let pid = executor.run_until(future);
executor.run_until_stalled();
let result = env.system.setpgid(pid, pid);
assert_eq!(result, Ok(()));
let pgid = state.borrow().processes[&pid].pgid();
assert_eq!(pgid, pid);
}
#[test]
fn setpgid_creating_new_group_from_child() {
let (system, mut executor) = virtual_system_with_executor();
let state = Rc::clone(&system.state);
let mut env = Env::with_system(Box::new(system));
let child = env.system.new_child_process().unwrap();
let future = child(
&mut env,
Box::new(|child_env| {
Box::pin(async move {
let result = child_env.system.setpgid(Pid(0), Pid(0));
assert_eq!(result, Ok(()));
})
}),
);
let pid = executor.run_until(future);
executor.run_until_stalled();
let pgid = state.borrow().processes[&pid].pgid();
assert_eq!(pgid, pid);
}
#[test]
fn setpgid_extending_existing_group_from_parent() {
let (system, mut executor) = virtual_system_with_executor();
let state = Rc::clone(&system.state);
let mut env = Env::with_system(Box::new(system));
let child_1 = env.system.new_child_process().unwrap();
let future = child_1(&mut env, Box::new(|_env| Box::pin(pending())));
let pid_1 = executor.run_until(future);
env.system.setpgid(pid_1, pid_1).unwrap();
let child_2 = env.system.new_child_process().unwrap();
let future = child_2(&mut env, Box::new(|_env| Box::pin(pending())));
let pid_2 = executor.run_until(future);
executor.run_until_stalled();
let result = env.system.setpgid(pid_2, pid_1);
assert_eq!(result, Ok(()));
let pgid = state.borrow().processes[&pid_2].pgid();
assert_eq!(pgid, pid_1);
}
#[test]
fn setpgid_with_nonexisting_pid() {
let (system, mut executor) = virtual_system_with_executor();
let state = Rc::clone(&system.state);
let mut env = Env::with_system(Box::new(system));
let child = env.system.new_child_process().unwrap();
let future = child(&mut env, Box::new(|_env| Box::pin(pending())));
let pid = executor.run_until(future);
executor.run_until_stalled();
let dummy_pid = Pid(123);
let result = env.system.setpgid(dummy_pid, dummy_pid);
assert_eq!(result, Err(Errno::ESRCH));
let pgid = state.borrow().processes[&pid].pgid();
assert_eq!(pgid, Pid(1));
}
#[test]
fn setpgid_with_unrelated_pid() {
let (system, mut executor) = virtual_system_with_executor();
let parent_pid = system.process_id;
let state = Rc::clone(&system.state);
let mut env = Env::with_system(Box::new(system));
let child = env.system.new_child_process().unwrap();
let future = child(
&mut env,
Box::new(move |child_env| {
Box::pin(async move {
let result = child_env.system.setpgid(parent_pid, Pid(0));
assert_eq!(result, Err(Errno::ESRCH));
})
}),
);
let _pid = executor.run_until(future);
executor.run_until_stalled();
let pgid = state.borrow().processes[&parent_pid].pgid();
assert_eq!(pgid, Pid(1));
}
#[test]
fn setpgid_with_execed_child() {
let (system, mut executor) = virtual_system_with_executor();
let path = "/some/file";
let mut content = INode::default();
content.body = FileBody::Regular {
content: vec![],
is_native_executable: true,
};
content.permissions.0 |= 0o100;
let content = Rc::new(RefCell::new(content));
let state = Rc::clone(&system.state);
state.borrow_mut().file_system.save(path, content).unwrap();
let mut env = Env::with_system(Box::new(system));
let child = env.system.new_child_process().unwrap();
let future = child(
&mut env,
Box::new(move |child_env| {
Box::pin(async move {
let path = CString::new(path).unwrap();
let _ = child_env.system.execve(&path, &[], &[]);
})
}),
);
let pid = executor.run_until(future);
executor.run_until_stalled();
let result = env.system.setpgid(pid, pid);
assert_eq!(result, Err(Errno::EACCES));
let pgid = state.borrow().processes[&pid].pgid();
assert_eq!(pgid, Pid(1));
}
#[test]
fn setpgid_with_nonexisting_pgid() {
let (system, mut executor) = virtual_system_with_executor();
let state = Rc::clone(&system.state);
let mut env = Env::with_system(Box::new(system));
let child_1 = env.system.new_child_process().unwrap();
let future = child_1(&mut env, Box::new(|_env| Box::pin(pending())));
let pid_1 = executor.run_until(future);
let child_2 = env.system.new_child_process().unwrap();
let future = child_2(&mut env, Box::new(|_env| Box::pin(pending())));
let pid_2 = executor.run_until(future);
executor.run_until_stalled();
let result = env.system.setpgid(pid_2, pid_1);
assert_eq!(result, Err(Errno::EPERM));
let pgid = state.borrow().processes[&pid_2].pgid();
assert_eq!(pgid, Pid(1));
}
#[test]
fn tcsetpgrp_success() {
let mut system = VirtualSystem::new();
let pid = Pid(10);
let ppid = system.process_id;
let pgid = Pid(9);
system
.state
.borrow_mut()
.processes
.insert(pid, Process::with_parent_and_group(ppid, pgid));
system.tcsetpgrp(Fd::STDIN, pgid).unwrap();
let foreground = system.state.borrow().foreground;
assert_eq!(foreground, Some(pgid));
}
#[test]
fn tcsetpgrp_with_invalid_fd() {
let mut system = VirtualSystem::new();
let result = system.tcsetpgrp(Fd(100), Pid(2));
assert_eq!(result, Err(Errno::EBADF));
}
#[test]
fn tcsetpgrp_with_nonexisting_pgrp() {
let mut system = VirtualSystem::new();
let result = system.tcsetpgrp(Fd::STDIN, Pid(100));
assert_eq!(result, Err(Errno::EPERM));
}
#[test]
fn new_child_process_without_executor() {
let mut system = VirtualSystem::new();
let result = system.new_child_process();
match result {
Ok(_) => panic!("unexpected Ok value"),
Err(e) => assert_eq!(e, Errno::ENOSYS),
}
}
#[test]
fn new_child_process_with_executor() {
let (mut system, mut executor) = virtual_system_with_executor();
let result = system.new_child_process();
let state = system.state.borrow();
assert_eq!(state.processes.len(), 2);
drop(state);
let mut env = Env::with_system(Box::new(system));
let child_process = result.unwrap();
let future = child_process(&mut env, Box::new(|_env| Box::pin(async {})));
let pid = executor.run_until(future);
assert_eq!(pid, Pid(3));
}
#[test]
fn wait_for_running_child() {
let (mut system, mut executor) = virtual_system_with_executor();
let child_process = system.new_child_process();
let mut env = Env::with_system(Box::new(system));
let child_process = child_process.unwrap();
let future = child_process(&mut env, Box::new(|_env| Box::pin(async move {})));
let pid = executor.run_until(future);
let result = env.system.wait(pid);
assert_eq!(result, Ok(None))
}
#[test]
fn wait_for_exited_child() {
let (mut system, mut executor) = virtual_system_with_executor();
let child_process = system.new_child_process();
let mut env = Env::with_system(Box::new(system));
let child_process = child_process.unwrap();
let future = child_process(
&mut env,
Box::new(|env| {
Box::pin(async move {
env.exit_status = ExitStatus(5);
})
}),
);
let pid = executor.run_until(future);
executor.run_until_stalled();
let result = env.system.wait(pid);
assert_eq!(result, Ok(Some((pid, ProcessState::exited(5)))));
}
#[test]
fn wait_for_signaled_child() {
let (mut system, mut executor) = virtual_system_with_executor();
let child_process = system.new_child_process();
let mut env = Env::with_system(Box::new(system));
let child_process = child_process.unwrap();
let future = child_process(
&mut env,
Box::new(|env| {
Box::pin(async move {
let pid = env.system.getpid();
let result = env.system.kill(pid, Some(SIGKILL)).await;
unreachable!("kill returned {result:?}");
})
}),
);
let pid = executor.run_until(future);
executor.run_until_stalled();
let result = env.system.wait(pid);
assert_eq!(
result,
Ok(Some((
pid,
ProcessState::Halted(ProcessResult::Signaled {
signal: SIGKILL,
core_dump: false
})
)))
);
}
#[test]
fn wait_for_stopped_child() {
let (mut system, mut executor) = virtual_system_with_executor();
let child_process = system.new_child_process();
let mut env = Env::with_system(Box::new(system));
let child_process = child_process.unwrap();
let future = child_process(
&mut env,
Box::new(|env| {
Box::pin(async move {
let pid = env.system.getpid();
let result = env.system.kill(pid, Some(SIGSTOP)).await;
unreachable!("kill returned {result:?}");
})
}),
);
let pid = executor.run_until(future);
executor.run_until_stalled();
let result = env.system.wait(pid);
assert_eq!(result, Ok(Some((pid, ProcessState::stopped(SIGSTOP)))));
}
#[test]
fn wait_for_resumed_child() {
let (mut system, mut executor) = virtual_system_with_executor();
let child_process = system.new_child_process();
let mut env = Env::with_system(Box::new(system));
let child_process = child_process.unwrap();
let future = child_process(
&mut env,
Box::new(|env| {
Box::pin(async move {
let pid = env.system.getpid();
let result = env.system.kill(pid, Some(SIGSTOP)).await;
assert_eq!(result, Ok(()));
env.exit_status = ExitStatus(123);
})
}),
);
let pid = executor.run_until(future);
executor.run_until_stalled();
env.system
.kill(pid, Some(SIGCONT))
.now_or_never()
.unwrap()
.unwrap();
let result = env.system.wait(pid);
assert_eq!(result, Ok(Some((pid, ProcessState::Running))));
executor.run_until_stalled();
let result = env.system.wait(pid);
assert_eq!(result, Ok(Some((pid, ProcessState::exited(123)))));
}
#[test]
fn wait_without_child() {
let mut system = VirtualSystem::new();
let result = system.wait(Pid::ALL);
assert_eq!(result, Err(Errno::ECHILD));
let result = system.wait(system.process_id);
assert_eq!(result, Err(Errno::ECHILD));
let result = system.wait(Pid(1234));
assert_eq!(result, Err(Errno::ECHILD));
}
#[test]
fn exiting_child_sends_sigchld_to_parent() {
let (mut system, mut executor) = virtual_system_with_executor();
system.sigaction(SIGCHLD, SignalHandling::Catch).unwrap();
let child_process = system.new_child_process().unwrap();
let mut env = Env::with_system(Box::new(system));
let future = child_process(&mut env, Box::new(|_env| Box::pin(async {})));
executor.run_until(future);
executor.run_until_stalled();
assert_eq!(env.system.caught_signals(), [SIGCHLD]);
}
#[test]
fn execve_returns_enosys_for_executable_file() {
let mut system = VirtualSystem::new();
let path = "/some/file";
let mut content = INode::default();
content.body = FileBody::Regular {
content: vec![],
is_native_executable: true,
};
content.permissions.0 |= 0o100;
let content = Rc::new(RefCell::new(content));
let mut state = system.state.borrow_mut();
state.file_system.save(path, content).unwrap();
drop(state);
let path = CString::new(path).unwrap();
let result = system.execve(&path, &[], &[]);
assert_eq!(result, Err(Errno::ENOSYS));
}
#[test]
fn execve_saves_arguments() {
let mut system = VirtualSystem::new();
let path = "/some/file";
let mut content = INode::default();
content.body = FileBody::Regular {
content: vec![],
is_native_executable: true,
};
content.permissions.0 |= 0o100;
let content = Rc::new(RefCell::new(content));
let mut state = system.state.borrow_mut();
state.file_system.save(path, content).unwrap();
drop(state);
let path = CString::new(path).unwrap();
let args = [c"file".to_owned(), c"bar".to_owned()];
let envs = [c"foo=FOO".to_owned(), c"baz".to_owned()];
let _ = system.execve(&path, &args, &envs);
let process = system.current_process();
let arguments = process.last_exec.as_ref().unwrap();
assert_eq!(arguments.0, path);
assert_eq!(arguments.1, args);
assert_eq!(arguments.2, envs);
}
#[test]
fn execve_returns_enoexec_for_non_executable_file() {
let mut system = VirtualSystem::new();
let path = "/some/file";
let mut content = INode::default();
content.permissions.0 |= 0o100;
let content = Rc::new(RefCell::new(content));
let mut state = system.state.borrow_mut();
state.file_system.save(path, content).unwrap();
drop(state);
let path = CString::new(path).unwrap();
let result = system.execve(&path, &[], &[]);
assert_eq!(result, Err(Errno::ENOEXEC));
}
#[test]
fn execve_returns_enoent_on_file_not_found() {
let mut system = VirtualSystem::new();
let result = system.execve(c"/no/such/file", &[], &[]);
assert_eq!(result, Err(Errno::ENOENT));
}
#[test]
fn chdir_changes_directory() {
let mut system = VirtualSystem::new();
let _ = system.open(
c"/dir/file",
OFlag::O_WRONLY | OFlag::O_CREAT,
nix::sys::stat::Mode::empty(),
);
let result = system.chdir(c"/dir");
assert_eq!(result, Ok(()));
assert_eq!(system.current_process().cwd, Path::new("/dir"));
}
#[test]
fn chdir_fails_with_non_existing_directory() {
let mut system = VirtualSystem::new();
let result = system.chdir(c"/no/such/dir");
assert_eq!(result, Err(Errno::ENOENT));
}
#[test]
fn chdir_fails_with_non_directory_file() {
let mut system = VirtualSystem::new();
let _ = system.open(
c"/dir/file",
OFlag::O_WRONLY | OFlag::O_CREAT,
nix::sys::stat::Mode::empty(),
);
let result = system.chdir(c"/dir/file");
assert_eq!(result, Err(Errno::ENOTDIR));
}
#[test]
fn getrlimit_for_unset_resource_returns_infinity() {
let system = VirtualSystem::new();
let result = system.getrlimit(Resource::CPU).unwrap();
assert_eq!(
result,
LimitPair {
soft: RLIM_INFINITY,
hard: RLIM_INFINITY,
},
);
}
#[test]
fn setrlimit_and_getrlimit_with_finite_limits() {
let mut system = VirtualSystem::new();
system
.setrlimit(
Resource::CORE,
LimitPair {
soft: 4096,
hard: 8192,
},
)
.unwrap();
system
.setrlimit(Resource::CPU, LimitPair { soft: 10, hard: 30 })
.unwrap();
let result = system.getrlimit(Resource::CORE).unwrap();
assert_eq!(
result,
LimitPair {
soft: 4096,
hard: 8192,
},
);
let result = system.getrlimit(Resource::CPU).unwrap();
assert_eq!(result, LimitPair { soft: 10, hard: 30 },);
}
#[test]
fn setrlimit_rejects_soft_limit_higher_than_hard_limit() {
let mut system = VirtualSystem::new();
let result = system.setrlimit(Resource::CPU, LimitPair { soft: 2, hard: 1 });
let error = result.unwrap_err();
assert_eq!(error.raw_os_error(), Some(nix::libc::EINVAL));
let result = system.getrlimit(Resource::CPU).unwrap();
assert_eq!(
result,
LimitPair {
soft: RLIM_INFINITY,
hard: RLIM_INFINITY,
},
);
}
#[test]
fn setrlimit_refuses_raising_hard_limit() {
let mut system = VirtualSystem::new();
system
.setrlimit(Resource::CPU, LimitPair { soft: 1, hard: 1 })
.unwrap();
let result = system.setrlimit(Resource::CPU, LimitPair { soft: 1, hard: 2 });
let error = result.unwrap_err();
assert_eq!(error.raw_os_error(), Some(nix::libc::EPERM));
let result = system.getrlimit(Resource::CPU).unwrap();
assert_eq!(result, LimitPair { soft: 1, hard: 1 });
}
}