zenoh_util/lib_loader.rs
1//
2// Copyright (c) 2023 ZettaScale Technology
3//
4// This program and the accompanying materials are made available under the
5// terms of the Eclipse Public License 2.0 which is available at
6// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
7// which is available at https://www.apache.org/licenses/LICENSE-2.0.
8//
9// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
10//
11// Contributors:
12// ZettaScale Zenoh Team, <zenoh@zettascale.tech>
13//
14use std::{
15 env::consts::{DLL_PREFIX, DLL_SUFFIX},
16 ffi::OsString,
17 ops::Deref,
18 path::PathBuf,
19};
20
21use libloading::Library;
22use tracing::{debug, warn};
23use zenoh_core::{zconfigurable, zerror};
24use zenoh_result::{bail, ZResult};
25
26use crate::LibSearchDirs;
27
28zconfigurable! {
29 /// The libraries prefix for the current platform (usually: `"lib"`)
30 pub static ref LIB_PREFIX: String = DLL_PREFIX.to_string();
31 /// The libraries suffix for the current platform (`".dll"` or `".so"` or `".dylib"`...)
32 pub static ref LIB_SUFFIX: String = DLL_SUFFIX.to_string();
33}
34
35/// LibLoader allows search for libraries and to load them.
36#[derive(Clone, Debug)]
37pub struct LibLoader {
38 search_paths: Option<Vec<PathBuf>>,
39}
40
41impl LibLoader {
42 /// Return an empty `LibLoader`.
43 pub fn empty() -> LibLoader {
44 LibLoader { search_paths: None }
45 }
46
47 /// Creates a new [LibLoader] with a set of paths where the libraries will be searched for.
48 /// If `exe_parent_dir`is true, the parent directory of the current executable is also added
49 /// to the set of paths for search.
50 pub fn new(dirs: LibSearchDirs) -> LibLoader {
51 let mut search_paths = Vec::new();
52
53 for path in dirs.into_iter() {
54 match path {
55 Ok(path) => search_paths.push(path),
56 Err(err) => tracing::error!("{err}"),
57 }
58 }
59
60 LibLoader {
61 search_paths: Some(search_paths),
62 }
63 }
64
65 /// Return the list of search paths used by this [LibLoader]
66 pub fn search_paths(&self) -> Option<&[PathBuf]> {
67 self.search_paths.as_deref()
68 }
69
70 /// Load a library from the specified path.
71 ///
72 /// # Safety
73 ///
74 /// This function calls [libloading::Library::new()](https://docs.rs/libloading/0.7.0/libloading/struct.Library.html#method.new)
75 /// which is unsafe.
76 /// The library should be valid, or it might cause the undefined behavior.
77 pub unsafe fn load_file(path: &str) -> ZResult<(Library, PathBuf)> {
78 let path = Self::str_to_canonical_path(path)?;
79
80 if !path.exists() {
81 bail!("Library file '{}' doesn't exist", path.display())
82 } else if !path.is_file() {
83 bail!("Library file '{}' is not a file", path.display())
84 } else {
85 // SAFETY: Call unsafe `libloading::Library::new()`.
86 unsafe { Ok((Library::new(path.clone())?, path)) }
87 }
88 }
89
90 /// Search for library with filename: [struct@LIB_PREFIX]+`name`+[struct@LIB_SUFFIX] and load it.
91 /// The result is a tuple with:
92 /// * the [Library]
93 /// * its full path
94 ///
95 /// # Safety
96 ///
97 /// This function calls [libloading::Library::new()](https://docs.rs/libloading/0.7.0/libloading/struct.Library.html#method.new)
98 /// which is unsafe.
99 /// The library should be valid, or it might cause the undefined behavior.
100 pub unsafe fn search_and_load(&self, name: &str) -> ZResult<Option<(Library, PathBuf)>> {
101 let filename = format!("{}{}{}", *LIB_PREFIX, name, *LIB_SUFFIX);
102 let filename_ostr = OsString::from(&filename);
103 tracing::debug!(
104 "Search for library {} to load in {:?}",
105 filename,
106 self.search_paths
107 );
108 let Some(search_paths) = self.search_paths() else {
109 return Ok(None);
110 };
111 for dir in search_paths {
112 match dir.read_dir() {
113 Ok(read_dir) => {
114 for entry in read_dir.flatten() {
115 if entry.file_name() == filename_ostr {
116 let path = entry.path();
117 // SAFETY: Call unsafe `libloading::Library::new()`.
118 return unsafe { Ok(Some((Library::new(path.clone())?, path))) };
119 }
120 }
121 }
122 Err(err) => debug!(
123 "Failed to read in directory {:?} ({}). Can't use it to search for libraries.",
124 dir, err
125 ),
126 }
127 }
128 Err(zerror!("Library file '{}' not found", filename).into())
129 }
130
131 /// Search and load all libraries with filename starting with [struct@LIB_PREFIX]+`prefix` and ending with [struct@LIB_SUFFIX].
132 /// The result is a list of tuple with:
133 /// * the [Library]
134 /// * its full path
135 /// * its short name (i.e. filename stripped of prefix and suffix)
136 ///
137 /// # Safety
138 ///
139 /// This function calls [libloading::Library::new()](https://docs.rs/libloading/0.7.0/libloading/struct.Library.html#method.new)
140 /// which is unsafe.
141 /// The library should be valid, or it might cause the undefined behavior.
142 pub unsafe fn load_all_with_prefix(
143 &self,
144 prefix: Option<&str>,
145 ) -> Option<Vec<(Library, PathBuf, String)>> {
146 let lib_prefix = format!("{}{}", *LIB_PREFIX, prefix.unwrap_or(""));
147 tracing::debug!(
148 "Search for libraries {}*{} to load in {:?}",
149 lib_prefix,
150 *LIB_SUFFIX,
151 self.search_paths
152 );
153 let mut result = vec![];
154 for dir in self.search_paths()? {
155 match dir.read_dir() {
156 Ok(read_dir) => {
157 for entry in read_dir.flatten() {
158 if let Ok(filename) = entry.file_name().into_string() {
159 if filename.starts_with(&lib_prefix) && filename.ends_with(&*LIB_SUFFIX)
160 {
161 let name = &filename
162 [(lib_prefix.len())..(filename.len() - LIB_SUFFIX.len())];
163 let path = entry.path();
164 if !result.iter().any(|(_, _, n)| n == name) {
165 // SAFETY: Call unsafe `libloading::Library::new()`.
166 unsafe {
167 match Library::new(path.as_os_str()) {
168 Ok(lib) => result.push((lib, path, name.to_string())),
169 Err(err) => warn!("{}", err),
170 }
171 }
172 } else {
173 debug!(
174 "Do not load plugin {} from {:?}: already loaded.",
175 name, path
176 );
177 }
178 }
179 }
180 }
181 }
182 Err(err) => debug!(
183 "Failed to read in directory {:?} ({}). Can't use it to search for libraries.",
184 dir, err
185 ),
186 }
187 }
188 Some(result)
189 }
190
191 pub fn _plugin_name(path: &std::path::Path) -> Option<&str> {
192 path.file_name().and_then(|f| {
193 f.to_str().map(|s| {
194 let start = if s.starts_with(LIB_PREFIX.as_str()) {
195 LIB_PREFIX.len()
196 } else {
197 0
198 };
199 let end = s.len()
200 - if s.ends_with(LIB_SUFFIX.as_str()) {
201 LIB_SUFFIX.len()
202 } else {
203 0
204 };
205 &s[start..end]
206 })
207 })
208 }
209 pub fn plugin_name<P>(path: &P) -> Option<&str>
210 where
211 P: AsRef<std::path::Path>,
212 {
213 Self::_plugin_name(path.as_ref())
214 }
215
216 fn str_to_canonical_path(s: &str) -> ZResult<PathBuf> {
217 let cow_str = shellexpand::full(s)?;
218 Ok(PathBuf::from(cow_str.deref()).canonicalize()?)
219 }
220}
221
222impl Default for LibLoader {
223 fn default() -> Self {
224 LibLoader::new(LibSearchDirs::default())
225 }
226}