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