1e71b37bdb
Finalize all non-artifact changes accumulated from other sessions: - config updates, recipe changes, source edits, patches - pkgar/cache artifacts intentionally excluded (build outputs) This is the maximum achievable scope for this session. Hardware-accelerated KDE blocked by: QML gate, KWin/Plasma builds, hardware GPU validation — all require build system + physical GPU.
639 lines
18 KiB
Rust
639 lines
18 KiB
Rust
use std::collections::HashMap;
|
|
use std::env;
|
|
use std::fs;
|
|
use std::path::{Path, PathBuf};
|
|
use std::process;
|
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
|
|
use log::{LevelFilter, Metadata, Record};
|
|
use redox_scheme::scheme::SchemeSync;
|
|
use redox_scheme::{CallerCtx, OpenResult};
|
|
#[cfg(target_os = "redox")]
|
|
use redox_scheme::scheme::SchemeState;
|
|
#[cfg(target_os = "redox")]
|
|
use redox_scheme::{SignalBehavior, Socket};
|
|
use syscall::error::{Error, Result, EACCES, EBADF, EINVAL, ENOENT, EROFS};
|
|
use syscall::flag::{EventFlags, MODE_DIR, MODE_FILE, O_ACCMODE, O_RDONLY};
|
|
use syscall::schemev2::NewFdFlags;
|
|
use syscall::Stat;
|
|
|
|
const SCHEME_NAME: &str = "driver-params";
|
|
const SCHEME_ROOT_ID: usize = 1;
|
|
|
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
|
enum ParamKind {
|
|
Bool,
|
|
Integer,
|
|
Text,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
struct ParamEntry {
|
|
kind: ParamKind,
|
|
value: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
enum HandleKind {
|
|
Root,
|
|
DriverDir(String),
|
|
Parameter {
|
|
driver_name: String,
|
|
parameter_name: String,
|
|
},
|
|
}
|
|
|
|
struct DriverParamsScheme {
|
|
drivers: HashMap<String, HashMap<String, ParamEntry>>,
|
|
handles: HashMap<usize, HandleKind>,
|
|
next_id: usize,
|
|
bound_path: PathBuf,
|
|
}
|
|
|
|
impl ParamKind {
|
|
fn infer(value: &str) -> Self {
|
|
if matches!(value, "true" | "false") {
|
|
Self::Bool
|
|
} else if value.parse::<i64>().is_ok() {
|
|
Self::Integer
|
|
} else {
|
|
Self::Text
|
|
}
|
|
}
|
|
|
|
fn normalize(self, value: &str) -> Result<String> {
|
|
match self {
|
|
Self::Bool => match value {
|
|
"true" | "false" => Ok(value.to_string()),
|
|
_ => Err(Error::new(EINVAL)),
|
|
},
|
|
Self::Integer => value
|
|
.parse::<i64>()
|
|
.map(|parsed| parsed.to_string())
|
|
.map_err(|_| Error::new(EINVAL)),
|
|
Self::Text => Ok(value.to_string()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl DriverParamsScheme {
|
|
fn new(bound_path: PathBuf) -> Self {
|
|
Self {
|
|
drivers: HashMap::new(),
|
|
handles: HashMap::new(),
|
|
next_id: SCHEME_ROOT_ID + 1,
|
|
bound_path,
|
|
}
|
|
}
|
|
|
|
fn alloc_handle(&mut self, kind: HandleKind) -> usize {
|
|
let id = self.next_id;
|
|
self.next_id += 1;
|
|
self.handles.insert(id, kind);
|
|
id
|
|
}
|
|
|
|
fn handle(&self, id: usize) -> Result<&HandleKind> {
|
|
self.handles.get(&id).ok_or(Error::new(EBADF))
|
|
}
|
|
|
|
fn read_driver_manager_bound(&mut self) {
|
|
match fs::read_to_string(&self.bound_path) {
|
|
Ok(contents) => {
|
|
for driver_name in Self::discover_driver_names(&contents) {
|
|
self.drivers
|
|
.entry(driver_name)
|
|
.or_default()
|
|
.entry("enabled".to_string())
|
|
.or_insert_with(|| ParamEntry {
|
|
kind: ParamKind::Bool,
|
|
value: "true".to_string(),
|
|
});
|
|
}
|
|
}
|
|
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {}
|
|
Err(err) => log::warn!(
|
|
"driver-params: failed to read {}: {err}",
|
|
self.bound_path.display()
|
|
),
|
|
}
|
|
}
|
|
|
|
fn discover_driver_names(contents: &str) -> Vec<String> {
|
|
let mut discovered = Vec::new();
|
|
|
|
for line in contents.lines().map(str::trim).filter(|line| !line.is_empty()) {
|
|
if let Some(driver_name) = Self::extract_driver_name(line) {
|
|
if !discovered.iter().any(|existing| existing == &driver_name) {
|
|
discovered.push(driver_name);
|
|
}
|
|
}
|
|
}
|
|
|
|
discovered
|
|
}
|
|
|
|
fn extract_driver_name(line: &str) -> Option<String> {
|
|
for key in ["driver", "name"] {
|
|
if let Some(value) = Self::extract_assignment_value(line, key) {
|
|
return Some(value);
|
|
}
|
|
}
|
|
|
|
if let Some((_, tail)) = line.rsplit_once("->") {
|
|
let candidate = tail.split_whitespace().next().unwrap_or_default();
|
|
if Self::valid_component(candidate) {
|
|
return Some(candidate.to_string());
|
|
}
|
|
}
|
|
|
|
if Self::valid_component(line) {
|
|
return Some(line.to_string());
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
fn extract_assignment_value(line: &str, key: &str) -> Option<String> {
|
|
let needle = format!("{key}=");
|
|
let start = line.find(&needle)? + needle.len();
|
|
let tail = &line[start..];
|
|
let candidate = tail
|
|
.split(|ch: char| ch.is_whitespace() || ch == ',' || ch == ';')
|
|
.next()
|
|
.unwrap_or_default()
|
|
.trim_matches('"');
|
|
|
|
Self::valid_component(candidate).then(|| candidate.to_string())
|
|
}
|
|
|
|
fn valid_component(value: &str) -> bool {
|
|
!value.is_empty()
|
|
&& value
|
|
.chars()
|
|
.all(|ch| ch.is_ascii_alphanumeric() || matches!(ch, '_' | '-' | '.'))
|
|
}
|
|
|
|
fn sorted_driver_names(&self) -> Vec<String> {
|
|
let mut drivers = self.drivers.keys().cloned().collect::<Vec<_>>();
|
|
drivers.sort_unstable();
|
|
drivers
|
|
}
|
|
|
|
fn sorted_parameter_names(&self, driver_name: &str) -> Result<Vec<String>> {
|
|
let mut names = self
|
|
.drivers
|
|
.get(driver_name)
|
|
.ok_or(Error::new(ENOENT))?
|
|
.keys()
|
|
.cloned()
|
|
.collect::<Vec<_>>();
|
|
names.sort_unstable();
|
|
Ok(names)
|
|
}
|
|
|
|
fn list_output(values: &[String]) -> String {
|
|
if values.is_empty() {
|
|
String::new()
|
|
} else {
|
|
format!("{}\n", values.join("\n"))
|
|
}
|
|
}
|
|
|
|
fn read_handle_string(&mut self, kind: &HandleKind) -> Result<String> {
|
|
self.read_driver_manager_bound();
|
|
|
|
match kind {
|
|
HandleKind::Root => Ok(Self::list_output(&self.sorted_driver_names())),
|
|
HandleKind::DriverDir(driver_name) => {
|
|
Ok(Self::list_output(&self.sorted_parameter_names(driver_name)?))
|
|
}
|
|
HandleKind::Parameter {
|
|
driver_name,
|
|
parameter_name,
|
|
} => {
|
|
let entry = self
|
|
.drivers
|
|
.get(driver_name)
|
|
.and_then(|parameters| parameters.get(parameter_name))
|
|
.ok_or(Error::new(ENOENT))?;
|
|
Ok(format!("{}\n", entry.value))
|
|
}
|
|
}
|
|
}
|
|
|
|
fn parameter_handle(
|
|
&mut self,
|
|
driver_name: &str,
|
|
parameter_name: &str,
|
|
write_intent: bool,
|
|
) -> Result<HandleKind> {
|
|
if !Self::valid_component(driver_name) || !Self::valid_component(parameter_name) {
|
|
return Err(Error::new(ENOENT));
|
|
}
|
|
|
|
if !self.drivers.contains_key(driver_name) {
|
|
if !write_intent {
|
|
return Err(Error::new(ENOENT));
|
|
}
|
|
|
|
self.drivers.insert(driver_name.to_string(), HashMap::new());
|
|
}
|
|
|
|
if !self
|
|
.drivers
|
|
.get(driver_name)
|
|
.and_then(|parameters| parameters.get(parameter_name))
|
|
.is_some()
|
|
&& !write_intent
|
|
{
|
|
return Err(Error::new(ENOENT));
|
|
}
|
|
|
|
Ok(HandleKind::Parameter {
|
|
driver_name: driver_name.to_string(),
|
|
parameter_name: parameter_name.to_string(),
|
|
})
|
|
}
|
|
|
|
fn open_from_root(&mut self, path: &str, write_intent: bool) -> Result<HandleKind> {
|
|
let trimmed = path.trim_matches('/');
|
|
if trimmed.is_empty() {
|
|
return Ok(HandleKind::Root);
|
|
}
|
|
|
|
let segments = trimmed
|
|
.split('/')
|
|
.filter(|segment| !segment.is_empty())
|
|
.collect::<Vec<_>>();
|
|
|
|
match segments.as_slice() {
|
|
[driver_name] => {
|
|
if !Self::valid_component(driver_name) {
|
|
return Err(Error::new(ENOENT));
|
|
}
|
|
if self.drivers.contains_key(*driver_name) {
|
|
Ok(HandleKind::DriverDir((*driver_name).to_string()))
|
|
} else {
|
|
Err(Error::new(ENOENT))
|
|
}
|
|
}
|
|
[driver_name, parameter_name] => {
|
|
self.parameter_handle(driver_name, parameter_name, write_intent)
|
|
}
|
|
_ => Err(Error::new(ENOENT)),
|
|
}
|
|
}
|
|
|
|
fn open_from_driver(
|
|
&mut self,
|
|
driver_name: &str,
|
|
path: &str,
|
|
write_intent: bool,
|
|
) -> Result<HandleKind> {
|
|
let trimmed = path.trim_matches('/');
|
|
if trimmed.is_empty() {
|
|
return Ok(HandleKind::DriverDir(driver_name.to_string()));
|
|
}
|
|
|
|
if trimmed.contains('/') {
|
|
return Err(Error::new(ENOENT));
|
|
}
|
|
|
|
self.parameter_handle(driver_name, trimmed, write_intent)
|
|
}
|
|
|
|
fn parse_write_value(buf: &[u8]) -> Result<String> {
|
|
let value = std::str::from_utf8(buf)
|
|
.map_err(|_| Error::new(EINVAL))?
|
|
.trim()
|
|
.to_string();
|
|
|
|
if value.is_empty() {
|
|
return Err(Error::new(EINVAL));
|
|
}
|
|
|
|
Ok(value)
|
|
}
|
|
}
|
|
|
|
impl SchemeSync for DriverParamsScheme {
|
|
fn scheme_root(&mut self) -> Result<usize> {
|
|
Ok(SCHEME_ROOT_ID)
|
|
}
|
|
|
|
fn openat(
|
|
&mut self,
|
|
dirfd: usize,
|
|
path: &str,
|
|
flags: usize,
|
|
_fcntl_flags: u32,
|
|
_ctx: &CallerCtx,
|
|
) -> Result<OpenResult> {
|
|
self.read_driver_manager_bound();
|
|
|
|
let write_intent = flags & O_ACCMODE != O_RDONLY;
|
|
let kind = if dirfd == SCHEME_ROOT_ID {
|
|
self.open_from_root(path, write_intent)?
|
|
} else {
|
|
match self.handle(dirfd)?.clone() {
|
|
HandleKind::DriverDir(driver_name) => {
|
|
self.open_from_driver(&driver_name, path, write_intent)?
|
|
}
|
|
_ => return Err(Error::new(EACCES)),
|
|
}
|
|
};
|
|
|
|
Ok(OpenResult::ThisScheme {
|
|
number: self.alloc_handle(kind),
|
|
flags: NewFdFlags::empty(),
|
|
})
|
|
}
|
|
|
|
fn read(
|
|
&mut self,
|
|
id: usize,
|
|
buf: &mut [u8],
|
|
offset: u64,
|
|
_flags: u32,
|
|
_ctx: &CallerCtx,
|
|
) -> Result<usize> {
|
|
let kind = self.handle(id)?.clone();
|
|
let data = self.read_handle_string(&kind)?;
|
|
let bytes = data.as_bytes();
|
|
let offset = usize::try_from(offset).map_err(|_| Error::new(EINVAL))?;
|
|
|
|
if offset >= bytes.len() {
|
|
return Ok(0);
|
|
}
|
|
|
|
let count = (bytes.len() - offset).min(buf.len());
|
|
buf[..count].copy_from_slice(&bytes[offset..offset + count]);
|
|
Ok(count)
|
|
}
|
|
|
|
fn write(
|
|
&mut self,
|
|
id: usize,
|
|
buf: &[u8],
|
|
_offset: u64,
|
|
_flags: u32,
|
|
_ctx: &CallerCtx,
|
|
) -> Result<usize> {
|
|
let value = Self::parse_write_value(buf)?;
|
|
|
|
match self.handle(id)?.clone() {
|
|
HandleKind::Parameter {
|
|
driver_name,
|
|
parameter_name,
|
|
} => {
|
|
let parameters = self.drivers.entry(driver_name).or_default();
|
|
|
|
match parameters.get_mut(¶meter_name) {
|
|
Some(entry) => {
|
|
entry.value = entry.kind.normalize(&value)?;
|
|
}
|
|
None => {
|
|
let kind = ParamKind::infer(&value);
|
|
let normalized = kind.normalize(&value)?;
|
|
parameters.insert(
|
|
parameter_name,
|
|
ParamEntry {
|
|
kind,
|
|
value: normalized,
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
Ok(buf.len())
|
|
}
|
|
_ => Err(Error::new(EROFS)),
|
|
}
|
|
}
|
|
|
|
fn fstat(&mut self, id: usize, stat: &mut Stat, _ctx: &CallerCtx) -> Result<()> {
|
|
self.read_driver_manager_bound();
|
|
|
|
stat.st_mode = match self.handle(id)? {
|
|
HandleKind::Root | HandleKind::DriverDir(_) => MODE_DIR | 0o755,
|
|
HandleKind::Parameter { .. } => MODE_FILE | 0o644,
|
|
};
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result<usize> {
|
|
let path = match self.handle(id)? {
|
|
HandleKind::Root => format!("{SCHEME_NAME}:/"),
|
|
HandleKind::DriverDir(driver_name) => format!("{SCHEME_NAME}:/{driver_name}"),
|
|
HandleKind::Parameter {
|
|
driver_name,
|
|
parameter_name,
|
|
} => {
|
|
format!("{SCHEME_NAME}:/{driver_name}/{parameter_name}")
|
|
}
|
|
};
|
|
|
|
let bytes = path.as_bytes();
|
|
let count = bytes.len().min(buf.len());
|
|
buf[..count].copy_from_slice(&bytes[..count]);
|
|
Ok(count)
|
|
}
|
|
|
|
fn fsync(&mut self, id: usize, _ctx: &CallerCtx) -> Result<()> {
|
|
let _ = self.handle(id)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn fcntl(&mut self, id: usize, _cmd: usize, _arg: usize, _ctx: &CallerCtx) -> Result<usize> {
|
|
let _ = self.handle(id)?;
|
|
Ok(0)
|
|
}
|
|
|
|
fn fevent(&mut self, id: usize, _flags: EventFlags, _ctx: &CallerCtx) -> Result<EventFlags> {
|
|
let _ = self.handle(id)?;
|
|
Ok(EventFlags::empty())
|
|
}
|
|
|
|
fn on_close(&mut self, id: usize) {
|
|
if id != SCHEME_ROOT_ID {
|
|
self.handles.remove(&id);
|
|
}
|
|
}
|
|
}
|
|
|
|
struct StderrLogger {
|
|
default_level: LevelFilter,
|
|
}
|
|
|
|
static LOGGER_LEVEL: AtomicUsize = AtomicUsize::new(3);
|
|
|
|
const STDERR_LOGGER: StderrLogger = StderrLogger {
|
|
default_level: LevelFilter::Info,
|
|
};
|
|
|
|
fn level_filter_to_usize(level: LevelFilter) -> usize {
|
|
match level {
|
|
LevelFilter::Off => 0,
|
|
LevelFilter::Error => 1,
|
|
LevelFilter::Warn => 2,
|
|
LevelFilter::Info => 3,
|
|
LevelFilter::Debug => 4,
|
|
LevelFilter::Trace => 5,
|
|
}
|
|
}
|
|
|
|
fn usize_to_level_filter(level: usize, fallback: LevelFilter) -> LevelFilter {
|
|
match level {
|
|
0 => LevelFilter::Off,
|
|
1 => LevelFilter::Error,
|
|
2 => LevelFilter::Warn,
|
|
3 => LevelFilter::Info,
|
|
4 => LevelFilter::Debug,
|
|
5 => LevelFilter::Trace,
|
|
_ => fallback,
|
|
}
|
|
}
|
|
|
|
impl log::Log for StderrLogger {
|
|
fn enabled(&self, metadata: &Metadata) -> bool {
|
|
metadata.level()
|
|
<= usize_to_level_filter(
|
|
LOGGER_LEVEL.load(Ordering::Relaxed),
|
|
self.default_level,
|
|
)
|
|
}
|
|
|
|
fn log(&self, record: &Record) {
|
|
if self.enabled(record.metadata()) {
|
|
eprintln!("[{}] {}", record.level(), record.args());
|
|
}
|
|
}
|
|
|
|
fn flush(&self) {}
|
|
}
|
|
|
|
fn init_logging(level: LevelFilter) {
|
|
LOGGER_LEVEL.store(level_filter_to_usize(level), Ordering::Relaxed);
|
|
let _ = log::set_logger(&STDERR_LOGGER);
|
|
log::set_max_level(level);
|
|
}
|
|
|
|
fn log_level_from_env() -> LevelFilter {
|
|
match env::var("DRIVER_PARAMS_LOG").as_deref() {
|
|
Ok("trace") => LevelFilter::Trace,
|
|
Ok("debug") => LevelFilter::Debug,
|
|
Ok("warn") => LevelFilter::Warn,
|
|
Ok("error") => LevelFilter::Error,
|
|
_ => LevelFilter::Info,
|
|
}
|
|
}
|
|
|
|
fn bound_path_from_env() -> PathBuf {
|
|
env::var("DRIVER_PARAMS_BOUND_PATH")
|
|
.map(PathBuf::from)
|
|
.unwrap_or_else(|_| PathBuf::from("/scheme/driver-manager/bound"))
|
|
}
|
|
|
|
#[cfg(target_os = "redox")]
|
|
fn init_notify_fd() -> std::result::Result<i32, String> {
|
|
let raw = env::var("INIT_NOTIFY")
|
|
.map_err(|_| "driver-params: INIT_NOTIFY not set".to_string())?;
|
|
raw.parse::<i32>()
|
|
.map_err(|_| "driver-params: INIT_NOTIFY is not a valid fd".to_string())
|
|
}
|
|
|
|
#[cfg(target_os = "redox")]
|
|
fn notify_scheme_ready(
|
|
notify_fd: i32,
|
|
socket: &Socket,
|
|
scheme: &mut DriverParamsScheme,
|
|
) -> std::result::Result<(), String> {
|
|
let cap_id = scheme
|
|
.scheme_root()
|
|
.map_err(|err| format!("driver-params: scheme_root failed: {err}"))?;
|
|
let cap_fd = socket
|
|
.create_this_scheme_fd(0, cap_id, 0, 0)
|
|
.map_err(|err| format!("driver-params: create_this_scheme_fd failed: {err}"))?;
|
|
|
|
syscall::call_wo(
|
|
notify_fd as usize,
|
|
&libredox::Fd::new(cap_fd).into_raw().to_ne_bytes(),
|
|
syscall::CallFlags::FD,
|
|
&[],
|
|
)
|
|
.map_err(|err| format!("driver-params: failed to notify init that scheme is ready: {err}"))?;
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(target_os = "redox")]
|
|
fn run_daemon(bound_path: PathBuf) -> std::result::Result<(), String> {
|
|
let notify_fd = init_notify_fd()?;
|
|
let socket = Socket::create()
|
|
.map_err(|err| format!("driver-params: failed to create scheme socket: {err}"))?;
|
|
let mut state = SchemeState::new();
|
|
let mut scheme = DriverParamsScheme::new(bound_path);
|
|
scheme.read_driver_manager_bound();
|
|
|
|
notify_scheme_ready(notify_fd, &socket, &mut scheme)?;
|
|
|
|
libredox::call::setrens(0, 0)
|
|
.map_err(|err| format!("driver-params: failed to enter null namespace: {err}"))?;
|
|
|
|
log::info!("driver-params: registered scheme:{SCHEME_NAME}");
|
|
|
|
loop {
|
|
let request = socket
|
|
.next_request(SignalBehavior::Restart)
|
|
.map_err(|err| format!("driver-params: failed to read scheme request: {err}"))?;
|
|
|
|
let Some(request) = request else {
|
|
return Ok(());
|
|
};
|
|
|
|
if let redox_scheme::RequestKind::Call(request) = request.kind() {
|
|
let response = request.handle_sync(&mut scheme, &mut state);
|
|
socket
|
|
.write_response(response, SignalBehavior::Restart)
|
|
.map_err(|err| format!("driver-params: failed to write response: {err}"))?;
|
|
}
|
|
}
|
|
}
|
|
|
|
fn host_probe(bound_path: &Path) {
|
|
let mut scheme = DriverParamsScheme::new(bound_path.to_path_buf());
|
|
scheme.read_driver_manager_bound();
|
|
|
|
for driver_name in scheme.sorted_driver_names() {
|
|
println!("{driver_name}");
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
init_logging(log_level_from_env());
|
|
|
|
let bound_path = bound_path_from_env();
|
|
|
|
if env::args().nth(1).as_deref() == Some("--probe") {
|
|
host_probe(&bound_path);
|
|
return;
|
|
}
|
|
|
|
#[cfg(not(target_os = "redox"))]
|
|
{
|
|
log::error!(
|
|
"driver-params: daemon mode is only supported on Redox; use --probe on host"
|
|
);
|
|
process::exit(1);
|
|
}
|
|
|
|
#[cfg(target_os = "redox")]
|
|
{
|
|
if let Err(err) = run_daemon(bound_path) {
|
|
log::error!("{err}");
|
|
process::exit(1);
|
|
}
|
|
}
|
|
}
|