summaryrefslogtreecommitdiffstats
path: root/build/liblowercase/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'build/liblowercase/lib.rs')
-rw-r--r--build/liblowercase/lib.rs252
1 files changed, 252 insertions, 0 deletions
diff --git a/build/liblowercase/lib.rs b/build/liblowercase/lib.rs
new file mode 100644
index 0000000000..9e068a8c38
--- /dev/null
+++ b/build/liblowercase/lib.rs
@@ -0,0 +1,252 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* LD_PRELOAD library that intercepts some libc functions and lowercases
+ * paths under a given set of directories before calling the real libc
+ * functions.
+ *
+ * The set of directories is defined with the LOWERCASE_DIRS environment
+ * variable, separated with a `:`.
+ *
+ * Only the parts of the directories below the LOWERCASE_DIRS directories
+ * are lowercased.
+ *
+ * For example, with LOWERCASE_DIRS=/Foo:/Bar:
+ * `/home/QuX` is unchanged.
+ * `/Foo/QuX` becomes `/Foo/qux`.
+ * `/foo/QuX` is unchanged.
+ * `/Bar/QuX` becomes `/Bar/qux`.
+ * etc.
+ *
+ * This is, by no means, supposed to be a generic LD_PRELOAD library. It
+ * only intercepts the libc functions that matter in order to build Firefox.
+ */
+
+use std::borrow::Cow;
+use std::env::{self, current_dir};
+use std::ffi::{c_void, CStr, CString, OsStr, OsString};
+use std::mem::transmute;
+use std::os::raw::{c_char, c_int};
+use std::os::unix::ffi::{OsStrExt, OsStringExt};
+use std::path::{Path, PathBuf};
+use std::ptr::null;
+
+use once_cell::sync::Lazy;
+use path_dedot::ParseDot;
+
+#[cfg(not(all(target_arch = "x86_64", target_os = "linux", target_env = "gnu")))]
+compile_error!("Platform is not supported");
+
+static LOWERCASE_DIRS: Lazy<Vec<PathBuf>> = Lazy::new(|| match env::var_os("LOWERCASE_DIRS") {
+ None => Vec::new(),
+ Some(value) => value
+ .as_bytes()
+ .split(|&c| c == b':')
+ .map(|p| canonicalize_path(Path::new(OsStr::from_bytes(p))).into_owned())
+ .collect(),
+});
+
+fn canonicalize_path(path: &Path) -> Cow<Path> {
+ let path = if path.is_absolute() {
+ Cow::Borrowed(path)
+ } else {
+ match current_dir() {
+ Ok(cwd) => Cow::Owned(cwd.join(path)),
+ Err(_) => Cow::Borrowed(path),
+ }
+ };
+
+ // TODO: avoid allocation when the path doesn't need .. / . removals.
+ Cow::Owned(path.parse_dot().unwrap())
+}
+
+#[test]
+fn test_canonicalize_path() {
+ use std::env::set_current_dir;
+ use std::iter::repeat;
+ use tempfile::tempdir;
+
+ fn do_test(curdir: &Path) {
+ let foobarbaz = curdir.join("foo/bar/baz");
+
+ assert_eq!(foobarbaz, canonicalize_path(Path::new("foo/bar/baz")));
+ assert_eq!(foobarbaz, canonicalize_path(Path::new("./foo/bar/baz")));
+ assert_eq!(foobarbaz, canonicalize_path(Path::new("foo/./bar/baz")));
+ assert_eq!(foobarbaz, canonicalize_path(Path::new("foo/././bar/baz")));
+ assert_eq!(
+ foobarbaz,
+ canonicalize_path(Path::new("foo/././bar/qux/../baz"))
+ );
+ assert_eq!(
+ foobarbaz,
+ canonicalize_path(Path::new("foo/./bar/../qux/../bar/baz"))
+ );
+ assert_eq!(
+ foobarbaz,
+ canonicalize_path(Path::new("foo/bar/./../../foo/bar/baz"))
+ );
+
+ let depth = curdir.components().count();
+ for depth in depth..=depth + 1 {
+ let path = repeat("..").take(depth).collect::<Vec<_>>();
+ let mut path = path.join("/");
+ path.push_str("/foo/bar/baz");
+
+ assert_eq!(
+ Path::new("/foo/bar/baz"),
+ canonicalize_path(Path::new(&path))
+ );
+ }
+ }
+
+ let orig_curdir = current_dir().unwrap();
+
+ do_test(&orig_curdir);
+
+ let tempdir = tempdir().unwrap();
+ set_current_dir(&tempdir).unwrap();
+
+ do_test(tempdir.path());
+
+ set_current_dir(orig_curdir).unwrap();
+}
+
+fn normalize_path(path: &CStr) -> Cow<CStr> {
+ let orig_path = path;
+ let path = Path::new(OsStr::from_bytes(orig_path.to_bytes()));
+ match normalize_path_for_dirs(&path, &LOWERCASE_DIRS) {
+ Cow::Borrowed(_) => Cow::Borrowed(orig_path),
+ Cow::Owned(p) => Cow::Owned(CString::new(p.into_os_string().into_vec()).unwrap()),
+ }
+}
+
+fn normalize_path_for_dirs<'a>(path: &'a Path, dirs: &[PathBuf]) -> Cow<'a, Path> {
+ let orig_path = path;
+ let path = canonicalize_path(path);
+
+ for lowercase_dir in dirs.iter() {
+ if path.starts_with(lowercase_dir) {
+ // TODO: avoid allocation when the string doesn't actually need
+ // modification.
+ let mut lowercased_path = path.into_owned().into_os_string().into_vec();
+ lowercased_path[lowercase_dir.as_os_str().as_bytes().len()..].make_ascii_lowercase();
+ return Cow::Owned(OsString::from_vec(lowercased_path).into());
+ }
+ }
+
+ Cow::Borrowed(orig_path)
+}
+
+#[test]
+fn test_normalize_path() {
+ let paths = vec![
+ Path::new("/Foo/Bar").to_owned(),
+ Path::new("/Qux").to_owned(),
+ current_dir().unwrap().join("Fuga"),
+ ];
+
+ assert_eq!(
+ normalize_path_for_dirs(Path::new("/foo/bar/Baz"), &paths),
+ Path::new("/foo/bar/Baz")
+ );
+ assert_eq!(
+ normalize_path_for_dirs(Path::new("/Foo/Bar/Baz"), &paths),
+ Path::new("/Foo/Bar/baz")
+ );
+ assert_eq!(
+ normalize_path_for_dirs(Path::new("/Foo/BarBaz"), &paths),
+ Path::new("/Foo/BarBaz")
+ );
+ assert_eq!(
+ normalize_path_for_dirs(Path::new("/Foo/Bar"), &paths),
+ Path::new("/Foo/Bar")
+ );
+ assert_eq!(
+ normalize_path_for_dirs(Path::new("/Foo/Bar/Baz/../Qux"), &paths),
+ Path::new("/Foo/Bar/qux")
+ );
+ assert_eq!(
+ normalize_path_for_dirs(Path::new("/Foo/Bar/Baz/../../Qux"), &paths),
+ Path::new("/Foo/Bar/Baz/../../Qux")
+ );
+ assert_eq!(
+ normalize_path_for_dirs(Path::new("/Qux/Foo/Bar/Baz"), &paths),
+ Path::new("/Qux/foo/bar/baz")
+ );
+ assert_eq!(
+ normalize_path_for_dirs(Path::new("/foo/../Qux/Baz"), &paths),
+ Path::new("/Qux/baz")
+ );
+ assert_eq!(
+ normalize_path_for_dirs(Path::new("fuga/Foo/Bar"), &paths),
+ Path::new("fuga/Foo/Bar")
+ );
+ assert_eq!(
+ normalize_path_for_dirs(Path::new("Fuga/Foo/Bar"), &paths),
+ current_dir().unwrap().join("Fuga/foo/bar")
+ );
+ assert_eq!(
+ normalize_path_for_dirs(Path::new("Fuga/../Foo/Bar"), &paths),
+ Path::new("Fuga/../Foo/Bar")
+ );
+}
+
+macro_rules! wrappers {
+ ($(fn $name:ident($( $a:ident : $t:ty ),*) $( -> $ret:ty)?;)*) => {
+ $(
+ paste::item! {
+ #[allow(non_upper_case_globals)]
+ static [< real $name >]: Lazy<extern "C" fn($($t),*) $( -> $ret)?> =
+ Lazy::new(|| unsafe {
+ transmute(libc::dlsym(
+ libc::RTLD_NEXT,
+ concat!(stringify!($name), "\0").as_ptr() as _
+ ))
+ });
+ #[no_mangle]
+ unsafe extern "C" fn $name($($a : $t),*) $(-> $ret)? {
+ $( wrappers!(@normalize ($a: $t)); )*
+ [< real $name >]($($a),*)
+ }
+ }
+ )*
+ };
+ (@normalize ($a:ident: *const c_char)) => {
+ let $a = if $a.is_null() {
+ None
+ } else {
+ Some(normalize_path(CStr::from_ptr($a)))
+ };
+ let $a = $a.as_ref().map(|p| p.as_ptr()).unwrap_or(null());
+ };
+ (@normalize ($a:ident: $t:ty)) => {}
+}
+
+// Note: actual definitions for e.g. fopen/fopen64 would be using c_char
+// instead of c_void for mode, but the wrappers macro treats all `*const c_char`s
+// as "to maybe be lowercased".
+wrappers! {
+ fn open(path: *const c_char, flags: c_int, mode: libc::mode_t) -> c_int;
+ fn open64(path: *const c_char, flags: c_int, mode: libc::mode_t) -> c_int;
+ fn fopen(path: *const c_char, mode: *const c_void) -> *mut libc::FILE;
+ fn fopen64(path: *const c_char, mode: *const c_void) -> *mut libc::FILE;
+
+ fn opendir(path: *const c_char) -> *mut libc::DIR;
+
+ fn __xstat(ver: c_int, path: *const c_char, buf: *mut libc::stat) -> c_int;
+ fn __xstat64(ver: c_int, path: *const c_char, buf: *mut libc::stat64) -> c_int;
+
+ fn __lxstat(ver: c_int, path: *const c_char, buf: *mut libc::stat) -> c_int;
+ fn __lxstat64(ver: c_int, path: *const c_char, buf: *mut libc::stat64) -> c_int;
+ fn __fxstatat(ver: c_int, fd: c_int, path: *const c_char, buf: *mut libc::stat, flag: c_int) -> c_int;
+ fn __fxstatat64(ver: c_int, fd: c_int, path: *const c_char, buf: *mut libc::stat64, flag: c_int) -> c_int;
+
+ fn access(path: *const c_char, mode: c_int) -> c_int;
+
+ fn mkdir(path: *const c_char, mode: libc::mode_t) -> c_int;
+
+ fn chdir(path: *const c_char) -> c_int;
+
+ fn symlink(target: *const c_char, linkpath: *const c_char) -> c_int;
+}