rustc_session/
filesearch.rs

1//! A module for searching for libraries
2
3use std::path::{Path, PathBuf};
4use std::{env, fs};
5
6use rustc_fs_util::try_canonicalize;
7use rustc_target::spec::Target;
8
9use crate::search_paths::{PathKind, SearchPath};
10
11#[derive(Clone)]
12pub struct FileSearch {
13    cli_search_paths: Vec<SearchPath>,
14    tlib_path: SearchPath,
15}
16
17impl FileSearch {
18    pub fn cli_search_paths<'b>(&'b self, kind: PathKind) -> impl Iterator<Item = &'b SearchPath> {
19        self.cli_search_paths.iter().filter(move |sp| sp.kind.matches(kind))
20    }
21
22    pub fn search_paths<'b>(&'b self, kind: PathKind) -> impl Iterator<Item = &'b SearchPath> {
23        self.cli_search_paths
24            .iter()
25            .filter(move |sp| sp.kind.matches(kind))
26            .chain(std::iter::once(&self.tlib_path))
27    }
28
29    pub fn new(cli_search_paths: &[SearchPath], tlib_path: &SearchPath, target: &Target) -> Self {
30        let this = FileSearch {
31            cli_search_paths: cli_search_paths.to_owned(),
32            tlib_path: tlib_path.clone(),
33        };
34        this.refine(&["lib", &target.staticlib_prefix, &target.dll_prefix])
35    }
36    // Produce a new file search from this search that has a smaller set of candidates.
37    fn refine(mut self, allowed_prefixes: &[&str]) -> FileSearch {
38        self.cli_search_paths
39            .iter_mut()
40            .for_each(|search_paths| search_paths.files.retain(allowed_prefixes));
41        self.tlib_path.files.retain(allowed_prefixes);
42
43        self
44    }
45}
46
47pub fn make_target_lib_path(sysroot: &Path, target_triple: &str) -> PathBuf {
48    let rustlib_path = rustc_target::relative_target_rustlib_path(sysroot, target_triple);
49    sysroot.join(rustlib_path).join("lib")
50}
51
52/// Returns a path to the target's `bin` folder within its `rustlib` path in the sysroot. This is
53/// where binaries are usually installed, e.g. the self-contained linkers, lld-wrappers, LLVM tools,
54/// etc.
55pub fn make_target_bin_path(sysroot: &Path, target_triple: &str) -> PathBuf {
56    let rustlib_path = rustc_target::relative_target_rustlib_path(sysroot, target_triple);
57    sysroot.join(rustlib_path).join("bin")
58}
59
60#[cfg(unix)]
61fn current_dll_path() -> Result<PathBuf, String> {
62    use std::sync::OnceLock;
63
64    // This is somewhat expensive relative to other work when compiling `fn main() {}` as `dladdr`
65    // needs to iterate over the symbol table of librustc_driver.so until it finds a match.
66    // As such cache this to avoid recomputing if we try to get the sysroot in multiple places.
67    static CURRENT_DLL_PATH: OnceLock<Result<PathBuf, String>> = OnceLock::new();
68    CURRENT_DLL_PATH
69        .get_or_init(|| {
70            use std::ffi::{CStr, OsStr};
71            use std::os::unix::prelude::*;
72
73            #[cfg(not(target_os = "aix"))]
74            unsafe {
75                let addr = current_dll_path as usize as *mut _;
76                let mut info = std::mem::zeroed();
77                if libc::dladdr(addr, &mut info) == 0 {
78                    return Err("dladdr failed".into());
79                }
80                #[cfg(target_os = "cygwin")]
81                let fname_ptr = info.dli_fname.as_ptr();
82                #[cfg(not(target_os = "cygwin"))]
83                let fname_ptr = {
84                    assert!(!info.dli_fname.is_null(), "dli_fname cannot be null");
85                    info.dli_fname
86                };
87                let bytes = CStr::from_ptr(fname_ptr).to_bytes();
88                let os = OsStr::from_bytes(bytes);
89                try_canonicalize(Path::new(os)).map_err(|e| e.to_string())
90            }
91
92            #[cfg(target_os = "aix")]
93            unsafe {
94                // On AIX, the symbol `current_dll_path` references a function descriptor.
95                // A function descriptor is consisted of (See https://reviews.llvm.org/D62532)
96                // * The address of the entry point of the function.
97                // * The TOC base address for the function.
98                // * The environment pointer.
99                // The function descriptor is in the data section.
100                let addr = current_dll_path as u64;
101                let mut buffer = vec![std::mem::zeroed::<libc::ld_info>(); 64];
102                loop {
103                    if libc::loadquery(
104                        libc::L_GETINFO,
105                        buffer.as_mut_ptr() as *mut u8,
106                        (size_of::<libc::ld_info>() * buffer.len()) as u32,
107                    ) >= 0
108                    {
109                        break;
110                    } else {
111                        if std::io::Error::last_os_error().raw_os_error().unwrap() != libc::ENOMEM {
112                            return Err("loadquery failed".into());
113                        }
114                        buffer.resize(buffer.len() * 2, std::mem::zeroed::<libc::ld_info>());
115                    }
116                }
117                let mut current = buffer.as_mut_ptr() as *mut libc::ld_info;
118                loop {
119                    let data_base = (*current).ldinfo_dataorg as u64;
120                    let data_end = data_base + (*current).ldinfo_datasize;
121                    if (data_base..data_end).contains(&addr) {
122                        let bytes = CStr::from_ptr(&(*current).ldinfo_filename[0]).to_bytes();
123                        let os = OsStr::from_bytes(bytes);
124                        return try_canonicalize(Path::new(os)).map_err(|e| e.to_string());
125                    }
126                    if (*current).ldinfo_next == 0 {
127                        break;
128                    }
129                    current = (current as *mut i8).offset((*current).ldinfo_next as isize)
130                        as *mut libc::ld_info;
131                }
132                return Err(format!("current dll's address {} is not in the load map", addr));
133            }
134        })
135        .clone()
136}
137
138#[cfg(windows)]
139fn current_dll_path() -> Result<PathBuf, String> {
140    use std::ffi::OsString;
141    use std::io;
142    use std::os::windows::prelude::*;
143
144    use windows::Win32::Foundation::HMODULE;
145    use windows::Win32::System::LibraryLoader::{
146        GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, GetModuleFileNameW, GetModuleHandleExW,
147    };
148    use windows::core::PCWSTR;
149
150    let mut module = HMODULE::default();
151    unsafe {
152        GetModuleHandleExW(
153            GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
154            PCWSTR(current_dll_path as *mut u16),
155            &mut module,
156        )
157    }
158    .map_err(|e| e.to_string())?;
159
160    let mut filename = vec![0; 1024];
161    let n = unsafe { GetModuleFileNameW(Some(module), &mut filename) } as usize;
162    if n == 0 {
163        return Err(format!("GetModuleFileNameW failed: {}", io::Error::last_os_error()));
164    }
165    if n >= filename.capacity() {
166        return Err(format!("our buffer was too small? {}", io::Error::last_os_error()));
167    }
168
169    filename.truncate(n);
170
171    let path = try_canonicalize(OsString::from_wide(&filename)).map_err(|e| e.to_string())?;
172
173    // See comments on this target function, but the gist is that
174    // gcc chokes on verbatim paths which fs::canonicalize generates
175    // so we try to avoid those kinds of paths.
176    Ok(rustc_fs_util::fix_windows_verbatim_for_gcc(&path))
177}
178
179#[cfg(target_os = "wasi")]
180fn current_dll_path() -> Result<PathBuf, String> {
181    Err("current_dll_path is not supported on WASI".to_string())
182}
183
184/// This function checks if sysroot is found using env::args().next(), and if it
185/// is not found, finds sysroot from current rustc_driver dll.
186pub(crate) fn default_sysroot() -> PathBuf {
187    fn default_from_rustc_driver_dll() -> Result<PathBuf, String> {
188        let dll = current_dll_path()?;
189
190        // `dll` will be in one of the following two:
191        // - compiler's libdir: $sysroot/lib/*.dll
192        // - target's libdir: $sysroot/lib/rustlib/$target/lib/*.dll
193        //
194        // use `parent` twice to chop off the file name and then also the
195        // directory containing the dll
196        let dir = dll.parent().and_then(|p| p.parent()).ok_or_else(|| {
197            format!("Could not move 2 levels upper using `parent()` on {}", dll.display())
198        })?;
199
200        // if `dir` points to target's dir, move up to the sysroot
201        let mut sysroot_dir = if dir.ends_with(crate::config::host_tuple()) {
202            dir.parent() // chop off `$target`
203                .and_then(|p| p.parent()) // chop off `rustlib`
204                .and_then(|p| p.parent()) // chop off `lib`
205                .map(|s| s.to_owned())
206                .ok_or_else(|| {
207                    format!("Could not move 3 levels upper using `parent()` on {}", dir.display())
208                })?
209        } else {
210            dir.to_owned()
211        };
212
213        // On multiarch linux systems, there will be multiarch directory named
214        // with the architecture(e.g `x86_64-linux-gnu`) under the `lib` directory.
215        // Which cause us to mistakenly end up in the lib directory instead of the sysroot directory.
216        if sysroot_dir.ends_with("lib") {
217            sysroot_dir =
218                sysroot_dir.parent().map(|real_sysroot| real_sysroot.to_owned()).ok_or_else(
219                    || format!("Could not move to parent path of {}", sysroot_dir.display()),
220                )?
221        }
222
223        Ok(sysroot_dir)
224    }
225
226    // Use env::args().next() to get the path of the executable without
227    // following symlinks/canonicalizing any component. This makes the rustc
228    // binary able to locate Rust libraries in systems using content-addressable
229    // storage (CAS).
230    fn from_env_args_next() -> Option<PathBuf> {
231        let mut p = PathBuf::from(env::args_os().next()?);
232
233        // Check if sysroot is found using env::args().next() only if the rustc in argv[0]
234        // is a symlink (see #79253). We might want to change/remove it to conform with
235        // https://www.gnu.org/prep/standards/standards.html#Finding-Program-Files in the
236        // future.
237        if fs::read_link(&p).is_err() {
238            // Path is not a symbolic link or does not exist.
239            return None;
240        }
241
242        // Pop off `bin/rustc`, obtaining the suspected sysroot.
243        p.pop();
244        p.pop();
245        // Look for the target rustlib directory in the suspected sysroot.
246        let mut rustlib_path = rustc_target::relative_target_rustlib_path(&p, "dummy");
247        rustlib_path.pop(); // pop off the dummy target.
248        rustlib_path.exists().then_some(p)
249    }
250
251    from_env_args_next()
252        .unwrap_or_else(|| default_from_rustc_driver_dll().expect("Failed finding sysroot"))
253}