Skip to main content

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    Koid, MonotonicDuration, NullableHandle, ObjectQuery, Process, ProcessOptions, Rights, Status,
9    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                    _padding: [0; 4],
157                };
158                let sys_count = 1;
159
160                ok(unsafe {
161                    // Requires that `self` contains a currently valid handle.
162                    // No handles or values are moved as a result of this call (regardless of
163                    // success), and the values used here are safely dropped when this function
164                    // returns.
165                    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    /// Wraps the [zx_job_set_critical](//docs/reference/syscalls/job_set_critical.md) syscall.
178    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    /// Wraps the
185    /// [zx_object_get_info](https://fuchsia.dev/fuchsia-src/reference/syscalls/object_get_info.md)
186    /// syscall for the ZX_INFO_JOB_PROCESSES topic.
187    pub fn processes(&self) -> Result<Vec<Koid>, Status> {
188        self.0.get_info_vec::<JobProcessesInfo>()
189    }
190
191    /// Wraps the
192    /// [zx_object_get_info](https://fuchsia.dev/fuchsia-src/reference/syscalls/object_get_info.md)
193    /// syscall for the ZX_INFO_JOB_CHILDREN topic.
194    pub fn children(&self) -> Result<Vec<Koid>, Status> {
195        self.0.get_info_vec::<JobChildrenInfo>()
196    }
197
198    /// Wraps the
199    /// [zx_object_get_child](https://fuchsia.dev/fuchsia-src/reference/syscalls/object_get_child.md)
200    /// syscall.
201    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/// Represents the [ZX_JOB_POL_RELATIVE and
217/// ZX_JOB_POL_ABSOLUTE](//docs/reference/syscalls/job_set_policy.md) constants
218#[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/// Holds a timer policy or a basic policy set for
234/// [zx_job_set_policy](//docs/reference/syscalls/job_set_policy.md)
235#[derive(Debug, Clone, PartialEq)]
236pub enum JobPolicy {
237    Basic(JobPolicyOption, Vec<(JobCondition, JobAction)>),
238    TimerSlack(MonotonicDuration, JobDefaultTimerMode),
239}
240
241/// Represents the [ZX_POL_*](//docs/reference/syscalls/job_set_policy.md) constants
242#[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/// Represents the [ZX_POL_ACTION_*](//docs/reference/syscalls/job_set_policy.md) constants
286#[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/// Represents the [ZX_TIMER_SLACK_*](//docs/reference/syscalls/job_set_policy.md) constants
308pub type JobDefaultTimerMode = crate::TimerSlackMode;
309
310impl Task for Job {}
311
312bitflags! {
313    /// Options that may be used by `Job::set_critical`.
314    #[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    // The unit tests are built with a different crate name, but fuchsia_runtime returns a "real"
324    // zx::Job that we need to use.
325    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            // Careful not to clone stdio here, or the test runner can hang.
406            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    // The vdso_next tests don't have permission to create raw processes.
438    #[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    // The vdso_next tests don't have permission to create raw processes.
473    #[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        // We can even create a process on the handle we got back, and test ProcessKoid
488        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}