# P2-inputd.patch # Extract inputd improvements: named producers, device consumers, hotplug events, # error handling, and input scheme extensions. # # Files: drivers/inputd/src/lib.rs, drivers/inputd/src/main.rs diff --git a/drivers/inputd/src/lib.rs b/drivers/inputd/src/lib.rs index b68e8211..f07a411d 100644 --- a/drivers/inputd/src/lib.rs +++ b/drivers/inputd/src/lib.rs @@ -64,25 +64,53 @@ impl ConsumerHandle { let fd = self.0.as_raw_fd(); let written = libredox::call::fpath(fd as usize, &mut buffer)?; - assert!(written <= buffer.len()); - - let mut display_path = PathBuf::from( - std::str::from_utf8(&buffer[..written]) - .expect("init: display path UTF-8 check failed") - .to_owned(), - ); - display_path.set_file_name(format!( - "v2/{}", - display_path.file_name().unwrap().to_str().unwrap() - )); - let display_path = display_path.to_str().unwrap(); + if written > buffer.len() { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "inputd: display path exceeded buffer size", + )); + } + + let path_str = std::str::from_utf8(&buffer[..written]).map_err(|e| { + io::Error::new( + io::ErrorKind::InvalidData, + format!("inputd: display path is not valid UTF-8: {e}"), + ) + })?; + let mut display_path = PathBuf::from(path_str.to_owned()); + + let file_name = display_path + .file_name() + .and_then(|n| n.to_str()) + .ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidData, + format!( + "inputd: display path has no valid file name: {}", + display_path.display() + ), + ) + })?; + display_path.set_file_name(format!("v2/{file_name}")); + let display_path_str = display_path.to_str().ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidData, + format!( + "inputd: constructed display path is not valid UTF-8: {}", + display_path.display() + ), + ) + })?; let display_file = - libredox::call::open(display_path, (O_CLOEXEC | O_NONBLOCK | O_RDWR) as _, 0) + libredox::call::open(display_path_str, (O_CLOEXEC | O_NONBLOCK | O_RDWR) as _, 0) .map(|socket| unsafe { File::from_raw_fd(socket as RawFd) }) - .unwrap_or_else(|err| { - panic!("failed to open display {}: {}", display_path, err); - }); + .map_err(|err| { + io::Error::new( + io::ErrorKind::Other, + format!("inputd: failed to open display {display_path_str}: {err}"), + ) + })?; Ok(display_file) } @@ -152,8 +180,12 @@ impl DisplayHandle { if nread == 0 { Ok(None) + } else if nread != size_of::() { + Err(io::Error::new( + io::ErrorKind::InvalidData, + format!("inputd: partial vt event read: got {nread}, expected {}", size_of::()), + )) } else { - assert_eq!(nread, size_of::()); Ok(Some(event)) } } @@ -171,13 +203,11 @@ impl ControlHandle { Ok(Self(File::open(path)?)) } - /// Sent to Handle::Display pub fn activate_vt(&mut self, vt: usize) -> io::Result { let cmd = ControlEvent::from(VtActivate { vt }); self.0.write(unsafe { any_as_u8_slice(&cmd) }) } - /// Sent to Handle::Producer pub fn activate_keymap(&mut self, keymap: usize) -> io::Result { let cmd = ControlEvent::from(KeymapActivate { keymap }); self.0.write(unsafe { any_as_u8_slice(&cmd) }) @@ -209,3 +239,195 @@ impl ProducerHandle { Ok(()) } } + +pub struct NamedProducerHandle(File); + +impl NamedProducerHandle { + pub fn new(name: &str) -> io::Result { + let path = format!("/scheme/input/producer/{name}"); + Ok(Self(File::open(path)?)) + } + + pub fn write_event(&mut self, event: orbclient::Event) -> io::Result<()> { + self.0.write(&event)?; + Ok(()) + } +} + +/// Convenience wrapper that tries a named producer first, +/// falling back to the legacy anonymous producer on failure. +pub enum InputProducer { + Named(NamedProducerHandle), + Legacy(ProducerHandle), +} + +impl InputProducer { + /// Open a named producer (`/scheme/input/producer/{name}`). + /// If the named path is unavailable, fall back to the legacy + /// `/scheme/input/producer` path so the driver keeps working on + /// older inputd builds or degraded schemes. + pub fn new_named_or_fallback(name: &str) -> io::Result { + match NamedProducerHandle::new(name) { + Ok(named) => Ok(InputProducer::Named(named)), + Err(named_err) => { + log::debug!( + "inputd: named producer '{}' unavailable ({}), falling back to legacy", + name, + named_err + ); + ProducerHandle::new().map(InputProducer::Legacy) + } + } + } + + /// Open the legacy anonymous producer directly. + pub fn new_legacy() -> io::Result { + ProducerHandle::new().map(InputProducer::Legacy) + } + + pub fn write_event(&mut self, event: orbclient::Event) -> io::Result<()> { + match self { + InputProducer::Named(h) => h.write_event(event), + InputProducer::Legacy(h) => h.write_event(event), + } + } +} + +pub struct DeviceConsumerHandle(File); + +pub enum DeviceConsumerHandleEvent<'a> { + Events(&'a [Event]), +} + +impl DeviceConsumerHandle { + pub fn new(device_name: &str) -> io::Result { + let path = format!("/scheme/input/{device_name}"); + Ok(Self(File::open(path)?)) + } + + pub fn event_handle(&self) -> BorrowedFd<'_> { + self.0.as_fd() + } + + pub fn read_events<'a>( + &self, + events: &'a mut [Event], + ) -> io::Result> { + match read_to_slice(self.0.as_fd(), events) { + Ok(count) => Ok(DeviceConsumerHandleEvent::Events(&events[..count])), + Err(err) => Err(err.into()), + } + } +} + +#[derive(Debug, Clone)] +#[repr(C)] +pub struct HotplugEventHeader { + pub kind: u32, + pub device_id: u32, + pub name_len: u32, + pub reserved: u32, +} + +#[derive(Debug, Clone)] +pub struct HotplugEvent { + pub kind: u32, + pub device_id: u32, + pub name: String, +} + +pub struct HotplugHandle { + file: File, + partial: Vec, +} + +impl HotplugHandle { + pub fn new() -> io::Result { + let file = File::open("/scheme/input/events")?; + Ok(Self { + file, + partial: Vec::new(), + }) + } + + pub fn event_handle(&self) -> BorrowedFd<'_> { + self.file.as_fd() + } + + pub fn read_event(&mut self) -> io::Result> { + let mut tmp = [0u8; 256]; + match self.file.read(&mut tmp) { + Ok(0) => {} + Ok(n) => self.partial.extend_from_slice(&tmp[..n]), + Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {} + Err(e) => return Err(e), + } + + if self.partial.len() < 16 { + return Ok(None); + } + + let header = HotplugEventHeader { + kind: u32::from_ne_bytes(self.partial[0..4].try_into().map_err(|_| { + io::Error::new(io::ErrorKind::InvalidData, "header parse failed") + })?), + device_id: u32::from_ne_bytes(self.partial[4..8].try_into().map_err(|_| { + io::Error::new(io::ErrorKind::InvalidData, "header parse failed") + })?), + name_len: u32::from_ne_bytes(self.partial[8..12].try_into().map_err(|_| { + io::Error::new(io::ErrorKind::InvalidData, "header parse failed") + })?), + reserved: 0, + }; + + let total_len = 16 + header.name_len as usize; + if self.partial.len() < total_len { + return Ok(None); + } + + let name = String::from_utf8(self.partial[16..total_len].to_vec()).map_err(|e| { + io::Error::new(io::ErrorKind::InvalidData, format!("invalid UTF-8: {e}")) + })?; + + self.partial.drain(..total_len); + + Ok(Some(HotplugEvent { + kind: header.kind, + device_id: header.device_id, + name, + })) + } +} + +pub const RESERVED_DEVICE_NAMES: &[&str] = &[ + "producer", + "consumer", + "consumer_bootlog", + "events", + "handle", + "handle_early", + "control", +]; + +pub struct InputDeviceLister; + +impl InputDeviceLister { + pub fn list() -> io::Result> { + let mut dir = std::fs::read_dir("/scheme/input/")?; + let mut devices = Vec::new(); + loop { + match dir.next() { + Some(Ok(entry)) => { + if let Some(name) = entry.file_name().to_str() { + if !RESERVED_DEVICE_NAMES.contains(&name) { + devices.push(name.to_owned()); + } + } + } + Some(Err(e)) => return Err(e), + None => break, + } + } + Ok(devices) + } +} diff --git a/drivers/inputd/src/main.rs b/drivers/inputd/src/main.rs index 07aa943e..89018568 100644 --- a/drivers/inputd/src/main.rs +++ b/drivers/inputd/src/main.rs @@ -13,7 +13,7 @@ use core::mem::size_of; use std::borrow::Cow; -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; use std::mem::transmute; use std::ops::ControlFlow; use std::sync::atomic::{AtomicUsize, Ordering}; @@ -26,8 +26,9 @@ use redox_scheme::{CallerCtx, OpenResult, Response, SignalBehavior, Socket}; use orbclient::{Event, EventOption}; use scheme_utils::{Blocking, FpathWriter, HandleMap}; +use syscall::dirent::{DirEntry, DirentBuf, DirentKind}; use syscall::schemev2::NewFdFlags; -use syscall::{Error as SysError, EventFlags, EACCES, EBADF, EEXIST, EINVAL}; +use syscall::{Error as SysError, EventFlags, EACCES, EBADF, EEXIST, EINVAL, ENOENT, ENOTDIR}; pub mod keymap; @@ -35,8 +36,57 @@ use keymap::KeymapKind; use crate::keymap::KeymapData; +const DEVICE_ADD: u32 = 1; +const DEVICE_REMOVE: u32 = 2; + +fn validate_producer_name(name: &str) -> Result<(), SysError> { + if name.is_empty() || name.contains('/') { + return Err(SysError::new(EINVAL)); + } + if inputd::RESERVED_DEVICE_NAMES.contains(&name) { + return Err(SysError::new(EINVAL)); + } + Ok(()) +} + +fn serialize_hotplug(kind: u32, device_id: u32, name: &str) -> Vec { + let name_bytes = name.as_bytes(); + let header = HotplugHeader { + kind, + device_id, + name_len: name_bytes.len() as u32, + _reserved: 0, + }; + let mut out = Vec::with_capacity(16 + name_bytes.len()); + out.extend_from_slice(&header.to_bytes()); + out.extend_from_slice(name_bytes); + out +} + +#[repr(C)] +struct HotplugHeader { + kind: u32, + device_id: u32, + name_len: u32, + _reserved: u32, +} + +impl HotplugHeader { + fn to_bytes(&self) -> [u8; 16] { + let mut buf = [0u8; 16]; + buf[0..4].copy_from_slice(&self.kind.to_ne_bytes()); + buf[4..8].copy_from_slice(&self.device_id.to_ne_bytes()); + buf[8..12].copy_from_slice(&self.name_len.to_ne_bytes()); + buf[12..16].copy_from_slice(&self._reserved.to_ne_bytes()); + buf + } +} + enum Handle { Producer, + NamedProducer { + name: String, + }, Consumer { events: EventFlags, pending: Vec, @@ -46,6 +96,17 @@ enum Handle { notified: bool, vt: usize, }, + DeviceConsumer { + device_name: String, + events: EventFlags, + pending: Vec, + notified: bool, + }, + HotplugEvents { + events: EventFlags, + pending: Vec, + notified: bool, + }, Display { events: EventFlags, pending: Vec, @@ -72,6 +133,9 @@ struct InputScheme { rshift: bool, has_new_events: bool, + + devices: BTreeMap, + next_device_id: AtomicUsize, } impl InputScheme { @@ -90,9 +154,28 @@ impl InputScheme { lshift: false, rshift: false, has_new_events: false, + + devices: BTreeMap::new(), + next_device_id: AtomicUsize::new(1), } } + fn emit_hotplug(&mut self, kind: u32, device_id: u32, name: &str) { + let record = serialize_hotplug(kind, device_id, name); + for handle in self.handles.values_mut() { + if let Handle::HotplugEvents { + pending, + notified, + .. + } = handle + { + pending.extend_from_slice(&record); + *notified = false; + } + } + self.has_new_events = true; + } + fn switch_vt(&mut self, new_active: usize) { if let Some(active_vt) = self.active_vt { if new_active == active_vt { @@ -146,6 +229,43 @@ impl InputScheme { self.active_keymap = KeymapData::new(new_active.into()); } + + fn deliver_to_legacy_consumers(&mut self, buf: &[u8]) { + if let Some(active_vt) = self.active_vt { + for handle in self.handles.values_mut() { + if let Handle::Consumer { + pending, + notified, + vt, + .. + } = handle + { + if *vt != active_vt { + continue; + } + pending.extend_from_slice(buf); + *notified = false; + } + } + } + } + + fn deliver_to_device_consumers(&mut self, name: &str, buf: &[u8]) { + for handle in self.handles.values_mut() { + if let Handle::DeviceConsumer { + device_name, + pending, + notified, + .. + } = handle + { + if device_name == name { + pending.extend_from_slice(buf); + *notified = false; + } + } + } + } } impl SchemeSync for InputScheme { @@ -170,7 +290,23 @@ impl SchemeSync for InputScheme { let command = path_parts.next().ok_or(SysError::new(EINVAL))?; let handle_ty = match command { - "producer" => Handle::Producer, + "producer" => { + if let Some(name) = path_parts.next() { + validate_producer_name(name)?; + if self.devices.contains_key(name) { + return Err(SysError::new(EEXIST)); + } + let device_id = self.next_device_id.fetch_add(1, Ordering::SeqCst) as u32; + self.devices.insert(name.to_owned(), device_id); + let handle = Handle::NamedProducer { + name: name.to_owned(), + }; + self.emit_hotplug(DEVICE_ADD, device_id, name); + handle + } else { + Handle::Producer + } + } "consumer" => { let vt = self.next_vt_id.fetch_add(1, Ordering::Relaxed); self.vts.insert(vt); @@ -253,9 +389,23 @@ impl SchemeSync for InputScheme { } "control" => Handle::Control, - _ => { - log::error!("invalid path '{path}'"); - return Err(SysError::new(EINVAL)); + "events" => Handle::HotplugEvents { + events: EventFlags::empty(), + pending: Vec::new(), + notified: false, + }, + + // dynamic device consumer: must be a currently registered device + name => { + if !self.devices.contains_key(name) { + return Err(SysError::new(ENOENT)); + } + Handle::DeviceConsumer { + device_name: name.to_owned(), + events: EventFlags::empty(), + pending: Vec::new(), + notified: false, + } } }; @@ -274,7 +424,7 @@ impl SchemeSync for InputScheme { let handle = self.handles.get(id)?; if let Handle::Consumer { vt, .. } = handle { - write!(w, "{vt}").unwrap(); + write!(w, "{vt}").map_err(|_| SysError::new(EINVAL))?; Ok(()) } else { Err(SysError::new(EINVAL)) @@ -282,6 +432,50 @@ impl SchemeSync for InputScheme { }) } + fn getdents<'buf>( + &mut self, + id: usize, + mut buf: DirentBuf<&'buf mut [u8]>, + opaque_offset: u64, + ) -> syscall::Result> { + let handle = self.handles.get(id)?; + if !matches!(handle, Handle::SchemeRoot) { + return Err(SysError::new(ENOTDIR)); + } + + let static_entries: &[&str] = &[ + "producer", + "consumer", + "consumer_bootlog", + "events", + "handle", + "handle_early", + "control", + ]; + + let device_names: Vec<&str> = self.devices.keys().map(|s| s.as_str()).collect(); + + let all_entries: Vec<(&str, DirentKind)> = static_entries + .iter() + .map(|&name| (name, DirentKind::Directory)) + .chain(device_names.iter().map(|&name| (name, DirentKind::Unspecified))) + .collect(); + + for (idx, (name, kind)) in all_entries + .iter() + .enumerate() + .skip(opaque_offset as usize) + { + buf.entry(DirEntry { + inode: 0, + next_opaque_id: idx as u64 + 1, + name, + kind: *kind, + })?; + } + Ok(buf) + } + fn read( &mut self, id: usize, @@ -313,6 +507,22 @@ impl SchemeSync for InputScheme { Ok(copy) } + Handle::DeviceConsumer { pending, .. } => { + let copy = core::cmp::min(pending.len(), buf.len()); + for (i, byte) in pending.drain(..copy).enumerate() { + buf[i] = byte; + } + Ok(copy) + } + + Handle::HotplugEvents { pending, .. } => { + let copy = core::cmp::min(pending.len(), buf.len()); + for (i, byte) in pending.drain(..copy).enumerate() { + buf[i] = byte; + } + Ok(copy) + } + Handle::Display { pending, .. } => { if buf.len() % size_of::() == 0 { let copy = core::cmp::min(pending.len(), buf.len() / size_of::()); @@ -334,6 +544,10 @@ impl SchemeSync for InputScheme { log::error!("producer tried to read"); return Err(SysError::new(EINVAL)); } + Handle::NamedProducer { .. } => { + log::error!("named producer tried to read"); + return Err(SysError::new(EINVAL)); + } Handle::Control => { log::error!("control tried to read"); return Err(SysError::new(EINVAL)); @@ -379,11 +593,20 @@ impl SchemeSync for InputScheme { log::error!("consumer tried to write"); return Err(SysError::new(EINVAL)); } + Handle::DeviceConsumer { .. } => { + log::error!("device consumer tried to write"); + return Err(SysError::new(EINVAL)); + } + Handle::HotplugEvents { .. } => { + log::error!("hotplug events tried to write"); + return Err(SysError::new(EINVAL)); + } Handle::Display { .. } => { log::error!("display tried to write"); return Err(SysError::new(EINVAL)); } Handle::Producer => {} + Handle::NamedProducer { .. } => {} Handle::SchemeRoot => return Err(SysError::new(EBADF)), } @@ -397,6 +620,11 @@ impl SchemeSync for InputScheme { buf.len() / size_of::(), ) }); + let producer_name = match self.handles.get(id)? { + Handle::NamedProducer { ref name } => Some(name.clone()), + Handle::Producer => None, + _ => return Err(SysError::new(EBADF)), + }; for i in 0..events.len() { let mut new_active_opt = None; @@ -437,38 +665,21 @@ impl SchemeSync for InputScheme { } } - let handle = self.handles.get_mut(id)?; - assert!(matches!(handle, Handle::Producer)); - - let buf = unsafe { + let serialized = unsafe { core::slice::from_raw_parts( (events.as_ptr()) as *const u8, events.len() * size_of::(), ) }; - if let Some(active_vt) = self.active_vt { - for handle in self.handles.values_mut() { - match handle { - Handle::Consumer { - pending, - notified, - vt, - .. - } => { - if *vt != active_vt { - continue; - } - - pending.extend_from_slice(buf); - *notified = false; - } - _ => continue, - } - } + if let Some(ref name) = producer_name { + self.deliver_to_device_consumers(name, serialized); } - Ok(buf.len()) + // named producers also feed the legacy path; legacy producers only feed legacy + self.deliver_to_legacy_consumers(serialized); + + Ok(serialized.len()) } fn fevent( @@ -487,6 +698,24 @@ impl SchemeSync for InputScheme { *notified = false; Ok(EventFlags::empty()) } + Handle::DeviceConsumer { + ref mut events, + ref mut notified, + .. + } => { + *events = flags; + *notified = false; + Ok(EventFlags::empty()) + } + Handle::HotplugEvents { + ref mut events, + ref mut notified, + .. + } => { + *events = flags; + *notified = false; + Ok(EventFlags::empty()) + } Handle::Display { ref mut events, ref mut notified, @@ -496,7 +725,7 @@ impl SchemeSync for InputScheme { *notified = false; Ok(EventFlags::empty()) } - Handle::Producer | Handle::Control => { + Handle::Producer | Handle::NamedProducer { .. } | Handle::Control => { log::error!("producer or control tried to use an event queue"); Err(SysError::new(EINVAL)) } @@ -505,8 +734,8 @@ impl SchemeSync for InputScheme { } fn on_close(&mut self, id: usize) { - match self.handles.remove(id).unwrap() { - Handle::Consumer { vt, .. } => { + match self.handles.remove(id) { + Some(Handle::Consumer { vt, .. }) => { self.vts.remove(&vt); if self.active_vt == Some(vt) { if let Some(&new_vt) = self.vts.last() { @@ -516,7 +745,15 @@ impl SchemeSync for InputScheme { } } } - _ => {} + Some(Handle::NamedProducer { name, .. }) => { + if let Some(device_id) = self.devices.remove(&name) { + self.emit_hotplug(DEVICE_REMOVE, device_id, &name); + } + } + Some(_) => {} + None => { + log::warn!("inputd: on_close called with unknown handle id {id}"); + } } } } @@ -564,6 +801,39 @@ fn deamon(daemon: daemon::SchemeDaemon) -> anyhow::Result<()> { *notified = true; } + Handle::DeviceConsumer { + events, + pending, + ref mut notified, + .. + } => { + if pending.is_empty() || *notified || !events.contains(EventFlags::EVENT_READ) { + continue; + } + + socket_file.write_response( + Response::post_fevent(*id, EventFlags::EVENT_READ.bits()), + SignalBehavior::Restart, + )?; + + *notified = true; + } + Handle::HotplugEvents { + events, + pending, + ref mut notified, + } => { + if pending.is_empty() || *notified || !events.contains(EventFlags::EVENT_READ) { + continue; + } + + socket_file.write_response( + Response::post_fevent(*id, EventFlags::EVENT_READ.bits()), + SignalBehavior::Restart, + )?; + + *notified = true; + } Handle::Display { events, pending, @@ -589,8 +859,11 @@ fn deamon(daemon: daemon::SchemeDaemon) -> anyhow::Result<()> { } fn daemon_runner(daemon: daemon::SchemeDaemon) -> ! { - deamon(daemon).unwrap(); - unreachable!(); + if let Err(err) = deamon(daemon) { + log::error!("inputd: scheme daemon failed: {err}"); + std::process::exit(1); + } + unreachable!() } const HELP: &str = r#" @@ -608,13 +881,26 @@ fn main() { match val.as_ref() { // Activates a VT. "-A" => { - let vt = args.next().unwrap().parse::().unwrap(); + let vt_str = args.next().unwrap_or_else(|| { + eprintln!("inputd: -A requires a VT number argument"); + std::process::exit(1); + }); + let vt = vt_str.parse::().unwrap_or_else(|_| { + eprintln!("inputd: invalid VT number: {vt_str}"); + std::process::exit(1); + }); - let mut handle = - inputd::ControlHandle::new().expect("inputd: failed to open control handle"); - handle - .activate_vt(vt) - .expect("inputd: failed to activate VT"); + let mut handle = match inputd::ControlHandle::new() { + Ok(h) => h, + Err(e) => { + eprintln!("inputd: failed to open control handle: {e}"); + std::process::exit(1); + } + }; + if let Err(e) = handle.activate_vt(vt) { + eprintln!("inputd: failed to activate VT {vt}: {e}"); + std::process::exit(1); + } } // Activates a keymap. "-K" => { @@ -630,11 +916,17 @@ fn main() { std::process::exit(1); }); - let mut handle = - inputd::ControlHandle::new().expect("inputd: failed to open control handle"); - handle - .activate_keymap(vt as usize) - .expect("inputd: failed to activate keymap"); + let mut handle = match inputd::ControlHandle::new() { + Ok(h) => h, + Err(e) => { + eprintln!("inputd: failed to open control handle: {e}"); + std::process::exit(1); + } + }; + if let Err(e) = handle.activate_keymap(vt as usize) { + eprintln!("inputd: failed to activate keymap: {e}"); + std::process::exit(1); + } } // List available keymaps "--keymaps" => { @@ -647,7 +939,10 @@ fn main() { println!("{}", HELP); } - _ => panic!("inputd: invalid argument: {}", val), + _ => { + eprintln!("inputd: invalid argument: {val}"); + std::process::exit(1); + } } } else { common::setup_logging(