702ec7efac
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
355 lines
11 KiB
Rust
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))
|
|
}
|
|
}
|