1use crate::{
8 Koid, MonotonicDuration, NullableHandle, ObjectQuery, Process, ProcessOptions, Rights, Status,
9 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 _padding: [0; 4],
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 self.0.get_info_vec::<JobProcessesInfo>()
189 }
190
191 pub fn children(&self) -> Result<Vec<Koid>, Status> {
195 self.0.get_info_vec::<JobChildrenInfo>()
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
307pub type JobDefaultTimerMode = crate::TimerSlackMode;
309
310impl Task for Job {}
311
312bitflags! {
313 #[repr(transparent)]
315 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
316 pub struct JobCriticalOptions: u32 {
317 const RETCODE_NONZERO = sys::ZX_JOB_CRITICAL_PROCESS_RETCODE_NONZERO;
318 }
319}
320
321#[cfg(test)]
322mod tests {
323 use crate::INFO_VEC_SIZE_INITIAL;
326 use std::collections::HashSet;
327 use std::ffi::CString;
328 use zx::{
329 Instant, JobAction, JobCondition, JobCriticalOptions, JobDefaultTimerMode, JobInfo,
330 JobPolicy, JobPolicyOption, Koid, MonotonicDuration, Signals, Task, sys,
331 };
332
333 #[test]
334 fn info_default() {
335 let job = fuchsia_runtime::job_default();
336 let info = job.info().unwrap();
337 assert_eq!(
338 info,
339 JobInfo { return_code: 0, exited: false, kill_on_oom: false, debugger_attached: false }
340 );
341 }
342
343 #[test]
344 fn runtime_info_default() {
345 let job = fuchsia_runtime::job_default();
346 let info = job.get_runtime_info().unwrap();
347 assert!(info.cpu_time > 0);
348 assert!(info.queue_time > 0);
349 }
350
351 #[test]
352 fn kill_and_info() {
353 let default_job = fuchsia_runtime::job_default();
354 let job = default_job.create_child_job().expect("Failed to create child job");
355 let info = job.info().unwrap();
356 assert_eq!(
357 info,
358 JobInfo { return_code: 0, exited: false, kill_on_oom: false, debugger_attached: false }
359 );
360
361 job.kill().expect("Failed to kill job");
362 job.wait_one(Signals::TASK_TERMINATED, Instant::INFINITE).unwrap();
363
364 let info = job.info().unwrap();
365 assert_eq!(
366 info,
367 JobInfo {
368 return_code: sys::ZX_TASK_RETCODE_SYSCALL_KILL,
369 exited: true,
370 kill_on_oom: false,
371 debugger_attached: false
372 }
373 );
374 }
375
376 #[test]
377 fn create_and_set_policy() {
378 let default_job = fuchsia_runtime::job_default();
379 let child_job = default_job.create_child_job().expect("failed to create child job");
380 child_job
381 .set_policy(JobPolicy::Basic(
382 JobPolicyOption::Relative,
383 vec![
384 (JobCondition::NewChannel, JobAction::Deny),
385 (JobCondition::NewProcess, JobAction::Allow),
386 (JobCondition::BadHandle, JobAction::Kill),
387 ],
388 ))
389 .expect("failed to set job basic policy");
390 child_job
391 .set_policy(JobPolicy::TimerSlack(
392 MonotonicDuration::from_millis(10),
393 JobDefaultTimerMode::Early,
394 ))
395 .expect("failed to set job timer slack policy");
396 }
397
398 #[test]
399 fn create_and_set_critical() {
400 let default_job = fuchsia_runtime::job_default();
401 let child_job = default_job.create_child_job().expect("failed to create child job");
402
403 let binpath = CString::new("/pkg/bin/sleep_forever_util").unwrap();
404 let process =
405 fdio::spawn(&child_job, fdio::SpawnOptions::DEFAULT_LOADER, &binpath, &[&binpath])
407 .expect("Failed to spawn process");
408
409 child_job
410 .set_critical(JobCriticalOptions::RETCODE_NONZERO, &process)
411 .expect("failed to set critical process for job");
412 }
413
414 #[test]
415 fn create_and_report_children() {
416 let fresh_job =
417 fuchsia_runtime::job_default().create_child_job().expect("failed to create child job");
418 let mut created_children = Vec::new();
419 created_children.push(fresh_job.create_child_job().expect("failed to create child job"));
420 let reported_children_koids = fresh_job.children().unwrap();
421 assert_eq!(reported_children_koids.len(), 1);
422 assert_eq!(Koid::from(reported_children_koids[0]), created_children[0].koid().unwrap());
423 for _ in 0..INFO_VEC_SIZE_INITIAL {
424 created_children
425 .push(fresh_job.create_child_job().expect("failed to create child job"));
426 }
427 let reported_children_koids = fresh_job.children().unwrap();
428 let created_children_koids =
429 created_children.iter().map(|p| p.koid().unwrap()).collect::<Vec<_>>();
430 assert_eq!(reported_children_koids.len(), INFO_VEC_SIZE_INITIAL + 1);
431 assert_eq!(
432 HashSet::<_>::from_iter(&reported_children_koids),
433 HashSet::from_iter(&created_children_koids)
434 );
435 }
436
437 #[cfg(not(feature = "vdso_next"))]
439 #[test]
440 fn create_and_report_processes() {
441 let fresh_job =
442 fuchsia_runtime::job_default().create_child_job().expect("failed to create child job");
443 let mut created_processes = Vec::new();
444 let options = zx::ProcessOptions::empty();
445 created_processes.push(
446 fresh_job
447 .create_child_process(options.clone(), "first".as_bytes())
448 .expect("failed to create child process")
449 .0,
450 );
451 let reported_process_koids = fresh_job.processes().unwrap();
452 assert_eq!(reported_process_koids.len(), 1);
453 assert_eq!(Koid::from(reported_process_koids[0]), created_processes[0].koid().unwrap());
454 for index in 0..INFO_VEC_SIZE_INITIAL {
455 created_processes.push(
456 fresh_job
457 .create_child_process(options.clone(), format!("{index}").as_bytes())
458 .expect("failed to create child process")
459 .0,
460 );
461 }
462 let reported_process_koids = fresh_job.processes().unwrap();
463 let created_process_koids =
464 created_processes.iter().map(|p| p.koid().unwrap()).collect::<Vec<_>>();
465 assert_eq!(reported_process_koids.len(), INFO_VEC_SIZE_INITIAL + 1);
466 assert_eq!(
467 HashSet::<_>::from_iter(&reported_process_koids),
468 HashSet::from_iter(&created_process_koids)
469 );
470 }
471
472 #[cfg(not(feature = "vdso_next"))]
474 #[test]
475 fn get_child_from_koid() {
476 let fresh_job =
477 fuchsia_runtime::job_default().create_child_job().expect("failed to create child job");
478 let created_job = fresh_job.create_child_job().expect("failed to create child job");
479 let mut reported_job_koids = fresh_job.children().unwrap();
480 assert_eq!(reported_job_koids.len(), 1);
481 let reported_job_koid = reported_job_koids.remove(0);
482 let reported_job_handle = zx::Job::from(
483 fresh_job.get_child(&reported_job_koid, zx::Rights::SAME_RIGHTS).unwrap(),
484 );
485 assert_eq!(reported_job_handle.koid(), created_job.koid());
486
487 let created_process = reported_job_handle
489 .create_child_process(zx::ProcessOptions::empty(), "first".as_bytes())
490 .expect("failed to create child process")
491 .0;
492 let mut reported_process_koids = reported_job_handle.processes().unwrap();
493 assert_eq!(reported_process_koids.len(), 1);
494 let reported_process_koid = reported_process_koids.remove(0);
495 let reported_process_handle =
496 reported_job_handle.get_child(&reported_process_koid, zx::Rights::SAME_RIGHTS).unwrap();
497 assert_eq!(reported_process_handle.koid(), created_process.koid());
498 }
499}