Skip to main content

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}