1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

use fuchsia_runtime as runtime;
use fuchsia_zircon as zx;
use lazy_static::lazy_static;
use std::borrow::Borrow;
use std::ffi::CStr;
use std::ops::Deref;
use std::panic;
use tracing::error;

lazy_static! {
    static ref SCOPED_JOB: Scoped<zx::Job> = initialize();
}

fn initialize() -> Scoped<zx::Job> {
    let job = runtime::job_default().create_child_job().expect("couldn't create job");

    unsafe {
        libc::atexit(exit_hook);
    }
    install_panic_hook();
    // We cannot panic past this point.

    Scoped::new(job)
}

#[cfg(panic = "abort")]
fn install_panic_hook() {
    let old_hook = panic::take_hook();
    let new_hook = Box::new(move |panic_info: &panic::PanicInfo<'_>| {
        exit_hook();
        old_hook(panic_info);
    });
    panic::set_hook(new_hook);
}

#[cfg(panic = "unwind")]
fn install_panic_hook() {
    // When panic=unwind we can rely on the destructor of the individual
    // processes.
}

extern "C" fn exit_hook() {
    kill(SCOPED_JOB.0.as_ref().unwrap(), "scoped job root");
}

/// Installs the hooks that ensure scoped tasks are cleaned up on panic and
/// process exit.
///
/// Programs and tests that are multithreaded should call this before starting
/// any threads. Single-threaded programs do not have to call this, and can rely
/// on hooks being installed lazily.
///
/// This function can be called multiple times. It will not have any effect
/// after the first call.
pub fn install_hooks() {
    let _ = &*SCOPED_JOB;
}

/// Returns the default job for spawning scoped processes.
///
/// This job is a child of the job returned by [`fuchsia_runtime::job_default`].
pub fn job_default() -> &'static Scoped<zx::Job> {
    &*SCOPED_JOB
}

/// Creates and returns a child job of the job returned by [`job_default`].
pub fn create_child_job() -> Result<Scoped<zx::Job>, zx::Status> {
    Ok(Scoped::new(SCOPED_JOB.create_child_job()?))
}

/// A convenience wrapper around [`fdio::spawn_etc`] that returns a
/// [`Scoped`].
///
/// Note that you must assign the return value to a local. You can use a leading
/// underscore in the name to avoid the unused variable lint, but don't use just
/// `_` or the process will be killed immediately. For example,
///
/// ```
/// let _process = scoped_task::spawn_etc(
///     &scoped_task::job_default(),
///     SpawnOptions::CLONE_ALL,
///     c"/pkg/bin/echo",
///     &[c"hello world"],
///     None,
///     &mut [],
/// ).expect("could not spawn process");
/// ```
pub fn spawn_etc<'a>(
    job: impl Borrow<&'a Scoped<zx::Job>>,
    options: fdio::SpawnOptions,
    path: &CStr,
    argv: &[&CStr],
    environ: Option<&[&CStr]>,
    actions: &mut [fdio::SpawnAction<'_>],
) -> Result<Scoped<zx::Process>, (zx::Status, String)> {
    fdio::spawn_etc(job.borrow().0.as_ref().unwrap(), options, path, argv, environ, actions)
        .map(Scoped::new)
}

/// A convenience wrapper around [`fdio::spawn`] that returns a
/// [`Scoped`].
///
/// Note that you must assign the return value to a local. You can use a leading
/// underscore in the name to avoid the unused variable lint, but don't use just
/// `_` or the process will be killed immediately. For example,
///
/// ```
/// let _process = scoped_task::spawn(
///     &scoped_task::job_default(),
///     SpawnOptions::CLONE_ALL,
///     c"/pkg/bin/echo",
///     &[c"hello world"],
/// ).expect("could not spawn process");
/// ```
pub fn spawn<'a>(
    job: impl Borrow<&'a Scoped<zx::Job>>,
    options: fdio::SpawnOptions,
    path: &CStr,
    argv: &[&CStr],
) -> Result<Scoped<zx::Process>, zx::Status> {
    fdio::spawn(job.borrow().0.as_ref().unwrap(), options, path, argv).map(Scoped::new)
}

/// Scoped wrapper for a process or job backed by a Zircon handle.
///
/// The process or job is killed when the wrapper goes out of scope or the
/// current process panics. See the module-level documentation for more details.
#[must_use]
pub struct Scoped<T: zx::Task = zx::Process>(Option<T>);

impl<T: zx::Task> Scoped<T> {
    fn new(process: T) -> Self {
        Scoped(Some(process))
    }

    /// Kills the process, consuming the Scoped wrapper and returning
    /// the inner task object.
    pub fn kill(mut self) -> Result<T, zx::Status> {
        let inner = self.0.take().unwrap();
        let result = inner.kill();
        result.map(|()| inner)
    }

    /// Consumes the Scoped wrapper and returns the inner task object. The task
    /// will no longer be killed on Drop, but may still be killed on exit.
    #[must_use]
    pub fn into_inner(mut self) -> T {
        self.0.take().unwrap()
    }
}

impl<T: zx::Task> Drop for Scoped<T> {
    fn drop(&mut self) {
        if let Some(process) = self.0.take() {
            kill(&process, "scoped task");
        }
    }
}

impl<T: zx::Task> Deref for Scoped<T> {
    type Target = T;
    fn deref(&self) -> &T {
        self.0.as_ref().unwrap()
    }
}

fn kill<T: zx::Task>(process: &T, what: &'static str) {
    let result: Result<(), zx::Status> = (|| {
        process.kill()?;
        process.wait_handle(zx::Signals::TASK_TERMINATED, zx::Time::INFINITE)?;
        Ok(())
    })();
    match result {
        Ok(()) => {}
        Err(status) => {
            eprintln!("error: could not kill {what}: {status}");
            error!(%status, "error: could not kill {what}");
        }
    }
}