use crate::{
error::{text::*, ModuleError},
extensions::FileExt,
system::{UEvent, UEventAction},
util::{read_uevent, write_uevent, MODULE_PATH, SYSFS_PATH},
};
#[cfg(feature = "gz")]
use flate2::bufread::GzDecoder;
use nix::{
kmod::{delete_module, finit_module, init_module, DeleteModuleFlags, ModuleInitFlags},
sys::utsname::uname,
};
use std::{
collections::HashMap,
ffi::CString,
fs,
fs::DirEntry,
io::{prelude::*, BufRead},
path::{Path, PathBuf},
};
use walkdir::WalkDir;
use xmas_elf::ElfFile;
#[cfg(feature = "xz")]
use xz2::bufread::XzDecoder;
#[cfg(feature = "zst")]
use zstd::stream::read::Decoder as ZstDecoder;
const SIGNATURE_MAGIC: &[u8] = b"~Module signature appended~\n";
pub type Result<T, E = ModuleError> = std::result::Result<T, E>;
#[derive(Debug, Clone, Copy)]
pub enum Taint {
Proprietary,
OutOfTree,
Forced,
Staging,
Unsigned,
}
#[derive(Debug, Clone, Copy)]
pub enum Type {
BuiltIn,
Dynamic,
}
#[derive(Debug, Clone)]
pub enum Status {
Live,
Coming,
Going,
Unknown(String),
}
#[derive(Debug)]
pub struct LoadedModule {
name: String,
module_type: Type,
path: PathBuf,
parameters: HashMap<String, Vec<u8>>,
ref_count: Option<u32>,
taint: Option<Taint>,
status: Option<Status>,
size: u64,
holders: Vec<Self>,
}
impl LoadedModule {
pub fn refresh(&mut self) -> Result<()> {
let mut map = HashMap::new();
let par = self.path.join("parameters");
if par.exists() {
for entry in fs::read_dir(par)? {
let entry: DirEntry = entry?;
map.insert(
entry
.file_name()
.into_string()
.map_err(|_| ModuleError::InvalidModule(PARAMETER.into()))?,
fs::read(entry.path()).unwrap_or_default(),
);
}
}
self.parameters = map;
self.ref_count = fs::read_to_string(self.path.join("refcnt"))
.map(|s| s.trim().parse())?
.ok();
self.taint = match fs::read_to_string(self.path.join("taint"))?.trim() {
"P" => Some(Taint::Proprietary),
"O" => Some(Taint::OutOfTree),
"F" => Some(Taint::Forced),
"C" => Some(Taint::Staging),
"E" => Some(Taint::Unsigned),
_ => None,
};
self.status = Some(
match fs::read_to_string(self.path.join("initstate"))?.trim() {
"live" => Status::Live,
"coming" => Status::Coming,
"going" => Status::Going,
s => Status::Unknown(s.into()),
},
);
self.size = fs::read_to_string(self.path.join("coresize"))
.map(|s| s.trim().parse())?
.map_err(|_| ModuleError::InvalidModule(PARAMETER.into()))?;
let mut v = Vec::new();
for re in fs::read_dir(self.path.join("holders"))? {
let re: DirEntry = re?;
v.push(Self::from_dir(&re.path())?)
}
self.holders = v;
Ok(())
}
pub fn from_name(name: &str) -> Result<Self> {
Self::from_dir(&Path::new(SYSFS_PATH).join("module").join(name))
}
pub fn get_loaded() -> Result<Vec<Self>> {
let dir = Path::new(SYSFS_PATH).join("module");
let mut mods = Vec::new();
for module in fs::read_dir(dir)? {
let module: DirEntry = module?;
let m = Self::from_dir(&module.path())?;
if let Type::BuiltIn = m.module_type() {
continue;
}
mods.push(m);
}
Ok(mods)
}
pub fn unload(self) -> Result<()> {
delete_module(
&CString::new(self.name.as_str()).unwrap(),
DeleteModuleFlags::O_NONBLOCK,
)
.map_err(|e| ModuleError::UnloadError(self.name, e.to_string()))?;
Ok(())
}
pub unsafe fn force_unload(self) -> Result<()> {
delete_module(
&CString::new(self.name.as_str()).unwrap(),
DeleteModuleFlags::O_NONBLOCK | DeleteModuleFlags::O_TRUNC,
)
.map_err(|e| ModuleError::UnloadError(self.name, e.to_string()))?;
Ok(())
}
pub fn name(&self) -> &str {
&self.name
}
pub fn module_type(&self) -> Type {
self.module_type
}
pub fn parameters(&self) -> &HashMap<String, Vec<u8>> {
&self.parameters
}
pub fn ref_count(&self) -> Option<u32> {
self.ref_count
}
pub fn size(&self) -> u64 {
self.size
}
pub fn taint(&self) -> Option<Taint> {
self.taint
}
pub fn holders(&self) -> &Vec<Self> {
&self.holders
}
pub fn module_file(&self) -> Result<ModuleFile> {
ModuleFile::from_name(&self.name)
}
pub fn status(&self) -> &Status {
self.status.as_ref().unwrap()
}
}
impl LoadedModule {
fn from_dir(path: &Path) -> Result<Self> {
let name = path
.file_name()
.expect("Missing module name")
.to_str()
.expect("Invalid module name");
let path = path.with_file_name(name.replace('-', "_"));
if !path.exists() {
return Err(ModuleError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
format!("Couldn't find loaded module at {}", path.display()),
)));
}
let module_type = if path.join("coresize").exists() {
Type::Dynamic
} else {
Type::BuiltIn
};
let mut s = Self {
name: path
.file_stem()
.and_then(|s| s.to_str())
.map(|s| s.trim().to_owned())
.ok_or_else(|| ModuleError::InvalidModule(NAME.into()))?,
module_type,
path,
parameters: HashMap::new(),
ref_count: None,
taint: None,
status: None,
size: 0,
holders: Vec::new(),
};
if let Type::Dynamic = s.module_type {
s.refresh()?;
}
Ok(s)
}
}
impl UEvent for LoadedModule {
fn write(&self, action: UEventAction, uuid: Option<String>, args: HashMap<String, String>) {
write_uevent(&self.path.join("uevent"), action, uuid, args)
}
fn read(&self) -> HashMap<String, String> {
read_uevent(&self.path.join("uevent"))
}
}
#[derive(Debug)]
pub struct ModuleFile {
name: String,
path: PathBuf,
info: Option<ModInfo>,
signature: bool,
}
impl ModuleFile {
pub fn refresh(&mut self) -> Result<()> {
let img = self.read()?;
self.info = Some(self._info(&img)?);
self.signature = img.ends_with(SIGNATURE_MAGIC);
Ok(())
}
pub fn from_name(name: &str) -> Result<Self> {
Self::from_name_with_uname(name, uname().release())
}
pub fn from_name_with_uname(name: &str, uname: &str) -> Result<Self> {
let path = Path::new(MODULE_PATH).join(uname);
for entry in WalkDir::new(path) {
let entry = entry.map_err(|e| ModuleError::Io(e.into()))?;
if !entry.file_type().is_file() {
continue;
}
let m_name = entry
.path()
.file_stem()
.and_then(|s| s.to_str())
.and_then(|s| s.splitn(2, '.').next())
.ok_or_else(|| ModuleError::InvalidModule(INVALID_EXTENSION.into()))?;
if m_name == name {
let mut s = Self {
name: name.into(),
path: entry.into_path(),
info: None,
signature: false,
};
s.refresh()?;
return Ok(s);
}
}
Err(ModuleError::LoadError(name.into(), NOT_FOUND.into()))
}
pub fn from_path(path: &Path) -> Result<Self> {
let mut s = Self {
name: path
.file_stem()
.and_then(|s| s.to_str())
.ok_or_else(|| {
ModuleError::LoadError(path.display().to_string(), NOT_FOUND.into())
})?
.into(),
path: path.into(),
info: None,
signature: false,
};
s.refresh()?;
Ok(s)
}
pub fn load(&self, param: &str) -> Result<LoadedModule> {
let img = self.read()?;
init_module(
&img,
&CString::new(param).expect("param can't have internal null bytes"),
)
.map_err(|e| ModuleError::LoadError(self.name.clone(), e.to_string()))?;
Ok(LoadedModule::from_dir(
&Path::new(SYSFS_PATH).join("module").join(&self.name),
)?)
}
pub unsafe fn force_load(&self, param: &str) -> Result<LoadedModule> {
let mut file = fs::File::create_memory("decompressed module");
file.write_all(&self.read()?)?;
finit_module(
&file,
&CString::new(param).expect("param can't have internal null bytes"),
ModuleInitFlags::MODULE_INIT_IGNORE_MODVERSIONS
| ModuleInitFlags::MODULE_INIT_IGNORE_VERMAGIC,
)
.map_err(|e| ModuleError::LoadError(self.name.clone(), e.to_string()))?;
Ok(LoadedModule::from_dir(
&Path::new(SYSFS_PATH).join("module").join(&self.name),
)?)
}
pub fn path(&self) -> &Path {
&self.path
}
pub fn name(&self) -> &str {
&self.name
}
pub fn info(&self) -> &ModInfo {
self.info.as_ref().unwrap()
}
pub fn has_signature(&self) -> bool {
self.signature
}
}
impl ModuleFile {
fn read(&self) -> Result<Vec<u8>> {
self.decompress(fs::read(&self.path)?)
}
fn _info(&self, img: &[u8]) -> Result<ModInfo> {
let elf = ElfFile::new(img).map_err(|e| ModuleError::InvalidModule(e.to_string()))?;
let sect = elf
.find_section_by_name(".modinfo")
.ok_or_else(|| ModuleError::InvalidModule(MODINFO.into()))?;
let data = sect.raw_data(&elf);
let mut map = HashMap::new();
for kv in BufRead::split(data, b'\0') {
let kv = kv?;
let s = String::from_utf8(kv).map_err(|e| ModuleError::InvalidModule(e.to_string()))?;
let mut s = s.splitn(2, '=');
let key = s
.next()
.map(|s| s.to_string())
.ok_or_else(|| ModuleError::InvalidModule(MODINFO.into()))?;
let value = s
.next()
.map(|s| s.to_string())
.ok_or_else(|| ModuleError::InvalidModule(MODINFO.into()))?;
let vec = map.entry(key).or_insert_with(Vec::new);
if !value.is_empty() {
vec.push(value);
}
}
fn y_n(s: &str) -> bool {
s == "Y" || s == "y"
}
fn one(map: &mut HashMap<String, Vec<String>>, key: &str) -> String {
map.remove(key).map(|mut v| v.remove(0)).unwrap_or_default()
}
fn more(map: &mut HashMap<String, Vec<String>>, key: &str) -> Vec<String> {
map.remove(key).unwrap_or_default()
}
let mut x = HashMap::new();
for (name, typ) in map
.remove("parmtype")
.unwrap_or_default()
.into_iter()
.map(|s| {
let mut i = s.splitn(2, ':').map(|s| s.trim().to_owned());
(i.next(), i.next())
})
{
let name: Option<String> = name;
let typ: Option<String> = typ;
let name = name.ok_or_else(|| ModuleError::InvalidModule(MODINFO.into()))?;
let typ = typ.ok_or_else(|| ModuleError::InvalidModule(MODINFO.into()))?;
if x.insert(name, (typ, None)).is_some() {
return Err(ModuleError::InvalidModule(MODINFO.into()));
};
}
for (name, desc) in map.remove("parm").unwrap_or_default().into_iter().map(|s| {
let mut i = s.splitn(2, ':').map(|s| s.trim().to_owned());
(i.next(), i.next())
}) {
let name: Option<String> = name;
let desc: Option<String> = desc;
let name = name.ok_or_else(|| ModuleError::InvalidModule(MODINFO.into()))?;
x.entry(name).or_insert(("Unknown".into(), None)).1 = desc;
}
let mut parameters = Vec::new();
for (name, (type_, description)) in x {
parameters.push(ModParam {
name,
type_,
description,
})
}
Ok(ModInfo {
alias: more(&mut map, "alias"),
soft_dependencies: more(&mut map, "softdep"),
license: one(&mut map, "license"),
authors: more(&mut map, "author"),
description: one(&mut map, "description"),
version: one(&mut map, "version"),
firmware: more(&mut map, "firmware"),
version_magic: one(&mut map, "vermagic"),
name: one(&mut map, "name"),
in_tree: y_n(&one(&mut map, "intree")),
retpoline: y_n(&one(&mut map, "retpoline")),
staging: y_n(&one(&mut map, "staging")),
dependencies: more(&mut map, "depends"),
source_checksum: one(&mut map, "srcversion"),
parameters,
})
}
fn decompress(&self, data: Vec<u8>) -> Result<Vec<u8>> {
#[cfg(any(feature = "xz", feature = "gz", feature = "zst"))]
let mut v = Vec::new();
let ext = self
.path
.extension()
.and_then(|e| e.to_str())
.ok_or_else(|| ModuleError::InvalidModule(INVALID_EXTENSION.into()))?;
match ext {
#[cfg(feature = "xz")]
"xz" => {
let mut data = XzDecoder::new(data.as_slice());
data.read_to_end(&mut v)
.map_err(|e| ModuleError::InvalidModule(e.to_string()))?;
Ok(v)
}
#[cfg(feature = "gz")]
"gz" => {
let mut data = GzDecoder::new(data.as_slice());
data.read_to_end(&mut v)
.map_err(|e| ModuleError::InvalidModule(e.to_string()))?;
Ok(v)
}
#[cfg(feature = "zst")]
"zst" => {
let mut data = ZstDecoder::new(data.as_slice())
.map_err(|_| ModuleError::InvalidModule(COMPRESSION.into()))?;
data.read_to_end(&mut v)
.map_err(|e| ModuleError::InvalidModule(e.to_string()))?;
Ok(v)
}
"ko" => Ok(data),
_ => Err(ModuleError::InvalidModule(COMPRESSION.into())),
}
}
}
#[derive(Debug, Clone)]
pub struct ModParam {
pub name: String,
pub type_: String,
pub description: Option<String>,
}
#[derive(Debug)]
pub struct ModInfo {
pub alias: Vec<String>,
pub soft_dependencies: Vec<String>,
pub license: String,
pub authors: Vec<String>,
pub description: String,
pub version: String,
pub firmware: Vec<String>,
pub version_magic: String,
pub name: String,
pub in_tree: bool,
pub retpoline: bool,
pub staging: bool,
pub dependencies: Vec<String>,
pub source_checksum: String,
pub parameters: Vec<ModParam>,
}