zx/
job.rs

1// Copyright 2017 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Type-safe bindings for Zircon jobs.
6
7use 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/// An object representing a Zircon job.
14///
15/// As essentially a subtype of `NullableHandle`, it can be freely interconverted.
16#[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
42// JobInfo is able to be safely replaced with a byte representation and is a PoD type.
43struct 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    /// Create a new job as a child of the current job.
65    ///
66    /// Wraps the
67    /// [zx_job_create](https://fuchsia.dev/fuchsia-src/reference/syscalls/job_create.md)
68    /// syscall.
69    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    /// Create a new process as a child of the current job.
79    ///
80    /// On success, returns a handle to the new process and a handle to the
81    /// root of the new process's address space.
82    ///
83    /// Wraps the
84    /// [zx_process_create](https://fuchsia.dev/fuchsia-src/reference/syscalls/process_create.md)
85    /// syscall.
86    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    /// Wraps the
116    /// [zx_object_get_info](https://fuchsia.dev/fuchsia-src/reference/syscalls/object_get_info.md)
117    /// syscall for the ZX_INFO_JOB topic.
118    pub fn info(&self) -> Result<JobInfo, Status> {
119        Ok(JobInfo::from(self.0.get_info_single::<JobInfoQuery>()?))
120    }
121
122    /// Wraps the [zx_job_set_policy](//docs/reference/syscalls/job_set_policy.md) syscall.
123    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                    // No handles or values are moved as a result of this call (regardless of
139                    // success), and the values used here are safely dropped when this function
140                    // returns.
141                    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                    // Requires that `self` contains a currently valid handle.
161                    // No handles or values are moved as a result of this call (regardless of
162                    // success), and the values used here are safely dropped when this function
163                    // returns.
164                    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    /// Wraps the [zx_job_set_critical](//docs/reference/syscalls/job_set_critical.md) syscall.
177    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    /// Wraps the
184    /// [zx_object_get_info](https://fuchsia.dev/fuchsia-src/reference/syscalls/object_get_info.md)
185    /// syscall for the ZX_INFO_JOB_PROCESSES topic.
186    pub fn processes(&self) -> Result<Vec<Koid>, Status> {
187        self.0.get_info_vec::<JobProcessesInfo>()
188    }
189
190    /// Wraps the
191    /// [zx_object_get_info](https://fuchsia.dev/fuchsia-src/reference/syscalls/object_get_info.md)
192    /// syscall for the ZX_INFO_JOB_CHILDREN topic.
193    pub fn children(&self) -> Result<Vec<Koid>, Status> {
194        self.0.get_info_vec::<JobChildrenInfo>()
195    }
196
197    /// Wraps the
198    /// [zx_object_get_child](https://fuchsia.dev/fuchsia-src/reference/syscalls/object_get_child.md)
199    /// syscall.
200    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/// Represents the [ZX_JOB_POL_RELATIVE and
216/// ZX_JOB_POL_ABSOLUTE](//docs/reference/syscalls/job_set_policy.md) constants
217#[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/// Holds a timer policy or a basic policy set for
233/// [zx_job_set_policy](//docs/reference/syscalls/job_set_policy.md)
234#[derive(Debug, Clone, PartialEq)]
235pub enum JobPolicy {
236    Basic(JobPolicyOption, Vec<(JobCondition, JobAction)>),
237    TimerSlack(MonotonicDuration, JobDefaultTimerMode),
238}
239
240/// Represents the [ZX_POL_*](//docs/reference/syscalls/job_set_policy.md) constants
241#[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/// Represents the [ZX_POL_ACTION_*](//docs/reference/syscalls/job_set_policy.md) constants
285#[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/// Represents the [ZX_TIMER_SLACK_*](//docs/reference/syscalls/job_set_policy.md) constants
307#[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    /// Options that may be used by `Job::set_critical`.
328    #[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    // The unit tests are built with a different crate name, but fuchsia_runtime returns a "real"
338    // zx::Job that we need to use.
339    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            // Careful not to clone stdio here, or the test runner can hang.
420            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    // The vdso_next tests don't have permission to create raw processes.
452    #[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    // The vdso_next tests don't have permission to create raw processes.
487    #[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        // We can even create a process on the handle we got back, and test ProcessKoid
502        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}