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