use std::{
cell::RefCell,
mem::MaybeUninit,
os::fd::{AsRawFd, IntoRawFd},
sync::Arc,
};
use libc::c_uint;
use memchr::arch::all::{is_equal, memchr::One};
use nix::errno::Errno;
use redix::{raxFind, raxIterator, raxNotFound, raxStart, RaxIterator, RaxMap, GREATER_EQUAL};
use crate::{
fd::{closeall, ROOT_FD, ROOT_F_MODE, ROOT_MNT_ID},
lookup::{FileMapEntry, FileType, MaybeFd},
path::XPath,
XPathBuf,
};
type InnerMap = RaxMap<XPathBuf, FileMapEntry>;
pub struct FileMap(InnerMap);
impl FileMap {
pub(crate) fn find_descendant(&mut self, path: &XPath) -> Option<(MaybeFd, usize)> {
let mut iter = MaybeUninit::<RaxIterator<XPathBuf, FileMapEntry>>::uninit();
unsafe { raxStart(&raw mut iter as *mut raxIterator, self.0.rax) };
let mut iter = unsafe { iter.assume_init() };
iter.fixup();
let path = path.as_bytes();
if !iter.seek_bytes(GREATER_EQUAL, path) {
return None;
}
let mut result = None;
while !iter.eof() {
let cpath = iter.key_bytes();
if !(cpath.len() >= path.len() && is_equal(path, &cpath[..path.len()])) {
break; }
let left = &cpath[path.len()..];
if left.is_empty() || left[0] == b'/' || path.last() == Some(&b'/') {
if let Some(entry) = iter.value() {
let depth = One::new(b'/').count(left);
result = Some((MaybeFd::RawFd(entry.fd.as_raw_fd()), depth));
break;
}
}
iter.forward();
}
result
}
pub(crate) fn try_insert(
&mut self,
path: XPathBuf,
entry: FileMapEntry,
) -> Result<Option<FileMapEntry>, Errno> {
self.0.insert(path, entry).or(Err(Errno::ENOMEM))
}
pub(crate) fn remove(&mut self, path: &XPath) -> Option<FileMapEntry> {
if path.is_root() {
self.map_get(path).cloned()
} else {
self.map_remove(path)
}
}
pub(crate) fn get(&self, path: &XPath) -> Option<&FileMapEntry> {
self.map_get(path)
}
pub(crate) fn get_mut(&mut self, path: &XPath) -> Option<&mut FileMapEntry> {
self.map_get_mut(path)
}
fn map_get(&self, path: &XPath) -> Option<&FileMapEntry> {
let bytes = path.as_bytes();
unsafe {
let value = raxFind(self.0.rax, bytes.as_ptr(), bytes.len());
if value.is_null() || std::ptr::eq(value, raxNotFound) {
None
} else {
Some(&*(value as *mut libc::c_void as *const FileMapEntry))
}
}
}
fn map_get_mut(&mut self, path: &XPath) -> Option<&mut FileMapEntry> {
let bytes = path.as_bytes();
unsafe {
let value = raxFind(self.0.rax, bytes.as_ptr(), bytes.len());
if value.is_null() || std::ptr::eq(value, raxNotFound) {
None
} else {
Some(&mut *(value as *mut libc::c_void as *mut FileMapEntry))
}
}
}
fn map_remove(&mut self, path: &XPath) -> Option<FileMapEntry> {
self.0.remove(XPathBuf::from(path.as_bytes())).1
}
pub(crate) fn try_new() -> Result<Self, Errno> {
let mut map = Self(RaxMap::try_new().or(Err(Errno::ENOMEM))?);
let entry = FileMapEntry::new(
ROOT_FD().into(),
Some(FileType::Dir),
Some(ROOT_F_MODE()),
Some(ROOT_MNT_ID()),
None,
);
map.try_insert(XPathBuf::from("/"), entry)?;
Ok(map)
}
pub fn set_alloc_hardened() {
#[cfg(all(
not(coverage),
not(feature = "prof"),
not(target_os = "android"),
not(target_arch = "riscv64"),
target_page_size_4k,
target_pointer_width = "64"
))]
{
extern "C" fn hmalloc(size: usize) -> *mut u8 {
unsafe { hardened_malloc::malloc(size).cast() }
}
extern "C" fn hrealloc(ptr: *mut libc::c_void, size: usize) -> *mut u8 {
unsafe { hardened_malloc::realloc(ptr, size).cast() }
}
extern "C" fn hfree(ptr: *mut libc::c_void) {
unsafe { hardened_malloc::free(ptr) }
}
unsafe { redix::set_allocator(hmalloc, hrealloc, hfree) };
}
}
}
impl Drop for FileMap {
fn drop(&mut self) {
#[expect(clippy::cast_possible_truncation)]
let closefds = RefCell::new(Vec::with_capacity(self.0.len() as usize));
self.0.iter(|_, iter| {
if iter.seek_min() {
while !iter.eof() {
let data: *mut libc::c_void = iter.data;
if !data.is_null() {
let entry = data as *mut FileMapEntry;
unsafe {
let ptr = std::ptr::addr_of_mut!((*entry).fd);
if let MaybeFd::Owned(fd) = std::ptr::read(ptr) {
std::ptr::write(ptr, MaybeFd::default());
#[expect(clippy::cast_sign_loss)]
if let Some(fd) = Arc::into_inner(fd) {
closefds.borrow_mut().push(fd.into_raw_fd() as c_uint);
}
}
}
}
iter.forward();
}
}
});
let mut closefds = closefds.into_inner();
if closefds.is_empty() {
return;
}
closefds.sort_unstable();
let _ = closeall(&closefds);
}
}
#[cfg(test)]
mod tests {
use std::{os::fd::AsRawFd, sync::Once};
use super::*;
use crate::{
fd::{open_static_files, ROOT_FD},
lookup::{FileMapEntry, FileType, MaybeFd},
path::{XPath, XPathBuf},
};
fn setup() {
static INIT: Once = Once::new();
INIT.call_once(|| {
open_static_files().unwrap();
});
}
fn entry_raw(fd: i32) -> FileMapEntry {
FileMapEntry::new(
MaybeFd::RawFd(fd),
Some(FileType::Reg),
Some(0o644),
Some(1),
None,
)
}
fn entry_dir(fd: i32) -> FileMapEntry {
FileMapEntry::new(
MaybeFd::RawFd(fd),
Some(FileType::Dir),
Some(0o755),
Some(1),
None,
)
}
#[test]
fn test_filemap_1() {
setup();
let map = FileMap::try_new().unwrap();
let root = map.get(XPath::from_bytes(b"/"));
assert!(root.is_some());
let entry = root.unwrap();
assert_eq!(entry.fd.as_raw_fd(), ROOT_FD());
}
#[test]
fn test_filemap_2() {
setup();
let map = FileMap::try_new().unwrap();
let entry = map.get(XPath::from_bytes(b"/")).unwrap();
assert_eq!(entry.fd.as_raw_fd(), ROOT_FD());
}
#[test]
fn test_filemap_3() {
setup();
let mut map = FileMap::try_new().unwrap();
let path = XPathBuf::from("/home");
let old = map.try_insert(path, entry_dir(42)).unwrap();
assert!(old.is_none());
let got = map.get(XPath::from_bytes(b"/home"));
assert!(got.is_some());
assert_eq!(got.unwrap().fd.as_raw_fd(), 42);
}
#[test]
fn test_filemap_4() {
setup();
let mut map = FileMap::try_new().unwrap();
let path = XPathBuf::from("/etc");
map.try_insert(path.clone(), entry_dir(10)).unwrap();
let old = map.try_insert(path, entry_dir(20)).unwrap();
assert!(old.is_some());
assert_eq!(old.unwrap().fd.as_raw_fd(), 10);
let got = map.get(XPath::from_bytes(b"/etc"));
assert_eq!(got.unwrap().fd.as_raw_fd(), 20);
}
#[test]
fn test_filemap_5() {
setup();
let mut map = FileMap::try_new().unwrap();
let paths = ["/a", "/b", "/c", "/a/b", "/a/b/c"];
for (i, p) in paths.iter().enumerate() {
let fd = (100 + i) as i32;
map.try_insert(XPathBuf::from(*p), entry_raw(fd)).unwrap();
}
for (i, p) in paths.iter().enumerate() {
let fd = (100 + i) as i32;
let e = map.get(XPath::from_bytes(p.as_bytes()));
assert!(e.is_some(), "entry for {p} must exist");
assert_eq!(e.unwrap().fd.as_raw_fd(), fd);
}
}
#[test]
fn test_filemap_6() {
setup();
let map = FileMap::try_new().unwrap();
assert!(map.get(XPath::from_bytes(b"/no/such/path")).is_none());
}
#[test]
fn test_filemap_7() {
setup();
let mut map = FileMap::try_new().unwrap();
map.try_insert(XPathBuf::from("/mutable"), entry_raw(50))
.unwrap();
{
let e = map.get_mut(XPath::from_bytes(b"/mutable")).unwrap();
e.fd = MaybeFd::RawFd(99);
}
let e = map.get(XPath::from_bytes(b"/mutable")).unwrap();
assert_eq!(e.fd.as_raw_fd(), 99);
}
#[test]
fn test_filemap_8() {
setup();
let mut map = FileMap::try_new().unwrap();
assert!(map.get_mut(XPath::from_bytes(b"/ghost")).is_none());
}
#[test]
fn test_filemap_9() {
setup();
let mut map = FileMap::try_new().unwrap();
map.try_insert(XPathBuf::from("/tmp"), entry_dir(30))
.unwrap();
assert!(map.get(XPath::from_bytes(b"/tmp")).is_some());
let removed = map.remove(XPath::from_bytes(b"/tmp"));
assert!(removed.is_some());
assert_eq!(removed.unwrap().fd.as_raw_fd(), 30);
assert!(map.get(XPath::from_bytes(b"/tmp")).is_none());
}
#[test]
fn test_filemap_10() {
setup();
let mut map = FileMap::try_new().unwrap();
let removed = map.remove(XPath::from_bytes(b"/nonexistent"));
assert!(removed.is_none());
}
#[test]
fn test_filemap_11() {
setup();
let mut map = FileMap::try_new().unwrap();
let removed = map.remove(XPath::from_bytes(b"/"));
assert!(removed.is_some());
assert!(map.get(XPath::from_bytes(b"/")).is_some());
}
#[test]
fn test_filemap_12() {
setup();
let mut map = FileMap::try_new().unwrap();
map.try_insert(XPathBuf::from("/usr"), entry_dir(60))
.unwrap();
let result = map.find_descendant(XPath::from_bytes(b"/usr"));
assert!(result.is_some());
let (fd, depth) = result.unwrap();
assert_eq!(fd.as_raw_fd(), 60);
assert_eq!(depth, 0);
}
#[test]
fn test_filemap_13() {
setup();
let mut map = FileMap::try_new().unwrap();
map.try_insert(XPathBuf::from("/usr/bin"), entry_dir(70))
.unwrap();
let result = map.find_descendant(XPath::from_bytes(b"/usr"));
assert!(result.is_some());
let (fd, depth) = result.unwrap();
assert_eq!(fd.as_raw_fd(), 70);
assert_eq!(depth, 1);
}
#[test]
fn test_filemap_14() {
setup();
let mut map = FileMap::try_new().unwrap();
map.try_insert(XPathBuf::from("/a/b/c/d/e"), entry_raw(80))
.unwrap();
let result = map.find_descendant(XPath::from_bytes(b"/a"));
assert!(result.is_some());
let (fd, depth) = result.unwrap();
assert_eq!(fd.as_raw_fd(), 80);
assert_eq!(depth, 4, "/a/b/c/d/e is 4 levels below /a");
}
#[test]
fn test_filemap_15() {
setup();
let mut map = FileMap::try_new().unwrap();
map.try_insert(XPathBuf::from("/opt/foo"), entry_raw(90))
.unwrap();
let result = map.find_descendant(XPath::from_bytes(b"/var"));
assert!(result.is_none());
}
#[test]
fn test_filemap_16() {
setup();
let mut map = FileMap::try_new().unwrap();
map.try_insert(XPathBuf::from("/usr"), entry_dir(60))
.unwrap();
map.try_insert(XPathBuf::from("/usrlocal"), entry_raw(61))
.unwrap();
let result = map.find_descendant(XPath::from_bytes(b"/usr"));
assert!(result.is_some());
let (fd, depth) = result.unwrap();
assert_eq!(fd.as_raw_fd(), 60);
assert_eq!(depth, 0);
}
#[test]
fn test_filemap_17() {
setup();
let mut map = FileMap::try_new().unwrap();
map.try_insert(XPathBuf::from("/lib/a"), entry_raw(100))
.unwrap();
map.try_insert(XPathBuf::from("/lib/b"), entry_raw(101))
.unwrap();
let result = map.find_descendant(XPath::from_bytes(b"/lib"));
assert!(result.is_some());
let (fd, depth) = result.unwrap();
assert_eq!(fd.as_raw_fd(), 100);
assert_eq!(depth, 1);
}
#[test]
fn test_filemap_18() {
setup();
let mut map = FileMap::try_new().unwrap();
let result = map.find_descendant(XPath::from_bytes(b"/"));
assert!(result.is_some());
let (fd, depth) = result.unwrap();
assert_eq!(fd.as_raw_fd(), ROOT_FD());
assert_eq!(depth, 0);
}
#[test]
fn test_filemap_19() {
setup();
let mut map = FileMap::try_new().unwrap();
map.try_insert(XPathBuf::from("/srv"), entry_dir(110))
.unwrap();
let result = map.find_descendant(XPath::from_bytes(b"/"));
assert!(result.is_some());
let (fd, _depth) = result.unwrap();
assert_eq!(fd.as_raw_fd(), ROOT_FD());
}
#[test]
fn test_filemap_20() {
setup();
let mut map = FileMap::try_new().unwrap();
let mut inserted = 0usize;
for i in 0..10000 {
let path = format!("/large/{i}");
let fd = (1000i32 + i as i32) as i32;
match map.try_insert(XPathBuf::from(path), entry_raw(fd)) {
Ok(_) => inserted += 1,
Err(Errno::ENOMEM) => break,
Err(errno) => panic!("unexpected error: {errno}"),
}
}
for i in 0..inserted {
let path = format!("/large/{i}");
let fd = (1000i32 + i as i32) as i32;
let e = map.get(XPath::from_bytes(path.as_bytes()));
assert!(e.is_some(), "entry {path} must exist");
assert_eq!(e.unwrap().fd.as_raw_fd(), fd);
}
if inserted > 0 {
let result = map.find_descendant(XPath::from_bytes(b"/large"));
assert!(result.is_some());
}
}
#[test]
fn test_filemap_21() {
setup();
let mut map = FileMap::try_new().unwrap();
let path = XPathBuf::from("/cycle");
map.try_insert(path.clone(), entry_raw(200)).unwrap();
assert_eq!(
map.get(XPath::from_bytes(b"/cycle"))
.unwrap()
.fd
.as_raw_fd(),
200
);
let removed = map.remove(XPath::from_bytes(b"/cycle"));
assert!(removed.is_some());
assert!(map.get(XPath::from_bytes(b"/cycle")).is_none());
map.try_insert(path, entry_raw(300)).unwrap();
assert_eq!(
map.get(XPath::from_bytes(b"/cycle"))
.unwrap()
.fd
.as_raw_fd(),
300
);
}
#[test]
fn test_filemap_22() {
setup();
let mut map = FileMap::try_new().unwrap();
let deep = "/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p";
map.try_insert(XPathBuf::from(deep), entry_raw(400))
.unwrap();
let e = map.get(XPath::from_bytes(deep.as_bytes()));
assert!(e.is_some());
assert_eq!(e.unwrap().fd.as_raw_fd(), 400);
let result = map.find_descendant(XPath::from_bytes(b"/a"));
assert!(result.is_some());
let (_fd, depth) = result.unwrap();
assert_eq!(depth, 15);
}
#[test]
fn test_filemap_23() {
setup();
let mut map = FileMap::try_new().unwrap();
map.try_insert(XPathBuf::from("/usr/lib/lib.so.6"), entry_raw(500))
.unwrap();
let e = map.get(XPath::from_bytes(b"/usr/lib/lib.so.6"));
assert!(e.is_some());
assert_eq!(e.unwrap().fd.as_raw_fd(), 500);
}
#[test]
fn test_filemap_24() {
setup();
let mut map = FileMap::try_new().unwrap();
for i in 0..10 {
let fd = (9000 + i) as i32;
let path = format!("/drop_test/{i}");
map.try_insert(XPathBuf::from(path), entry_raw(fd)).unwrap();
}
}
#[test]
fn test_filemap_25() {
setup();
let mut map = FileMap::try_new().unwrap();
map.try_insert(XPathBuf::from("/data/file"), entry_raw(600))
.unwrap();
let result = map.find_descendant(XPath::from_bytes(b"/data/"));
assert!(result.is_some());
let (fd, depth) = result.unwrap();
assert_eq!(fd.as_raw_fd(), 600);
assert_eq!(depth, 0);
}
#[test]
fn test_filemap_26() {
setup();
let mut map = FileMap::try_new().unwrap();
map.try_insert(XPathBuf::from("/foobar"), entry_raw(700))
.unwrap();
let result = map.find_descendant(XPath::from_bytes(b"/foo"));
assert!(result.is_none());
}
#[test]
fn test_filemap_27() {
setup();
let mut map = FileMap::try_new().unwrap();
map.try_insert(XPathBuf::from("/foo/bar"), entry_raw(710))
.unwrap();
map.try_insert(XPathBuf::from("/foobar"), entry_raw(720))
.unwrap();
let result = map.find_descendant(XPath::from_bytes(b"/foo"));
assert!(result.is_some());
let (fd, depth) = result.unwrap();
assert_eq!(fd.as_raw_fd(), 710);
assert_eq!(depth, 1);
}
#[test]
fn test_filemap_28() {
setup();
let mut map = FileMap::try_new().unwrap();
map.try_insert(XPathBuf::from("/ab"), entry_raw(800))
.unwrap();
map.try_insert(XPathBuf::from("/abc"), entry_raw(801))
.unwrap();
map.try_insert(XPathBuf::from("/ab/cd"), entry_raw(802))
.unwrap();
let result = map.find_descendant(XPath::from_bytes(b"/ab"));
assert!(result.is_some());
let (fd, depth) = result.unwrap();
assert_eq!(fd.as_raw_fd(), 800);
assert_eq!(depth, 0);
}
#[test]
fn test_filemap_29() {
setup();
let mut map = FileMap::try_new().unwrap();
map.try_insert(XPathBuf::from("/xyz1"), entry_raw(810))
.unwrap();
map.try_insert(XPathBuf::from("/xyz2"), entry_raw(811))
.unwrap();
let result = map.find_descendant(XPath::from_bytes(b"/xyz"));
assert!(result.is_none());
}
#[test]
fn test_filemap_30() {
setup();
let mut map = FileMap::try_new().unwrap();
let result = map.find_descendant(XPath::from_bytes(b"/zzz"));
assert!(result.is_none());
}
#[test]
fn test_filemap_31() {
setup();
let mut map = FileMap::try_new().unwrap();
map.try_insert(XPathBuf::from("/a"), entry_raw(820))
.unwrap();
map.try_insert(XPathBuf::from("/b"), entry_raw(821))
.unwrap();
let removed = map.remove(XPath::from_bytes(b"/a"));
assert!(removed.is_some());
assert_eq!(removed.unwrap().fd.as_raw_fd(), 820);
assert!(map.get(XPath::from_bytes(b"/a")).is_none());
assert!(map.get(XPath::from_bytes(b"/b")).is_some());
}
#[test]
fn test_filemap_32() {
setup();
let mut map = FileMap::try_new().unwrap();
let e1 = entry_dir(830);
let e2 = entry_dir(831);
map.try_insert(XPathBuf::from("/dup"), e1).unwrap();
let old = map.try_insert(XPathBuf::from("/dup"), e2).unwrap();
assert!(old.is_some());
assert_eq!(old.unwrap().fd.as_raw_fd(), 830);
}
}