use anyhow::{ensure, Context, Error, Result};
use colored::Colorize;
use libc::c_char;
use std::cell::RefCell;
use std::collections::HashMap;
use std::ffi::{CStr, CString};
use std::io::prelude::*;
use std::os::unix::fs::OpenOptionsExt;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::time::Instant;
use std::{fs, io};
pub mod satisfier;
mod transaction;
pub use transaction::*;
mod symbol;
pub use symbol::*;
mod expr;
pub use expr::Expr;
pub mod types;
use types::*;
pub use types::{SymbolValue, Tristate};
mod vtable;
use vtable::*;
#[derive(Debug)]
pub struct Bridge {
#[allow(dead_code)]
vtable: BridgeVTable,
pub kernel_dir: PathBuf,
pub history: RefCell<Vec<Transaction>>,
pub symbols: Vec<*mut CSymbol>,
pub name_to_symbol: HashMap<String, *mut CSymbol>,
}
impl Bridge {
pub fn new(kernel_dir: PathBuf) -> Result<Bridge> {
let (library_path, env) =
prepare_bridge(&kernel_dir).context(format!("Could not prepare bridge in {}", kernel_dir.display()))?;
let time_start = Instant::now();
print!("{:>12} bridge\r", "Initializing".cyan());
io::stdout().flush().unwrap();
let vtable = unsafe { BridgeVTable::new(library_path)? };
let env: Vec<CString> = env
.iter()
.map(|(k, v)| {
CString::new(format!("{}={}", k, v)).expect("Could not convert environment variable to CString")
})
.collect();
let mut ffi_env: Vec<*const c_char> = env.iter().map(|cstr| cstr.as_ptr()).collect();
ffi_env.push(std::ptr::null());
(vtable.c_init)(ffi_env.as_ptr());
let symbols = vtable.get_all_symbols();
let mut name_to_symbol = HashMap::new();
for symbol in &symbols {
if unsafe { (**symbol).symbol_type } == SymbolType::Unknown {
continue;
}
if let Some(name) = unsafe { (**symbol).name().map(|obj| obj.into_owned()) } {
name_to_symbol.insert(name, *symbol);
}
}
let bridge = Bridge {
vtable,
kernel_dir,
symbols,
name_to_symbol,
history: RefCell::new(Vec::new()),
};
let n_valid_symbols = bridge
.symbols
.iter()
.filter(|s| !unsafe { &***s }.name.is_null() && !unsafe { &***s }.flags.intersects(SymbolFlags::CONST))
.count();
println!(
"{:>12} bridge [kernel {}, {} symbols] in {:.2?}",
"Initialized".green(),
bridge.get_env("KERNELVERSION").unwrap(),
n_valid_symbols,
time_start.elapsed()
);
Ok(bridge)
}
pub fn wrap_symbol(&self, symbol: *mut CSymbol) -> Symbol {
Symbol {
c_symbol: symbol,
bridge: self,
}
}
pub fn symbol(&self, name: &str) -> Option<Symbol> {
self.name_to_symbol.get(name).map(|s| self.wrap_symbol(*s))
}
pub fn recalculate_all_symbols(&self) {
for symbol in &self.symbols {
if unsafe { &**symbol }.flags.intersects(SymbolFlags::CONST) {
continue;
}
let symbol = self.wrap_symbol(*symbol);
if symbol.name().is_none() {
continue;
}
symbol.recalculate();
}
}
pub fn write_config(&self, path: impl AsRef<Path>) -> Result<()> {
let c: CString = CString::new(path.as_ref().to_str().context("Invalid filename")?)?;
ensure!((self.vtable.c_conf_write)(c.as_ptr()) == 0, "Could not write config");
Ok(())
}
pub fn read_config_unchecked(&self, path: impl AsRef<Path>) -> Result<()> {
let c: CString = CString::new(path.as_ref().to_str().context("Invalid filename")?)?;
ensure!(
(self.vtable.c_conf_read_unchecked)(c.as_ptr()) == 0,
"Error while executing conf_read({:?}). Is the file accessible?",
path.as_ref()
);
Ok(())
}
pub fn get_env(&self, name: &str) -> Option<String> {
let param = CString::new(name).unwrap();
let ret = (self.vtable.c_get_env)(param.as_ptr());
if ret.is_null() {
None
} else {
Some(unsafe { CStr::from_ptr(ret) }.to_str().unwrap().to_owned())
}
}
}
fn prepare_bridge(kernel_dir: &PathBuf) -> Result<(PathBuf, EnvironMap)> {
let time_start = Instant::now();
let kconfig_dir = kernel_dir.join("scripts").join("kconfig");
let kconfig_bridge_c = kconfig_dir.join("autokernel_bridge.c");
fs::OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.mode(0o644)
.open(&kconfig_bridge_c)
.context(format!("Could not open {}", kconfig_bridge_c.display()))?
.write_all(include_bytes!("cbridge/bridge.c"))?;
let kconfig_interceptor_sh = kconfig_dir.join("autokernel_interceptor.sh");
fs::OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.mode(0o755)
.open(&kconfig_interceptor_sh)
.context(format!("Could not open {}", kconfig_interceptor_sh.display()))?
.write_all(include_bytes!("cbridge/interceptor.sh"))?;
let interceptor_shell = fs::canonicalize(&kconfig_interceptor_sh)?
.into_os_string()
.into_string()
.map_err(|e| Error::msg(format!("OsString conversion failed for {:?}", e)))?;
print!("{:>12} bridge for {}\r", "Building".cyan(), kernel_dir.display());
io::stdout().flush().unwrap();
let bridge_library = kconfig_dir.join("autokernel_bridge.so");
let builder_output = Command::new("bash")
.args(["-c", "--"])
.arg("umask 022 && make SHELL=\"$INTERCEPTOR_SHELL\" defconfig")
.env("INTERCEPTOR_SHELL", interceptor_shell)
.current_dir(kernel_dir)
.stderr(Stdio::inherit())
.output()?;
ensure!(builder_output.status.success());
let builder_output = String::from_utf8_lossy(&builder_output.stdout).to_string();
let builder_output = builder_output
.split_once("[AUTOKERNEL BRIDGE]")
.context("Interceptor output did not contain [AUTOKERNEL BRIDGE]")?
.1;
let env = serde_json::from_str(builder_output)?;
println!(
"{:>12} bridge for {} in {:.2?}",
"Built".green(),
kernel_dir.display(),
time_start.elapsed()
);
Ok((bridge_library, env))
}