mirror of https://github.com/aya-rs/aya
Merge pull request #1160 from dave-tucker/modprobe
commit
29f4f2b780
@ -0,0 +1,30 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import os
|
||||||
|
import glob
|
||||||
|
import sys
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
def find_kernels(directory: str) -> List[str]:
|
||||||
|
return glob.glob(f"{directory}/**/vmlinuz-*", recursive=True)
|
||||||
|
|
||||||
|
def find_modules_directory(directory: str, kernel: str) -> str:
|
||||||
|
matches = glob.glob(f"{directory}/**/modules/{kernel}", recursive=True)
|
||||||
|
if len(matches) != 1:
|
||||||
|
raise RuntimeError(f"Expected to find exactly one modules directory. Found {len(matches)}.")
|
||||||
|
return matches[0]
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
images = find_kernels('test/.tmp')
|
||||||
|
modules = []
|
||||||
|
|
||||||
|
for image in images:
|
||||||
|
image_name = os.path.basename(image).replace('vmlinuz-', '')
|
||||||
|
module_dir = find_modules_directory('test/.tmp', image_name)
|
||||||
|
modules.append(module_dir)
|
||||||
|
|
||||||
|
args = ' '.join(f"{image}:{module}" for image, module in zip(images, modules))
|
||||||
|
print(args)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"rust-analyzer.check.allTargets": true,
|
"rust-analyzer.check.allTargets": true,
|
||||||
"rust-analyzer.check.command": "clippy",
|
"rust-analyzer.check.command": "clippy",
|
||||||
"search.exclude": {
|
"search.exclude": {
|
||||||
"/xtask/public-api/*.txt": true,
|
"/xtask/public-api/*.txt": true
|
||||||
},
|
},
|
||||||
|
"yaml.format.singleQuote": true
|
||||||
}
|
}
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "init"
|
|
||||||
publish = false
|
|
||||||
version = "0.1.0"
|
|
||||||
|
|
||||||
authors.workspace = true
|
|
||||||
edition.workspace = true
|
|
||||||
homepage.workspace = true
|
|
||||||
license.workspace = true
|
|
||||||
repository.workspace = true
|
|
||||||
rust-version.workspace = true
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
anyhow = { workspace = true, features = ["std"] }
|
|
||||||
nix = { workspace = true, features = ["fs", "mount", "reboot"] }
|
|
@ -0,0 +1,38 @@
|
|||||||
|
[package]
|
||||||
|
name = "test-distro"
|
||||||
|
publish = false
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
authors.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
homepage.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "init"
|
||||||
|
path = "src/init.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "modprobe"
|
||||||
|
path = "src/modprobe.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "depmod"
|
||||||
|
path = "src/depmod.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = { workspace = true, features = ["std"] }
|
||||||
|
clap = { workspace = true, default-features = true, features = ["derive"] }
|
||||||
|
glob = { workspace = true }
|
||||||
|
nix = { workspace = true, features = [
|
||||||
|
"user",
|
||||||
|
"fs",
|
||||||
|
"mount",
|
||||||
|
"reboot",
|
||||||
|
"kmod",
|
||||||
|
"feature",
|
||||||
|
] }
|
||||||
|
object = { workspace = true, features = ["elf", "read_core", "std"] }
|
||||||
|
walkdir = { workspace = true }
|
||||||
|
xz2 = { workspace = true }
|
@ -0,0 +1,140 @@
|
|||||||
|
//! depmod is used to build the modules.alias file to assist with loading
|
||||||
|
//! kernel modules.
|
||||||
|
//!
|
||||||
|
//! This implementation is incredibly naive and is only designed to work within
|
||||||
|
//! the constraints of the test environment. Not for production use.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
io::{BufWriter, Read, Write as _},
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::{Context as _, anyhow};
|
||||||
|
use clap::Parser;
|
||||||
|
use object::{Object, ObjectSection, ObjectSymbol, Section};
|
||||||
|
use test_distro::resolve_modules_dir;
|
||||||
|
use walkdir::WalkDir;
|
||||||
|
use xz2::read::XzDecoder;
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
struct Args {
|
||||||
|
#[clap(long, short)]
|
||||||
|
base_dir: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> anyhow::Result<()> {
|
||||||
|
let Args { base_dir } = Parser::parse();
|
||||||
|
|
||||||
|
let modules_dir = if let Some(base_dir) = base_dir {
|
||||||
|
base_dir
|
||||||
|
} else {
|
||||||
|
resolve_modules_dir().context("failed to resolve modules dir")?
|
||||||
|
};
|
||||||
|
|
||||||
|
let modules_alias = modules_dir.join("modules.alias");
|
||||||
|
let f = std::fs::OpenOptions::new()
|
||||||
|
.create(true)
|
||||||
|
.write(true)
|
||||||
|
.truncate(true)
|
||||||
|
.open(&modules_alias)
|
||||||
|
.with_context(|| format!("failed to open: {}", modules_alias.display()))?;
|
||||||
|
let mut output = BufWriter::new(&f);
|
||||||
|
for entry in WalkDir::new(modules_dir) {
|
||||||
|
let entry = entry.context("failed to read entry in walkdir")?;
|
||||||
|
if entry.file_type().is_file() {
|
||||||
|
let path = entry.path();
|
||||||
|
|
||||||
|
let module_name = path
|
||||||
|
.file_name()
|
||||||
|
.ok_or_else(|| anyhow!("{} does not have a file name", path.display()))?
|
||||||
|
.to_str()
|
||||||
|
.ok_or_else(|| anyhow!("{} is not valid utf-8", path.display()))?;
|
||||||
|
|
||||||
|
let (module_name, compressed) =
|
||||||
|
if let Some(module_name) = module_name.strip_suffix(".xz") {
|
||||||
|
(module_name, true)
|
||||||
|
} else {
|
||||||
|
(module_name, false)
|
||||||
|
};
|
||||||
|
|
||||||
|
let module_name = if let Some(module_name) = module_name.strip_suffix(".ko") {
|
||||||
|
module_name
|
||||||
|
} else {
|
||||||
|
// Not a kernel module
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut f =
|
||||||
|
File::open(path).with_context(|| format!("failed to open: {}", path.display()))?;
|
||||||
|
let stat = f
|
||||||
|
.metadata()
|
||||||
|
.with_context(|| format!("failed to get metadata for {}", path.display()))?;
|
||||||
|
|
||||||
|
if compressed {
|
||||||
|
let mut decoder = XzDecoder::new(f);
|
||||||
|
// We don't know the size of the decompressed data, so we assume it's
|
||||||
|
// no more than twice the size of the compressed data.
|
||||||
|
let mut decompressed = Vec::with_capacity(stat.len() as usize * 2);
|
||||||
|
decoder.read_to_end(&mut decompressed)?;
|
||||||
|
read_aliases_from_module(&decompressed, module_name, &mut output)
|
||||||
|
} else {
|
||||||
|
let mut buf = Vec::with_capacity(stat.len() as usize);
|
||||||
|
f.read_to_end(&mut buf)
|
||||||
|
.with_context(|| format!("failed to read: {}", path.display()))?;
|
||||||
|
read_aliases_from_module(&buf, module_name, &mut output)
|
||||||
|
}
|
||||||
|
.with_context(|| format!("failed to read aliases from module {}", path.display()))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_aliases_from_module(
|
||||||
|
contents: &[u8],
|
||||||
|
module_name: &str,
|
||||||
|
output: &mut BufWriter<&File>,
|
||||||
|
) -> Result<(), anyhow::Error> {
|
||||||
|
let obj = object::read::File::parse(contents).context("failed to parse")?;
|
||||||
|
|
||||||
|
let section = (|| -> anyhow::Result<Option<Section<'_, '_, &[u8]>>> {
|
||||||
|
for s in obj.sections() {
|
||||||
|
let name = s
|
||||||
|
.name_bytes()
|
||||||
|
.with_context(|| format!("failed to get name of section idx {}", s.index()))?;
|
||||||
|
if name == b".modinfo" {
|
||||||
|
return Ok(Some(s));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(None)
|
||||||
|
})()?;
|
||||||
|
let section = section.context("failed to find .modinfo section")?;
|
||||||
|
let section_idx = section.index();
|
||||||
|
let data = section
|
||||||
|
.data()
|
||||||
|
.context("failed to get modinfo section data")?;
|
||||||
|
|
||||||
|
for s in obj.symbols() {
|
||||||
|
if s.section_index() != Some(section_idx) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let name = s
|
||||||
|
.name()
|
||||||
|
.with_context(|| format!("failed to get name of symbol idx {}", s.index()))?;
|
||||||
|
if name.contains("alias") {
|
||||||
|
let start = s.address() as usize;
|
||||||
|
let end = start + s.size() as usize;
|
||||||
|
let sym_data = &data[start..end];
|
||||||
|
let cstr = std::ffi::CStr::from_bytes_with_nul(sym_data)
|
||||||
|
.with_context(|| format!("failed to convert {:?} to cstr", sym_data))?;
|
||||||
|
let sym_str = cstr
|
||||||
|
.to_str()
|
||||||
|
.with_context(|| format!("failed to convert {:?} to str", cstr))?;
|
||||||
|
let alias = sym_str
|
||||||
|
.strip_prefix("alias=")
|
||||||
|
.with_context(|| format!("failed to strip prefix 'alias=' from {}", sym_str))?;
|
||||||
|
writeln!(output, "alias {} {}", alias, module_name).expect("write");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use anyhow::Context as _;
|
||||||
|
use nix::sys::utsname::uname;
|
||||||
|
|
||||||
|
/// Kernel modules are in `/lib/modules`.
|
||||||
|
/// They may be in the root of this directory,
|
||||||
|
/// or in subdirectory named after the kernel release.
|
||||||
|
pub fn resolve_modules_dir() -> anyhow::Result<PathBuf> {
|
||||||
|
let modules_dir = PathBuf::from("/lib/modules");
|
||||||
|
let stat = modules_dir
|
||||||
|
.metadata()
|
||||||
|
.with_context(|| format!("stat(): {}", modules_dir.display()))?;
|
||||||
|
if stat.is_dir() {
|
||||||
|
return Ok(modules_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
let utsname = uname().context("uname()")?;
|
||||||
|
let release = utsname.release();
|
||||||
|
let modules_dir = modules_dir.join(release);
|
||||||
|
let stat = modules_dir
|
||||||
|
.metadata()
|
||||||
|
.with_context(|| format!("stat(): {}", modules_dir.display()))?;
|
||||||
|
anyhow::ensure!(
|
||||||
|
stat.is_dir(),
|
||||||
|
"{} is not a directory",
|
||||||
|
modules_dir.display()
|
||||||
|
);
|
||||||
|
Ok(modules_dir)
|
||||||
|
}
|
@ -0,0 +1,140 @@
|
|||||||
|
//! modprobe is used to load kernel modules into the kernel.
|
||||||
|
//!
|
||||||
|
//! This implementation is incredibly naive and is only designed to work within
|
||||||
|
//! the constraints of the test environment. Not for production use.
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
io::{BufRead as _, Read as _},
|
||||||
|
path::Path,
|
||||||
|
};
|
||||||
|
|
||||||
|
use anyhow::{Context as _, anyhow, bail};
|
||||||
|
use clap::Parser;
|
||||||
|
use glob::glob;
|
||||||
|
use nix::kmod::init_module;
|
||||||
|
use test_distro::resolve_modules_dir;
|
||||||
|
|
||||||
|
macro_rules! output {
|
||||||
|
($quiet:expr, $($arg:tt)*) => {
|
||||||
|
if !$quiet {
|
||||||
|
println!($($arg)*);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
struct Args {
|
||||||
|
/// Suppress all output and don't return an error code.
|
||||||
|
#[clap(short, long, default_value = "false")]
|
||||||
|
quiet: bool,
|
||||||
|
|
||||||
|
/// The name of the module to load.
|
||||||
|
/// This can be either an alias like `net-sched-sch-ingress` or a module
|
||||||
|
/// name like `sch_ingress`.
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() -> anyhow::Result<()> {
|
||||||
|
let Args { quiet, name } = Parser::parse();
|
||||||
|
let ret = try_main(quiet, name);
|
||||||
|
if quiet { Ok(()) } else { ret }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_main(quiet: bool, name: String) -> anyhow::Result<()> {
|
||||||
|
let modules_dir = resolve_modules_dir()?;
|
||||||
|
|
||||||
|
output!(quiet, "resolving alias for module: {}", name);
|
||||||
|
let module = resolve_alias(quiet, &modules_dir, &name)?;
|
||||||
|
|
||||||
|
let pattern = format!(
|
||||||
|
"{}/kernel/**/{}.ko*",
|
||||||
|
modules_dir
|
||||||
|
.to_str()
|
||||||
|
.ok_or_else(|| anyhow!("failed to convert {} to string", modules_dir.display()))?,
|
||||||
|
module
|
||||||
|
);
|
||||||
|
let module_path = glob(&pattern)
|
||||||
|
.with_context(|| format!("failed to glob: {}", pattern))?
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| anyhow!("module not found: {}", module))?
|
||||||
|
.context("glob error")?;
|
||||||
|
|
||||||
|
output!(quiet, "loading module: {}", module_path.display());
|
||||||
|
let mut f =
|
||||||
|
File::open(&module_path).with_context(|| format!("open(): {}", module_path.display()))?;
|
||||||
|
|
||||||
|
let stat = f
|
||||||
|
.metadata()
|
||||||
|
.with_context(|| format!("stat(): {}", module_path.display()))?;
|
||||||
|
|
||||||
|
let extension = module_path
|
||||||
|
.as_path()
|
||||||
|
.extension()
|
||||||
|
.ok_or_else(|| anyhow!("module has no extension: {}", module_path.display()))?;
|
||||||
|
|
||||||
|
let contents = if extension == "xz" {
|
||||||
|
output!(quiet, "decompressing module");
|
||||||
|
let mut decompressed = Vec::with_capacity(stat.len() as usize * 2);
|
||||||
|
xz2::read::XzDecoder::new(f).read_to_end(&mut decompressed)?;
|
||||||
|
decompressed
|
||||||
|
} else {
|
||||||
|
let mut contents: Vec<u8> = Vec::with_capacity(stat.len() as usize);
|
||||||
|
f.read_to_end(&mut contents)?;
|
||||||
|
contents
|
||||||
|
};
|
||||||
|
|
||||||
|
if !contents.starts_with(&[0x7f, 0x45, 0x4c, 0x46]) {
|
||||||
|
bail!("module is not an valid ELF file");
|
||||||
|
}
|
||||||
|
|
||||||
|
match init_module(&contents, c"") {
|
||||||
|
Ok(()) => {
|
||||||
|
output!(quiet, "module loaded successfully");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
if e == nix::errno::Errno::EEXIST {
|
||||||
|
Err(anyhow!("module already loaded"))
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("failed to load module: {}", e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_alias(quiet: bool, module_dir: &Path, name: &str) -> anyhow::Result<String> {
|
||||||
|
let modules_alias = module_dir.join("modules.alias");
|
||||||
|
output!(
|
||||||
|
quiet,
|
||||||
|
"opening modules.alias file: {}",
|
||||||
|
modules_alias.display()
|
||||||
|
);
|
||||||
|
let alias_file = File::open(&modules_alias)
|
||||||
|
.with_context(|| format!("open(): {}", modules_alias.display()))?;
|
||||||
|
let alias_file = std::io::BufReader::new(alias_file);
|
||||||
|
|
||||||
|
for line in alias_file.lines() {
|
||||||
|
let line = line?;
|
||||||
|
if line.starts_with("alias ") {
|
||||||
|
let mut parts = line.split_whitespace();
|
||||||
|
let prefix = parts.next();
|
||||||
|
if prefix != Some("alias") {
|
||||||
|
bail!("alias line incorrect prefix: {}", line);
|
||||||
|
}
|
||||||
|
let alias = parts
|
||||||
|
.next()
|
||||||
|
.with_context(|| format!("alias line missing alias: {}", line))?;
|
||||||
|
let module = parts
|
||||||
|
.next()
|
||||||
|
.with_context(|| format!("alias line missing module: {}", line))?;
|
||||||
|
if parts.next().is_some() {
|
||||||
|
bail!("alias line has too many parts: {}", line);
|
||||||
|
}
|
||||||
|
if alias == name {
|
||||||
|
return Ok(module.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bail!("alias not found: {}", name)
|
||||||
|
}
|
Loading…
Reference in New Issue