#![allow(clippy::multiple_unsafe_ops_per_block)]
use std::ffi::{CStr, c_char, c_int};
use dlsym::{Error, Library, library_filename};
#[cfg(not(windows))]
fn libc() -> Library {
Library::this().unwrap()
}
#[cfg(windows)]
fn libc() -> Library {
unsafe { Library::open("msvcrt.dll") }.unwrap()
}
#[test]
fn variadic_call() {
let lib = libc();
let sprintf = unsafe {
lib.get::<unsafe extern "C" fn(*mut c_char, *const c_char, ...) -> c_int>(c"sprintf")
}
.unwrap();
let mut buf = [0 as c_char; 32];
let n = unsafe { sprintf.with(|f| f(buf.as_mut_ptr(), c"%d-%s".as_ptr(), 42, c"ok".as_ptr())) };
assert_eq!(n, 5);
assert_eq!(unsafe { CStr::from_ptr(buf.as_ptr()) }, c"42-ok");
}
#[cfg(all(target_os = "linux", target_env = "gnu"))]
#[test]
fn data_symbol() {
let lib = libc();
let name = unsafe { lib.get::<*mut *const c_char>(c"program_invocation_name") }.unwrap();
let name = unsafe { CStr::from_ptr(*name.as_ref()) };
assert!(!name.to_bytes().is_empty());
}
#[cfg(all(target_os = "linux", target_env = "gnu", target_arch = "x86_64"))]
#[test]
fn versioned_symbol() {
use dlsym::os::unix::LibraryExt;
let lib = libc();
let memcpy = unsafe { lib.get_versioned::<*const u8>(c"memcpy", c"GLIBC_2.14") }.unwrap();
assert!(!memcpy.as_ptr().is_null());
let err = unsafe { lib.get_versioned::<*const u8>(c"memcpy", c"DLSYM_9.9") }.unwrap_err();
assert!(matches!(err, Error::SymbolNotFound { .. }), "{err:?}");
}
#[cfg(target_os = "linux")]
#[test]
fn open_options() {
use dlsym::os::unix::OpenOptions;
let libm = unsafe { OpenOptions::new().lazy(true).global(true).open("libm.so.6") }.unwrap();
let again = unsafe { OpenOptions::new().no_load(true).open("libm.so.6") }.unwrap();
drop(again);
drop(libm);
let missing = unsafe { OpenOptions::new().no_load(true).open("libdlsym-missing.so") };
assert!(missing.is_err());
}
#[cfg(windows)]
#[test]
fn already_loaded() {
use dlsym::os::windows::LibraryExt;
let kernel32 = Library::open_already_loaded("kernel32.dll").unwrap();
let f = unsafe { kernel32.get::<*const u8>(c"GetLastError") }.unwrap();
assert!(!f.as_ptr().is_null());
assert!(Library::open_already_loaded("dlsym-definitely-missing.dll").is_err());
}
#[test]
fn open_first_returns_first_loadable() {
let candidates = [
"libdlsym-missing-1",
#[cfg(any(target_os = "linux", target_os = "fullrust"))]
"libm.so.6",
#[cfg(target_vendor = "apple")]
"/usr/lib/libm.dylib",
#[cfg(windows)]
"ucrtbase.dll",
#[cfg(target_os = "freebsd")]
"libm.so.5",
];
unsafe { Library::open_first(candidates) }.unwrap();
let err =
unsafe { Library::open_first(["libdlsym-missing-1", "libdlsym-missing-2"]) }.unwrap_err();
match err {
Error::Open { path, .. } => assert_eq!(path, "libdlsym-missing-2"),
e => panic!("{e:?}"),
}
assert!(unsafe { Library::open_first(Vec::<&str>::new()) }.is_err());
}
#[test]
fn leaked_symbols_are_static() {
let lib: &'static Library = libc().leak();
let strlen: dlsym::Symbol<'static, unsafe extern "C" fn(*const c_char) -> usize> =
unsafe { lib.get(c"strlen") }.unwrap();
assert_eq!(unsafe { strlen.call((c"four".as_ptr(),)) }, 4);
}
#[test]
fn filenames() {
let name = library_filename("z");
if cfg!(windows) {
assert_eq!(name, "z.dll");
} else if cfg!(target_vendor = "apple") {
assert_eq!(name, "libz.dylib");
} else {
assert_eq!(name, "libz.so");
}
}
#[cfg(not(target_os = "fullrust"))]
#[test]
fn paths_and_address_info() {
#[cfg(target_os = "linux")]
let name = "libm.so.6";
#[cfg(target_vendor = "apple")]
let name = "/usr/lib/libm.dylib";
#[cfg(windows)]
let name = "ucrtbase.dll";
#[cfg(target_os = "freebsd")]
let name = "libm.so.5";
#[cfg(not(any(
target_os = "linux",
target_os = "freebsd",
target_vendor = "apple",
windows
)))]
let name = "libm.so";
let lib = unsafe { Library::open(name) }.unwrap();
let cos = unsafe { lib.get::<*const u8>(c"cos") }.unwrap();
let info = cos.info().expect("address info");
let file = info
.library
.file_name()
.unwrap()
.to_string_lossy()
.to_lowercase();
assert!(
["libm", "ucrtbase", "libsystem", "musl"]
.iter()
.any(|n| file.contains(n)),
"{info:?}"
);
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
{
let this = Library::this().unwrap();
let f = unsafe { this.get::<*const u8>(c"dlsym") }.unwrap();
let info = f.info().expect("address info");
assert_eq!(info.symbol.as_deref(), Some(c"dlsym"), "{info:?}");
assert_eq!(info.symbol_address, f.as_ptr());
}
if cfg!(any(
all(target_os = "linux", target_env = "gnu"),
target_vendor = "apple",
windows
)) {
let path = lib.path().expect("library path");
assert!(
path.is_absolute() || cfg!(target_vendor = "apple"),
"{path:?}"
);
assert!(Library::this().unwrap().path().is_some());
}
assert!(dlsym::AddressInfo::of(std::ptr::null()).is_none());
}
#[cfg(target_os = "fullrust")]
#[test]
fn no_address_info() {
let lib = unsafe { Library::open("libm.so.6") }.unwrap();
let cos = unsafe { lib.get::<*const u8>(c"cos") }.unwrap();
assert!(cos.info().is_none());
assert!(lib.path().is_none());
}
#[cfg(target_os = "fullrust")]
#[test]
fn foreign_data_symbol() {
use std::os::unix::ffi::OsStrExt;
let (key, value) = std::env::vars_os().next().expect("an environment variable");
let mut wanted = key.as_bytes().to_vec();
wanted.push(b'=');
wanted.extend_from_slice(value.as_bytes());
let lib = libc();
let environ = unsafe { lib.get::<*mut *const *const c_char>(c"environ") }.unwrap();
let mut entry = unsafe { *environ.as_ref() };
assert!(!entry.is_null());
let mut found = false;
while let Some(s) = unsafe { entry.as_ref() }.filter(|s| !s.is_null()) {
found |= unsafe { CStr::from_ptr(*s) }.to_bytes() == wanted;
entry = unsafe { entry.add(1) };
}
assert!(found, "{key:?} missing from the foreign environment");
}
#[cfg(target_os = "fullrust")]
#[test]
fn foreign_errno() {
let lib = libc();
let close = unsafe { lib.get::<unsafe extern "C" fn(c_int) -> c_int>(c"close") }.unwrap();
assert_eq!(unsafe { close.call((-1,)) }, -1);
const EBADF: i32 = 9;
assert_eq!(dlsym::__private::errno_error().raw_os_error(), Some(EBADF));
}