use {
super::exception::*,
super::job_policy::*,
super::process::Process,
crate::object::*,
crate::task::Task,
alloc::sync::{Arc, Weak},
alloc::vec::Vec,
spin::Mutex,
};
#[allow(dead_code)]
pub struct Job {
base: KObjectBase,
_counter: CountHelper,
parent: Option<Arc<Job>>,
parent_policy: JobPolicy,
exceptionate: Arc<Exceptionate>,
debug_exceptionate: Arc<Exceptionate>,
inner: Mutex<JobInner>,
}
impl_kobject!(Job
fn get_child(&self, id: KoID) -> ZxResult<Arc<dyn KernelObject>> {
let inner = self.inner.lock();
if let Some(job) = inner.children.iter().filter_map(|o|o.upgrade()).find(|o| o.id() == id) {
return Ok(job);
}
if let Some(proc) = inner.processes.iter().find(|o| o.id() == id) {
return Ok(proc.clone());
}
Err(ZxError::NOT_FOUND)
}
fn related_koid(&self) -> KoID {
self.parent.as_ref().map(|p| p.id()).unwrap_or(0)
}
);
define_count_helper!(Job);
#[derive(Default)]
struct JobInner {
policy: JobPolicy,
children: Vec<Weak<Job>>,
processes: Vec<Arc<Process>>,
killed: bool,
timer_policy: TimerSlack,
self_ref: Weak<Job>,
}
impl Job {
pub fn root() -> Arc<Self> {
let job = Arc::new(Job {
base: KObjectBase::new(),
_counter: CountHelper::new(),
parent: None,
parent_policy: JobPolicy::default(),
exceptionate: Exceptionate::new(ExceptionChannelType::Job),
debug_exceptionate: Exceptionate::new(ExceptionChannelType::JobDebugger),
inner: Mutex::new(JobInner::default()),
});
job.inner.lock().self_ref = Arc::downgrade(&job);
job
}
pub fn create_child(self: &Arc<Self>) -> ZxResult<Arc<Self>> {
let mut inner = self.inner.lock();
if inner.killed {
return Err(ZxError::BAD_STATE);
}
let child = Arc::new(Job {
base: KObjectBase::new(),
_counter: CountHelper::new(),
parent: Some(self.clone()),
parent_policy: inner.policy.merge(&self.parent_policy),
exceptionate: Exceptionate::new(ExceptionChannelType::Job),
debug_exceptionate: Exceptionate::new(ExceptionChannelType::JobDebugger),
inner: Mutex::new(JobInner::default()),
});
let child_weak = Arc::downgrade(&child);
child.inner.lock().self_ref = child_weak.clone();
inner.children.push(child_weak);
Ok(child)
}
fn remove_child(&self, to_remove: &Weak<Job>) {
let mut inner = self.inner.lock();
inner.children.retain(|child| !to_remove.ptr_eq(child));
if inner.killed && inner.processes.is_empty() && inner.children.is_empty() {
drop(inner);
self.terminate()
}
}
pub fn policy(&self) -> JobPolicy {
self.inner.lock().policy.merge(&self.parent_policy)
}
pub fn parent(&self) -> Option<Arc<Self>> {
self.parent.clone()
}
pub fn set_policy_basic(
&self,
options: SetPolicyOptions,
policies: &[BasicPolicy],
) -> ZxResult {
let mut inner = self.inner.lock();
if !inner.is_empty() {
return Err(ZxError::BAD_STATE);
}
for policy in policies {
if self.parent_policy.get_action(policy.condition).is_some() {
match options {
SetPolicyOptions::Absolute => return Err(ZxError::ALREADY_EXISTS),
SetPolicyOptions::Relative => {}
}
} else {
inner.policy.apply(*policy);
}
}
Ok(())
}
pub fn set_policy_timer_slack(&self, policy: TimerSlackPolicy) -> ZxResult {
let mut inner = self.inner.lock();
if !inner.is_empty() {
return Err(ZxError::BAD_STATE);
}
check_timer_policy(&policy)?;
inner.timer_policy = inner.timer_policy.generate_new(policy);
Ok(())
}
pub(super) fn add_process(&self, process: Arc<Process>) -> ZxResult {
let mut inner = self.inner.lock();
if inner.killed {
return Err(ZxError::BAD_STATE);
}
inner.processes.push(process);
Ok(())
}
pub(super) fn remove_process(&self, id: KoID) {
let mut inner = self.inner.lock();
inner.processes.retain(|proc| proc.id() != id);
if inner.killed && inner.processes.is_empty() && inner.children.is_empty() {
drop(inner);
self.terminate()
}
}
pub fn get_info(&self) -> JobInfo {
JobInfo::default()
}
pub fn check_root_job(&self) -> ZxResult {
if self.parent.is_some() {
Err(ZxError::ACCESS_DENIED)
} else {
Ok(())
}
}
pub fn process_ids(&self) -> Vec<KoID> {
self.inner.lock().processes.iter().map(|p| p.id()).collect()
}
pub fn children_ids(&self) -> Vec<KoID> {
self.inner
.lock()
.children
.iter()
.filter_map(|j| j.upgrade())
.map(|j| j.id())
.collect()
}
pub fn is_empty(&self) -> bool {
self.inner.lock().is_empty()
}
fn terminate(&self) {
self.exceptionate.shutdown();
self.debug_exceptionate.shutdown();
self.base.signal_set(Signal::JOB_TERMINATED);
if let Some(parent) = self.parent.as_ref() {
parent.remove_child(&self.inner.lock().self_ref)
}
}
}
impl Task for Job {
fn kill(&self) {
let (children, processes) = {
let mut inner = self.inner.lock();
if inner.killed {
return;
}
inner.killed = true;
(inner.children.clone(), inner.processes.clone())
};
if children.is_empty() && processes.is_empty() {
self.terminate();
return;
}
for child in children {
if let Some(child) = child.upgrade() {
child.kill();
}
}
for proc in processes {
proc.kill();
}
}
fn suspend(&self) {
panic!("job do not support suspend");
}
fn resume(&self) {
panic!("job do not support resume");
}
fn exceptionate(&self) -> Arc<Exceptionate> {
self.exceptionate.clone()
}
fn debug_exceptionate(&self) -> Arc<Exceptionate> {
self.debug_exceptionate.clone()
}
}
impl JobInner {
fn is_empty(&self) -> bool {
self.processes.is_empty() && self.children.is_empty()
}
}
impl Drop for Job {
fn drop(&mut self) {
self.terminate();
}
}
#[repr(C)]
#[derive(Default)]
pub struct JobInfo {
return_code: i64,
exited: bool,
kill_on_oom: bool,
debugger_attached: bool,
padding: [u8; 5],
}
#[cfg(test)]
mod tests {
use super::*;
use crate::task::{CurrentThread, Status, Thread, ThreadState, TASK_RETCODE_SYSCALL_KILL};
#[test]
fn create() {
let root_job = Job::root();
let job = Job::create_child(&root_job).expect("failed to create job");
let child = root_job
.get_child(job.id())
.unwrap()
.downcast_arc()
.unwrap();
assert!(Arc::ptr_eq(&child, &job));
assert_eq!(job.related_koid(), root_job.id());
assert_eq!(root_job.related_koid(), 0);
root_job.kill();
assert_eq!(root_job.create_child().err(), Some(ZxError::BAD_STATE));
}
#[test]
fn set_policy() {
let root_job = Job::root();
assert_eq!(
root_job.policy().get_action(PolicyCondition::BadHandle),
None
);
let policy = &[BasicPolicy {
condition: PolicyCondition::BadHandle,
action: PolicyAction::Deny,
}];
root_job
.set_policy_basic(SetPolicyOptions::Relative, policy)
.expect("failed to set policy");
assert_eq!(
root_job.policy().get_action(PolicyCondition::BadHandle),
Some(PolicyAction::Deny)
);
let policy = &[BasicPolicy {
condition: PolicyCondition::BadHandle,
action: PolicyAction::Allow,
}];
root_job
.set_policy_basic(SetPolicyOptions::Relative, policy)
.expect("failed to set policy");
assert_eq!(
root_job.policy().get_action(PolicyCondition::BadHandle),
Some(PolicyAction::Allow)
);
let job = Job::create_child(&root_job).expect("failed to create job");
assert_eq!(
job.policy().get_action(PolicyCondition::BadHandle),
Some(PolicyAction::Allow)
);
assert_eq!(
root_job.set_policy_basic(SetPolicyOptions::Relative, &[]),
Err(ZxError::BAD_STATE)
);
let policy = &[BasicPolicy {
condition: PolicyCondition::WrongObject,
action: PolicyAction::Allow,
}];
job.set_policy_basic(SetPolicyOptions::Relative, policy)
.expect("failed to set policy");
assert_eq!(
job.policy().get_action(PolicyCondition::WrongObject),
Some(PolicyAction::Allow)
);
let policy = &[BasicPolicy {
condition: PolicyCondition::BadHandle,
action: PolicyAction::Deny,
}];
job.set_policy_basic(SetPolicyOptions::Relative, policy)
.expect("failed to set policy");
assert_eq!(
job.policy().get_action(PolicyCondition::BadHandle),
Some(PolicyAction::Allow)
);
assert_eq!(
job.set_policy_basic(SetPolicyOptions::Absolute, policy),
Err(ZxError::ALREADY_EXISTS)
);
}
#[test]
fn parent_child() {
let root_job = Job::root();
let job = Job::create_child(&root_job).expect("failed to create job");
let proc = Process::create(&root_job, "proc").expect("failed to create process");
assert_eq!(root_job.get_child(job.id()).unwrap().id(), job.id());
assert_eq!(root_job.get_child(proc.id()).unwrap().id(), proc.id());
assert_eq!(
root_job.get_child(root_job.id()).err(),
Some(ZxError::NOT_FOUND)
);
assert!(Arc::ptr_eq(&job.parent().unwrap(), &root_job));
let job1 = root_job.create_child().expect("failed to create job");
let proc1 = Process::create(&root_job, "proc1").expect("failed to create process");
assert_eq!(root_job.children_ids(), vec![job.id(), job1.id()]);
assert_eq!(root_job.process_ids(), vec![proc.id(), proc1.id()]);
root_job.kill();
assert_eq!(root_job.create_child().err(), Some(ZxError::BAD_STATE));
}
#[test]
fn check() {
let root_job = Job::root();
assert!(root_job.is_empty());
let job = root_job.create_child().expect("failed to create job");
assert_eq!(root_job.check_root_job(), Ok(()));
assert_eq!(job.check_root_job(), Err(ZxError::ACCESS_DENIED));
assert!(!root_job.is_empty());
assert!(job.is_empty());
let _proc = Process::create(&job, "proc").expect("failed to create process");
assert!(!job.is_empty());
}
#[test]
fn kill() {
let root_job = Job::root();
let job = Job::create_child(&root_job).expect("failed to create job");
let proc = Process::create(&root_job, "proc").expect("failed to create process");
let thread = Thread::create(&proc, "thread").expect("failed to create thread");
let current_thread = CurrentThread(thread.clone());
root_job.kill();
assert!(root_job.inner.lock().killed);
assert!(job.inner.lock().killed);
assert_eq!(proc.status(), Status::Exited(TASK_RETCODE_SYSCALL_KILL));
assert_eq!(thread.state(), ThreadState::Dying);
assert!(!root_job.signal().contains(Signal::JOB_TERMINATED));
assert!(job.signal().contains(Signal::JOB_TERMINATED));
assert!(!proc.signal().contains(Signal::PROCESS_TERMINATED));
assert!(!thread.signal().contains(Signal::THREAD_TERMINATED));
std::mem::drop(current_thread);
assert!(root_job.inner.lock().killed);
assert!(job.inner.lock().killed);
assert_eq!(proc.status(), Status::Exited(TASK_RETCODE_SYSCALL_KILL));
assert_eq!(thread.state(), ThreadState::Dead);
assert!(root_job.signal().contains(Signal::JOB_TERMINATED));
assert!(job.signal().contains(Signal::JOB_TERMINATED));
assert!(proc.signal().contains(Signal::PROCESS_TERMINATED));
assert!(thread.signal().contains(Signal::THREAD_TERMINATED));
let root_job = Job::root();
root_job.kill();
assert!(root_job.inner.lock().killed);
assert!(root_job.signal().contains(Signal::JOB_TERMINATED));
let root_job = Job::root();
let job = Job::create_child(&root_job).expect("failed to create job");
let proc = Process::create(&root_job, "proc").expect("failed to create process");
root_job.kill();
assert!(root_job.inner.lock().killed);
assert!(job.inner.lock().killed);
assert_eq!(proc.status(), Status::Exited(TASK_RETCODE_SYSCALL_KILL));
assert!(root_job.signal().contains(Signal::JOB_TERMINATED));
assert!(job.signal().contains(Signal::JOB_TERMINATED));
assert!(proc.signal().contains(Signal::PROCESS_TERMINATED));
}
#[test]
fn critical_process() {
let root_job = Job::root();
let job = root_job.create_child().unwrap();
let job1 = root_job.create_child().unwrap();
let proc = Process::create(&job, "proc").expect("failed to create process");
assert_eq!(
proc.set_critical_at_job(&job1, true).err(),
Some(ZxError::INVALID_ARGS)
);
proc.set_critical_at_job(&root_job, true).unwrap();
assert_eq!(
proc.set_critical_at_job(&job, true).err(),
Some(ZxError::ALREADY_BOUND)
);
proc.exit(666);
assert!(root_job.inner.lock().killed);
assert!(root_job.signal().contains(Signal::JOB_TERMINATED));
}
}