Add runtime tools and Red Bear service wiring

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-14 10:50:42 +01:00
parent fd60edc823
commit 51f3c21121
62 changed files with 9613 additions and 881 deletions
@@ -1,4 +1,4 @@
#[derive(Clone, Debug)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Subsystem {
Gpu,
Network,
@@ -9,8 +9,16 @@ pub enum Subsystem {
Unknown,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum InputKind {
Keyboard,
Mouse,
Generic,
}
#[derive(Clone, Debug)]
pub struct DeviceInfo {
pub is_pci: bool,
pub bus: u8,
pub dev: u8,
pub func: u8,
@@ -19,15 +27,89 @@ pub struct DeviceInfo {
pub class_code: u8,
pub subclass: u8,
pub subsystem: Subsystem,
pub input_kind: Option<InputKind>,
pub name: String,
pub path: String,
pub devpath: String,
pub devnode: String,
pub scheme_target: String,
pub symlinks: Vec<String>,
}
impl DeviceInfo {
pub fn new_platform_input(
name: &str,
devpath: &str,
input_kind: InputKind,
devnode: &str,
scheme_target: &str,
) -> Self {
Self {
is_pci: false,
bus: 0,
dev: 0,
func: 0,
vendor_id: 0,
device_id: 0,
class_code: 0,
subclass: 0,
subsystem: Subsystem::Input,
input_kind: Some(input_kind),
name: name.to_string(),
devpath: devpath.to_string(),
devnode: devnode.to_string(),
scheme_target: scheme_target.to_string(),
symlinks: Vec::new(),
}
}
pub fn set_node_metadata(
&mut self,
devnode: impl Into<String>,
scheme_target: impl Into<String>,
symlinks: Vec<String>,
) {
self.devnode = devnode.into();
self.scheme_target = scheme_target.into();
self.symlinks = symlinks;
}
pub fn subsystem_name(&self) -> &'static str {
match self.subsystem {
Subsystem::Gpu => "drm",
Subsystem::Network => "net",
Subsystem::Storage => "block",
Subsystem::Audio => "sound",
Subsystem::Usb => "usb",
Subsystem::Input => "input",
Subsystem::Unknown => "unknown",
}
}
pub fn id_path(&self) -> String {
if let Some(slot) = self.devpath.strip_prefix("/devices/pci/") {
return format!("pci-{slot}");
}
self.devpath
.trim_start_matches("/devices/")
.replace('/', "-")
}
pub fn is_input_keyboard(&self) -> bool {
self.input_kind == Some(InputKind::Keyboard)
}
pub fn is_input_mouse(&self) -> bool {
self.input_kind == Some(InputKind::Mouse)
}
}
pub fn classify_pci_device(bus: u8, dev: u8, func: u8) -> DeviceInfo {
let path = format!("/devices/pci/{:04x}:{:02x}:{:02x}.{}", bus, 0, dev, func);
let devpath = format!("/devices/pci/{:04x}:{:02x}:{:02x}.{}", bus, 0, dev, func);
let config_path = format!("/scheme/pci/{}.{}.{}", bus, dev, func);
let (vendor_id, device_id, class_code, subclass) = read_pci_config(&config_path);
let input_kind = detect_input_kind(class_code, subclass);
let subsystem = match class_code {
0x03 => Subsystem::Gpu,
@@ -39,9 +121,10 @@ pub fn classify_pci_device(bus: u8, dev: u8, func: u8) -> DeviceInfo {
_ => Subsystem::Unknown,
};
let name = format_device_name(vendor_id, device_id, class_code);
let name = format_device_name(vendor_id, device_id, class_code, subclass, input_kind);
DeviceInfo {
is_pci: true,
bus,
dev,
func,
@@ -50,8 +133,12 @@ pub fn classify_pci_device(bus: u8, dev: u8, func: u8) -> DeviceInfo {
class_code,
subclass,
subsystem,
input_kind,
name,
path,
devpath,
devnode: String::new(),
scheme_target: String::new(),
symlinks: Vec::new(),
}
}
@@ -68,7 +155,40 @@ fn read_pci_config(path: &str) -> (u16, u16, u8, u8) {
}
}
fn format_device_name(vendor_id: u16, device_id: u16, class_code: u8) -> String {
fn detect_input_kind(class_code: u8, subclass: u8) -> Option<InputKind> {
if class_code != 0x09 {
return None;
}
match subclass {
0x00 => Some(InputKind::Keyboard),
0x04 => Some(InputKind::Generic),
_ => Some(InputKind::Generic),
}
}
fn format_device_name(
vendor_id: u16,
device_id: u16,
class_code: u8,
subclass: u8,
input_kind: Option<InputKind>,
) -> String {
if class_code == 0x03 {
if let Some(name) = gpu_device_name(vendor_id, device_id) {
return format!("{name} [{vendor_id:04x}:{device_id:04x}]");
}
}
if class_code == 0x09 {
let name = match (subclass, input_kind) {
(0x00, Some(InputKind::Keyboard)) => "PS/2 Keyboard Controller",
(0x04, _) => "USB HID Controller",
_ => "Input Device",
};
return format!("{name} [{vendor_id:04x}:{device_id:04x}]");
}
let vendor_name = match vendor_id {
0x8086 => "Intel",
0x1002 => "AMD",
@@ -95,19 +215,93 @@ fn format_device_name(vendor_id: u16, device_id: u16, class_code: u8) -> String
)
}
pub fn format_device_info(dev: &DeviceInfo) -> String {
let subsystem = match dev.subsystem {
Subsystem::Gpu => "gpu",
Subsystem::Network => "net",
Subsystem::Storage => "block",
Subsystem::Audio => "sound",
Subsystem::Usb => "usb",
Subsystem::Input => "input",
Subsystem::Unknown => "unknown",
};
format!(
"P={}\nE=SUBSYSTEM={}\nE=PCI_VENDOR_ID={:#06x}\nE=PCI_DEVICE_ID={:#06x}\nE=PCI_CLASS={:#04x}{:02x}\nE=DEVNAME={}\n",
dev.path, subsystem, dev.vendor_id, dev.device_id, dev.class_code, dev.subclass, dev.name
)
fn gpu_device_name(vendor_id: u16, device_id: u16) -> Option<&'static str> {
match vendor_id {
0x1002 => match device_id {
0x73A3 => Some("AMD Radeon RX 6600 XT / 6650 XT (RDNA2)"),
0x73BF => Some("AMD Radeon RX 6800 XT / 6900 XT (RDNA2)"),
0x73DF => Some("AMD Radeon RX 6700 XT / 6750 XT (RDNA2)"),
0x73EF => Some("AMD Radeon RX 6800 / 6850M XT (RDNA2)"),
0x7422 => Some("AMD Radeon 780M (RDNA3)"),
0x7448 => Some("AMD Radeon RX 7900 XT (RDNA3)"),
0x744C => Some("AMD Radeon RX 7900 XTX (RDNA3)"),
0x7480 => Some("AMD Radeon RX 7800 XT / 7700 XT (RDNA3)"),
_ => Some("AMD Radeon GPU"),
},
0x8086 => match device_id {
0x3E92 => Some("Intel UHD Graphics 630"),
0x5912 => Some("Intel HD Graphics 630"),
0x9A49 => Some("Intel Iris Xe Graphics (Tiger Lake)"),
0x46A6 => Some("Intel Iris Xe Graphics (Alder Lake-P)"),
0x56A0 => Some("Intel Arc Graphics (DG2)"),
0x56A1 => Some("Intel Arc A380 (DG2)"),
_ => Some("Intel Graphics"),
},
_ => None,
}
}
pub fn device_properties(dev: &DeviceInfo) -> Vec<(String, String)> {
let mut props = Vec::new();
props.push(("DEVPATH".to_string(), dev.devpath.clone()));
props.push(("SUBSYSTEM".to_string(), dev.subsystem_name().to_string()));
props.push(("ID_MODEL_FROM_DATABASE".to_string(), dev.name.clone()));
if !dev.devnode.is_empty() {
props.push(("DEVNAME".to_string(), dev.devnode.clone()));
}
let id_path = dev.id_path();
if !id_path.is_empty() {
props.push(("ID_PATH".to_string(), id_path));
}
if dev.is_pci {
props.push((
"PCI_VENDOR_ID".to_string(),
format!("0x{:04x}", dev.vendor_id),
));
props.push((
"PCI_DEVICE_ID".to_string(),
format!("0x{:04x}", dev.device_id),
));
props.push((
"PCI_CLASS".to_string(),
format!("0x{:02x}{:02x}", dev.class_code, dev.subclass),
));
}
if dev.subsystem == Subsystem::Input {
props.push(("ID_INPUT".to_string(), "1".to_string()));
match dev.input_kind {
Some(InputKind::Keyboard) => {
props.push(("ID_INPUT_KEYBOARD".to_string(), "1".to_string()));
}
Some(InputKind::Mouse) => {
props.push(("ID_INPUT_MOUSE".to_string(), "1".to_string()));
}
_ => {}
}
}
props
}
pub fn format_device_info(dev: &DeviceInfo) -> String {
let mut info = format!("P={}\n", dev.devpath);
for (key, value) in device_properties(dev) {
info.push_str(&format!("E={key}={value}\n"));
}
for link in &dev.symlinks {
info.push_str(&format!("S={}\n", link.trim_start_matches('/')));
}
info
}
pub fn format_uevent_info(dev: &DeviceInfo) -> String {
let mut info = String::from("ACTION=add\n");
for (key, value) in device_properties(dev) {
info.push_str(&format!("{key}={value}\n"));
}
info
}
@@ -2,10 +2,13 @@ mod device_db;
mod scheme;
use std::env;
use std::process;
use std::os::fd::{AsRawFd, FromRawFd, RawFd};
use log::{error, info, LevelFilter, Metadata, Record};
use redox_scheme::{SignalBehavior, Socket};
use redox_scheme::{
scheme::{SchemeState, SchemeSync},
SignalBehavior, Socket,
};
use scheme::UdevScheme;
@@ -25,45 +28,35 @@ impl log::Log for StderrLogger {
fn flush(&self) {}
}
fn run() -> Result<(), String> {
let mut scheme = UdevScheme::new();
match scheme.scan_pci_devices() {
Ok(n) => info!("udev-shim: enumerated {} PCI device(s)", n),
Err(e) => error!("udev-shim: PCI scan failed: {}", e),
fn init_logging(level: LevelFilter) {
if log::set_boxed_logger(Box::new(StderrLogger { level })).is_err() {
return;
}
log::set_max_level(level);
}
let socket =
Socket::create("udev").map_err(|e| format!("failed to register udev scheme: {}", e))?;
info!("udev-shim: registered scheme:udev");
unsafe fn get_init_notify_fd() -> RawFd {
let fd: RawFd = env::var("INIT_NOTIFY")
.expect("udev-shim: INIT_NOTIFY not set")
.parse()
.expect("udev-shim: INIT_NOTIFY is not a valid fd");
libc::fcntl(fd, libc::F_SETFD, libc::FD_CLOEXEC);
fd
}
loop {
let request = match socket.next_request(SignalBehavior::Restart) {
Ok(Some(r)) => r,
Ok(None) => {
info!("udev-shim: scheme unmounted, exiting");
break;
}
Err(e) => {
error!("udev-shim: failed to read scheme request: {}", e);
continue;
}
};
fn notify_scheme_ready(notify_fd: RawFd, socket: &Socket, scheme: &mut UdevScheme) {
let cap_id = scheme.scheme_root().expect("udev-shim: scheme_root failed");
let cap_fd = socket
.create_this_scheme_fd(0, cap_id, 0, 0)
.expect("udev-shim: create_this_scheme_fd failed");
let response = match request.handle_scheme_block_mut(&mut scheme) {
Ok(r) => r,
Err(_req) => {
error!("udev-shim: failed to handle request");
continue;
}
};
if let Err(e) = socket.write_response(response, SignalBehavior::Restart) {
error!("udev-shim: failed to write response: {}", e);
}
}
Ok(())
syscall::call_wo(
notify_fd as usize,
&libredox::Fd::new(cap_fd).into_raw().to_ne_bytes(),
syscall::CallFlags::FD,
&[],
)
.expect("udev-shim: failed to notify init that scheme is ready");
}
fn main() {
@@ -72,11 +65,40 @@ fn main() {
Ok("trace") => LevelFilter::Trace,
_ => LevelFilter::Info,
};
let _ = log::set_boxed_logger(Box::new(StderrLogger { level: log_level }));
log::set_max_level(log_level);
if let Err(e) = run() {
error!("udev-shim: fatal error: {}", e);
process::exit(1);
init_logging(log_level);
let mut scheme = UdevScheme::new();
match scheme.scan_pci_devices() {
Ok(n) => info!("udev-shim: enumerated {} PCI device(s)", n),
Err(e) => error!("udev-shim: PCI scan failed: {}", e),
}
let notify_fd = unsafe { get_init_notify_fd() };
let socket = Socket::create().expect("udev-shim: failed to create udev scheme");
let mut state = SchemeState::new();
notify_scheme_ready(notify_fd, &socket, &mut scheme);
libredox::call::setrens(0, 0).expect("udev-shim: failed to enter null namespace");
info!("udev-shim: registered scheme:udev");
while let Some(request) = socket
.next_request(SignalBehavior::Restart)
.expect("udev-shim: failed to read scheme request")
{
match request.kind() {
redox_scheme::RequestKind::Call(request) => {
let response = request.handle_sync(&mut scheme, &mut state);
socket
.write_response(response, SignalBehavior::Restart)
.expect("udev-shim: failed to write response");
}
_ => (),
}
}
std::process::exit(0);
}
@@ -1,170 +1,633 @@
use std::collections::BTreeMap;
use syscall::data::Stat;
use syscall::error::{Error, Result, EBADF, EINVAL, ENOENT, EROFS};
use syscall::flag::{EventFlags, MODE_DIR, MODE_FILE, SEEK_CUR, SEEK_END, SEEK_SET};
use redox_scheme::scheme::SchemeSync;
use redox_scheme::{CallerCtx, OpenResult};
use syscall::error::{Error, Result, EACCES, EBADF, ENOENT, EROFS};
use syscall::flag::{EventFlags, MODE_DIR, MODE_FILE};
use syscall::schemev2::NewFdFlags;
use crate::device_db::{classify_pci_device, format_device_info, DeviceInfo, Subsystem};
use crate::device_db::{
classify_pci_device, format_device_info, format_uevent_info, DeviceInfo, InputKind, Subsystem,
};
struct Handle {
kind: HandleKind,
offset: usize,
}
const SCHEME_ROOT_ID: usize = 1;
#[derive(Clone)]
enum HandleKind {
Root,
Devices,
Device(usize),
Dev,
DevInputDir,
DevInput(usize),
DevInputMice,
DevDriDir,
DevDri(usize),
DevLinks,
LinksInputDir,
LinksInputByPathDir,
LinksDriDir,
LinksDriByPathDir,
Link(usize),
Uevent,
}
pub struct UdevScheme {
next_id: usize,
handles: BTreeMap<usize, Handle>,
handles: BTreeMap<usize, HandleKind>,
devices: Vec<DeviceInfo>,
}
impl UdevScheme {
pub fn new() -> Self {
UdevScheme {
next_id: 0,
Self {
next_id: SCHEME_ROOT_ID + 1,
handles: BTreeMap::new(),
devices: Vec::new(),
}
}
pub fn scan_pci_devices(&mut self) -> Result<usize> {
let dir = match std::fs::read_dir("/scheme/pci") {
Ok(d) => d,
Err(e) => {
log::warn!("udev-shim: failed to read /scheme/pci: {e}");
return Ok(0);
self.devices.clear();
let mut pci_slots = Vec::new();
match std::fs::read_dir("/scheme/pci") {
Ok(dir) => {
for entry in dir {
let entry = match entry {
Ok(entry) => entry,
Err(_) => continue,
};
let name = match entry.file_name().to_str() {
Some(name) => name.to_string(),
None => continue,
};
if let Some(slot) = parse_pci_slot(&name) {
pci_slots.push(slot);
}
}
}
};
let mut count = 0;
for entry in dir {
let entry = match entry {
Ok(e) => e,
Err(_) => continue,
};
let name = match entry.file_name().to_str() {
Some(n) => n.to_string(),
None => continue,
};
let parts: Vec<&str> = name.split('.').collect();
if parts.len() < 3 {
continue;
Err(err) => {
log::warn!("udev-shim: failed to read /scheme/pci: {err}");
}
let bus: u8 = parts[0].parse().unwrap_or(0);
let dev: u8 = parts[1].parse().unwrap_or(0);
let func: u8 = parts[2].parse().unwrap_or(0);
let info = classify_pci_device(bus, dev, func);
self.devices.push(info);
count += 1;
}
Ok(count)
pci_slots.sort_unstable();
for (bus, dev, func) in pci_slots {
self.devices.push(classify_pci_device(bus, dev, func));
}
if path_exists("/scheme/input") {
self.devices.push(DeviceInfo::new_platform_input(
"Redox Keyboard Input",
"/devices/platform/keyboard0",
InputKind::Keyboard,
"",
"",
));
}
if path_exists("/scheme/pointer") || path_exists("/scheme/mouse") {
self.devices.push(DeviceInfo::new_platform_input(
"Redox Mouse Input",
"/devices/platform/mouse0",
InputKind::Mouse,
"",
"",
));
}
self.assign_virtual_nodes();
Ok(self.devices.len())
}
fn assign_virtual_nodes(&mut self) {
for dev in &mut self.devices {
if dev.subsystem == Subsystem::Gpu || (dev.subsystem == Subsystem::Input && !dev.is_pci)
{
dev.set_node_metadata("", "", Vec::new());
} else {
dev.symlinks.clear();
}
}
let mut gpu_indices: Vec<usize> = self
.devices
.iter()
.enumerate()
.filter_map(|(idx, dev)| (dev.subsystem == Subsystem::Gpu).then_some(idx))
.collect();
gpu_indices.sort_by_key(|idx| {
let dev = &self.devices[*idx];
(gpu_priority(dev), dev.bus, dev.dev, dev.func)
});
for (card_idx, device_idx) in gpu_indices.into_iter().enumerate() {
let devnode = format!("/dev/dri/card{card_idx}");
let scheme_target = format!("drm/card{card_idx}");
let symlink = format!(
"/links/dri/by-path/{}-card",
self.devices[device_idx].id_path()
);
self.devices[device_idx].set_node_metadata(devnode, scheme_target, vec![symlink]);
}
let mut input_indices: Vec<usize> = self
.devices
.iter()
.enumerate()
.filter_map(|(idx, dev)| {
(dev.subsystem == Subsystem::Input && !dev.is_pci).then_some(idx)
})
.collect();
input_indices.sort_by_key(|idx| {
let dev = &self.devices[*idx];
(input_priority(dev), dev.devpath.clone())
});
for (event_idx, device_idx) in input_indices.into_iter().enumerate() {
let devnode = format!("/dev/input/event{event_idx}");
let scheme_target = format!("evdev/event{event_idx}");
let suffix = match self.devices[device_idx].input_kind {
Some(InputKind::Keyboard) => "event-kbd",
Some(InputKind::Mouse) => "event-mouse",
Some(InputKind::Generic) | None => "event",
};
let symlink = format!(
"/links/input/by-path/{}-{}",
self.devices[device_idx].id_path(),
suffix
);
self.devices[device_idx].set_node_metadata(devnode, scheme_target, vec![symlink]);
}
}
fn find_device_by_devnode(&self, devnode: &str) -> Option<usize> {
self.devices
.iter()
.enumerate()
.find_map(|(idx, dev)| (dev.devnode == devnode).then_some(idx))
}
fn find_device_by_link(&self, prefix: &str, tail: &str) -> Option<usize> {
let expected = format!("{prefix}{tail}");
self.devices.iter().enumerate().find_map(|(idx, dev)| {
dev.symlinks
.iter()
.any(|link| link == &expected)
.then_some(idx)
})
}
fn mouse_device_index(&self) -> Option<usize> {
self.devices.iter().enumerate().find_map(|(idx, dev)| {
(dev.is_input_mouse() && !dev.scheme_target.is_empty()).then_some(idx)
})
}
fn input_event_indices(&self) -> Vec<usize> {
self.devices
.iter()
.enumerate()
.filter_map(|(idx, dev)| {
(dev.devnode.starts_with("/dev/input/event") && !dev.scheme_target.is_empty())
.then_some(idx)
})
.collect()
}
fn dri_card_indices(&self) -> Vec<usize> {
self.devices
.iter()
.enumerate()
.filter_map(|(idx, dev)| {
(dev.devnode.starts_with("/dev/dri/card") && !dev.scheme_target.is_empty())
.then_some(idx)
})
.collect()
}
fn directory_listing<I>(&self, entries: I) -> String
where
I: IntoIterator<Item = String>,
{
let mut listing = String::new();
for entry in entries {
listing.push_str(&entry);
listing.push('\n');
}
listing
}
fn link_listing(&self, prefix: &str) -> String {
self.directory_listing(
self.devices
.iter()
.flat_map(|dev| dev.symlinks.iter())
.filter_map(|link| {
link.strip_prefix(prefix).and_then(|tail| {
(!tail.is_empty() && !tail.contains('/')).then(|| tail.to_string())
})
}),
)
}
fn uevent_content(&self) -> String {
let mut content = String::new();
for (idx, dev) in self.devices.iter().enumerate() {
if idx > 0 {
content.push('\n');
}
content.push_str(&format_uevent_info(dev));
}
content
}
fn content_for_handle(&self, kind: &HandleKind) -> Result<String> {
match kind {
HandleKind::Root => Ok(self.directory_listing(
["devices", "dev", "links", "uevent"]
.into_iter()
.map(String::from),
)),
HandleKind::Devices => {
Ok(self.directory_listing((0..self.devices.len()).map(|idx| idx.to_string())))
}
HandleKind::Device(idx) => self
.devices
.get(*idx)
.map(format_device_info)
.ok_or_else(|| Error::new(ENOENT)),
HandleKind::Dev => {
Ok(self.directory_listing(["input", "dri"].into_iter().map(String::from)))
}
HandleKind::DevInputDir => {
let mut entries: Vec<String> = self
.input_event_indices()
.into_iter()
.filter_map(|idx| basename(&self.devices[idx].devnode))
.collect();
if self.mouse_device_index().is_some() {
entries.push("mice".to_string());
}
Ok(self.directory_listing(entries))
}
HandleKind::DevInput(idx) => {
let dev = self.devices.get(*idx).ok_or_else(|| Error::new(ENOENT))?;
if dev.scheme_target.is_empty() {
return Err(Error::new(ENOENT));
}
let mut info = format_device_info(dev);
info.push_str(&format!("SCHEME_TARGET={}\n", dev.scheme_target));
Ok(info)
}
HandleKind::DevDri(idx) => {
let dev = self.devices.get(*idx).ok_or_else(|| Error::new(ENOENT))?;
if dev.scheme_target.is_empty() {
return Err(Error::new(ENOENT));
}
let mut info = format_device_info(dev);
info.push_str(&format!("SCHEME_TARGET={}\n", dev.scheme_target));
Ok(info)
}
HandleKind::Link(idx) => {
let dev = self.devices.get(*idx).ok_or_else(|| Error::new(ENOENT))?;
if dev.scheme_target.is_empty() {
return Err(Error::new(ENOENT));
}
let mut info = format_device_info(dev);
info.push_str(&format!("SCHEME_TARGET={}\n", dev.scheme_target));
Ok(info)
}
HandleKind::DevInputMice => {
let idx = self
.mouse_device_index()
.ok_or_else(|| Error::new(ENOENT))?;
let dev = &self.devices[idx];
let mut info = format_device_info(dev);
info.push_str(&format!("SCHEME_TARGET={}\n", dev.scheme_target));
Ok(info)
}
HandleKind::DevDriDir => Ok(self.directory_listing(
self.dri_card_indices()
.into_iter()
.filter_map(|idx| basename(&self.devices[idx].devnode)),
)),
HandleKind::DevLinks => {
Ok(self.directory_listing(["input", "dri"].into_iter().map(String::from)))
}
HandleKind::LinksInputDir => {
Ok(self.directory_listing(["by-path"].into_iter().map(String::from)))
}
HandleKind::LinksInputByPathDir => Ok(self.link_listing("/links/input/by-path/")),
HandleKind::LinksDriDir => {
Ok(self.directory_listing(["by-path"].into_iter().map(String::from)))
}
HandleKind::LinksDriByPathDir => Ok(self.link_listing("/links/dri/by-path/")),
HandleKind::Uevent => Ok(self.uevent_content()),
}
}
fn is_directory(kind: &HandleKind) -> bool {
matches!(
kind,
HandleKind::Root
| HandleKind::Devices
| HandleKind::Dev
| HandleKind::DevInputDir
| HandleKind::DevDriDir
| HandleKind::DevLinks
| HandleKind::LinksInputDir
| HandleKind::LinksInputByPathDir
| HandleKind::LinksDriDir
| HandleKind::LinksDriByPathDir
)
}
fn kind_for_id(&self, id: usize) -> Result<HandleKind> {
if id == SCHEME_ROOT_ID {
return Ok(HandleKind::Root);
}
self.handles
.get(&id)
.cloned()
.ok_or_else(|| Error::new(EBADF))
}
fn kind_for_path(&self, path: &str) -> Result<HandleKind> {
let cleaned = path.trim_matches('/');
match cleaned {
"" => Ok(HandleKind::Root),
"devices" => Ok(HandleKind::Devices),
"dev" => Ok(HandleKind::Dev),
"dev/input" => Ok(HandleKind::DevInputDir),
"dev/input/mice" => {
if self.mouse_device_index().is_none() {
return Err(Error::new(ENOENT));
}
Ok(HandleKind::DevInputMice)
}
"dev/dri" => Ok(HandleKind::DevDriDir),
"links" => Ok(HandleKind::DevLinks),
"links/input" => Ok(HandleKind::LinksInputDir),
"links/input/by-path" => Ok(HandleKind::LinksInputByPathDir),
"links/dri" => Ok(HandleKind::LinksDriDir),
"links/dri/by-path" => Ok(HandleKind::LinksDriByPathDir),
"uevent" => Ok(HandleKind::Uevent),
_ => {
if let Some(rest) = cleaned.strip_prefix("devices/") {
let idx = rest.parse::<usize>().map_err(|_| Error::new(ENOENT))?;
if idx >= self.devices.len() {
return Err(Error::new(ENOENT));
}
Ok(HandleKind::Device(idx))
} else if let Some(rest) = cleaned.strip_prefix("dev/input/") {
let devnode = format!("/dev/input/{rest}");
let idx = self
.find_device_by_devnode(&devnode)
.ok_or_else(|| Error::new(ENOENT))?;
Ok(HandleKind::DevInput(idx))
} else if let Some(rest) = cleaned.strip_prefix("dev/dri/") {
let devnode = format!("/dev/dri/{rest}");
let idx = self
.find_device_by_devnode(&devnode)
.ok_or_else(|| Error::new(ENOENT))?;
Ok(HandleKind::DevDri(idx))
} else if let Some(rest) = cleaned.strip_prefix("links/input/by-path/") {
let idx = self
.find_device_by_link("/links/input/by-path/", rest)
.ok_or_else(|| Error::new(ENOENT))?;
Ok(HandleKind::Link(idx))
} else if let Some(rest) = cleaned.strip_prefix("links/dri/by-path/") {
let idx = self
.find_device_by_link("/links/dri/by-path/", rest)
.ok_or_else(|| Error::new(ENOENT))?;
Ok(HandleKind::Link(idx))
} else {
Err(Error::new(ENOENT))
}
}
}
}
fn allocate_handle(&mut self, kind: HandleKind) -> usize {
let id = self.next_id;
self.next_id = self.next_id.saturating_add(1);
self.handles.insert(id, kind);
id
}
fn path_for_handle(&self, kind: &HandleKind) -> Result<String> {
match kind {
HandleKind::Root => Ok("/scheme/udev".to_string()),
HandleKind::Devices => Ok("/scheme/udev/devices".to_string()),
HandleKind::Device(idx) => {
if *idx >= self.devices.len() {
return Err(Error::new(ENOENT));
}
Ok(format!("/scheme/udev/devices/{idx}"))
}
HandleKind::Dev => Ok("/scheme/udev/dev".to_string()),
HandleKind::DevInputDir => Ok("/scheme/udev/dev/input".to_string()),
HandleKind::DevInput(idx) => self
.devices
.get(*idx)
.filter(|dev| !dev.devnode.is_empty())
.map(|dev| format!("/scheme/udev{}", dev.devnode))
.ok_or_else(|| Error::new(ENOENT)),
HandleKind::DevInputMice => Ok("/scheme/udev/dev/input/mice".to_string()),
HandleKind::DevDriDir => Ok("/scheme/udev/dev/dri".to_string()),
HandleKind::DevDri(idx) => self
.devices
.get(*idx)
.filter(|dev| !dev.devnode.is_empty())
.map(|dev| format!("/scheme/udev{}", dev.devnode))
.ok_or_else(|| Error::new(ENOENT)),
HandleKind::DevLinks => Ok("/scheme/udev/links".to_string()),
HandleKind::LinksInputDir => Ok("/scheme/udev/links/input".to_string()),
HandleKind::LinksInputByPathDir => Ok("/scheme/udev/links/input/by-path".to_string()),
HandleKind::LinksDriDir => Ok("/scheme/udev/links/dri".to_string()),
HandleKind::LinksDriByPathDir => Ok("/scheme/udev/links/dri/by-path".to_string()),
HandleKind::Link(idx) => self
.devices
.get(*idx)
.and_then(|dev| dev.symlinks.first())
.map(|link| format!("/scheme/udev{link}"))
.ok_or_else(|| Error::new(ENOENT)),
HandleKind::Uevent => Ok("/scheme/udev/uevent".to_string()),
}
}
}
impl redox_scheme::SchemeBlockMut for UdevScheme {
fn open(&mut self, path: &str, _flags: usize, _uid: u32, _gid: u32) -> Result<Option<usize>> {
let cleaned = path.trim_matches('/');
let kind = if cleaned.is_empty() {
HandleKind::Root
} else if cleaned == "devices" || cleaned == "devices/" {
HandleKind::Root
} else if let Some(rest) = cleaned.strip_prefix("devices/") {
let idx: usize = rest
.trim_end_matches('/')
.parse()
.map_err(|_| Error::new(ENOENT))?;
if idx >= self.devices.len() {
return Err(Error::new(ENOENT));
}
HandleKind::Device(idx)
} else {
return Err(Error::new(ENOENT));
};
let id = self.next_id;
self.next_id += 1;
self.handles.insert(id, Handle { kind, offset: 0 });
Ok(Some(id))
impl SchemeSync for UdevScheme {
fn scheme_root(&mut self) -> Result<usize> {
Ok(SCHEME_ROOT_ID)
}
fn read(&mut self, id: usize, buf: &mut [u8]) -> Result<Option<usize>> {
let handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?;
fn openat(
&mut self,
dirfd: usize,
path: &str,
_flags: usize,
_fcntl_flags: u32,
_ctx: &CallerCtx,
) -> Result<OpenResult> {
if dirfd != SCHEME_ROOT_ID {
return Err(Error::new(EACCES));
}
let content = match &handle.kind {
HandleKind::Root => {
let mut listing = String::new();
for (i, dev) in self.devices.iter().enumerate() {
listing.push_str(&format!("devices/{}\n", i));
}
listing
}
HandleKind::Device(idx) => {
let dev = &self.devices[*idx];
format_device_info(dev)
}
let kind = self.kind_for_path(path)?;
let id = if matches!(kind, HandleKind::Root) {
SCHEME_ROOT_ID
} else {
self.allocate_handle(kind)
};
Ok(OpenResult::ThisScheme {
number: id,
flags: NewFdFlags::empty(),
})
}
fn read(
&mut self,
id: usize,
buf: &mut [u8],
offset: u64,
_flags: u32,
_ctx: &CallerCtx,
) -> Result<usize> {
let kind = self.kind_for_id(id)?;
let content = self.content_for_handle(&kind)?;
let bytes = content.as_bytes();
let remaining = &bytes[handle.offset..];
if offset >= bytes.len() as u64 {
return Ok(0);
}
let start = offset as usize;
let remaining = &bytes[start..];
let to_copy = remaining.len().min(buf.len());
buf[..to_copy].copy_from_slice(&remaining[..to_copy]);
handle.offset += to_copy;
Ok(Some(to_copy))
Ok(to_copy)
}
fn write(&mut self, id: usize, _buf: &[u8]) -> Result<Option<usize>> {
let _ = self.handles.get(&id).ok_or(Error::new(EBADF))?;
fn write(
&mut self,
id: usize,
_buf: &[u8],
_offset: u64,
_flags: u32,
_ctx: &CallerCtx,
) -> Result<usize> {
let _kind = self.kind_for_id(id)?;
Err(Error::new(EROFS))
}
fn seek(&mut self, id: usize, pos: isize, whence: usize) -> Result<Option<isize>> {
let handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?;
let len = match &handle.kind {
HandleKind::Root => self.devices.len() * 20,
HandleKind::Device(idx) => format_device_info(&self.devices[*idx]).len(),
fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result<usize> {
let kind = self.kind_for_id(id)?;
let path = self.path_for_handle(&kind)?;
let bytes = path.as_bytes();
let to_copy = bytes.len().min(buf.len());
buf[..to_copy].copy_from_slice(&bytes[..to_copy]);
Ok(to_copy)
}
fn fstat(&mut self, id: usize, stat: &mut syscall::Stat, _ctx: &CallerCtx) -> Result<()> {
let kind = self.kind_for_id(id)?;
let size = self.content_for_handle(&kind)?.len() as u64;
stat.st_mode = if Self::is_directory(&kind) {
MODE_DIR | 0o555
} else {
MODE_FILE | 0o444
};
let new_offset = match whence {
SEEK_SET => pos as isize,
SEEK_CUR => handle.offset as isize + pos,
SEEK_END => len as isize + pos,
_ => return Err(Error::new(EINVAL)),
};
if new_offset < 0 {
return Err(Error::new(EINVAL));
stat.st_size = size;
stat.st_blocks = size.div_ceil(512);
stat.st_blksize = 4096;
stat.st_nlink = 1;
Ok(())
}
fn fsync(&mut self, id: usize, _ctx: &CallerCtx) -> Result<()> {
let _kind = self.kind_for_id(id)?;
Ok(())
}
fn fcntl(&mut self, id: usize, _cmd: usize, _arg: usize, _ctx: &CallerCtx) -> Result<usize> {
let _kind = self.kind_for_id(id)?;
Ok(0)
}
fn fsize(&mut self, id: usize, _ctx: &CallerCtx) -> Result<u64> {
let kind = self.kind_for_id(id)?;
Ok(self.content_for_handle(&kind)?.len() as u64)
}
fn ftruncate(&mut self, id: usize, _len: u64, _ctx: &CallerCtx) -> Result<()> {
let _kind = self.kind_for_id(id)?;
Err(Error::new(EROFS))
}
fn fevent(&mut self, id: usize, _flags: EventFlags, _ctx: &CallerCtx) -> Result<EventFlags> {
let _kind = self.kind_for_id(id)?;
Ok(EventFlags::empty())
}
fn on_close(&mut self, id: usize) {
if id != SCHEME_ROOT_ID {
self.handles.remove(&id);
}
}
}
fn path_exists(path: &str) -> bool {
std::fs::metadata(path).is_ok()
}
fn parse_pci_slot(name: &str) -> Option<(u8, u8, u8)> {
let mut parts = name.split('.');
let bus = parts.next()?.parse::<u8>().ok()?;
let dev = parts.next()?.parse::<u8>().ok()?;
let func = parts.next()?.parse::<u8>().ok()?;
if parts.next().is_some() {
return None;
}
Some((bus, dev, func))
}
fn basename(path: &str) -> Option<String> {
path.rsplit('/').next().and_then(|part| {
if part.is_empty() {
None
} else {
Some(part.to_string())
}
})
}
fn gpu_priority(dev: &DeviceInfo) -> u8 {
match dev.vendor_id {
0x1002 => 0,
0x8086 => 1,
_ => 2,
}
}
fn input_priority(dev: &DeviceInfo) -> u8 {
if dev.is_input_keyboard() {
0
} else {
match dev.input_kind {
Some(InputKind::Mouse) => 1,
Some(InputKind::Generic) | None => 2,
Some(InputKind::Keyboard) => 0,
}
handle.offset = new_offset as usize;
Ok(Some(new_offset))
}
fn fstat(&mut self, id: usize, stat: &mut Stat) -> Result<Option<usize>> {
let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?;
match &handle.kind {
HandleKind::Root => {
stat.st_mode = MODE_DIR | 0o555;
}
HandleKind::Device(_) => {
stat.st_mode = MODE_FILE | 0o444;
}
}
Ok(Some(0))
}
fn fevent(&mut self, id: usize, _flags: EventFlags) -> Result<Option<EventFlags>> {
let _ = self.handles.get(&id).ok_or(Error::new(EBADF))?;
Ok(Some(EventFlags::empty()))
}
fn close(&mut self, id: usize) -> Result<Option<usize>> {
self.handles.remove(&id);
Ok(Some(0))
}
}