Bug 1700051: part 30) Narrow scope of `newOffset`. r=smaug
[gecko.git] / build / liblowercase / lib.rs
blobddf8c6b8327c4d9869e375cbf3d545cc313e8f2d
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
7  * functions.
8  *
9  * The set of directories is defined with the LOWERCASE_DIRS environment
10  * variable, separated with a `:`.
11  *
12  * Only the parts of the directories below the LOWERCASE_DIRS directories
13  * are lowercased.
14  *
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`.
20  *   etc.
21  *
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.
24  */
26 use std::borrow::Cow;
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};
33 use std::ptr::null;
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") {
42     None => Vec::new(),
43     Some(value) => value
44         .as_bytes()
45         .split(|&c| c == b':')
46         .map(|p| canonicalize_path(Path::new(OsStr::from_bytes(p))).into_owned())
47         .collect(),
48 });
50 fn canonicalize_path(path: &Path) -> Cow<Path> {
51     let path = if path.is_absolute() {
52         Cow::Borrowed(path)
53     } else {
54         match current_dir() {
55             Ok(cwd) => Cow::Owned(cwd.join(path)),
56             Err(_) => Cow::Borrowed(path),
57         }
58     };
60     // TODO: avoid allocation when the path doesn't need .. / . removals.
61     Cow::Owned(path.parse_dot().unwrap())
64 #[test]
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")));
77         assert_eq!(
78             foobarbaz,
79             canonicalize_path(Path::new("foo/././bar/qux/../baz"))
80         );
81         assert_eq!(
82             foobarbaz,
83             canonicalize_path(Path::new("foo/./bar/../qux/../bar/baz"))
84         );
85         assert_eq!(
86             foobarbaz,
87             canonicalize_path(Path::new("foo/bar/./../../foo/bar/baz"))
88         );
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");
96             assert_eq!(
97                 Path::new("/foo/bar/baz"),
98                 canonicalize_path(Path::new(&path))
99             );
100         }
101     }
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()),
121     }
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
131             // modification.
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());
135         }
136     }
138     Cow::Borrowed(orig_path)
141 #[test]
142 fn test_normalize_path() {
143     let paths = vec![
144         Path::new("/Foo/Bar").to_owned(),
145         Path::new("/Qux").to_owned(),
146         current_dir().unwrap().join("Fuga"),
147     ];
149     assert_eq!(
150         normalize_path_for_dirs(Path::new("/foo/bar/Baz"), &paths),
151         Path::new("/foo/bar/Baz")
152     );
153     assert_eq!(
154         normalize_path_for_dirs(Path::new("/Foo/Bar/Baz"), &paths),
155         Path::new("/Foo/Bar/baz")
156     );
157     assert_eq!(
158         normalize_path_for_dirs(Path::new("/Foo/BarBaz"), &paths),
159         Path::new("/Foo/BarBaz")
160     );
161     assert_eq!(
162         normalize_path_for_dirs(Path::new("/Foo/Bar"), &paths),
163         Path::new("/Foo/Bar")
164     );
165     assert_eq!(
166         normalize_path_for_dirs(Path::new("/Foo/Bar/Baz/../Qux"), &paths),
167         Path::new("/Foo/Bar/qux")
168     );
169     assert_eq!(
170         normalize_path_for_dirs(Path::new("/Foo/Bar/Baz/../../Qux"), &paths),
171         Path::new("/Foo/Bar/Baz/../../Qux")
172     );
173     assert_eq!(
174         normalize_path_for_dirs(Path::new("/Qux/Foo/Bar/Baz"), &paths),
175         Path::new("/Qux/foo/bar/baz")
176     );
177     assert_eq!(
178         normalize_path_for_dirs(Path::new("/foo/../Qux/Baz"), &paths),
179         Path::new("/Qux/baz")
180     );
181     assert_eq!(
182         normalize_path_for_dirs(Path::new("fuga/Foo/Bar"), &paths),
183         Path::new("fuga/Foo/Bar")
184     );
185     assert_eq!(
186         normalize_path_for_dirs(Path::new("Fuga/Foo/Bar"), &paths),
187         current_dir().unwrap().join("Fuga/foo/bar")
188     );
189     assert_eq!(
190         normalize_path_for_dirs(Path::new("Fuga/../Foo/Bar"), &paths),
191         Path::new("Fuga/../Foo/Bar")
192     );
195 macro_rules! wrappers {
196     ($(fn $name:ident($( $a:ident : $t:ty ),*) $( -> $ret:ty)?;)*) => {
197         $(
198             paste::item! {
199                 #[allow(non_upper_case_globals)]
200                 static [< real $name >]: Lazy<extern "C" fn($($t),*) $( -> $ret)?> =
201                     Lazy::new(|| unsafe {
202                         transmute(libc::dlsym(
203                             libc::RTLD_NEXT,
204                             concat!(stringify!($name), "\0").as_ptr() as _
205                         ))
206                     });
207                 #[no_mangle]
208                 unsafe extern "C" fn $name($($a : $t),*) $(-> $ret)? {
209                     $( wrappers!(@normalize ($a: $t)); )*
210                     [< real $name >]($($a),*)
211                 }
212             }
213         )*
214     };
215     (@normalize ($a:ident: *const c_char)) => {
216         let $a = if $a.is_null() {
217             None
218         } else {
219             Some(normalize_path(CStr::from_ptr($a)))
220         };
221         let $a = $a.as_ref().map(|p| p.as_ptr()).unwrap_or(null());
222     };
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".
229 wrappers! {
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;