use alloc::sync::Arc;
use ax_fs_ng::vfs::FS_CONTEXT;
use ax_runtime::hal::cpu::user::UserContext;
use bitflags::bitflags;
use linux_raw_sys::general::*;
use scope_local::Scope;
use starry_signal::Signo;
use super::schedule_abi::fork_schedule_policy;
use crate::{
StarryError, StarryResult,
file::{FD_TABLE, PidFd, PreparedFileDescriptor, prepare_file_like},
mm::{MmHandle, VmMutPtr, copy_from_kernel},
sync::RawSpinLock,
task::{
PidIdentity, PidReservation, PidReservationKind, ProcessData, ProcessDataInit,
ProcessImage, Tgid, Thread, Tid, TidNumber, UserThreadInitialSchedulerState,
UserThreadOptions, new_user_task, prepare_user_thread,
},
};
struct CloneTransaction {
identity: Arc<PidIdentity>,
committed: bool,
}
impl CloneTransaction {
fn new(identity: Arc<PidIdentity>) -> Self {
Self {
identity,
committed: false,
}
}
fn commit(&mut self) {
self.committed = true;
}
}
impl Drop for CloneTransaction {
fn drop(&mut self) {
if self.committed {
return;
}
self.identity.abort_failed_task_publication();
}
}
bitflags! {
#[derive(Debug, Clone, Copy, Default)]
pub struct CloneFlags: u64 {
const VM = CLONE_VM as u64;
const FS = CLONE_FS as u64;
const FILES = CLONE_FILES as u64;
const SIGHAND = CLONE_SIGHAND as u64;
const PIDFD = CLONE_PIDFD as u64;
const PTRACE = CLONE_PTRACE as u64;
const VFORK = CLONE_VFORK as u64;
const PARENT = CLONE_PARENT as u64;
const THREAD = CLONE_THREAD as u64;
const NEWNS = CLONE_NEWNS as u64;
const SYSVSEM = CLONE_SYSVSEM as u64;
const SETTLS = CLONE_SETTLS as u64;
const PARENT_SETTID = CLONE_PARENT_SETTID as u64;
const CHILD_CLEARTID = CLONE_CHILD_CLEARTID as u64;
const UNTRACED = CLONE_UNTRACED as u64;
const CHILD_SETTID = CLONE_CHILD_SETTID as u64;
const NEWCGROUP = CLONE_NEWCGROUP as u64;
const NEWUTS = CLONE_NEWUTS as u64;
const NEWIPC = CLONE_NEWIPC as u64;
const NEWUSER = CLONE_NEWUSER as u64;
const NEWPID = CLONE_NEWPID as u64;
const NEWNET = CLONE_NEWNET as u64;
const IO = CLONE_IO as u64;
const CLEAR_SIGHAND = 0x100000000u64;
const INTO_CGROUP = 0x200000000u64;
const DETACHED = CLONE_DETACHED as u64;
}
}
ax_tracepoint::define_event_trace!(
sched_process_fork,
TP_kops(crate::tracepoint::KernelTraceAux),
TP_system(sched),
TP_PROTO(parent_tid: u64, child_tid: u64),
TP_STRUCT__entry {
parent_tid: u64,
child_tid: u64,
},
TP_fast_assign {
parent_tid: parent_tid,
child_tid: child_tid,
},
TP_ident(__entry),
TP_printk({
alloc::format!(
"parent_tid={} child_tid={}",
__entry.parent_tid,
__entry.child_tid,
)
})
);
fn emit_sched_process_fork(parent_tid: TidNumber, child_tid: TidNumber) {
trace_sched_process_fork(parent_tid.get() as u64, child_tid.get() as u64);
}
#[derive(Debug, Clone, Copy, Default)]
pub struct CloneArgs {
pub flags: CloneFlags,
pub exit_signal: u64,
pub stack: usize,
pub tls: usize,
pub parent_tid: usize,
pub child_tid: usize,
pub pidfd: usize,
}
impl CloneArgs {
fn validate(&self) -> StarryResult<()> {
let Self { flags, .. } = self;
if flags.contains(CloneFlags::THREAD)
&& !flags.contains(CloneFlags::VM | CloneFlags::SIGHAND)
{
return Err(StarryError::InvalidInput);
}
if flags.contains(CloneFlags::SIGHAND) && !flags.contains(CloneFlags::VM) {
return Err(StarryError::InvalidInput);
}
if flags.contains(CloneFlags::PIDFD | CloneFlags::DETACHED) {
return Err(StarryError::InvalidInput);
}
if flags.contains(CloneFlags::NEWNS | CloneFlags::FS) {
return Err(StarryError::InvalidInput);
}
if flags.contains(CloneFlags::NEWPID | CloneFlags::THREAD) {
return Err(StarryError::InvalidInput);
}
if flags.contains(CloneFlags::INTO_CGROUP | CloneFlags::THREAD) {
return Err(StarryError::InvalidInput);
}
Ok(())
}
fn validate_cgroup_target(&self, has_requested_cgroup: bool) -> StarryResult<()> {
self.validate()?;
if self.flags.contains(CloneFlags::INTO_CGROUP) != has_requested_cgroup {
return Err(StarryError::InvalidInput);
}
Ok(())
}
pub fn do_clone(
self,
current: &crate::task::UserTaskRef,
uctx: &UserContext,
) -> crate::StarryResult<isize> {
self.do_clone_in_cgroup(current, uctx, None)
}
pub(super) fn do_clone_in_cgroup(
self,
current: &crate::task::UserTaskRef,
uctx: &UserContext,
requested_cgroup: Option<Arc<ax_cgroup::CgroupNode>>,
) -> StarryResult<isize> {
self.validate_cgroup_target(requested_cgroup.is_some())?;
let Self {
flags,
exit_signal,
stack,
tls,
parent_tid: parent_tid_ptr,
child_tid,
pidfd,
} = self;
debug!(
"do_clone <= flags: {:?}, exit_signal: {}, stack: {:#x}, tls: {:#x}",
flags, exit_signal, stack, tls
);
let exit_signal = if exit_signal > 0 {
Some(Signo::from_repr(exit_signal as u8).ok_or(StarryError::InvalidInput)?)
} else {
None
};
let needs_vfork_block = flags.contains(CloneFlags::VFORK);
let mut new_uctx = *uctx;
new_uctx.prepare_clone_child_return_state();
if stack != 0 {
new_uctx.set_sp(stack);
}
if flags.contains(CloneFlags::SETTLS) {
new_uctx.set_tls(tls);
}
new_uctx.set_retval(0);
#[cfg(target_arch = "riscv64")]
let child_fp_fs = match uctx.sstatus.fs() {
riscv::register::sstatus::FS::Dirty => riscv::register::sstatus::FS::Clean,
fs => fs,
};
#[cfg(target_arch = "riscv64")]
new_uctx.sstatus.set_fs(child_fp_fs);
let set_child_tid = if flags.contains(CloneFlags::CHILD_SETTID) {
child_tid
} else {
0
};
let curr = current;
let curr_thread = curr.as_thread();
let old_proc_data = &curr_thread.proc_data;
if flags.contains(CloneFlags::NEWCGROUP) && !curr_thread.cred().has_cap_sys_admin() {
return Err(StarryError::OperationNotPermitted);
}
let (child_policy, child_reset_on_fork) =
fork_schedule_policy(curr.base_policy(), curr.reset_on_fork())?;
let child_nice = match child_policy {
ax_std::os::arceos::task::sched::SchedulePolicy::Fair { nice, .. } => {
i32::from(nice.get())
}
_ => curr_thread.nice(),
};
let child_scheduler_state = UserThreadInitialSchedulerState::new(
child_policy,
curr.affinity(),
child_reset_on_fork,
);
#[cfg(target_arch = "riscv64")]
let child_fp_state = {
let mut fp_state = ax_cpu::registers::FpState::default();
fp_state.save();
fp_state.fs = child_fp_fs;
fp_state
};
let parent_pid_ns = curr_thread.active_pid_namespace();
let target_pid_ns = if flags.contains(CloneFlags::THREAD) {
parent_pid_ns.clone()
} else if flags.contains(CloneFlags::NEWPID) {
crate::namespace::PidNamespace::new_child(parent_pid_ns.clone())
} else {
old_proc_data.nsproxy.lock().pid_ns_for_children.clone()
};
let reservation_kind = if flags.contains(CloneFlags::THREAD) {
PidReservationKind::Thread
} else {
PidReservationKind::ProcessLeader
};
let reservation = PidReservation::reserve(&target_pid_ns, reservation_kind)?;
let root_tid = TidNumber::from(
reservation
.number_in(&crate::task::ROOT_PID_NS)
.ok_or(StarryError::BadState)?,
);
let parent_visible_tid = TidNumber::from(
reservation
.number_in(&parent_pid_ns)
.ok_or(StarryError::BadState)?,
);
let child_visible_tid = TidNumber::from(
reservation
.number_in(&target_pid_ns)
.ok_or(StarryError::BadState)?,
);
let identity = reservation.identity();
let tid_lease = identity.acquire_role::<Tid>()?;
let mut tgid_lease = (!flags.contains(CloneFlags::THREAD))
.then(|| identity.acquire_role::<Tgid>())
.transpose()?;
let mut clone_transaction = CloneTransaction::new(identity.clone());
let mut prepared_fork = None;
let child_kind = if flags.contains(CloneFlags::THREAD) {
ax_cgroup::CgroupChildKind::Thread
} else {
ax_cgroup::CgroupChildKind::Process
};
let mut cgroup_guard = match (child_kind, requested_cgroup) {
(ax_cgroup::CgroupChildKind::Process, Some(target)) => {
crate::cgroup::begin_process_at(target, &identity)?
}
(kind, None) => crate::cgroup::begin_task(old_proc_data, &identity, kind)?,
(ax_cgroup::CgroupChildKind::Thread, Some(_)) => {
unreachable!("CLONE_INTO_CGROUP with CLONE_THREAD passed validation")
}
};
let child_cgroup = cgroup_guard.cgroup();
let mut prepared_nsproxy = (!flags.contains(CloneFlags::THREAD)).then(|| {
let mut nsproxy = old_proc_data.nsproxy.lock().clone_all();
if flags.contains(CloneFlags::NEWUTS) {
nsproxy.unshare_uts();
}
if flags.contains(CloneFlags::NEWIPC) {
nsproxy.unshare_ipc();
}
if flags.contains(CloneFlags::NEWNS) {
nsproxy.unshare_mnt();
}
if flags.contains(CloneFlags::NEWNET) {
nsproxy.unshare_net();
}
if flags.contains(CloneFlags::NEWUSER) {
nsproxy.unshare_user();
}
if flags.contains(CloneFlags::NEWCGROUP) {
nsproxy.unshare_cgroup(child_cgroup.clone());
}
if flags.contains(CloneFlags::NEWPID) {
nsproxy.pid_ns_for_children = target_pid_ns.clone();
}
nsproxy
});
let new_proc_data = if flags.contains(CloneFlags::THREAD) {
old_proc_data.clone()
} else {
let prepared = if flags.contains(CloneFlags::PARENT) {
old_proc_data
.proc
.parent()
.ok_or(StarryError::InvalidInput)?
} else {
old_proc_data.proc.clone()
}
.prepare_fork(identity.clone())?;
let proc = prepared.process().clone();
prepared_fork = Some(prepared);
let aspace = if flags.contains(CloneFlags::VM) {
old_proc_data
.clone_aspace_user_ref()
.map_err(|_| StarryError::InvalidInput)?
} else {
let parent_mm = old_proc_data.pin_aspace()?;
let aspace = parent_mm.lock().try_clone()?;
copy_from_kernel(&mut aspace.lock())?;
MmHandle::from_arc(aspace).map_err(|_| StarryError::BadState)?
};
let signal_actions = if flags.contains(CloneFlags::SIGHAND) {
old_proc_data.signal.actions()
} else if flags.contains(CloneFlags::CLEAR_SIGHAND) {
Arc::new(RawSpinLock::new(Default::default()))
} else {
Arc::new(RawSpinLock::new(
old_proc_data.signal.actions().lock_irqsave().clone(),
))
};
let process_image = ProcessImage::new(
old_proc_data.exe_path().as_ref().clone(),
old_proc_data.cmdline(),
old_proc_data.envp(),
old_proc_data.auxv().as_ref().clone(),
old_proc_data.root_path().as_ref().clone(),
old_proc_data.cwd_path().as_ref().clone(),
);
let mut process_init = ProcessDataInit::new(
process_image,
aspace,
signal_actions,
prepared_nsproxy
.take()
.expect("process clone must prepare one namespace proxy"),
exit_signal,
curr_thread.tid_number(),
)
.with_cgroup(child_cgroup.clone());
if flags.contains(CloneFlags::VM) {
process_init = process_init.with_shared_memory(old_proc_data);
}
let proc_data = ProcessData::new(
proc,
identity.clone(),
tgid_lease
.take()
.expect("process clone must own one TGID lease"),
process_init,
);
proc_data.set_umask(old_proc_data.umask());
proc_data.replace_personality(old_proc_data.personality());
proc_data.set_dumpable(old_proc_data.dumpable());
proc_data.set_transparent_huge_page_mode(old_proc_data.transparent_huge_page_mode())?;
proc_data
};
let mut scope = Scope::new();
let current_fd_table = crate::file::current_fd_table();
if flags.contains(CloneFlags::FILES) {
let _guard = current_fd_table.read();
FD_TABLE
.scope_mut(&mut scope)
.clone_from(&crate::file::new_file_table_scope(current_fd_table.clone()));
} else {
FD_TABLE
.scope_mut(&mut scope)
.clone_from(&crate::file::clone_file_table_scope(¤t_fd_table));
}
let current_fs_context = ax_fs_ng::vfs::current_fs_context();
if flags.contains(CloneFlags::FS) {
FS_CONTEXT
.scope_mut(&mut scope)
.clone_from(&Some(current_fs_context));
} else {
let mut fs_context = current_fs_context.lock().clone();
if flags.contains(CloneFlags::NEWNS) {
fs_context.unshare_mount_namespace()?;
}
*FS_CONTEXT.scope_mut(&mut scope) = Some(fs_context.into_shared());
}
let parent_cred = Some(curr_thread.cred());
let thr = Thread::new(
identity.clone(),
tid_lease,
new_proc_data.clone(),
parent_cred,
curr_thread.signal().blocked(),
scope,
)?;
thr.set_nice(child_nice);
if flags.contains(CloneFlags::CHILD_CLEARTID) {
thr.set_clear_child_tid(child_tid);
}
let mut prepared_pidfd: Option<PreparedFileDescriptor> = None;
let mut pidfd_copyout = None;
if flags.contains(CloneFlags::PIDFD) {
let pidfd_obj = if flags.contains(CloneFlags::THREAD) {
PidFd::new_thread(identity.clone(), &thr, root_tid)
} else {
PidFd::new_process(identity.clone())
};
let prepared = prepare_file_like(
|| Ok(Arc::try_new(pidfd_obj).map_err(|_| StarryError::NoMemory)?),
true,
)?;
let fd = prepared.fd();
prepared_pidfd = Some(prepared);
pidfd_copyout = Some((pidfd as *mut i32, fd));
}
if needs_vfork_block {
thr.prepare_vfork_done()?;
}
let options = UserThreadOptions::new(curr.name().as_ref())
.map_err(map_task_creation_error)?
.with_scheduler_state(child_scheduler_state);
#[cfg(target_arch = "riscv64")]
let options = options.with_fp_state(child_fp_state);
#[cfg(not(target_arch = "riscv64"))]
let options = options.inherit_current_fp();
let prepared_task = prepare_user_thread(
new_user_task(new_uctx, set_child_tid, child_visible_tid),
thr,
options,
)
.map_err(map_task_creation_error)?;
#[cfg(target_arch = "aarch64")]
prepared_task
.with_task(|task| crate::perf::task::on_clone_inherit(curr_thread, task.as_thread()));
let staged_task = prepared_task.stage().map_err(map_task_creation_error)?;
staged_task
.with_task(|task| crate::perf::sw::on_clone_inherit(curr_thread, task.as_thread()));
if let Some((pidfd_ptr, fd)) = pidfd_copyout {
pidfd_ptr.vm_write(current, fd)?;
}
cgroup_guard.publish()?;
let published_fork = prepared_fork
.take()
.map(|prepared| prepared.publish().ok_or(StarryError::WouldBlock))
.transpose()?;
staged_task.with_task(|task| {
publish_clone(curr_thread, task.as_thread(), || {
let published_identity = reservation.publish()?;
debug_assert!(Arc::ptr_eq(&published_identity, &identity));
if let Some(published) = published_fork {
let process = published.commit();
debug_assert!(Arc::ptr_eq(&process, &new_proc_data.proc));
}
task.as_thread().attach_pid_task(task);
new_proc_data.proc.add_thread(root_tid);
Ok(())
})
})?;
if let Some(pidfd) = prepared_pidfd.take() {
pidfd.install();
}
if flags.contains(CloneFlags::PARENT_SETTID) && parent_tid_ptr != 0 {
let _ = (parent_tid_ptr as *mut u32).vm_write(current, parent_visible_tid.get());
}
let parent_pid = curr.as_thread().proc_data.proc.pid_number();
let parent_tid = curr.as_thread().tid_number();
let ptrace_event = if flags.contains(CloneFlags::THREAD) {
super::ptrace::PTRACE_EVENT_CLONE
} else if flags.contains(CloneFlags::VFORK) {
super::ptrace::PTRACE_EVENT_VFORK
} else {
super::ptrace::PTRACE_EVENT_FORK
};
let trace_clone =
super::ptrace::ptrace_notify_clone(parent_pid, parent_tid, &identity, ptrace_event);
if trace_clone && let Some(tracer) = curr.as_thread().proc_data.ptrace_tracer_identity() {
if !flags.contains(CloneFlags::THREAD) {
new_proc_data.set_ptrace_tracer(&tracer);
let attach_mode = if curr.as_thread().proc_data.is_ptrace_seized() {
crate::task::PtraceAttachMode::Seize
} else {
crate::task::PtraceAttachMode::Attach
};
new_proc_data.set_ptrace_attach_mode(attach_mode);
}
new_proc_data.set_ptrace_stop(root_tid, starry_signal::Signo::SIGSTOP, &new_uctx);
}
cgroup_guard.commit();
clone_transaction.commit();
let task = staged_task.activate();
if trace_clone && needs_vfork_block {
let _ = crate::task::send_signal_to_thread(
None,
parent_tid,
Some(starry_signal::SignalInfo::new_kernel(
starry_signal::Signo::SIGTRAP,
)),
);
}
emit_sched_process_fork(curr_thread.tid(), root_tid);
#[cfg(target_arch = "aarch64")]
crate::perf::task::on_clone_sideband(
curr.as_thread(),
&new_proc_data.identity(),
&identity,
);
if needs_vfork_block && task.as_thread().wait_vfork_done(current) {
let _ = super::ptrace::ptrace_notify_vfork_done(parent_pid, parent_tid, &identity);
}
Ok(parent_visible_tid.get() as _)
}
}
fn publish_clone(
parent: &Thread,
child: &Thread,
publish: impl FnOnce() -> StarryResult<()>,
) -> StarryResult<()> {
let _update = parent.proc_data.thread_group_update();
if parent.signal().pending().has(Signo::SIGKILL) {
return Err(StarryError::Interrupted);
}
child.inherit_security(parent)?;
publish()
}
fn map_task_creation_error(error: ax_std::os::arceos::task::thread::TaskError) -> StarryError {
use ax_std::os::arceos::task::thread::TaskError;
match error {
TaskError::ThreadCapacity => StarryError::WouldBlock,
TaskError::TimerCapacity => StarryError::NoMemory,
TaskError::RuntimeFailure(status)
if status == ax_std::os::arceos::task::runtime::RuntimeStatus::NoMemory as u32 =>
{
StarryError::NoMemory
}
TaskError::DeadlineAdmission | TaskError::ThreadBusy => StarryError::ResourceBusy,
_ => StarryError::BadState,
}
}
ax_tracepoint::define_event_trace!(
sys_clone,
TP_kops(crate::tracepoint::KernelTraceAux),
TP_system(syscalls),
TP_PROTO(flags: u32, stack: usize, parent_tid: usize),
TP_STRUCT__entry {
stack: usize,
parent_tid: usize,
flags: u32,
},
TP_fast_assign {
flags: flags,
stack: stack,
parent_tid: parent_tid,
},
TP_ident(__entry),
TP_printk({
let flags = __entry.flags;
let stack = __entry.stack;
let parent_tid = __entry.parent_tid;
alloc::format!("clone with flags: {flags}, stack: {stack:#x}, parent_tid: {parent_tid:#x}")
})
);
pub fn sys_clone(
current: &crate::task::UserTaskRef,
uctx: &UserContext,
flags: u32,
stack: usize,
parent_tid: usize,
#[cfg(any(target_arch = "x86_64", target_arch = "loongarch64"))] child_tid: usize,
tls: usize,
#[cfg(not(any(target_arch = "x86_64", target_arch = "loongarch64")))] child_tid: usize,
) -> StarryResult<isize> {
const FLAG_MASK: u32 = 0xff;
let clone_flags = CloneFlags::from_bits_truncate((flags & !FLAG_MASK) as u64);
let exit_signal = (flags & FLAG_MASK) as u64;
trace_sys_clone(clone_flags.bits() as _, stack, parent_tid);
if clone_flags.contains(CloneFlags::PIDFD | CloneFlags::PARENT_SETTID) {
return Err(StarryError::InvalidInput);
}
let args = CloneArgs {
flags: clone_flags,
exit_signal,
stack,
tls,
parent_tid,
child_tid,
pidfd: if clone_flags.contains(CloneFlags::PIDFD) {
parent_tid
} else {
0
},
};
args.do_clone(current, uctx)
}
#[cfg(target_arch = "x86_64")]
pub fn sys_fork(
current: &crate::task::UserTaskRef,
uctx: &UserContext,
) -> crate::StarryResult<isize> {
sys_clone(current, uctx, SIGCHLD, 0, 0, 0, 0)
}
#[cfg(target_arch = "x86_64")]
pub fn sys_vfork(
current: &crate::task::UserTaskRef,
uctx: &UserContext,
) -> crate::StarryResult<isize> {
let flags = (CloneFlags::VFORK | CloneFlags::VM).bits() as u32 | SIGCHLD;
sys_clone(current, uctx, flags, 0, 0, 0, 0)
}
#[cfg(all(test, axtest))]
mod axtests {
use alloc::sync::Arc;
use super::CloneTransaction;
use crate::task::{PidReservation, PidReservationKind, Tgid, Tid};
#[axtest::axtest]
fn clone_publication_refreshes_security_after_preparation() {
use crate::task::{ROOT_PID_NS, Thread};
let make_thread = || {
let reservation =
PidReservation::reserve(&ROOT_PID_NS, PidReservationKind::ProcessLeader).unwrap();
let identity = reservation.identity();
let tid = identity.acquire_role::<Tid>().unwrap();
let tgid = identity.acquire_role::<Tgid>().unwrap();
let process = crate::task::new_test_process_data(identity.clone(), tgid);
let thread = Thread::new(
identity,
tid,
process,
None,
Default::default(),
scope_local::Scope::new(),
)
.unwrap();
(reservation, thread)
};
let (_parent_reservation, parent) = make_thread();
let (_child_reservation, child) = make_thread();
child.set_seccomp_state(parent.seccomp_state());
parent.set_no_new_privs();
parent
.append_seccomp_filter(alloc::vec![crate::task::SockFilter {
code: 0x06, jt: 0,
jf: 0,
k: 0x7fff_0000, }])
.unwrap();
let updated = parent.seccomp_state();
let original = child.seccomp_state();
let probe = ax_std::os::arceos::task::thread::ThreadAllocationProbe::fail_at(0).unwrap();
let failed = super::publish_clone(&parent, &child, || {
panic!("failed security inheritance must not publish a child")
});
let attempts = probe.attempts();
drop(probe);
assert_eq!(failed.unwrap_err().linux_errno(), syscalls::Errno::ENOMEM);
assert_eq!(attempts, 1);
assert!(Arc::ptr_eq(&original, &child.seccomp_state()));
assert!(!child.no_new_privs());
assert!(!child.has_seccomp_syscall_work());
super::publish_clone(&parent, &child, || {
assert!(child.no_new_privs(), "clone published stale no_new_privs");
assert!(
Arc::ptr_eq(&updated, &child.seccomp_state()),
"clone published a stale seccomp snapshot"
);
assert!(child.has_seccomp_syscall_work());
Ok(())
})
.unwrap();
assert!(parent.signal().send_signal(
starry_signal::SignalInfo::new_kernel(starry_signal::Signo::SIGKILL),
false,
));
let interrupted = super::publish_clone(&parent, &child, || {
panic!("fatal parent must not publish a child")
});
assert_eq!(
interrupted.unwrap_err().linux_errno(),
syscalls::Errno::EINTR
);
}
#[axtest::axtest]
fn creation_errors_preserve_resource_domain() {
use ax_std::os::arceos::task::{runtime::RuntimeStatus, thread::TaskError};
use syscalls::Errno;
let errors = [
TaskError::ThreadCapacity,
TaskError::RuntimeFailure(RuntimeStatus::NoMemory as u32),
TaskError::RuntimeFailure(RuntimeStatus::Platform as u32),
];
assert_eq!(
errors.map(|error| super::map_task_creation_error(error).linux_errno()),
[Errno::EAGAIN, Errno::ENOMEM, Errno::EFAULT],
"clone must distinguish thread limits, OOM, and runtime faults"
);
}
#[axtest::axtest]
fn unpublished_process_rollback_releases_identity_and_topology() {
let namespace = crate::task::new_test_pid_namespace();
let reservation =
PidReservation::reserve(&namespace, PidReservationKind::ProcessLeader).unwrap();
let identity = reservation.identity();
let retired_identity = Arc::downgrade(&identity);
let tid = identity.acquire_role::<Tid>().unwrap();
let tgid = identity.acquire_role::<Tgid>().unwrap();
let transaction = CloneTransaction::new(identity.clone());
let process = crate::task::new_test_process_data(identity.clone(), tgid);
let retired_topology = Arc::downgrade(&process.proc);
drop(process);
drop(transaction);
drop(reservation);
drop(tid);
drop(identity);
assert!(
retired_topology.upgrade().is_none(),
"cancelled clone retained process topology"
);
assert!(
retired_identity.upgrade().is_none(),
"cancelled clone retained PID identity"
);
}
#[axtest::axtest]
fn cancelled_process_releases_tgid_before_last_identity() {
let namespace = crate::task::new_test_pid_namespace();
let reservation =
PidReservation::reserve(&namespace, PidReservationKind::ProcessLeader).unwrap();
let identity = reservation.identity();
let retired_identity = Arc::downgrade(&identity);
let tid = identity.acquire_role::<Tid>().unwrap();
let tgid = identity.acquire_role::<Tgid>().unwrap();
let transaction = CloneTransaction::new(identity.clone());
let process = crate::task::new_test_process_data(identity.clone(), tgid);
let retired_topology = Arc::downgrade(&process.proc);
drop(transaction);
drop(reservation);
drop(tid);
drop(identity);
drop(process);
assert!(
retired_topology.upgrade().is_none(),
"deferred clone retained process topology"
);
assert!(
retired_identity.upgrade().is_none(),
"deferred clone retained PID identity"
);
}
}