1use crate::{
8 AsHandleRef, HandleBased, HandleRef, Koid, MonotonicDuration, NullableHandle, ObjectQuery,
9 Process, ProcessOptions, Rights, Status, Task, Topic, Vmar, ok, sys,
10};
11use bitflags::bitflags;
12
13#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
17#[repr(transparent)]
18pub struct Job(NullableHandle);
19impl_handle_based!(Job);
20
21#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)]
22pub struct JobInfo {
23 pub return_code: i64,
24 pub exited: bool,
25 pub kill_on_oom: bool,
26 pub debugger_attached: bool,
27}
28
29impl From<sys::zx_info_job_t> for JobInfo {
30 fn from(
31 sys::zx_info_job_t { return_code, exited, kill_on_oom, debugger_attached }: sys::zx_info_job_t,
32 ) -> Self {
33 Self {
34 return_code,
35 exited: exited != 0,
36 kill_on_oom: kill_on_oom != 0,
37 debugger_attached: debugger_attached != 0,
38 }
39 }
40}
41
42struct JobInfoQuery;
44unsafe impl ObjectQuery for JobInfoQuery {
45 const TOPIC: Topic = Topic::JOB;
46 type InfoTy = sys::zx_info_job_t;
47}
48
49struct JobProcessesInfo;
50
51unsafe impl ObjectQuery for JobProcessesInfo {
52 const TOPIC: Topic = Topic::JOB_PROCESSES;
53 type InfoTy = Koid;
54}
55
56struct JobChildrenInfo;
57
58unsafe impl ObjectQuery for JobChildrenInfo {
59 const TOPIC: Topic = Topic::JOB_CHILDREN;
60 type InfoTy = Koid;
61}
62
63impl Job {
64 pub fn create_child_job(&self) -> Result<Job, Status> {
70 let parent_job_raw = self.raw_handle();
71 let mut out = 0;
72 let options = 0;
73 let status = unsafe { sys::zx_job_create(parent_job_raw, options, &mut out) };
74 ok(status)?;
75 unsafe { Ok(Job::from(NullableHandle::from_raw(out))) }
76 }
77
78 pub fn create_child_process(
87 &self,
88 options: ProcessOptions,
89 name: &[u8],
90 ) -> Result<(Process, Vmar), Status> {
91 let parent_job_raw = self.raw_handle();
92 let name_ptr = name.as_ptr();
93 let name_len = name.len();
94 let mut process_out = 0;
95 let mut vmar_out = 0;
96 let status = unsafe {
97 sys::zx_process_create(
98 parent_job_raw,
99 name_ptr,
100 name_len,
101 options.bits(),
102 &mut process_out,
103 &mut vmar_out,
104 )
105 };
106 ok(status)?;
107 unsafe {
108 Ok((
109 Process::from(NullableHandle::from_raw(process_out)),
110 Vmar::from(NullableHandle::from_raw(vmar_out)),
111 ))
112 }
113 }
114
115 pub fn info(&self) -> Result<JobInfo, Status> {
119 Ok(JobInfo::from(self.0.get_info_single::<JobInfoQuery>()?))
120 }
121
122 pub fn set_policy(&self, policy: JobPolicy) -> Result<(), Status> {
124 match policy {
125 JobPolicy::Basic(policy_option, policy_set) => {
126 let sys_opt = policy_option.into();
127 let sys_topic = sys::ZX_JOB_POL_BASIC;
128 let sys_pol: Vec<sys::zx_policy_basic> = policy_set
129 .into_iter()
130 .map(|(condition, action)| sys::zx_policy_basic {
131 condition: condition.into(),
132 policy: action.into(),
133 })
134 .collect();
135 let sys_count = sys_pol.len() as u32;
136
137 ok(unsafe {
138 sys::zx_job_set_policy(
142 self.raw_handle(),
143 sys_opt,
144 sys_topic,
145 sys_pol.as_ptr().cast::<u8>(),
146 sys_count,
147 )
148 })
149 }
150 JobPolicy::TimerSlack(min_slack_duration, default_mode) => {
151 let sys_opt = sys::ZX_JOB_POL_RELATIVE;
152 let sys_topic = sys::ZX_JOB_POL_TIMER_SLACK;
153 let sys_pol = sys::zx_policy_timer_slack {
154 min_slack: min_slack_duration.into_nanos(),
155 default_mode: default_mode.into(),
156 };
157 let sys_count = 1;
158
159 ok(unsafe {
160 sys::zx_job_set_policy(
165 self.raw_handle(),
166 sys_opt,
167 sys_topic,
168 std::ptr::from_ref(&sys_pol).cast::<u8>(),
169 sys_count,
170 )
171 })
172 }
173 }
174 }
175
176 pub fn set_critical(&self, opts: JobCriticalOptions, process: &Process) -> Result<(), Status> {
178 ok(unsafe {
179 sys::zx_job_set_critical(self.raw_handle(), opts.bits(), process.raw_handle())
180 })
181 }
182
183 pub fn processes(&self) -> Result<Vec<Koid>, Status> {
187 self.0.get_info_vec::<JobProcessesInfo>()
188 }
189
190 pub fn children(&self) -> Result<Vec<Koid>, Status> {
194 self.0.get_info_vec::<JobChildrenInfo>()
195 }
196
197 pub fn get_child(&self, koid: &Koid, rights: Rights) -> Result<NullableHandle, Status> {
201 let mut handle: sys::zx_handle_t = Default::default();
202 let status = unsafe {
203 sys::zx_object_get_child(
204 self.raw_handle(),
205 Koid::from(*koid).raw_koid(),
206 rights.bits(),
207 std::ptr::from_mut(&mut handle),
208 )
209 };
210 ok(status)?;
211 Ok(unsafe { NullableHandle::from_raw(handle) })
212 }
213}
214
215#[derive(Debug, Clone, PartialEq)]
218pub enum JobPolicyOption {
219 Relative,
220 Absolute,
221}
222
223impl Into<u32> for JobPolicyOption {
224 fn into(self) -> u32 {
225 match self {
226 JobPolicyOption::Relative => sys::ZX_JOB_POL_RELATIVE,
227 JobPolicyOption::Absolute => sys::ZX_JOB_POL_ABSOLUTE,
228 }
229 }
230}
231
232#[derive(Debug, Clone, PartialEq)]
235pub enum JobPolicy {
236 Basic(JobPolicyOption, Vec<(JobCondition, JobAction)>),
237 TimerSlack(MonotonicDuration, JobDefaultTimerMode),
238}
239
240#[derive(Debug, Clone, PartialEq)]
242pub enum JobCondition {
243 BadHandle,
244 WrongObject,
245 VmarWx,
246 NewAny,
247 NewVmo,
248 NewChannel,
249 NewEvent,
250 NewEventpair,
251 NewPort,
252 NewSocket,
253 NewFifo,
254 NewTimer,
255 NewProcess,
256 NewProfile,
257 NewPager,
258 AmbientMarkVmoExec,
259}
260
261impl Into<u32> for JobCondition {
262 fn into(self) -> u32 {
263 match self {
264 JobCondition::BadHandle => sys::ZX_POL_BAD_HANDLE,
265 JobCondition::WrongObject => sys::ZX_POL_WRONG_OBJECT,
266 JobCondition::VmarWx => sys::ZX_POL_VMAR_WX,
267 JobCondition::NewAny => sys::ZX_POL_NEW_ANY,
268 JobCondition::NewVmo => sys::ZX_POL_NEW_VMO,
269 JobCondition::NewChannel => sys::ZX_POL_NEW_CHANNEL,
270 JobCondition::NewEvent => sys::ZX_POL_NEW_EVENT,
271 JobCondition::NewEventpair => sys::ZX_POL_NEW_EVENTPAIR,
272 JobCondition::NewPort => sys::ZX_POL_NEW_PORT,
273 JobCondition::NewSocket => sys::ZX_POL_NEW_SOCKET,
274 JobCondition::NewFifo => sys::ZX_POL_NEW_FIFO,
275 JobCondition::NewTimer => sys::ZX_POL_NEW_TIMER,
276 JobCondition::NewProcess => sys::ZX_POL_NEW_PROCESS,
277 JobCondition::NewProfile => sys::ZX_POL_NEW_PROFILE,
278 JobCondition::NewPager => sys::ZX_POL_NEW_PAGER,
279 JobCondition::AmbientMarkVmoExec => sys::ZX_POL_AMBIENT_MARK_VMO_EXEC,
280 }
281 }
282}
283
284#[derive(Debug, Clone, PartialEq)]
286pub enum JobAction {
287 Allow,
288 Deny,
289 AllowException,
290 DenyException,
291 Kill,
292}
293
294impl Into<u32> for JobAction {
295 fn into(self) -> u32 {
296 match self {
297 JobAction::Allow => sys::ZX_POL_ACTION_ALLOW,
298 JobAction::Deny => sys::ZX_POL_ACTION_DENY,
299 JobAction::AllowException => sys::ZX_POL_ACTION_ALLOW_EXCEPTION,
300 JobAction::DenyException => sys::ZX_POL_ACTION_DENY_EXCEPTION,
301 JobAction::Kill => sys::ZX_POL_ACTION_KILL,
302 }
303 }
304}
305
306#[derive(Debug, Clone, PartialEq)]
308pub enum JobDefaultTimerMode {
309 Center,
310 Early,
311 Late,
312}
313
314impl Into<u32> for JobDefaultTimerMode {
315 fn into(self) -> u32 {
316 match self {
317 JobDefaultTimerMode::Center => sys::ZX_TIMER_SLACK_CENTER,
318 JobDefaultTimerMode::Early => sys::ZX_TIMER_SLACK_EARLY,
319 JobDefaultTimerMode::Late => sys::ZX_TIMER_SLACK_LATE,
320 }
321 }
322}
323
324impl Task for Job {}
325
326bitflags! {
327 #[repr(transparent)]
329 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
330 pub struct JobCriticalOptions: u32 {
331 const RETCODE_NONZERO = sys::ZX_JOB_CRITICAL_PROCESS_RETCODE_NONZERO;
332 }
333}
334
335#[cfg(test)]
336mod tests {
337 use crate::INFO_VEC_SIZE_INITIAL;
340 use std::collections::HashSet;
341 use std::ffi::CString;
342 use zx::{
343 AsHandleRef, Instant, JobAction, JobCondition, JobCriticalOptions, JobDefaultTimerMode,
344 JobInfo, JobPolicy, JobPolicyOption, Koid, MonotonicDuration, Signals, Task, sys,
345 };
346
347 #[test]
348 fn info_default() {
349 let job = fuchsia_runtime::job_default();
350 let info = job.info().unwrap();
351 assert_eq!(
352 info,
353 JobInfo { return_code: 0, exited: false, kill_on_oom: false, debugger_attached: false }
354 );
355 }
356
357 #[test]
358 fn runtime_info_default() {
359 let job = fuchsia_runtime::job_default();
360 let info = job.get_runtime_info().unwrap();
361 assert!(info.cpu_time > 0);
362 assert!(info.queue_time > 0);
363 }
364
365 #[test]
366 fn kill_and_info() {
367 let default_job = fuchsia_runtime::job_default();
368 let job = default_job.create_child_job().expect("Failed to create child job");
369 let info = job.info().unwrap();
370 assert_eq!(
371 info,
372 JobInfo { return_code: 0, exited: false, kill_on_oom: false, debugger_attached: false }
373 );
374
375 job.kill().expect("Failed to kill job");
376 job.wait_one(Signals::TASK_TERMINATED, Instant::INFINITE).unwrap();
377
378 let info = job.info().unwrap();
379 assert_eq!(
380 info,
381 JobInfo {
382 return_code: sys::ZX_TASK_RETCODE_SYSCALL_KILL,
383 exited: true,
384 kill_on_oom: false,
385 debugger_attached: false
386 }
387 );
388 }
389
390 #[test]
391 fn create_and_set_policy() {
392 let default_job = fuchsia_runtime::job_default();
393 let child_job = default_job.create_child_job().expect("failed to create child job");
394 child_job
395 .set_policy(JobPolicy::Basic(
396 JobPolicyOption::Relative,
397 vec![
398 (JobCondition::NewChannel, JobAction::Deny),
399 (JobCondition::NewProcess, JobAction::Allow),
400 (JobCondition::BadHandle, JobAction::Kill),
401 ],
402 ))
403 .expect("failed to set job basic policy");
404 child_job
405 .set_policy(JobPolicy::TimerSlack(
406 MonotonicDuration::from_millis(10),
407 JobDefaultTimerMode::Early,
408 ))
409 .expect("failed to set job timer slack policy");
410 }
411
412 #[test]
413 fn create_and_set_critical() {
414 let default_job = fuchsia_runtime::job_default();
415 let child_job = default_job.create_child_job().expect("failed to create child job");
416
417 let binpath = CString::new("/pkg/bin/sleep_forever_util").unwrap();
418 let process =
419 fdio::spawn(&child_job, fdio::SpawnOptions::DEFAULT_LOADER, &binpath, &[&binpath])
421 .expect("Failed to spawn process");
422
423 child_job
424 .set_critical(JobCriticalOptions::RETCODE_NONZERO, &process)
425 .expect("failed to set critical process for job");
426 }
427
428 #[test]
429 fn create_and_report_children() {
430 let fresh_job =
431 fuchsia_runtime::job_default().create_child_job().expect("failed to create child job");
432 let mut created_children = Vec::new();
433 created_children.push(fresh_job.create_child_job().expect("failed to create child job"));
434 let reported_children_koids = fresh_job.children().unwrap();
435 assert_eq!(reported_children_koids.len(), 1);
436 assert_eq!(Koid::from(reported_children_koids[0]), created_children[0].get_koid().unwrap());
437 for _ in 0..INFO_VEC_SIZE_INITIAL {
438 created_children
439 .push(fresh_job.create_child_job().expect("failed to create child job"));
440 }
441 let reported_children_koids = fresh_job.children().unwrap();
442 let created_children_koids =
443 created_children.iter().map(|p| p.get_koid().unwrap()).collect::<Vec<_>>();
444 assert_eq!(reported_children_koids.len(), INFO_VEC_SIZE_INITIAL + 1);
445 assert_eq!(
446 HashSet::<_>::from_iter(&reported_children_koids),
447 HashSet::from_iter(&created_children_koids)
448 );
449 }
450
451 #[cfg(not(feature = "vdso_next"))]
453 #[test]
454 fn create_and_report_processes() {
455 let fresh_job =
456 fuchsia_runtime::job_default().create_child_job().expect("failed to create child job");
457 let mut created_processes = Vec::new();
458 let options = zx::ProcessOptions::empty();
459 created_processes.push(
460 fresh_job
461 .create_child_process(options.clone(), "first".as_bytes())
462 .expect("failed to create child process")
463 .0,
464 );
465 let reported_process_koids = fresh_job.processes().unwrap();
466 assert_eq!(reported_process_koids.len(), 1);
467 assert_eq!(Koid::from(reported_process_koids[0]), created_processes[0].get_koid().unwrap());
468 for index in 0..INFO_VEC_SIZE_INITIAL {
469 created_processes.push(
470 fresh_job
471 .create_child_process(options.clone(), format!("{index}").as_bytes())
472 .expect("failed to create child process")
473 .0,
474 );
475 }
476 let reported_process_koids = fresh_job.processes().unwrap();
477 let created_process_koids =
478 created_processes.iter().map(|p| p.get_koid().unwrap()).collect::<Vec<_>>();
479 assert_eq!(reported_process_koids.len(), INFO_VEC_SIZE_INITIAL + 1);
480 assert_eq!(
481 HashSet::<_>::from_iter(&reported_process_koids),
482 HashSet::from_iter(&created_process_koids)
483 );
484 }
485
486 #[cfg(not(feature = "vdso_next"))]
488 #[test]
489 fn get_child_from_koid() {
490 let fresh_job =
491 fuchsia_runtime::job_default().create_child_job().expect("failed to create child job");
492 let created_job = fresh_job.create_child_job().expect("failed to create child job");
493 let mut reported_job_koids = fresh_job.children().unwrap();
494 assert_eq!(reported_job_koids.len(), 1);
495 let reported_job_koid = reported_job_koids.remove(0);
496 let reported_job_handle = zx::Job::from(
497 fresh_job.get_child(&reported_job_koid, zx::Rights::SAME_RIGHTS).unwrap(),
498 );
499 assert_eq!(reported_job_handle.get_koid(), created_job.get_koid());
500
501 let created_process = reported_job_handle
503 .create_child_process(zx::ProcessOptions::empty(), "first".as_bytes())
504 .expect("failed to create child process")
505 .0;
506 let mut reported_process_koids = reported_job_handle.processes().unwrap();
507 assert_eq!(reported_process_koids.len(), 1);
508 let reported_process_koid = reported_process_koids.remove(0);
509 let reported_process_handle =
510 reported_job_handle.get_child(&reported_process_koid, zx::Rights::SAME_RIGHTS).unwrap();
511 assert_eq!(reported_process_handle.get_koid(), created_process.get_koid());
512 }
513}