yggdrasil/kernel/src/syscall/imp/sys_process.rs

354 lines
11 KiB
Rust

use core::{mem::MaybeUninit, num::NonZeroUsize, sync::atomic::AtomicU32, time::Duration};
use abi::{
error::Error,
mem::{MappingFlags, MappingSource},
process::{
options::ProcessOptionVariant, thread::ThreadOptionVariant, ExitCode, MutexOperation,
ProcessGroupId, ProcessId, ProcessWait, Signal, SpawnFlags, SpawnOption, SpawnOptions,
ThreadSpawnOptions, WaitFlags,
},
};
use alloc::sync::Arc;
use libk::{
block,
task::{binary::LoadOptions, process::Process, runtime, thread::Thread, ThreadId},
time::monotonic_time,
vfs::IoContext,
};
use libk_mm::{
process::VirtualRangeBacking,
table::{EntryLevelExt, MapAttributes},
};
use crate::{arch::L3, proc, syscall::run_with_io};
// Memory management
pub(crate) fn map_memory(
_hint: Option<NonZeroUsize>,
len: usize,
flags: MappingFlags,
source: &MappingSource,
) -> Result<usize, Error> {
let thread = Thread::current();
let process = thread.process();
let space = thread.address_space();
let len = len.page_align_up::<L3>();
run_with_io(&process, |io| {
let backing = match source {
MappingSource::Anonymous => VirtualRangeBacking::anonymous(),
&MappingSource::File(fd, offset) => {
let file = io.files.file(fd)?;
VirtualRangeBacking::file(offset, file.clone())?
}
};
let mut attrs = MapAttributes::NON_GLOBAL | MapAttributes::USER_READ;
if flags.contains(MappingFlags::WRITE) {
attrs |= MapAttributes::USER_WRITE;
}
space.allocate(None, len, backing, attrs)
})
.inspect_err(|error| {
log::warn!("map_memory({len}) failed: {error:?}");
})
}
pub(crate) fn unmap_memory(address: usize, len: usize) -> Result<(), Error> {
let thread = Thread::current();
let space = thread.address_space();
if len & 0xFFF != 0 {
return Err(Error::InvalidArgument);
}
unsafe {
space.unmap(address, len)?;
}
Ok(())
}
// Process/thread management
pub(crate) fn create_process_group() -> ProcessGroupId {
Process::create_group()
}
pub(crate) fn get_process_group_id() -> ProcessGroupId {
let thread = Thread::current();
let process = thread.process();
process.group_id()
}
pub(crate) fn exit_process(code: ExitCode) -> ! {
let thread = Thread::current();
thread.exit_process(code)
}
pub(crate) fn spawn_process(options: &SpawnOptions<'_>) -> Result<ProcessId, Error> {
let thread = Thread::current();
let process = thread.process();
let result = run_with_io(&process, |mut io| {
// let attach_debugger = options
// .optional
// .iter()
// .try_find_map::<_, Error, _>(|entry| {
// if let &SpawnOption::AttachDebug(fd) = entry {
// let channel = io.files.file(fd)?;
// let channel = channel.as_message_channel()?.clone();
// Ok(Some(channel))
// } else {
// Ok(None)
// }
// })?;
// Setup a new process from the file
let load_options = LoadOptions {
group_id: process.group_id(),
parent: Some(process.clone()),
path: options.program,
args: options.arguments,
envs: options.arguments,
single_step: false,
// single_step: attach_debugger.is_some(),
disable_aslr: options.flags.contains(SpawnFlags::DISABLE_ASLR),
};
let (child_process, child_main) = proc::load_binary(io.ioctx_mut(), &load_options)?;
let pid = child_process.id;
// Inherit group and session from the creator
child_process.inherit(&process)?;
// Inherit root from the creator
// let child_ioctx = IoContext::new(io.ioctx().root().clone());
let child_ioctx = IoContext::inherit(io.ioctx_mut());
let mut child_io = child_process.io.lock();
child_io.set_ioctx(child_ioctx);
for opt in options.optional {
match opt {
&SpawnOption::MoveFile { source, child } => {
if let Ok(src_file) = io.files.take_file(source) {
child_io.files.set_file(child, src_file)?;
}
}
&SpawnOption::CopyFile { source, child } => {
if let Ok(src_file) = io.files.file(source) {
child_io.files.set_file(child, src_file.send()?)?;
}
}
&SpawnOption::SetProcessGroup(pgroup) => {
let pgroup = if pgroup.into_raw() == 0 {
todo!()
} else {
pgroup
};
child_process.set_group_id(pgroup);
}
_ => (),
}
}
if let Some(fd) = options.optional.iter().find_map(|item| {
if let &SpawnOption::GainTerminal(fd) = item {
Some(fd)
} else {
None
}
}) {
log::debug!("{} requested terminal {:?}", pid, fd);
let file = child_io.files.file(fd)?;
file.set_terminal_group(child_process.group_id())?;
if let Some(node) = file.node() {
child_process.set_session_terminal(node.clone());
}
}
drop(child_io);
child_main.enqueue();
// if let Some(debugger) = attach_debugger {
// child_main.attach_debugger(ThreadDebugger::new(debugger));
// } else {
// child_main.enqueue();
// }
Ok(pid as _)
});
result
}
pub(crate) fn wait_process(
wait: &ProcessWait,
status: &mut ExitCode,
flags: WaitFlags,
) -> Result<ProcessId, Error> {
let thread = Thread::current();
let process = thread.process();
match wait {
&ProcessWait::Process(id) => {
*status = process.wait_for_child(id, flags)?;
Ok(id)
}
ProcessWait::Group(_id) => todo!(),
ProcessWait::AnyChild => {
let (id, exit) = process.wait_for_any_child(flags)?;
*status = exit;
Ok(id)
}
}
}
pub(crate) fn get_pid() -> ProcessId {
let thread = Thread::current();
let process = thread.process();
process.id
}
pub(crate) fn get_tid() -> u32 {
let thread = Thread::current();
thread.id.as_user().try_into().unwrap()
}
pub(crate) fn nanosleep(
duration: &Duration,
remaining: &mut MaybeUninit<Duration>,
) -> Result<(), Error> {
let duration = *duration;
let now = monotonic_time();
let deadline = now + duration;
let result = block! { runtime::sleep_until(deadline).await };
match result {
Ok(()) => Ok(()),
Err(Error::Interrupted) => {
let now = monotonic_time();
let rem = deadline.checked_sub_time(&now).unwrap_or_default();
remaining.write(rem);
Err(Error::Interrupted)
}
Err(_) => unreachable!(),
}
}
pub(crate) fn send_signal(pid: ProcessId, signal: Signal) -> Result<(), Error> {
// Debug is only issued by kernel for internal purposes of interrupting a thread when someone
// attaches to it
if signal == Signal::Debug {
return Err(Error::InvalidArgument);
}
let thread = Thread::current();
let target = Process::get(pid).ok_or(Error::DoesNotExist)?;
target.raise_signal(Some(thread.id), signal);
Ok(())
}
// TODO this would've been much simpler if physical pages behaved like Rc<...>'s and kernel could
// keep a copy of it, guaranteeing it cannot be freed and remapped into another process.
pub(crate) fn mutex(mutex: &AtomicU32, op: &MutexOperation) -> Result<(), Error> {
let thread = Thread::current();
let process = thread.process();
let mutex = process.get_or_insert_mutex((mutex as *const AtomicU32).addr())?;
match op {
&MutexOperation::WaitWhileEqual(value, _timeout) => {
block! { mutex.wait_until(|v| v != value).await }?
}
&MutexOperation::WaitUntilEqual(value, _timeout) => {
block! { mutex.wait_until(|v| v == value).await }?
}
&MutexOperation::WaitWhileSet(mask, _timeout) => {
block! { mutex.wait_until(|v| v & mask != v).await }?
}
&MutexOperation::WaitUntilSet(mask, _timeout) => {
block! { mutex.wait_until(|v| v & mask == v).await }?
}
MutexOperation::Wake(limit) => {
mutex.wake(*limit as _);
Ok(())
}
}
}
pub(crate) fn start_session() -> Result<(), Error> {
let thread = Thread::current();
let process = thread.process();
let session_terminal = process.clear_session_terminal();
if let Some(ctty) = session_terminal {
// Drop all FDs referring to the old session terminal
run_with_io(&process, |mut io| {
io.files.retain(|_, f| {
f.node()
.map(|node| !Arc::ptr_eq(node, &ctty))
.unwrap_or(true)
});
});
}
let group_id = Process::create_group();
process.set_session_id(process.id);
process.set_group_id(group_id);
Ok(())
}
pub(crate) fn spawn_thread(options: &ThreadSpawnOptions) -> Result<u32, Error> {
let thread = Thread::current();
let process = thread.process();
process
.spawn_thread(options)
.map(|tid| tid.as_user() as u32)
}
pub(crate) fn exit_thread() -> ! {
let thread = Thread::current();
// TODO exit codes are not supported in here
thread.exit(ExitCode::SUCCESS)
}
pub(crate) fn wait_thread(id: u32) -> Result<(), Error> {
let tid = ThreadId::User(id as u64);
let this_thread = Thread::current();
let process = this_thread.process();
block!(process.wait_for_thread(tid).await)?
}
pub(crate) fn get_thread_option(option: u32, buffer: &mut [u8]) -> Result<usize, Error> {
let option = ThreadOptionVariant::try_from(option)?;
let thread = Thread::current();
thread.get_option(option, buffer)
}
pub(crate) fn set_thread_option(option: u32, buffer: &[u8]) -> Result<(), Error> {
let option = ThreadOptionVariant::try_from(option)?;
let thread = Thread::current();
thread.set_option(option, buffer)
}
pub(crate) fn get_process_option(option: u32, buffer: &mut [u8]) -> Result<usize, Error> {
let option = ProcessOptionVariant::try_from(option)?;
let thread = Thread::current();
let process = thread.process();
process.get_option(option, buffer)
}
pub(crate) fn set_process_option(option: u32, buffer: &[u8]) -> Result<(), Error> {
let option = ProcessOptionVariant::try_from(option)?;
let thread = Thread::current();
let process = thread.process();
process.set_option(option, buffer)
}