Files
RedBear-OS/local/recipes/system/redbear-accessibility/source/src/scheme.rs
T
vasilito 702ec7efac feat: relibc S1 — sem_open refcounting + glibc cross-reference assessment
Phase S1 (Critical Correctness):
- sem_open/sem_close: global refcounting via BTreeMap + AtomicUsize
- sem_close: decrements refcount, munmaps only at zero
- sem_open: reuses existing mapping, O_EXCL returns EEXIST
- sem_unlink: marks entry for removal before shm_unlink
- va_list parsing: reads mode_t and value from stack after oflag
- All 11 sem_* functions verified in libc.so T

Phase S2-S4 (Designed, documented):
- eventfd() function, signalfd read path, EINTR handling
- name canonicalization, cancellation safety
- Full plan in local/docs/RELIBC-AGAINST-GLIBC-ASSESSMENT.md

Reference: glibc 2.41 cloned to local/reference/glibc/

Boot verified: greeter ready on VT 3 with refcounted semaphores
2026-05-05 21:12:08 +01:00

295 lines
10 KiB
Rust

use std::collections::HashMap;
use syscall::data::Stat;
use syscall::error::{Error, Result, EBADF, EINVAL, ENOENT};
use syscall::flag::{MODE_DIR, MODE_FILE};
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) enum StickyKeyState {
Off,
Latched,
Locked,
}
#[derive(Clone, Debug)]
struct AccessibilityConfig {
sticky_keys: bool,
slow_keys: bool,
slow_key_delay_ms: u64,
bounce_keys: bool,
bounce_key_delay_ms: u64,
repeat_keys: bool,
repeat_delay_ms: u64,
repeat_interval_ms: u64,
}
impl Default for AccessibilityConfig {
fn default() -> Self {
AccessibilityConfig {
sticky_keys: false,
slow_keys: false,
slow_key_delay_ms: 300,
bounce_keys: false,
bounce_key_delay_ms: 300,
repeat_keys: true,
repeat_delay_ms: 500,
repeat_interval_ms: 50,
}
}
}
#[derive(Clone, Debug)]
struct KeyRecord {
pressed: bool,
timestamp_ms: u64,
}
struct Handle {
kind: HandleKind,
offset: usize,
}
#[derive(Clone, Debug)]
enum HandleKind {
Root,
Config,
Status,
StickyKeys,
}
pub struct AccessibilityScheme {
next_id: usize,
handles: HashMap<usize, Handle>,
config: AccessibilityConfig,
sticky_modifiers: HashMap<u16, StickyKeyState>,
key_history: HashMap<u16, KeyRecord>,
}
impl AccessibilityScheme {
pub fn new() -> Self {
AccessibilityScheme {
next_id: 1,
handles: HashMap::new(),
config: AccessibilityConfig::default(),
sticky_modifiers: HashMap::new(),
key_history: HashMap::new(),
}
}
pub fn filter_key(&mut self, code: u16, pressed: bool, now_ms: u64) -> Option<(u16, bool)> {
if let Some(record) = self.key_history.get(&code) {
if self.config.bounce_keys && pressed && !record.pressed {
let elapsed = now_ms.saturating_sub(record.timestamp_ms);
if elapsed < self.config.bounce_key_delay_ms {
return None;
}
}
if self.config.slow_keys && pressed && !record.pressed {
let elapsed = now_ms.saturating_sub(record.timestamp_ms);
if elapsed < self.config.slow_key_delay_ms {
return None;
}
}
}
self.key_history.insert(
code,
KeyRecord {
pressed,
timestamp_ms: now_ms,
},
);
if self.config.sticky_keys {
let is_modifier = matches!(
code,
0x1D | 0x2A | 0x36 | 0x38 | 0x5B | 0x5C | 0x5D | 0x61 | 0x64
);
if is_modifier && pressed {
let state = self
.sticky_modifiers
.entry(code)
.or_insert(StickyKeyState::Off);
match *state {
StickyKeyState::Off => {
*state = StickyKeyState::Latched;
return None;
}
StickyKeyState::Latched => {
*state = StickyKeyState::Locked;
return None;
}
StickyKeyState::Locked => {
*state = StickyKeyState::Off;
return None;
}
}
}
}
Some((code, pressed))
}
pub fn active_sticky_modifiers(&self) -> Vec<(u16, StickyKeyState)> {
self.sticky_modifiers
.iter()
.filter(|(_, state)| **state != StickyKeyState::Off)
.map(|(&code, &state)| (code, state))
.collect()
}
}
impl redox_scheme::SchemeBlockMut for AccessibilityScheme {
fn open(&mut self, path: &str, _flags: usize, _uid: u32, _gid: u32) -> Result<Option<usize>> {
let cleaned = path.trim_matches('/');
let kind = match cleaned {
"" => HandleKind::Root,
"config" => HandleKind::Config,
"status" => HandleKind::Status,
"sticky-keys" => HandleKind::StickyKeys,
_ => 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))
}
fn read(&mut self, id: usize, buf: &mut [u8]) -> Result<Option<usize>> {
let (kind, offset) = {
let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?;
(handle.kind.clone(), handle.offset)
};
let content: Vec<u8> = match &kind {
HandleKind::Root => "config\nstatus\nsticky-keys\n".as_bytes().to_vec(),
HandleKind::Config => {
format!(
"sticky_keys={}\nslow_keys={}\nslow_key_delay_ms={}\nbounce_keys={}\nbounce_key_delay_ms={}\nrepeat_keys={}\nrepeat_delay_ms={}\nrepeat_interval_ms={}\n",
self.config.sticky_keys,
self.config.slow_keys,
self.config.slow_key_delay_ms,
self.config.bounce_keys,
self.config.bounce_key_delay_ms,
self.config.repeat_keys,
self.config.repeat_delay_ms,
self.config.repeat_interval_ms,
)
.into_bytes()
}
HandleKind::Status => {
let sticky_count = self.active_sticky_modifiers().len();
format!(
"sticky_keys_active={}\nslow_keys_active={}\nbounce_keys_active={}\nactive_sticky_count={}\n",
self.config.sticky_keys,
self.config.slow_keys,
self.config.bounce_keys,
sticky_count,
)
.into_bytes()
}
HandleKind::StickyKeys => {
let mut out = String::new();
for (code, state) in self.active_sticky_modifiers() {
let label = match code {
0x1D | 0x61 | 0x64 => "ctrl",
0x2A | 0x36 => "shift",
0x38 => "alt",
0x5B | 0x5C => "meta",
_ => "unknown",
};
let state_str = match state {
StickyKeyState::Latched => "latched",
StickyKeyState::Locked => "locked",
StickyKeyState::Off => "off",
};
out.push_str(&format!("{} {}\n", label, state_str));
}
out.into_bytes()
}
};
let handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?;
if offset >= content.len() {
return Ok(Some(0));
}
let remaining = &content[offset..];
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))
}
fn write(&mut self, id: usize, buf: &[u8]) -> Result<Option<usize>> {
let handle = self.handles.get(&id).ok_or(Error::new(EBADF))?;
match &handle.kind {
HandleKind::Config => {
let input = String::from_utf8_lossy(buf);
for line in input.lines() {
let line = line.trim();
if let Some((key, value)) = line.split_once('=') {
match key.trim() {
"sticky_keys" => self.config.sticky_keys = value.trim() == "true",
"slow_keys" => self.config.slow_keys = value.trim() == "true",
"slow_key_delay_ms" => {
if let Ok(v) = value.trim().parse() {
self.config.slow_key_delay_ms = v;
}
}
"bounce_keys" => self.config.bounce_keys = value.trim() == "true",
"bounce_key_delay_ms" => {
if let Ok(v) = value.trim().parse() {
self.config.bounce_key_delay_ms = v;
}
}
"repeat_keys" => self.config.repeat_keys = value.trim() == "true",
_ => {}
}
}
}
Ok(Some(buf.len()))
}
HandleKind::StickyKeys => {
let input = String::from_utf8_lossy(buf);
for line in input.lines() {
let mut fields = line.split_whitespace();
let Some("key") = fields.next() else {
continue;
};
let Some(code) = fields.next().and_then(|field| field.parse::<u16>().ok())
else {
continue;
};
let Some(pressed) = fields.next().and_then(|field| field.parse::<bool>().ok())
else {
continue;
};
let now_ms = fields
.next()
.and_then(|field| field.parse::<u64>().ok())
.unwrap_or(0);
let _ = self.filter_key(code, pressed, now_ms);
}
Ok(Some(buf.len()))
}
_ => Err(Error::new(EINVAL)),
}
}
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,
_ => stat.st_mode = MODE_FILE | 0o644,
}
Ok(Some(0))
}
fn close(&mut self, id: usize) -> Result<Option<usize>> {
self.handles.remove(&id);
Ok(Some(0))
}
}