1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 /* LD_PRELOAD library that intercepts some libc functions and lowercases
6 * paths under a given set of directories before calling the real libc
9 * The set of directories is defined with the LOWERCASE_DIRS environment
10 * variable, separated with a `:`.
12 * Only the parts of the directories below the LOWERCASE_DIRS directories
15 * For example, with LOWERCASE_DIRS=/Foo:/Bar:
16 * `/home/QuX` is unchanged.
17 * `/Foo/QuX` becomes `/Foo/qux`.
18 * `/foo/QuX` is unchanged.
19 * `/Bar/QuX` becomes `/Bar/qux`.
22 * This is, by no means, supposed to be a generic LD_PRELOAD library. It
23 * only intercepts the libc functions that matter in order to build Firefox.
27 use std::env::{self, current_dir};
28 use std::ffi::{c_void, CStr, CString, OsStr, OsString};
29 use std::mem::transmute;
30 use std::os::raw::{c_char, c_int};
31 use std::os::unix::ffi::{OsStrExt, OsStringExt};
32 use std::path::{Path, PathBuf};
35 use once_cell::sync::Lazy;
36 use path_dedot::ParseDot;
38 #[cfg(not(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu")))]
39 compile_error!("Platform is not supported");
41 static LOWERCASE_DIRS: Lazy<Vec<PathBuf>> = Lazy::new(|| match env::var_os("LOWERCASE_DIRS") {
45 .split(|&c| c == b':')
46 .map(|p| canonicalize_path(Path::new(OsStr::from_bytes(p))).into_owned())
50 fn canonicalize_path(path: &Path) -> Cow<Path> {
51 let path = if path.is_absolute() {
55 Ok(cwd) => Cow::Owned(cwd.join(path)),
56 Err(_) => Cow::Borrowed(path),
60 // TODO: avoid allocation when the path doesn't need .. / . removals.
61 Cow::Owned(path.parse_dot().unwrap())
65 fn test_canonicalize_path() {
66 use std::env::set_current_dir;
67 use std::iter::repeat;
68 use tempfile::tempdir;
70 fn do_test(curdir: &Path) {
71 let foobarbaz = curdir.join("foo/bar/baz");
73 assert_eq!(foobarbaz, canonicalize_path(Path::new("foo/bar/baz")));
74 assert_eq!(foobarbaz, canonicalize_path(Path::new("./foo/bar/baz")));
75 assert_eq!(foobarbaz, canonicalize_path(Path::new("foo/./bar/baz")));
76 assert_eq!(foobarbaz, canonicalize_path(Path::new("foo/././bar/baz")));
79 canonicalize_path(Path::new("foo/././bar/qux/../baz"))
83 canonicalize_path(Path::new("foo/./bar/../qux/../bar/baz"))
87 canonicalize_path(Path::new("foo/bar/./../../foo/bar/baz"))
90 let depth = curdir.components().count();
91 for depth in depth..=depth + 1 {
92 let path = repeat("..").take(depth).collect::<Vec<_>>();
93 let mut path = path.join("/");
94 path.push_str("/foo/bar/baz");
97 Path::new("/foo/bar/baz"),
98 canonicalize_path(Path::new(&path))
103 let orig_curdir = current_dir().unwrap();
105 do_test(&orig_curdir);
107 let tempdir = tempdir().unwrap();
108 set_current_dir(&tempdir).unwrap();
110 do_test(tempdir.path());
112 set_current_dir(orig_curdir).unwrap();
115 fn normalize_path(path: &CStr) -> Cow<CStr> {
116 let orig_path = path;
117 let path = Path::new(OsStr::from_bytes(orig_path.to_bytes()));
118 match normalize_path_for_dirs(&path, &LOWERCASE_DIRS) {
119 Cow::Borrowed(_) => Cow::Borrowed(orig_path),
120 Cow::Owned(p) => Cow::Owned(CString::new(p.into_os_string().into_vec()).unwrap()),
124 fn normalize_path_for_dirs<'a>(path: &'a Path, dirs: &[PathBuf]) -> Cow<'a, Path> {
125 let orig_path = path;
126 let path = canonicalize_path(path);
128 for lowercase_dir in dirs.iter() {
129 if path.starts_with(lowercase_dir) {
130 // TODO: avoid allocation when the string doesn't actually need
132 let mut lowercased_path = path.into_owned().into_os_string().into_vec();
133 lowercased_path[lowercase_dir.as_os_str().as_bytes().len()..].make_ascii_lowercase();
134 return Cow::Owned(OsString::from_vec(lowercased_path).into());
138 Cow::Borrowed(orig_path)
142 fn test_normalize_path() {
144 Path::new("/Foo/Bar").to_owned(),
145 Path::new("/Qux").to_owned(),
146 current_dir().unwrap().join("Fuga"),
150 normalize_path_for_dirs(Path::new("/foo/bar/Baz"), &paths),
151 Path::new("/foo/bar/Baz")
154 normalize_path_for_dirs(Path::new("/Foo/Bar/Baz"), &paths),
155 Path::new("/Foo/Bar/baz")
158 normalize_path_for_dirs(Path::new("/Foo/BarBaz"), &paths),
159 Path::new("/Foo/BarBaz")
162 normalize_path_for_dirs(Path::new("/Foo/Bar"), &paths),
163 Path::new("/Foo/Bar")
166 normalize_path_for_dirs(Path::new("/Foo/Bar/Baz/../Qux"), &paths),
167 Path::new("/Foo/Bar/qux")
170 normalize_path_for_dirs(Path::new("/Foo/Bar/Baz/../../Qux"), &paths),
171 Path::new("/Foo/Bar/Baz/../../Qux")
174 normalize_path_for_dirs(Path::new("/Qux/Foo/Bar/Baz"), &paths),
175 Path::new("/Qux/foo/bar/baz")
178 normalize_path_for_dirs(Path::new("/foo/../Qux/Baz"), &paths),
179 Path::new("/Qux/baz")
182 normalize_path_for_dirs(Path::new("fuga/Foo/Bar"), &paths),
183 Path::new("fuga/Foo/Bar")
186 normalize_path_for_dirs(Path::new("Fuga/Foo/Bar"), &paths),
187 current_dir().unwrap().join("Fuga/foo/bar")
190 normalize_path_for_dirs(Path::new("Fuga/../Foo/Bar"), &paths),
191 Path::new("Fuga/../Foo/Bar")
195 macro_rules! wrappers {
196 ($(fn $name:ident($( $a:ident : $t:ty ),*) $( -> $ret:ty)?;)*) => {
199 #[allow(non_upper_case_globals)]
200 static [< real $name >]: Lazy<extern "C" fn($($t),*) $( -> $ret)?> =
201 Lazy::new(|| unsafe {
202 transmute(libc::dlsym(
204 concat!(stringify!($name), "\0").as_ptr() as _
208 unsafe extern "C" fn $name($($a : $t),*) $(-> $ret)? {
209 $( wrappers!(@normalize ($a: $t)); )*
210 [< real $name >]($($a),*)
215 (@normalize ($a:ident: *const c_char)) => {
216 let $a = if $a.is_null() {
219 Some(normalize_path(CStr::from_ptr($a)))
221 let $a = $a.as_ref().map(|p| p.as_ptr()).unwrap_or(null());
223 (@normalize ($a:ident: $t:ty)) => {}
226 // Note: actual definitions for e.g. fopen/fopen64 would be using c_char
227 // instead of c_void for mode, but the wrappers macro treats all `*const c_char`s
228 // as "to maybe be lowercased".
230 fn open(path: *const c_char, flags: c_int, mode: libc::mode_t) -> c_int;
231 fn open64(path: *const c_char, flags: c_int, mode: libc::mode_t) -> c_int;
232 fn fopen(path: *const c_char, mode: *const c_void) -> *mut libc::FILE;
233 fn fopen64(path: *const c_char, mode: *const c_void) -> *mut libc::FILE;
235 fn opendir(path: *const c_char) -> *mut libc::DIR;
237 fn stat(path: *const c_char, buf: *mut libc::stat) -> c_int;
238 fn stat64(path: *const c_char, buf: *mut libc::stat64) -> c_int;
239 fn __xstat(ver: c_int, path: *const c_char, buf: *mut libc::stat) -> c_int;
240 fn __xstat64(ver: c_int, path: *const c_char, buf: *mut libc::stat64) -> c_int;
242 fn __lxstat(ver: c_int, path: *const c_char, buf: *mut libc::stat) -> c_int;
243 fn __lxstat64(ver: c_int, path: *const c_char, buf: *mut libc::stat64) -> c_int;
244 fn __fxstatat(ver: c_int, fd: c_int, path: *const c_char, buf: *mut libc::stat, flag: c_int) -> c_int;
245 fn __fxstatat64(ver: c_int, fd: c_int, path: *const c_char, buf: *mut libc::stat64, flag: c_int) -> c_int;
247 fn access(path: *const c_char, mode: c_int) -> c_int;
249 fn mkdir(path: *const c_char, mode: libc::mode_t) -> c_int;
251 fn chdir(path: *const c_char) -> c_int;
253 fn symlink(target: *const c_char, linkpath: *const c_char) -> c_int;