procspawn/
core.rs

1use std::env;
2use std::ffi::{OsStr, OsString};
3use std::io;
4use std::mem;
5use std::panic;
6use std::process;
7use std::sync::atomic::{AtomicBool, Ordering};
8
9#[cfg(feature = "safe-shared-libraries")]
10use findshlibs::{Avma, IterationControl, Segment, SharedLibrary};
11
12use ipc_channel::ipc::{self, IpcReceiver, IpcSender, OpaqueIpcReceiver, OpaqueIpcSender};
13use ipc_channel::ErrorKind as IpcErrorKind;
14use serde::{Deserialize, Serialize};
15
16use crate::error::PanicInfo;
17use crate::panic::{init_panic_hook, reset_panic_info, take_panic, BacktraceCapture};
18use crate::serde::with_ipc_mode;
19
20pub const ENV_NAME: &str = "__PROCSPAWN_CONTENT_PROCESS_ID";
21static INITIALIZED: AtomicBool = AtomicBool::new(false);
22static PASS_ARGS: AtomicBool = AtomicBool::new(false);
23
24#[cfg(not(feature = "safe-shared-libraries"))]
25static ALLOW_UNSAFE_SPAWN: AtomicBool = AtomicBool::new(false);
26
27/// Asserts no shared libraries are used for functions spawned.
28///
29/// If the `safe-shared-libraries` feature is disabled this function must be
30/// called once to validate that the application does not spawn functions
31/// from a shared library.
32///
33/// This must be called once before the first call to a spawn function as
34/// otherwise they will panic.
35///
36/// # Safety
37///
38/// You must only call this function if you can guarantee that none of your
39/// `spawn` calls cross a shared library boundary.
40pub unsafe fn assert_spawn_is_safe() {
41    #[cfg(not(feature = "safe-shared-libraries"))]
42    {
43        ALLOW_UNSAFE_SPAWN.store(true, Ordering::SeqCst);
44    }
45}
46
47/// Can be used to configure the process.
48pub struct ProcConfig {
49    callback: Option<Box<dyn FnOnce()>>,
50    panic_handling: bool,
51    pass_args: bool,
52    #[cfg(feature = "backtrace")]
53    capture_backtraces: bool,
54    #[cfg(feature = "backtrace")]
55    resolve_backtraces: bool,
56}
57
58impl Default for ProcConfig {
59    fn default() -> ProcConfig {
60        ProcConfig {
61            callback: None,
62            panic_handling: true,
63            pass_args: true,
64            #[cfg(feature = "backtrace")]
65            capture_backtraces: true,
66            #[cfg(feature = "backtrace")]
67            resolve_backtraces: true,
68        }
69    }
70}
71
72pub fn mark_initialized() {
73    INITIALIZED.store(true, Ordering::SeqCst);
74}
75
76pub fn should_pass_args() -> bool {
77    PASS_ARGS.load(Ordering::SeqCst)
78}
79
80fn find_shared_library_offset_by_name(name: &OsStr) -> isize {
81    #[cfg(feature = "safe-shared-libraries")]
82    {
83        let mut result = None;
84        findshlibs::TargetSharedLibrary::each(|shlib| {
85            if shlib.name() == name {
86                result = Some(
87                    shlib
88                        .segments()
89                        .next()
90                        .map_or(0, |x| x.actual_virtual_memory_address(shlib).0 as isize),
91                );
92                return IterationControl::Break;
93            }
94            IterationControl::Continue
95        });
96        match result {
97            Some(rv) => rv,
98            None => panic!("Unable to locate shared library {:?} in subprocess", name),
99        }
100    }
101    #[cfg(not(feature = "safe-shared-libraries"))]
102    {
103        let _ = name;
104        init as *const () as isize
105    }
106}
107
108fn find_library_name_and_offset(f: *const u8) -> (OsString, isize) {
109    #[cfg(feature = "safe-shared-libraries")]
110    {
111        let mut result = None;
112        findshlibs::TargetSharedLibrary::each(|shlib| {
113            let start = shlib
114                .segments()
115                .next()
116                .map_or(0, |x| x.actual_virtual_memory_address(shlib).0 as isize);
117            for seg in shlib.segments() {
118                if seg.contains_avma(shlib, Avma(f as usize)) {
119                    result = Some((shlib.name().to_owned(), start));
120                    return IterationControl::Break;
121                }
122            }
123            IterationControl::Continue
124        });
125        result.expect("Unable to locate function pointer in loaded image")
126    }
127    #[cfg(not(feature = "safe-shared-libraries"))]
128    {
129        let _ = f;
130        (OsString::new(), init as *const () as isize)
131    }
132}
133
134impl ProcConfig {
135    /// Creates a default proc config.
136    pub fn new() -> ProcConfig {
137        ProcConfig::default()
138    }
139
140    /// Attaches a callback that is used to initializes all processes.
141    pub fn config_callback<F: FnOnce() + 'static>(&mut self, f: F) -> &mut Self {
142        self.callback = Some(Box::new(f));
143        self
144    }
145
146    /// Enables or disables argument passing.
147    ///
148    /// By default all arguments are forwarded to the spawned process.
149    pub fn pass_args(&mut self, enabled: bool) -> &mut Self {
150        self.pass_args = enabled;
151        self
152    }
153
154    /// Configure the automatic panic handling.
155    ///
156    /// The default behavior is that panics are caught and that a panic handler
157    /// is installed.
158    pub fn panic_handling(&mut self, enabled: bool) -> &mut Self {
159        self.panic_handling = enabled;
160        self
161    }
162
163    /// Configures if backtraces should be captured.
164    ///
165    /// The default behavior is that if panic handling is enabled backtraces
166    /// will be captured.
167    ///
168    /// This requires the `backtrace` feature.
169    #[cfg(feature = "backtrace")]
170    pub fn capture_backtraces(&mut self, enabled: bool) -> &mut Self {
171        self.capture_backtraces = enabled;
172        self
173    }
174
175    /// Controls whether backtraces should be resolved.
176    #[cfg(feature = "backtrace")]
177    pub fn resolve_backtraces(&mut self, enabled: bool) -> &mut Self {
178        self.resolve_backtraces = enabled;
179        self
180    }
181
182    /// Consumes the config and initializes the process.
183    pub fn init(&mut self) {
184        mark_initialized();
185        PASS_ARGS.store(self.pass_args, Ordering::SeqCst);
186
187        if let Ok(token) = env::var(ENV_NAME) {
188            // permit nested invocations
189            std::env::remove_var(ENV_NAME);
190            if let Some(callback) = self.callback.take() {
191                callback();
192            }
193            bootstrap_ipc(token, self);
194        }
195    }
196
197    fn backtrace_capture(&self) -> BacktraceCapture {
198        #[cfg(feature = "backtrace")]
199        {
200            match (self.capture_backtraces, self.resolve_backtraces) {
201                (false, _) => BacktraceCapture::No,
202                (true, true) => BacktraceCapture::Resolved,
203                (true, false) => BacktraceCapture::Unresolved,
204            }
205        }
206        #[cfg(not(feature = "backtrace"))]
207        {
208            BacktraceCapture::No
209        }
210    }
211}
212
213/// Initializes procspawn.
214///
215/// This function must be called at the beginning of `main`.  Whatever comes
216/// before it is also executed for all processes spawned through the `spawn`
217/// function.
218///
219/// For more complex initializations see [`ProcConfig`](struct.ProcConfig.html).
220pub fn init() {
221    ProcConfig::default().init()
222}
223
224#[inline]
225pub fn assert_spawn_okay() {
226    if !INITIALIZED.load(Ordering::SeqCst) {
227        panic!("procspawn was not initialized");
228    }
229    #[cfg(not(feature = "safe-shared-libraries"))]
230    {
231        if !ALLOW_UNSAFE_SPAWN.load(Ordering::SeqCst) {
232            panic!(
233                "spawn() prevented because safe-shared-library feature was \
234                 disabled and assert_no_shared_libraries was not invoked."
235            );
236        }
237    }
238}
239
240fn is_benign_bootstrap_error(err: &io::Error) -> bool {
241    // on macos we will observe an unknown mach error
242    err.kind() == io::ErrorKind::Other && err.to_string() == "Unknown Mach error: 44e"
243}
244
245fn bootstrap_ipc(token: String, config: &ProcConfig) {
246    if config.panic_handling {
247        init_panic_hook(config.backtrace_capture());
248    }
249
250    {
251        let connection_bootstrap: IpcSender<IpcSender<MarshalledCall>> =
252            match IpcSender::connect(token) {
253                Ok(sender) => sender,
254                Err(err) => {
255                    if !is_benign_bootstrap_error(&err) {
256                        panic!("could not bootstrap ipc connection: {:?}", err);
257                    }
258                    process::exit(1);
259                }
260            };
261        let (tx, rx) = ipc::channel().unwrap();
262        connection_bootstrap.send(tx).unwrap();
263        let marshalled_call = rx.recv().unwrap();
264        marshalled_call.call(config.panic_handling);
265    }
266    process::exit(0);
267}
268
269/// Marshals a call across process boundaries.
270#[derive(Serialize, Deserialize, Debug)]
271pub struct MarshalledCall {
272    pub lib_name: OsString,
273    pub fn_offset: isize,
274    pub wrapper_offset: isize,
275    pub args_receiver: OpaqueIpcReceiver,
276    pub return_sender: OpaqueIpcSender,
277}
278
279impl MarshalledCall {
280    /// Marshalls the call.
281    pub fn marshal<A, R>(
282        f: fn(A) -> R,
283        args_receiver: IpcReceiver<A>,
284        return_sender: IpcSender<Result<R, PanicInfo>>,
285    ) -> MarshalledCall
286    where
287        A: Serialize + for<'de> Deserialize<'de>,
288        R: Serialize + for<'de> Deserialize<'de>,
289    {
290        let (lib_name, offset) = find_library_name_and_offset(f as *const () as *const u8);
291        let init_loc = init as *const () as isize;
292        let fn_offset = f as *const () as isize - offset;
293        let wrapper_offset = run_func::<A, R> as *const () as isize - init_loc;
294        MarshalledCall {
295            lib_name,
296            fn_offset,
297            wrapper_offset,
298            args_receiver: args_receiver.to_opaque(),
299            return_sender: return_sender.to_opaque(),
300        }
301    }
302
303    /// Unmarshals and performs the call.
304    pub fn call(self, panic_handling: bool) {
305        unsafe {
306            let ptr = self.wrapper_offset + init as *const () as isize;
307            let func: fn(&OsStr, isize, OpaqueIpcReceiver, OpaqueIpcSender, bool) =
308                mem::transmute(ptr);
309            func(
310                &self.lib_name,
311                self.fn_offset,
312                self.args_receiver,
313                self.return_sender,
314                panic_handling,
315            );
316        }
317    }
318}
319
320unsafe fn run_func<A, R>(
321    lib_name: &OsStr,
322    fn_offset: isize,
323    args_recv: OpaqueIpcReceiver,
324    sender: OpaqueIpcSender,
325    panic_handling: bool,
326) where
327    A: Serialize + for<'de> Deserialize<'de>,
328    R: Serialize + for<'de> Deserialize<'de>,
329{
330    let lib_offset = find_shared_library_offset_by_name(lib_name);
331    let function: fn(A) -> R = mem::transmute(fn_offset + lib_offset as *const () as isize);
332    let args = with_ipc_mode(|| args_recv.to().recv().unwrap());
333    let rv = if panic_handling {
334        reset_panic_info();
335        match panic::catch_unwind(panic::AssertUnwindSafe(|| function(args))) {
336            Ok(rv) => Ok(rv),
337            Err(panic) => Err(take_panic(&*panic)),
338        }
339    } else {
340        Ok(function(args))
341    };
342
343    // sending can fail easily because of bincode limitations.  If you see
344    // this in your tracebacks consider using the `Json` wrapper.
345    if let Err(err) = with_ipc_mode(|| sender.to().send(rv)) {
346        if let IpcErrorKind::Io(ref io) = *err {
347            if io.kind() == io::ErrorKind::NotFound || io.kind() == io::ErrorKind::ConnectionReset {
348                // this error is okay.  this means nobody actually
349                // waited for the call, so we just ignore it.
350            }
351        } else {
352            panic!("could not send event over ipc channel: {:?}", err);
353        }
354    }
355}