use std::fs::File;
use std::mem;
use std::ops::Deref as _;
use std::path::PathBuf;
use std::str;
use crate::util::bytes_to_path;
#[cfg(linux)]
use crate::util::uname_release;
use crate::util::ReadRaw as _;
use crate::Error;
use crate::ErrorExt as _;
use crate::Mmap;
use crate::Result;
const INDEX_NODE_MASK: u32 = 0x0fffffff;
const INDEX_NODE_CHILDS: u32 = 0x20000000;
const INDEX_NODE_VALUES: u32 = 0x40000000;
const INDEX_NODE_PREFIX: u32 = 0x80000000;
#[derive(Debug)]
pub struct DepmodIndex {
base_dir: PathBuf,
data: &'static [u8],
_mmap: Option<Mmap>,
}
impl DepmodIndex {
#[cfg(linux)]
pub fn with_system_default() -> Result<Self> {
let uname_r = uname_release().context("failed to query uname release string")?;
let uname_r = uname_r
.to_str()
.map_err(Error::with_invalid_data)
.context("uname release string is not valid Unicode")?;
let kernel_dir = PathBuf::from(format!("/lib/modules/{uname_r}"));
let depmod = kernel_dir.join("modules.dep.bin");
let file = File::open(&depmod)
.with_context(|| format!("failed to open depmod file `{}`", depmod.display()))?;
Self::new(kernel_dir, file)
}
pub fn new(base_dir: PathBuf, file: File) -> Result<Self> {
let mmap = Mmap::map(&file)?;
let data = unsafe { mem::transmute::<&[u8], &'static [u8]>(mmap.deref()) };
let depmod = Self {
base_dir,
data,
_mmap: Some(mmap),
};
let () = depmod.validate()?;
Ok(depmod)
}
fn validate(&self) -> Result<()> {
let mut reader = self.data;
let magic = reader
.read_u32()
.map(u32::from_be)
.ok_or_else(|| Error::with_invalid_data("failed to read magic"))?;
if magic != 0xb007f457 {
return Err(Error::with_invalid_data(format!(
"invalid magic {magic:#08x}"
)));
}
let version = reader
.read_u32()
.map(u32::from_be)
.ok_or_else(|| Error::with_invalid_data("failed to read version"))?;
if version != 0x00020001 {
return Err(Error::with_invalid_data(format!(
"unknown version {version:#08x}"
)));
}
Ok(())
}
pub fn find_path(&self, name: &str) -> Result<Option<PathBuf>> {
let mut reader = self.data;
let () = reader
.advance(8)
.ok_or_else(|| Error::with_invalid_data("failed to skip header"))?;
let mut name_bytes = name.as_bytes();
let mut offset;
loop {
offset = reader
.read_u32()
.map(u32::from_be)
.ok_or_else(|| Error::with_invalid_data("failed to read offset"))?;
let node_offset = (offset & INDEX_NODE_MASK) as usize;
if node_offset > self.data.len() {
return Err(Error::with_invalid_data("offset is out of bounds"));
}
reader = &self.data[node_offset..];
if offset & INDEX_NODE_PREFIX != 0 {
let prefix_cstr = reader
.read_cstr()
.ok_or_else(|| Error::with_invalid_data("failed to read prefix"))?;
let prefix = prefix_cstr.to_bytes();
if !name_bytes.starts_with(prefix) {
return Ok(None);
}
name_bytes = &name_bytes[prefix.len()..];
}
if offset & INDEX_NODE_CHILDS != 0 {
let first = reader
.read_u8()
.ok_or_else(|| Error::with_invalid_data("failed to read first child"))?;
let last = reader
.read_u8()
.ok_or_else(|| Error::with_invalid_data("failed to read last child"))?;
if !name_bytes.is_empty() {
let cur = name_bytes[0];
if cur < first || cur > last {
return Ok(None);
}
let () = reader
.advance(4 * (cur - first) as usize)
.ok_or_else(|| Error::with_invalid_data("failed to skip to child"))?;
name_bytes = &name_bytes[1..];
continue;
} else {
let () = reader
.advance(4 * (last - first + 1) as usize)
.ok_or_else(|| Error::with_invalid_data("failed to skip children"))?;
break;
}
} else if !name_bytes.is_empty() {
return Ok(None);
} else {
break;
}
}
if offset & INDEX_NODE_VALUES == 0 {
return Ok(None);
}
let value_count = reader
.read_u32()
.map(u32::from_be)
.ok_or_else(|| Error::with_invalid_data("failed to read value count"))?;
if value_count == 0 {
return Ok(None);
}
let () = reader
.advance(4)
.ok_or_else(|| Error::with_invalid_data("failed to skip priority"))?;
let colon_pos = reader
.iter()
.position(|&b| b == b':')
.ok_or_else(|| Error::with_invalid_data("expected string containing ':'"))?;
let path_bytes = &reader[..colon_pos];
let path = bytes_to_path(path_bytes)?;
Ok(Some(self.base_dir.join(path)))
}
}
#[cfg(test)]
mod tests {
use crate::ErrorKind;
use super::*;
use std::path::Path;
#[test]
fn load_depmod() {
let depmod = Path::new(&env!("CARGO_MANIFEST_DIR"))
.join("data")
.join("modules.dep.bin");
let file = File::open(&depmod).unwrap();
let depmod = DepmodIndex::new(PathBuf::from("/"), file).unwrap();
#[rustfmt::skip]
let test_values = [
("ecdh_generic", "/kernel/crypto/ecdh_generic.ko"),
("intel_pch_thermal", "/kernel/drivers/thermal/intel/intel_pch_thermal.ko"),
("libarc4", "/kernel/lib/crypto/libarc4.ko"),
("snd_sof", "/kernel/sound/soc/sof/snd-sof.ko"),
];
for (name, path) in test_values {
let mod_path = depmod.find_path(name).unwrap().unwrap();
assert_eq!(mod_path, Path::new(path));
}
let result = depmod.find_path("xyz").unwrap();
assert_eq!(result, None);
}
#[cfg(linux)]
#[test]
#[ignore = "test requires discoverable depmod file present"]
fn load_system_depmod() {
let result = DepmodIndex::with_system_default();
let depmod = match result {
Ok(depmod) => depmod,
Err(err) if err.kind() == ErrorKind::NotFound => return,
Err(err) => panic!("{err}"),
};
let mods = ["test_hexdump"];
for name in mods {
let _result = depmod.find_path(name).unwrap().unwrap();
}
}
}