From 1e504711bbbf1acd9b14544bf438969c9dff58c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Sch=C3=BCtz?= Date: Sun, 2 Nov 2025 21:17:20 +0100 Subject: [PATCH 1/5] add no-std support --- .github/workflows/libloading.yml | 13 ++ Cargo.toml | 4 + src/error.rs | 42 +++-- src/lib.rs | 9 + src/os/unix/consts.rs | 2 +- src/os/unix/mod.rs | 211 ++++++++++++++++++---- src/os/windows/mod.rs | 286 ++++++++++++++++++++++++++++-- src/safe.rs | 22 ++- src/util.rs | 10 +- tests/functions.rs | 293 +++++++++++++++++++++++++++++++ tests/library_filename.rs | 3 + tests/windows.rs | 30 ++++ 12 files changed, 852 insertions(+), 73 deletions(-) diff --git a/.github/workflows/libloading.yml b/.github/workflows/libloading.yml index 817d1df19..673d36f9f 100644 --- a/.github/workflows/libloading.yml +++ b/.github/workflows/libloading.yml @@ -33,6 +33,19 @@ jobs: # pwsh.exe drops quotes kekw. https://stackoverflow.com/a/59036879 shell: bash + no-std-build-test: + runs-on: ubuntu-latest + # This will rebuild core and alloc, which will re-mangle symbols in alloc, + # which then will break the pre-compiled std. This will cause the program to fail to link. + # This test does not test anything in an actual no-std environment only that the library compiles on it. + # We test all no-std only functions together with the ordinary functions. + steps: + - uses: actions/checkout@v4 + - name: Add nightly toolchain + run: rustup component add rust-src --toolchain nightly-x86_64-unknown-linux-gnu + - name: Compile without the standard library + run: cargo clean && cargo +nightly build -Zbuild-std=core,alloc --no-default-features + windows-test: runs-on: windows-latest strategy: diff --git a/Cargo.toml b/Cargo.toml index 9276fd6b6..a1084d9f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,10 @@ categories = ["api-bindings"] rust-version = "1.71.0" edition = "2015" +[features] +default = ["std"] +std = [] + [target.'cfg(windows)'.dependencies.windows-link] version = "0.2" diff --git a/src/error.rs b/src/error.rs index 43cf320b1..15d7d3f7f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,11 +1,12 @@ -use std::ffi::{CStr, CString}; +use alloc::ffi::CString; +use core::ffi::CStr; /// A `dlerror` error. pub struct DlDescription(pub(crate) CString); -impl std::fmt::Debug for DlDescription { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - std::fmt::Debug::fmt(&self.0, f) +impl core::fmt::Debug for DlDescription { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + core::fmt::Debug::fmt(&self.0, f) } } @@ -16,11 +17,15 @@ impl From<&CStr> for DlDescription { } /// A Windows API error. +#[cfg(not(feature = "std"))] +pub struct WindowsError(pub(crate) i32); + +#[cfg(feature = "std")] pub struct WindowsError(pub(crate) std::io::Error); -impl std::fmt::Debug for WindowsError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - std::fmt::Debug::fmt(&self.0, f) +impl core::fmt::Debug for WindowsError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + core::fmt::Debug::fmt(&self.0, f) } } @@ -82,17 +87,28 @@ pub enum Error { /// Could not create a new CString. CreateCString { /// The source error. - source: std::ffi::NulError, + source: alloc::ffi::NulError, }, /// Could not create a new CString from bytes with trailing null. CreateCStringWithTrailing { /// The source error. - source: std::ffi::FromBytesWithNulError, + source: core::ffi::FromBytesWithNulError, }, } -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { +impl core::error::Error for Error { + #[cfg(not(feature = "std"))] + fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { + use Error::*; + match *self { + CreateCString { ref source } => Some(source), + CreateCStringWithTrailing { ref source } => Some(source), + _ => None, + } + } + + #[cfg(feature = "std")] + fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { use Error::*; match *self { CreateCString { ref source } => Some(source), @@ -106,8 +122,8 @@ impl std::error::Error for Error { } } -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { +impl core::fmt::Display for Error { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { use Error::*; match *self { DlOpen { ref desc } => write!(f, "{}", desc.0.to_string_lossy()), diff --git a/src/lib.rs b/src/lib.rs index d1e2ced62..e78351c4a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,6 +40,11 @@ deny(missing_docs, clippy::all, unreachable_pub, unused) )] #![cfg_attr(libloading_docs, feature(doc_cfg))] +#![no_std] + +extern crate alloc; +#[cfg(feature = "std")] +extern crate std; pub mod changelog; mod error; @@ -51,7 +56,10 @@ mod util; pub use self::error::Error; #[cfg(any(unix, windows, libloading_docs))] pub use self::safe::{Library, Symbol}; + +#[cfg(feature = "std")] use std::env::consts::{DLL_PREFIX, DLL_SUFFIX}; +#[cfg(feature = "std")] use std::ffi::{OsStr, OsString}; /// Converts a library name to a filename generally appropriate for use on the system. @@ -71,6 +79,7 @@ use std::ffi::{OsStr, OsString}; /// Library::new(library_filename("LLVM")) /// }; /// ``` +#[cfg(feature = "std")] pub fn library_filename>(name: S) -> OsString { let name = name.as_ref(); let mut string = OsString::with_capacity(name.len() + DLL_PREFIX.len() + DLL_SUFFIX.len()); diff --git a/src/os/unix/consts.rs b/src/os/unix/consts.rs index 4ae00592d..862200e1a 100644 --- a/src/os/unix/consts.rs +++ b/src/os/unix/consts.rs @@ -1,4 +1,4 @@ -use std::os::raw::c_int; +use core::ffi::c_int; /// Perform lazy binding. /// diff --git a/src/os/unix/mod.rs b/src/os/unix/mod.rs index 0e42c50d9..9eb3d8bd9 100644 --- a/src/os/unix/mod.rs +++ b/src/os/unix/mod.rs @@ -4,14 +4,21 @@ mod unix_imports {} #[cfg(any(not(libloading_docs), unix))] mod unix_imports { + #[cfg(feature = "std")] pub(super) use std::os::unix::ffi::OsStrExt; } pub use self::consts::*; + +#[cfg(feature = "std")] use self::unix_imports::*; -use std::ffi::{CStr, OsStr}; -use std::os::raw; -use std::{fmt, marker, mem, ptr}; + +use core::ffi::CStr; +#[cfg(feature = "std")] +use std::ffi::OsStr; + +use core::ptr::null; +use core::{fmt, marker, mem, ptr}; use util::{cstr_cow_from_bytes, ensure_compatible_types}; mod consts; @@ -88,7 +95,7 @@ where /// A platform-specific counterpart of the cross-platform [`Library`](crate::Library). pub struct Library { - handle: *mut raw::c_void, + handle: *mut core::ffi::c_void, } unsafe impl Send for Library {} @@ -131,10 +138,67 @@ impl Library { /// termination routines contained within the library is safe as well. These routines may be /// executed when the library is unloaded. #[inline] + #[cfg(feature = "std")] pub unsafe fn new>(filename: P) -> Result { Library::open(Some(filename), RTLD_LAZY | RTLD_LOCAL) } + /// Find and eagerly load a shared library (module). + /// + /// If the `filename` contains a [path separator], the `filename` is interpreted as a `path` to + /// a file. Otherwise, platform-specific algorithms are employed to find a library with a + /// matching file name. + /// + /// This function will temporarily copy `filename` unless it is 0 terminated. + /// + /// If the `filename` contains 0 bytes other than the last character then this function will error + /// + /// This is equivalent to [Library::open](filename, [RTLD_LAZY] | [RTLD_LOCAL]). + /// + /// [path separator]: std::path::MAIN_SEPARATOR + /// + /// # Safety + /// + /// When a library is loaded, initialisation routines contained within the library are executed. + /// For the purposes of safety, the execution of these routines is conceptually the same calling an + /// unknown foreign function and may impose arbitrary requirements on the caller for the call + /// to be sound. + /// + /// Additionally, the callers of this function must also ensure that execution of the + /// termination routines contained within the library is safe as well. These routines may be + /// executed when the library is unloaded. + pub unsafe fn new_utf8(filename: impl AsRef) -> Result { + Library::open_utf8(filename.as_ref(), RTLD_LAZY | RTLD_LOCAL) + } + + /// Find and eagerly load a shared library (module). + /// + /// If the `filename` contains a [path separator], the `filename` is interpreted as a `path` to + /// a file. Otherwise, platform-specific algorithms are employed to find a library with a + /// matching file name. + /// + /// This function will temporarily copy `filename` unless it last element is 0. + /// + /// If the `filename` contains 0 bytes other than the last element then this function will error + /// + /// This is equivalent to [Library::open](filename, [RTLD_LAZY] | [RTLD_LOCAL]). + /// + /// [path separator]: std::path::MAIN_SEPARATOR + /// + /// # Safety + /// + /// When a library is loaded, initialisation routines contained within the library are executed. + /// For the purposes of safety, the execution of these routines is conceptually the same calling an + /// unknown foreign function and may impose arbitrary requirements on the caller for the call + /// to be sound. + /// + /// Additionally, the callers of this function must also ensure that execution of the + /// termination routines contained within the library is safe as well. These routines may be + /// executed when the library is unloaded. + pub unsafe fn new_raw(filename: &[u8]) -> Result { + Library::open_raw(filename, RTLD_LAZY | RTLD_LOCAL) + } + /// Load the `Library` representing the current executable. /// /// [`Library::get`] calls of the returned `Library` will look for symbols in following @@ -156,7 +220,7 @@ impl Library { unsafe { // SAFE: this does not load any new shared library images, no danger in it executing // initialiser routines. - Library::open(None::<&OsStr>, RTLD_LAZY | RTLD_LOCAL).expect("this should never fail") + Library::open_char_ptr(null(), RTLD_LAZY | RTLD_LOCAL).expect("this should never fail") } } @@ -177,25 +241,96 @@ impl Library { /// Additionally, the callers of this function must also ensure that execution of the /// termination routines contained within the library is safe as well. These routines may be /// executed when the library is unloaded. - pub unsafe fn open

(filename: Option

, flags: raw::c_int) -> Result + #[cfg(feature = "std")] + pub unsafe fn open

( + filename: Option

, + flags: core::ffi::c_int, + ) -> Result where P: AsRef, { + //TODO Why is this an option?, we have Library::this? let filename = match filename { None => None, Some(ref f) => Some(cstr_cow_from_bytes(f.as_ref().as_bytes())?), }; + + let result = Library::open_char_ptr( + match filename { + None => null(), + Some(ref f) => f.as_ptr(), + }, + flags, + ); + + drop(filename); + result + } + + /// Find and load an executable object file (shared library). + /// + /// This function will copy the filename if it does not end with a terminating 0 character. + /// + /// This function will fail if the filename contains a 0 character other than the last character. + /// + /// Corresponds to `dlopen(filename, flags)`. + /// + /// # Safety + /// + /// When a library is loaded, initialisation routines contained within the library are executed. + /// For the purposes of safety, the execution of these routines is conceptually the same calling an + /// unknown foreign function and may impose arbitrary requirements on the caller for the call + /// to be sound. + /// + /// Additionally, the callers of this function must also ensure that execution of the + /// termination routines contained within the library is safe as well. These routines may be + /// executed when the library is unloaded. + pub unsafe fn open_utf8( + filename: impl AsRef, + flags: core::ffi::c_int, + ) -> Result { + Library::open_raw(filename.as_ref().as_bytes(), flags) + } + + /// Find and load an executable object file (shared library). + /// + /// This function will copy the filename if the last element is not a 0 byte. + /// + /// This function will fail if the filename contains a 0 byte other than the last element. + /// + /// Corresponds to `dlopen(filename, flags)`. + /// + /// # Safety + /// + /// When a library is loaded, initialisation routines contained within the library are executed. + /// For the purposes of safety, the execution of these routines is conceptually the same calling an + /// unknown foreign function and may impose arbitrary requirements on the caller for the call + /// to be sound. + /// + /// Additionally, the callers of this function must also ensure that execution of the + /// termination routines contained within the library is safe as well. These routines may be + /// executed when the library is unloaded. + pub unsafe fn open_raw( + filename: &[u8], + flags: core::ffi::c_int, + ) -> Result { + let filename = cstr_cow_from_bytes(filename)?; + let res = Library::open_char_ptr(filename.as_ptr(), flags); + drop(filename); + res + } + + /// private helper to call dlopen+dlerror once we de-tangled the string into a raw pointer to a 0 terminated utf-8 string. + /// caller must ensure that the string is actually 0 terminated. + unsafe fn open_char_ptr( + filename: *const core::ffi::c_char, + flags: core::ffi::c_int, + ) -> Result { with_dlerror( move || { - let result = dlopen( - match filename { - None => ptr::null(), - Some(ref f) => f.as_ptr(), - }, - flags, - ); + let result = dlopen(filename, flags); + // ensure filename lives until dlopen completes - drop(filename); if result.is_null() { None } else { @@ -211,7 +346,7 @@ impl Library { where F: FnOnce() -> Result, crate::Error>, { - ensure_compatible_types::()?; + ensure_compatible_types::()?; let symbol = cstr_cow_from_bytes(symbol)?; // `dlsym` may return nullptr in two cases: when a symbol genuinely points to a null // pointer or the symbol cannot be found. In order to detect this case a double dlerror @@ -322,7 +457,7 @@ impl Library { /// /// The handle returned by this function shall be usable with APIs which accept handles /// as returned by `dlopen`. - pub fn into_raw(self) -> *mut raw::c_void { + pub fn into_raw(self) -> *mut core::ffi::c_void { let handle = self.handle; mem::forget(self); handle @@ -335,7 +470,7 @@ impl Library { /// The pointer shall be a result of a successful call of the `dlopen`-family of functions or a /// pointer previously returned by `Library::into_raw` call. It must be valid to call `dlclose` /// with this pointer as an argument. - pub unsafe fn from_raw(handle: *mut raw::c_void) -> Library { + pub unsafe fn from_raw(handle: *mut core::ffi::c_void) -> Library { Library { handle } } @@ -364,7 +499,7 @@ impl Library { // While the library is not free'd yet in case of an error, there is no reason to try // dropping it again, because all that will do is try calling `dlclose` again. only // this time it would ignore the return result, which we already seen failing… - std::mem::forget(self); + mem::forget(self); result } } @@ -379,7 +514,7 @@ impl Drop for Library { impl fmt::Debug for Library { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(&format!("Library@{:p}", self.handle)) + f.write_fmt(format_args!("Library@{:p}", self.handle)) } } @@ -388,19 +523,19 @@ impl fmt::Debug for Library { /// A major difference compared to the cross-platform `Symbol` is that this does not ensure that the /// `Symbol` does not outlive the `Library` it comes from. pub struct Symbol { - pointer: *mut raw::c_void, + pointer: *mut core::ffi::c_void, pd: marker::PhantomData, } impl Symbol { /// Convert the loaded `Symbol` into a raw pointer. - pub fn into_raw(self) -> *mut raw::c_void { + pub fn into_raw(self) -> *mut core::ffi::c_void { self.pointer } /// Convert the loaded `Symbol` into a raw pointer. /// For unix this does the same as into_raw. - pub fn as_raw_ptr(self) -> *mut raw::c_void { + pub fn as_raw_ptr(self) -> *mut core::ffi::c_void { self.pointer } } @@ -428,7 +563,7 @@ impl Clone for Symbol { } } -impl ::std::ops::Deref for Symbol { +impl core::ops::Deref for Symbol { type Target = T; fn deref(&self) -> &T { unsafe { @@ -445,13 +580,13 @@ impl fmt::Debug for Symbol { if dladdr(self.pointer, info.as_mut_ptr()) != 0 { let info = info.assume_init(); if info.dli_sname.is_null() { - f.write_str(&format!( + f.write_fmt(format_args!( "Symbol@{:p} from {:?}", self.pointer, CStr::from_ptr(info.dli_fname) )) } else { - f.write_str(&format!( + f.write_fmt(format_args!( "Symbol {:?}@{:p} from {:?}", CStr::from_ptr(info.dli_sname), self.pointer, @@ -459,7 +594,7 @@ impl fmt::Debug for Symbol { )) } } else { - f.write_str(&format!("Symbol@{:p}", self.pointer)) + f.write_fmt(format_args!("Symbol@{:p}", self.pointer)) } } } @@ -469,17 +604,23 @@ impl fmt::Debug for Symbol { #[cfg_attr(any(target_os = "linux", target_os = "android"), link(name = "dl"))] #[cfg_attr(any(target_os = "freebsd", target_os = "dragonfly"), link(name = "c"))] extern "C" { - fn dlopen(filename: *const raw::c_char, flags: raw::c_int) -> *mut raw::c_void; - fn dlclose(handle: *mut raw::c_void) -> raw::c_int; - fn dlsym(handle: *mut raw::c_void, symbol: *const raw::c_char) -> *mut raw::c_void; - fn dlerror() -> *mut raw::c_char; - fn dladdr(addr: *mut raw::c_void, info: *mut DlInfo) -> raw::c_int; + fn dlopen( + filename: *const core::ffi::c_char, + flags: core::ffi::c_int, + ) -> *mut core::ffi::c_void; + fn dlclose(handle: *mut core::ffi::c_void) -> core::ffi::c_int; + fn dlsym( + handle: *mut core::ffi::c_void, + symbol: *const core::ffi::c_char, + ) -> *mut core::ffi::c_void; + fn dlerror() -> *mut core::ffi::c_char; + fn dladdr(addr: *mut core::ffi::c_void, info: *mut DlInfo) -> core::ffi::c_int; } #[repr(C)] struct DlInfo { - dli_fname: *const raw::c_char, - dli_fbase: *mut raw::c_void, - dli_sname: *const raw::c_char, - dli_saddr: *mut raw::c_void, + dli_fname: *const core::ffi::c_char, + dli_fbase: *mut core::ffi::c_void, + dli_sname: *const core::ffi::c_char, + dli_saddr: *mut core::ffi::c_void, } diff --git a/src/os/windows/mod.rs b/src/os/windows/mod.rs index fa6713138..7f824d25f 100644 --- a/src/os/windows/mod.rs +++ b/src/os/windows/mod.rs @@ -5,6 +5,7 @@ mod windows_imports {} #[cfg(any(not(libloading_docs), windows))] mod windows_imports { use super::{BOOL, DWORD, FARPROC, HANDLE, HMODULE}; + #[cfg(feature = "std")] pub(super) use std::os::windows::ffi::{OsStrExt, OsStringExt}; windows_link::link!("kernel32.dll" "system" fn GetLastError() -> DWORD); windows_link::link!("kernel32.dll" "system" fn SetThreadErrorMode(new_mode: DWORD, old_mode: *mut DWORD) -> BOOL); @@ -16,9 +17,10 @@ mod windows_imports { } use self::windows_imports::*; +use alloc::vec::Vec; +use core::{fmt, marker, mem, ptr}; +#[cfg(feature = "std")] use std::ffi::{OsStr, OsString}; -use std::os::raw; -use std::{fmt, io, marker, mem, ptr}; use util::{cstr_cow_from_bytes, ensure_compatible_types}; /// The platform-specific counterpart of the cross-platform [`Library`](crate::Library). @@ -67,10 +69,81 @@ impl Library { /// termination routines contained within the library is safe as well. These routines may be /// executed when the library is unloaded. #[inline] + #[cfg(feature = "std")] pub unsafe fn new>(filename: P) -> Result { Library::load_with_flags(filename, 0) } + /// Find and load a module. + /// + /// If the `filename` specifies a full path, the function only searches that path for the + /// module. Otherwise, if the `filename` specifies a relative path or a module name without a + /// path, the function uses a Windows-specific search strategy to find the module. For more + /// information, see the [Remarks on MSDN][msdn]. + /// + /// If the `filename` specifies a library filename without a path and with the extension omitted, + /// the `.dll` extension is implicitly added. This behaviour may be suppressed by appending a + /// trailing `.` to the `filename`. + /// + /// This function will check if the last element in the filename slice is 0. If it is then + /// the pointer of the slice is passed to windows. Otherwise, the function will copy + /// the slice and append a 0 element at the end. To prevent unnecessary copying it is recommended + /// to add the 0 element on the caller side. + /// + /// This is equivalent to [Library::load_with_flags](filename, 0). + /// + /// [msdn]: https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryw#remarks + /// + /// # Safety + /// + /// When a library is loaded, initialisation routines contained within the library are executed. + /// For the purposes of safety, the execution of these routines is conceptually the same calling an + /// unknown foreign function and may impose arbitrary requirements on the caller for the call + /// to be sound. + /// + /// Additionally, the callers of this function must also ensure that execution of the + /// termination routines contained within the library is safe as well. These routines may be + /// executed when the library is unloaded. + #[inline] + pub unsafe fn new_raw(filename: &[u16]) -> Result { + Library::load_with_flags_raw(filename, 0) + } + + /// Find and load a module. + /// + /// If the `filename` specifies a full path, the function only searches that path for the + /// module. Otherwise, if the `filename` specifies a relative path or a module name without a + /// path, the function uses a Windows-specific search strategy to find the module. For more + /// information, see the [Remarks on MSDN][msdn]. + /// + /// If the `filename` specifies a library filename without a path and with the extension omitted, + /// the `.dll` extension is implicitly added. This behaviour may be suppressed by appending a + /// trailing `.` to the `filename`. + /// + /// This function will convert filename from its rust utf8 representation + /// to the utf16 w_char representation windows expects. + /// + /// This is equivalent to [Library::load_with_flags](filename, 0). + /// + /// [msdn]: https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryw#remarks + /// + /// # Safety + /// + /// When a library is loaded, initialisation routines contained within the library are executed. + /// For the purposes of safety, the execution of these routines is conceptually the same calling an + /// unknown foreign function and may impose arbitrary requirements on the caller for the call + /// to be sound. + /// + /// Additionally, the callers of this function must also ensure that execution of the + /// termination routines contained within the library is safe as well. These routines may be + /// executed when the library is unloaded. + #[inline] + pub unsafe fn new_utf8(filename: impl AsRef) -> Result { + let filename = filename.as_ref(); + let collector: Vec = filename.encode_utf16().collect(); + Library::new_raw(&collector) + } + /// Get the `Library` representing the original program executable. /// /// Note that the behaviour of the `Library` loaded with this method is different from @@ -86,7 +159,7 @@ impl Library { with_get_last_error( |source| crate::Error::GetModuleHandleExW { source }, || { - let result = GetModuleHandleExW(0, std::ptr::null_mut(), &mut handle); + let result = GetModuleHandleExW(0, ptr::null_mut(), &mut handle); if result == 0 { None } else { @@ -115,6 +188,7 @@ impl Library { /// This is equivalent to `GetModuleHandleExW(0, filename, _)`. /// /// [MSDN]: https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandleexw + #[cfg(feature = "std")] pub fn open_already_loaded>(filename: P) -> Result { let wide_filename: Vec = filename.as_ref().encode_wide().chain(Some(0)).collect(); @@ -141,6 +215,87 @@ impl Library { ret } + /// Get a module that is already loaded by the program. + /// + /// This function returns a `Library` corresponding to a module with the given name that is + /// already mapped into the address space of the process. If the module isn't found, an error is + /// returned. + /// + /// If the `filename` does not include a full path and there are multiple different loaded + /// modules corresponding to the `filename`, it is impossible to predict which module handle + /// will be returned. For more information refer to [MSDN]. + /// + /// If the `filename` specifies a library filename without a path and with the extension omitted, + /// the `.dll` extension is implicitly added. This behaviour may be suppressed by appending a + /// trailing `.` to the `filename`. + /// + /// This function will convert filename from its rust utf8 representation + /// to the utf16 w_char representation windows expects. + /// + /// This is equivalent to `GetModuleHandleExW(0, filename, _)`. + /// + /// [MSDN]: https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandleexw + pub fn open_already_loaded_utf8(filename: impl AsRef) -> Result { + let utf16: Vec = filename.as_ref().encode_utf16().chain(Some(0)).collect(); + Library::open_already_loaded_raw(utf16.as_slice()) + } + + /// Get a module that is already loaded by the program. + /// + /// This function returns a `Library` corresponding to a module with the given name that is + /// already mapped into the address space of the process. If the module isn't found, an error is + /// returned. + /// + /// If the `filename` does not include a full path and there are multiple different loaded + /// modules corresponding to the `filename`, it is impossible to predict which module handle + /// will be returned. For more information refer to [MSDN]. + /// + /// If the `filename` specifies a library filename without a path and with the extension omitted, + /// the `.dll` extension is implicitly added. This behaviour may be suppressed by appending a + /// trailing `.` to the `filename`. + /// + /// This function will check if the last element in the filename slice is 0. If it is then + /// the pointer of the slice is passed to windows. Otherwise, the function will copy + /// the slice and append a 0 element at the end. To prevent unnecessary copying it is recommended + /// to add the 0 element on the caller side. + /// + /// This is equivalent to `GetModuleHandleExW(0, filename, _)`. + /// + /// [MSDN]: https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandleexw + pub fn open_already_loaded_raw(filename: &[u16]) -> Result { + let mut temp_filename_vec = Vec::new(); + let mut raw_filename_ptr = filename.as_ptr(); + + //Poor mans 0 byte detection. + if filename.last() != Some(&0) { + temp_filename_vec.reserve_exact(filename.len() + 1); + temp_filename_vec.extend_from_slice(filename); + temp_filename_vec.push(0); + raw_filename_ptr = temp_filename_vec.as_ptr(); + } + + let ret = unsafe { + let mut handle: HMODULE = 0; + with_get_last_error( + |source| crate::Error::GetModuleHandleExW { source }, + || { + // Make sure no winapi calls as a result of drop happen inside this closure, because + // otherwise that might change the return value of the GetLastError. + let result = GetModuleHandleExW(0, raw_filename_ptr, &mut handle); + if result == 0 { + None + } else { + Some(Library(handle)) + } + }, + ) + .map_err(|e| e.unwrap_or(crate::Error::GetModuleHandleExWUnknown)) + }; + + drop(temp_filename_vec); // Drop temp_filename_vec here to ensure it doesn’t get moved and dropped + ret + } + /// Find and load a module, additionally adjusting behaviour with flags. /// /// See [`Library::new`] for documentation on the handling of the `filename` argument. See the @@ -160,11 +315,87 @@ impl Library { /// Additionally, the callers of this function must also ensure that execution of the /// termination routines contained within the library is safe as well. These routines may be /// executed when the library is unloaded. + #[cfg(feature = "std")] pub unsafe fn load_with_flags>( filename: P, flags: LOAD_LIBRARY_FLAGS, ) -> Result { let wide_filename: Vec = filename.as_ref().encode_wide().chain(Some(0)).collect(); + let _guard = ErrorModeGuard::new(); + let ret = load_with_flags_raw(wide_filename.as_slice()); + drop(wide_filename); // Drop wide_filename here to ensure it doesn’t get moved and dropped + ret + } + + /// Find and load a module, additionally adjusting behaviour with flags. + /// + /// See [`Library::new`] for documentation on the handling of the `filename` argument. See the + /// [flag table on MSDN][flags] for information on applicable values for the `flags` argument. + /// + /// This function will convert filename from its rust utf8 representation + /// to the utf16 w_char representation windows expects. + /// + /// Corresponds to `LoadLibraryExW(filename, reserved: NULL, flags)`. + /// + /// [flags]: https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryexw#parameters + /// + /// # Safety + /// + /// When a library is loaded, initialisation routines contained within the library are executed. + /// For the purposes of safety, the execution of these routines is conceptually the same calling an + /// unknown foreign function and may impose arbitrary requirements on the caller for the call + /// to be sound. + /// + /// Additionally, the callers of this function must also ensure that execution of the + /// termination routines contained within the library is safe as well. These routines may be + /// executed when the library is unloaded. + pub unsafe fn load_with_flags_utf8( + filename: impl AsRef, + flags: LOAD_LIBRARY_FLAGS, + ) -> Result { + let utf16: Vec = filename.as_ref().encode_utf16().chain(Some(0)).collect(); + Library::load_with_flags_raw(utf16.as_slice(), flags) + } + + /// Find and load a module, additionally adjusting behaviour with flags. + /// + /// See [`Library::new`] for documentation on the handling of the `filename` argument. See the + /// [flag table on MSDN][flags] for information on applicable values for the `flags` argument. + /// + /// This function will check if the last element in the filename slice is 0. If it is then + /// the pointer of the slice is passed to windows. Otherwise, the function will copy + /// the slice and append a 0 element at the end. To prevent unnecessary copying it is recommended + /// to add the 0 element on the caller side. + /// + /// Corresponds to `LoadLibraryExW(filename, reserved: NULL, flags)`. + /// + /// [flags]: https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryexw#parameters + /// + /// # Safety + /// + /// When a library is loaded, initialisation routines contained within the library are executed. + /// For the purposes of safety, the execution of these routines is conceptually the same calling an + /// unknown foreign function and may impose arbitrary requirements on the caller for the call + /// to be sound. + /// + /// Additionally, the callers of this function must also ensure that execution of the + /// termination routines contained within the library is safe as well. These routines may be + /// executed when the library is unloaded. + pub unsafe fn load_with_flags_raw( + filename: &[u16], + flags: LOAD_LIBRARY_FLAGS, + ) -> Result { + let mut temp_filename_vec = Vec::new(); + let mut raw_filename_ptr = filename.as_ptr(); + + //Poor mans 0 byte detection. + if filename.last() != Some(&0) { + temp_filename_vec.reserve_exact(filename.len() + 1); + temp_filename_vec.extend_from_slice(filename); + temp_filename_vec.push(0); + raw_filename_ptr = temp_filename_vec.as_ptr(); + } + let _guard = ErrorModeGuard::new(); let ret = with_get_last_error( @@ -172,7 +403,7 @@ impl Library { || { // Make sure no winapi calls as a result of drop happen inside this closure, because // otherwise that might change the return value of the GetLastError. - let handle = LoadLibraryExW(wide_filename.as_ptr(), 0, flags); + let handle = LoadLibraryExW(raw_filename_ptr, 0, flags); if handle == 0 { None } else { @@ -181,8 +412,10 @@ impl Library { }, ) .map_err(|e| e.unwrap_or(crate::Error::LoadLibraryExWUnknown)); - drop(wide_filename); // Drop wide_filename here to ensure it doesn’t get moved and dropped - // inside the closure by mistake. See comment inside the closure. + + //Ensure this lives until the closure returns. + drop(temp_filename_vec); + ret } @@ -319,7 +552,7 @@ impl Library { // While the library is not free'd yet in case of an error, there is no reason to try // dropping it again, because all that will do is try calling `FreeLibrary` again. only // this time it would ignore the return result, which we already seen failing... - std::mem::forget(self); + mem::forget(self); result } } @@ -333,6 +566,7 @@ impl Drop for Library { } impl fmt::Debug for Library { + #[cfg(feature = "std")] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { unsafe { // FIXME: use Maybeuninit::uninit_array when stable @@ -341,7 +575,7 @@ impl fmt::Debug for Library { if len == 0 { f.write_str(&format!("Library@{:#x}", self.0)) } else { - let string: OsString = OsString::from_wide( + let string: std::os::OsString = std::os::OsString::from_wide( // FIXME: use Maybeuninit::slice_get_ref when stable &*(&buf[..len] as *const [_] as *const [u16]), ); @@ -349,6 +583,11 @@ impl fmt::Debug for Library { } } } + + #[cfg(not(feature = "std"))] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_fmt(format_args!("Library@{:#x}", self.0)) + } } /// A symbol from a library. @@ -367,10 +606,10 @@ impl Symbol { } /// Convert the loaded `Symbol` into a raw pointer. - pub fn as_raw_ptr(self) -> *mut raw::c_void { + pub fn as_raw_ptr(self) -> *mut core::ffi::c_void { self.pointer - .map(|raw| raw as *mut raw::c_void) - .unwrap_or(std::ptr::null_mut()) + .map(|raw| raw as *mut core::ffi::c_void) + .unwrap_or(ptr::null_mut()) } } @@ -397,7 +636,7 @@ impl Clone for Symbol { } } -impl ::std::ops::Deref for Symbol { +impl core::ops::Deref for Symbol { type Target = T; fn deref(&self) -> &T { unsafe { &*((&self.pointer) as *const FARPROC as *const T) } @@ -408,7 +647,7 @@ impl fmt::Debug for Symbol { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.pointer { None => f.write_str("Symbol@0x0"), - Some(ptr) => f.write_str(&format!("Symbol@{:p}", ptr as *const ())), + Some(ptr) => f.write_fmt(format_args!("Symbol@{:p}", ptr as *const ())), } } } @@ -443,6 +682,7 @@ impl Drop for ErrorModeGuard { } } +#[cfg(feature = "std")] fn with_get_last_error( wrap: fn(crate::error::WindowsError) -> crate::Error, closure: F, @@ -456,12 +696,30 @@ where None } else { Some(wrap(crate::error::WindowsError( - io::Error::from_raw_os_error(error as i32), + std::io::Error::from_raw_os_error(error as i32), ))) } }) } +#[cfg(not(feature = "std"))] +fn with_get_last_error( + wrap: fn(crate::error::WindowsError) -> crate::Error, + closure: F, +) -> Result> +where + F: FnOnce() -> Option, +{ + closure().ok_or_else(|| { + let error = unsafe { GetLastError() }; + if error == 0 { + None + } else { + Some(wrap(crate::error::WindowsError(error as i32))) + } + }) +} + #[allow(clippy::upper_case_acronyms)] type BOOL = i32; #[allow(clippy::upper_case_acronyms)] diff --git a/src/safe.rs b/src/safe.rs index e217ee394..7cda4697e 100644 --- a/src/safe.rs +++ b/src/safe.rs @@ -5,11 +5,11 @@ use super::os::unix as imp; #[cfg(all(not(libloading_docs), windows))] use super::os::windows as imp; use super::Error; +use core::fmt; +use core::marker; +use core::ops; +#[cfg(feature = "std")] use std::ffi::OsStr; -use std::fmt; -use std::marker; -use std::ops; -use std::os::raw; /// A loaded dynamic library. #[cfg_attr(libloading_docs, doc(cfg(any(unix, windows))))] @@ -81,10 +81,22 @@ impl Library { /// let _ = Library::new("libsomelib.so.1").unwrap(); /// } /// ``` + #[cfg(feature = "std")] pub unsafe fn new>(filename: P) -> Result { imp::Library::new(filename).map(From::from) } + /// This function is equivalent to calling new, except that it accepts a rust utf-8 string + /// as the filename. It will either use or convert this string into the appropriate + /// representation that the target requires to load the library. + /// + /// Since this fn does a conversion this function is likely slower than using the `new` function. + /// + /// All other aspects such as safety are identical to the `new` function. + pub unsafe fn new_utf8(filename: impl AsRef) -> Result { + imp::Library::new_utf8(filename).map(From::from) + } + /// Get a pointer to a function or static variable by symbol name. /// /// The `symbol` may not contain any null bytes, with the exception of the last byte. Providing a @@ -259,7 +271,7 @@ impl<'lib, T> Symbol<'lib, T> { /// Using this function relinquishes all the lifetime guarantees. It is up to the developer to /// ensure the resulting `Symbol` is not used past the lifetime of the `Library` this symbol /// was loaded from. - pub unsafe fn try_as_raw_ptr(self) -> Option<*mut raw::c_void> { + pub unsafe fn try_as_raw_ptr(self) -> Option<*mut core::ffi::c_void> { Some( unsafe { // SAFE: the calling function has the same soundness invariants as this callee. diff --git a/src/util.rs b/src/util.rs index 599e6c254..7cb348c22 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,6 +1,6 @@ -use std::borrow::Cow; -use std::ffi::{CStr, CString}; -use std::os::raw; +use alloc::borrow::Cow; +use alloc::ffi::CString; +use core::ffi::CStr; use crate::Error; @@ -8,7 +8,7 @@ use crate::Error; /// /// Non-last null bytes still result in an error. pub(crate) fn cstr_cow_from_bytes(slice: &[u8]) -> Result, Error> { - static ZERO: raw::c_char = 0; + static ZERO: core::ffi::c_char = 0; Ok(match slice.last() { // Slice out of 0 elements None => unsafe { Cow::Borrowed(CStr::from_ptr(&ZERO)) }, @@ -26,7 +26,7 @@ pub(crate) fn cstr_cow_from_bytes(slice: &[u8]) -> Result, Error> #[inline] pub(crate) fn ensure_compatible_types() -> Result<(), Error> { - if ::std::mem::size_of::() != ::std::mem::size_of::() { + if size_of::() != size_of::() { Err(Error::IncompatibleSize) } else { Ok(()) diff --git a/tests/functions.rs b/tests/functions.rs index dc6b316e7..5ebff6fed 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -2,6 +2,7 @@ extern crate windows_sys; extern crate libloading; + use libloading::{Library, Symbol}; use std::os::raw::c_void; @@ -17,6 +18,13 @@ fn lib_path() -> std::path::PathBuf { .collect() } +fn lib_path_utf8() -> String { + lib_path() + .to_str() + .expect("lib_path not string") + .to_string() +} + fn make_helpers() { static ONCE: ::std::sync::Once = ::std::sync::Once::new(); ONCE.call_once(|| { @@ -36,6 +44,7 @@ fn make_helpers() { } #[test] +#[cfg(feature = "std")] fn test_id_u32() { make_helpers(); unsafe { @@ -46,6 +55,17 @@ fn test_id_u32() { } #[test] +fn test_id_u32_utf8() { + make_helpers(); + unsafe { + let lib = Library::new_utf8(lib_path_utf8()).unwrap(); + let f: Symbol u32> = lib.get(b"test_identity_u32\0").unwrap(); + assert_eq!(42, f(42)); + } +} + +#[test] +#[cfg(feature = "std")] fn test_try_into_ptr() { make_helpers(); unsafe { @@ -58,6 +78,19 @@ fn test_try_into_ptr() { } } +#[test] +fn test_try_into_ptr_utf8() { + make_helpers(); + unsafe { + let lib = Library::new_utf8(lib_path_utf8()).unwrap(); + let f: Symbol u32> = lib.get(b"test_identity_u32\0").unwrap(); + let ptr: *mut c_void = f.try_as_raw_ptr().unwrap(); + assert!(!ptr.is_null()); + let ptr_casted: extern "C" fn(u32) -> u32 = std::mem::transmute(ptr); + assert_eq!(42, ptr_casted(42)); + } +} + #[repr(C)] #[derive(Clone, Copy, PartialEq, Debug)] struct S { @@ -68,6 +101,7 @@ struct S { } #[test] +#[cfg(feature = "std")] fn test_id_struct() { make_helpers(); unsafe { @@ -91,17 +125,56 @@ fn test_id_struct() { } #[test] +fn test_id_struct_utf8() { + make_helpers(); + unsafe { + let lib = Library::new_utf8(lib_path_utf8()).unwrap(); + let f: Symbol S> = lib.get(b"test_identity_struct\0").unwrap(); + assert_eq!( + S { + a: 1, + b: 2, + c: 3, + d: 4 + }, + f(S { + a: 1, + b: 2, + c: 3, + d: 4 + }) + ); + } +} + +#[test] +#[allow(unpredictable_function_pointer_comparisons)] +#[cfg(feature = "std")] fn test_0_no_0() { make_helpers(); unsafe { let lib = Library::new(lib_path()).unwrap(); let f: Symbol S> = lib.get(b"test_identity_struct\0").unwrap(); let f2: Symbol S> = lib.get(b"test_identity_struct").unwrap(); + + assert_eq!(*f, *f2); + } +} + +#[test] +#[allow(unpredictable_function_pointer_comparisons)] +fn test_0_no_0_utf8() { + make_helpers(); + unsafe { + let lib = Library::new_utf8(lib_path_utf8()).unwrap(); + let f: Symbol S> = lib.get(b"test_identity_struct\0").unwrap(); + let f2: Symbol S> = lib.get(b"test_identity_struct").unwrap(); assert_eq!(*f, *f2); } } #[test] +#[cfg(feature = "std")] fn wrong_name_fails() { unsafe { Library::new("target/this_location_is_definitely_non existent:^~") @@ -111,6 +184,16 @@ fn wrong_name_fails() { } #[test] +fn wrong_name_fails_utf8() { + unsafe { + Library::new_utf8("target/this_location_is_definitely_non existent:^~") + .err() + .unwrap(); + } +} + +#[test] +#[cfg(feature = "std")] fn missing_symbol_fails() { make_helpers(); unsafe { @@ -121,6 +204,17 @@ fn missing_symbol_fails() { } #[test] +fn missing_symbol_fails_utf8() { + make_helpers(); + unsafe { + let lib = Library::new_utf8(lib_path_utf8()).unwrap(); + lib.get::<*mut ()>(b"test_does_not_exist").err().unwrap(); + lib.get::<*mut ()>(b"test_does_not_exist\0").err().unwrap(); + } +} + +#[test] +#[cfg(feature = "std")] fn interior_null_fails() { make_helpers(); unsafe { @@ -133,6 +227,19 @@ fn interior_null_fails() { } #[test] +fn interior_null_fails_utf8() { + make_helpers(); + unsafe { + let lib = Library::new_utf8(lib_path_utf8()).unwrap(); + lib.get::<*mut ()>(b"test_does\0_not_exist").err().unwrap(); + lib.get::<*mut ()>(b"test\0_does_not_exist\0") + .err() + .unwrap(); + } +} + +#[test] +#[cfg(feature = "std")] fn test_incompatible_type() { make_helpers(); unsafe { @@ -145,6 +252,19 @@ fn test_incompatible_type() { } #[test] +fn test_incompatible_type_utf8() { + make_helpers(); + unsafe { + let lib = Library::new_utf8(lib_path_utf8()).unwrap(); + assert!(match lib.get::<()>(b"test_identity_u32\0") { + Err(libloading::Error::IncompatibleSize) => true, + _ => false, + }) + } +} + +#[test] +#[cfg(feature = "std")] fn test_incompatible_type_named_fn() { make_helpers(); unsafe fn get<'a, T>(l: &'a Library, _: T) -> Result, libloading::Error> { @@ -160,6 +280,22 @@ fn test_incompatible_type_named_fn() { } #[test] +fn test_incompatible_type_named_fn_utf8() { + make_helpers(); + unsafe fn get<'a, T>(l: &'a Library, _: T) -> Result, libloading::Error> { + l.get::(b"test_identity_u32\0") + } + unsafe { + let lib = Library::new_utf8(lib_path_utf8()).unwrap(); + assert!(match get(&lib, test_incompatible_type_named_fn_utf8) { + Err(libloading::Error::IncompatibleSize) => true, + _ => false, + }) + } +} + +#[test] +#[cfg(feature = "std")] fn test_static_u32() { make_helpers(); unsafe { @@ -173,6 +309,20 @@ fn test_static_u32() { } #[test] +fn test_static_u32_utf8() { + make_helpers(); + unsafe { + let lib = Library::new_utf8(lib_path_utf8()).unwrap(); + let var: Symbol<*mut u32> = lib.get(b"TEST_STATIC_U32\0").unwrap(); + **var = 42; + let help: Symbol u32> = + lib.get(b"test_get_static_u32\0").unwrap(); + assert_eq!(42, help()); + } +} + +#[test] +#[cfg(feature = "std")] fn test_static_ptr() { make_helpers(); unsafe { @@ -185,6 +335,19 @@ fn test_static_ptr() { } } +#[test] +fn test_static_ptr_utf8() { + make_helpers(); + unsafe { + let lib = Library::new_utf8(lib_path_utf8()).unwrap(); + let var: Symbol<*mut *mut ()> = lib.get(b"TEST_STATIC_PTR\0").unwrap(); + **var = *var as *mut _; + let works: Symbol bool> = + lib.get(b"test_check_static_ptr\0").unwrap(); + assert!(works()); + } +} + #[test] // Something about i686-pc-windows-gnu, makes dll initialisation code call abort when it is loaded // and unloaded many times. So far it seems like an issue with mingw, not libloading, so ignoring @@ -193,6 +356,7 @@ fn test_static_ptr() { #[cfg(not(all(target_arch = "x86", target_os = "windows", target_env = "gnu")))] // Cygwin returns errors on `close`. #[cfg(not(target_os = "cygwin"))] +#[cfg(feature = "std")] fn manual_close_many_times() { make_helpers(); let join_handles: Vec<_> = (0..16) @@ -212,7 +376,32 @@ fn manual_close_many_times() { } } +#[test] +// See above for why +#[cfg(not(all(target_arch = "x86", target_os = "windows", target_env = "gnu")))] +// Cygwin returns errors on `close`. +#[cfg(not(target_os = "cygwin"))] +fn manual_close_many_times_utf8() { + make_helpers(); + let join_handles: Vec<_> = (0..16) + .map(|_| { + std::thread::spawn(|| unsafe { + for _ in 0..10000 { + let lib = Library::new_utf8(lib_path_utf8()).expect("open library"); + let _: Symbol u32> = + lib.get(b"test_identity_u32").expect("get fn"); + lib.close().expect("close is successful"); + } + }) + }) + .collect(); + for handle in join_handles { + handle.join().expect("thread should succeed"); + } +} + #[cfg(unix)] +#[cfg(feature = "std")] #[test] fn library_this_get() { use libloading::os::unix::Library; @@ -232,7 +421,28 @@ fn library_this_get() { } } +#[cfg(unix)] +#[test] +fn library_this_get_utf8() { + use libloading::os::unix::Library; + make_helpers(); + // SAFE: functions are never called + unsafe { + let _lib = Library::new_utf8(lib_path_utf8()).unwrap(); + let this = Library::this(); + // Library we loaded in `_lib` (should be RTLD_LOCAL). + assert!(this + .get::(b"test_identity_u32") + .is_err()); + // Something obscure from libc... + // Cygwin behaves like Windows so ignore it. + #[cfg(not(target_os = "cygwin"))] + assert!(this.get::(b"freopen").is_ok()); + } +} + #[cfg(windows)] +#[cfg(feature = "std")] #[test] fn library_this() { use libloading::os::windows::Library; @@ -253,6 +463,26 @@ fn library_this() { #[cfg(windows)] #[test] +fn library_this_utf8() { + use libloading::os::windows::Library; + make_helpers(); + unsafe { + // SAFE: well-known library without initialisers is loaded. + let _lib = Library::new_utf8(lib_path_utf8()).unwrap(); + let this = Library::this().expect("this library"); + // SAFE: functions are never called. + // Library we loaded in `_lib`. + assert!(this + .get::(b"test_identity_u32") + .is_err()); + // Something "obscure" from kernel32... + assert!(this.get::(b"GetLastError").is_err()); + } +} + +#[cfg(windows)] +#[cfg(feature = "std")] +#[test] fn works_getlasterror() { use libloading::os::windows::{Library, Symbol}; use windows_sys::Win32::Foundation::{GetLastError, SetLastError}; @@ -267,6 +497,21 @@ fn works_getlasterror() { #[cfg(windows)] #[test] +fn works_getlasterror_utf8() { + use libloading::os::windows::{Library, Symbol}; + use windows_sys::Win32::Foundation::{GetLastError, SetLastError}; + + unsafe { + let lib = Library::new_utf8("kernel32.dll").unwrap(); + let gle: Symbol u32> = lib.get(b"GetLastError").unwrap(); + SetLastError(42); + assert_eq!(GetLastError(), gle()) + } +} + +#[cfg(windows)] +#[cfg(feature = "std")] +#[test] fn works_getlasterror0() { use libloading::os::windows::{Library, Symbol}; use windows_sys::Win32::Foundation::{GetLastError, SetLastError}; @@ -281,6 +526,21 @@ fn works_getlasterror0() { #[cfg(windows)] #[test] +fn works_getlasterror0_utf8() { + use libloading::os::windows::{Library, Symbol}; + use windows_sys::Win32::Foundation::{GetLastError, SetLastError}; + + unsafe { + let lib = Library::new_utf8("kernel32.dll").unwrap(); + let gle: Symbol u32> = lib.get(b"GetLastError\0").unwrap(); + SetLastError(42); + assert_eq!(GetLastError(), gle()) + } +} + +#[cfg(windows)] +#[cfg(feature = "std")] +#[test] fn works_pin_module() { use libloading::os::windows::Library; @@ -292,6 +552,18 @@ fn works_pin_module() { #[cfg(windows)] #[test] +fn works_pin_module_utf8() { + use libloading::os::windows::Library; + + unsafe { + let lib = Library::new_utf8("kernel32.dll").unwrap(); + lib.pin().unwrap(); + } +} + +#[cfg(windows)] +#[cfg(feature = "std")] +#[test] fn library_open_already_loaded() { use libloading::os::windows::Library; @@ -310,3 +582,24 @@ fn library_open_already_loaded() { assert!(Library::open_already_loaded(LIBPATH).is_ok()); } } + +#[cfg(windows)] +#[test] +fn library_open_already_loaded_utf8() { + use libloading::os::windows::Library; + + // Present on Windows systems and NOT used by any other tests to prevent races. + const LIBPATH: &str = "Msftedit.dll"; + + // Not loaded yet. + assert!(match Library::open_already_loaded_utf8(LIBPATH) { + Err(libloading::Error::GetModuleHandleExW { .. }) => true, + _ => false, + }); + + unsafe { + let _lib = Library::new_utf8(LIBPATH).unwrap(); + // Loaded now. + assert!(Library::open_already_loaded_utf8(LIBPATH).is_ok()); + } +} diff --git a/tests/library_filename.rs b/tests/library_filename.rs index 4642ece08..dd5cd7e09 100644 --- a/tests/library_filename.rs +++ b/tests/library_filename.rs @@ -1,4 +1,6 @@ extern crate libloading; + +#[cfg(feature = "std")] use libloading::library_filename; use std::path::Path; @@ -10,6 +12,7 @@ const EXPECTED: &str = "libaudioengine.so"; const EXPECTED: &str = "libaudioengine.dylib"; #[test] +#[cfg(feature = "std")] fn test_library_filename() { let name = "audioengine"; let resolved = library_filename(name); diff --git a/tests/windows.rs b/tests/windows.rs index 13a414502..534c9cf9e 100644 --- a/tests/windows.rs +++ b/tests/windows.rs @@ -14,15 +14,29 @@ use std::os::raw::c_void; // The DLLs were kindly compiled by WindowsBunny (aka. @retep998). #[cfg(target_arch = "x86")] +#[cfg(feature = "std")] fn load_ordinal_lib() -> Library { unsafe { Library::new("tests/nagisa32.dll").expect("nagisa32.dll") } } #[cfg(target_arch = "x86_64")] +#[cfg(feature = "std")] fn load_ordinal_lib() -> Library { unsafe { Library::new("tests/nagisa64.dll").expect("nagisa64.dll") } } +#[cfg(target_arch = "x86")] +#[cfg(not(feature = "std"))] +fn load_ordinal_lib() -> Library { + unsafe { Library::new_utf8("tests/nagisa32.dll").expect("nagisa32.dll") } +} + +#[cfg(target_arch = "x86_64")] +#[cfg(not(feature = "std"))] +fn load_ordinal_lib() -> Library { + unsafe { Library::new_utf8("tests/nagisa64.dll").expect("nagisa64.dll") } +} + #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] #[test] fn test_ordinal() { @@ -57,6 +71,7 @@ fn test_ordinal_missing_fails() { } #[test] +#[cfg(feature = "std")] fn test_new_kernel23() { unsafe { Library::new("kernel23").err().unwrap(); @@ -64,8 +79,23 @@ fn test_new_kernel23() { } #[test] +fn test_new_kernel23_utf8() { + unsafe { + Library::new_utf8("kernel23").err().unwrap(); + } +} + +#[test] +#[cfg(feature = "std")] fn test_new_kernel32_no_ext() { unsafe { Library::new("kernel32").unwrap(); } } + +#[test] +fn test_new_kernel32_no_ext_utf8() { + unsafe { + Library::new_utf8("kernel32").unwrap(); + } +} From 0cdd68a2c59667d73906b687a841971afeb73dd5 Mon Sep 17 00:00:00 2001 From: Alexander Schuetz Date: Tue, 4 Nov 2025 00:47:32 +0100 Subject: [PATCH 2/5] rework to use traits instead of file names. --- .github/workflows/libloading.yml | 6 +- Cargo.toml | 2 +- src/as_filename.rs | 356 ++++++++++++++++++++++++++++ src/as_symbol_name.rs | 193 +++++++++++++++ src/error.rs | 68 ++++-- src/lib.rs | 8 +- src/os/unix/mod.rs | 209 ++++------------- src/os/windows/mod.rs | 389 +++++-------------------------- src/safe.rs | 22 +- tests/functions.rs | 97 +++++--- tests/library_filename.rs | 30 +-- tests/windows.rs | 30 --- 12 files changed, 794 insertions(+), 616 deletions(-) create mode 100644 src/as_filename.rs create mode 100644 src/as_symbol_name.rs diff --git a/.github/workflows/libloading.yml b/.github/workflows/libloading.yml index 673d36f9f..274208268 100644 --- a/.github/workflows/libloading.yml +++ b/.github/workflows/libloading.yml @@ -15,7 +15,7 @@ jobs: strategy: fail-fast: false matrix: - rust_toolchain: [nightly, stable, 1.71.0] + rust_toolchain: [nightly, stable, 1.81.0] os: [ubuntu-latest, windows-latest, macOS-latest] timeout-minutes: 20 steps: @@ -24,10 +24,12 @@ jobs: - run: rustup default ${{ matrix.rust_toolchain }} - run: rustup component add clippy - run: cargo update -p libc --precise 0.2.155 - if: ${{ matrix.rust_toolchain == '1.71.0' }} + if: ${{ matrix.rust_toolchain == '1.81.0' }} - run: cargo clippy - run: cargo test -- --nocapture - run: cargo test --release -- --nocapture + - run: cargo test --no-default-features -- --nocapture + - run: cargo test --release --no-default-features -- --nocapture - run: cargo rustdoc -Zunstable-options --config 'build.rustdocflags=["--cfg", "libloading_docs", "-D", "rustdoc::broken_intra_doc_links"]' if: ${{ matrix.rust_toolchain == 'nightly' }} # pwsh.exe drops quotes kekw. https://stackoverflow.com/a/59036879 diff --git a/Cargo.toml b/Cargo.toml index a1084d9f0..9cea23f76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ readme = "README.mkd" description = "Bindings around the platform's dynamic library loading primitives with greatly improved memory safety." keywords = ["dlopen", "load", "shared", "dylib"] categories = ["api-bindings"] -rust-version = "1.71.0" +rust-version = "1.81.0" edition = "2015" [features] diff --git a/src/as_filename.rs b/src/as_filename.rs new file mode 100644 index 000000000..d28702e92 --- /dev/null +++ b/src/as_filename.rs @@ -0,0 +1,356 @@ +use alloc::ffi::CString; +use alloc::string::String; +use alloc::vec::Vec; +use core::ffi::{c_char, CStr}; +use util::cstr_cow_from_bytes; +use Error; + +mod private { + + pub trait AsFilenameSeal { + #[allow(unused)] //Posix doesnt use this + fn windows_filename( + &self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result; + + #[allow(unused)] //Windows doesnt use this + fn posix_filename( + &self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, + ) -> Result; + } +} + +/// This trait is implemented on all types where libloading can derrive a filename from. +/// It is sealed and cannot be implemented by a user of libloading. +/// +/// This trait is implemented for the following common types: +/// - String &String &str +/// - CString &CString &CStr +/// - OsString &OsString &OsStr +/// - PathBuf &PathBuf &Path +/// - &[u8] assumes utf8 data! +/// - &[u16] assumes utf16-ne data! +/// +pub trait AsFilename: private::AsFilenameSeal {} + +impl AsFilename for T where T: private::AsFilenameSeal {} + +impl private::AsFilenameSeal for &str { + fn windows_filename( + &self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + let mut utf16: Vec = self.encode_utf16().collect(); + if utf16.last() != Some(&0) { + utf16.push(0); + }; + function(utf16.as_ptr()) + } + + fn posix_filename( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + let cow = cstr_cow_from_bytes(self.as_bytes())?; + function(cow.as_ptr()) + } +} + +impl private::AsFilenameSeal for &String { + fn windows_filename( + &self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + self.as_str().windows_filename(function) + } + + fn posix_filename( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + let cow = cstr_cow_from_bytes(self.as_bytes())?; + function(cow.as_ptr()) + } +} + +impl private::AsFilenameSeal for String { + fn windows_filename( + &self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + self.as_str().windows_filename(function) + } + + fn posix_filename( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + let cow = cstr_cow_from_bytes(self.as_bytes())?; + function(cow.as_ptr()) + } +} + +impl private::AsFilenameSeal for &CStr { + fn windows_filename( + &self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + //We assume cstr is utf-8 here, if it's something bespoke like CESU-8 (thanks java) then yeah... no. + let utf8 = self.to_str()?; + utf8.windows_filename(function) + } + + fn posix_filename( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + function(self.as_ptr()) + } +} + +impl private::AsFilenameSeal for &CString { + fn windows_filename( + &self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + self.as_c_str().windows_filename(function) + } + + fn posix_filename( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_c_str().posix_filename(function) + } +} + +impl private::AsFilenameSeal for CString { + fn windows_filename( + &self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + self.as_c_str().windows_filename(function) + } + + fn posix_filename( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_c_str().posix_filename(function) + } +} + +/// This implementation assumes that a slice always contains utf-8 bytes. +/// (which is likely the most common case if the slice originated in rust) +impl private::AsFilenameSeal for &[u8] { + fn windows_filename( + &self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + let utf8 = core::str::from_utf8(self)?; + utf8.windows_filename(function) + } + + fn posix_filename( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + let utf8 = core::str::from_utf8(self)?; + utf8.posix_filename(function) + } +} + +impl private::AsFilenameSeal for [u8; N] { + fn windows_filename( + &self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + self.as_slice().windows_filename(function) + } + + fn posix_filename( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_slice().posix_filename(function) + } +} + +impl private::AsFilenameSeal for &[u8; N] { + fn windows_filename( + &self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + self.as_slice().windows_filename(function) + } + + fn posix_filename( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_slice().posix_filename(function) + } +} + +/// This implementation assumes that the slice contains utf-16 in native endian. +/// Sidenote: For windows this is always utf-16-le because the last big endian Windows system was the xbox 360 that rust doesn't support. +/// For linux this is highly likely to also be utf-16-le because big endian is only used in some old mips routers or some IBM hardware. +impl private::AsFilenameSeal for &[u16] { + fn windows_filename( + &self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + //Check that we have valid utf-16 + for c in core::char::decode_utf16(self.iter().copied()) { + let _ = c?; + } + + if self.last() != Some(&0) { + let mut copy = self.to_vec(); + copy.push(0); + return function(copy.as_ptr()); + } + + function(self.as_ptr()) + } + + fn posix_filename( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + let utf8 = String::from_utf16(self)?; + utf8.posix_filename(function) + } +} + +#[cfg(feature = "std")] +#[cfg(any(windows, unix))] +mod std { + use as_filename::private; + use core::ffi::c_char; + use std::ffi::{OsStr, OsString}; + use Error; + + impl private::AsFilenameSeal for &OsStr { + #[cfg(unix)] + fn windows_filename( + &self, + _function: impl FnOnce(*const u16) -> Result, + ) -> Result { + panic!("windows_filename() not implemented for OsStr on posix platform"); + } + + #[cfg(windows)] + fn windows_filename( + &self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + let mut utf16: alloc::vec::Vec = + std::os::windows::ffi::OsStrExt::encode_wide(*self).collect(); + if utf16.last() != Some(&0) { + utf16.push(0); + }; + function(utf16.as_ptr()) + } + + #[cfg(unix)] + fn posix_filename( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + let cow = + crate::util::cstr_cow_from_bytes(std::os::unix::ffi::OsStrExt::as_bytes(*self))?; + function(cow.as_ptr()) + } + + #[cfg(windows)] + fn posix_filename( + &self, + _function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + panic!("posix_filename() not implemented for OsStr on windows") + } + } + + impl private::AsFilenameSeal for &OsString { + fn windows_filename( + &self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + self.as_os_str().windows_filename(function) + } + + fn posix_filename( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_os_str().posix_filename(function) + } + } + + impl private::AsFilenameSeal for OsString { + fn windows_filename( + &self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + self.as_os_str().windows_filename(function) + } + + fn posix_filename( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_os_str().posix_filename(function) + } + } + + impl private::AsFilenameSeal for std::path::PathBuf { + fn windows_filename( + &self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + self.as_os_str().windows_filename(function) + } + + fn posix_filename( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_os_str().posix_filename(function) + } + } + + impl private::AsFilenameSeal for &std::path::PathBuf { + fn windows_filename( + &self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + self.as_os_str().windows_filename(function) + } + + fn posix_filename( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_os_str().posix_filename(function) + } + } + + impl private::AsFilenameSeal for &std::path::Path { + fn windows_filename( + &self, + function: impl FnOnce(*const u16) -> Result, + ) -> Result { + self.as_os_str().windows_filename(function) + } + + fn posix_filename( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_os_str().posix_filename(function) + } + } +} diff --git a/src/as_symbol_name.rs b/src/as_symbol_name.rs new file mode 100644 index 000000000..069bccf50 --- /dev/null +++ b/src/as_symbol_name.rs @@ -0,0 +1,193 @@ +use crate::{util, Error}; +use alloc::ffi::CString; +use alloc::string::String; +use core::ffi::{c_char, CStr}; + +mod private { + + pub trait AsSymbolNameSeal { + fn symbol_name( + &self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, + ) -> Result; + } +} + +/// This trait is implemented on all types where libloading can derrive a symbol name from. +/// It is sealed and cannot be implemented by a user of libloading. +/// +/// This trait is implemented for the following common types: +/// - String &String &str +/// - CString &CString &CStr +/// - OsString &OsString &OsStr +/// - PathBuf &PathBuf &Path +/// - &[u8] assumes utf8 data! +/// - &[u16] assumes utf16-ne data! +/// +pub trait AsSymbolName: private::AsSymbolNameSeal {} + +impl AsSymbolName for T where T: private::AsSymbolNameSeal {} + +impl private::AsSymbolNameSeal for &str { + fn symbol_name( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_bytes().symbol_name(function) + } +} + +impl private::AsSymbolNameSeal for &String { + fn symbol_name( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_str().symbol_name(function) + } +} + +impl private::AsSymbolNameSeal for String { + fn symbol_name( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_str().symbol_name(function) + } +} + +impl private::AsSymbolNameSeal for &CStr { + fn symbol_name( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + function(self.as_ptr()) + } +} + +impl private::AsSymbolNameSeal for &CString { + fn symbol_name( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + function(self.as_ptr()) + } +} + +impl private::AsSymbolNameSeal for CString { + fn symbol_name( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + function(self.as_ptr()) + } +} + +impl private::AsSymbolNameSeal for &[u8] { + fn symbol_name( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + let cow = util::cstr_cow_from_bytes(self)?; + function(cow.as_ptr()) + } +} + +impl private::AsSymbolNameSeal for [u8; N] { + fn symbol_name( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_slice().symbol_name(function) + } +} + +impl private::AsSymbolNameSeal for &[u8; N] { + fn symbol_name( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_slice().symbol_name(function) + } +} + +impl private::AsSymbolNameSeal for &[u16] { + fn symbol_name( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + let string = String::from_utf16(self)?; + string.symbol_name(function) + } +} + +#[cfg(feature = "std")] +#[cfg(any(windows, unix))] +mod std { + use as_symbol_name::private; + use std::ffi::{c_char, OsStr, OsString}; + use std::path::{Path, PathBuf}; + use Error; + + impl private::AsSymbolNameSeal for &OsStr { + #[cfg(unix)] + fn symbol_name( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + std::os::unix::ffi::OsStrExt::as_bytes(*self).symbol_name(function) + } + + #[cfg(windows)] + fn symbol_name( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_encoded_bytes().symbol_name(function) + } + } + + impl private::AsSymbolNameSeal for &OsString { + fn symbol_name( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_os_str().symbol_name(function) + } + } + + impl private::AsSymbolNameSeal for OsString { + fn symbol_name( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_os_str().symbol_name(function) + } + } + + impl private::AsSymbolNameSeal for PathBuf { + fn symbol_name( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_os_str().symbol_name(function) + } + } + + impl private::AsSymbolNameSeal for &PathBuf { + fn symbol_name( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_os_str().symbol_name(function) + } + } + + impl private::AsSymbolNameSeal for &Path { + fn symbol_name( + &self, + function: impl FnOnce(*const c_char) -> Result, + ) -> Result { + self.as_os_str().symbol_name(function) + } + } +} diff --git a/src/error.rs b/src/error.rs index 15d7d3f7f..ed37c227b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,8 @@ use alloc::ffi::CString; +use alloc::string::FromUtf16Error; +use core::char::DecodeUtf16Error; use core::ffi::CStr; +use core::str::Utf8Error; /// A `dlerror` error. pub struct DlDescription(pub(crate) CString); @@ -17,18 +20,22 @@ impl From<&CStr> for DlDescription { } /// A Windows API error. -#[cfg(not(feature = "std"))] +#[derive(Copy, Clone)] pub struct WindowsError(pub(crate) i32); -#[cfg(feature = "std")] -pub struct WindowsError(pub(crate) std::io::Error); - impl core::fmt::Debug for WindowsError { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { core::fmt::Debug::fmt(&self.0, f) } } +#[cfg(feature = "std")] +impl From for std::io::Error { + fn from(value: WindowsError) -> Self { + std::io::Error::from_raw_os_error(value.0) + } +} + /// Errors. #[derive(Debug)] #[non_exhaustive] @@ -89,6 +96,21 @@ pub enum Error { /// The source error. source: alloc::ffi::NulError, }, + /// Could not parse some sequence of bytes as utf-8. + Utf8Error { + /// The source error. + source: Utf8Error, + }, + /// Could not parse some sequence of bytes as utf-16. + DecodeUtf16Error { + ///The source error. + source: DecodeUtf16Error, + }, + /// Could not parse some sequence of bytes as utf-16. + FromUtf16Error { + ///The source error. + source: FromUtf16Error, + }, /// Could not create a new CString from bytes with trailing null. CreateCStringWithTrailing { /// The source error. @@ -96,27 +118,30 @@ pub enum Error { }, } -impl core::error::Error for Error { - #[cfg(not(feature = "std"))] - fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { - use Error::*; - match *self { - CreateCString { ref source } => Some(source), - CreateCStringWithTrailing { ref source } => Some(source), - _ => None, - } +impl From for Error { + fn from(value: Utf8Error) -> Self { + Self::Utf8Error { source: value } + } +} + +impl From for Error { + fn from(value: DecodeUtf16Error) -> Self { + Self::DecodeUtf16Error { source: value } } +} - #[cfg(feature = "std")] +impl From for Error { + fn from(value: FromUtf16Error) -> Self { + Self::FromUtf16Error { source: value } + } +} + +impl core::error::Error for Error { fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { use Error::*; match *self { CreateCString { ref source } => Some(source), CreateCStringWithTrailing { ref source } => Some(source), - LoadLibraryExW { ref source } => Some(&source.0), - GetModuleHandleExW { ref source } => Some(&source.0), - GetProcAddress { ref source } => Some(&source.0), - FreeLibrary { ref source } => Some(&source.0), _ => None, } } @@ -152,10 +177,17 @@ impl core::fmt::Display for Error { write!(f, "FreeLibrary failed, but system did not report the error") } CreateCString { .. } => write!(f, "could not create a C string from bytes"), + CreateCStringWithTrailing { .. } => write!( f, "could not create a C string from bytes with trailing null" ), + Utf8Error { .. } => write!( + f, + "could not create a C string from bytes with trailing null" + ), + DecodeUtf16Error { .. } => write!(f, "could not decode some bytes as utf16"), + FromUtf16Error { .. } => write!(f, "could not parse some utf16 bytes to a string"), IncompatibleSize => write!(f, "requested type cannot possibly work"), } } diff --git a/src/lib.rs b/src/lib.rs index e78351c4a..ea1b9ab07 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,7 +27,7 @@ //! fn call_dynamic() -> Result> { //! unsafe { //! let lib = libloading::Library::new("/path/to/liblibrary.so")?; -//! let func: libloading::Symbol u32> = lib.get(b"my_func")?; +//! let func: libloading::Symbol u32> = lib.get(b"my_func")?; //! Ok(func()) //! } //! } @@ -46,6 +46,12 @@ extern crate alloc; #[cfg(feature = "std")] extern crate std; +mod as_filename; +mod as_symbol_name; + +pub use as_filename::AsFilename; +pub use as_symbol_name::AsSymbolName; + pub mod changelog; mod error; pub mod os; diff --git a/src/os/unix/mod.rs b/src/os/unix/mod.rs index 9eb3d8bd9..1cdf57afa 100644 --- a/src/os/unix/mod.rs +++ b/src/os/unix/mod.rs @@ -1,25 +1,10 @@ -// A hack for docs.rs to build documentation that has both windows and linux documentation in the -// same rustdoc build visible. -#[cfg(all(libloading_docs, not(unix)))] -mod unix_imports {} -#[cfg(any(not(libloading_docs), unix))] -mod unix_imports { - #[cfg(feature = "std")] - pub(super) use std::os::unix::ffi::OsStrExt; -} - pub use self::consts::*; - -#[cfg(feature = "std")] -use self::unix_imports::*; - +use as_filename::AsFilename; +use as_symbol_name::AsSymbolName; use core::ffi::CStr; -#[cfg(feature = "std")] -use std::ffi::OsStr; - use core::ptr::null; use core::{fmt, marker, mem, ptr}; -use util::{cstr_cow_from_bytes, ensure_compatible_types}; +use util::ensure_compatible_types; mod consts; @@ -138,67 +123,10 @@ impl Library { /// termination routines contained within the library is safe as well. These routines may be /// executed when the library is unloaded. #[inline] - #[cfg(feature = "std")] - pub unsafe fn new>(filename: P) -> Result { + pub unsafe fn new(filename: impl AsFilename) -> Result { Library::open(Some(filename), RTLD_LAZY | RTLD_LOCAL) } - /// Find and eagerly load a shared library (module). - /// - /// If the `filename` contains a [path separator], the `filename` is interpreted as a `path` to - /// a file. Otherwise, platform-specific algorithms are employed to find a library with a - /// matching file name. - /// - /// This function will temporarily copy `filename` unless it is 0 terminated. - /// - /// If the `filename` contains 0 bytes other than the last character then this function will error - /// - /// This is equivalent to [Library::open](filename, [RTLD_LAZY] | [RTLD_LOCAL]). - /// - /// [path separator]: std::path::MAIN_SEPARATOR - /// - /// # Safety - /// - /// When a library is loaded, initialisation routines contained within the library are executed. - /// For the purposes of safety, the execution of these routines is conceptually the same calling an - /// unknown foreign function and may impose arbitrary requirements on the caller for the call - /// to be sound. - /// - /// Additionally, the callers of this function must also ensure that execution of the - /// termination routines contained within the library is safe as well. These routines may be - /// executed when the library is unloaded. - pub unsafe fn new_utf8(filename: impl AsRef) -> Result { - Library::open_utf8(filename.as_ref(), RTLD_LAZY | RTLD_LOCAL) - } - - /// Find and eagerly load a shared library (module). - /// - /// If the `filename` contains a [path separator], the `filename` is interpreted as a `path` to - /// a file. Otherwise, platform-specific algorithms are employed to find a library with a - /// matching file name. - /// - /// This function will temporarily copy `filename` unless it last element is 0. - /// - /// If the `filename` contains 0 bytes other than the last element then this function will error - /// - /// This is equivalent to [Library::open](filename, [RTLD_LAZY] | [RTLD_LOCAL]). - /// - /// [path separator]: std::path::MAIN_SEPARATOR - /// - /// # Safety - /// - /// When a library is loaded, initialisation routines contained within the library are executed. - /// For the purposes of safety, the execution of these routines is conceptually the same calling an - /// unknown foreign function and may impose arbitrary requirements on the caller for the call - /// to be sound. - /// - /// Additionally, the callers of this function must also ensure that execution of the - /// termination routines contained within the library is safe as well. These routines may be - /// executed when the library is unloaded. - pub unsafe fn new_raw(filename: &[u8]) -> Result { - Library::open_raw(filename, RTLD_LAZY | RTLD_LOCAL) - } - /// Load the `Library` representing the current executable. /// /// [`Library::get`] calls of the returned `Library` will look for symbols in following @@ -241,83 +169,18 @@ impl Library { /// Additionally, the callers of this function must also ensure that execution of the /// termination routines contained within the library is safe as well. These routines may be /// executed when the library is unloaded. - #[cfg(feature = "std")] pub unsafe fn open

( filename: Option

, flags: core::ffi::c_int, ) -> Result where - P: AsRef, + P: AsFilename, { - //TODO Why is this an option?, we have Library::this? - let filename = match filename { - None => None, - Some(ref f) => Some(cstr_cow_from_bytes(f.as_ref().as_bytes())?), + let Some(filename) = filename else { + return Self::open_char_ptr(null(), flags); }; - let result = Library::open_char_ptr( - match filename { - None => null(), - Some(ref f) => f.as_ptr(), - }, - flags, - ); - - drop(filename); - result - } - - /// Find and load an executable object file (shared library). - /// - /// This function will copy the filename if it does not end with a terminating 0 character. - /// - /// This function will fail if the filename contains a 0 character other than the last character. - /// - /// Corresponds to `dlopen(filename, flags)`. - /// - /// # Safety - /// - /// When a library is loaded, initialisation routines contained within the library are executed. - /// For the purposes of safety, the execution of these routines is conceptually the same calling an - /// unknown foreign function and may impose arbitrary requirements on the caller for the call - /// to be sound. - /// - /// Additionally, the callers of this function must also ensure that execution of the - /// termination routines contained within the library is safe as well. These routines may be - /// executed when the library is unloaded. - pub unsafe fn open_utf8( - filename: impl AsRef, - flags: core::ffi::c_int, - ) -> Result { - Library::open_raw(filename.as_ref().as_bytes(), flags) - } - - /// Find and load an executable object file (shared library). - /// - /// This function will copy the filename if the last element is not a 0 byte. - /// - /// This function will fail if the filename contains a 0 byte other than the last element. - /// - /// Corresponds to `dlopen(filename, flags)`. - /// - /// # Safety - /// - /// When a library is loaded, initialisation routines contained within the library are executed. - /// For the purposes of safety, the execution of these routines is conceptually the same calling an - /// unknown foreign function and may impose arbitrary requirements on the caller for the call - /// to be sound. - /// - /// Additionally, the callers of this function must also ensure that execution of the - /// termination routines contained within the library is safe as well. These routines may be - /// executed when the library is unloaded. - pub unsafe fn open_raw( - filename: &[u8], - flags: core::ffi::c_int, - ) -> Result { - let filename = cstr_cow_from_bytes(filename)?; - let res = Library::open_char_ptr(filename.as_ptr(), flags); - drop(filename); - res + filename.posix_filename(|posix_filename| Library::open_char_ptr(posix_filename, flags)) } /// private helper to call dlopen+dlerror once we de-tangled the string into a raw pointer to a 0 terminated utf-8 string. @@ -342,38 +205,43 @@ impl Library { .map_err(|e| e.unwrap_or(crate::Error::DlOpenUnknown)) } - unsafe fn get_impl(&self, symbol: &[u8], on_null: F) -> Result, crate::Error> + unsafe fn get_impl( + &self, + symbol: impl AsSymbolName, + on_null: F, + ) -> Result, crate::Error> where F: FnOnce() -> Result, crate::Error>, { ensure_compatible_types::()?; - let symbol = cstr_cow_from_bytes(symbol)?; // `dlsym` may return nullptr in two cases: when a symbol genuinely points to a null // pointer or the symbol cannot be found. In order to detect this case a double dlerror // pattern must be used, which is, sadly, a little bit racy. // // We try to leave as little space as possible for this to occur, but we can’t exactly // fully prevent it. - let result = with_dlerror( - || { - dlerror(); - let symbol = dlsym(self.handle, symbol.as_ptr()); - if symbol.is_null() { - None - } else { - Some(Symbol { - pointer: symbol, - pd: marker::PhantomData, - }) - } - }, - |desc| crate::Error::DlSym { desc: desc.into() }, - ); - match result { - Err(None) => on_null(), - Err(Some(e)) => Err(e), - Ok(x) => Ok(x), - } + symbol.symbol_name(|posix_symbol| { + let result = with_dlerror( + || { + dlerror(); + let symbol = dlsym(self.handle, posix_symbol); + if symbol.is_null() { + None + } else { + Some(Symbol { + pointer: symbol, + pd: marker::PhantomData, + }) + } + }, + |desc| crate::Error::DlSym { desc: desc.into() }, + ); + match result { + Err(None) => on_null(), + Err(Some(e)) => Err(e), + Ok(x) => Ok(x), + } + }) } /// Get a pointer to a function or static variable by symbol name. @@ -400,7 +268,7 @@ impl Library { /// pointer without it being an error. If loading a null pointer is something you care about, /// consider using the [`Library::get_singlethreaded`] call. #[inline(always)] - pub unsafe fn get(&self, symbol: &[u8]) -> Result, crate::Error> { + pub unsafe fn get(&self, symbol: impl AsSymbolName) -> Result, crate::Error> { extern crate cfg_if; cfg_if::cfg_if! { // These targets are known to have MT-safe `dlerror`. @@ -444,7 +312,10 @@ impl Library { /// The implementation of thread-local variables is extremely platform specific and uses of such /// variables that work on e.g. Linux may have unintended behaviour on other targets. #[inline(always)] - pub unsafe fn get_singlethreaded(&self, symbol: &[u8]) -> Result, crate::Error> { + pub unsafe fn get_singlethreaded( + &self, + symbol: impl AsSymbolName, + ) -> Result, crate::Error> { self.get_impl(symbol, || { Ok(Symbol { pointer: ptr::null_mut(), diff --git a/src/os/windows/mod.rs b/src/os/windows/mod.rs index 7f824d25f..bf41a6140 100644 --- a/src/os/windows/mod.rs +++ b/src/os/windows/mod.rs @@ -5,8 +5,6 @@ mod windows_imports {} #[cfg(any(not(libloading_docs), windows))] mod windows_imports { use super::{BOOL, DWORD, FARPROC, HANDLE, HMODULE}; - #[cfg(feature = "std")] - pub(super) use std::os::windows::ffi::{OsStrExt, OsStringExt}; windows_link::link!("kernel32.dll" "system" fn GetLastError() -> DWORD); windows_link::link!("kernel32.dll" "system" fn SetThreadErrorMode(new_mode: DWORD, old_mode: *mut DWORD) -> BOOL); windows_link::link!("kernel32.dll" "system" fn GetModuleHandleExW(flags: u32, module_name: *const u16, module: *mut HMODULE) -> BOOL); @@ -17,11 +15,10 @@ mod windows_imports { } use self::windows_imports::*; -use alloc::vec::Vec; +use as_filename::AsFilename; +use as_symbol_name::AsSymbolName; use core::{fmt, marker, mem, ptr}; -#[cfg(feature = "std")] -use std::ffi::{OsStr, OsString}; -use util::{cstr_cow_from_bytes, ensure_compatible_types}; +use util::ensure_compatible_types; /// The platform-specific counterpart of the cross-platform [`Library`](crate::Library). pub struct Library(HMODULE); @@ -69,81 +66,10 @@ impl Library { /// termination routines contained within the library is safe as well. These routines may be /// executed when the library is unloaded. #[inline] - #[cfg(feature = "std")] - pub unsafe fn new>(filename: P) -> Result { + pub unsafe fn new(filename: impl AsFilename) -> Result { Library::load_with_flags(filename, 0) } - /// Find and load a module. - /// - /// If the `filename` specifies a full path, the function only searches that path for the - /// module. Otherwise, if the `filename` specifies a relative path or a module name without a - /// path, the function uses a Windows-specific search strategy to find the module. For more - /// information, see the [Remarks on MSDN][msdn]. - /// - /// If the `filename` specifies a library filename without a path and with the extension omitted, - /// the `.dll` extension is implicitly added. This behaviour may be suppressed by appending a - /// trailing `.` to the `filename`. - /// - /// This function will check if the last element in the filename slice is 0. If it is then - /// the pointer of the slice is passed to windows. Otherwise, the function will copy - /// the slice and append a 0 element at the end. To prevent unnecessary copying it is recommended - /// to add the 0 element on the caller side. - /// - /// This is equivalent to [Library::load_with_flags](filename, 0). - /// - /// [msdn]: https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryw#remarks - /// - /// # Safety - /// - /// When a library is loaded, initialisation routines contained within the library are executed. - /// For the purposes of safety, the execution of these routines is conceptually the same calling an - /// unknown foreign function and may impose arbitrary requirements on the caller for the call - /// to be sound. - /// - /// Additionally, the callers of this function must also ensure that execution of the - /// termination routines contained within the library is safe as well. These routines may be - /// executed when the library is unloaded. - #[inline] - pub unsafe fn new_raw(filename: &[u16]) -> Result { - Library::load_with_flags_raw(filename, 0) - } - - /// Find and load a module. - /// - /// If the `filename` specifies a full path, the function only searches that path for the - /// module. Otherwise, if the `filename` specifies a relative path or a module name without a - /// path, the function uses a Windows-specific search strategy to find the module. For more - /// information, see the [Remarks on MSDN][msdn]. - /// - /// If the `filename` specifies a library filename without a path and with the extension omitted, - /// the `.dll` extension is implicitly added. This behaviour may be suppressed by appending a - /// trailing `.` to the `filename`. - /// - /// This function will convert filename from its rust utf8 representation - /// to the utf16 w_char representation windows expects. - /// - /// This is equivalent to [Library::load_with_flags](filename, 0). - /// - /// [msdn]: https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryw#remarks - /// - /// # Safety - /// - /// When a library is loaded, initialisation routines contained within the library are executed. - /// For the purposes of safety, the execution of these routines is conceptually the same calling an - /// unknown foreign function and may impose arbitrary requirements on the caller for the call - /// to be sound. - /// - /// Additionally, the callers of this function must also ensure that execution of the - /// termination routines contained within the library is safe as well. These routines may be - /// executed when the library is unloaded. - #[inline] - pub unsafe fn new_utf8(filename: impl AsRef) -> Result { - let filename = filename.as_ref(); - let collector: Vec = filename.encode_utf16().collect(); - Library::new_raw(&collector) - } - /// Get the `Library` representing the original program executable. /// /// Note that the behaviour of the `Library` loaded with this method is different from @@ -188,173 +114,26 @@ impl Library { /// This is equivalent to `GetModuleHandleExW(0, filename, _)`. /// /// [MSDN]: https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandleexw - #[cfg(feature = "std")] - pub fn open_already_loaded>(filename: P) -> Result { - let wide_filename: Vec = filename.as_ref().encode_wide().chain(Some(0)).collect(); - - let ret = unsafe { - let mut handle: HMODULE = 0; - with_get_last_error( - |source| crate::Error::GetModuleHandleExW { source }, - || { - // Make sure no winapi calls as a result of drop happen inside this closure, because - // otherwise that might change the return value of the GetLastError. - let result = GetModuleHandleExW(0, wide_filename.as_ptr(), &mut handle); - if result == 0 { - None - } else { - Some(Library(handle)) - } - }, - ) - .map_err(|e| e.unwrap_or(crate::Error::GetModuleHandleExWUnknown)) - }; - - drop(wide_filename); // Drop wide_filename here to ensure it doesn’t get moved and dropped - // inside the closure by mistake. See comment inside the closure. - ret - } - - /// Get a module that is already loaded by the program. - /// - /// This function returns a `Library` corresponding to a module with the given name that is - /// already mapped into the address space of the process. If the module isn't found, an error is - /// returned. - /// - /// If the `filename` does not include a full path and there are multiple different loaded - /// modules corresponding to the `filename`, it is impossible to predict which module handle - /// will be returned. For more information refer to [MSDN]. - /// - /// If the `filename` specifies a library filename without a path and with the extension omitted, - /// the `.dll` extension is implicitly added. This behaviour may be suppressed by appending a - /// trailing `.` to the `filename`. - /// - /// This function will convert filename from its rust utf8 representation - /// to the utf16 w_char representation windows expects. - /// - /// This is equivalent to `GetModuleHandleExW(0, filename, _)`. - /// - /// [MSDN]: https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandleexw - pub fn open_already_loaded_utf8(filename: impl AsRef) -> Result { - let utf16: Vec = filename.as_ref().encode_utf16().chain(Some(0)).collect(); - Library::open_already_loaded_raw(utf16.as_slice()) - } - - /// Get a module that is already loaded by the program. - /// - /// This function returns a `Library` corresponding to a module with the given name that is - /// already mapped into the address space of the process. If the module isn't found, an error is - /// returned. - /// - /// If the `filename` does not include a full path and there are multiple different loaded - /// modules corresponding to the `filename`, it is impossible to predict which module handle - /// will be returned. For more information refer to [MSDN]. - /// - /// If the `filename` specifies a library filename without a path and with the extension omitted, - /// the `.dll` extension is implicitly added. This behaviour may be suppressed by appending a - /// trailing `.` to the `filename`. - /// - /// This function will check if the last element in the filename slice is 0. If it is then - /// the pointer of the slice is passed to windows. Otherwise, the function will copy - /// the slice and append a 0 element at the end. To prevent unnecessary copying it is recommended - /// to add the 0 element on the caller side. - /// - /// This is equivalent to `GetModuleHandleExW(0, filename, _)`. - /// - /// [MSDN]: https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandleexw - pub fn open_already_loaded_raw(filename: &[u16]) -> Result { - let mut temp_filename_vec = Vec::new(); - let mut raw_filename_ptr = filename.as_ptr(); - - //Poor mans 0 byte detection. - if filename.last() != Some(&0) { - temp_filename_vec.reserve_exact(filename.len() + 1); - temp_filename_vec.extend_from_slice(filename); - temp_filename_vec.push(0); - raw_filename_ptr = temp_filename_vec.as_ptr(); - } - - let ret = unsafe { - let mut handle: HMODULE = 0; - with_get_last_error( - |source| crate::Error::GetModuleHandleExW { source }, - || { - // Make sure no winapi calls as a result of drop happen inside this closure, because - // otherwise that might change the return value of the GetLastError. - let result = GetModuleHandleExW(0, raw_filename_ptr, &mut handle); - if result == 0 { - None - } else { - Some(Library(handle)) - } - }, - ) - .map_err(|e| e.unwrap_or(crate::Error::GetModuleHandleExWUnknown)) - }; - - drop(temp_filename_vec); // Drop temp_filename_vec here to ensure it doesn’t get moved and dropped - ret - } - - /// Find and load a module, additionally adjusting behaviour with flags. - /// - /// See [`Library::new`] for documentation on the handling of the `filename` argument. See the - /// [flag table on MSDN][flags] for information on applicable values for the `flags` argument. - /// - /// Corresponds to `LoadLibraryExW(filename, reserved: NULL, flags)`. - /// - /// [flags]: https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryexw#parameters - /// - /// # Safety - /// - /// When a library is loaded, initialisation routines contained within the library are executed. - /// For the purposes of safety, the execution of these routines is conceptually the same calling an - /// unknown foreign function and may impose arbitrary requirements on the caller for the call - /// to be sound. - /// - /// Additionally, the callers of this function must also ensure that execution of the - /// termination routines contained within the library is safe as well. These routines may be - /// executed when the library is unloaded. - #[cfg(feature = "std")] - pub unsafe fn load_with_flags>( - filename: P, - flags: LOAD_LIBRARY_FLAGS, - ) -> Result { - let wide_filename: Vec = filename.as_ref().encode_wide().chain(Some(0)).collect(); - let _guard = ErrorModeGuard::new(); - let ret = load_with_flags_raw(wide_filename.as_slice()); - drop(wide_filename); // Drop wide_filename here to ensure it doesn’t get moved and dropped - ret - } - - /// Find and load a module, additionally adjusting behaviour with flags. - /// - /// See [`Library::new`] for documentation on the handling of the `filename` argument. See the - /// [flag table on MSDN][flags] for information on applicable values for the `flags` argument. - /// - /// This function will convert filename from its rust utf8 representation - /// to the utf16 w_char representation windows expects. - /// - /// Corresponds to `LoadLibraryExW(filename, reserved: NULL, flags)`. - /// - /// [flags]: https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryexw#parameters - /// - /// # Safety - /// - /// When a library is loaded, initialisation routines contained within the library are executed. - /// For the purposes of safety, the execution of these routines is conceptually the same calling an - /// unknown foreign function and may impose arbitrary requirements on the caller for the call - /// to be sound. - /// - /// Additionally, the callers of this function must also ensure that execution of the - /// termination routines contained within the library is safe as well. These routines may be - /// executed when the library is unloaded. - pub unsafe fn load_with_flags_utf8( - filename: impl AsRef, - flags: LOAD_LIBRARY_FLAGS, - ) -> Result { - let utf16: Vec = filename.as_ref().encode_utf16().chain(Some(0)).collect(); - Library::load_with_flags_raw(utf16.as_slice(), flags) + pub fn open_already_loaded(filename: impl AsFilename) -> Result { + filename.windows_filename(|windows_filename| { + unsafe { + let mut handle: HMODULE = 0; + with_get_last_error( + |source| crate::Error::GetModuleHandleExW { source }, + || { + // Make sure no winapi calls as a result of drop happen inside this closure, because + // otherwise that might change the return value of the GetLastError. + let result = GetModuleHandleExW(0, windows_filename, &mut handle); + if result == 0 { + None + } else { + Some(Library(handle)) + } + }, + ) + .map_err(|e| e.unwrap_or(crate::Error::GetModuleHandleExWUnknown)) + } + }) } /// Find and load a module, additionally adjusting behaviour with flags. @@ -362,11 +141,6 @@ impl Library { /// See [`Library::new`] for documentation on the handling of the `filename` argument. See the /// [flag table on MSDN][flags] for information on applicable values for the `flags` argument. /// - /// This function will check if the last element in the filename slice is 0. If it is then - /// the pointer of the slice is passed to windows. Otherwise, the function will copy - /// the slice and append a 0 element at the end. To prevent unnecessary copying it is recommended - /// to add the 0 element on the caller side. - /// /// Corresponds to `LoadLibraryExW(filename, reserved: NULL, flags)`. /// /// [flags]: https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryexw#parameters @@ -381,42 +155,27 @@ impl Library { /// Additionally, the callers of this function must also ensure that execution of the /// termination routines contained within the library is safe as well. These routines may be /// executed when the library is unloaded. - pub unsafe fn load_with_flags_raw( - filename: &[u16], + pub unsafe fn load_with_flags( + filename: impl AsFilename, flags: LOAD_LIBRARY_FLAGS, ) -> Result { - let mut temp_filename_vec = Vec::new(); - let mut raw_filename_ptr = filename.as_ptr(); - - //Poor mans 0 byte detection. - if filename.last() != Some(&0) { - temp_filename_vec.reserve_exact(filename.len() + 1); - temp_filename_vec.extend_from_slice(filename); - temp_filename_vec.push(0); - raw_filename_ptr = temp_filename_vec.as_ptr(); - } - - let _guard = ErrorModeGuard::new(); - - let ret = with_get_last_error( - |source| crate::Error::LoadLibraryExW { source }, - || { - // Make sure no winapi calls as a result of drop happen inside this closure, because - // otherwise that might change the return value of the GetLastError. - let handle = LoadLibraryExW(raw_filename_ptr, 0, flags); - if handle == 0 { - None - } else { - Some(Library(handle)) - } - }, - ) - .map_err(|e| e.unwrap_or(crate::Error::LoadLibraryExWUnknown)); - - //Ensure this lives until the closure returns. - drop(temp_filename_vec); - - ret + filename.windows_filename(|windows_filename| { + let _guard = ErrorModeGuard::new(); + with_get_last_error( + |source| crate::Error::LoadLibraryExW { source }, + || { + // Make sure no winapi calls as a result of drop happen inside this closure, because + // otherwise that might change the return value of the GetLastError. + let handle = LoadLibraryExW(windows_filename, 0, flags); + if handle == 0 { + None + } else { + Some(Library(handle)) + } + }, + ) + .map_err(|e| e.unwrap_or(crate::Error::LoadLibraryExWUnknown)) + }) } /// Attempts to pin the module represented by the current `Library` into memory. @@ -468,24 +227,25 @@ impl Library { /// # Safety /// /// Users of this API must specify the correct type of the function or variable loaded. - pub unsafe fn get(&self, symbol: &[u8]) -> Result, crate::Error> { + pub unsafe fn get(&self, symbol: impl AsSymbolName) -> Result, crate::Error> { ensure_compatible_types::()?; - let symbol = cstr_cow_from_bytes(symbol)?; - with_get_last_error( - |source| crate::Error::GetProcAddress { source }, - || { - let symbol = GetProcAddress(self.0, symbol.as_ptr().cast()); - if symbol.is_none() { - None - } else { - Some(Symbol { - pointer: symbol, - pd: marker::PhantomData, - }) - } - }, - ) - .map_err(|e| e.unwrap_or(crate::Error::GetProcAddressUnknown)) + symbol.symbol_name(|windows_symbol| { + with_get_last_error( + |source| crate::Error::GetProcAddress { source }, + || { + let symbol = GetProcAddress(self.0, windows_symbol.cast()); + if symbol.is_none() { + None + } else { + Some(Symbol { + pointer: symbol, + pd: marker::PhantomData, + }) + } + }, + ) + .map_err(|e| e.unwrap_or(crate::Error::GetProcAddressUnknown)) + }) } /// Get a pointer to a function or static variable by ordinal number. @@ -573,13 +333,13 @@ impl fmt::Debug for Library { let mut buf = mem::MaybeUninit::<[mem::MaybeUninit; 1024]>::uninit().assume_init(); let len = GetModuleFileNameW(self.0, buf[..].as_mut_ptr().cast(), 1024) as usize; if len == 0 { - f.write_str(&format!("Library@{:#x}", self.0)) + f.write_fmt(format_args!("Library@{:#x}", self.0)) } else { - let string: std::os::OsString = std::os::OsString::from_wide( + let string: std::ffi::OsString = std::os::windows::ffi::OsStringExt::from_wide( // FIXME: use Maybeuninit::slice_get_ref when stable &*(&buf[..len] as *const [_] as *const [u16]), ); - f.write_str(&format!("Library@{:#x} from {:?}", self.0, string)) + f.write_fmt(format_args!("Library@{:#x} from {:?}", self.0, string)) } } } @@ -682,27 +442,6 @@ impl Drop for ErrorModeGuard { } } -#[cfg(feature = "std")] -fn with_get_last_error( - wrap: fn(crate::error::WindowsError) -> crate::Error, - closure: F, -) -> Result> -where - F: FnOnce() -> Option, -{ - closure().ok_or_else(|| { - let error = unsafe { GetLastError() }; - if error == 0 { - None - } else { - Some(wrap(crate::error::WindowsError( - std::io::Error::from_raw_os_error(error as i32), - ))) - } - }) -} - -#[cfg(not(feature = "std"))] fn with_get_last_error( wrap: fn(crate::error::WindowsError) -> crate::Error, closure: F, diff --git a/src/safe.rs b/src/safe.rs index 7cda4697e..409eaf96b 100644 --- a/src/safe.rs +++ b/src/safe.rs @@ -5,11 +5,11 @@ use super::os::unix as imp; #[cfg(all(not(libloading_docs), windows))] use super::os::windows as imp; use super::Error; +use as_filename::AsFilename; +use as_symbol_name::AsSymbolName; use core::fmt; use core::marker; use core::ops; -#[cfg(feature = "std")] -use std::ffi::OsStr; /// A loaded dynamic library. #[cfg_attr(libloading_docs, doc(cfg(any(unix, windows))))] @@ -81,22 +81,10 @@ impl Library { /// let _ = Library::new("libsomelib.so.1").unwrap(); /// } /// ``` - #[cfg(feature = "std")] - pub unsafe fn new>(filename: P) -> Result { + pub unsafe fn new(filename: impl AsFilename) -> Result { imp::Library::new(filename).map(From::from) } - /// This function is equivalent to calling new, except that it accepts a rust utf-8 string - /// as the filename. It will either use or convert this string into the appropriate - /// representation that the target requires to load the library. - /// - /// Since this fn does a conversion this function is likely slower than using the `new` function. - /// - /// All other aspects such as safety are identical to the `new` function. - pub unsafe fn new_utf8(filename: impl AsRef) -> Result { - imp::Library::new_utf8(filename).map(From::from) - } - /// Get a pointer to a function or static variable by symbol name. /// /// The `symbol` may not contain any null bytes, with the exception of the last byte. Providing a @@ -141,7 +129,7 @@ impl Library { /// # Library::new("/path/to/awesome.module").unwrap() /// # }; /// unsafe { - /// let awesome_function: Symbol f64> = + /// let awesome_function: Symbol f64> = /// lib.get(b"awesome_function\0").unwrap(); /// awesome_function(0.42); /// } @@ -157,7 +145,7 @@ impl Library { /// **awesome_variable = 42.0; /// }; /// ``` - pub unsafe fn get(&self, symbol: &[u8]) -> Result, Error> { + pub unsafe fn get(&self, symbol: impl AsSymbolName) -> Result, Error> { self.0.get(symbol).map(|from| Symbol::from_raw(from, self)) } diff --git a/tests/functions.rs b/tests/functions.rs index 5ebff6fed..98675aabb 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -5,6 +5,8 @@ extern crate libloading; use libloading::{Library, Symbol}; use std::os::raw::c_void; +use std::thread; +use std::time::Duration; const TARGET_DIR: Option<&'static str> = option_env!("CARGO_TARGET_DIR"); const TARGET_TMPDIR: Option<&'static str> = option_env!("CARGO_TARGET_TMPDIR"); @@ -28,6 +30,13 @@ fn lib_path_utf8() -> String { fn make_helpers() { static ONCE: ::std::sync::Once = ::std::sync::Once::new(); ONCE.call_once(|| { + if std::env::var_os("PRECOMPILED_TEST_HELPER").is_some() { + //I can't be asked to make rustc work in wine. + //I can call it myself from my linux host and then just move the file here this allows me to skip this. + eprintln!("WILL NOT COMPILE TEST HELPERS, PROGRAM WILL ASSUME THAT {} EXISTS AND WAS EXTERNALLY PRE COMPILED", lib_path().display()); + return; + } + let rustc = std::env::var_os("RUSTC").unwrap_or_else(|| "rustc".into()); let mut cmd = ::std::process::Command::new(rustc); cmd.arg("src/test_helpers.rs").arg("-o").arg(lib_path()); @@ -43,6 +52,26 @@ fn make_helpers() { }); } +#[cfg(not(windows))] +fn is_wine() -> bool { + false +} + +#[cfg(windows)] +fn is_wine() -> bool { + unsafe { + //This detects wine, the linux runtime for windows programs. + //Wine exposes the symbol wine_get_version in ntdll.dll; naturally, this symbol is absent on actual windows. + let lib = Library::new("ntdll.dll").expect("open library"); + let wine: Result i32>, _> = lib.get("wine_get_version"); + if wine.is_ok() { + return true; + } + } + + false +} + #[test] #[cfg(feature = "std")] fn test_id_u32() { @@ -58,7 +87,7 @@ fn test_id_u32() { fn test_id_u32_utf8() { make_helpers(); unsafe { - let lib = Library::new_utf8(lib_path_utf8()).unwrap(); + let lib = Library::new(lib_path_utf8()).unwrap(); let f: Symbol u32> = lib.get(b"test_identity_u32\0").unwrap(); assert_eq!(42, f(42)); } @@ -82,7 +111,7 @@ fn test_try_into_ptr() { fn test_try_into_ptr_utf8() { make_helpers(); unsafe { - let lib = Library::new_utf8(lib_path_utf8()).unwrap(); + let lib = Library::new(lib_path_utf8()).unwrap(); let f: Symbol u32> = lib.get(b"test_identity_u32\0").unwrap(); let ptr: *mut c_void = f.try_as_raw_ptr().unwrap(); assert!(!ptr.is_null()); @@ -128,7 +157,7 @@ fn test_id_struct() { fn test_id_struct_utf8() { make_helpers(); unsafe { - let lib = Library::new_utf8(lib_path_utf8()).unwrap(); + let lib = Library::new(lib_path_utf8()).unwrap(); let f: Symbol S> = lib.get(b"test_identity_struct\0").unwrap(); assert_eq!( S { @@ -166,7 +195,7 @@ fn test_0_no_0() { fn test_0_no_0_utf8() { make_helpers(); unsafe { - let lib = Library::new_utf8(lib_path_utf8()).unwrap(); + let lib = Library::new(lib_path_utf8()).unwrap(); let f: Symbol S> = lib.get(b"test_identity_struct\0").unwrap(); let f2: Symbol S> = lib.get(b"test_identity_struct").unwrap(); assert_eq!(*f, *f2); @@ -186,7 +215,7 @@ fn wrong_name_fails() { #[test] fn wrong_name_fails_utf8() { unsafe { - Library::new_utf8("target/this_location_is_definitely_non existent:^~") + Library::new("target/this_location_is_definitely_non existent:^~") .err() .unwrap(); } @@ -207,7 +236,7 @@ fn missing_symbol_fails() { fn missing_symbol_fails_utf8() { make_helpers(); unsafe { - let lib = Library::new_utf8(lib_path_utf8()).unwrap(); + let lib = Library::new(lib_path_utf8()).unwrap(); lib.get::<*mut ()>(b"test_does_not_exist").err().unwrap(); lib.get::<*mut ()>(b"test_does_not_exist\0").err().unwrap(); } @@ -230,7 +259,7 @@ fn interior_null_fails() { fn interior_null_fails_utf8() { make_helpers(); unsafe { - let lib = Library::new_utf8(lib_path_utf8()).unwrap(); + let lib = Library::new(lib_path_utf8()).unwrap(); lib.get::<*mut ()>(b"test_does\0_not_exist").err().unwrap(); lib.get::<*mut ()>(b"test\0_does_not_exist\0") .err() @@ -255,7 +284,7 @@ fn test_incompatible_type() { fn test_incompatible_type_utf8() { make_helpers(); unsafe { - let lib = Library::new_utf8(lib_path_utf8()).unwrap(); + let lib = Library::new(lib_path_utf8()).unwrap(); assert!(match lib.get::<()>(b"test_identity_u32\0") { Err(libloading::Error::IncompatibleSize) => true, _ => false, @@ -286,7 +315,7 @@ fn test_incompatible_type_named_fn_utf8() { l.get::(b"test_identity_u32\0") } unsafe { - let lib = Library::new_utf8(lib_path_utf8()).unwrap(); + let lib = Library::new(lib_path_utf8()).unwrap(); assert!(match get(&lib, test_incompatible_type_named_fn_utf8) { Err(libloading::Error::IncompatibleSize) => true, _ => false, @@ -312,7 +341,7 @@ fn test_static_u32() { fn test_static_u32_utf8() { make_helpers(); unsafe { - let lib = Library::new_utf8(lib_path_utf8()).unwrap(); + let lib = Library::new(lib_path_utf8()).unwrap(); let var: Symbol<*mut u32> = lib.get(b"TEST_STATIC_U32\0").unwrap(); **var = 42; let help: Symbol u32> = @@ -339,7 +368,7 @@ fn test_static_ptr() { fn test_static_ptr_utf8() { make_helpers(); unsafe { - let lib = Library::new_utf8(lib_path_utf8()).unwrap(); + let lib = Library::new(lib_path_utf8()).unwrap(); let var: Symbol<*mut *mut ()> = lib.get(b"TEST_STATIC_PTR\0").unwrap(); **var = *var as *mut _; let works: Symbol bool> = @@ -358,6 +387,13 @@ fn test_static_ptr_utf8() { #[cfg(not(target_os = "cygwin"))] #[cfg(feature = "std")] fn manual_close_many_times() { + if is_wine() { + // The wine runtime to run windows programs under linux + // will run out of thread local storage indices and fail this test. + eprintln!("DETECTED WINE RUNTIME, WILL SKIP THIS TEST"); + return; + } + make_helpers(); let join_handles: Vec<_> = (0..16) .map(|_| { @@ -382,12 +418,18 @@ fn manual_close_many_times() { // Cygwin returns errors on `close`. #[cfg(not(target_os = "cygwin"))] fn manual_close_many_times_utf8() { + if is_wine() { + //See above for why + eprintln!("DETECTED WINE RUNTIME, WILL SKIP THIS TEST"); + return; + } + make_helpers(); let join_handles: Vec<_> = (0..16) .map(|_| { std::thread::spawn(|| unsafe { for _ in 0..10000 { - let lib = Library::new_utf8(lib_path_utf8()).expect("open library"); + let lib = Library::new(lib_path_utf8()).expect("open library"); let _: Symbol u32> = lib.get(b"test_identity_u32").expect("get fn"); lib.close().expect("close is successful"); @@ -428,7 +470,7 @@ fn library_this_get_utf8() { make_helpers(); // SAFE: functions are never called unsafe { - let _lib = Library::new_utf8(lib_path_utf8()).unwrap(); + let _lib = Library::new(lib_path_utf8()).unwrap(); let this = Library::this(); // Library we loaded in `_lib` (should be RTLD_LOCAL). assert!(this @@ -468,7 +510,7 @@ fn library_this_utf8() { make_helpers(); unsafe { // SAFE: well-known library without initialisers is loaded. - let _lib = Library::new_utf8(lib_path_utf8()).unwrap(); + let _lib = Library::new(lib_path_utf8()).unwrap(); let this = Library::this().expect("this library"); // SAFE: functions are never called. // Library we loaded in `_lib`. @@ -502,7 +544,7 @@ fn works_getlasterror_utf8() { use windows_sys::Win32::Foundation::{GetLastError, SetLastError}; unsafe { - let lib = Library::new_utf8("kernel32.dll").unwrap(); + let lib = Library::new("kernel32.dll").unwrap(); let gle: Symbol u32> = lib.get(b"GetLastError").unwrap(); SetLastError(42); assert_eq!(GetLastError(), gle()) @@ -531,7 +573,7 @@ fn works_getlasterror0_utf8() { use windows_sys::Win32::Foundation::{GetLastError, SetLastError}; unsafe { - let lib = Library::new_utf8("kernel32.dll").unwrap(); + let lib = Library::new("kernel32.dll").unwrap(); let gle: Symbol u32> = lib.get(b"GetLastError\0").unwrap(); SetLastError(42); assert_eq!(GetLastError(), gle()) @@ -556,7 +598,7 @@ fn works_pin_module_utf8() { use libloading::os::windows::Library; unsafe { - let lib = Library::new_utf8("kernel32.dll").unwrap(); + let lib = Library::new("kernel32.dll").unwrap(); lib.pin().unwrap(); } } @@ -582,24 +624,3 @@ fn library_open_already_loaded() { assert!(Library::open_already_loaded(LIBPATH).is_ok()); } } - -#[cfg(windows)] -#[test] -fn library_open_already_loaded_utf8() { - use libloading::os::windows::Library; - - // Present on Windows systems and NOT used by any other tests to prevent races. - const LIBPATH: &str = "Msftedit.dll"; - - // Not loaded yet. - assert!(match Library::open_already_loaded_utf8(LIBPATH) { - Err(libloading::Error::GetModuleHandleExW { .. }) => true, - _ => false, - }); - - unsafe { - let _lib = Library::new_utf8(LIBPATH).unwrap(); - // Loaded now. - assert!(Library::open_already_loaded_utf8(LIBPATH).is_ok()); - } -} diff --git a/tests/library_filename.rs b/tests/library_filename.rs index dd5cd7e09..7029e0a72 100644 --- a/tests/library_filename.rs +++ b/tests/library_filename.rs @@ -1,20 +1,20 @@ extern crate libloading; - #[cfg(feature = "std")] -use libloading::library_filename; -use std::path::Path; +mod test { + use libloading::library_filename; + use std::path::Path; -#[cfg(any(target_os = "windows", target_os = "cygwin"))] -const EXPECTED: &str = "audioengine.dll"; -#[cfg(target_os = "linux")] -const EXPECTED: &str = "libaudioengine.so"; -#[cfg(target_os = "macos")] -const EXPECTED: &str = "libaudioengine.dylib"; + #[cfg(any(target_os = "windows", target_os = "cygwin"))] + const EXPECTED: &str = "audioengine.dll"; + #[cfg(target_os = "linux")] + const EXPECTED: &str = "libaudioengine.so"; + #[cfg(target_os = "macos")] + const EXPECTED: &str = "libaudioengine.dylib"; -#[test] -#[cfg(feature = "std")] -fn test_library_filename() { - let name = "audioengine"; - let resolved = library_filename(name); - assert!(Path::new(&resolved).ends_with(EXPECTED)); + #[test] + fn test_library_filename() { + let name = "audioengine"; + let resolved = library_filename(name); + assert!(Path::new(&resolved).ends_with(EXPECTED)); + } } diff --git a/tests/windows.rs b/tests/windows.rs index 534c9cf9e..13a414502 100644 --- a/tests/windows.rs +++ b/tests/windows.rs @@ -14,29 +14,15 @@ use std::os::raw::c_void; // The DLLs were kindly compiled by WindowsBunny (aka. @retep998). #[cfg(target_arch = "x86")] -#[cfg(feature = "std")] fn load_ordinal_lib() -> Library { unsafe { Library::new("tests/nagisa32.dll").expect("nagisa32.dll") } } #[cfg(target_arch = "x86_64")] -#[cfg(feature = "std")] fn load_ordinal_lib() -> Library { unsafe { Library::new("tests/nagisa64.dll").expect("nagisa64.dll") } } -#[cfg(target_arch = "x86")] -#[cfg(not(feature = "std"))] -fn load_ordinal_lib() -> Library { - unsafe { Library::new_utf8("tests/nagisa32.dll").expect("nagisa32.dll") } -} - -#[cfg(target_arch = "x86_64")] -#[cfg(not(feature = "std"))] -fn load_ordinal_lib() -> Library { - unsafe { Library::new_utf8("tests/nagisa64.dll").expect("nagisa64.dll") } -} - #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] #[test] fn test_ordinal() { @@ -71,7 +57,6 @@ fn test_ordinal_missing_fails() { } #[test] -#[cfg(feature = "std")] fn test_new_kernel23() { unsafe { Library::new("kernel23").err().unwrap(); @@ -79,23 +64,8 @@ fn test_new_kernel23() { } #[test] -fn test_new_kernel23_utf8() { - unsafe { - Library::new_utf8("kernel23").err().unwrap(); - } -} - -#[test] -#[cfg(feature = "std")] fn test_new_kernel32_no_ext() { unsafe { Library::new("kernel32").unwrap(); } } - -#[test] -fn test_new_kernel32_no_ext_utf8() { - unsafe { - Library::new_utf8("kernel32").unwrap(); - } -} From dadc1ad597612631111f565cd5cabc17016141e5 Mon Sep 17 00:00:00 2001 From: Alexander Schuetz Date: Tue, 4 Nov 2025 01:00:41 +0100 Subject: [PATCH 3/5] remove 2 left-over debug imports in a test --- tests/functions.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/functions.rs b/tests/functions.rs index 98675aabb..8a7a3cee4 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -5,8 +5,6 @@ extern crate libloading; use libloading::{Library, Symbol}; use std::os::raw::c_void; -use std::thread; -use std::time::Duration; const TARGET_DIR: Option<&'static str> = option_env!("CARGO_TARGET_DIR"); const TARGET_TMPDIR: Option<&'static str> = option_env!("CARGO_TARGET_TMPDIR"); From 049af8f239377d4def9da23073d53aafafdcff0b Mon Sep 17 00:00:00 2001 From: Alexander Schuetz Date: Tue, 4 Nov 2025 01:14:55 +0100 Subject: [PATCH 4/5] add missing documentation --- src/as_filename.rs | 22 ++++++++++++++++++++++ src/as_symbol_name.rs | 12 ++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/as_filename.rs b/src/as_filename.rs index d28702e92..cfec07935 100644 --- a/src/as_filename.rs +++ b/src/as_filename.rs @@ -8,12 +8,34 @@ use Error; mod private { pub trait AsFilenameSeal { + /// + /// This function is guaranteed to error or invoke the `FnOnce` parameter, + /// and if called, return whatever the `FnOnce` returns. + /// + /// The pointer parameter to the `FnOnce` is guaranteed to point to a valid "array" of + /// undefined size which is terminated by a single '0' u16. + /// + /// The data the pointer points to is guaranteed to live until the `FnOnce` returns. + /// + /// The data can be assumed to be formatted like the windows `LPCWSTR` if it is at all valid. + /// #[allow(unused)] //Posix doesnt use this fn windows_filename( &self, function: impl FnOnce(*const u16) -> Result, ) -> Result; + /// + /// This function is guaranteed to error or invoke the `FnOnce` parameter, + /// and if called, return whatever the `FnOnce` returns. + /// + /// The pointer parameter to the `FnOnce` is guaranteed to point to a valid 0 terminated + /// c-string. + /// + /// The data the pointer points to is guaranteed to live until the `FnOnce` returns. + /// + /// The data can be assumed to be 0 terminated utf-8. + /// #[allow(unused)] //Windows doesnt use this fn posix_filename( &self, diff --git a/src/as_symbol_name.rs b/src/as_symbol_name.rs index 069bccf50..fb9ce9a6c 100644 --- a/src/as_symbol_name.rs +++ b/src/as_symbol_name.rs @@ -6,6 +6,18 @@ use core::ffi::{c_char, CStr}; mod private { pub trait AsSymbolNameSeal { + + /// + /// This function is guaranteed to error or invoke the `FnOnce` parameter, + /// and if called, return whatever the `FnOnce` returns. + /// + /// The pointer parameter to the `FnOnce` is guaranteed to point to a valid 0 terminated + /// c-string. + /// + /// The data the pointer points to is guaranteed to live until the `FnOnce` returns. + /// + /// The data can be assumed to be 0 terminated utf-8 on unix or 0 terminated wtf-8 on windows. + /// fn symbol_name( &self, function: impl FnOnce(*const core::ffi::c_char) -> Result, From 4c4aaf22d164f5f28332aec307d165ce0790c735 Mon Sep 17 00:00:00 2001 From: Alexander Schuetz Date: Tue, 4 Nov 2025 18:04:44 +0100 Subject: [PATCH 5/5] make the name traits take self instead of ref to self. re-do some name implementations to use less utf-8/16. --- src/as_filename.rs | 284 ++++++++++++++++++++++++------------------ src/as_symbol_name.rs | 157 +++++++---------------- src/error.rs | 42 ++----- src/lib.rs | 13 +- src/util.rs | 53 ++++---- 5 files changed, 253 insertions(+), 296 deletions(-) diff --git a/src/as_filename.rs b/src/as_filename.rs index cfec07935..c6aba020d 100644 --- a/src/as_filename.rs +++ b/src/as_filename.rs @@ -1,9 +1,7 @@ +use crate::Error; use alloc::ffi::CString; use alloc::string::String; -use alloc::vec::Vec; -use core::ffi::{c_char, CStr}; -use util::cstr_cow_from_bytes; -use Error; +use core::ffi::CStr; mod private { @@ -13,15 +11,14 @@ mod private { /// and if called, return whatever the `FnOnce` returns. /// /// The pointer parameter to the `FnOnce` is guaranteed to point to a valid "array" of - /// undefined size which is terminated by a single '0' u16. + /// undefined size which is terminated by a single '0' u16 and guaranteed to not contain + /// interior '0' u16 elements. /// /// The data the pointer points to is guaranteed to live until the `FnOnce` returns. /// - /// The data can be assumed to be formatted like the windows `LPCWSTR` if it is at all valid. - /// - #[allow(unused)] //Posix doesnt use this + #[cfg(windows)] fn windows_filename( - &self, + self, function: impl FnOnce(*const u16) -> Result, ) -> Result; @@ -30,217 +27,254 @@ mod private { /// and if called, return whatever the `FnOnce` returns. /// /// The pointer parameter to the `FnOnce` is guaranteed to point to a valid 0 terminated - /// c-string. + /// c-string. The c-string is guaranteed to not contain interior '0' bytes. /// /// The data the pointer points to is guaranteed to live until the `FnOnce` returns. /// - /// The data can be assumed to be 0 terminated utf-8. - /// - #[allow(unused)] //Windows doesnt use this + #[cfg(unix)] fn posix_filename( - &self, + self, function: impl FnOnce(*const core::ffi::c_char) -> Result, ) -> Result; } } -/// This trait is implemented on all types where libloading can derrive a filename from. +/// This trait is implemented on all types where libloading can derive a filename from. /// It is sealed and cannot be implemented by a user of libloading. /// -/// This trait is implemented for the following common types: -/// - String &String &str -/// - CString &CString &CStr -/// - OsString &OsString &OsStr -/// - PathBuf &PathBuf &Path -/// - &[u8] assumes utf8 data! -/// - &[u16] assumes utf16-ne data! -/// pub trait AsFilename: private::AsFilenameSeal {} impl AsFilename for T where T: private::AsFilenameSeal {} impl private::AsFilenameSeal for &str { + #[cfg(windows)] fn windows_filename( - &self, + self, function: impl FnOnce(*const u16) -> Result, ) -> Result { - let mut utf16: Vec = self.encode_utf16().collect(); + let mut utf16: alloc::vec::Vec = self.encode_utf16().collect(); + + if let Some(position) = crate::util::find_interior_element(&utf16, 0) { + return Err(Error::InteriorZeroElements { position }); + } + if utf16.last() != Some(&0) { utf16.push(0); }; + function(utf16.as_ptr()) } + #[cfg(unix)] fn posix_filename( - &self, - function: impl FnOnce(*const c_char) -> Result, + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, ) -> Result { - let cow = cstr_cow_from_bytes(self.as_bytes())?; - function(cow.as_ptr()) + self.as_bytes().posix_filename(function) } } impl private::AsFilenameSeal for &String { + #[cfg(windows)] fn windows_filename( - &self, + self, function: impl FnOnce(*const u16) -> Result, ) -> Result { self.as_str().windows_filename(function) } + #[cfg(unix)] fn posix_filename( - &self, - function: impl FnOnce(*const c_char) -> Result, + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, ) -> Result { - let cow = cstr_cow_from_bytes(self.as_bytes())?; - function(cow.as_ptr()) + self.as_str().posix_filename(function) } } impl private::AsFilenameSeal for String { + #[cfg(windows)] fn windows_filename( - &self, + self, function: impl FnOnce(*const u16) -> Result, ) -> Result { self.as_str().windows_filename(function) } + #[cfg(unix)] fn posix_filename( - &self, - function: impl FnOnce(*const c_char) -> Result, + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, ) -> Result { - let cow = cstr_cow_from_bytes(self.as_bytes())?; - function(cow.as_ptr()) + let mut data = self.into_bytes(); + if let Some(position) = crate::util::find_interior_element(&data, 0) { + return Err(Error::InteriorZeroElements { position }); + } + + if data.last() != Some(&0) { + data.push(0); + } + + function(data.as_ptr().cast()) } } +/// The windows implementation requires that the &CStr points to a 0 terminated utf-8 string, +/// which is valid for calling [`Cstr::to_str`]. +/// +/// The unix implementation has no requirements beyond those which &CStr already guarantees. impl private::AsFilenameSeal for &CStr { + #[cfg(windows)] fn windows_filename( - &self, + self, function: impl FnOnce(*const u16) -> Result, ) -> Result { - //We assume cstr is utf-8 here, if it's something bespoke like CESU-8 (thanks java) then yeah... no. let utf8 = self.to_str()?; utf8.windows_filename(function) } + #[cfg(unix)] fn posix_filename( - &self, - function: impl FnOnce(*const c_char) -> Result, + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, ) -> Result { function(self.as_ptr()) } } impl private::AsFilenameSeal for &CString { + #[cfg(windows)] fn windows_filename( - &self, + self, function: impl FnOnce(*const u16) -> Result, ) -> Result { self.as_c_str().windows_filename(function) } + #[cfg(unix)] fn posix_filename( - &self, - function: impl FnOnce(*const c_char) -> Result, + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, ) -> Result { self.as_c_str().posix_filename(function) } } impl private::AsFilenameSeal for CString { + #[cfg(windows)] fn windows_filename( - &self, + self, function: impl FnOnce(*const u16) -> Result, ) -> Result { self.as_c_str().windows_filename(function) } + #[cfg(unix)] fn posix_filename( - &self, - function: impl FnOnce(*const c_char) -> Result, + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, ) -> Result { self.as_c_str().posix_filename(function) } } -/// This implementation assumes that a slice always contains utf-8 bytes. -/// (which is likely the most common case if the slice originated in rust) +/// For Windows the buffer must contain valid data to call [`core::str::from_utf8`]. +/// +/// For Unix there is no such requirement. +/// +/// Both implementations further require no interior 0 bytes. +/// impl private::AsFilenameSeal for &[u8] { + #[cfg(windows)] fn windows_filename( - &self, + self, function: impl FnOnce(*const u16) -> Result, ) -> Result { let utf8 = core::str::from_utf8(self)?; utf8.windows_filename(function) } + #[cfg(unix)] fn posix_filename( - &self, - function: impl FnOnce(*const c_char) -> Result, + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, ) -> Result { - let utf8 = core::str::from_utf8(self)?; - utf8.posix_filename(function) + if let Some(position) = crate::util::find_interior_element(self, 0) { + return Err(Error::InteriorZeroElements { position }); + } + + if self.last() != Some(&0) { + let copy = crate::util::copy_and_push(self, 0); + return function(copy.as_ptr().cast()); + } + + function(self.as_ptr().cast()) } } impl private::AsFilenameSeal for [u8; N] { + #[cfg(windows)] fn windows_filename( - &self, + self, function: impl FnOnce(*const u16) -> Result, ) -> Result { self.as_slice().windows_filename(function) } + #[cfg(unix)] fn posix_filename( - &self, - function: impl FnOnce(*const c_char) -> Result, + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, ) -> Result { self.as_slice().posix_filename(function) } } impl private::AsFilenameSeal for &[u8; N] { + #[cfg(windows)] fn windows_filename( - &self, + self, function: impl FnOnce(*const u16) -> Result, ) -> Result { self.as_slice().windows_filename(function) } + #[cfg(unix)] fn posix_filename( - &self, - function: impl FnOnce(*const c_char) -> Result, + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, ) -> Result { self.as_slice().posix_filename(function) } } -/// This implementation assumes that the slice contains utf-16 in native endian. -/// Sidenote: For windows this is always utf-16-le because the last big endian Windows system was the xbox 360 that rust doesn't support. -/// For linux this is highly likely to also be utf-16-le because big endian is only used in some old mips routers or some IBM hardware. +/// For Unix the buffer must contain valid data to call [`String::from_utf16`]. +/// +/// For Windows there is no such requirement. +/// +/// Both implementations require that the buffer contains no interior 0 elements/characters. impl private::AsFilenameSeal for &[u16] { + #[cfg(windows)] fn windows_filename( - &self, + self, function: impl FnOnce(*const u16) -> Result, ) -> Result { - //Check that we have valid utf-16 - for c in core::char::decode_utf16(self.iter().copied()) { - let _ = c?; + if let Some(position) = crate::util::find_interior_element(self, 0) { + return Err(Error::InteriorZeroElements { position }); } if self.last() != Some(&0) { - let mut copy = self.to_vec(); - copy.push(0); + let copy = crate::util::copy_and_push(self, 0); return function(copy.as_ptr()); } function(self.as_ptr()) } + #[cfg(unix)] fn posix_filename( - &self, - function: impl FnOnce(*const c_char) -> Result, + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, ) -> Result { let utf8 = String::from_utf16(self)?; utf8.posix_filename(function) @@ -250,127 +284,137 @@ impl private::AsFilenameSeal for &[u16] { #[cfg(feature = "std")] #[cfg(any(windows, unix))] mod std { + use crate::Error; use as_filename::private; - use core::ffi::c_char; use std::ffi::{OsStr, OsString}; - use Error; impl private::AsFilenameSeal for &OsStr { - #[cfg(unix)] - fn windows_filename( - &self, - _function: impl FnOnce(*const u16) -> Result, - ) -> Result { - panic!("windows_filename() not implemented for OsStr on posix platform"); - } - #[cfg(windows)] fn windows_filename( - &self, + self, function: impl FnOnce(*const u16) -> Result, ) -> Result { - let mut utf16: alloc::vec::Vec = - std::os::windows::ffi::OsStrExt::encode_wide(*self).collect(); - if utf16.last() != Some(&0) { - utf16.push(0); + let mut wide: alloc::vec::Vec = + std::os::windows::ffi::OsStrExt::encode_wide(self).collect(); + + if let Some(position) = crate::util::find_interior_element(&wide, 0) { + return Err(Error::InteriorZeroElements { position }); + } + + if wide.last() != Some(&0) { + wide.push(0); }; - function(utf16.as_ptr()) - } - #[cfg(unix)] - fn posix_filename( - &self, - function: impl FnOnce(*const c_char) -> Result, - ) -> Result { - let cow = - crate::util::cstr_cow_from_bytes(std::os::unix::ffi::OsStrExt::as_bytes(*self))?; - function(cow.as_ptr()) + function(wide.as_ptr()) } - #[cfg(windows)] + #[cfg(unix)] fn posix_filename( - &self, - _function: impl FnOnce(*const c_char) -> Result, + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, ) -> Result { - panic!("posix_filename() not implemented for OsStr on windows") + std::os::unix::ffi::OsStrExt::as_bytes(self).posix_filename(function) } } impl private::AsFilenameSeal for &OsString { + #[cfg(windows)] fn windows_filename( - &self, + self, function: impl FnOnce(*const u16) -> Result, ) -> Result { self.as_os_str().windows_filename(function) } + #[cfg(unix)] fn posix_filename( - &self, - function: impl FnOnce(*const c_char) -> Result, + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, ) -> Result { self.as_os_str().posix_filename(function) } } impl private::AsFilenameSeal for OsString { + #[cfg(windows)] fn windows_filename( - &self, + self, function: impl FnOnce(*const u16) -> Result, ) -> Result { + // This is the best we can do. + // There is no into_wide for windows. + // The internal repr is wtf-8 and this is different + // from LCPWSTR that we need for the ffi calls. self.as_os_str().windows_filename(function) } + #[cfg(unix)] fn posix_filename( - &self, - function: impl FnOnce(*const c_char) -> Result, + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, ) -> Result { - self.as_os_str().posix_filename(function) + let mut data = std::os::unix::ffi::OsStringExt::into_vec(self); + if let Some(position) = crate::util::find_interior_element(&data, 0) { + return Err(Error::InteriorZeroElements { position }); + } + + if data.last() != Some(&0) { + data.push(0); + } + + function(data.as_ptr().cast()) } } impl private::AsFilenameSeal for std::path::PathBuf { + #[cfg(windows)] fn windows_filename( - &self, + self, function: impl FnOnce(*const u16) -> Result, ) -> Result { - self.as_os_str().windows_filename(function) + self.into_os_string().windows_filename(function) } + #[cfg(unix)] fn posix_filename( - &self, - function: impl FnOnce(*const c_char) -> Result, + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, ) -> Result { - self.as_os_str().posix_filename(function) + self.into_os_string().posix_filename(function) } } impl private::AsFilenameSeal for &std::path::PathBuf { + #[cfg(windows)] fn windows_filename( - &self, + self, function: impl FnOnce(*const u16) -> Result, ) -> Result { self.as_os_str().windows_filename(function) } + #[cfg(unix)] fn posix_filename( - &self, - function: impl FnOnce(*const c_char) -> Result, + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, ) -> Result { self.as_os_str().posix_filename(function) } } impl private::AsFilenameSeal for &std::path::Path { + #[cfg(windows)] fn windows_filename( - &self, + self, function: impl FnOnce(*const u16) -> Result, ) -> Result { self.as_os_str().windows_filename(function) } + #[cfg(unix)] fn posix_filename( - &self, - function: impl FnOnce(*const c_char) -> Result, + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, ) -> Result { self.as_os_str().posix_filename(function) } diff --git a/src/as_symbol_name.rs b/src/as_symbol_name.rs index fb9ce9a6c..4a6947f62 100644 --- a/src/as_symbol_name.rs +++ b/src/as_symbol_name.rs @@ -1,14 +1,13 @@ -use crate::{util, Error}; +use crate::Error; use alloc::ffi::CString; use alloc::string::String; -use core::ffi::{c_char, CStr}; +use core::ffi::CStr; mod private { pub trait AsSymbolNameSeal { - /// - /// This function is guaranteed to error or invoke the `FnOnce` parameter, + /// This function is guaranteed to error or invoke the `FnOnce` parameter, /// and if called, return whatever the `FnOnce` returns. /// /// The pointer parameter to the `FnOnce` is guaranteed to point to a valid 0 terminated @@ -16,34 +15,24 @@ mod private { /// /// The data the pointer points to is guaranteed to live until the `FnOnce` returns. /// - /// The data can be assumed to be 0 terminated utf-8 on unix or 0 terminated wtf-8 on windows. - /// fn symbol_name( - &self, + self, function: impl FnOnce(*const core::ffi::c_char) -> Result, ) -> Result; } } -/// This trait is implemented on all types where libloading can derrive a symbol name from. +/// This trait is implemented on all types where libloading can derive a symbol name from. /// It is sealed and cannot be implemented by a user of libloading. /// -/// This trait is implemented for the following common types: -/// - String &String &str -/// - CString &CString &CStr -/// - OsString &OsString &OsStr -/// - PathBuf &PathBuf &Path -/// - &[u8] assumes utf8 data! -/// - &[u16] assumes utf16-ne data! -/// pub trait AsSymbolName: private::AsSymbolNameSeal {} impl AsSymbolName for T where T: private::AsSymbolNameSeal {} impl private::AsSymbolNameSeal for &str { fn symbol_name( - &self, - function: impl FnOnce(*const c_char) -> Result, + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, ) -> Result { self.as_bytes().symbol_name(function) } @@ -51,8 +40,8 @@ impl private::AsSymbolNameSeal for &str { impl private::AsSymbolNameSeal for &String { fn symbol_name( - &self, - function: impl FnOnce(*const c_char) -> Result, + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, ) -> Result { self.as_str().symbol_name(function) } @@ -60,17 +49,26 @@ impl private::AsSymbolNameSeal for &String { impl private::AsSymbolNameSeal for String { fn symbol_name( - &self, - function: impl FnOnce(*const c_char) -> Result, + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, ) -> Result { - self.as_str().symbol_name(function) + let mut data = self.into_bytes(); + if let Some(position) = crate::util::find_interior_element(&data, 0) { + return Err(Error::InteriorZeroElements { position }); + } + + if data.last() != Some(&0) { + data.push(0); + } + + function(data.as_ptr().cast()) } } impl private::AsSymbolNameSeal for &CStr { fn symbol_name( - &self, - function: impl FnOnce(*const c_char) -> Result, + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, ) -> Result { function(self.as_ptr()) } @@ -78,8 +76,8 @@ impl private::AsSymbolNameSeal for &CStr { impl private::AsSymbolNameSeal for &CString { fn symbol_name( - &self, - function: impl FnOnce(*const c_char) -> Result, + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, ) -> Result { function(self.as_ptr()) } @@ -87,8 +85,8 @@ impl private::AsSymbolNameSeal for &CString { impl private::AsSymbolNameSeal for CString { fn symbol_name( - &self, - function: impl FnOnce(*const c_char) -> Result, + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, ) -> Result { function(self.as_ptr()) } @@ -96,18 +94,26 @@ impl private::AsSymbolNameSeal for CString { impl private::AsSymbolNameSeal for &[u8] { fn symbol_name( - &self, - function: impl FnOnce(*const c_char) -> Result, + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, ) -> Result { - let cow = util::cstr_cow_from_bytes(self)?; - function(cow.as_ptr()) + if let Some(position) = crate::util::find_interior_element(self, 0) { + return Err(Error::InteriorZeroElements { position }); + } + + if self.last() != Some(&0) { + let copy = crate::util::copy_and_push(self, 0); + return function(copy.as_ptr().cast()); + } + + function(self.as_ptr().cast()) } } impl private::AsSymbolNameSeal for [u8; N] { fn symbol_name( - &self, - function: impl FnOnce(*const c_char) -> Result, + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, ) -> Result { self.as_slice().symbol_name(function) } @@ -115,91 +121,20 @@ impl private::AsSymbolNameSeal for [u8; N] { impl private::AsSymbolNameSeal for &[u8; N] { fn symbol_name( - &self, - function: impl FnOnce(*const c_char) -> Result, + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, ) -> Result { self.as_slice().symbol_name(function) } } +/// This implementation requires that the buffer contains valid data to call [`String::from_utf16`]. impl private::AsSymbolNameSeal for &[u16] { fn symbol_name( - &self, - function: impl FnOnce(*const c_char) -> Result, + self, + function: impl FnOnce(*const core::ffi::c_char) -> Result, ) -> Result { let string = String::from_utf16(self)?; string.symbol_name(function) } } - -#[cfg(feature = "std")] -#[cfg(any(windows, unix))] -mod std { - use as_symbol_name::private; - use std::ffi::{c_char, OsStr, OsString}; - use std::path::{Path, PathBuf}; - use Error; - - impl private::AsSymbolNameSeal for &OsStr { - #[cfg(unix)] - fn symbol_name( - &self, - function: impl FnOnce(*const c_char) -> Result, - ) -> Result { - std::os::unix::ffi::OsStrExt::as_bytes(*self).symbol_name(function) - } - - #[cfg(windows)] - fn symbol_name( - &self, - function: impl FnOnce(*const c_char) -> Result, - ) -> Result { - self.as_encoded_bytes().symbol_name(function) - } - } - - impl private::AsSymbolNameSeal for &OsString { - fn symbol_name( - &self, - function: impl FnOnce(*const c_char) -> Result, - ) -> Result { - self.as_os_str().symbol_name(function) - } - } - - impl private::AsSymbolNameSeal for OsString { - fn symbol_name( - &self, - function: impl FnOnce(*const c_char) -> Result, - ) -> Result { - self.as_os_str().symbol_name(function) - } - } - - impl private::AsSymbolNameSeal for PathBuf { - fn symbol_name( - &self, - function: impl FnOnce(*const c_char) -> Result, - ) -> Result { - self.as_os_str().symbol_name(function) - } - } - - impl private::AsSymbolNameSeal for &PathBuf { - fn symbol_name( - &self, - function: impl FnOnce(*const c_char) -> Result, - ) -> Result { - self.as_os_str().symbol_name(function) - } - } - - impl private::AsSymbolNameSeal for &Path { - fn symbol_name( - &self, - function: impl FnOnce(*const c_char) -> Result, - ) -> Result { - self.as_os_str().symbol_name(function) - } - } -} diff --git a/src/error.rs b/src/error.rs index ed37c227b..eb9ae99d3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,6 +1,5 @@ use alloc::ffi::CString; use alloc::string::FromUtf16Error; -use core::char::DecodeUtf16Error; use core::ffi::CStr; use core::str::Utf8Error; @@ -91,30 +90,20 @@ pub enum Error { FreeLibraryUnknown, /// The requested type cannot possibly work. IncompatibleSize, - /// Could not create a new CString. - CreateCString { - /// The source error. - source: alloc::ffi::NulError, - }, /// Could not parse some sequence of bytes as utf-8. Utf8Error { /// The source error. source: Utf8Error, }, /// Could not parse some sequence of bytes as utf-16. - DecodeUtf16Error { - ///The source error. - source: DecodeUtf16Error, - }, - /// Could not parse some sequence of bytes as utf-16. FromUtf16Error { ///The source error. source: FromUtf16Error, }, - /// Could not create a new CString from bytes with trailing null. - CreateCStringWithTrailing { - /// The source error. - source: core::ffi::FromBytesWithNulError, + /// The data contained interior 0/null elements. + InteriorZeroElements { + /// The position of the interior element which was 0/null. + position: usize, }, } @@ -124,12 +113,6 @@ impl From for Error { } } -impl From for Error { - fn from(value: DecodeUtf16Error) -> Self { - Self::DecodeUtf16Error { source: value } - } -} - impl From for Error { fn from(value: FromUtf16Error) -> Self { Self::FromUtf16Error { source: value } @@ -140,8 +123,8 @@ impl core::error::Error for Error { fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { use Error::*; match *self { - CreateCString { ref source } => Some(source), - CreateCStringWithTrailing { ref source } => Some(source), + FromUtf16Error { ref source } => Some(source), + Utf8Error { ref source } => Some(source), _ => None, } } @@ -176,18 +159,9 @@ impl core::fmt::Display for Error { FreeLibraryUnknown => { write!(f, "FreeLibrary failed, but system did not report the error") } - CreateCString { .. } => write!(f, "could not create a C string from bytes"), - - CreateCStringWithTrailing { .. } => write!( - f, - "could not create a C string from bytes with trailing null" - ), - Utf8Error { .. } => write!( - f, - "could not create a C string from bytes with trailing null" - ), - DecodeUtf16Error { .. } => write!(f, "could not decode some bytes as utf16"), + Utf8Error { .. } => write!(f, "could not parse bytes as utf-8"), FromUtf16Error { .. } => write!(f, "could not parse some utf16 bytes to a string"), + InteriorZeroElements { .. } => write!(f, "interior zero element in parameter"), IncompatibleSize => write!(f, "requested type cannot possibly work"), } } diff --git a/src/lib.rs b/src/lib.rs index ea1b9ab07..c289cbc9b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -60,14 +60,10 @@ mod safe; mod util; pub use self::error::Error; + #[cfg(any(unix, windows, libloading_docs))] pub use self::safe::{Library, Symbol}; -#[cfg(feature = "std")] -use std::env::consts::{DLL_PREFIX, DLL_SUFFIX}; -#[cfg(feature = "std")] -use std::ffi::{OsStr, OsString}; - /// Converts a library name to a filename generally appropriate for use on the system. /// /// This function will prepend prefixes (such as `lib`) and suffixes (such as `.so`) to the library @@ -86,9 +82,12 @@ use std::ffi::{OsStr, OsString}; /// }; /// ``` #[cfg(feature = "std")] -pub fn library_filename>(name: S) -> OsString { +pub fn library_filename>(name: S) -> std::ffi::OsString { + use std::env::consts::{DLL_PREFIX, DLL_SUFFIX}; + let name = name.as_ref(); - let mut string = OsString::with_capacity(name.len() + DLL_PREFIX.len() + DLL_SUFFIX.len()); + let mut string = + std::ffi::OsString::with_capacity(name.len() + DLL_PREFIX.len() + DLL_SUFFIX.len()); string.push(DLL_PREFIX); string.push(name); string.push(DLL_SUFFIX); diff --git a/src/util.rs b/src/util.rs index 7cb348c22..e53e9c9ab 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,29 +1,5 @@ -use alloc::borrow::Cow; -use alloc::ffi::CString; -use core::ffi::CStr; - use crate::Error; -/// Checks for the last byte and avoids allocating if it is zero. -/// -/// Non-last null bytes still result in an error. -pub(crate) fn cstr_cow_from_bytes(slice: &[u8]) -> Result, Error> { - static ZERO: core::ffi::c_char = 0; - Ok(match slice.last() { - // Slice out of 0 elements - None => unsafe { Cow::Borrowed(CStr::from_ptr(&ZERO)) }, - // Slice with trailing 0 - Some(&0) => Cow::Borrowed( - CStr::from_bytes_with_nul(slice) - .map_err(|source| Error::CreateCStringWithTrailing { source })?, - ), - // Slice with no trailing 0 - Some(_) => { - Cow::Owned(CString::new(slice).map_err(|source| Error::CreateCString { source })?) - } - }) -} - #[inline] pub(crate) fn ensure_compatible_types() -> Result<(), Error> { if size_of::() != size_of::() { @@ -32,3 +8,32 @@ pub(crate) fn ensure_compatible_types() -> Result<(), Error> { Ok(()) } } + +/// This function finds the interior index of a given element in a slice. +/// It returns the index or none if it can't find it. +/// Note: Interior means anything except the last element in the slice. +/// On empty slices this function returns None. +pub(crate) fn find_interior_element(data: &[T], to_find: T) -> Option { + for (position, element) in data + .iter() + .take(data.len().saturating_sub(1)) + .copied() + .enumerate() + { + if element == to_find { + return Some(position); + } + } + + None +} + +/// This function copies the slice into a vec and appends an element to its end. +/// The vec is allocated with reserve_exact. +pub(crate) fn copy_and_push(data: &[T], to_push: T) -> alloc::vec::Vec { + let mut copy = alloc::vec::Vec::new(); + copy.reserve_exact(data.len() + 1); + copy.extend_from_slice(data); + copy.push(to_push); + copy +}