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    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    /// Search for library with filename: [struct@LIB_PREFIX]+`name`+[struct@LIB_SUFFIX] and load it.
89    /// The result is a tuple with:
90    ///    * the [Library]
91    ///    * its full path
92    ///
93    /// # Safety
94    ///
95    /// This function calls [libloading::Library::new()](https://docs.rs/libloading/0.7.0/libloading/struct.Library.html#method.new)
96    /// which is unsafe.
97    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    /// Search and load all libraries with filename starting with [struct@LIB_PREFIX]+`prefix` and ending with [struct@LIB_SUFFIX].
128    /// The result is a list of tuple with:
129    ///    * the [Library]
130    ///    * its full path
131    ///    * its short name (i.e. filename stripped of prefix and suffix)
132    ///
133    /// # Safety
134    ///
135    /// This function calls [libloading::Library::new()](https://docs.rs/libloading/0.7.0/libloading/struct.Library.html#method.new)
136    /// which is unsafe.
137    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}