1use crate::{
8 object_get_info_single, object_get_info_vec, ok, sys, AsHandleRef, Handle, HandleBased,
9 HandleRef, Koid, MonotonicDuration, ObjectQuery, Process, ProcessOptions, Rights, Status, Task,
10 Topic, Vmar,
11};
12use bitflags::bitflags;
13
14#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
18#[repr(transparent)]
19pub struct Job(Handle);
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(Handle::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(Handle::from_raw(process_out)),
111 Vmar::from(Handle::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<Handle, 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 { Handle::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 sys, AsHandleRef, Instant, Job, JobAction, JobCondition, JobCriticalOptions,
345 JobDefaultTimerMode, JobInfo, JobPolicy, JobPolicyOption, Koid, MonotonicDuration, Signals,
346 Task,
347 };
348
349 #[test]
350 fn info_default() {
351 let job = fuchsia_runtime::job_default();
352 let info = job.info().unwrap();
353 assert_eq!(
354 info,
355 JobInfo { return_code: 0, exited: false, kill_on_oom: false, debugger_attached: false }
356 );
357 }
358
359 #[test]
360 fn runtime_info_default() {
361 let job = fuchsia_runtime::job_default();
362 let info = job.get_runtime_info().unwrap();
363 assert!(info.cpu_time > 0);
364 assert!(info.queue_time > 0);
365 }
366
367 #[test]
368 fn kill_and_info() {
369 let default_job = fuchsia_runtime::job_default();
370 let job = default_job.create_child_job().expect("Failed to create child job");
371 let info = job.info().unwrap();
372 assert_eq!(
373 info,
374 JobInfo { return_code: 0, exited: false, kill_on_oom: false, debugger_attached: false }
375 );
376
377 job.kill().expect("Failed to kill job");
378 job.wait_handle(Signals::TASK_TERMINATED, Instant::INFINITE).unwrap();
379
380 let info = job.info().unwrap();
381 assert_eq!(
382 info,
383 JobInfo {
384 return_code: sys::ZX_TASK_RETCODE_SYSCALL_KILL,
385 exited: true,
386 kill_on_oom: false,
387 debugger_attached: false
388 }
389 );
390 }
391
392 #[test]
393 fn create_and_set_policy() {
394 let default_job = fuchsia_runtime::job_default();
395 let child_job = default_job.create_child_job().expect("failed to create child job");
396 child_job
397 .set_policy(JobPolicy::Basic(
398 JobPolicyOption::Relative,
399 vec![
400 (JobCondition::NewChannel, JobAction::Deny),
401 (JobCondition::NewProcess, JobAction::Allow),
402 (JobCondition::BadHandle, JobAction::Kill),
403 ],
404 ))
405 .expect("failed to set job basic policy");
406 child_job
407 .set_policy(JobPolicy::TimerSlack(
408 MonotonicDuration::from_millis(10),
409 JobDefaultTimerMode::Early,
410 ))
411 .expect("failed to set job timer slack policy");
412 }
413
414 #[test]
415 fn create_and_set_critical() {
416 let default_job = fuchsia_runtime::job_default();
417 let child_job = default_job.create_child_job().expect("failed to create child job");
418
419 let binpath = CString::new("/pkg/bin/sleep_forever_util").unwrap();
420 let process =
421 fdio::spawn(&child_job, fdio::SpawnOptions::DEFAULT_LOADER, &binpath, &[&binpath])
423 .expect("Failed to spawn process");
424
425 child_job
426 .set_critical(JobCriticalOptions::RETCODE_NONZERO, &process)
427 .expect("failed to set critical process for job");
428 }
429
430 #[test]
431 fn create_and_report_children() {
432 let fresh_job =
433 fuchsia_runtime::job_default().create_child_job().expect("failed to create child job");
434 let mut created_children = Vec::new();
435 created_children.push(fresh_job.create_child_job().expect("failed to create child job"));
436 let reported_children_koids = fresh_job.children().unwrap();
437 assert_eq!(reported_children_koids.len(), 1);
438 assert_eq!(Koid::from(reported_children_koids[0]), created_children[0].get_koid().unwrap());
439 for _ in 0..INFO_VEC_SIZE_INITIAL {
440 created_children
441 .push(fresh_job.create_child_job().expect("failed to create child job"));
442 }
443 let reported_children_koids = fresh_job.children().unwrap();
444 let created_children_koids =
445 created_children.iter().map(|p| p.get_koid().unwrap()).collect::<Vec<_>>();
446 assert_eq!(reported_children_koids.len(), INFO_VEC_SIZE_INITIAL + 1);
447 assert_eq!(
448 HashSet::<_>::from_iter(&reported_children_koids),
449 HashSet::from_iter(&created_children_koids)
450 );
451 }
452
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 #[test]
487 fn get_child_from_koid() {
488 let fresh_job =
489 fuchsia_runtime::job_default().create_child_job().expect("failed to create child job");
490 let created_job = fresh_job.create_child_job().expect("failed to create child job");
491 let mut reported_job_koids = fresh_job.children().unwrap();
492 assert_eq!(reported_job_koids.len(), 1);
493 let reported_job_koid = reported_job_koids.remove(0);
494 let reported_job_handle =
495 Job::from(fresh_job.get_child(&reported_job_koid, zx::Rights::SAME_RIGHTS).unwrap());
496 assert_eq!(reported_job_handle.get_koid(), created_job.get_koid());
497
498 let created_process = reported_job_handle
500 .create_child_process(zx::ProcessOptions::empty(), "first".as_bytes())
501 .expect("failed to create child process")
502 .0;
503 let mut reported_process_koids = reported_job_handle.processes().unwrap();
504 assert_eq!(reported_process_koids.len(), 1);
505 let reported_process_koid = reported_process_koids.remove(0);
506 let reported_process_handle =
507 reported_job_handle.get_child(&reported_process_koid, zx::Rights::SAME_RIGHTS).unwrap();
508 assert_eq!(reported_process_handle.get_koid(), created_process.get_koid());
509 }
510}