Files
RedBear-OS/local/recipes/system/redbear-ime/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

355 lines
11 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, Debug, Default)]
struct InputState {
composing: bool,
preedit: String,
committed: String,
}
enum HandleKind {
Root,
State,
Compose,
Candidates,
}
struct Handle {
kind: HandleKind,
offset: usize,
}
pub struct ImeScheme {
next_id: usize,
handles: HashMap<usize, Handle>,
state: InputState,
engine: Box<dyn ImeEngine>,
}
trait ImeEngine {
fn feed_key(&mut self, scancode: u8, pressed: bool, shift: bool, altgr: bool) -> ImeResult;
fn reset(&mut self);
fn candidates(&self) -> &[String];
}
#[derive(Clone, Debug)]
struct ImeResult {
committed: Option<char>,
preedit: String,
candidates_changed: bool,
}
struct BasicLatinEngine {
dead_key: Option<char>,
candidates: Vec<String>,
compose_scancode: u8,
dead_key_index: usize,
}
impl BasicLatinEngine {
fn new() -> Self {
BasicLatinEngine {
dead_key: None,
candidates: Vec::new(),
compose_scancode: 0x5D,
dead_key_index: 0,
}
}
fn compose_dead(&self, base: char) -> Option<char> {
let dk = self.dead_key?;
match (dk, base) {
('\u{0300}', 'a') => Some('à'),
('\u{0300}', 'e') => Some('è'),
('\u{0300}', 'i') => Some('ì'),
('\u{0300}', 'o') => Some('ò'),
('\u{0300}', 'u') => Some('ù'),
('\u{0301}', 'a') => Some('á'),
('\u{0301}', 'e') => Some('é'),
('\u{0301}', 'i') => Some('í'),
('\u{0301}', 'o') => Some('ó'),
('\u{0301}', 'u') => Some('ú'),
('\u{0302}', 'a') => Some('â'),
('\u{0302}', 'e') => Some('ê'),
('\u{0302}', 'i') => Some('î'),
('\u{0302}', 'o') => Some('ô'),
('\u{0302}', 'u') => Some('û'),
('\u{0308}', 'a') => Some('ä'),
('\u{0308}', 'e') => Some('ë'),
('\u{0308}', 'i') => Some('ï'),
('\u{0308}', 'o') => Some('ö'),
('\u{0308}', 'u') => Some('ü'),
('\u{030C}', 'a') => Some('ǎ'),
('\u{030C}', 'e') => Some('ě'),
('\u{030C}', 'i') => Some('ǐ'),
('\u{030C}', 'o') => Some('ǒ'),
('\u{030C}', 'u') => Some('ǔ'),
_ => None,
}
}
fn scancode_to_char(scancode: u8, shift: bool) -> Option<char> {
let lower = match scancode {
0x02 => '1',
0x03 => '2',
0x04 => '3',
0x05 => '4',
0x06 => '5',
0x07 => '6',
0x08 => '7',
0x09 => '8',
0x0A => '9',
0x0B => '0',
0x10 => 'q',
0x11 => 'w',
0x12 => 'e',
0x13 => 'r',
0x14 => 't',
0x15 => 'y',
0x16 => 'u',
0x17 => 'i',
0x18 => 'o',
0x19 => 'p',
0x1E => 'a',
0x1F => 's',
0x20 => 'd',
0x21 => 'f',
0x22 => 'g',
0x23 => 'h',
0x24 => 'j',
0x25 => 'k',
0x26 => 'l',
0x2C => 'z',
0x2D => 'x',
0x2E => 'c',
0x2F => 'v',
0x30 => 'b',
0x31 => 'n',
0x32 => 'm',
0x27 => ';',
0x28 => '\'',
0x29 => '`',
0x33 => ',',
0x34 => '.',
0x35 => '/',
0x0C => '-',
0x0D => '=',
_ => return None,
};
if shift {
Some(lower.to_ascii_uppercase())
} else {
Some(lower)
}
}
}
impl ImeEngine for BasicLatinEngine {
fn feed_key(&mut self, scancode: u8, pressed: bool, shift: bool, _altgr: bool) -> ImeResult {
if !pressed {
return ImeResult {
committed: None,
preedit: String::new(),
candidates_changed: false,
};
}
if let Some(current_dead) = self.dead_key.take() {
if let Some(base) = Self::scancode_to_char(scancode, shift) {
match self.compose_dead(base) {
Some(composed) => {
self.candidates.clear();
return ImeResult {
committed: Some(composed),
preedit: String::new(),
candidates_changed: true,
};
}
None => {
self.dead_key = None;
return ImeResult {
committed: Some(current_dead),
preedit: String::new(),
candidates_changed: false,
};
}
}
} else {
self.dead_key = Some(current_dead);
return ImeResult {
committed: None,
preedit: current_dead.to_string(),
candidates_changed: false,
};
}
}
let is_dead_trigger = scancode == self.compose_scancode;
if is_dead_trigger {
let dead_keys = [
('\u{0300}', "grave"),
('\u{0301}', "acute"),
('\u{0302}', "circumflex"),
('\u{0308}', "diaeresis"),
('\u{030C}', "caron"),
];
let (dk, _name) = dead_keys[self.dead_key_index % dead_keys.len()];
self.dead_key_index = (self.dead_key_index + 1) % dead_keys.len();
self.dead_key = Some(dk);
self.candidates = vec![dk.to_string()];
return ImeResult {
committed: None,
preedit: dk.to_string(),
candidates_changed: true,
};
}
ImeResult {
committed: None,
preedit: String::new(),
candidates_changed: false,
}
}
fn reset(&mut self) {
self.dead_key = None;
self.candidates.clear();
}
fn candidates(&self) -> &[String] {
&self.candidates
}
}
impl ImeScheme {
pub fn new() -> Self {
ImeScheme {
next_id: 1,
handles: HashMap::new(),
state: InputState::default(),
engine: Box::new(BasicLatinEngine::new()),
}
}
pub fn feed(&mut self, scancode: u8, pressed: bool, shift: bool, altgr: bool) {
let result = self.engine.feed_key(scancode, pressed, shift, altgr);
if let Some(ch) = result.committed {
self.state.committed.push(ch);
self.state.composing = false;
self.state.preedit.clear();
}
if result.candidates_changed {
self.state.composing = !self.engine.candidates().is_empty();
}
if !result.preedit.is_empty() {
self.state.composing = true;
self.state.preedit = result.preedit;
}
}
}
impl redox_scheme::SchemeBlockMut for ImeScheme {
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,
"state" => HandleKind::State,
"compose" => HandleKind::Compose,
"candidates" => HandleKind::Candidates,
_ => 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 handle = self.handles.get_mut(&id).ok_or(Error::new(EBADF))?;
let content: Vec<u8> = match &handle.kind {
HandleKind::Root => "state\ncompose\ncandidates\n".as_bytes().to_vec(),
HandleKind::State => format!(
"composing={}\npreedit={}\ncommitted={}\n",
self.state.composing, self.state.preedit, self.state.committed
)
.into_bytes(),
HandleKind::Compose => "basic-latin\n".as_bytes().to_vec(),
HandleKind::Candidates => {
let mut out = String::new();
for (i, c) in self.engine.candidates().iter().enumerate() {
out.push_str(&format!("{}\t{}\n", i, c));
}
out.into_bytes()
}
};
if handle.offset >= content.len() {
return Ok(Some(0));
}
let remaining = &content[handle.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::Compose => {
let input = String::from_utf8_lossy(buf);
for line in input.lines() {
let line = line.trim();
if line == "reset" {
self.engine.reset();
self.state = InputState::default();
continue;
}
let mut fields = line.split_whitespace();
let Some("feed") = fields.next() else {
continue;
};
let Some(scancode) = fields.next().and_then(|field| field.parse::<u8>().ok())
else {
continue;
};
let Some(pressed) = fields.next().and_then(|field| field.parse::<bool>().ok())
else {
continue;
};
let shift = fields
.next()
.and_then(|field| field.parse::<bool>().ok())
.unwrap_or(false);
let altgr = fields
.next()
.and_then(|field| field.parse::<bool>().ok())
.unwrap_or(false);
self.feed(scancode, pressed, shift, altgr);
}
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))
}
}