Skip to main content

shadow_rs/utility/
mod.rs

1//! Miscellaneous utilities that are used by Shadow.
2
3// defines macros, so must be included first
4#[macro_use]
5pub mod enum_passthrough;
6#[macro_use]
7pub mod macros;
8
9pub mod byte_queue;
10pub mod callback_queue;
11pub mod childpid_watcher;
12pub mod counter;
13pub mod give;
14pub mod legacy_callback_queue;
15pub mod once_set;
16pub mod pcap_writer;
17pub mod perf_timer;
18pub mod proc_maps;
19pub mod shm_cleanup;
20pub mod sockaddr;
21pub mod status_bar;
22pub mod stream_len;
23pub mod syscall;
24pub mod units;
25
26use std::collections::HashSet;
27use std::ffi::{CString, OsStr};
28use std::io::Read;
29use std::marker::PhantomData;
30use std::os::unix::fs::{DirBuilderExt, MetadataExt};
31use std::os::unix::prelude::OsStrExt;
32use std::path::{Path, PathBuf};
33use std::sync::RwLock;
34
35use once_cell::sync::Lazy;
36use shadow_shim_helper_rs::HostId;
37
38use crate::core::worker::Worker;
39use crate::host::host::Host;
40
41/// A pointer to an object that is safe to dereference from any thread,
42/// *if* the Host lock for the specified host is held.
43#[derive(Debug)]
44pub struct HostTreePointer<T> {
45    host_id: HostId,
46    ptr: *mut T,
47}
48
49// We can't `derive` Copy and Clone without unnecessarily requiring
50// T to be Copy and Clone. https://github.com/rust-lang/rust/issues/26925
51impl<T> Copy for HostTreePointer<T> {}
52impl<T> Clone for HostTreePointer<T> {
53    fn clone(&self) -> Self {
54        *self
55    }
56}
57
58unsafe impl<T> Send for HostTreePointer<T> {}
59unsafe impl<T> Sync for HostTreePointer<T> {}
60
61impl<T> HostTreePointer<T> {
62    /// Create a pointer that may only be accessed when the host with id
63    /// `host_id` is active.
64    pub fn new_for_host(host_id: HostId, ptr: *mut T) -> Self {
65        Self { host_id, ptr }
66    }
67
68    /// Create a pointer that may only be accessed when the current host is
69    /// active.
70    pub fn new(ptr: *mut T) -> Self {
71        let host_id = Worker::with_active_host(|h| h.info().id);
72        Self::new_for_host(host_id.unwrap(), ptr)
73    }
74
75    /// Get the pointer.
76    ///
77    /// Panics if the configured host is not active.
78    ///
79    /// # Safety
80    ///
81    /// Pointer must only be dereferenced while the configures Host is
82    /// still active, in addition to the normal safety requirements for
83    /// dereferencing a pointer.
84    pub unsafe fn ptr(&self) -> *mut T {
85        // While a caller might conceivably get the pointer without the lock
86        // held but only dereference after it actually is held, better to be
87        // conservative here and try to catch mistakes.
88        //
89        // This function is still `unsafe` since it's now the caller's
90        // responsibility to not release the lock and *then* dereference the
91        // pointer.
92        // SAFETY: caller's responsibility
93        Worker::with_active_host(|h| unsafe { self.ptr_with_host(h) }).unwrap()
94    }
95
96    /// Get the pointer.
97    ///
98    /// Panics if `host` is not the one associated with `self`.
99    ///
100    /// # Safety
101    ///
102    /// Pointer must only be dereferenced while the configures Host is still
103    /// active, in addition to the normal safety requirements for dereferencing
104    /// a pointer.
105    pub unsafe fn ptr_with_host(&self, host: &Host) -> *mut T {
106        assert_eq!(self.host_id, host.info().id);
107        self.ptr
108    }
109
110    /// Get the pointer without checking the active host.
111    ///
112    /// # Safety
113    ///
114    /// Pointer must only be dereferenced while the configures Host is still
115    /// active, in addition to the normal safety requirements for dereferencing
116    /// a pointer.
117    pub unsafe fn ptr_unchecked(&self) -> *mut T {
118        self.ptr
119    }
120}
121
122/// A trait we can use as a compile-time check to make sure that an object is Send.
123pub trait IsSend: Send {}
124
125/// A trait we can use as a compile-time check to make sure that an object is Sync.
126pub trait IsSync: Sync {}
127
128/// Runtime memory error checking to help catch errors that C code is prone
129/// to. Can probably drop once C interop is removed.
130///
131/// Prefer to place `Magic` struct fields as the *first* field.  This causes the
132/// `Magic` field to be dropped first when dropping the enclosing struct, which
133/// validates that the `Magic` is valid before running `Drop` implementations of
134/// the other fields.
135///
136/// T should be the type of the struct that contains the Magic.
137#[derive(Debug)]
138pub struct Magic<T: 'static> {
139    #[cfg(debug_assertions)]
140    magic: std::any::TypeId,
141    // The PhantomData docs recommend using `* const T` here to avoid a drop
142    // check, but that would incorrectly make this type !Send and !Sync. As long
143    // as the drop check doesn't cause issue (i.e. cause the borrow checker to
144    // fail), it should be fine to just use T here.
145    // https://doc.rust-lang.org/nomicon/dropck.html
146    _phantom: PhantomData<T>,
147}
148
149impl<T> Magic<T> {
150    pub fn new() -> Self {
151        Self {
152            #[cfg(debug_assertions)]
153            magic: std::any::TypeId::of::<T>(),
154            _phantom: PhantomData,
155        }
156    }
157
158    pub fn debug_check(&self) {
159        #[cfg(debug_assertions)]
160        {
161            if unsafe { std::ptr::read_volatile(&self.magic) } != std::any::TypeId::of::<T>() {
162                // Do not pass Go; do not collect $200... and do not run Drop
163                // implementations etc. after learning that Rust's soundness
164                // requirements have likely been violated.
165                std::process::abort();
166            }
167            // Ensure no other operations are performed on the object before validating.
168            std::sync::atomic::compiler_fence(std::sync::atomic::Ordering::SeqCst);
169        }
170    }
171}
172
173impl<T> Default for Magic<T> {
174    fn default() -> Self {
175        Self::new()
176    }
177}
178
179impl<T> Drop for Magic<T> {
180    fn drop(&mut self) {
181        self.debug_check();
182        #[cfg(debug_assertions)]
183        unsafe {
184            std::ptr::write_volatile(&mut self.magic, std::any::TypeId::of::<()>())
185        };
186    }
187}
188
189impl<T> Clone for Magic<T> {
190    fn clone(&self) -> Self {
191        self.debug_check();
192        Self::new()
193    }
194}
195
196/// Helper for tracking the number of allocated objects.
197#[derive(Debug)]
198pub struct ObjectCounter {
199    name: &'static str,
200}
201
202impl ObjectCounter {
203    pub fn new(name: &'static str) -> Self {
204        Worker::increment_object_alloc_counter(name);
205        Self { name }
206    }
207}
208
209impl Drop for ObjectCounter {
210    fn drop(&mut self) {
211        Worker::increment_object_dealloc_counter(self.name);
212    }
213}
214
215impl Clone for ObjectCounter {
216    fn clone(&self) -> Self {
217        Worker::increment_object_alloc_counter(self.name);
218        Self { name: self.name }
219    }
220}
221
222pub fn tilde_expansion(path: &str) -> std::path::PathBuf {
223    // if the path begins with a "~"
224    if let Some(x) = path.strip_prefix('~') {
225        // get the tilde-prefix (everything before the first separator)
226        let (tilde_prefix, remainder) = x.split_once('/').unwrap_or((x, ""));
227
228        if tilde_prefix.is_empty() {
229            if let Ok(ref home) = std::env::var("HOME") {
230                return [home, remainder].iter().collect::<std::path::PathBuf>();
231            }
232        } else if ['+', '-'].contains(&tilde_prefix.chars().next().unwrap()) {
233            // not supported
234        } else {
235            return ["/home", tilde_prefix, remainder]
236                .iter()
237                .collect::<std::path::PathBuf>();
238        }
239    }
240
241    // if we don't have a tilde-prefix that we support, just return the unmodified path
242    std::path::PathBuf::from(path)
243}
244
245/// Copy the contents of the `src` directory to a new directory named `dst`. Permissions will be
246/// preserved.
247pub fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> std::io::Result<()> {
248    // a directory to copy
249    struct DirCopyTask {
250        src: PathBuf,
251        dst: PathBuf,
252        mode: u32,
253    }
254
255    // a stack of directories to copy
256    let mut stack: Vec<DirCopyTask> = vec![];
257
258    stack.push(DirCopyTask {
259        src: src.as_ref().to_path_buf(),
260        dst: dst.as_ref().to_path_buf(),
261        mode: src.as_ref().metadata()?.mode(),
262    });
263
264    while let Some(DirCopyTask { src, dst, mode }) = stack.pop() {
265        // create the directory with the same permissions
266        create_dir_with_mode(&dst, mode)?;
267
268        // copy directory contents
269        for entry in std::fs::read_dir(src)? {
270            let entry = entry?;
271            let meta = entry.metadata()?;
272            let new_dst_path = dst.join(entry.file_name());
273
274            if meta.is_dir() {
275                stack.push(DirCopyTask {
276                    src: entry.path(),
277                    dst: new_dst_path,
278                    mode: meta.mode(),
279                });
280            } else {
281                // copy() will also copy the permissions
282                std::fs::copy(entry.path(), &new_dst_path)?;
283            }
284        }
285    }
286
287    Ok(())
288}
289
290fn create_dir_with_mode(path: impl AsRef<Path>, mode: u32) -> std::io::Result<()> {
291    let mut dir_builder = std::fs::DirBuilder::new();
292    dir_builder.mode(mode);
293    dir_builder.create(&path)
294}
295
296/// Helper for converting a PathBuf to a CString
297pub fn pathbuf_to_nul_term_cstring(buf: PathBuf) -> CString {
298    let mut bytes = buf.as_os_str().to_os_string().as_bytes().to_vec();
299    bytes.push(0);
300    CString::from_vec_with_nul(bytes).unwrap()
301}
302
303/// Get the return code for a process that exited by the given signal, following the behaviour of
304/// bash.
305pub fn return_code_for_signal(signal: nix::sys::signal::Signal) -> i32 {
306    // bash adds 128 to to the signal
307    (signal as i32).checked_add(128).unwrap()
308}
309
310/// Convert a `&[u8]` to `&[i8]`. Useful when interacting with C strings. Panics if
311/// `i8::try_from(c)` fails for any `c` in the slice.
312pub fn u8_to_i8_slice(s: &[u8]) -> &[i8] {
313    // assume that if try_from() was successful, then a direct cast would also be
314    assert!(s.iter().all(|x| i8::try_from(*x).is_ok()));
315    unsafe { std::slice::from_raw_parts(s.as_ptr() as *const i8, s.len()) }
316}
317
318/// Convert a `&[i8]` to `&[u8]`. Useful when interacting with C strings. Panics if
319/// `u8::try_from(c)` fails for any `c` in the slice.
320pub fn i8_to_u8_slice(s: &[i8]) -> &[u8] {
321    // assume that if try_from() was successful, then a direct cast would also be
322    assert!(s.iter().all(|x| u8::try_from(*x).is_ok()));
323    unsafe { std::slice::from_raw_parts(s.as_ptr() as *const u8, s.len()) }
324}
325
326/// Returns `true` if [`eq_ignore_ascii_case`](u8::eq_ignore_ascii_case) returns `true` on all `u8`
327/// ascii pairs. Should only be used for ascii byte strings.
328pub fn case_insensitive_eq(a: &[u8], b: &[u8]) -> bool {
329    if a.len() != b.len() {
330        return false;
331    }
332
333    a.iter().zip(b).all(|(x, y)| x.eq_ignore_ascii_case(y))
334}
335
336#[derive(Debug)]
337pub enum VerifyPluginPathError {
338    NotFound,
339    // Not a file.
340    NotFile,
341    // File isn't executable.
342    NotExecutable,
343    // File appears to be an ELF, but an incompatible one. e.g. not dynamically
344    // linked.
345    NotDynamicallyLinkedElf,
346    // File appears to be a script with a "shebang" line, but doesn't specify a
347    // compatible intepreter.
348    IncompatibleInterpreter(Box<VerifyPluginPathError>),
349    // Not an ELF nor a script.
350    UnknownFileType,
351    // Permission denied traversing the path.
352    PathPermissionDenied,
353    UnhandledIoError(std::io::Error),
354}
355impl std::error::Error for VerifyPluginPathError {}
356
357impl From<std::io::Error> for VerifyPluginPathError {
358    fn from(value: std::io::Error) -> Self {
359        match value.kind() {
360            std::io::ErrorKind::NotFound => VerifyPluginPathError::NotFound,
361            std::io::ErrorKind::PermissionDenied => VerifyPluginPathError::PathPermissionDenied,
362            // TODO handle TooManyLinks when stabilized
363            // TODO handle InvalidFileName when stabilized
364            _ => {
365                log::warn!("Unhandled IO error");
366                VerifyPluginPathError::UnhandledIoError(value)
367            }
368        }
369    }
370}
371
372impl std::fmt::Display for VerifyPluginPathError {
373    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
374        match self {
375            VerifyPluginPathError::NotFound => f.write_str("path not found"),
376            VerifyPluginPathError::NotFile => f.write_str("not a file"),
377            VerifyPluginPathError::NotExecutable => f.write_str("not executable"),
378            VerifyPluginPathError::NotDynamicallyLinkedElf => {
379                f.write_str("not a dynamically linked ELF")
380            }
381            VerifyPluginPathError::PathPermissionDenied => {
382                f.write_str("permission denied traversing path")
383            }
384            VerifyPluginPathError::UnhandledIoError(e) => write!(f, "unhandled io error: {e}"),
385            VerifyPluginPathError::IncompatibleInterpreter(e) => {
386                write!(f, "script with incompatible interpreter: {e}")
387            }
388            VerifyPluginPathError::UnknownFileType => f.write_str("Uncrecognized file type"),
389        }
390    }
391}
392
393/// Check that the plugin path is executable under Shadow.
394fn verify_plugin_path_internal(
395    path: impl AsRef<std::path::Path> + std::fmt::Debug,
396) -> Result<(), VerifyPluginPathError> {
397    let file = std::fs::File::open(&path)?;
398    let metadata = file.metadata()?;
399    if !metadata.is_file() {
400        return Err(VerifyPluginPathError::NotFile);
401    }
402    // this mask doesn't guarantee that we can execute the file (the file might have S_IXUSR
403    // but be owned by a different user), but it should catch most errors
404    let mask = libc::S_IXUSR | libc::S_IXGRP | libc::S_IXOTH;
405    if (metadata.mode() & mask) == 0 {
406        log::debug!("{path:?} not executable");
407        return Err(VerifyPluginPathError::NotExecutable);
408    }
409    // Get up to PATH_MAX bytes; that should be enough to ensure we get the
410    // interpreter name where applicable.
411    let mut buf = Vec::with_capacity(linux_api::limits::PATH_MAX);
412    file.take(linux_api::limits::PATH_MAX.try_into().unwrap())
413        .read_to_end(&mut buf)?;
414
415    if buf.starts_with(b"\x7fELF") {
416        // Looks like an ELF file.
417        if is_dynamic_bin(&path) {
418            Ok(())
419        } else {
420            log::debug!("{path:?} is ELF, but not dynamically linked");
421            Err(VerifyPluginPathError::NotDynamicallyLinkedElf)
422        }
423    } else if let Some(interp) = get_interpreter(&buf) {
424        // Looks like a script.
425        // Linux allows recursion here. It has a depth limit of 4, but trying to
426        // check and precisely match that doesn't seem worthwhile.
427        log::debug!("{path:?} has interpreter {interp:?}; checking");
428        verify_plugin_path(interp)
429            .map_err(|e| VerifyPluginPathError::IncompatibleInterpreter(Box::new(e)))
430    } else {
431        Err(VerifyPluginPathError::UnknownFileType)
432    }
433}
434
435/// Check that the plugin path is executable under Shadow.
436// Memoization wrapper around `verify_plugin_path_internal`
437// TODO: maybe move this cache into `sim_config.rs`? This seems slightly more
438// possible to go stale for paths exec'd by managed code.
439pub fn verify_plugin_path(path: impl AsRef<std::path::Path>) -> Result<(), VerifyPluginPathError> {
440    let path = path.as_ref();
441
442    // a cache so we don't check the same path multiple times (assuming the user doesn't move any
443    // binaries while shadow is running)
444    static CHECKED_BINS: Lazy<RwLock<HashSet<PathBuf>>> = Lazy::new(|| RwLock::new(HashSet::new()));
445
446    if CHECKED_BINS.read().unwrap().contains(path) {
447        return Ok(());
448    }
449
450    let res = verify_plugin_path_internal(path);
451    if res.is_ok() {
452        CHECKED_BINS.write().unwrap().insert(path.to_path_buf());
453    }
454    res
455}
456
457fn get_interpreter(header: &[u8]) -> Option<&Path> {
458    // Verify and strip "shebang"
459    let mut header = header.strip_prefix(b"#!")?;
460    // Skip any spaces. (Other whitespace isn't skipped AFAIK).
461    while header.first() == Some(&b' ') {
462        header = &header[1..];
463    }
464    // The path is the next contiguous set of non-space or newline characters.
465    let interp_path = header.split(|b| b == &b' ' || b == &b'\n').next()?;
466    let p = OsStr::from_bytes(interp_path);
467    Some(Path::new(p))
468}
469
470fn is_dynamic_bin(path: impl AsRef<std::path::Path>) -> bool {
471    let path = path.as_ref();
472
473    // check if the binary is dynamically linked
474    let ld_path = "/lib64/ld-linux-x86-64.so.2";
475    let ld_output = std::process::Command::new(ld_path)
476        .arg("--verify")
477        .arg(path)
478        .output()
479        .expect("Unable to run '{ld_path}'");
480
481    if ld_output.status.success() {
482        true
483    } else {
484        log::debug!("ld stderr: {:?}", ld_output.stderr);
485        // technically ld-linux could return errors for other reasons, but this is the most
486        // likely reason given that we already checked that the file exists
487        false
488    }
489}
490
491/// Inject `injected_preloads` into the environment `envv`.
492///
493/// * Ordering of `envv` is preserved.
494/// * Ordering of preloads already in `envv` is preserved.
495/// * Addition of duplicate entries from `injected_preloads` is suppressed (to avoid
496///   unbounded growth of env through chain of execve's)
497pub fn inject_preloads(mut envv: Vec<CString>, injected_preloads: &[PathBuf]) -> Vec<CString> {
498    let ld_preload_key = CString::new("LD_PRELOAD=").unwrap();
499
500    let ld_preload_kv;
501    if let Some(kv) = envv
502        .iter_mut()
503        .find(|v| v.to_bytes().starts_with(ld_preload_key.as_bytes()))
504    {
505        // We found an existing LD_PRELOAD definition, so we'll mutate it.  In
506        // the (unusual) case that LD_PRELOAD is defined multiple times, the
507        // first is the one that will be effective; we mutate that one and
508        // ignore the others.
509        ld_preload_kv = kv;
510    } else {
511        // No existing LD_PRELOAD definition; add an empty one.
512        envv.push(ld_preload_key.clone());
513        ld_preload_kv = envv.last_mut().unwrap();
514    }
515
516    let previous_preloads_string = ld_preload_kv
517        .as_bytes()
518        .strip_prefix(ld_preload_key.as_bytes())
519        .unwrap();
520
521    let injected_preloads_bytes = injected_preloads
522        .iter()
523        .map(|path| path.as_os_str().as_bytes());
524
525    for p in injected_preloads_bytes.clone() {
526        // Should have been caught earlier in configuraton parsing,
527        // but verify here at point of use.
528        assert!(
529            !p.iter().any(|c| *c == b' ' || *c == b':'),
530            "Preload path contains LD_PRELOAD separator"
531        );
532    }
533
534    // `ld.so(8)`: The items of the list can be separated by spaces or colons,
535    // and there is no support for escaping either separator.
536    let previous_preloads = previous_preloads_string.split(|c| *c == b':' || *c == b' ');
537
538    // Deduplicate. e.g. in the case where one managed process exec's another
539    // and passes in its own environment to the child, we don't want to add
540    // duplicates here.
541    let filtered_previous_preloads =
542        previous_preloads.filter(|p| !injected_preloads_bytes.clone().any(|q| &q == p));
543
544    let injected_preloads_bytes = injected_preloads
545        .iter()
546        .map(|path| path.as_os_str().as_bytes());
547
548    let mut preloads = injected_preloads_bytes.chain(filtered_previous_preloads);
549
550    // Some way to use `join` here? I couldn't work out a nice way.
551    let mut output = Vec::<u8>::new();
552    output.extend(ld_preload_key.as_bytes());
553    // Insert first entry without a separator
554    if let Some(p) = preloads.next() {
555        output.extend(p);
556    }
557    // Add the rest with separators
558    for preload in preloads {
559        output.push(b':');
560        output.extend(preload);
561    }
562
563    // We could probably safely use an unchecked CString constructor here, but
564    // probably not worth the risk of a subtle bug.
565    *ld_preload_kv = CString::new(output).unwrap();
566
567    envv
568}
569
570/// If debug assertions are enabled, panics if `FD_CLOEXEC` is not set on `file`.
571///
572/// In shadow we want `FD_CLOEXEC` set on most files that we create, to avoid them leaking
573/// into subprocesses that we spawn. Rust's file APIs typically set this in practice,
574/// but don't formally guarantee it. It's unlikely that they'd ever not set it, but we'd
575/// like to know if that happens.
576///
577/// The likely result of it not being set is just file descriptors leaking into
578/// subprocesses.  This counts against kernel limits against the total number
579/// of file descriptors, and may cause the underlying file description to remain
580/// open longer than needed. Theoretically the subprocess could also operate on
581/// the leaked descriptor, causing difficult-to-diagnose issues, but this is
582/// unlikely in practice, especially since shadow's shim should prevent any
583/// native file operations from being executed from managed code in the first place.
584pub fn debug_assert_cloexec(file: &(impl std::os::fd::AsRawFd + std::fmt::Debug)) {
585    #[cfg(debug_assertions)]
586    {
587        let flags = nix::fcntl::fcntl(file.as_raw_fd(), nix::fcntl::FcntlArg::F_GETFD).unwrap();
588        let flags = nix::fcntl::FdFlag::from_bits_retain(flags);
589        debug_assert!(
590            flags.contains(nix::fcntl::FdFlag::FD_CLOEXEC),
591            "{file:?} is unexpectedly not FD_CLOEXEC, which may lead to resource leaks or strange behavior"
592        );
593    }
594    #[cfg(not(debug_assertions))]
595    {
596        // Silence unused variable warning
597        let _ = file;
598    }
599}
600
601#[cfg(test)]
602mod tests {
603    use super::*;
604
605    #[test]
606    fn test_tilde_expansion() {
607        if let Ok(ref home) = std::env::var("HOME") {
608            assert_eq!(
609                tilde_expansion("~/test"),
610                [home, "test"].iter().collect::<std::path::PathBuf>()
611            );
612
613            assert_eq!(
614                tilde_expansion("~"),
615                [home].iter().collect::<std::path::PathBuf>()
616            );
617
618            assert_eq!(
619                tilde_expansion("~/"),
620                [home].iter().collect::<std::path::PathBuf>()
621            );
622
623            assert_eq!(
624                tilde_expansion("~someuser/test"),
625                ["/home", "someuser", "test"]
626                    .iter()
627                    .collect::<std::path::PathBuf>()
628            );
629
630            assert_eq!(
631                tilde_expansion("/~/test"),
632                ["/", "~", "test"].iter().collect::<std::path::PathBuf>()
633            );
634
635            assert_eq!(
636                tilde_expansion(""),
637                [""].iter().collect::<std::path::PathBuf>()
638            );
639        }
640    }
641
642    #[test]
643    fn test_inject_preloads() {
644        // Base case
645        assert_eq!(
646            inject_preloads(vec![], &[]),
647            vec![CString::new("LD_PRELOAD=").unwrap()]
648        );
649
650        // Other env vars are preserved
651        assert_eq!(
652            inject_preloads(
653                vec![
654                    CString::new("foo=foo").unwrap(),
655                    CString::new("bar=bar").unwrap(),
656                ],
657                &[]
658            ),
659            vec![
660                CString::new("foo=foo").unwrap(),
661                CString::new("bar=bar").unwrap(),
662                CString::new("LD_PRELOAD=").unwrap()
663            ]
664        );
665
666        // Prefixes existing preloads
667        assert_eq!(
668            inject_preloads(
669                vec![CString::new("LD_PRELOAD=/existing.so").unwrap()],
670                &[PathBuf::from("/injected.so")]
671            ),
672            vec![CString::new("LD_PRELOAD=/injected.so:/existing.so").unwrap()]
673        );
674
675        // Doesn't duplicate
676        assert_eq!(
677            inject_preloads(
678                vec![CString::new("LD_PRELOAD=/injected.so").unwrap()],
679                &[PathBuf::from("/injected.so")]
680            ),
681            &[CString::new("LD_PRELOAD=/injected.so").unwrap()]
682        );
683
684        // Multiple existing, multiple injected, partial dedupe
685        assert_eq!(
686            inject_preloads(
687                vec![
688                    CString::new("foo=foo").unwrap(),
689                    CString::new("LD_PRELOAD=/existing1.so:/injected1.so:/existing2.so").unwrap(),
690                    CString::new("bar=bar").unwrap()
691                ],
692                &[
693                    PathBuf::from("/injected1.so"),
694                    PathBuf::from("/injected2.so"),
695                ],
696            ),
697            &[
698                CString::new("foo=foo").unwrap(),
699                CString::new("LD_PRELOAD=/injected1.so:/injected2.so:/existing1.so:/existing2.so")
700                    .unwrap(),
701                CString::new("bar=bar").unwrap(),
702            ]
703        );
704    }
705}
706
707mod export {
708    use std::io::IsTerminal;
709
710    #[unsafe(no_mangle)]
711    pub unsafe extern "C-unwind" fn utility_handleErrorInner(
712        file_name: *const libc::c_char,
713        line: libc::c_int,
714        fn_name: *const libc::c_char,
715        format: *const libc::c_char,
716        va_list: *mut libc::c_void,
717    ) -> ! {
718        use std::ffi::CStr;
719        let file_name = unsafe { CStr::from_ptr(file_name) };
720        let file_name = file_name.to_bytes().escape_ascii();
721
722        let fn_name = unsafe { CStr::from_ptr(fn_name) };
723        let fn_name = fn_name.to_bytes().escape_ascii();
724
725        log::logger().flush();
726
727        let indent = "    ";
728
729        // add four spaces at the start of every line
730        let backtrace = format!("{:?}", backtrace::Backtrace::new());
731        let backtrace = backtrace
732            .trim_end()
733            .split('\n')
734            .map(|x| format!("{indent}{x}"))
735            .collect::<Vec<String>>()
736            .join("\n");
737
738        let pid = nix::unistd::getpid();
739        let ppid = nix::unistd::getppid();
740
741        let error_msg = unsafe { vsprintf::vsprintf_raw(format, va_list).unwrap() };
742        let error_msg = error_msg.escape_ascii();
743
744        let error_msg = format!(
745            "**ERROR ENCOUNTERED**\n\
746              {indent}At process: {pid} (parent {ppid})\n\
747              {indent}At file: {file_name}\n\
748              {indent}At line: {line}\n\
749              {indent}At function: {fn_name}\n\
750              {indent}Message: {error_msg}\n\
751            **BEGIN BACKTRACE**\n\
752            {backtrace}\n\
753            **END BACKTRACE**\n\
754            **ABORTING**"
755        );
756
757        eprintln!("{error_msg}");
758
759        // If stderr is a terminal, and stdout isn't, also print to stdout.
760        // This helps ensure the error is preserved in the case that stdout
761        // is recorded to a file but stderr is not.
762        if std::io::stderr().lock().is_terminal() && !std::io::stdout().lock().is_terminal() {
763            println!("{error_msg}");
764        }
765
766        std::process::abort()
767    }
768}