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
27pub 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
47pub 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 pub fn new() -> ProcConfig {
137 ProcConfig::default()
138 }
139
140 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 pub fn pass_args(&mut self, enabled: bool) -> &mut Self {
150 self.pass_args = enabled;
151 self
152 }
153
154 pub fn panic_handling(&mut self, enabled: bool) -> &mut Self {
159 self.panic_handling = enabled;
160 self
161 }
162
163 #[cfg(feature = "backtrace")]
170 pub fn capture_backtraces(&mut self, enabled: bool) -> &mut Self {
171 self.capture_backtraces = enabled;
172 self
173 }
174
175 #[cfg(feature = "backtrace")]
177 pub fn resolve_backtraces(&mut self, enabled: bool) -> &mut Self {
178 self.resolve_backtraces = enabled;
179 self
180 }
181
182 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 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
213pub 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 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#[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 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 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 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 }
351 } else {
352 panic!("could not send event over ipc channel: {:?}", err);
353 }
354 }
355}