1use 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 pub static ref LIB_PREFIX: String = DLL_PREFIX.to_string();
31 pub static ref LIB_SUFFIX: String = DLL_SUFFIX.to_string();
33}
34
35#[derive(Clone, Debug)]
37pub struct LibLoader {
38 search_paths: Option<Vec<PathBuf>>,
39}
40
41impl LibLoader {
42 pub fn empty() -> LibLoader {
44 LibLoader { search_paths: None }
45 }
46
47 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 pub fn search_paths(&self) -> Option<&[PathBuf]> {
67 self.search_paths.as_deref()
68 }
69
70 pub unsafe fn load_file(path: &str) -> ZResult<(Library, PathBuf)> {
77 let path = Self::str_to_canonical_path(path)?;
78
79 if !path.exists() {
80 bail!("Library file '{}' doesn't exist", path.display())
81 } else if !path.is_file() {
82 bail!("Library file '{}' is not a file", path.display())
83 } else {
84 Ok((Library::new(path.clone())?, path))
85 }
86 }
87
88 pub unsafe fn search_and_load(&self, name: &str) -> ZResult<Option<(Library, PathBuf)>> {
98 let filename = format!("{}{}{}", *LIB_PREFIX, name, *LIB_SUFFIX);
99 let filename_ostr = OsString::from(&filename);
100 tracing::debug!(
101 "Search for library {} to load in {:?}",
102 filename,
103 self.search_paths
104 );
105 let Some(search_paths) = self.search_paths() else {
106 return Ok(None);
107 };
108 for dir in search_paths {
109 match dir.read_dir() {
110 Ok(read_dir) => {
111 for entry in read_dir.flatten() {
112 if entry.file_name() == filename_ostr {
113 let path = entry.path();
114 return Ok(Some((Library::new(path.clone())?, path)));
115 }
116 }
117 }
118 Err(err) => debug!(
119 "Failed to read in directory {:?} ({}). Can't use it to search for libraries.",
120 dir, err
121 ),
122 }
123 }
124 Err(zerror!("Library file '{}' not found", filename).into())
125 }
126
127 pub unsafe fn load_all_with_prefix(
138 &self,
139 prefix: Option<&str>,
140 ) -> Option<Vec<(Library, PathBuf, String)>> {
141 let lib_prefix = format!("{}{}", *LIB_PREFIX, prefix.unwrap_or(""));
142 tracing::debug!(
143 "Search for libraries {}*{} to load in {:?}",
144 lib_prefix,
145 *LIB_SUFFIX,
146 self.search_paths
147 );
148 let mut result = vec![];
149 for dir in self.search_paths()? {
150 match dir.read_dir() {
151 Ok(read_dir) => {
152 for entry in read_dir.flatten() {
153 if let Ok(filename) = entry.file_name().into_string() {
154 if filename.starts_with(&lib_prefix) && filename.ends_with(&*LIB_SUFFIX)
155 {
156 let name = &filename
157 [(lib_prefix.len())..(filename.len() - LIB_SUFFIX.len())];
158 let path = entry.path();
159 if !result.iter().any(|(_, _, n)| n == name) {
160 match Library::new(path.as_os_str()) {
161 Ok(lib) => result.push((lib, path, name.to_string())),
162 Err(err) => warn!("{}", err),
163 }
164 } else {
165 debug!(
166 "Do not load plugin {} from {:?}: already loaded.",
167 name, path
168 );
169 }
170 }
171 }
172 }
173 }
174 Err(err) => debug!(
175 "Failed to read in directory {:?} ({}). Can't use it to search for libraries.",
176 dir, err
177 ),
178 }
179 }
180 Some(result)
181 }
182
183 pub fn _plugin_name(path: &std::path::Path) -> Option<&str> {
184 path.file_name().and_then(|f| {
185 f.to_str().map(|s| {
186 let start = if s.starts_with(LIB_PREFIX.as_str()) {
187 LIB_PREFIX.len()
188 } else {
189 0
190 };
191 let end = s.len()
192 - if s.ends_with(LIB_SUFFIX.as_str()) {
193 LIB_SUFFIX.len()
194 } else {
195 0
196 };
197 &s[start..end]
198 })
199 })
200 }
201 pub fn plugin_name<P>(path: &P) -> Option<&str>
202 where
203 P: AsRef<std::path::Path>,
204 {
205 Self::_plugin_name(path.as_ref())
206 }
207
208 fn str_to_canonical_path(s: &str) -> ZResult<PathBuf> {
209 let cow_str = shellexpand::full(s)?;
210 Ok(PathBuf::from(cow_str.deref()).canonicalize()?)
211 }
212}
213
214impl Default for LibLoader {
215 fn default() -> Self {
216 LibLoader::new(LibSearchDirs::default())
217 }
218}