wasmer_wasix/state/linker/
locator.rs

1use std::{
2    borrow::Cow,
3    ffi::OsStr,
4    path::{Path, PathBuf},
5};
6
7use shared_buffer::OwnedBuffer;
8use tracing::trace;
9use virtual_fs::{AsyncReadExt, FileSystem, FsError};
10
11use crate::{WasiFs, fs::WasiFsRoot};
12
13use super::{LinkError, LocateModuleError};
14
15const DEFAULT_RUNTIME_PATH: [&str; 3] = ["/lib", "/usr/lib", "/usr/local/lib"];
16
17pub(super) async fn locate_module(
18    module_path: &Path,
19    library_path: &[impl AsRef<Path>],
20    runtime_path: &[impl AsRef<str>],
21    calling_module_path: Option<impl AsRef<Path>>,
22    fs: &WasiFs,
23) -> Result<(PathBuf, OwnedBuffer), LinkError> {
24    async fn try_load(
25        fs: &WasiFsRoot,
26        path: impl AsRef<Path>,
27    ) -> Result<(PathBuf, OwnedBuffer), FsError> {
28        let mut file = match fs.new_open_options().read(true).open(path.as_ref()) {
29            Ok(f) => f,
30            // Fallback for cases where the module thinks it's running on unix,
31            // but the compiled side module is a .wasm file
32            Err(_) if path.as_ref().extension() == Some(OsStr::new("so")) => fs
33                .new_open_options()
34                .read(true)
35                .open(path.as_ref().with_extension("wasm"))?,
36            Err(e) => return Err(e),
37        };
38
39        let buf = if let Some(buf) = file.as_owned_buffer() {
40            buf
41        } else {
42            let mut buf = Vec::new();
43            file.read_to_end(&mut buf).await?;
44            OwnedBuffer::from(buf)
45        };
46
47        Ok((path.as_ref().to_owned(), buf))
48    }
49
50    if module_path.is_absolute() {
51        trace!(?module_path, "Locating module with absolute path");
52        try_load(&fs.root_fs, module_path).await.map_err(|e| {
53            LinkError::SharedLibraryMissing(
54                module_path.to_string_lossy().into_owned(),
55                LocateModuleError::Single(e),
56            )
57        })
58    } else if module_path.components().count() > 1 {
59        trace!(?module_path, "Locating module with relative path");
60        try_load(
61            &fs.root_fs,
62            fs.relative_path_to_absolute(module_path.to_string_lossy().into_owned()),
63        )
64        .await
65        .map_err(|e| {
66            LinkError::SharedLibraryMissing(
67                module_path.to_string_lossy().into_owned(),
68                LocateModuleError::Single(e),
69            )
70        })
71    } else {
72        // Go through all dynamic library lookup paths
73        // Note: a path without a slash does *not* look at the current directory. This is by design.
74
75        trace!(
76            ?module_path,
77            "Locating module by name in default runtime path"
78        );
79
80        let calling_module_dir = calling_module_path
81            .as_ref()
82            .map(|p| p.as_ref().parent().unwrap_or_else(|| p.as_ref()));
83
84        let runtime_path = runtime_path.iter().map(|path| {
85            let path = path.as_ref();
86
87            let relative = path
88                .strip_prefix("$ORIGIN")
89                .or_else(|| path.strip_prefix("${ORIGIN}"));
90
91            match relative {
92                Some(relative) => {
93                    let Some(calling_module_dir) = calling_module_dir else {
94                        // This is an internal error because the only time calling_module_path
95                        // should be empty is when loading a module through dlopen, and a
96                        // dlopen'ed module isn't being required by another module so we don't
97                        // have a RUNPATH to consider at all. See the invocation of
98                        // `load_module_tree` in `load_module`.
99                        panic!(
100                            "Internal error: $ORIGIN or ${{ORIGIN}} in RUNPATH, but \
101                            no calling module path provided"
102                        );
103                    };
104                    Cow::Owned(PathBuf::from(
105                        fs.relative_path_to_absolute(
106                            calling_module_dir
107                                .join(relative)
108                                .to_string_lossy()
109                                .into_owned(),
110                        ),
111                    ))
112                }
113                None => Cow::Borrowed(Path::new(path)),
114            }
115        });
116
117        // Search order is: LD_LIBRARY_PATH -> RUNPATH -> system default folders
118        let search_paths = library_path
119            .iter()
120            .map(|path| Cow::Borrowed(path.as_ref()))
121            .chain(runtime_path)
122            .chain(
123                DEFAULT_RUNTIME_PATH
124                    .iter()
125                    .map(|path| Cow::Borrowed(Path::new(path))),
126            );
127
128        let mut errors: Vec<(PathBuf, FsError)> = Vec::new();
129        for path in search_paths {
130            let full_path = path.join(module_path);
131            trace!(search_path = ?path, full_path = ?full_path, "Searching module");
132            match try_load(&fs.root_fs, &full_path).await {
133                Ok(ret) => {
134                    trace!(?module_path, full_path = ?ret.0, "Located module");
135                    return Ok(ret);
136                }
137                Err(e) => errors.push((full_path, e)),
138            };
139        }
140
141        trace!(?module_path, "Failed to locate module");
142        Err(LinkError::SharedLibraryMissing(
143            module_path.to_string_lossy().into_owned(),
144            LocateModuleError::Multiple(errors),
145        ))
146    }
147}