From 6b8ce0503f4927c4681b893e548e1640326e9862 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:14:29 +0400 Subject: [PATCH 01/65] feat(aweos): add bounded PS/2 keyboard and mouse protocol runtime --- kernel/src/drivers/ps2.rs | 238 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 kernel/src/drivers/ps2.rs diff --git a/kernel/src/drivers/ps2.rs b/kernel/src/drivers/ps2.rs new file mode 100644 index 0000000..a885b78 --- /dev/null +++ b/kernel/src/drivers/ps2.rs @@ -0,0 +1,238 @@ +#![no_std] + +use crate::arch::x86_64::{io_in8, io_out8}; + +pub const DATA_PORT: u16 = 0x60; +pub const STATUS_PORT: u16 = 0x64; +pub const COMMAND_PORT: u16 = 0x64; +pub const MAX_EVENTS: usize = 128; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Ps2Error { + Timeout, + Controller, + QueueFull, + InvalidPacket, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum KeyCode { + Escape, + Enter, + Backspace, + Tab, + Space, + Left, + Right, + Up, + Down, + Character(u8), + Unknown(u8), +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Ps2Event { + Key { code: KeyCode, pressed: bool }, + Pointer { dx: i16, dy: i16, buttons: u8 }, +} + +pub struct EventQueue { + slots: [Option; MAX_EVENTS], + head: usize, + len: usize, +} + +impl EventQueue { + pub const fn new() -> Self { + Self { + slots: [None; MAX_EVENTS], + head: 0, + len: 0, + } + } + + pub const fn len(&self) -> usize { self.len } + pub const fn is_empty(&self) -> bool { self.len == 0 } + + pub fn push(&mut self, event: Ps2Event) -> Result<(), Ps2Error> { + if self.len == MAX_EVENTS { return Err(Ps2Error::QueueFull); } + let index = (self.head + self.len) % MAX_EVENTS; + self.slots[index] = Some(event); + self.len += 1; + Ok(()) + } + + pub fn pop(&mut self) -> Option { + if self.len == 0 { return None; } + let event = self.slots[self.head].take(); + self.head = (self.head + 1) % MAX_EVENTS; + self.len -= 1; + event + } +} + +impl Default for EventQueue { fn default() -> Self { Self::new() } } + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct KeyboardDecoder { + extended: bool, + break_code: bool, +} + +impl KeyboardDecoder { + pub const fn new() -> Self { Self { extended: false, break_code: false } } + + pub fn feed(&mut self, byte: u8) -> Option { + match byte { + 0xE0 => { self.extended = true; None } + 0xF0 => { self.break_code = true; None } + code => { + let pressed = !self.break_code; + let mapped = if self.extended { + match code { + 0x4B => KeyCode::Left, + 0x4D => KeyCode::Right, + 0x48 => KeyCode::Up, + 0x50 => KeyCode::Down, + other => KeyCode::Unknown(other), + } + } else { + match code { + 0x01 => KeyCode::Escape, + 0x0D => KeyCode::Tab, + 0x1C => KeyCode::Enter, + 0x0E => KeyCode::Backspace, + 0x39 => KeyCode::Space, + 0x10..=0x35 => KeyCode::Character(code), + other => KeyCode::Unknown(other), + } + }; + self.extended = false; + self.break_code = false; + Some(Ps2Event::Key { code: mapped, pressed }) + } + } + } +} + +impl Default for KeyboardDecoder { fn default() -> Self { Self::new() } } + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct MouseDecoder { + packet: [u8; 3], + index: usize, +} + +impl MouseDecoder { + pub const fn new() -> Self { Self { packet: [0; 3], index: 0 } } + + pub fn feed(&mut self, byte: u8) -> Result, Ps2Error> { + if self.index == 0 && byte & 0x08 == 0 { + return Ok(None); + } + self.packet[self.index] = byte; + self.index += 1; + if self.index < 3 { return Ok(None); } + self.index = 0; + let flags = self.packet[0]; + let x = self.packet[1] as i16; + let y = self.packet[2] as i16; + let dx = if flags & 0x10 != 0 { x - 256 } else { x }; + let dy_raw = if flags & 0x20 != 0 { y - 256 } else { y }; + let dy = -dy_raw; + if flags & 0xC0 != 0 { return Err(Ps2Error::InvalidPacket); } + Ok(Some(Ps2Event::Pointer { dx, dy, buttons: flags & 0x07 })) + } +} + +impl Default for MouseDecoder { fn default() -> Self { Self::new() } } + +pub struct Controller { + pub events: EventQueue, + pub keyboard: KeyboardDecoder, + pub mouse: MouseDecoder, + pub initialized: bool, +} + +impl Controller { + pub const fn new() -> Self { + Self { + events: EventQueue::new(), + keyboard: KeyboardDecoder::new(), + mouse: MouseDecoder::new(), + initialized: false, + } + } + + /// Initialize the legacy 8042 controller with bounded polling time. + pub unsafe fn init(&mut self) -> Result<(), Ps2Error> { + const LIMIT: usize = 100_000; + let mut spins = 0; + while unsafe { io_in8(STATUS_PORT) } & 0x01 != 0 { + let _ = unsafe { io_in8(DATA_PORT) }; + spins += 1; + if spins == LIMIT { return Err(Ps2Error::Timeout); } + } + unsafe { io_out8(COMMAND_PORT, 0xAE); } // enable first PS/2 port + self.initialized = true; + Ok(()) + } + + /// Poll all currently available bytes. This function never blocks forever. + pub unsafe fn poll(&mut self, max_bytes: usize) -> Result { + if !self.initialized { return Err(Ps2Error::Controller); } + let mut read = 0; + while read < max_bytes { + let status = unsafe { io_in8(STATUS_PORT) }; + if status & 0x01 == 0 { break; } + let byte = unsafe { io_in8(DATA_PORT) }; + // Bit 5 distinguishes the auxiliary mouse port on the legacy controller. + if status & 0x20 != 0 { + if let Some(event) = self.mouse.feed(byte)? { self.events.push(event)?; } + } else if let Some(event) = self.keyboard.feed(byte) { + self.events.push(event)?; + } + read += 1; + } + Ok(read) + } +} + +impl Default for Controller { fn default() -> Self { Self::new() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn keyboard_decodes_press_release_and_extended_keys() { + let mut d = KeyboardDecoder::new(); + assert_eq!(d.feed(0x1C), Some(Ps2Event::Key { code: KeyCode::Enter, pressed: true })); + assert_eq!(d.feed(0xF0), None); + assert_eq!(d.feed(0x1C), Some(Ps2Event::Key { code: KeyCode::Enter, pressed: false })); + assert_eq!(d.feed(0xE0), None); + assert_eq!(d.feed(0x4B), Some(Ps2Event::Key { code: KeyCode::Left, pressed: true })); + } + + #[test] + fn mouse_decodes_signed_deltas_and_buttons() { + let mut d = MouseDecoder::new(); + assert_eq!(d.feed(0x09).unwrap(), None); + assert_eq!(d.feed(0xFE).unwrap(), None); + assert_eq!(d.feed(0x02).unwrap(), Some(Ps2Event::Pointer { dx: -2, dy: -2, buttons: 1 })); + } + + #[test] + fn malformed_mouse_packet_is_rejected() { + let mut d = MouseDecoder::new(); + assert_eq!(d.feed(0xC8).unwrap_err(), Ps2Error::InvalidPacket); + } + + #[test] + fn event_queue_is_bounded_fifo() { + let mut q = EventQueue::new(); + q.push(Ps2Event::Key { code: KeyCode::Escape, pressed: true }).unwrap(); + assert_eq!(q.pop(), Some(Ps2Event::Key { code: KeyCode::Escape, pressed: true })); + assert!(q.pop().is_none()); + } +} \ No newline at end of file From b8a8ee38901976f555e648d90783e2c534017245 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:14:36 +0400 Subject: [PATCH 02/65] feat(aweos): wire PS/2 runtime driver into kernel drivers --- kernel/src/drivers/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/src/drivers/mod.rs b/kernel/src/drivers/mod.rs index 36f5820..f0d03a4 100644 --- a/kernel/src/drivers/mod.rs +++ b/kernel/src/drivers/mod.rs @@ -38,6 +38,7 @@ pub mod linux_transaction_orchestrator; pub mod pci; pub mod pci_virtio_probe; pub mod product_gate; +pub mod ps2; pub mod universal; pub mod virtio; pub mod virtio_block; @@ -113,6 +114,7 @@ pub use pci_virtio_probe::{ MAX_VIRTIO_PROBES, ProbeError as VirtioProbeError, VirtioDeviceKind, VirtioPciProbe, }; pub use product_gate::{ProductGate, ProductGateError}; +pub use ps2::{Controller as Ps2Controller, EventQueue as Ps2EventQueue, KeyCode as Ps2KeyCode, MouseDecoder as Ps2MouseDecoder, KeyboardDecoder as Ps2KeyboardDecoder, Ps2Error, Ps2Event}; pub use universal::{ DriverAbi, DriverAction, DriverError, DriverOs, DriverRequest, DriverResult, validate_request, }; From d6438a791687ec4ef7a55e0f41e23d444bc10b99 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:14:52 +0400 Subject: [PATCH 03/65] feat(aweos): implement bounded initd runtime supervisor --- services/initd/src/runtime.rs | 184 ++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 services/initd/src/runtime.rs diff --git a/services/initd/src/runtime.rs b/services/initd/src/runtime.rs new file mode 100644 index 0000000..bf95309 --- /dev/null +++ b/services/initd/src/runtime.rs @@ -0,0 +1,184 @@ +#![no_std] + +use super::{RestartPolicy, ServiceId, ServiceSpec, ServiceState, validate_spec, MAX_SERVICES}; + +pub const MAX_DEPENDENCIES: usize = 8; +pub const MAX_FAILURES: u8 = 3; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum RuntimeError { + Full, + Duplicate, + InvalidSpec, + InvalidDependency, + DependencyCycle, + MissingDependency, + SpawnFailed, + InvalidTransition, + Quarantined, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct ServiceRuntimeSpec { + pub spec: ServiceSpec, + pub dependencies: [Option; MAX_DEPENDENCIES], + pub dependency_count: u8, + pub entry: usize, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct RuntimeRecord { + pub spec: ServiceRuntimeSpec, + pub state: ServiceState, + pub process_id: u64, + pub failures: u8, +} + +pub type SpawnFn = fn(entry: usize, service: ServiceId, memory_pages: u32, cpu_budget: u32) -> Result; + +pub struct Supervisor { + records: [Option; MAX_SERVICES], + count: usize, + spawn: SpawnFn, +} + +impl Supervisor { + pub const fn new(spawn: SpawnFn) -> Self { + Self { records: [None; MAX_SERVICES], count: 0, spawn } + } + + pub const fn len(&self) -> usize { self.count } + + pub fn register(&mut self, runtime: ServiceRuntimeSpec) -> Result<(), RuntimeError> { + validate_spec(runtime.spec).map_err(|_| RuntimeError::InvalidSpec)?; + if runtime.dependency_count as usize > MAX_DEPENDENCIES || runtime.entry == 0 { + return Err(RuntimeError::InvalidSpec); + } + if self.find(runtime.spec.id).is_some() { + return Err(RuntimeError::Duplicate); + } + for index in 0..runtime.dependency_count as usize { + let dep = runtime.dependencies[index].ok_or(RuntimeError::InvalidDependency)?; + if dep == runtime.spec.id { return Err(RuntimeError::DependencyCycle); } + } + if self.count == MAX_SERVICES { return Err(RuntimeError::Full); } + let slot = self.records.iter().position(Option::is_none).ok_or(RuntimeError::Full)?; + self.records[slot] = Some(RuntimeRecord { + spec: runtime, + state: ServiceState::Declared, + process_id: 0, + failures: 0, + }); + self.count += 1; + Ok(()) + } + + pub fn start(&mut self, id: ServiceId) -> Result { + let index = self.find(id).ok_or(RuntimeError::MissingDependency)?; + if self.records[index].unwrap().state == ServiceState::Quarantined { + return Err(RuntimeError::Quarantined); + } + let runtime = self.records[index].unwrap(); + for dep_index in 0..runtime.spec.dependency_count as usize { + let dep = runtime.spec.dependencies[dep_index].ok_or(RuntimeError::InvalidDependency)?; + let dep_record = self.find(dep).ok_or(RuntimeError::MissingDependency)?; + if self.records[dep_record].unwrap().state != ServiceState::Running { + return Err(RuntimeError::MissingDependency); + } + } + if !matches!(runtime.state, ServiceState::Declared | ServiceState::Failed | ServiceState::Stopped) { + return Err(RuntimeError::InvalidTransition); + } + self.records[index].as_mut().unwrap().state = ServiceState::Starting; + let pid = match (self.spawn)(runtime.spec.entry, id, runtime.spec.spec.memory_limit_pages, runtime.spec.spec.cpu_budget_ticks) { + Ok(pid) if pid != 0 => pid, + _ => { + self.records[index].as_mut().unwrap().state = ServiceState::Failed; + return Err(RuntimeError::SpawnFailed); + } + }; + let record = self.records[index].as_mut().unwrap(); + record.process_id = pid; + record.failures = 0; + record.state = ServiceState::Running; + Ok(pid) + } + + pub fn report_failure(&mut self, id: ServiceId) -> Result { + let index = self.find(id).ok_or(RuntimeError::MissingDependency)?; + let record = self.records[index].as_mut().unwrap(); + if record.state != ServiceState::Running { return Err(RuntimeError::InvalidTransition); } + record.failures = record.failures.saturating_add(1); + record.state = if record.failures > MAX_FAILURES { + ServiceState::Quarantined + } else { + ServiceState::Failed + }; + Ok(record.state) + } + + pub fn restart(&mut self, id: ServiceId) -> Result { + let index = self.find(id).ok_or(RuntimeError::MissingDependency)?; + let record = self.records[index].unwrap(); + if record.state == ServiceState::Quarantined || record.failures > MAX_FAILURES { + return Err(RuntimeError::Quarantined); + } + match record.spec.spec.restart { + RestartPolicy::Never => return Err(RuntimeError::InvalidTransition), + RestartPolicy::OnFailure | RestartPolicy::Always => self.start(id), + } + } + + pub fn state(&self, id: ServiceId) -> Option { + self.find(id).and_then(|i| self.records[i].map(|r| r.state)) + } + + pub fn process_id(&self, id: ServiceId) -> Option { + self.find(id).and_then(|i| self.records[i].map(|r| r.process_id)).filter(|pid| *pid != 0) + } + + fn find(&self, id: ServiceId) -> Option { + self.records.iter().position(|r| r.map(|record| record.spec.spec.id) == Some(id)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn spawn(entry: usize, service: ServiceId, _mem: u32, _cpu: u32) -> Result { + if entry == 0 || service.0 == 0 { return Err(()); } + Ok((entry as u64) ^ (service.0 as u64)) + } + + fn spec(id: u16, deps: [Option; MAX_DEPENDENCIES], count: u8) -> ServiceRuntimeSpec { + ServiceRuntimeSpec { + spec: ServiceSpec { + id: ServiceId(id), restart: RestartPolicy::OnFailure, + capability_mask: u64::MAX, memory_limit_pages: 4, cpu_budget_ticks: 100, + }, + dependencies: deps, dependency_count: count, entry: id as usize + 1, + } + } + + #[test] + fn dependencies_are_enforced_before_spawn() { + let mut s = Supervisor::new(spawn); + s.register(spec(1, [None; MAX_DEPENDENCIES], 0)).unwrap(); + s.register(spec(2, [Some(ServiceId(1)), None, None, None, None, None, None, None], 1)).unwrap(); + assert_eq!(s.start(ServiceId(2)), Err(RuntimeError::MissingDependency)); + s.start(ServiceId(1)).unwrap(); + assert_ne!(s.start(ServiceId(2)), Err(RuntimeError::MissingDependency)); + assert_eq!(s.state(ServiceId(2)), Some(ServiceState::Running)); + } + + #[test] + fn failure_escalates_to_quarantine() { + let mut s = Supervisor::new(spawn); + s.register(spec(1, [None; MAX_DEPENDENCIES], 0)).unwrap(); + s.start(ServiceId(1)).unwrap(); + for _ in 0..MAX_FAILURES { assert_eq!(s.report_failure(ServiceId(1)).unwrap(), ServiceState::Failed); s.start(ServiceId(1)).unwrap(); } + assert_eq!(s.report_failure(ServiceId(1)).unwrap(), ServiceState::Quarantined); + assert_eq!(s.restart(ServiceId(1)), Err(RuntimeError::Quarantined)); + } +} \ No newline at end of file From 832d6e932ca4cf890eeba19822a90c0008b0bc20 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:15:03 +0400 Subject: [PATCH 04/65] feat(aweos): wire initd runtime supervisor into service crate --- services/initd/src/lib.rs | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/services/initd/src/lib.rs b/services/initd/src/lib.rs index ec88581..193e263 100644 --- a/services/initd/src/lib.rs +++ b/services/initd/src/lib.rs @@ -4,12 +4,15 @@ //! primitives needed for isolation; policy and lifecycle stay in userspace. mod core; +pub mod runtime; pub use core::{ BoundedPath, CoreError, CoreManager, CoreManagerKind, CoreManagerRegistry, CrashRecord, LogLevel, LogRecord, MAX_CORE_MANAGERS, MAX_LOG_MESSAGE, MAX_PATH, RecoveryAction, SecurityPolicy, UserImage, recovery_action, start_core_manager, validate_user_image, }; +pub use runtime::{RuntimeError, RuntimeRecord, ServiceRuntimeSpec, SpawnFn, Supervisor}; + pub const INIT_ABI_MAJOR: u16 = 1; pub const INIT_ABI_MINOR: u16 = 3; pub const MAX_SERVICES: usize = 32; @@ -221,12 +224,8 @@ mod tests { let mut table = ServiceTable::new(); table.register(SPEC).unwrap(); assert_eq!(table.len(), 1); - table - .set_state(ServiceId(1), ServiceState::Starting) - .unwrap(); - table - .set_state(ServiceId(1), ServiceState::Running) - .unwrap(); + table.set_state(ServiceId(1), ServiceState::Starting).unwrap(); + table.set_state(ServiceId(1), ServiceState::Running).unwrap(); assert_eq!(table.spec(ServiceId(1)), Some(SPEC)); } @@ -234,9 +233,7 @@ mod tests { fn failed_service_restarts_only_when_policy_allows() { let mut table = ServiceTable::new(); table.register(SPEC).unwrap(); - table - .set_state(ServiceId(1), ServiceState::Starting) - .unwrap(); + table.set_state(ServiceId(1), ServiceState::Starting).unwrap(); table.set_state(ServiceId(1), ServiceState::Failed).unwrap(); table.restart(ServiceId(1)).unwrap(); assert_eq!(table.state(ServiceId(1)), Some(ServiceState::Starting)); @@ -246,13 +243,9 @@ mod tests { fn quarantined_service_cannot_restart() { let mut table = ServiceTable::new(); table.register(SPEC).unwrap(); - table - .set_state(ServiceId(1), ServiceState::Starting) - .unwrap(); + table.set_state(ServiceId(1), ServiceState::Starting).unwrap(); table.set_state(ServiceId(1), ServiceState::Failed).unwrap(); - table - .set_state(ServiceId(1), ServiceState::Quarantined) - .unwrap(); + table.set_state(ServiceId(1), ServiceState::Quarantined).unwrap(); assert_eq!(table.restart(ServiceId(1)), Err(ServiceError::Quarantined)); } @@ -262,4 +255,4 @@ mod tests { table.register(SPEC).unwrap(); assert_eq!(table.register(SPEC), Err(ServiceError::Duplicate)); } -} +} \ No newline at end of file From 67cf4c99ad116ded9d6d5a4f17e62be7b2af9042 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:15:17 +0400 Subject: [PATCH 05/65] feat(aweos): add bounded appd execution supervisor --- services/appd/src/runtime.rs | 172 +++++++++++++++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 services/appd/src/runtime.rs diff --git a/services/appd/src/runtime.rs b/services/appd/src/runtime.rs new file mode 100644 index 0000000..e5ffc56 --- /dev/null +++ b/services/appd/src/runtime.rs @@ -0,0 +1,172 @@ +#![no_std] + +use super::{AppId, AppManifest, AppState, AppError, validate_manifest, AWE_APP_ABI_MAJOR, AWE_APP_ABI_MINOR}; + +pub const MAX_APPS: usize = 32; +pub const MAX_FAILURES: u8 = 3; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum RuntimeError { + Full, + InvalidManifest, + Duplicate, + NotFound, + CapabilityDenied, + SpawnFailed, + InvalidTransition, + Quarantined, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct RuntimeApp { + pub manifest: AppManifest, + pub state: AppState, + pub process_id: u64, + pub window_id: u16, + pub failures: u8, +} + +pub type SpawnFn = fn(app: AppId, memory_pages: u32, capability_mask: u64) -> Result; +pub type WindowFn = fn(app: AppId) -> Result; + +pub struct Supervisor { + apps: [Option; MAX_APPS], + count: usize, + spawn: SpawnFn, + create_window: WindowFn, +} + +impl Supervisor { + pub const fn new(spawn: SpawnFn, create_window: WindowFn) -> Self { + Self { apps: [None; MAX_APPS], count: 0, spawn, create_window } + } + + pub const fn len(&self) -> usize { self.count } + + pub fn admit(&mut self, manifest: AppManifest) -> Result<(), RuntimeError> { + validate_manifest(manifest).map_err(|_| RuntimeError::InvalidManifest)?; + if self.find(manifest.id).is_some() { return Err(RuntimeError::Duplicate); } + let slot = self.apps.iter().position(Option::is_none).ok_or(RuntimeError::Full)?; + self.apps[slot] = Some(RuntimeApp { + manifest, + state: AppState::Installed, + process_id: 0, + window_id: 0, + failures: 0, + }); + self.count += 1; + Ok(()) + } + + pub fn start(&mut self, id: AppId, caller_capabilities: u64) -> Result { + let index = self.find(id).ok_or(RuntimeError::NotFound)?; + let record = self.apps[index].unwrap(); + if record.state == AppState::Quarantined { return Err(RuntimeError::Quarantined); } + if record.manifest.capability_mask & !caller_capabilities != 0 { + return Err(RuntimeError::CapabilityDenied); + } + if !matches!(record.state, AppState::Installed | AppState::Stopped | AppState::Failed) { + return Err(RuntimeError::InvalidTransition); + } + self.apps[index].as_mut().unwrap().state = AppState::Starting; + let pid = match (self.spawn)(id, record.manifest.memory_limit_pages, record.manifest.capability_mask) { + Ok(pid) if pid != 0 => pid, + _ => { + self.apps[index].as_mut().unwrap().state = AppState::Failed; + return Err(RuntimeError::SpawnFailed); + } + }; + let window = match (self.create_window)(id) { + Ok(window) if window != 0 => window, + _ => { + self.apps[index].as_mut().unwrap().state = AppState::Failed; + return Err(RuntimeError::SpawnFailed); + } + }; + let record = self.apps[index].as_mut().unwrap(); + record.process_id = pid; + record.window_id = window; + record.failures = 0; + record.state = AppState::Running; + Ok(pid) + } + + pub fn stop(&mut self, id: AppId) -> Result<(), RuntimeError> { + let index = self.find(id).ok_or(RuntimeError::NotFound)?; + let record = self.apps[index].as_mut().unwrap(); + if record.state != AppState::Running { return Err(RuntimeError::InvalidTransition); } + record.state = AppState::Stopped; + record.process_id = 0; + Ok(()) + } + + pub fn report_failure(&mut self, id: AppId) -> Result { + let index = self.find(id).ok_or(RuntimeError::NotFound)?; + let record = self.apps[index].as_mut().unwrap(); + if record.state != AppState::Running { return Err(RuntimeError::InvalidTransition); } + record.failures = record.failures.saturating_add(1); + record.state = if record.failures > MAX_FAILURES { AppState::Quarantined } else { AppState::Failed }; + Ok(record.state) + } + + pub fn state(&self, id: AppId) -> Option { + self.find(id).and_then(|i| self.apps[i].map(|r| r.state)) + } + + pub fn record(&self, id: AppId) -> Option { + self.find(id).and_then(|i| self.apps[i]) + } + + fn find(&self, id: AppId) -> Option { + self.apps.iter().position(|r| r.map(|a| a.manifest.id) == Some(id)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn spawn(app: AppId, memory: u32, _caps: u64) -> Result { + if app.0 == 0 || memory == 0 { Err(()) } else { Ok(0x1000 + app.0) } + } + fn window(app: AppId) -> Result { + if app.0 == 0 || app.0 > u16::MAX as u64 { Err(()) } else { Ok(app.0 as u16) } + } + fn manifest(id: u64) -> AppManifest { + AppManifest { + id: AppId(id), abi_major: AWE_APP_ABI_MAJOR, abi_minor: AWE_APP_ABI_MINOR, + memory_limit_pages: 4, capability_mask: 0b101, dependency_count: 0, resource_count: 0, + } + } + + #[test] + fn admission_start_and_stop_are_real_lifecycle_steps() { + let mut s = Supervisor::new(spawn, window); + s.admit(manifest(1)).unwrap(); + let pid = s.start(AppId(1), 0b111).unwrap(); + assert_eq!(pid, 0x1001); + assert_eq!(s.record(AppId(1)).unwrap().window_id, 1); + assert_eq!(s.state(AppId(1)), Some(AppState::Running)); + s.stop(AppId(1)).unwrap(); + assert_eq!(s.state(AppId(1)), Some(AppState::Stopped)); + } + + #[test] + fn capability_admission_is_fail_closed() { + let mut s = Supervisor::new(spawn, window); + s.admit(manifest(2)).unwrap(); + assert_eq!(s.start(AppId(2), 0b001), Err(RuntimeError::CapabilityDenied)); + } + + #[test] + fn repeated_failures_quarantine_app() { + let mut s = Supervisor::new(spawn, window); + s.admit(manifest(3)).unwrap(); + s.start(AppId(3), 0b111).unwrap(); + for _ in 0..MAX_FAILURES { + assert_eq!(s.report_failure(AppId(3)).unwrap(), AppState::Failed); + s.start(AppId(3), 0b111).unwrap(); + } + assert_eq!(s.report_failure(AppId(3)).unwrap(), AppState::Quarantined); + } +} \ No newline at end of file From e164b3a8c200fa36a36f2197601a4290da5379f2 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:15:26 +0400 Subject: [PATCH 06/65] feat(aweos): wire appd runtime supervisor into application service --- services/appd/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/services/appd/src/lib.rs b/services/appd/src/lib.rs index 643a701..c1f56d4 100644 --- a/services/appd/src/lib.rs +++ b/services/appd/src/lib.rs @@ -5,6 +5,7 @@ mod awos; pub mod catalog; +pub mod runtime; pub use awos::{ AWOS_HEADER_LEN, AWOS_MAGIC, AWOS_MAX_CODE, AWOS_MAX_DATA, AWOS_MAX_MANIFEST, @@ -14,6 +15,8 @@ pub use awos::{ package_parts, package_transition, validate_awos, }; +pub use runtime::{RuntimeApp, RuntimeError as AppRuntimeError, SpawnFn as AppSpawnFn, Supervisor as AppSupervisor, WindowFn as AppWindowFn, MAX_APPS as RUNTIME_MAX_APPS, MAX_FAILURES as APP_MAX_FAILURES}; + pub const AWE_APP_ABI_MAJOR: u16 = 1; pub const AWE_APP_ABI_MINOR: u16 = 3; pub const MAX_DEPS: usize = 32; From 8c1cec7e4f185128b15da8ac0b6ff1da75b3ebe3 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:15:38 +0400 Subject: [PATCH 07/65] feat(aweos): add bounded runtime graphics and window manager core --- kernel/src/runtime/graphics.rs | 141 +++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 kernel/src/runtime/graphics.rs diff --git a/kernel/src/runtime/graphics.rs b/kernel/src/runtime/graphics.rs new file mode 100644 index 0000000..4529437 --- /dev/null +++ b/kernel/src/runtime/graphics.rs @@ -0,0 +1,141 @@ +#![no_std] + +use crate::runtime::InputEvent; + +pub const MAX_WINDOWS: usize = 32; +pub const MAX_WIDTH: u32 = 8192; +pub const MAX_HEIGHT: u32 = 8192; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Rect { pub x: i32, pub y: i32, pub width: u32, pub height: u32 } +impl Rect { + pub const fn valid(self) -> bool { self.width > 0 && self.height > 0 && self.width <= MAX_WIDTH && self.height <= MAX_HEIGHT } + pub fn contains(self, x: i32, y: i32) -> bool { + x >= self.x && y >= self.y && x < self.x.saturating_add(self.width as i32) && y < self.y.saturating_add(self.height as i32) + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Window { pub id: u16, pub rect: Rect, pub visible: bool, pub focused: bool, pub z: u16 } + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum WindowError { Full, Invalid, NotFound, BufferTooSmall, InvalidFramebuffer, Overflow } + +pub struct WindowManager { windows: [Option; MAX_WINDOWS], count: usize, next_id: u16, focused: Option } +impl WindowManager { + pub const fn new() -> Self { Self { windows: [None; MAX_WINDOWS], count: 0, next_id: 1, focused: None } } + pub const fn count(&self) -> usize { self.count } + pub const fn focused(&self) -> Option { self.focused } + + pub fn create(&mut self, rect: Rect) -> Result { + if !rect.valid() { return Err(WindowError::Invalid); } + let slot = self.windows.iter().position(Option::is_none).ok_or(WindowError::Full)?; + let id = self.next_id; + self.next_id = self.next_id.wrapping_add(1).max(1); + let z = self.count as u16; + self.windows[slot] = Some(Window { id, rect, visible: true, focused: false, z }); + self.count += 1; + self.focus(id)?; + Ok(id) + } + + pub fn destroy(&mut self, id: u16) -> Result<(), WindowError> { + let index = self.find(id).ok_or(WindowError::NotFound)?; + self.windows[index] = None; + self.count -= 1; + if self.focused == Some(id) { self.focused = None; self.raise_top_focus(); } + Ok(()) + } + + pub fn focus(&mut self, id: u16) -> Result<(), WindowError> { + let index = self.find(id).ok_or(WindowError::NotFound)?; + for window in self.windows.iter_mut().flatten() { window.focused = false; } + self.windows[index].as_mut().unwrap().focused = true; + self.focused = Some(id); + let max_z = self.windows.iter().flatten().map(|w| w.z).max().unwrap_or(0); + self.windows[index].as_mut().unwrap().z = max_z.saturating_add(1); + Ok(()) + } + + pub fn move_window(&mut self, id: u16, x: i32, y: i32) -> Result<(), WindowError> { + let index = self.find(id).ok_or(WindowError::NotFound)?; + self.windows[index].as_mut().unwrap().rect.x = x; + self.windows[index].as_mut().unwrap().rect.y = y; + Ok(()) + } + + pub fn resize(&mut self, id: u16, width: u32, height: u32) -> Result<(), WindowError> { + let index = self.find(id).ok_or(WindowError::NotFound)?; + let rect = Rect { x: self.windows[index].unwrap().rect.x, y: self.windows[index].unwrap().rect.y, width, height }; + if !rect.valid() { return Err(WindowError::Invalid); } + self.windows[index].as_mut().unwrap().rect = rect; + Ok(()) + } + + pub fn hit_test(&mut self, x: i32, y: i32) -> Option { + let mut best: Option = None; + for window in self.windows.iter().flatten() { + if window.visible && window.rect.contains(x, y) && best.map(|b| window.z > b.z).unwrap_or(true) { best = Some(*window); } + } + if let Some(w) = best { let _ = self.focus(w.id); Some(w.id) } else { None } + } + + pub fn handle_input(&mut self, event: InputEvent) -> Option { + match event { InputEvent::Pointer { x, y, .. } => self.hit_test(x, y), InputEvent::Key { .. } => self.focused } + } + + fn find(&self, id: u16) -> Option { self.windows.iter().position(|w| w.map(|v| v.id) == Some(id)) } + fn raise_top_focus(&mut self) { if let Some(id) = self.windows.iter().flatten().max_by_key(|w| w.z).map(|w| w.id) { let _ = self.focus(id); } } +} +impl Default for WindowManager { fn default() -> Self { Self::new() } } + +pub struct DoubleBuffer<'a> { pub width: u32, pub height: u32, pub pitch: u32, pub front: &'a mut [u8], pub back: &'a mut [u8] } +impl<'a> DoubleBuffer<'a> { + pub fn new(width: u32, height: u32, pitch: u32, front: &'a mut [u8], back: &'a mut [u8]) -> Result { + if width == 0 || height == 0 || pitch < width.saturating_mul(4) { return Err(WindowError::InvalidFramebuffer); } + let bytes = (height as usize).checked_mul(pitch as usize).ok_or(WindowError::Overflow)?; + if front.len() < bytes || back.len() < bytes { return Err(WindowError::BufferTooSmall); } + Ok(Self { width, height, pitch, front, back }) + } + pub fn clear(&mut self, pixel: [u8; 4]) { + for y in 0..self.height as usize { + let row = y * self.pitch as usize; + for x in 0..self.width as usize { let o = row + x * 4; self.back[o..o+4].copy_from_slice(&pixel); } + } + } + pub fn fill_rect(&mut self, rect: Rect, pixel: [u8; 4]) { + let x0 = rect.x.max(0) as u32; let y0 = rect.y.max(0) as u32; + let x1 = rect.x.saturating_add(rect.width as i32).max(0) as u32; + let y1 = rect.y.saturating_add(rect.height as i32).max(0) as u32; + let x1 = x1.min(self.width); let y1 = y1.min(self.height); + for y in y0.min(self.height)..y1 { let row = y as usize * self.pitch as usize; for x in x0.min(self.width)..x1 { let o = row + x as usize * 4; self.back[o..o+4].copy_from_slice(&pixel); } } + } + pub fn present(&mut self) { self.front[..self.back.len()].copy_from_slice(self.back); } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn window_manager_focus_hit_move_resize_and_destroy() { + let mut wm = WindowManager::new(); + let a = wm.create(Rect { x: 10, y: 10, width: 100, height: 100 }).unwrap(); + let b = wm.create(Rect { x: 20, y: 20, width: 100, height: 100 }).unwrap(); + assert_eq!(wm.hit_test(30, 30), Some(b)); + wm.move_window(b, 200, 200).unwrap(); + wm.resize(a, 120, 120).unwrap(); + assert_eq!(wm.hit_test(30, 30), Some(a)); + wm.destroy(a).unwrap(); + assert_eq!(wm.count(), 1); + } + + #[test] + fn double_buffer_validates_stride_and_capacity() { + let mut front = [0u8; 64]; let mut back = [0u8; 64]; + let mut db = DoubleBuffer::new(4, 4, 16, &mut front, &mut back).unwrap(); + db.clear([1, 2, 3, 4]); + db.fill_rect(Rect { x: 1, y: 1, width: 2, height: 2 }, [4, 3, 2, 1]); + db.present(); + assert_eq!(front[0..4], [1,2,3,4]); + } +} \ No newline at end of file From a5966ff0f341130606f9b80794a10cd64be40574 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:15:44 +0400 Subject: [PATCH 08/65] feat(aweos): expose integrated runtime graphics/window manager --- kernel/src/runtime/mod.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/kernel/src/runtime/mod.rs b/kernel/src/runtime/mod.rs index 64017ff..279445c 100644 --- a/kernel/src/runtime/mod.rs +++ b/kernel/src/runtime/mod.rs @@ -1,15 +1,17 @@ //! Native AWEOS runtime kernel contract. -//! A runtime handle is intentionally capability-scoped: callers receive only -//! the operations represented by the supplied capability set. +//! Runtime handles are capability-scoped and all privileged operations remain +//! behind explicit validation boundaries. #![allow(dead_code)] mod end_user; +pub mod graphics; pub use end_user::{ AppRecord, AppState, EndUserRuntime, EndUserRuntimeError, FramebufferInfo, InputEvent, RuntimeEvent, ServiceRecord, ServiceState, }; +pub use graphics::{DoubleBuffer, Rect as RuntimeRect, Window, WindowError, WindowManager}; #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct CapabilitySet(pub u64); From 73538221249a4a86993cf52cd2f03580aaa036c0 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:16:00 +0400 Subject: [PATCH 09/65] fix(aweos): gate PS/2 port I/O to x86_64 targets --- kernel/src/drivers/ps2.rs | 154 +++++++++----------------------------- 1 file changed, 34 insertions(+), 120 deletions(-) diff --git a/kernel/src/drivers/ps2.rs b/kernel/src/drivers/ps2.rs index a885b78..6a249b0 100644 --- a/kernel/src/drivers/ps2.rs +++ b/kernel/src/drivers/ps2.rs @@ -1,5 +1,6 @@ #![no_std] +#[cfg(target_arch = "x86_64")] use crate::arch::x86_64::{io_in8, io_out8}; pub const DATA_PORT: u16 = 0x60; @@ -8,80 +9,36 @@ pub const COMMAND_PORT: u16 = 0x64; pub const MAX_EVENTS: usize = 128; #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum Ps2Error { - Timeout, - Controller, - QueueFull, - InvalidPacket, -} +pub enum Ps2Error { Timeout, Controller, QueueFull, InvalidPacket, Unsupported } #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum KeyCode { - Escape, - Enter, - Backspace, - Tab, - Space, - Left, - Right, - Up, - Down, - Character(u8), - Unknown(u8), -} +pub enum KeyCode { Escape, Enter, Backspace, Tab, Space, Left, Right, Up, Down, Character(u8), Unknown(u8) } #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum Ps2Event { - Key { code: KeyCode, pressed: bool }, - Pointer { dx: i16, dy: i16, buttons: u8 }, -} - -pub struct EventQueue { - slots: [Option; MAX_EVENTS], - head: usize, - len: usize, -} +pub enum Ps2Event { Key { code: KeyCode, pressed: bool }, Pointer { dx: i16, dy: i16, buttons: u8 } } +pub struct EventQueue { slots: [Option; MAX_EVENTS], head: usize, len: usize } impl EventQueue { - pub const fn new() -> Self { - Self { - slots: [None; MAX_EVENTS], - head: 0, - len: 0, - } - } - + pub const fn new() -> Self { Self { slots: [None; MAX_EVENTS], head: 0, len: 0 } } pub const fn len(&self) -> usize { self.len } pub const fn is_empty(&self) -> bool { self.len == 0 } - pub fn push(&mut self, event: Ps2Event) -> Result<(), Ps2Error> { if self.len == MAX_EVENTS { return Err(Ps2Error::QueueFull); } let index = (self.head + self.len) % MAX_EVENTS; - self.slots[index] = Some(event); - self.len += 1; - Ok(()) + self.slots[index] = Some(event); self.len += 1; Ok(()) } - pub fn pop(&mut self) -> Option { if self.len == 0 { return None; } let event = self.slots[self.head].take(); - self.head = (self.head + 1) % MAX_EVENTS; - self.len -= 1; - event + self.head = (self.head + 1) % MAX_EVENTS; self.len -= 1; event } } - impl Default for EventQueue { fn default() -> Self { Self::new() } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct KeyboardDecoder { - extended: bool, - break_code: bool, -} - +pub struct KeyboardDecoder { extended: bool, break_code: bool } impl KeyboardDecoder { pub const fn new() -> Self { Self { extended: false, break_code: false } } - pub fn feed(&mut self, byte: u8) -> Option { match byte { 0xE0 => { self.extended = true; None } @@ -89,82 +46,42 @@ impl KeyboardDecoder { code => { let pressed = !self.break_code; let mapped = if self.extended { - match code { - 0x4B => KeyCode::Left, - 0x4D => KeyCode::Right, - 0x48 => KeyCode::Up, - 0x50 => KeyCode::Down, - other => KeyCode::Unknown(other), - } + match code { 0x4B => KeyCode::Left, 0x4D => KeyCode::Right, 0x48 => KeyCode::Up, 0x50 => KeyCode::Down, other => KeyCode::Unknown(other) } } else { - match code { - 0x01 => KeyCode::Escape, - 0x0D => KeyCode::Tab, - 0x1C => KeyCode::Enter, - 0x0E => KeyCode::Backspace, - 0x39 => KeyCode::Space, - 0x10..=0x35 => KeyCode::Character(code), - other => KeyCode::Unknown(other), - } + match code { 0x01 => KeyCode::Escape, 0x0D => KeyCode::Tab, 0x1C => KeyCode::Enter, 0x0E => KeyCode::Backspace, 0x39 => KeyCode::Space, 0x10..=0x35 => KeyCode::Character(code), other => KeyCode::Unknown(other) } }; - self.extended = false; - self.break_code = false; + self.extended = false; self.break_code = false; Some(Ps2Event::Key { code: mapped, pressed }) } } } } - impl Default for KeyboardDecoder { fn default() -> Self { Self::new() } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct MouseDecoder { - packet: [u8; 3], - index: usize, -} - +pub struct MouseDecoder { packet: [u8; 3], index: usize } impl MouseDecoder { pub const fn new() -> Self { Self { packet: [0; 3], index: 0 } } - pub fn feed(&mut self, byte: u8) -> Result, Ps2Error> { - if self.index == 0 && byte & 0x08 == 0 { - return Ok(None); - } - self.packet[self.index] = byte; - self.index += 1; + if self.index == 0 && byte & 0x08 == 0 { return Ok(None); } + self.packet[self.index] = byte; self.index += 1; if self.index < 3 { return Ok(None); } self.index = 0; let flags = self.packet[0]; - let x = self.packet[1] as i16; - let y = self.packet[2] as i16; + if flags & 0xC0 != 0 { return Err(Ps2Error::InvalidPacket); } + let x = self.packet[1] as i16; let y = self.packet[2] as i16; let dx = if flags & 0x10 != 0 { x - 256 } else { x }; let dy_raw = if flags & 0x20 != 0 { y - 256 } else { y }; - let dy = -dy_raw; - if flags & 0xC0 != 0 { return Err(Ps2Error::InvalidPacket); } - Ok(Some(Ps2Event::Pointer { dx, dy, buttons: flags & 0x07 })) + Ok(Some(Ps2Event::Pointer { dx, dy: -dy_raw, buttons: flags & 0x07 })) } } - impl Default for MouseDecoder { fn default() -> Self { Self::new() } } -pub struct Controller { - pub events: EventQueue, - pub keyboard: KeyboardDecoder, - pub mouse: MouseDecoder, - pub initialized: bool, -} - +pub struct Controller { pub events: EventQueue, pub keyboard: KeyboardDecoder, pub mouse: MouseDecoder, pub initialized: bool } impl Controller { - pub const fn new() -> Self { - Self { - events: EventQueue::new(), - keyboard: KeyboardDecoder::new(), - mouse: MouseDecoder::new(), - initialized: false, - } - } + pub const fn new() -> Self { Self { events: EventQueue::new(), keyboard: KeyboardDecoder::new(), mouse: MouseDecoder::new(), initialized: false } } - /// Initialize the legacy 8042 controller with bounded polling time. + #[cfg(target_arch = "x86_64")] pub unsafe fn init(&mut self) -> Result<(), Ps2Error> { const LIMIT: usize = 100_000; let mut spins = 0; @@ -173,12 +90,14 @@ impl Controller { spins += 1; if spins == LIMIT { return Err(Ps2Error::Timeout); } } - unsafe { io_out8(COMMAND_PORT, 0xAE); } // enable first PS/2 port - self.initialized = true; - Ok(()) + unsafe { io_out8(COMMAND_PORT, 0xAE); } + self.initialized = true; Ok(()) } - /// Poll all currently available bytes. This function never blocks forever. + #[cfg(not(target_arch = "x86_64"))] + pub unsafe fn init(&mut self) -> Result<(), Ps2Error> { Err(Ps2Error::Unsupported) } + + #[cfg(target_arch = "x86_64")] pub unsafe fn poll(&mut self, max_bytes: usize) -> Result { if !self.initialized { return Err(Ps2Error::Controller); } let mut read = 0; @@ -186,26 +105,24 @@ impl Controller { let status = unsafe { io_in8(STATUS_PORT) }; if status & 0x01 == 0 { break; } let byte = unsafe { io_in8(DATA_PORT) }; - // Bit 5 distinguishes the auxiliary mouse port on the legacy controller. if status & 0x20 != 0 { if let Some(event) = self.mouse.feed(byte)? { self.events.push(event)?; } - } else if let Some(event) = self.keyboard.feed(byte) { - self.events.push(event)?; - } + } else if let Some(event) = self.keyboard.feed(byte) { self.events.push(event)?; } read += 1; } Ok(read) } -} + #[cfg(not(target_arch = "x86_64"))] + pub unsafe fn poll(&mut self, _max_bytes: usize) -> Result { Err(Ps2Error::Unsupported) } +} impl Default for Controller { fn default() -> Self { Self::new() } } #[cfg(test)] mod tests { use super::*; - #[test] - fn keyboard_decodes_press_release_and_extended_keys() { + fn keyboard_decodes_press_release_and_extended() { let mut d = KeyboardDecoder::new(); assert_eq!(d.feed(0x1C), Some(Ps2Event::Key { code: KeyCode::Enter, pressed: true })); assert_eq!(d.feed(0xF0), None); @@ -213,23 +130,20 @@ mod tests { assert_eq!(d.feed(0xE0), None); assert_eq!(d.feed(0x4B), Some(Ps2Event::Key { code: KeyCode::Left, pressed: true })); } - #[test] - fn mouse_decodes_signed_deltas_and_buttons() { + fn mouse_decodes_signed_motion() { let mut d = MouseDecoder::new(); assert_eq!(d.feed(0x09).unwrap(), None); assert_eq!(d.feed(0xFE).unwrap(), None); assert_eq!(d.feed(0x02).unwrap(), Some(Ps2Event::Pointer { dx: -2, dy: -2, buttons: 1 })); } - #[test] fn malformed_mouse_packet_is_rejected() { let mut d = MouseDecoder::new(); assert_eq!(d.feed(0xC8).unwrap_err(), Ps2Error::InvalidPacket); } - #[test] - fn event_queue_is_bounded_fifo() { + fn queue_is_fifo_and_bounded() { let mut q = EventQueue::new(); q.push(Ps2Event::Key { code: KeyCode::Escape, pressed: true }).unwrap(); assert_eq!(q.pop(), Some(Ps2Event::Key { code: KeyCode::Escape, pressed: true })); From 9c001b9175d8902f4b56f4c1e69e344276a415a5 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:16:24 +0400 Subject: [PATCH 10/65] feat(aweos): replace demo userspace entry with validated runtime handoff --- kernel/src/entry.rs | 302 +++++++++----------------------------------- 1 file changed, 60 insertions(+), 242 deletions(-) diff --git a/kernel/src/entry.rs b/kernel/src/entry.rs index 29dcd8b..dcc8b41 100644 --- a/kernel/src/entry.rs +++ b/kernel/src/entry.rs @@ -1,295 +1,113 @@ #![no_std] use awe_boot_protocol::{validate, BootInfo}; - use crate::boot_phase::{BootPhase, BootProgress}; use crate::memory::PhysicalFrameAllocator; +use crate::runtime::{CapabilitySet, EndUserRuntime, FramebufferInfo, RuntimeContext}; #[repr(u8)] #[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum KernelBootStatus { - Ready = 0, - InvalidBootInfo = 1, - UnsupportedArchitecture = 2, - NoCpu = 3, - NoUsableMemory = 4, -} - -pub struct KernelContext { - progress: BootProgress, -} - -impl Default for KernelContext { - fn default() -> Self { - Self::new() - } -} +pub enum KernelBootStatus { Ready = 0, InvalidBootInfo = 1, UnsupportedArchitecture = 2, NoCpu = 3, NoUsableMemory = 4, InvalidFramebuffer = 5 } +pub struct KernelContext { progress: BootProgress } +impl Default for KernelContext { fn default() -> Self { Self::new() } } impl KernelContext { - pub const fn new() -> Self { - Self { - progress: BootProgress::new(), - } - } - pub const fn phase(&self) -> BootPhase { - self.progress.phase() - } - pub fn advance(&mut self) -> bool { - self.progress.advance() - } + pub const fn new() -> Self { Self { progress: BootProgress::new() } } + pub const fn phase(&self) -> BootPhase { self.progress.phase() } + pub fn advance(&mut self) -> bool { self.progress.advance() } } -/// Stable entry contract between AWE Loader and CellKernel. pub fn kernel_entry(info: &BootInfo) -> KernelBootStatus { - if !validate(info) { - return KernelBootStatus::InvalidBootInfo; - } - if !info.architecture.is_supported() { - return KernelBootStatus::UnsupportedArchitecture; - } - if info.cpu_count == 0 { - return KernelBootStatus::NoCpu; - } - if info.memory_region_count == 0 || info.memory_regions.is_null() { - return KernelBootStatus::NoUsableMemory; - } - + if !validate(info) { return KernelBootStatus::InvalidBootInfo; } + if !info.architecture.is_supported() { return KernelBootStatus::UnsupportedArchitecture; } + if info.cpu_count == 0 { return KernelBootStatus::NoCpu; } + if info.memory_region_count == 0 || info.memory_regions.is_null() { return KernelBootStatus::NoUsableMemory; } let mut frames = unsafe { PhysicalFrameAllocator::from_boot_info(info) }; - if frames.allocate().is_none() { - return KernelBootStatus::NoUsableMemory; - } + if frames.allocate().is_none() { return KernelBootStatus::NoUsableMemory; } #[cfg(all(target_arch = "x86_64", target_os = "none"))] { - use crate::arch::x86_64::gdt::init_gdt; + use crate::arch::x86_64::gdt::{init_gdt, KERNEL_CODE_SELECTOR, USER_CODE_SELECTOR}; + use crate::arch::x86_64::idt::IDT; use crate::arch::x86_64::interrupts::init_pic; + use crate::arch::x86_64::isr_stubs::init_idt_stubs; use crate::arch::x86_64::serial_write_str; use crate::memory::allocator::init_kernel_heap; use crate::platform::pit::Pit; + use crate::syscall::init_msr_syscall; static mut KERNEL_STACK: [u8; 65536] = [0; 65536]; static mut USER_STACK: [u8; 16384] = [0; 16384]; - - let stack_top = core::ptr::addr_of_mut!(KERNEL_STACK) as u64 + 65536; + let kernel_stack_top = core::ptr::addr_of_mut!(KERNEL_STACK) as u64 + 65536; let user_stack_top = core::ptr::addr_of_mut!(USER_STACK) as u64 + 16384; - use crate::arch::x86_64::gdt::{KERNEL_CODE_SELECTOR, USER_CODE_SELECTOR}; - use crate::arch::x86_64::idt::IDT; - use crate::arch::x86_64::isr_stubs::init_idt_stubs; - use crate::syscall::init_msr_syscall; - - init_gdt(stack_top); - serial_write_str("AWEOS: GDT & TSS initialized\r\n"); - - unsafe { - init_msr_syscall( - userspace_entry as *const () as usize as u64, - KERNEL_CODE_SELECTOR, - USER_CODE_SELECTOR, - ); - } - serial_write_str("AWEOS: SYSCALL/SYSRET MSRs initialized\r\n"); - + init_gdt(kernel_stack_top); + serial_write_str("AWEOS: GDT/TSS initialized\r\n"); + unsafe { init_msr_syscall(userspace_entry as *const () as usize as u64, KERNEL_CODE_SELECTOR, USER_CODE_SELECTOR); } let idt_ptr = core::ptr::addr_of_mut!(IDT); - unsafe { - init_idt_stubs(&mut *idt_ptr, KERNEL_CODE_SELECTOR); - (*idt_ptr).load(); - } - serial_write_str("AWEOS: IDT initialized\r\n"); - + unsafe { init_idt_stubs(&mut *idt_ptr, KERNEL_CODE_SELECTOR); (*idt_ptr).load(); } init_kernel_heap(); - serial_write_str("AWEOS: Kernel Heap initialized\r\n"); - - use crate::drivers::pci; - let mut pci_out = [None; 16]; - let mut enumerator = pci::Enumerator::new(pci::PortConfigSpace); - if let Ok(_count) = enumerator.scan_bus(0, &mut pci_out) { - serial_write_str("AWEOS: PCI Bus 0 enumerated\r\n"); - } - - unsafe { - init_pic(); + unsafe { init_pic(); } + if let Some(pit) = Pit::new(1000) { unsafe { pit.program(); } } + serial_write_str("AWEOS: core interrupt/timer path initialized\r\n"); + + let mut runtime = EndUserRuntime::new(); + if info.framebuffer_address != 0 && info.framebuffer_size != 0 { + let fb = FramebufferInfo { + address: info.framebuffer_address, + size: info.framebuffer_size, + width: info.framebuffer_width, + height: info.framebuffer_height, + pitch: info.framebuffer_pitch, + bytes_per_pixel: 4, + }; + if runtime.attach_framebuffer(fb).is_err() { return KernelBootStatus::InvalidFramebuffer; } + serial_write_str("AWEOS: BootInfo framebuffer validated\r\n"); } - if let Some(pit) = Pit::new(1000) { - unsafe { - pit.program(); - } - } - serial_write_str("AWEOS: Interrupts & PIC/PIT initialized\r\n"); - serial_write_str("AWEOS: Preemptive Scheduler initialized\r\n"); - serial_write_str("AWEOS: boot protocol validated\r\n"); - serial_write_str("AWEOS: kernel state = RUNNING\r\n"); - serial_write_str("AWEOS: kernel is alive\r\n"); - serial_write_str("AWEOS: Entering Ring 3 Userspace...\r\n"); - unsafe { - enter_userspace(userspace_entry as *const () as usize as u64, user_stack_top); - } + let _ = runtime.declare_service(1, CapabilitySet::DEVICE.union(CapabilitySet::IPC), 3); + let _ = runtime.declare_service(2, CapabilitySet::STORAGE.union(CapabilitySet::IPC), 3); + let _ = runtime.declare_service(3, CapabilitySet::NETWORK.union(CapabilitySet::IPC), 3); + let _ = runtime.declare_service(4, CapabilitySet::IPC, 3); + let _ = runtime.declare_service(5, CapabilitySet::PROCESS.union(CapabilitySet::IPC), 3); + let _ = runtime.declare_service(6, CapabilitySet::UI.union(CapabilitySet::IPC), 3); + serial_write_str("AWEOS: end-user runtime control plane prepared\r\n"); + unsafe { enter_userspace(userspace_entry as *const () as usize as u64, user_stack_top); } } - KernelBootStatus::Ready } #[cfg(all(target_arch = "x86_64", target_os = "none"))] #[unsafe(no_mangle)] pub extern "C" fn userspace_entry() -> ! { - use crate::arch::x86_64::serial_write_str; - use awe_ayui::{AppType, Compositor, Framebuffer, Rect}; - - let msg = b"AWEOS: Ring 3 userspace reached and active!\r\n"; - serial_write_str("AWEOS: Ring 3 userspace reached and active!\r\n"); - + let message = b"AWEOS: Ring 3 active; device access is mediated by syscall/IPC.\r\n"; let mut process = crate::process::ProcessDescriptor { - id: crate::process::ProcessId(1), - state: crate::process::ProcessState::Running, - budget: crate::process::ResourceBudget { - cpu_ticks: 1000, - memory_bytes: 1048576, - ipc_messages: 1000, - }, - }; - let mut context = crate::syscall::SyscallContext { - process: &mut process, + id: crate::process::ProcessId(1), state: crate::process::ProcessState::Running, + budget: crate::process::ResourceBudget { cpu_ticks: 1000, memory_bytes: 1024 * 1024, ipc_messages: 64 }, }; - - context.dispatch(8, [msg.as_ptr() as u64, msg.len() as u64, 0, 0, 0, 0]); - serial_write_str("AWEOS: Initializing AWE-Compositor & Graphical Desktop Shell...\r\n"); - - let mut compositor = Compositor::new(); - let term_win = compositor - .create_app_window( - Rect { x: 40, y: 40, width: 500, height: 340 }, - AppType::Terminal, - b"AWETerminal v1.0", - ) - .unwrap_or(awe_ayui::WindowId(1)); - let sysinfo_win = compositor - .create_app_window( - Rect { x: 300, y: 100, width: 440, height: 320 }, - AppType::SystemMonitor, - b"System Information", - ) - .unwrap_or(awe_ayui::WindowId(2)); - let about_win = compositor - .create_app_window( - Rect { x: 180, y: 160, width: 360, height: 220 }, - AppType::Generic, - b"About AWEOS", - ) - .unwrap_or(awe_ayui::WindowId(3)); - - compositor.focus(about_win).ok(); - compositor.focus(sysinfo_win).ok(); - compositor.focus(term_win).ok(); - - serial_write_str("AWEOS: Desktop GUI initialized automatically with 3 active windows\r\n"); - - static mut BACK_BUFFER: [u8; 800 * 600 * 4] = [0; 800 * 600 * 4]; - let back_buf = unsafe { &mut *core::ptr::addr_of_mut!(BACK_BUFFER) }; - let mut fb = Framebuffer { - width: 800, - height: 600, - stride: 800, - buffer: back_buf, - gpu_accel: true, - }; - - serial_write_str("AWEOS: Entering interactive AWE-Compositor Main Event Loop...\r\n"); - context.dispatch( - 8, - [ - b"AWEOS: userspace execution completed cleanly!\r\n".as_ptr() as u64, - 48, - 0, - 0, - 0, - 0, - ], - ); - - let mut tick = 0u64; - loop { - tick = tick.wrapping_add(1); - if tick % 50 == 0 { - compositor.render_to_framebuffer(&mut fb); - } - unsafe { - core::arch::asm!("pause"); - } - } + let mut syscall = crate::syscall::SyscallContext { process: &mut process }; + let _ = syscall.dispatch(8, [message.as_ptr() as u64, message.len() as u64, 0, 0, 0, 0]); + loop { let _ = syscall.dispatch(0, [0; 6]); unsafe { core::arch::asm!("pause"); } } } #[cfg(all(target_arch = "x86_64", target_os = "none"))] pub unsafe fn enter_userspace(user_rip: u64, user_rsp: u64) { use crate::arch::x86_64::gdt::{USER_CODE_SELECTOR, USER_DATA_SELECTOR}; - - let user_cs = USER_CODE_SELECTOR as u64; - let user_ss = USER_DATA_SELECTOR as u64; - // IF=1, IOPL=0. Ring 3 must use syscalls/IPC for privileged operations. + let user_cs = USER_CODE_SELECTOR as u64; let user_ss = USER_DATA_SELECTOR as u64; + // IF=1, IOPL=0: Ring 3 cannot execute privileged device I/O. let rflags = 0x0202u64; - - unsafe { - core::arch::asm!( - "push {0}", - "push {1}", - "push {2}", - "push {3}", - "push {4}", - "iretq", - in(reg) user_ss, - in(reg) user_rsp, - in(reg) rflags, - in(reg) user_cs, - in(reg) user_rip, - options(noreturn) - ); - } + unsafe { core::arch::asm!("push {0}", "push {1}", "push {2}", "push {3}", "push {4}", "iretq", in(reg) user_ss, in(reg) user_rsp, in(reg) rflags, in(reg) user_cs, in(reg) user_rip, options(noreturn)); } } #[cfg(test)] mod tests { use super::*; use awe_boot_protocol::{Architecture, BootInfo, MemoryRegion}; - - #[test] - fn accepts_valid_x86_64_handoff_with_memory() { - let regions = [MemoryRegion { - base: 0x1000, - length: 0x10000, - kind: 1, - reserved: 0, - }]; - let info = BootInfo { - magic: awe_boot_protocol::AWE_BOOT_MAGIC, - version: awe_boot_protocol::AWE_BOOT_VERSION, - size: core::mem::size_of::() as u32, - architecture: Architecture::X86_64, - cpu_count: 1, - memory_regions: regions.as_ptr(), - memory_region_count: 1, - framebuffer_address: 0, - framebuffer_size: 0, - framebuffer_width: 0, - framebuffer_height: 0, - framebuffer_pitch: 0, - acpi_rsdp: 0, - device_tree: 0, - kernel_base: 0, - kernel_size: 0, - }; - assert_eq!(kernel_entry(&info), KernelBootStatus::Ready); - } - - #[test] - fn rejects_invalid_handoff() { - let mut info = BootInfo::empty(Architecture::X86_64); - info.magic = 0; - assert_eq!(kernel_entry(&info), KernelBootStatus::InvalidBootInfo); - } - - #[test] - fn rejects_missing_memory_map() { - let info = BootInfo::empty(Architecture::X86_64); - assert_eq!(kernel_entry(&info), KernelBootStatus::NoUsableMemory); + fn info() -> (BootInfo, [MemoryRegion; 1]) { + let regions = [MemoryRegion { base: 0x1000, length: 0x10000, kind: 1, reserved: 0 }]; + let boot = BootInfo { magic: awe_boot_protocol::AWE_BOOT_MAGIC, version: awe_boot_protocol::AWE_BOOT_VERSION, size: core::mem::size_of::() as u32, architecture: Architecture::X86_64, cpu_count: 1, memory_regions: regions.as_ptr(), memory_region_count: 1, framebuffer_address: 0, framebuffer_size: 0, framebuffer_width: 0, framebuffer_height: 0, framebuffer_pitch: 0, acpi_rsdp: 0, device_tree: 0, kernel_base: 0, kernel_size: 0 }; + (boot, regions) } + #[test] fn invalid_boot_info_is_rejected() { let (mut boot, _) = info(); boot.magic = 0; assert_eq!(kernel_entry(&boot), KernelBootStatus::InvalidBootInfo); } + #[test] fn valid_minimum_boot_info_is_accepted() { let (boot, _) = info(); assert_eq!(kernel_entry(&boot), KernelBootStatus::Ready); } } From 596815894c1f8e98265f4afb805cfc417b8204a0 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:16:35 +0400 Subject: [PATCH 11/65] feat(aweos): connect kernel runtime to native service planes --- kernel/Cargo.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 1afa61b..1d568bb 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -10,3 +10,9 @@ path = "src/lib.rs" [dependencies] awe-boot-protocol = { workspace = true } awe-ayui = { workspace = true } +awe-initd = { workspace = true } +awe-appd = { workspace = true } +awe-driverd = { workspace = true } +awe-netd = { workspace = true } +awe-storaged = { workspace = true } +awe-securityd = { workspace = true } From 890884940539e5af184c44795cfb7b767eebfd73 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:16:48 +0400 Subject: [PATCH 12/65] feat(aweos): integrate process, initd, appd, UI and input runtime --- kernel/src/runtime/system.rs | 161 +++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 kernel/src/runtime/system.rs diff --git a/kernel/src/runtime/system.rs b/kernel/src/runtime/system.rs new file mode 100644 index 0000000..9499239 --- /dev/null +++ b/kernel/src/runtime/system.rs @@ -0,0 +1,161 @@ +#![no_std] + +use core::sync::atomic::{AtomicU16, AtomicU64, Ordering}; + +use awe_appd::{AppId, AppManifest, AppState, AppSupervisor}; +use awe_initd::{RestartPolicy, ServiceId, ServiceRuntimeSpec, ServiceSpec, ServiceState, Supervisor}; + +use super::{CapabilitySet, EndUserRuntime, FramebufferInfo, InputEvent, RuntimeContext, RuntimeEvent}; +use super::{Ps2Event, WindowManager, RuntimeRect}; + +static NEXT_PROCESS_ID: AtomicU64 = AtomicU64::new(1); +static NEXT_WINDOW_ID: AtomicU16 = AtomicU16::new(1); + +fn spawn_service(entry: usize, service: ServiceId, _memory_pages: u32, _cpu_budget: u32) -> Result { + if entry == 0 || service.0 == 0 { return Err(()); } + Ok(NEXT_PROCESS_ID.fetch_add(1, Ordering::Relaxed)) +} + +fn spawn_app(app: AppId, memory_pages: u32, _capabilities: u64) -> Result { + if app.0 == 0 || memory_pages == 0 { return Err(()); } + Ok(NEXT_PROCESS_ID.fetch_add(1, Ordering::Relaxed)) +} + +fn create_window(_app: AppId) -> Result { + let id = NEXT_WINDOW_ID.fetch_add(1, Ordering::Relaxed); + if id == 0 { Err(()) } else { Ok(id) } +} + +const ALL: CapabilitySet = CapabilitySet( + CapabilitySet::PROCESS.0 + | CapabilitySet::MEMORY.0 + | CapabilitySet::IPC.0 + | CapabilitySet::DEVICE.0 + | CapabilitySet::STORAGE.0 + | CapabilitySet::NETWORK.0 + | CapabilitySet::UI.0, +); + +pub struct SystemRuntime { + pub core: EndUserRuntime, + pub windows: WindowManager, + pub services: Supervisor, + pub apps: AppSupervisor, + pub cursor_x: i32, + pub cursor_y: i32, +} + +impl SystemRuntime { + pub const fn new() -> Self { + Self { + core: EndUserRuntime::new(), + windows: WindowManager::new(), + services: Supervisor::new(spawn_service), + apps: AppSupervisor::new(spawn_app, create_window), + cursor_x: 0, + cursor_y: 0, + } + } + + pub fn attach_framebuffer(&mut self, fb: FramebufferInfo) -> Result<(), super::EndUserRuntimeError> { + self.core.attach_framebuffer(fb) + } + + pub fn register_core_services(&mut self) -> Result<(), awe_initd::RuntimeError> { + let none = [None; awe_initd::MAX_DEPENDENCIES]; + let specs = [ + ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(1), restart: RestartPolicy::Always, capability_mask: CapabilitySet::DEVICE.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: none, dependency_count: 0, entry: 1 }, + ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(2), restart: RestartPolicy::Always, capability_mask: CapabilitySet::STORAGE.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: [Some(ServiceId(1)), None, None, None, None, None, None, None], dependency_count: 1, entry: 2 }, + ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(3), restart: RestartPolicy::Always, capability_mask: CapabilitySet::NETWORK.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: [Some(ServiceId(1)), None, None, None, None, None, None, None], dependency_count: 1, entry: 3 }, + ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(4), restart: RestartPolicy::Always, capability_mask: CapabilitySet::IPC.0, memory_limit_pages: 32, cpu_budget_ticks: 5_000 }, dependencies: [Some(ServiceId(1)), None, None, None, None, None, None, None], dependency_count: 1, entry: 4 }, + ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(5), restart: RestartPolicy::Always, capability_mask: CapabilitySet::PROCESS.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: [Some(ServiceId(4)), None, None, None, None, None, None, None], dependency_count: 1, entry: 5 }, + ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(6), restart: RestartPolicy::Always, capability_mask: CapabilitySet::UI.union(CapabilitySet::IPC).0, memory_limit_pages: 128, cpu_budget_ticks: 20_000 }, dependencies: [Some(ServiceId(4)), None, None, None, None, None, None, None], dependency_count: 1, entry: 6 }, + ]; + for spec in specs { self.services.register(spec)?; } + Ok(()) + } + + pub fn start_core_services(&mut self) -> Result<(), awe_initd::RuntimeError> { + for id in 1..=6 { self.services.start(ServiceId(id))?; } + Ok(()) + } + + pub fn admit_core_apps(&mut self) -> Result<(), awe_appd::AppRuntimeError> { + let apps = [ + (1u64, CapabilitySet::UI.union(CapabilitySet::IPC).union(CapabilitySet::STORAGE)), + (2u64, CapabilitySet::UI.union(CapabilitySet::IPC).union(CapabilitySet::STORAGE)), + (3u64, CapabilitySet::UI.union(CapabilitySet::IPC).union(CapabilitySet::STORAGE)), + (4u64, CapabilitySet::UI.union(CapabilitySet::IPC)), + ]; + for (id, caps) in apps { + self.apps.admit(AppManifest { id: AppId(id), abi_major: awe_appd::AWE_APP_ABI_MAJOR, abi_minor: awe_appd::AWE_APP_ABI_MINOR, memory_limit_pages: 32, capability_mask: caps.0, dependency_count: 0, resource_count: 0 }).map_err(|_| awe_appd::AppRuntimeError::InvalidManifest)?; + } + Ok(()) + } + + pub fn start_core_apps(&mut self) -> Result<(), awe_appd::AppRuntimeError> { + for id in 1..=4 { + self.apps.start(AppId(id), ALL.0).map_err(|e| e)?; + } + Ok(()) + } + + pub fn route_ps2(&mut self, event: Ps2Event) -> Result, super::EndUserRuntimeError> { + let translated = match event { + Ps2Event::Key { code, pressed } => InputEvent::Key { code: code as u16, pressed }, + Ps2Event::Pointer { dx, dy, buttons } => { + self.cursor_x = self.cursor_x.saturating_add(dx as i32); + self.cursor_y = self.cursor_y.saturating_add(dy as i32); + InputEvent::Pointer { x: self.cursor_x, y: self.cursor_y, buttons } + } + }; + self.core.push_input(translated)?; + if self.windows.handle_input(translated).is_some() { + Ok(Some(RuntimeEvent::Input(translated))) + } else { + Ok(Some(RuntimeEvent::Input(translated))) + } + } + + pub fn create_native_window(&mut self, x: i32, y: i32, width: u32, height: u32) -> Result { + self.windows.create(RuntimeRect { x, y, width, height }) + } + + pub fn pointer_target(&mut self) -> Option { self.windows.hit_test(self.cursor_x, self.cursor_y) } + + pub fn service_state(&self, id: u16) -> Option { self.services.state(ServiceId(id)) } + pub fn app_state(&self, id: u64) -> Option { self.apps.state(AppId(id)) } +} + +impl Default for SystemRuntime { fn default() -> Self { Self::new() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn core_services_start_in_dependency_order() { + let mut runtime = SystemRuntime::new(); + runtime.register_core_services().unwrap(); + runtime.start_core_services().unwrap(); + assert_eq!(runtime.service_state(1), Some(ServiceState::Running)); + assert_eq!(runtime.service_state(6), Some(ServiceState::Running)); + } + + #[test] + fn core_apps_are_admitted_and_started_with_capabilities() { + let mut runtime = SystemRuntime::new(); + runtime.admit_core_apps().unwrap(); + runtime.start_core_apps().unwrap(); + assert_eq!(runtime.app_state(1), Some(AppState::Running)); + assert_eq!(runtime.app_state(4), Some(AppState::Running)); + } + + #[test] + fn pointer_events_reach_window_manager() { + let mut runtime = SystemRuntime::new(); + runtime.create_native_window(0, 0, 100, 100).unwrap(); + runtime.route_ps2(Ps2Event::Pointer { dx: 20, dy: 20, buttons: 1 }).unwrap(); + assert!(runtime.pointer_target().is_some()); + } +} \ No newline at end of file From 50973304c74e578737ea3bb1883e67dbccdcea78 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:17:03 +0400 Subject: [PATCH 13/65] fix(aweos): map PS2 key codes explicitly into runtime events --- kernel/src/runtime/system.rs | 129 +++++++++++------------------------ 1 file changed, 40 insertions(+), 89 deletions(-) diff --git a/kernel/src/runtime/system.rs b/kernel/src/runtime/system.rs index 9499239..f9547fe 100644 --- a/kernel/src/runtime/system.rs +++ b/kernel/src/runtime/system.rs @@ -1,12 +1,11 @@ #![no_std] use core::sync::atomic::{AtomicU16, AtomicU64, Ordering}; - use awe_appd::{AppId, AppManifest, AppState, AppSupervisor}; use awe_initd::{RestartPolicy, ServiceId, ServiceRuntimeSpec, ServiceSpec, ServiceState, Supervisor}; - -use super::{CapabilitySet, EndUserRuntime, FramebufferInfo, InputEvent, RuntimeContext, RuntimeEvent}; -use super::{Ps2Event, WindowManager, RuntimeRect}; +use crate::drivers::{KeyCode, Ps2Event}; +use super::{CapabilitySet, EndUserRuntime, FramebufferInfo, InputEvent, RuntimeEvent}; +pub use super::{RuntimeRect, WindowManager, WindowError}; static NEXT_PROCESS_ID: AtomicU64 = AtomicU64::new(1); static NEXT_WINDOW_ID: AtomicU16 = AtomicU16::new(1); @@ -15,25 +14,18 @@ fn spawn_service(entry: usize, service: ServiceId, _memory_pages: u32, _cpu_budg if entry == 0 || service.0 == 0 { return Err(()); } Ok(NEXT_PROCESS_ID.fetch_add(1, Ordering::Relaxed)) } - fn spawn_app(app: AppId, memory_pages: u32, _capabilities: u64) -> Result { if app.0 == 0 || memory_pages == 0 { return Err(()); } Ok(NEXT_PROCESS_ID.fetch_add(1, Ordering::Relaxed)) } - fn create_window(_app: AppId) -> Result { let id = NEXT_WINDOW_ID.fetch_add(1, Ordering::Relaxed); if id == 0 { Err(()) } else { Ok(id) } } const ALL: CapabilitySet = CapabilitySet( - CapabilitySet::PROCESS.0 - | CapabilitySet::MEMORY.0 - | CapabilitySet::IPC.0 - | CapabilitySet::DEVICE.0 - | CapabilitySet::STORAGE.0 - | CapabilitySet::NETWORK.0 - | CapabilitySet::UI.0, + CapabilitySet::PROCESS.0 | CapabilitySet::MEMORY.0 | CapabilitySet::IPC.0 | + CapabilitySet::DEVICE.0 | CapabilitySet::STORAGE.0 | CapabilitySet::NETWORK.0 | CapabilitySet::UI.0, ); pub struct SystemRuntime { @@ -47,39 +39,25 @@ pub struct SystemRuntime { impl SystemRuntime { pub const fn new() -> Self { - Self { - core: EndUserRuntime::new(), - windows: WindowManager::new(), - services: Supervisor::new(spawn_service), - apps: AppSupervisor::new(spawn_app, create_window), - cursor_x: 0, - cursor_y: 0, - } - } - - pub fn attach_framebuffer(&mut self, fb: FramebufferInfo) -> Result<(), super::EndUserRuntimeError> { - self.core.attach_framebuffer(fb) + Self { core: EndUserRuntime::new(), windows: WindowManager::new(), services: Supervisor::new(spawn_service), apps: AppSupervisor::new(spawn_app, create_window), cursor_x: 0, cursor_y: 0 } } - + pub fn attach_framebuffer(&mut self, fb: FramebufferInfo) -> Result<(), super::EndUserRuntimeError> { self.core.attach_framebuffer(fb) } pub fn register_core_services(&mut self) -> Result<(), awe_initd::RuntimeError> { - let none = [None; awe_initd::MAX_DEPENDENCIES]; + let n = [None; awe_initd::MAX_DEPENDENCIES]; + let d1 = [Some(ServiceId(1)), None, None, None, None, None, None, None]; + let d4 = [Some(ServiceId(4)), None, None, None, None, None, None, None]; let specs = [ - ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(1), restart: RestartPolicy::Always, capability_mask: CapabilitySet::DEVICE.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: none, dependency_count: 0, entry: 1 }, - ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(2), restart: RestartPolicy::Always, capability_mask: CapabilitySet::STORAGE.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: [Some(ServiceId(1)), None, None, None, None, None, None, None], dependency_count: 1, entry: 2 }, - ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(3), restart: RestartPolicy::Always, capability_mask: CapabilitySet::NETWORK.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: [Some(ServiceId(1)), None, None, None, None, None, None, None], dependency_count: 1, entry: 3 }, - ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(4), restart: RestartPolicy::Always, capability_mask: CapabilitySet::IPC.0, memory_limit_pages: 32, cpu_budget_ticks: 5_000 }, dependencies: [Some(ServiceId(1)), None, None, None, None, None, None, None], dependency_count: 1, entry: 4 }, - ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(5), restart: RestartPolicy::Always, capability_mask: CapabilitySet::PROCESS.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: [Some(ServiceId(4)), None, None, None, None, None, None, None], dependency_count: 1, entry: 5 }, - ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(6), restart: RestartPolicy::Always, capability_mask: CapabilitySet::UI.union(CapabilitySet::IPC).0, memory_limit_pages: 128, cpu_budget_ticks: 20_000 }, dependencies: [Some(ServiceId(4)), None, None, None, None, None, None, None], dependency_count: 1, entry: 6 }, + ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(1), restart: RestartPolicy::Always, capability_mask: CapabilitySet::DEVICE.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: n, dependency_count: 0, entry: 1 }, + ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(2), restart: RestartPolicy::Always, capability_mask: CapabilitySet::STORAGE.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: d1, dependency_count: 1, entry: 2 }, + ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(3), restart: RestartPolicy::Always, capability_mask: CapabilitySet::NETWORK.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: d1, dependency_count: 1, entry: 3 }, + ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(4), restart: RestartPolicy::Always, capability_mask: CapabilitySet::IPC.0, memory_limit_pages: 32, cpu_budget_ticks: 5_000 }, dependencies: d1, dependency_count: 1, entry: 4 }, + ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(5), restart: RestartPolicy::Always, capability_mask: CapabilitySet::PROCESS.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: d4, dependency_count: 1, entry: 5 }, + ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(6), restart: RestartPolicy::Always, capability_mask: CapabilitySet::UI.union(CapabilitySet::IPC).0, memory_limit_pages: 128, cpu_budget_ticks: 20_000 }, dependencies: d4, dependency_count: 1, entry: 6 }, ]; for spec in specs { self.services.register(spec)?; } Ok(()) } - - pub fn start_core_services(&mut self) -> Result<(), awe_initd::RuntimeError> { - for id in 1..=6 { self.services.start(ServiceId(id))?; } - Ok(()) - } - + pub fn start_core_services(&mut self) -> Result<(), awe_initd::RuntimeError> { for id in 1..=6 { self.services.start(ServiceId(id))?; } Ok(()) } pub fn admit_core_apps(&mut self) -> Result<(), awe_appd::AppRuntimeError> { let apps = [ (1u64, CapabilitySet::UI.union(CapabilitySet::IPC).union(CapabilitySet::STORAGE)), @@ -88,21 +66,15 @@ impl SystemRuntime { (4u64, CapabilitySet::UI.union(CapabilitySet::IPC)), ]; for (id, caps) in apps { - self.apps.admit(AppManifest { id: AppId(id), abi_major: awe_appd::AWE_APP_ABI_MAJOR, abi_minor: awe_appd::AWE_APP_ABI_MINOR, memory_limit_pages: 32, capability_mask: caps.0, dependency_count: 0, resource_count: 0 }).map_err(|_| awe_appd::AppRuntimeError::InvalidManifest)?; - } - Ok(()) - } - - pub fn start_core_apps(&mut self) -> Result<(), awe_appd::AppRuntimeError> { - for id in 1..=4 { - self.apps.start(AppId(id), ALL.0).map_err(|e| e)?; + let manifest = AppManifest { id: AppId(id), abi_major: awe_appd::AWE_APP_ABI_MAJOR, abi_minor: awe_appd::AWE_APP_ABI_MINOR, memory_limit_pages: 32, capability_mask: caps.0, dependency_count: 0, resource_count: 0 }; + self.apps.admit(manifest).map_err(|_| awe_appd::AppRuntimeError::InvalidManifest)?; } Ok(()) } - - pub fn route_ps2(&mut self, event: Ps2Event) -> Result, super::EndUserRuntimeError> { + pub fn start_core_apps(&mut self) -> Result<(), awe_appd::AppRuntimeError> { for id in 1..=4 { self.apps.start(AppId(id), ALL.0)?; } Ok(()) } + pub fn route_ps2(&mut self, event: Ps2Event) -> Result { let translated = match event { - Ps2Event::Key { code, pressed } => InputEvent::Key { code: code as u16, pressed }, + Ps2Event::Key { code, pressed } => InputEvent::Key { code: key_code_value(code), pressed }, Ps2Event::Pointer { dx, dy, buttons } => { self.cursor_x = self.cursor_x.saturating_add(dx as i32); self.cursor_y = self.cursor_y.saturating_add(dy as i32); @@ -110,52 +82,31 @@ impl SystemRuntime { } }; self.core.push_input(translated)?; - if self.windows.handle_input(translated).is_some() { - Ok(Some(RuntimeEvent::Input(translated))) - } else { - Ok(Some(RuntimeEvent::Input(translated))) - } - } - - pub fn create_native_window(&mut self, x: i32, y: i32, width: u32, height: u32) -> Result { - self.windows.create(RuntimeRect { x, y, width, height }) + self.windows.handle_input(translated); + Ok(RuntimeEvent::Input(translated)) } - - pub fn pointer_target(&mut self) -> Option { self.windows.hit_test(self.cursor_x, self.cursor_y) } - + pub fn create_native_window(&mut self, rect: RuntimeRect) -> Result { self.windows.create(rect) } pub fn service_state(&self, id: u16) -> Option { self.services.state(ServiceId(id)) } pub fn app_state(&self, id: u64) -> Option { self.apps.state(AppId(id)) } } +fn key_code_value(code: KeyCode) -> u16 { + match code { + KeyCode::Escape => 0x01, KeyCode::Enter => 0x1C, KeyCode::Backspace => 0x0E, + KeyCode::Tab => 0x0D, KeyCode::Space => 0x39, KeyCode::Left => 0x4B, + KeyCode::Right => 0x4D, KeyCode::Up => 0x48, KeyCode::Down => 0x50, + KeyCode::Character(v) | KeyCode::Unknown(v) => v as u16, + } +} + impl Default for SystemRuntime { fn default() -> Self { Self::new() } } #[cfg(test)] mod tests { use super::*; - - #[test] - fn core_services_start_in_dependency_order() { - let mut runtime = SystemRuntime::new(); - runtime.register_core_services().unwrap(); - runtime.start_core_services().unwrap(); - assert_eq!(runtime.service_state(1), Some(ServiceState::Running)); - assert_eq!(runtime.service_state(6), Some(ServiceState::Running)); - } - - #[test] - fn core_apps_are_admitted_and_started_with_capabilities() { - let mut runtime = SystemRuntime::new(); - runtime.admit_core_apps().unwrap(); - runtime.start_core_apps().unwrap(); - assert_eq!(runtime.app_state(1), Some(AppState::Running)); - assert_eq!(runtime.app_state(4), Some(AppState::Running)); - } - - #[test] - fn pointer_events_reach_window_manager() { - let mut runtime = SystemRuntime::new(); - runtime.create_native_window(0, 0, 100, 100).unwrap(); - runtime.route_ps2(Ps2Event::Pointer { dx: 20, dy: 20, buttons: 1 }).unwrap(); - assert!(runtime.pointer_target().is_some()); - } -} \ No newline at end of file + #[test] fn core_services_start_in_dependency_order() { let mut r = SystemRuntime::new(); r.register_core_services().unwrap(); r.start_core_services().unwrap(); assert_eq!(r.service_state(6), Some(ServiceState::Running)); } + #[test] fn core_apps_admit_and_start() { let mut r = SystemRuntime::new(); r.admit_core_apps().unwrap(); r.start_core_apps().unwrap(); assert_eq!(r.app_state(1), Some(AppState::Running)); } + #[test] fn ps2_input_reaches_window_manager() { let mut r = SystemRuntime::new(); r.create_native_window(RuntimeRect { x: 0, y: 0, width: 100, height: 100 }).unwrap(); r.route_ps2(Ps2Event::Pointer { dx: 20, dy: 20, buttons: 1 }).unwrap(); assert!(r.pointer_target().is_some()); } + #[test] fn capability_set_is_used_for_app_admission() { let mut r = SystemRuntime::new(); r.admit_core_apps().unwrap(); assert_eq!(r.apps.start(AppId(1), CapabilitySet::UI.0), Err(awe_appd::AppRuntimeError::CapabilityDenied)); } + #[test] fn runtime_context_requires_capability() { assert!(RuntimeContext::new(CapabilitySet::UI).require(CapabilitySet::UI).is_ok()); } +} From 4420b29131c1eecb48b4355494b211941d05f5fd Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:17:09 +0400 Subject: [PATCH 14/65] feat(aweos): expose integrated system runtime coordinator --- kernel/src/runtime/mod.rs | 35 ++++++++--------------------------- 1 file changed, 8 insertions(+), 27 deletions(-) diff --git a/kernel/src/runtime/mod.rs b/kernel/src/runtime/mod.rs index 279445c..5aa746a 100644 --- a/kernel/src/runtime/mod.rs +++ b/kernel/src/runtime/mod.rs @@ -6,16 +6,17 @@ mod end_user; pub mod graphics; +pub mod system; pub use end_user::{ AppRecord, AppState, EndUserRuntime, EndUserRuntimeError, FramebufferInfo, InputEvent, RuntimeEvent, ServiceRecord, ServiceState, }; pub use graphics::{DoubleBuffer, Rect as RuntimeRect, Window, WindowError, WindowManager}; +pub use system::SystemRuntime; #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct CapabilitySet(pub u64); - impl CapabilitySet { pub const NONE: Self = Self(0); pub const PROCESS: Self = Self(1 << 0); @@ -25,38 +26,18 @@ impl CapabilitySet { pub const STORAGE: Self = Self(1 << 4); pub const NETWORK: Self = Self(1 << 5); pub const UI: Self = Self(1 << 6); - - pub const fn contains(self, required: Self) -> bool { - (self.0 & required.0) == required.0 - } - - pub const fn union(self, other: Self) -> Self { - Self(self.0 | other.0) - } + pub const fn contains(self, required: Self) -> bool { (self.0 & required.0) == required.0 } + pub const fn union(self, other: Self) -> Self { Self(self.0 | other.0) } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum RuntimeError { - CapabilityDenied, - InvalidHandle, - ResourceExhausted, -} +pub enum RuntimeError { CapabilityDenied, InvalidHandle, ResourceExhausted } #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct RuntimeContext { - pub capabilities: CapabilitySet, -} - +pub struct RuntimeContext { pub capabilities: CapabilitySet } impl RuntimeContext { - pub const fn new(capabilities: CapabilitySet) -> Self { - Self { capabilities } - } - + pub const fn new(capabilities: CapabilitySet) -> Self { Self { capabilities } } pub const fn require(self, required: CapabilitySet) -> Result<(), RuntimeError> { - if self.capabilities.contains(required) { - Ok(()) - } else { - Err(RuntimeError::CapabilityDenied) - } + if self.capabilities.contains(required) { Ok(()) } else { Err(RuntimeError::CapabilityDenied) } } } From 437372a5f60e0a215ed63959ecb02aed75dfa3a4 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:17:21 +0400 Subject: [PATCH 15/65] fix(aweos): expose runtime pointer target for UI integration tests --- kernel/src/runtime/system.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kernel/src/runtime/system.rs b/kernel/src/runtime/system.rs index f9547fe..c1dc91d 100644 --- a/kernel/src/runtime/system.rs +++ b/kernel/src/runtime/system.rs @@ -86,6 +86,7 @@ impl SystemRuntime { Ok(RuntimeEvent::Input(translated)) } pub fn create_native_window(&mut self, rect: RuntimeRect) -> Result { self.windows.create(rect) } + pub fn pointer_target(&mut self) -> Option { self.windows.hit_test(self.cursor_x, self.cursor_y) } pub fn service_state(&self, id: u16) -> Option { self.services.state(ServiceId(id)) } pub fn app_state(&self, id: u64) -> Option { self.apps.state(AppId(id)) } } @@ -108,5 +109,5 @@ mod tests { #[test] fn core_apps_admit_and_start() { let mut r = SystemRuntime::new(); r.admit_core_apps().unwrap(); r.start_core_apps().unwrap(); assert_eq!(r.app_state(1), Some(AppState::Running)); } #[test] fn ps2_input_reaches_window_manager() { let mut r = SystemRuntime::new(); r.create_native_window(RuntimeRect { x: 0, y: 0, width: 100, height: 100 }).unwrap(); r.route_ps2(Ps2Event::Pointer { dx: 20, dy: 20, buttons: 1 }).unwrap(); assert!(r.pointer_target().is_some()); } #[test] fn capability_set_is_used_for_app_admission() { let mut r = SystemRuntime::new(); r.admit_core_apps().unwrap(); assert_eq!(r.apps.start(AppId(1), CapabilitySet::UI.0), Err(awe_appd::AppRuntimeError::CapabilityDenied)); } - #[test] fn runtime_context_requires_capability() { assert!(RuntimeContext::new(CapabilitySet::UI).require(CapabilitySet::UI).is_ok()); } + #[test] fn runtime_context_requires_capability() { assert!(super::super::RuntimeContext::new(CapabilitySet::UI).require(CapabilitySet::UI).is_ok()); } } From abb49ec69fff0c58786a280788c5dff1af50c6c1 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:17:39 +0400 Subject: [PATCH 16/65] feat(aweos): add bounded Ethernet IPv4 UDP packet engine --- services/netd/src/packet.rs | 100 ++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 services/netd/src/packet.rs diff --git a/services/netd/src/packet.rs b/services/netd/src/packet.rs new file mode 100644 index 0000000..16c2ece --- /dev/null +++ b/services/netd/src/packet.rs @@ -0,0 +1,100 @@ +#![no_std] + +pub const ETH_HEADER: usize = 14; +pub const IPV4_MIN_HEADER: usize = 20; +pub const UDP_HEADER: usize = 8; +pub const MAX_PACKET: usize = 1514; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum PacketError { TooShort, InvalidEthernet, InvalidIpv4, InvalidChecksum, InvalidLength, UnsupportedProtocol, OutputTooSmall } + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct EthernetFrame { pub destination: [u8; 6], pub source: [u8; 6], pub ethertype: u16 } +impl EthernetFrame { + pub fn parse(bytes: &[u8]) -> Result { + if bytes.len() < ETH_HEADER { return Err(PacketError::TooShort); } + Ok(Self { destination: bytes[0..6].try_into().map_err(|_| PacketError::InvalidEthernet)?, source: bytes[6..12].try_into().map_err(|_| PacketError::InvalidEthernet)?, ethertype: u16::from_be_bytes([bytes[12], bytes[13]]) }) + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct Ipv4Header { pub source: [u8;4], pub destination: [u8;4], pub total_length: u16, pub protocol: u8, pub header_len: usize } +impl Ipv4Header { + pub fn parse(bytes: &[u8]) -> Result { + if bytes.len() < IPV4_MIN_HEADER { return Err(PacketError::TooShort); } + if bytes[0] >> 4 != 4 { return Err(PacketError::InvalidIpv4); } + let header_len = ((bytes[0] & 0x0f) as usize) * 4; + if header_len < IPV4_MIN_HEADER || header_len > bytes.len() { return Err(PacketError::InvalidIpv4); } + let total_length = u16::from_be_bytes([bytes[2], bytes[3]]); + if total_length as usize > bytes.len() || total_length as usize < header_len { return Err(PacketError::InvalidLength); } + let mut header = [0u8; IPV4_MIN_HEADER]; + header.copy_from_slice(&bytes[..IPV4_MIN_HEADER]); + header[10] = 0; header[11] = 0; + if checksum16(&bytes[..header_len]) != u16::from_be_bytes([bytes[10], bytes[11]]) { return Err(PacketError::InvalidChecksum); } + Ok(Self { source: bytes[12..16].try_into().unwrap_or([0;4]), destination: bytes[16..20].try_into().unwrap_or([0;4]), total_length, protocol: bytes[9], header_len }) + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct UdpHeader { pub source_port: u16, pub destination_port: u16, pub length: u16 } +impl UdpHeader { + pub fn parse(bytes: &[u8]) -> Result { + if bytes.len() < UDP_HEADER { return Err(PacketError::TooShort); } + let length = u16::from_be_bytes([bytes[4], bytes[5]]); + if length < UDP_HEADER as u16 || length as usize > bytes.len() { return Err(PacketError::InvalidLength); } + Ok(Self { source_port: u16::from_be_bytes([bytes[0], bytes[1]]), destination_port: u16::from_be_bytes([bytes[2], bytes[3]]), length }) + } +} + +pub fn checksum16(bytes: &[u8]) -> u16 { + let mut sum: u32 = 0; + let mut i = 0; + while i + 1 < bytes.len() { + sum = sum.wrapping_add(u16::from_be_bytes([bytes[i], bytes[i + 1]]) as u32); + i += 2; + } + if i < bytes.len() { sum = sum.wrapping_add((bytes[i] as u32) << 8); } + while (sum >> 16) != 0 { sum = (sum & 0xffff) + (sum >> 16); } + !(sum as u16) +} + +pub fn build_udp_ipv4( + out: &mut [u8], + source_mac: [u8; 6], + destination_mac: [u8; 6], + source_ip: [u8; 4], + destination_ip: [u8; 4], + source_port: u16, + destination_port: u16, + payload: &[u8], +) -> Result { + let total = ETH_HEADER.checked_add(IPV4_MIN_HEADER).and_then(|v| v.checked_add(UDP_HEADER)).and_then(|v| v.checked_add(payload.len())).ok_or(PacketError::InvalidLength)?; + if total > MAX_PACKET || out.len() < total { return Err(PacketError::OutputTooSmall); } + out[0..6].copy_from_slice(&destination_mac); out[6..12].copy_from_slice(&source_mac); out[12..14].copy_from_slice(&0x0800u16.to_be_bytes()); + let ip = ETH_HEADER; out[ip] = 0x45; out[ip+1] = 0; (out[ip+2..ip+4]).copy_from_slice(&(total - ETH_HEADER as usize).try_into().map_err(|_| PacketError::InvalidLength)?.to_be_bytes()); + out[ip+4..ip+6].copy_from_slice(&0u16.to_be_bytes()); out[ip+6..ip+8].copy_from_slice(&0x4000u16.to_be_bytes()); out[ip+8] = 64; out[ip+9] = 17; out[ip+10..ip+12].fill(0); + out[ip+12..ip+16].copy_from_slice(&source_ip); out[ip+16..ip+20].copy_from_slice(&destination_ip); let c = checksum16(&out[ip..ip+20]); out[ip+10..ip+12].copy_from_slice(&c.to_be_bytes()); + let udp = ip + IPV4_MIN_HEADER; out[udp..udp+2].copy_from_slice(&source_port.to_be_bytes()); out[udp+2..udp+4].copy_from_slice(&destination_port.to_be_bytes()); + let udp_len = (UDP_HEADER + payload.len()) as u16; out[udp+4..udp+6].copy_from_slice(&udp_len.to_be_bytes()); out[udp+6..udp+8].fill(0); out[udp+8..udp+8+payload.len()].copy_from_slice(payload); + Ok(total) +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] fn udp_ipv4_packet_build_and_parse() { + let mut packet = [0u8; MAX_PACKET]; + let n = build_udp_ipv4(&mut packet, [1,2,3,4,5,6], [6,5,4,3,2,1], [10,0,0,1], [10,0,0,2], 1000, 2000, b"hello").unwrap(); + assert_eq!(EthernetFrame::parse(&packet[..n]).unwrap().ethertype, 0x0800); + let ip = Ipv4Header::parse(&packet[ETH_HEADER..n]).unwrap(); + assert_eq!(ip.protocol, 17); assert_eq!(ip.source, [10,0,0,1]); + let udp = UdpHeader::parse(&packet[ETH_HEADER + ip.header_len..n]).unwrap(); + assert_eq!(udp.destination_port, 2000); assert_eq!(udp.length, 13); + } + #[test] fn rejects_bad_ipv4_checksum() { + let mut packet = [0u8; MAX_PACKET]; + let n = build_udp_ipv4(&mut packet, [1;6], [2;6], [10,0,0,1], [10,0,0,2], 1, 2, b"x").unwrap(); + packet[ETH_HEADER + 8] ^= 1; + assert_eq!(Ipv4Header::parse(&packet[ETH_HEADER..n]), Err(PacketError::InvalidChecksum)); + } +} \ No newline at end of file From eb056aa423ab333fda9f2399e88fbe5763844460 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:17:52 +0400 Subject: [PATCH 17/65] fix(aweos): validate IPv4 checksum over zeroed checksum field --- services/netd/src/packet.rs | 40 ++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/services/netd/src/packet.rs b/services/netd/src/packet.rs index 16c2ece..99fe098 100644 --- a/services/netd/src/packet.rs +++ b/services/netd/src/packet.rs @@ -27,11 +27,12 @@ impl Ipv4Header { if header_len < IPV4_MIN_HEADER || header_len > bytes.len() { return Err(PacketError::InvalidIpv4); } let total_length = u16::from_be_bytes([bytes[2], bytes[3]]); if total_length as usize > bytes.len() || total_length as usize < header_len { return Err(PacketError::InvalidLength); } - let mut header = [0u8; IPV4_MIN_HEADER]; - header.copy_from_slice(&bytes[..IPV4_MIN_HEADER]); + let expected = u16::from_be_bytes([bytes[10], bytes[11]]); + let mut header = [0u8; 60]; + header[..header_len].copy_from_slice(&bytes[..header_len]); header[10] = 0; header[11] = 0; - if checksum16(&bytes[..header_len]) != u16::from_be_bytes([bytes[10], bytes[11]]) { return Err(PacketError::InvalidChecksum); } - Ok(Self { source: bytes[12..16].try_into().unwrap_or([0;4]), destination: bytes[16..20].try_into().unwrap_or([0;4]), total_length, protocol: bytes[9], header_len }) + if checksum16(&header[..header_len]) != expected { return Err(PacketError::InvalidChecksum); } + Ok(Self { source: bytes[12..16].try_into().map_err(|_| PacketError::InvalidIpv4)?, destination: bytes[16..20].try_into().map_err(|_| PacketError::InvalidIpv4)?, total_length, protocol: bytes[9], header_len }) } } @@ -49,33 +50,26 @@ impl UdpHeader { pub fn checksum16(bytes: &[u8]) -> u16 { let mut sum: u32 = 0; let mut i = 0; - while i + 1 < bytes.len() { - sum = sum.wrapping_add(u16::from_be_bytes([bytes[i], bytes[i + 1]]) as u32); - i += 2; - } + while i + 1 < bytes.len() { sum = sum.wrapping_add(u16::from_be_bytes([bytes[i], bytes[i + 1]]) as u32); i += 2; } if i < bytes.len() { sum = sum.wrapping_add((bytes[i] as u32) << 8); } while (sum >> 16) != 0 { sum = (sum & 0xffff) + (sum >> 16); } !(sum as u16) } -pub fn build_udp_ipv4( - out: &mut [u8], - source_mac: [u8; 6], - destination_mac: [u8; 6], - source_ip: [u8; 4], - destination_ip: [u8; 4], - source_port: u16, - destination_port: u16, - payload: &[u8], -) -> Result { +pub fn build_udp_ipv4(out: &mut [u8], source_mac: [u8; 6], destination_mac: [u8; 6], source_ip: [u8; 4], destination_ip: [u8; 4], source_port: u16, destination_port: u16, payload: &[u8]) -> Result { let total = ETH_HEADER.checked_add(IPV4_MIN_HEADER).and_then(|v| v.checked_add(UDP_HEADER)).and_then(|v| v.checked_add(payload.len())).ok_or(PacketError::InvalidLength)?; if total > MAX_PACKET || out.len() < total { return Err(PacketError::OutputTooSmall); } out[0..6].copy_from_slice(&destination_mac); out[6..12].copy_from_slice(&source_mac); out[12..14].copy_from_slice(&0x0800u16.to_be_bytes()); - let ip = ETH_HEADER; out[ip] = 0x45; out[ip+1] = 0; (out[ip+2..ip+4]).copy_from_slice(&(total - ETH_HEADER as usize).try_into().map_err(|_| PacketError::InvalidLength)?.to_be_bytes()); - out[ip+4..ip+6].copy_from_slice(&0u16.to_be_bytes()); out[ip+6..ip+8].copy_from_slice(&0x4000u16.to_be_bytes()); out[ip+8] = 64; out[ip+9] = 17; out[ip+10..ip+12].fill(0); - out[ip+12..ip+16].copy_from_slice(&source_ip); out[ip+16..ip+20].copy_from_slice(&destination_ip); let c = checksum16(&out[ip..ip+20]); out[ip+10..ip+12].copy_from_slice(&c.to_be_bytes()); - let udp = ip + IPV4_MIN_HEADER; out[udp..udp+2].copy_from_slice(&source_port.to_be_bytes()); out[udp+2..udp+4].copy_from_slice(&destination_port.to_be_bytes()); - let udp_len = (UDP_HEADER + payload.len()) as u16; out[udp+4..udp+6].copy_from_slice(&udp_len.to_be_bytes()); out[udp+6..udp+8].fill(0); out[udp+8..udp+8+payload.len()].copy_from_slice(payload); + let ip = ETH_HEADER; + out[ip] = 0x45; out[ip+1] = 0; + out[ip+2..ip+4].copy_from_slice(&((total - ETH_HEADER) as u16).to_be_bytes()); + out[ip+4..ip+6].fill(0); out[ip+6..ip+8].copy_from_slice(&0x4000u16.to_be_bytes()); out[ip+8] = 64; out[ip+9] = 17; out[ip+10..ip+12].fill(0); + out[ip+12..ip+16].copy_from_slice(&source_ip); out[ip+16..ip+20].copy_from_slice(&destination_ip); + let c = checksum16(&out[ip..ip+20]); out[ip+10..ip+12].copy_from_slice(&c.to_be_bytes()); + let udp = ip + IPV4_MIN_HEADER; + out[udp..udp+2].copy_from_slice(&source_port.to_be_bytes()); out[udp+2..udp+4].copy_from_slice(&destination_port.to_be_bytes()); + let udp_len = u16::try_from(UDP_HEADER + payload.len()).map_err(|_| PacketError::InvalidLength)?; + out[udp+4..udp+6].copy_from_slice(&udp_len.to_be_bytes()); out[udp+6..udp+8].fill(0); out[udp+8..udp+8+payload.len()].copy_from_slice(payload); Ok(total) } From 23d7ad71aea4dec99231126c6662081d3b7a2097 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:18:02 +0400 Subject: [PATCH 18/65] feat(aweos): integrate bounded Ethernet IPv4 UDP engine into netd --- services/netd/src/lib.rs | 182 ++++++++++----------------------------- 1 file changed, 46 insertions(+), 136 deletions(-) diff --git a/services/netd/src/lib.rs b/services/netd/src/lib.rs index 4a2a738..fd55f76 100644 --- a/services/netd/src/lib.rs +++ b/services/netd/src/lib.rs @@ -1,10 +1,9 @@ -//! AWEOS User-Space Network Service (`netd`) -//! -//! Manages network interfaces, socket tables, ARP cache, IPv4/UDP/TCP routing, -//! and firewall policy enforcement in user-space. - +//! AWEOS User-Space Network Service (`netd`). #![no_std] +pub mod packet; +pub use packet::{build_udp_ipv4, checksum16, EthernetFrame, Ipv4Header, PacketError, UdpHeader, ETH_HEADER, IPV4_MIN_HEADER, MAX_PACKET, UDP_HEADER}; + pub const MAX_INTERFACES: usize = 4; pub const MAX_SOCKETS: usize = 32; pub const MAX_FIREWALL_RULES: usize = 16; @@ -12,32 +11,18 @@ pub const MAX_PACKET_LEN: usize = 1514; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct MacAddress(pub [u8; 6]); - #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Ipv4Address(pub [u8; 4]); - impl Ipv4Address { pub const UNSPECIFIED: Self = Self([0, 0, 0, 0]); pub const LOOPBACK: Self = Self([127, 0, 0, 1]); - - pub const fn new(a: u8, b: u8, c: u8, d: u8) -> Self { - Self([a, b, c, d]) - } + pub const fn new(a: u8, b: u8, c: u8, d: u8) -> Self { Self([a, b, c, d]) } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum SocketProtocol { - Udp, - Tcp, -} - +pub enum SocketProtocol { Udp, Tcp } #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum SocketState { - Closed, - Bound, - Listening, - Connected, -} +pub enum SocketState { Closed, Bound, Listening, Connected } #[derive(Debug, Clone, Copy)] pub struct NetworkSocket { @@ -50,38 +35,18 @@ pub struct NetworkSocket { pub state: SocketState, pub owner_pid: u32, } - impl NetworkSocket { pub const fn new(socket_id: u32, protocol: SocketProtocol, owner_pid: u32) -> Self { - Self { - socket_id, - protocol, - local_port: 0, - remote_port: 0, - local_ip: Ipv4Address::UNSPECIFIED, - remote_ip: Ipv4Address::UNSPECIFIED, - state: SocketState::Closed, - owner_pid, - } + Self { socket_id, protocol, local_port: 0, remote_port: 0, local_ip: Ipv4Address::UNSPECIFIED, remote_ip: Ipv4Address::UNSPECIFIED, state: SocketState::Closed, owner_pid } } - pub fn bind(&mut self, ip: Ipv4Address, port: u16) -> Result<(), &'static str> { - if self.state != SocketState::Closed { - return Err("Socket already bound or active"); - } - self.local_ip = ip; - self.local_port = port; - self.state = SocketState::Bound; - Ok(()) + if self.state != SocketState::Closed { return Err("Socket already bound or active"); } + self.local_ip = ip; self.local_port = port; self.state = SocketState::Bound; Ok(()) } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum FirewallAction { - Allow, - Deny, -} - +pub enum FirewallAction { Allow, Deny } #[derive(Debug, Clone, Copy)] pub struct FirewallRule { pub rule_id: u32, @@ -90,19 +55,13 @@ pub struct FirewallRule { pub port_range_end: u16, pub action: FirewallAction, } - impl FirewallRule { pub fn matches(&self, protocol: SocketProtocol, port: u16) -> bool { - if let Some(p) = self.protocol - && p != protocol - { - return false; - } + if let Some(p) = self.protocol && p != protocol { return false; } port >= self.port_range_start && port <= self.port_range_end } } -/// Network Daemon Manager Instance. #[derive(Debug)] pub struct NetworkDaemon { interfaces: [Option; MAX_INTERFACES], @@ -111,109 +70,60 @@ pub struct NetworkDaemon { socket_counter: u32, rule_counter: u32, } - impl NetworkDaemon { pub const fn new() -> Self { - Self { - interfaces: [None; MAX_INTERFACES], - sockets: [None; MAX_SOCKETS], - firewall_rules: [None; MAX_FIREWALL_RULES], - socket_counter: 1, - rule_counter: 1, - } + Self { interfaces: [None; MAX_INTERFACES], sockets: [None; MAX_SOCKETS], firewall_rules: [None; MAX_FIREWALL_RULES], socket_counter: 1, rule_counter: 1 } } - pub fn add_interface(&mut self, mac: MacAddress) -> Result { - for (idx, slot) in self.interfaces.iter_mut().enumerate() { - if slot.is_none() { - *slot = Some(mac); - return Ok(idx); - } - } + for (idx, slot) in self.interfaces.iter_mut().enumerate() { if slot.is_none() { *slot = Some(mac); return Ok(idx); } } Err("No free interface slots") } - - pub fn create_socket( - &mut self, - protocol: SocketProtocol, - owner_pid: u32, - ) -> Result { + pub fn create_socket(&mut self, protocol: SocketProtocol, owner_pid: u32) -> Result { let sid = self.socket_counter; - for slot in self.sockets.iter_mut() { - if slot.is_none() { - *slot = Some(NetworkSocket::new(sid, protocol, owner_pid)); - self.socket_counter += 1; - return Ok(sid); - } - } + for slot in self.sockets.iter_mut() { if slot.is_none() { *slot = Some(NetworkSocket::new(sid, protocol, owner_pid)); self.socket_counter = self.socket_counter.saturating_add(1); return Ok(sid); } } Err("Socket table full") } - - pub fn add_firewall_rule( - &mut self, - protocol: Option, - port_start: u16, - port_end: u16, - action: FirewallAction, - ) -> Result { + pub fn add_firewall_rule(&mut self, protocol: Option, port_start: u16, port_end: u16, action: FirewallAction) -> Result { + if port_start > port_end { return Err("Invalid port range"); } let rid = self.rule_counter; - for slot in self.firewall_rules.iter_mut() { - if slot.is_none() { - *slot = Some(FirewallRule { - rule_id: rid, - protocol, - port_range_start: port_start, - port_range_end: port_end, - action, - }); - self.rule_counter += 1; - return Ok(rid); - } - } + for slot in self.firewall_rules.iter_mut() { if slot.is_none() { *slot = Some(FirewallRule { rule_id: rid, protocol, port_range_start: port_start, port_range_end: port_end, action }); self.rule_counter = self.rule_counter.saturating_add(1); return Ok(rid); } } Err("Firewall rule capacity reached") } - pub fn evaluate_packet(&self, protocol: SocketProtocol, dst_port: u16) -> FirewallAction { - for rule in self.firewall_rules.iter().flatten() { - if rule.matches(protocol, dst_port) { - return rule.action; - } - } - FirewallAction::Deny // Fail-closed by default + for rule in self.firewall_rules.iter().flatten() { if rule.matches(protocol, dst_port) { return rule.action; } } + FirewallAction::Deny } -} - -impl Default for NetworkDaemon { - fn default() -> Self { - Self::new() + pub fn validate_ipv4_udp(&self, frame: &[u8], expected_destination: Option) -> Result<(Ipv4Header, UdpHeader), PacketError> { + let eth = EthernetFrame::parse(frame)?; + if eth.ethertype != 0x0800 { return Err(PacketError::UnsupportedProtocol); } + let ip = Ipv4Header::parse(&frame[ETH_HEADER..])?; + if ip.protocol != 17 { return Err(PacketError::UnsupportedProtocol); } + let udp_start = ETH_HEADER + ip.header_len; + let udp = UdpHeader::parse(&frame[udp_start..])?; + if let Some(port) = expected_destination && udp.destination_port != port { return Err(PacketError::InvalidLength); } + if self.evaluate_packet(SocketProtocol::Udp, udp.destination_port) != FirewallAction::Allow { return Err(PacketError::UnsupportedProtocol); } + Ok((ip, udp)) } } +impl Default for NetworkDaemon { fn default() -> Self { Self::new() } } #[cfg(test)] mod tests { use super::*; - #[test] - fn test_netd_socket_and_firewall() { + fn socket_firewall_and_packet_engine_work() { let mut netd = NetworkDaemon::new(); - netd.add_interface(MacAddress([0x02, 0x00, 0x00, 0x00, 0x00, 0x01])) - .unwrap(); - - let sock_id = netd.create_socket(SocketProtocol::Udp, 100).unwrap(); - assert_eq!(sock_id, 1); - - // Deny by default - assert_eq!( - netd.evaluate_packet(SocketProtocol::Udp, 80), - FirewallAction::Deny - ); - - // Add rule to allow HTTP (port 80) - netd.add_firewall_rule(Some(SocketProtocol::Udp), 80, 80, FirewallAction::Allow) - .unwrap(); - assert_eq!( - netd.evaluate_packet(SocketProtocol::Udp, 80), - FirewallAction::Allow - ); + netd.add_interface(MacAddress([0x02,0,0,0,0,1])).unwrap(); + assert_eq!(netd.create_socket(SocketProtocol::Udp, 100).unwrap(), 1); + netd.add_firewall_rule(Some(SocketProtocol::Udp), 2000, 2000, FirewallAction::Allow).unwrap(); + let mut frame = [0u8; MAX_PACKET_LEN]; + let n = build_udp_ipv4(&mut frame, [1;6], [2;6], [10,0,0,1], [10,0,0,2], 1000, 2000, b"hello").unwrap(); + let (_, udp) = netd.validate_ipv4_udp(&frame[..n], Some(2000)).unwrap(); + assert_eq!(udp.source_port, 1000); assert_eq!(udp.destination_port, 2000); } -} + #[test] + fn invalid_port_range_is_rejected() { + let mut netd = NetworkDaemon::new(); + assert!(netd.add_firewall_rule(None, 200, 100, FirewallAction::Allow).is_err()); + } +} \ No newline at end of file From e95e1a379670faec5d97f6c9aaab74f204247b52 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:18:15 +0400 Subject: [PATCH 19/65] feat(aweos): add bounded block-backed persistent file store --- kernel/src/storage/file_store.rs | 145 +++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 kernel/src/storage/file_store.rs diff --git a/kernel/src/storage/file_store.rs b/kernel/src/storage/file_store.rs new file mode 100644 index 0000000..7d1c676 --- /dev/null +++ b/kernel/src/storage/file_store.rs @@ -0,0 +1,145 @@ +#![no_std] + +use super::{BlockDevice, StorageError, BLOCK_SIZE}; + +pub const MAX_FILES: usize = 64; +pub const MAX_FILE_BLOCKS: usize = 32; +pub const MAX_FILE_SIZE: usize = MAX_FILE_BLOCKS * BLOCK_SIZE; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum FileStoreError { Full, NotFound, InvalidName, InvalidOffset, TooLarge, NoSpace, Storage(StorageError) } +impl From for FileStoreError { fn from(v: StorageError) -> Self { Self::Storage(v) } } + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct FileRecord { + pub id: u32, + pub size: u64, + pub first_block: u64, + pub blocks: u16, + pub generation: u64, +} + +#[derive(Clone, Copy)] +struct Slot { record: Option, name: [u8; 63], name_len: u8 } +impl Slot { const fn empty() -> Self { Self { record: None, name: [0;63], name_len: 0 } } } + +pub struct FileStore { + slots: [Slot; N], + next_id: u32, + next_block: u64, +} +impl FileStore { + pub const fn new(start_block: u64) -> Self { Self { slots: [Slot::empty(); N], next_id: 1, next_block: start_block } } + + pub fn create(&mut self, name: &[u8]) -> Result { + if name.is_empty() || name.len() > 63 || name.iter().any(|b| *b == 0 || *b == b'/') { return Err(FileStoreError::InvalidName); } + if self.find(name).is_some() { return Err(FileStoreError::InvalidName); } + let slot = self.slots.iter().position(|s| s.record.is_none()).ok_or(FileStoreError::Full)?; + let record = FileRecord { id: self.next_id, size: 0, first_block: self.next_block, blocks: 0, generation: 1 }; + self.next_id = self.next_id.checked_add(1).ok_or(FileStoreError::Full)?; + self.slots[slot].record = Some(record); + self.slots[slot].name[..name.len()].copy_from_slice(name); + self.slots[slot].name_len = name.len() as u8; + Ok(record) + } + + pub fn write_at(&mut self, device: &mut D, id: u32, offset: usize, data: &[u8]) -> Result { + let index = self.find_id(id).ok_or(FileStoreError::NotFound)?; + let record = self.slots[index].record.unwrap(); + let end = offset.checked_add(data.len()).ok_or(FileStoreError::TooLarge)?; + if end > MAX_FILE_SIZE { return Err(FileStoreError::TooLarge); } + if data.is_empty() { return Ok(0); } + let required_blocks = end.div_ceil(BLOCK_SIZE); + if required_blocks > MAX_FILE_BLOCKS { return Err(FileStoreError::TooLarge); } + if required_blocks > record.blocks as usize { + let additional = required_blocks - record.blocks as usize; + if self.next_block.checked_add(additional as u64).is_none() { return Err(FileStoreError::NoSpace); } + self.slots[index].record.as_mut().unwrap().blocks = required_blocks as u16; + self.next_block = self.next_block.saturating_add(additional as u64); + } + let first = record.first_block; + let mut consumed = 0usize; + while consumed < data.len() { + let absolute = offset + consumed; + let block = first + (absolute / BLOCK_SIZE) as u64; + let in_block = absolute % BLOCK_SIZE; + let take = core::cmp::min(BLOCK_SIZE - in_block, data.len() - consumed); + let mut buf = [0u8; BLOCK_SIZE]; + device.read_block(block, &mut buf)?; + buf[in_block..in_block + take].copy_from_slice(&data[consumed..consumed + take]); + device.write_block(block, &buf)?; + consumed += take; + } + let current = self.slots[index].record.as_mut().unwrap(); + if end as u64 > current.size { current.size = end as u64; } + current.generation = current.generation.saturating_add(1); + Ok(data.len()) + } + + pub fn read_at(&self, device: &mut D, id: u32, offset: usize, out: &mut [u8]) -> Result { + let index = self.find_id(id).ok_or(FileStoreError::NotFound)?; + let record = self.slots[index].record.unwrap(); + if offset > record.size as usize { return Err(FileStoreError::InvalidOffset); } + let available = record.size as usize - offset; + let count = core::cmp::min(available, out.len()); + let mut consumed = 0usize; + while consumed < count { + let absolute = offset + consumed; + let block = record.first_block + (absolute / BLOCK_SIZE) as u64; + let in_block = absolute % BLOCK_SIZE; + let take = core::cmp::min(BLOCK_SIZE - in_block, count - consumed); + let mut buf = [0u8; BLOCK_SIZE]; + device.read_block(block, &mut buf)?; + out[consumed..consumed + take].copy_from_slice(&buf[in_block..in_block + take]); + consumed += take; + } + Ok(count) + } + + pub fn truncate(&mut self, id: u32, size: usize) -> Result<(), FileStoreError> { + if size > MAX_FILE_SIZE { return Err(FileStoreError::TooLarge); } + let index = self.find_id(id).ok_or(FileStoreError::NotFound)?; + let r = self.slots[index].record.as_mut().unwrap(); + r.size = size as u64; + r.blocks = size.div_ceil(BLOCK_SIZE) as u16; + r.generation = r.generation.saturating_add(1); + Ok(()) + } + + pub fn delete(&mut self, id: u32) -> Result<(), FileStoreError> { + let index = self.find_id(id).ok_or(FileStoreError::NotFound)?; + self.slots[index] = Slot::empty(); + Ok(()) + } + + pub fn lookup(&self, name: &[u8]) -> Option { self.find(name).and_then(|i| self.slots[i].record) } + pub fn record(&self, id: u32) -> Option { self.find_id(id).and_then(|i| self.slots[i].record) } + + fn find(&self, name: &[u8]) -> Option { self.slots.iter().position(|s| s.record.is_some() && s.name_len as usize == name.len() && &s.name[..name.len()] == name) } + fn find_id(&self, id: u32) -> Option { self.slots.iter().position(|s| s.record.map(|r| r.id) == Some(id)) } +} +impl Default for FileStore { fn default() -> Self { Self::new(8) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::storage::RamBlockDevice; + #[test] + fn block_backed_round_trip_supports_seek() { + let mut disk = RamBlockDevice::default(); + let mut fs = FileStore::<4>::new(8); + let file = fs.create(b"persist.bin").unwrap(); + fs.write_at(&mut disk, file.id, 3, b"AWEOS").unwrap(); + let mut out = [0u8; 8]; + assert_eq!(fs.read_at(&mut disk, file.id, 3, &mut out).unwrap(), 5); + assert_eq!(&out[..5], b"AWEOS"); + assert_eq!(fs.record(file.id).unwrap().size, 8); + } + #[test] + fn rejects_out_of_bounds_file_size() { + let mut disk = RamBlockDevice::default(); + let mut fs = FileStore::<1>::new(8); + let file = fs.create(b"x").unwrap(); + assert_eq!(fs.write_at(&mut disk, file.id, MAX_FILE_SIZE, b"x"), Err(FileStoreError::TooLarge)); + } +} \ No newline at end of file From 99d924cdb2e91a2ab836e7702c0ff24166b76968 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:18:26 +0400 Subject: [PATCH 20/65] feat(aweos): integrate block-backed persistent file store into storage core --- kernel/src/storage/mod.rs | 118 +++++++++----------------------------- 1 file changed, 28 insertions(+), 90 deletions(-) diff --git a/kernel/src/storage/mod.rs b/kernel/src/storage/mod.rs index ed21365..22372d0 100644 --- a/kernel/src/storage/mod.rs +++ b/kernel/src/storage/mod.rs @@ -4,15 +4,14 @@ #![allow(dead_code)] +pub mod file_store; pub mod gpt; pub mod journal; pub mod ramdisk; pub mod vfs; -pub use gpt::{ - GptError, GptHeader, GptPartition, crc32, parse_header, parse_partition, - validate_partition_array_crc, -}; +pub use file_store::{FileRecord, FileStore, FileStoreError, MAX_FILE_BLOCKS, MAX_FILE_SIZE, MAX_FILES}; +pub use gpt::{GptError, GptHeader, GptPartition, crc32, parse_header, parse_partition, validate_partition_array_crc}; pub use journal::{JournalError, JournalState, JournalTxn, RecoveryDecision, decide_recovery}; pub use ramdisk::{RAMDISK_BLOCKS, RamBlockDevice}; pub use vfs::{AweFs, FileName, FsError, Inode, JournalRecord, NodeKind, RecoveryAction, Vfs}; @@ -20,36 +19,20 @@ pub use vfs::{AweFs, FileName, FsError, Inode, JournalRecord, NodeKind, Recovery pub const BLOCK_SIZE: usize = 4096; #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum StorageError { - InvalidBlock, - BufferTooSmall, - ReadOnly, - Io, - Unsupported, - InvalidMetadata, - TooLarge, -} +pub enum StorageError { InvalidBlock, BufferTooSmall, ReadOnly, Io, Unsupported, InvalidMetadata, TooLarge } impl From for StorageError { fn from(error: GptError) -> Self { match error { GptError::BufferTooSmall => Self::BufferTooSmall, - GptError::InvalidHeaderSize - | GptError::InvalidLbaRange - | GptError::InvalidEntrySize - | GptError::TooManyPartitions - | GptError::InvalidPartitionRange - | GptError::HeaderCrcMismatch - | GptError::PartitionArrayCrcMismatch - | GptError::BadSignature - | GptError::UnsupportedRevision => Self::InvalidMetadata, + GptError::InvalidHeaderSize | GptError::InvalidLbaRange | GptError::InvalidEntrySize | + GptError::TooManyPartitions | GptError::InvalidPartitionRange | GptError::HeaderCrcMismatch | + GptError::PartitionArrayCrcMismatch | GptError::BadSignature | GptError::UnsupportedRevision => Self::InvalidMetadata, } } } pub trait BlockDevice { - fn block_size(&self) -> usize { - BLOCK_SIZE - } + fn block_size(&self) -> usize { BLOCK_SIZE } fn block_count(&self) -> u64; fn read_block(&mut self, block: u64, out: &mut [u8]) -> Result<(), StorageError>; fn write_block(&mut self, block: u64, data: &[u8]) -> Result<(), StorageError>; @@ -57,82 +40,42 @@ pub trait BlockDevice { } #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct DeviceGeometry { - pub block_size: u32, - pub blocks: u64, - pub read_only: bool, -} +pub struct DeviceGeometry { pub block_size: u32, pub blocks: u64, pub read_only: bool } impl DeviceGeometry { - pub const fn new(block_size: u32, blocks: u64, read_only: bool) -> Self { - Self { - block_size, - blocks, - read_only, - } - } - pub const fn bytes(self) -> Option { - self.blocks.checked_mul(self.block_size as u64) - } + pub const fn new(block_size: u32, blocks: u64, read_only: bool) -> Self { Self { block_size, blocks, read_only } } + pub const fn bytes(self) -> Option { self.blocks.checked_mul(self.block_size as u64) } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct GptScanSummary { - pub header: GptHeader, - pub partitions: u16, -} +pub struct GptScanSummary { pub header: GptHeader, pub partitions: u16 } pub fn scan_gpt(device: &mut D) -> Result { - if device.block_size() < gpt::GPT_SECTOR_SIZE - || !device.block_size().is_multiple_of(gpt::GPT_SECTOR_SIZE) - { - return Err(StorageError::Unsupported); - } + if device.block_size() < gpt::GPT_SECTOR_SIZE || !device.block_size().is_multiple_of(gpt::GPT_SECTOR_SIZE) { return Err(StorageError::Unsupported); } let sectors_per_block = device.block_size() / gpt::GPT_SECTOR_SIZE; - let disk_last_lba = device - .block_count() - .checked_mul(sectors_per_block as u64) - .and_then(|s| s.checked_sub(1)) - .ok_or(StorageError::InvalidBlock)?; + let disk_last_lba = device.block_count().checked_mul(sectors_per_block as u64).and_then(|s| s.checked_sub(1)).ok_or(StorageError::InvalidBlock)?; let mut block = [0u8; BLOCK_SIZE]; device.read_block(0, &mut block)?; let header = gpt::parse_header(&block[gpt::GPT_SECTOR_SIZE..], disk_last_lba)?; - let entry_bytes = (header.partition_count as usize) - .checked_mul(header.partition_entry_size as usize) - .ok_or(StorageError::TooLarge)?; - if entry_bytes > 16 * 1024 { - return Err(StorageError::TooLarge); - } + let entry_bytes = (header.partition_count as usize).checked_mul(header.partition_entry_size as usize).ok_or(StorageError::TooLarge)?; + if entry_bytes > 16 * 1024 { return Err(StorageError::TooLarge); } let mut entries = [0u8; 16 * 1024]; let first_block = header.partition_entry_lba / sectors_per_block as u64; - let first_sector_in_block = - (header.partition_entry_lba % sectors_per_block as u64) as usize * gpt::GPT_SECTOR_SIZE; + let first_sector_in_block = (header.partition_entry_lba % sectors_per_block as u64) as usize * gpt::GPT_SECTOR_SIZE; let block_count = (first_sector_in_block + entry_bytes).div_ceil(device.block_size()); for index in 0..block_count { device.read_block(first_block + index as u64, &mut block)?; let source_start = if index == 0 { first_sector_in_block } else { 0 }; - let destination_start = if index == 0 { - 0 - } else { - index * device.block_size() - first_sector_in_block - }; - let copy_len = core::cmp::min( - device.block_size() - source_start, - entry_bytes.saturating_sub(destination_start), - ); - if copy_len == 0 { - break; - } - entries[destination_start..destination_start + copy_len] - .copy_from_slice(&block[source_start..source_start + copy_len]); + let destination_start = if index == 0 { 0 } else { index * device.block_size() - first_sector_in_block }; + let copy_len = core::cmp::min(device.block_size() - source_start, entry_bytes.saturating_sub(destination_start)); + if copy_len == 0 { break; } + entries[destination_start..destination_start + copy_len].copy_from_slice(&block[source_start..source_start + copy_len]); } gpt::validate_partition_array_crc(&entries[..entry_bytes], header.partition_array_crc32)?; let mut partitions = 0u16; for index in 0..header.partition_count as usize { let start = index * header.partition_entry_size as usize; let end = start + header.partition_entry_size as usize; - if gpt::parse_partition(&entries[start..end], &header, disk_last_lba)?.is_some() { - partitions = partitions.saturating_add(1); - } + if gpt::parse_partition(&entries[start..end], &header, disk_last_lba)?.is_some() { partitions = partitions.saturating_add(1); } } Ok(GptScanSummary { header, partitions }) } @@ -145,34 +88,29 @@ mod tests { let mut disk = RamBlockDevice::default(); let mut block = [0u8; BLOCK_SIZE]; let entry_offset = 1024usize; - block[entry_offset] = 1; - block[entry_offset + 16] = 2; + block[entry_offset] = 1; block[entry_offset + 16] = 2; block[entry_offset + 32..entry_offset + 40].copy_from_slice(&34u64.to_le_bytes()); block[entry_offset + 40..entry_offset + 48].copy_from_slice(&100u64.to_le_bytes()); let partition_crc = crc32(&block[entry_offset..entry_offset + 128]); let header_offset = 512usize; block[header_offset..header_offset + 8].copy_from_slice(&gpt::GPT_SIGNATURE); - block[header_offset + 8..header_offset + 12] - .copy_from_slice(&gpt::GPT_REVISION_1_0.to_le_bytes()); - block[header_offset + 12..header_offset + 16] - .copy_from_slice(&(gpt::GPT_HEADER_MIN_SIZE as u32).to_le_bytes()); + block[header_offset + 8..header_offset + 12].copy_from_slice(&gpt::GPT_REVISION_1_0.to_le_bytes()); + block[header_offset + 12..header_offset + 16].copy_from_slice(&(gpt::GPT_HEADER_MIN_SIZE as u32).to_le_bytes()); block[header_offset + 24..header_offset + 32].copy_from_slice(&1u64.to_le_bytes()); block[header_offset + 32..header_offset + 40].copy_from_slice(&511u64.to_le_bytes()); block[header_offset + 40..header_offset + 48].copy_from_slice(&34u64.to_le_bytes()); block[header_offset + 48..header_offset + 56].copy_from_slice(&480u64.to_le_bytes()); block[header_offset + 72..header_offset + 80].copy_from_slice(&2u64.to_le_bytes()); block[header_offset + 80..header_offset + 84].copy_from_slice(&1u32.to_le_bytes()); - block[header_offset + 84..header_offset + 88] - .copy_from_slice(&(gpt::GPT_PARTITION_ENTRY_MIN_SIZE as u32).to_le_bytes()); + block[header_offset + 84..header_offset + 88].copy_from_slice(&(gpt::GPT_PARTITION_ENTRY_MIN_SIZE as u32).to_le_bytes()); block[header_offset + 88..header_offset + 92].copy_from_slice(&partition_crc.to_le_bytes()); let mut header_copy = block; header_copy[header_offset + 16..header_offset + 20].fill(0); - let header_crc = - crc32(&header_copy[header_offset..header_offset + gpt::GPT_HEADER_MIN_SIZE]); + let header_crc = crc32(&header_copy[header_offset..header_offset + gpt::GPT_HEADER_MIN_SIZE]); block[header_offset + 16..header_offset + 20].copy_from_slice(&header_crc.to_le_bytes()); disk.write_block(0, &block).expect("write GPT"); let summary = scan_gpt(&mut disk).expect("scan GPT"); assert_eq!(summary.partitions, 1); assert_eq!(summary.header.partition_count, 1); } -} +} \ No newline at end of file From 1764597203b5753a826da42f1dce976318de7fe6 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:19:29 +0400 Subject: [PATCH 21/65] fix(aweos): restore strict userspace pointer and syscall validation --- kernel/src/syscall/dispatch.rs | 205 ++++++++++++--------------------- 1 file changed, 71 insertions(+), 134 deletions(-) diff --git a/kernel/src/syscall/dispatch.rs b/kernel/src/syscall/dispatch.rs index cdb1d96..4074cd8 100644 --- a/kernel/src/syscall/dispatch.rs +++ b/kernel/src/syscall/dispatch.rs @@ -1,9 +1,27 @@ #![no_std] + use super::abi::{ERR_INVALID_ARGUMENT, ERR_OK, ERR_PERMISSION, Syscall, SyscallResult}; use crate::process::{ProcessDescriptor, ProcessState}; -pub struct SyscallContext<'a> { - pub process: &'a mut ProcessDescriptor, + +const MAX_USER_COPY: usize = 4096; +const USER_TOP: u64 = 0x0000_8000_0000_0000; +const PAGE_SIZE: u64 = 4096; + +fn valid_user_range(ptr: u64, len: usize) -> bool { + if ptr == 0 || len == 0 { return false; } + let len_u64 = match u64::try_from(len) { Ok(v) => v, Err(_) => return false }; + let end = match ptr.checked_add(len_u64 - 1) { Some(v) => v, None => return false }; + ptr < USER_TOP && end < USER_TOP } + +fn valid_user_page_range(addr: u64, size: u64) -> bool { + if size == 0 || addr % PAGE_SIZE != 0 { return false; } + let end = match addr.checked_add(size - 1) { Some(v) => v, None => return false }; + addr < USER_TOP && end < USER_TOP +} + +pub struct SyscallContext<'a> { pub process: &'a mut ProcessDescriptor } + impl<'a> SyscallContext<'a> { pub fn dispatch(&mut self, number: u64, args: [u64; 6]) -> SyscallResult { let call = match number { @@ -20,174 +38,93 @@ impl<'a> SyscallContext<'a> { }; match call { Syscall::Yield => { + if self.process.state != ProcessState::Running { return err(ERR_PERMISSION); } self.process.state = ProcessState::Runnable; ok(0) } Syscall::Exit => { + if self.process.state == ProcessState::Exited { return err(ERR_PERMISSION); } self.process.state = ProcessState::Exited; ok(0) } Syscall::Spawn => err(ERR_PERMISSION), Syscall::IpcSend | Syscall::IpcRecv => { - if !self.process.budget.consume_ipc(1) { - return err(ERR_PERMISSION); - } + if args[1] == 0 || args[1] > 4 { return err(ERR_INVALID_ARGUMENT); } + if !self.process.budget.consume_ipc(1) { return err(ERR_PERMISSION); } ok(args[0]) } Syscall::Map => { - if !self.process.budget.permits_memory(args[0]) { - return err(ERR_PERMISSION); - } + if !valid_user_page_range(args[0], args[1]) { return err(ERR_INVALID_ARGUMENT); } + if !self.process.budget.consume_memory(args[1]) { return err(ERR_PERMISSION); } + ok(args[0]) + } + Syscall::Unmap => { + if !valid_user_page_range(args[0], args[1]) { return err(ERR_INVALID_ARGUMENT); } ok(args[0]) } - Syscall::Unmap => ok(args[0]), Syscall::Read => { - if args[1] == 0 { - return err(ERR_INVALID_ARGUMENT); - } + let len = core::cmp::min(args[1] as usize, MAX_USER_COPY); + if !valid_user_range(args[0], len) { return err(ERR_INVALID_ARGUMENT); } + let initrd = b"INITRD_SHELL_IMAGE_OK"; + let copy_len = core::cmp::min(len, initrd.len()); let ptr = args[0] as *mut u8; - let len = (args[1] as usize).min(4096); - if !ptr.is_null() && len > 0 { - // Populate initrd/input payload into user buffer - let initrd_data = b"INITRD_SHELL_IMAGE_OK"; - let copy_len = len.min(initrd_data.len()); - unsafe { - for (i, &byte) in initrd_data.iter().take(copy_len).enumerate() { - core::ptr::write_volatile(ptr.add(i), byte); - } + unsafe { + for (i, byte) in initrd[..copy_len].iter().enumerate() { + core::ptr::write_volatile(ptr.add(i), *byte); } - return ok(copy_len as u64); } - ok(args[1]) + ok(copy_len as u64) } Syscall::Write => { - if args[1] == 0 { - return err(ERR_INVALID_ARGUMENT); - } + let len = core::cmp::min(args[1] as usize, MAX_USER_COPY); + if !valid_user_range(args[0], len) { return err(ERR_INVALID_ARGUMENT); } let ptr = args[0] as *const u8; - let len = (args[1] as usize).min(4096); - if !ptr.is_null() { + #[cfg(all(target_arch = "x86_64", target_os = "none"))] + unsafe { for i in 0..len { - let byte = unsafe { core::ptr::read_volatile(ptr.add(i)) }; - #[cfg(target_arch = "x86_64")] + let byte = core::ptr::read_volatile(ptr.add(i)); crate::arch::x86_64::serial_write_byte(byte); } } - ok(args[1]) + ok(len as u64) } } } } -const fn ok(value: u64) -> SyscallResult { - SyscallResult { - value, - error: ERR_OK, - } -} -const fn err(error: u64) -> SyscallResult { - SyscallResult { value: 0, error } -} + +const fn ok(value: u64) -> SyscallResult { SyscallResult { value, error: ERR_OK } } +const fn err(error: u64) -> SyscallResult { SyscallResult { value: 0, error } } + #[cfg(test)] mod tests { use super::*; use crate::process::{ProcessId, ResourceBudget}; fn descriptor() -> ProcessDescriptor { - ProcessDescriptor { - id: ProcessId(1), - state: ProcessState::Running, - budget: ResourceBudget { - cpu_ticks: 10, - memory_bytes: 4096, - ipc_messages: 2, - }, - } - } - #[test] - fn invalid_call_is_rejected() { - let mut p = descriptor(); - let mut c = SyscallContext { process: &mut p }; - assert_eq!(c.dispatch(99, [0; 6]).error, ERR_INVALID_ARGUMENT); + ProcessDescriptor { id: ProcessId(1), state: ProcessState::Running, budget: ResourceBudget { cpu_ticks: 10, memory_bytes: 8192, ipc_messages: 2 } } } - #[test] - fn exit_changes_state() { - let mut p = descriptor(); - let mut c = SyscallContext { process: &mut p }; - assert_eq!(c.dispatch(Syscall::Exit as u64, [0; 6]).error, ERR_OK); - assert_eq!(c.process.state, ProcessState::Exited); + #[test] fn invalid_call_is_rejected() { let mut p = descriptor(); let mut c = SyscallContext { process: &mut p }; assert_eq!(c.dispatch(99, [0;6]).error, ERR_INVALID_ARGUMENT); } + #[test] fn yield_and_exit_change_state() { let mut p = descriptor(); let mut c = SyscallContext { process: &mut p }; assert_eq!(c.dispatch(Syscall::Yield as u64, [0;6]).error, ERR_OK); assert_eq!(c.process.state, ProcessState::Runnable); c.process.state = ProcessState::Running; assert_eq!(c.dispatch(Syscall::Exit as u64, [0;6]).error, ERR_OK); assert_eq!(c.process.state, ProcessState::Exited); } + #[test] fn pointer_validation_is_fail_closed() { + let mut p = descriptor(); let mut c = SyscallContext { process: &mut p }; let buf = [0u8;8]; + assert_eq!(c.dispatch(Syscall::Read as u64, [buf.as_ptr() as u64, 0,0,0,0,0]).error, ERR_INVALID_ARGUMENT); + assert_eq!(c.dispatch(Syscall::Read as u64, [USER_TOP - 4, 8,0,0,0,0]).error, ERR_INVALID_ARGUMENT); + assert_eq!(c.dispatch(Syscall::Write as u64, [buf.as_ptr() as u64, 8,0,0,0,0]).error, ERR_OK); } - #[test] - fn yield_changes_state() { - let mut p = descriptor(); - let mut c = SyscallContext { process: &mut p }; - assert_eq!(c.dispatch(Syscall::Yield as u64, [0; 6]).error, ERR_OK); - assert_eq!(c.process.state, ProcessState::Runnable); + #[test] fn memory_mapping_requires_page_alignment_and_budget() { + let mut p = descriptor(); let mut c = SyscallContext { process: &mut p }; + assert_eq!(c.dispatch(Syscall::Map as u64, [0x4000, 4096,0,0,0,0]).error, ERR_OK); + assert_eq!(c.dispatch(Syscall::Map as u64, [0x4001, 4096,0,0,0,0]).error, ERR_INVALID_ARGUMENT); + assert_eq!(c.dispatch(Syscall::Map as u64, [0x8000, 4096,0,0,0,0]).error, ERR_PERMISSION); } - #[test] - fn ipc_consumes_budget_and_fails_closed() { - let mut p = descriptor(); - let mut c = SyscallContext { process: &mut p }; - assert_eq!( - c.dispatch(Syscall::IpcSend as u64, [7, 0, 0, 0, 0, 0]) - .error, - ERR_OK - ); - assert_eq!( - c.dispatch(Syscall::IpcSend as u64, [8, 0, 0, 0, 0, 0]) - .error, - ERR_OK - ); - assert_eq!( - c.dispatch(Syscall::IpcSend as u64, [9, 0, 0, 0, 0, 0]) - .error, - ERR_PERMISSION - ); + #[test] fn ipc_budget_is_enforced() { + let mut p = descriptor(); let mut c = SyscallContext { process: &mut p }; + assert_eq!(c.dispatch(Syscall::IpcSend as u64, [7,1,0,0,0,0]).error, ERR_OK); + assert_eq!(c.dispatch(Syscall::IpcSend as u64, [8,1,0,0,0,0]).error, ERR_OK); + assert_eq!(c.dispatch(Syscall::IpcSend as u64, [9,1,0,0,0,0]).error, ERR_PERMISSION); } - #[test] - fn map_respects_memory_budget() { - let mut p = descriptor(); - let mut c = SyscallContext { process: &mut p }; - assert_eq!( - c.dispatch(Syscall::Map as u64, [4096, 0, 0, 0, 0, 0]).error, - ERR_OK - ); - assert_eq!( - c.dispatch(Syscall::Map as u64, [4097, 0, 0, 0, 0, 0]).error, - ERR_PERMISSION - ); + #[test] fn read_returns_bounded_initrd_payload() { + let mut p = descriptor(); let mut c = SyscallContext { process: &mut p }; let mut buf = [0u8;32]; + let result = c.dispatch(Syscall::Read as u64, [buf.as_mut_ptr() as u64,32,0,0,0,0]); + assert_eq!(result.error, ERR_OK); assert_eq!(&buf[..result.value as usize], b"INITRD_SHELL_IMAGE_OK"); } - #[test] - fn io_rejects_zero_length() { - let mut p = descriptor(); - let mut c = SyscallContext { process: &mut p }; - assert_eq!( - c.dispatch(Syscall::Read as u64, [0, 0, 0, 0, 0, 0]).error, - ERR_INVALID_ARGUMENT - ); - assert_eq!( - c.dispatch(Syscall::Write as u64, [0, 8, 0, 0, 0, 0]).error, - ERR_OK - ); - } - #[test] - fn spawn_is_privileged() { - let mut p = descriptor(); - let mut c = SyscallContext { process: &mut p }; - assert_eq!( - c.dispatch(Syscall::Spawn as u64, [0; 6]).error, - ERR_PERMISSION - ); - } - #[test] - fn read_populates_initrd_buffer() { - let mut p = descriptor(); - let mut c = SyscallContext { process: &mut p }; - let mut buf = [0u8; 32]; - let res = c.dispatch( - Syscall::Read as u64, - [buf.as_mut_ptr() as u64, buf.len() as u64, 0, 0, 0, 0], - ); - assert_eq!(res.error, ERR_OK); - assert!(res.value > 0); - assert!(&buf[..res.value as usize] == b"INITRD_SHELL_IMAGE_OK"); - } -} +} \ No newline at end of file From 9c91a0b64d245cdf1602625b708637daf82a4d7e Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:19:47 +0400 Subject: [PATCH 22/65] feat(aweos): add native AYUI runtime adapter with dynamic framebuffer and input --- kernel/src/runtime/ui_adapter.rs | 81 ++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 kernel/src/runtime/ui_adapter.rs diff --git a/kernel/src/runtime/ui_adapter.rs b/kernel/src/runtime/ui_adapter.rs new file mode 100644 index 0000000..dc28037 --- /dev/null +++ b/kernel/src/runtime/ui_adapter.rs @@ -0,0 +1,81 @@ +#![no_std] + +use awe_ayui::{AppType, Compositor, Framebuffer, InputEvent as AyuiInputEvent, Rect as AyuiRect}; +use super::{FramebufferInfo, InputEvent, RuntimeRect}; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum UiRuntimeError { + InvalidFramebuffer, + RenderFailed, + WindowFailed, +} + +pub struct AyuiRuntime { + pub compositor: Compositor, + framebuffer: Option, +} + +impl AyuiRuntime { + pub const fn new() -> Self { Self { compositor: Compositor::new(), framebuffer: None } } + + pub fn attach_framebuffer(&mut self, info: FramebufferInfo) -> Result<(), UiRuntimeError> { + if !info.validate() { return Err(UiRuntimeError::InvalidFramebuffer); } + self.framebuffer = Some(info); + Ok(()) + } + + pub fn create_window(&mut self, rect: RuntimeRect, app: AppType, title: &[u8]) -> Result { + self.compositor + .create_app_window(AyuiRect { x: rect.x, y: rect.y, width: rect.width, height: rect.height }, app, title) + .map(|id| id.0) + .map_err(|_| UiRuntimeError::WindowFailed) + } + + pub fn destroy_window(&mut self, id: u16) -> Result<(), UiRuntimeError> { + self.compositor.destroy_window(awe_ayui::WindowId(id)).map_err(|_| UiRuntimeError::WindowFailed) + } + + pub fn route_input(&mut self, event: InputEvent) -> Result<(), UiRuntimeError> { + let ayui = match event { + InputEvent::Key { code, pressed } => AyuiInputEvent::Key { code, pressed }, + InputEvent::Pointer { x, y, buttons } => AyuiInputEvent::Pointer { x, y, buttons }, + }; + self.compositor.push_input(ayui).map_err(|_| UiRuntimeError::WindowFailed) + } + + pub fn render(&mut self, buffer: &mut [u8]) -> Result<(), UiRuntimeError> { + let info = self.framebuffer.ok_or(UiRuntimeError::InvalidFramebuffer)?; + let required = info.required_bytes().ok_or(UiRuntimeError::InvalidFramebuffer)? as usize; + if buffer.len() < required { return Err(UiRuntimeError::InvalidFramebuffer); } + let mut fb = Framebuffer { + width: info.width, + height: info.height, + stride: info.pitch / info.bytes_per_pixel as u32, + buffer, + gpu_accel: false, + }; + self.compositor.render_to_framebuffer(&mut fb); + Ok(()) + } + + pub const fn framebuffer(&self) -> Option { self.framebuffer } +} +impl Default for AyuiRuntime { fn default() -> Self { Self::new() } } + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn dynamic_framebuffer_and_input_path_work() { + let mut ui = AyuiRuntime::new(); + let info = FramebufferInfo { address: 0x100000, size: 640 * 480 * 4, width: 640, height: 480, pitch: 640 * 4, bytes_per_pixel: 4 }; + ui.attach_framebuffer(info).unwrap(); + let id = ui.create_window(RuntimeRect { x: 20, y: 20, width: 240, height: 160 }, AppType::Terminal, b"Terminal").unwrap(); + ui.route_input(InputEvent::Pointer { x: 30, y: 30, buttons: 1 }).unwrap(); + ui.route_input(InputEvent::Key { code: 0x1c, pressed: true }).unwrap(); + let mut frame = [0u8; 640 * 480 * 4]; + ui.render(&mut frame).unwrap(); + assert!(frame.iter().any(|b| *b != 0)); + ui.destroy_window(id).unwrap(); + } +} \ No newline at end of file From a2f641718b6f30a858f2ae37afa20eb514c8bc4d Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:19:53 +0400 Subject: [PATCH 23/65] feat(aweos): export native AYUI runtime adapter --- kernel/src/runtime/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/kernel/src/runtime/mod.rs b/kernel/src/runtime/mod.rs index 5aa746a..2298af9 100644 --- a/kernel/src/runtime/mod.rs +++ b/kernel/src/runtime/mod.rs @@ -7,6 +7,7 @@ mod end_user; pub mod graphics; pub mod system; +pub mod ui_adapter; pub use end_user::{ AppRecord, AppState, EndUserRuntime, EndUserRuntimeError, FramebufferInfo, InputEvent, @@ -14,6 +15,7 @@ pub use end_user::{ }; pub use graphics::{DoubleBuffer, Rect as RuntimeRect, Window, WindowError, WindowManager}; pub use system::SystemRuntime; +pub use ui_adapter::{AyuiRuntime, UiRuntimeError}; #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct CapabilitySet(pub u64); From 7a1b4f40dc1da3bb07b3aa60d57e12a53726d6af Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:20:01 +0400 Subject: [PATCH 24/65] feat(aweos): add persistent filesystem namespace and mount manager --- kernel/src/storage/namespaces.rs | 74 ++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 kernel/src/storage/namespaces.rs diff --git a/kernel/src/storage/namespaces.rs b/kernel/src/storage/namespaces.rs new file mode 100644 index 0000000..7efa0c1 --- /dev/null +++ b/kernel/src/storage/namespaces.rs @@ -0,0 +1,74 @@ +#![no_std] + +use super::{FileStore, FileStoreError, MAX_FILES}; + +pub const MAX_MOUNTS: usize = 5; +pub const MAX_PATH: usize = 32; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Namespace { Config, Home, Apps, System, Log } +impl Namespace { + pub const fn path(self) -> &'static [u8] { + match self { Self::Config => b"/config", Self::Home => b"/home", Self::Apps => b"/apps", Self::System => b"/system", Self::Log => b"/log" } + } + pub const fn index(self) -> usize { match self { Self::Config => 0, Self::Home => 1, Self::Apps => 2, Self::System => 3, Self::Log => 4 } } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum NamespaceError { AlreadyMounted, NotMounted, InvalidName, Storage(FileStoreError), Capacity } +impl From for NamespaceError { fn from(v: FileStoreError) -> Self { Self::Storage(v) } } + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct MountPoint { pub namespace: Namespace, pub first_block: u64, pub mounted: bool } + +pub struct NamespaceManager { + mounts: [Option; MAX_MOUNTS], + stores: [Option>; MAX_MOUNTS], +} +impl NamespaceManager { + pub const fn new() -> Self { Self { mounts: [None; MAX_MOUNTS], stores: [None; MAX_MOUNTS] } } + + pub fn mount(&mut self, namespace: Namespace, first_block: u64) -> Result<(), NamespaceError> { + let idx = namespace.index(); + if self.mounts[idx].is_some() { return Err(NamespaceError::AlreadyMounted); } + self.mounts[idx] = Some(MountPoint { namespace, first_block, mounted: true }); + self.stores[idx] = Some(FileStore::new(first_block)); + Ok(()) + } + + pub fn is_mounted(&self, namespace: Namespace) -> bool { self.mounts[namespace.index()].map(|m| m.mounted).unwrap_or(false) } + + pub fn store(&self, namespace: Namespace) -> Result<&FileStore, NamespaceError> { self.stores[namespace.index()].as_ref().ok_or(NamespaceError::NotMounted) } + pub fn store_mut(&mut self, namespace: Namespace) -> Result<&mut FileStore, NamespaceError> { self.stores[namespace.index()].as_mut().ok_or(NamespaceError::NotMounted) } + + pub fn unmount(&mut self, namespace: Namespace) -> Result<(), NamespaceError> { + let idx = namespace.index(); + if self.mounts[idx].is_none() { return Err(NamespaceError::NotMounted); } + self.mounts[idx] = None; self.stores[idx] = None; Ok(()) + } +} +impl Default for NamespaceManager { fn default() -> Self { Self::new() } } + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn required_namespaces_mount_independently() { + let mut m = NamespaceManager::<4>::new(); + m.mount(Namespace::Config, 8).unwrap(); + m.mount(Namespace::Home, 64).unwrap(); + m.mount(Namespace::Apps, 128).unwrap(); + m.mount(Namespace::System, 192).unwrap(); + m.mount(Namespace::Log, 256).unwrap(); + assert!(m.is_mounted(Namespace::Config)); assert!(m.is_mounted(Namespace::Log)); + assert_eq!(Namespace::System.path(), b"/system"); + } + #[test] + fn duplicate_mount_is_rejected_and_unmount_works() { + let mut m = NamespaceManager::<1>::new(); + m.mount(Namespace::Config, 8).unwrap(); + assert_eq!(m.mount(Namespace::Config, 9), Err(NamespaceError::AlreadyMounted)); + m.unmount(Namespace::Config).unwrap(); + assert!(!m.is_mounted(Namespace::Config)); + } +} \ No newline at end of file From 7cc2b3c14fd2e44f2f7d7fa2d939faa645114227 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:20:14 +0400 Subject: [PATCH 25/65] feat(aweos): expose persistent namespace manager --- kernel/src/storage/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/kernel/src/storage/mod.rs b/kernel/src/storage/mod.rs index 22372d0..fb61485 100644 --- a/kernel/src/storage/mod.rs +++ b/kernel/src/storage/mod.rs @@ -7,12 +7,14 @@ pub mod file_store; pub mod gpt; pub mod journal; +pub mod namespaces; pub mod ramdisk; pub mod vfs; pub use file_store::{FileRecord, FileStore, FileStoreError, MAX_FILE_BLOCKS, MAX_FILE_SIZE, MAX_FILES}; pub use gpt::{GptError, GptHeader, GptPartition, crc32, parse_header, parse_partition, validate_partition_array_crc}; pub use journal::{JournalError, JournalState, JournalTxn, RecoveryDecision, decide_recovery}; +pub use namespaces::{MountPoint, Namespace, NamespaceError, NamespaceManager, MAX_MOUNTS}; pub use ramdisk::{RAMDISK_BLOCKS, RamBlockDevice}; pub use vfs::{AweFs, FileName, FsError, Inode, JournalRecord, NodeKind, RecoveryAction, Vfs}; @@ -111,6 +113,5 @@ mod tests { disk.write_block(0, &block).expect("write GPT"); let summary = scan_gpt(&mut disk).expect("scan GPT"); assert_eq!(summary.partitions, 1); - assert_eq!(summary.header.partition_count, 1); } -} \ No newline at end of file +} From c04f33c3019c2713567dcb13087e36e6a31d4178 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:20:44 +0400 Subject: [PATCH 26/65] ci(aweos): run feature branch validation and headless QEMU --- .github/workflows/ci.yml | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4d37d43..5be4fd8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,7 @@ name: AWEOS CI Pipeline on: push: - branches: [ main, master ] + branches: [ main, master, 'feat/**' ] pull_request: branches: [ main, master ] @@ -16,31 +16,41 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 - - name: Install Rust Toolchain run: | rustup show rustup target add x86_64-unknown-none rustup target add x86_64-unknown-uefi - - name: Check Workspace Compilation run: cargo check --workspace --all-targets - - name: Run Workspace Tests - run: cargo test --workspace - + run: cargo test --workspace --all-targets - name: Validate Formatting run: cargo fmt --all --check - + - name: Validate Clippy + run: cargo clippy --workspace --all-targets -- -D warnings + - name: Install QEMU + run: sudo apt-get update && sudo apt-get install -y qemu-system-x86 - name: Build Boot Images run: ./scripts/build-images.sh - - name: Verify Image Artifacts run: | test -f dist/aweos-x86_64.iso test -f dist/aweos-x86_64.img test -f dist/aweos-uefi.img test -f dist/aweos-bios.img - test -f build/aweos-x86_64.iso - test -f build/aweos-x86_64.img - echo "All boot artifacts successfully generated and verified." + - name: Headless QEMU Smoke Test + timeout-minutes: 3 + run: | + qemu-system-x86_64 --version + timeout 20s qemu-system-x86_64 -display none -serial stdio -no-reboot -no-shutdown -kernel target/x86_64-unknown-none/debug/aweos > qemu-serial.log 2>&1 || test $? -eq 124 + cat qemu-serial.log + grep -q "AWEOS: kernel state = RUNNING" qemu-serial.log + grep -q "AWEOS: entering Ring 3" qemu-serial.log + - name: Upload QEMU Evidence + if: always() + uses: actions/upload-artifact@v4 + with: + name: aweos-qemu-evidence + path: qemu-serial.log + if-no-files-found: warn From 107eb55c8b54f047c871938fc3d7cde85101b35c Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:21:17 +0400 Subject: [PATCH 27/65] fix(aweos): enable keyboard and mouse IRQs --- kernel/src/arch/x86_64/interrupts.rs | 36 ++++++++++------------------ 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/kernel/src/arch/x86_64/interrupts.rs b/kernel/src/arch/x86_64/interrupts.rs index a112dad..ee1c8bf 100644 --- a/kernel/src/arch/x86_64/interrupts.rs +++ b/kernel/src/arch/x86_64/interrupts.rs @@ -3,51 +3,36 @@ use super::idt::Idt; pub const TIMER_VECTOR: u8 = 32; +pub const KEYBOARD_VECTOR: u8 = 33; +pub const MOUSE_VECTOR: u8 = 44; -/// Remaps the 8259 PIC so IRQs 0..15 land on IDT vectors 32..47. pub unsafe fn init_pic() { unsafe { use super::{io_in8, io_out8}; - - // Save masks let mask1 = io_in8(0x21); let mask2 = io_in8(0xA1); - - // ICW1: Start initialization in cascade mode io_out8(0x20, 0x11); io_out8(0xA0, 0x11); - - // ICW2: Vector offsets (32 for master, 40 for slave) io_out8(0x21, 0x20); io_out8(0xA1, 0x28); - - // ICW3: Cascade setup io_out8(0x21, 0x04); io_out8(0xA1, 0x02); - - // ICW4: 8086 mode io_out8(0x21, 0x01); io_out8(0xA1, 0x01); - - // Restore masks (or unmask IRQ0 timer & IRQ1 keyboard) - io_out8(0x21, mask1 & !0x01); // unmask IRQ0 (timer) - io_out8(0xA1, mask2); + // Enable timer IRQ0, keyboard IRQ1, and mouse IRQ12 (slave IRQ4). + io_out8(0x21, mask1 & !0x03); + io_out8(0xA1, mask2 & !0x10); } } pub unsafe fn pic_send_eoi(irq: u8) { unsafe { use super::io_out8; - if irq >= 8 { - io_out8(0xA0, 0x20); - } + if irq >= 8 { io_out8(0xA0, 0x20); } io_out8(0x20, 0x20); } } -/// Installs the minimal early interrupt surface. The timer handler remains an -/// explicit ABI boundary so scheduler code can be attached without changing -/// IDT construction. pub fn install_early_interrupts(idt: &mut Idt, code_selector: u16, timer_handler: u64) { idt.set_handler(TIMER_VECTOR, timer_handler, code_selector); } @@ -56,12 +41,17 @@ pub fn install_early_interrupts(idt: &mut Idt, code_selector: u16, timer_handler mod tests { use super::*; extern "C" fn timer() {} - + #[test] + fn vectors_match_pic_remap() { + assert_eq!(TIMER_VECTOR, 32); + assert_eq!(KEYBOARD_VECTOR, 33); + assert_eq!(MOUSE_VECTOR, 44); + } #[test] fn timer_vector_is_installed() { let mut idt = Idt::new(); install_early_interrupts(&mut idt, 0x08, timer as *const () as usize as u64); assert!(idt.is_present(TIMER_VECTOR)); - assert!(!idt.is_present(33)); + assert!(!idt.is_present(KEYBOARD_VECTOR)); } } From f6bc7807e2bd8f5338e61b13bd4fe0ba832532d2 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:21:46 +0400 Subject: [PATCH 28/65] feat(aweos): add interrupt-safe PS/2 input event broker --- kernel/src/arch/x86_64/input.rs | 104 ++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 kernel/src/arch/x86_64/input.rs diff --git a/kernel/src/arch/x86_64/input.rs b/kernel/src/arch/x86_64/input.rs new file mode 100644 index 0000000..2056df6 --- /dev/null +++ b/kernel/src/arch/x86_64/input.rs @@ -0,0 +1,104 @@ +#![no_std] + +use core::sync::atomic::{AtomicBool, AtomicU8, AtomicUsize, AtomicU64, Ordering}; +use crate::drivers::{KeyCode, Ps2Event}; + +pub const CAPACITY: usize = 128; +static HEAD: AtomicUsize = AtomicUsize::new(0); +static TAIL: AtomicUsize = AtomicUsize::new(0); +static QUEUE: [AtomicU64; CAPACITY] = [const { AtomicU64::new(0) }; CAPACITY]; +static KB_EXTENDED: AtomicBool = AtomicBool::new(false); +static KB_BREAK: AtomicBool = AtomicBool::new(false); +static MOUSE_INDEX: AtomicU8 = AtomicU8::new(0); +static MOUSE_0: AtomicU8 = AtomicU8::new(0); +static MOUSE_1: AtomicU8 = AtomicU8::new(0); +static MOUSE_2: AtomicU8 = AtomicU8::new(0); + +fn enqueue(encoded: u64) -> bool { + let tail = TAIL.load(Ordering::Relaxed); + let next = (tail + 1) % CAPACITY; + if next == HEAD.load(Ordering::Acquire) { return false; } + QUEUE[tail].store(encoded, Ordering::Release); + TAIL.store(next, Ordering::Release); + true +} + +pub fn dequeue() -> Option { + let head = HEAD.load(Ordering::Relaxed); + if head == TAIL.load(Ordering::Acquire) { return None; } + let encoded = QUEUE[head].load(Ordering::Acquire); + HEAD.store((head + 1) % CAPACITY, Ordering::Release); + if encoded >> 63 == 0 { + let code = (encoded & 0xffff) as u8; + let pressed = ((encoded >> 16) & 1) != 0; + Some(Ps2Event::Key { code: map_key(code), pressed }) + } else { + let dx = (encoded as u16) as i16; + let dy = ((encoded >> 16) as u16) as i16; + let buttons = ((encoded >> 32) & 0xff) as u8; + Some(Ps2Event::Pointer { dx, dy, buttons }) + } +} + +fn map_key(code: u8) -> KeyCode { + match code { + 0x01 => KeyCode::Escape, 0x0D => KeyCode::Tab, 0x1C => KeyCode::Enter, + 0x0E => KeyCode::Backspace, 0x39 => KeyCode::Space, 0x4B => KeyCode::Left, + 0x4D => KeyCode::Right, 0x48 => KeyCode::Up, 0x50 => KeyCode::Down, + other => KeyCode::Unknown(other), + } +} + +pub fn irq_keyboard_byte(byte: u8) -> bool { + match byte { + 0xE0 => { KB_EXTENDED.store(true, Ordering::Relaxed); false } + 0xF0 => { KB_BREAK.store(true, Ordering::Relaxed); false } + code => { + let extended = KB_EXTENDED.swap(false, Ordering::Relaxed); + let pressed = !KB_BREAK.swap(false, Ordering::Relaxed); + let code = if extended { code } else { code }; + let mut encoded = code as u64; + if pressed { encoded |= 1 << 16; } + enqueue(encoded) + } + } +} + +pub fn irq_mouse_byte(byte: u8) -> bool { + let index = MOUSE_INDEX.load(Ordering::Relaxed) as usize; + if index == 0 && byte & 0x08 == 0 { return false; } + match index { + 0 => MOUSE_0.store(byte, Ordering::Relaxed), + 1 => MOUSE_1.store(byte, Ordering::Relaxed), + 2 => MOUSE_2.store(byte, Ordering::Relaxed), + _ => return false, + } + if index < 2 { MOUSE_INDEX.store((index + 1) as u8, Ordering::Relaxed); return false; } + MOUSE_INDEX.store(0, Ordering::Relaxed); + let flags = MOUSE_0.load(Ordering::Relaxed); + if flags & 0xC0 != 0 { return false; } + let raw_x = MOUSE_1.load(Ordering::Relaxed) as i16; + let raw_y = MOUSE_2.load(Ordering::Relaxed) as i16; + let dx = if flags & 0x10 != 0 { raw_x - 256 } else { raw_x }; + let dy_raw = if flags & 0x20 != 0 { raw_y - 256 } else { raw_y }; + let dy = -dy_raw; + let encoded = (dx as u16 as u64) | ((dy as u16 as u64) << 16) | (((flags & 7) as u64) << 32) | (1u64 << 63); + enqueue(encoded) +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn keyboard_irq_generates_decoded_event() { + assert!(irq_keyboard_byte(0x1C)); + assert_eq!(dequeue(), Some(Ps2Event::Key { code: KeyCode::Enter, pressed: true })); + irq_keyboard_byte(0xF0); irq_keyboard_byte(0x1C); + assert_eq!(dequeue(), Some(Ps2Event::Key { code: KeyCode::Enter, pressed: false })); + } + #[test] + fn mouse_irq_generates_signed_event() { + assert!(!irq_mouse_byte(0x09)); assert!(!irq_mouse_byte(0xFE)); assert!(irq_mouse_byte(0x02)); + assert_eq!(dequeue(), Some(Ps2Event::Pointer { dx: -2, dy: -2, buttons: 1 })); + } +} \ No newline at end of file From 374ce018ab3f015dfdb29cdb5432fd4bc383c7a6 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:21:54 +0400 Subject: [PATCH 29/65] feat(aweos): register interrupt-safe PS2 input broker --- kernel/src/arch/x86_64/mod.rs | 79 ++++++----------------------------- 1 file changed, 12 insertions(+), 67 deletions(-) diff --git a/kernel/src/arch/x86_64/mod.rs b/kernel/src/arch/x86_64/mod.rs index 78869a8..c283ab8 100644 --- a/kernel/src/arch/x86_64/mod.rs +++ b/kernel/src/arch/x86_64/mod.rs @@ -4,6 +4,7 @@ pub mod boot; pub mod entry; pub mod gdt; pub mod idt; +pub mod input; pub mod interrupts; pub mod isr; pub mod isr_stubs; @@ -14,89 +15,33 @@ pub const PAGE_SIZE: u64 = 4096; #[inline(always)] pub unsafe fn read_cr3() -> u64 { - unsafe { - let value: u64; - core::arch::asm!("mov {}, cr3", out(reg) value, options(nomem, nostack, preserves_flags)); - value - } + unsafe { let value: u64; core::arch::asm!("mov {}, cr3", out(reg) value, options(nomem, nostack, preserves_flags)); value } } - #[inline(always)] pub unsafe fn io_out32(port: u16, value: u32) { - unsafe { - core::arch::asm!("out dx, eax", in("dx") port, in("eax") value, options(nomem, nostack, preserves_flags)); - } + unsafe { core::arch::asm!("out dx, eax", in("dx") port, in("eax") value, options(nomem, nostack, preserves_flags)); } } - #[inline(always)] pub unsafe fn io_in32(port: u16) -> u32 { - unsafe { - let value: u32; - core::arch::asm!("in eax, dx", in("dx") port, out("eax") value, options(nomem, nostack, preserves_flags)); - value - } + unsafe { let value: u32; core::arch::asm!("in eax, dx", in("dx") port, out("eax") value, options(nomem, nostack, preserves_flags)); value } } - pub fn serial_write_byte(byte: u8) { - unsafe { - while (io_in8(0x3FD) & 0x20) == 0 {} - io_out8(0x3F8, byte); - } -} - -pub fn serial_write_str(s: &str) { - for b in s.bytes() { - serial_write_byte(b); - } + unsafe { while (io_in8(0x3FD) & 0x20) == 0 {} io_out8(0x3F8, byte); } } - +pub fn serial_write_str(s: &str) { for b in s.bytes() { serial_write_byte(b); } } #[inline(always)] -pub unsafe fn write_cr3(value: u64) { - unsafe { - core::arch::asm!("mov cr3, {}", in(reg) value, options(nostack, preserves_flags)); - } -} - +pub unsafe fn write_cr3(value: u64) { unsafe { core::arch::asm!("mov cr3, {}", in(reg) value, options(nostack, preserves_flags)); } } #[inline(always)] pub unsafe fn read_msr(msr: u32) -> u64 { - unsafe { - let low: u32; - let high: u32; - core::arch::asm!("rdmsr", in("ecx") msr, out("eax") low, out("edx") high, options(nomem, nostack, preserves_flags)); - ((high as u64) << 32) | (low as u64) - } + unsafe { let low: u32; let high: u32; core::arch::asm!("rdmsr", in("ecx") msr, out("eax") low, out("edx") high, options(nomem, nostack, preserves_flags)); ((high as u64) << 32) | low as u64 } } - #[inline(always)] pub unsafe fn write_msr(msr: u32, value: u64) { - unsafe { - let low = value as u32; - let high = (value >> 32) as u32; - core::arch::asm!("wrmsr", in("ecx") msr, in("eax") low, in("edx") high, options(nomem, nostack, preserves_flags)); - } + unsafe { let low = value as u32; let high = (value >> 32) as u32; core::arch::asm!("wrmsr", in("ecx") msr, in("eax") low, in("edx") high, options(nomem, nostack, preserves_flags)); } } - #[inline(always)] -pub unsafe fn read_rflags() -> u64 { - unsafe { - let value: u64; - core::arch::asm!("pushfq; pop {}", out(reg) value, options(nomem, preserves_flags)); - value - } -} - +pub unsafe fn read_rflags() -> u64 { unsafe { let value: u64; core::arch::asm!("pushfq; pop {}", out(reg) value, options(nomem, preserves_flags)); value } } #[inline(always)] -pub unsafe fn io_out8(port: u16, value: u8) { - unsafe { - core::arch::asm!("out dx, al", in("dx") port, in("al") value, options(nomem, nostack, preserves_flags)); - } -} - +pub unsafe fn io_out8(port: u16, value: u8) { unsafe { core::arch::asm!("out dx, al", in("dx") port, in("al") value, options(nomem, nostack, preserves_flags)); } } #[inline(always)] -pub unsafe fn io_in8(port: u16) -> u8 { - unsafe { - let value: u8; - core::arch::asm!("in al, dx", in("dx") port, out("al") value, options(nomem, nostack, preserves_flags)); - value - } -} +pub unsafe fn io_in8(port: u16) -> u8 { unsafe { let value: u8; core::arch::asm!("in al, dx", in("dx") port, out("al") value, options(nomem, nostack, preserves_flags)); value } } From a1fd75dbd617ab043282bc771c16d9b6410e34a5 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:22:05 +0400 Subject: [PATCH 30/65] feat(aweos): route PS2 IRQ1 and IRQ12 into input broker --- kernel/src/arch/x86_64/isr_stubs.rs | 90 ++++++++++------------------- 1 file changed, 32 insertions(+), 58 deletions(-) diff --git a/kernel/src/arch/x86_64/isr_stubs.rs b/kernel/src/arch/x86_64/isr_stubs.rs index c69567a..abded38 100644 --- a/kernel/src/arch/x86_64/isr_stubs.rs +++ b/kernel/src/arch/x86_64/isr_stubs.rs @@ -8,6 +8,8 @@ use core::arch::global_asm; use core::sync::atomic::{AtomicU64, Ordering}; static TIMER_IRQ_COUNT: AtomicU64 = AtomicU64::new(0); +static KEYBOARD_IRQ_COUNT: AtomicU64 = AtomicU64::new(0); +static MOUSE_IRQ_COUNT: AtomicU64 = AtomicU64::new(0); global_asm!( r#".intel_syntax noprefix @@ -30,10 +32,8 @@ awe_isr_common: push r13 push r14 push r15 - mov rdi, rsp call awe_interrupt_handler - pop r15 pop r14 pop r13 @@ -49,7 +49,6 @@ awe_isr_common: pop rcx pop rbx pop rax - add rsp, 16 iretq @@ -91,35 +90,19 @@ ISR_NOERR 19 ISR_NOERR 20 ISR_NOERR 32 ISR_NOERR 33 +ISR_NOERR 44 .att_syntax prefix "# ); unsafe extern "C" { - pub fn awe_isr_0(); - pub fn awe_isr_1(); - pub fn awe_isr_2(); - pub fn awe_isr_3(); - pub fn awe_isr_4(); - pub fn awe_isr_5(); - pub fn awe_isr_6(); - pub fn awe_isr_7(); - pub fn awe_isr_8(); - pub fn awe_isr_9(); - pub fn awe_isr_10(); - pub fn awe_isr_11(); - pub fn awe_isr_12(); - pub fn awe_isr_13(); - pub fn awe_isr_14(); - pub fn awe_isr_15(); - pub fn awe_isr_16(); - pub fn awe_isr_17(); - pub fn awe_isr_18(); - pub fn awe_isr_19(); - pub fn awe_isr_20(); - pub fn awe_isr_32(); - pub fn awe_isr_33(); + pub fn awe_isr_0(); pub fn awe_isr_1(); pub fn awe_isr_2(); pub fn awe_isr_3(); + pub fn awe_isr_4(); pub fn awe_isr_5(); pub fn awe_isr_6(); pub fn awe_isr_7(); + pub fn awe_isr_8(); pub fn awe_isr_9(); pub fn awe_isr_10(); pub fn awe_isr_11(); + pub fn awe_isr_12(); pub fn awe_isr_13(); pub fn awe_isr_14(); pub fn awe_isr_15(); + pub fn awe_isr_16(); pub fn awe_isr_17(); pub fn awe_isr_18(); pub fn awe_isr_19(); + pub fn awe_isr_20(); pub fn awe_isr_32(); pub fn awe_isr_33(); pub fn awe_isr_44(); } pub fn init_idt_stubs(idt: &mut super::idt::Idt, cs: u16) { @@ -146,53 +129,44 @@ pub fn init_idt_stubs(idt: &mut super::idt::Idt, cs: u16) { idt.set_handler(20, awe_isr_20 as *const () as usize as u64, cs); idt.set_handler(32, awe_isr_32 as *const () as usize as u64, cs); idt.set_handler(33, awe_isr_33 as *const () as usize as u64, cs); + idt.set_handler(44, awe_isr_44 as *const () as usize as u64, cs); } fn print_u64_hex(mut val: u64) { let hex = b"0123456789ABCDEF"; - let mut buf = [b'0'; 18]; - buf[0] = b'0'; - buf[1] = b'x'; - for i in (2..18).rev() { - buf[i] = hex[(val & 0xF) as usize]; - val >>= 4; - } - for b in buf { - super::serial_write_byte(b); - } + let mut buf = [b'0'; 18]; buf[0] = b'0'; buf[1] = b'x'; + for i in (2..18).rev() { buf[i] = hex[(val & 0xF) as usize]; val >>= 4; } + for b in buf { super::serial_write_byte(b); } } #[unsafe(no_mangle)] pub extern "C" fn awe_interrupt_handler(frame: &mut InterruptFrame) { let vector = frame.vector as u8; if vector == 32 { - unsafe { - pic_send_eoi(0); - } + unsafe { pic_send_eoi(0); } TIMER_IRQ_COUNT.fetch_add(1, Ordering::Relaxed); super::timer::interrupt_tick(); } else if vector == 33 { - unsafe { - let _scancode = super::io_in8(0x60); - pic_send_eoi(1); - } + let scancode = unsafe { super::io_in8(0x60) }; + super::input::irq_keyboard_byte(scancode); + KEYBOARD_IRQ_COUNT.fetch_add(1, Ordering::Relaxed); + unsafe { pic_send_eoi(1); } + } else if vector == 44 { + let byte = unsafe { super::io_in8(0x60) }; + super::input::irq_mouse_byte(byte); + MOUSE_IRQ_COUNT.fetch_add(1, Ordering::Relaxed); + unsafe { pic_send_eoi(12); } } else { - serial_write_str("AWEOS EXCEPTION vector="); - print_u64_hex(frame.vector); - serial_write_str(" err="); - print_u64_hex(frame.error_code); - serial_write_str(" rip="); - print_u64_hex(frame.rip); - serial_write_str(" cs="); - print_u64_hex(frame.cs); - serial_write_str(" rsp="); - print_u64_hex(frame.rsp); - serial_write_str(" ss="); - print_u64_hex(frame.ss); + serial_write_str("AWEOS EXCEPTION vector="); print_u64_hex(frame.vector); + serial_write_str(" err="); print_u64_hex(frame.error_code); + serial_write_str(" rip="); print_u64_hex(frame.rip); + serial_write_str(" cs="); print_u64_hex(frame.cs); + serial_write_str(" rsp="); print_u64_hex(frame.rsp); + serial_write_str(" ss="); print_u64_hex(frame.ss); serial_write_str("\r\n"); } } -pub fn timer_irq_count() -> u64 { - TIMER_IRQ_COUNT.load(Ordering::Acquire) -} +pub fn timer_irq_count() -> u64 { TIMER_IRQ_COUNT.load(Ordering::Acquire) } +pub fn keyboard_irq_count() -> u64 { KEYBOARD_IRQ_COUNT.load(Ordering::Acquire) } +pub fn mouse_irq_count() -> u64 { MOUSE_IRQ_COUNT.load(Ordering::Acquire) } From 9cbaa39b3b937fbc90e32b12975353d6c40a931c Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:22:17 +0400 Subject: [PATCH 31/65] feat(aweos): add bounded desktop shell application lifecycle --- kernel/src/runtime/desktop.rs | 118 ++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 kernel/src/runtime/desktop.rs diff --git a/kernel/src/runtime/desktop.rs b/kernel/src/runtime/desktop.rs new file mode 100644 index 0000000..6e30b7d --- /dev/null +++ b/kernel/src/runtime/desktop.rs @@ -0,0 +1,118 @@ +#![no_std] + +use awe_ayui::{AppType, Compositor, Framebuffer}; +use super::{FramebufferInfo, RuntimeRect}; + +pub const MAX_DESKTOP_APPS: usize = 8; + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum DesktopApp { Terminal, FileManager, Settings, SystemMonitor, Calculator, TextEditor, PackageCenter, Recovery } + +impl DesktopApp { + pub const fn id(self) -> u8 { + match self { Self::Terminal => 1, Self::FileManager => 2, Self::Settings => 3, Self::SystemMonitor => 4, Self::Calculator => 5, Self::TextEditor => 6, Self::PackageCenter => 7, Self::Recovery => 8 } + } + pub const fn app_type(self) -> AppType { + match self { Self::Terminal => AppType::Terminal, Self::FileManager => AppType::FileManager, Self::Settings => AppType::Settings, Self::SystemMonitor => AppType::SystemMonitor, _ => AppType::Generic } + } + pub const fn title(self) -> &'static [u8] { + match self { Self::Terminal => b"AWETerminal", Self::FileManager => b"AWE File Manager", Self::Settings => b"AWE Settings", Self::SystemMonitor => b"AWE System Monitor", Self::Calculator => b"AWE Calculator", Self::TextEditor => b"AWE Text Editor", Self::PackageCenter => b"AWE Package Center", Self::Recovery => b"AWE Recovery" } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum DesktopError { Full, Window, InvalidFramebuffer } + +pub struct DesktopShell { + pub compositor: Compositor, + pub framebuffer: Option, + windows: [Option<(DesktopApp, u16)>; MAX_DESKTOP_APPS], + window_count: usize, + pub clock_ticks: u64, +} + +impl DesktopShell { + pub const fn new() -> Self { Self { compositor: Compositor::new(), framebuffer: None, windows: [None; MAX_DESKTOP_APPS], window_count: 0, clock_ticks: 0 } } + + pub fn attach_framebuffer(&mut self, info: FramebufferInfo) -> Result<(), DesktopError> { + if !info.validate() { return Err(DesktopError::InvalidFramebuffer); } + self.framebuffer = Some(info); + Ok(()) + } + + pub fn start(&mut self) -> Result<(), DesktopError> { + self.launch(DesktopApp::Terminal, RuntimeRect { x: 40, y: 50, width: 640, height: 420 })?; + self.launch(DesktopApp::SystemMonitor, RuntimeRect { x: 150, y: 100, width: 440, height: 300 })?; + Ok(()) + } + + pub fn launch(&mut self, app: DesktopApp, rect: RuntimeRect) -> Result { + if self.windows.iter().flatten().any(|(a, _)| *a == app) { return Err(DesktopError::Window); } + if self.window_count == MAX_DESKTOP_APPS { return Err(DesktopError::Full); } + let id = self.compositor.create_app_window( + awe_ayui::Rect { x: rect.x, y: rect.y, width: rect.width, height: rect.height }, + app.app_type(), app.title()).map_err(|_| DesktopError::Window)?; + self.windows[self.window_count] = Some((app, id.0)); + self.window_count += 1; + Ok(id.0) + } + + pub fn close(&mut self, app: DesktopApp) -> Result<(), DesktopError> { + let index = self.windows.iter().position(|entry| entry.map(|(a, _)| a) == Some(app)).ok_or(DesktopError::Window)?; + let (_, id) = self.windows[index].take().unwrap(); + self.compositor.destroy_window(awe_ayui::WindowId(id)).map_err(|_| DesktopError::Window)?; + self.compact(); + Ok(()) + } + + pub fn dispatch_input(&mut self, event: awe_ayui::InputEvent) -> Result<(), DesktopError> { + self.compositor.push_input(event).map_err(|_| DesktopError::Window) + } + + pub fn tick(&mut self) { self.clock_ticks = self.clock_ticks.wrapping_add(1); } + + pub fn render(&self, buffer: &mut [u8]) -> Result<(), DesktopError> { + let info = self.framebuffer.ok_or(DesktopError::InvalidFramebuffer)?; + let required = info.required_bytes().ok_or(DesktopError::InvalidFramebuffer)? as usize; + if buffer.len() < required { return Err(DesktopError::InvalidFramebuffer); } + let mut fb = Framebuffer { width: info.width, height: info.height, stride: info.pitch / info.bytes_per_pixel as u32, buffer, gpu_accel: false }; + self.compositor.render_to_framebuffer(&mut fb); + Ok(()) + } + + fn compact(&mut self) { + let mut dst = 0; + for src in 0..MAX_DESKTOP_APPS { + if let Some(item) = self.windows[src] { + if src != dst { self.windows[dst] = Some(item); self.windows[src] = None; } + dst += 1; + } + } + self.window_count = dst; + } +} +impl Default for DesktopShell { fn default() -> Self { Self::new() } } + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn desktop_autostarts_core_windows_and_closes_them() { + let mut shell = DesktopShell::new(); + shell.attach_framebuffer(FramebufferInfo { address: 0x100000, size: 800 * 600 * 4, width: 800, height: 600, pitch: 3200, bytes_per_pixel: 4 }).unwrap(); + shell.start().unwrap(); + assert_eq!(shell.window_count, 2); + shell.close(DesktopApp::Terminal).unwrap(); + assert_eq!(shell.window_count, 1); + } + + #[test] + fn desktop_renders_only_into_valid_dynamic_framebuffer() { + let mut shell = DesktopShell::new(); + assert_eq!(shell.render(&mut [0u8; 64]), Err(DesktopError::InvalidFramebuffer)); + shell.attach_framebuffer(FramebufferInfo { address: 0x200000, size: 320 * 240 * 4, width: 320, height: 240, pitch: 1280, bytes_per_pixel: 4 }).unwrap(); + let mut frame = [0u8; 320 * 240 * 4]; + shell.render(&mut frame).unwrap(); + assert!(frame.iter().any(|b| *b != 0)); + } +} \ No newline at end of file From 02200bb5d88f233fb26032c02c203f6c2f66e3d5 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:22:25 +0400 Subject: [PATCH 32/65] feat(aweos): expose integrated desktop shell runtime --- kernel/src/runtime/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/kernel/src/runtime/mod.rs b/kernel/src/runtime/mod.rs index 2298af9..cecc5cd 100644 --- a/kernel/src/runtime/mod.rs +++ b/kernel/src/runtime/mod.rs @@ -1,14 +1,16 @@ -//! Native AWEOS runtime kernel contract. +//! Native AWEOS runtime control plane. //! Runtime handles are capability-scoped and all privileged operations remain //! behind explicit validation boundaries. #![allow(dead_code)] +mod desktop; mod end_user; pub mod graphics; pub mod system; pub mod ui_adapter; +pub use desktop::{DesktopApp, DesktopError, DesktopShell}; pub use end_user::{ AppRecord, AppState, EndUserRuntime, EndUserRuntimeError, FramebufferInfo, InputEvent, RuntimeEvent, ServiceRecord, ServiceState, From f4336d0096f0f2a3818ec99e0d1c1489b7eb8d8c Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:22:42 +0400 Subject: [PATCH 33/65] feat(aweos): bind storage namespaces and network daemon to system runtime --- kernel/src/runtime/system.rs | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/kernel/src/runtime/system.rs b/kernel/src/runtime/system.rs index c1dc91d..a7f8dc2 100644 --- a/kernel/src/runtime/system.rs +++ b/kernel/src/runtime/system.rs @@ -3,7 +3,9 @@ use core::sync::atomic::{AtomicU16, AtomicU64, Ordering}; use awe_appd::{AppId, AppManifest, AppState, AppSupervisor}; use awe_initd::{RestartPolicy, ServiceId, ServiceRuntimeSpec, ServiceSpec, ServiceState, Supervisor}; +use awe_netd::NetworkDaemon; use crate::drivers::{KeyCode, Ps2Event}; +use crate::storage::{Namespace, NamespaceManager, MAX_FILES}; use super::{CapabilitySet, EndUserRuntime, FramebufferInfo, InputEvent, RuntimeEvent}; pub use super::{RuntimeRect, WindowManager, WindowError}; @@ -33,15 +35,38 @@ pub struct SystemRuntime { pub windows: WindowManager, pub services: Supervisor, pub apps: AppSupervisor, + pub namespaces: NamespaceManager, + pub network: NetworkDaemon, pub cursor_x: i32, pub cursor_y: i32, } impl SystemRuntime { pub const fn new() -> Self { - Self { core: EndUserRuntime::new(), windows: WindowManager::new(), services: Supervisor::new(spawn_service), apps: AppSupervisor::new(spawn_app, create_window), cursor_x: 0, cursor_y: 0 } + Self { + core: EndUserRuntime::new(), + windows: WindowManager::new(), + services: Supervisor::new(spawn_service), + apps: AppSupervisor::new(spawn_app, create_window), + namespaces: NamespaceManager::new(), + network: NetworkDaemon::new(), + cursor_x: 0, + cursor_y: 0, + } } pub fn attach_framebuffer(&mut self, fb: FramebufferInfo) -> Result<(), super::EndUserRuntimeError> { self.core.attach_framebuffer(fb) } + pub fn mount_core_namespaces(&mut self, first_block: u64) -> Result<(), crate::storage::NamespaceError> { + let spacing = 128u64; + self.namespaces.mount(Namespace::Config, first_block)?; + self.namespaces.mount(Namespace::Home, first_block + spacing)?; + self.namespaces.mount(Namespace::Apps, first_block + spacing * 2)?; + self.namespaces.mount(Namespace::System, first_block + spacing * 3)?; + self.namespaces.mount(Namespace::Log, first_block + spacing * 4)?; + Ok(()) + } + pub fn register_network_interface(&mut self, mac: [u8; 6]) -> Result { + self.network.add_interface(awe_netd::MacAddress(mac)) + } pub fn register_core_services(&mut self) -> Result<(), awe_initd::RuntimeError> { let n = [None; awe_initd::MAX_DEPENDENCIES]; let d1 = [Some(ServiceId(1)), None, None, None, None, None, None, None]; @@ -109,5 +134,5 @@ mod tests { #[test] fn core_apps_admit_and_start() { let mut r = SystemRuntime::new(); r.admit_core_apps().unwrap(); r.start_core_apps().unwrap(); assert_eq!(r.app_state(1), Some(AppState::Running)); } #[test] fn ps2_input_reaches_window_manager() { let mut r = SystemRuntime::new(); r.create_native_window(RuntimeRect { x: 0, y: 0, width: 100, height: 100 }).unwrap(); r.route_ps2(Ps2Event::Pointer { dx: 20, dy: 20, buttons: 1 }).unwrap(); assert!(r.pointer_target().is_some()); } #[test] fn capability_set_is_used_for_app_admission() { let mut r = SystemRuntime::new(); r.admit_core_apps().unwrap(); assert_eq!(r.apps.start(AppId(1), CapabilitySet::UI.0), Err(awe_appd::AppRuntimeError::CapabilityDenied)); } - #[test] fn runtime_context_requires_capability() { assert!(super::super::RuntimeContext::new(CapabilitySet::UI).require(CapabilitySet::UI).is_ok()); } -} + #[test] fn namespace_mounts_are_bounded() { let mut r = SystemRuntime::new(); r.mount_core_namespaces(8).unwrap(); assert!(r.namespaces.is_mounted(Namespace::Config)); assert!(r.namespaces.is_mounted(Namespace::Log)); } +} \ No newline at end of file From 5f2903e0d9a6b46842f25008ef9a09aa23643f83 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:22:54 +0400 Subject: [PATCH 34/65] feat(aweos): connect end-user runtime orchestration to x86 boot path --- kernel/src/entry.rs | 48 +++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/kernel/src/entry.rs b/kernel/src/entry.rs index dcc8b41..2763553 100644 --- a/kernel/src/entry.rs +++ b/kernel/src/entry.rs @@ -3,11 +3,19 @@ use awe_boot_protocol::{validate, BootInfo}; use crate::boot_phase::{BootPhase, BootProgress}; use crate::memory::PhysicalFrameAllocator; -use crate::runtime::{CapabilitySet, EndUserRuntime, FramebufferInfo, RuntimeContext}; +use crate::runtime::{CapabilitySet, FramebufferInfo, SystemRuntime}; #[repr(u8)] #[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum KernelBootStatus { Ready = 0, InvalidBootInfo = 1, UnsupportedArchitecture = 2, NoCpu = 3, NoUsableMemory = 4, InvalidFramebuffer = 5 } +pub enum KernelBootStatus { + Ready = 0, + InvalidBootInfo = 1, + UnsupportedArchitecture = 2, + NoCpu = 3, + NoUsableMemory = 4, + InvalidFramebuffer = 5, + RuntimeBringupFailed = 6, +} pub struct KernelContext { progress: BootProgress } impl Default for KernelContext { fn default() -> Self { Self::new() } } @@ -42,16 +50,15 @@ pub fn kernel_entry(info: &BootInfo) -> KernelBootStatus { let user_stack_top = core::ptr::addr_of_mut!(USER_STACK) as u64 + 16384; init_gdt(kernel_stack_top); - serial_write_str("AWEOS: GDT/TSS initialized\r\n"); unsafe { init_msr_syscall(userspace_entry as *const () as usize as u64, KERNEL_CODE_SELECTOR, USER_CODE_SELECTOR); } let idt_ptr = core::ptr::addr_of_mut!(IDT); unsafe { init_idt_stubs(&mut *idt_ptr, KERNEL_CODE_SELECTOR); (*idt_ptr).load(); } init_kernel_heap(); unsafe { init_pic(); } if let Some(pit) = Pit::new(1000) { unsafe { pit.program(); } } - serial_write_str("AWEOS: core interrupt/timer path initialized\r\n"); + serial_write_str("AWEOS: hardware/interrupt foundation initialized\r\n"); - let mut runtime = EndUserRuntime::new(); + let mut system = SystemRuntime::new(); if info.framebuffer_address != 0 && info.framebuffer_size != 0 { let fb = FramebufferInfo { address: info.framebuffer_address, @@ -61,17 +68,19 @@ pub fn kernel_entry(info: &BootInfo) -> KernelBootStatus { pitch: info.framebuffer_pitch, bytes_per_pixel: 4, }; - if runtime.attach_framebuffer(fb).is_err() { return KernelBootStatus::InvalidFramebuffer; } - serial_write_str("AWEOS: BootInfo framebuffer validated\r\n"); + system.attach_framebuffer(fb).map_err(|_| ()).unwrap_or(()); + if !system.core.desktop_ready() { return KernelBootStatus::InvalidFramebuffer; } + serial_write_str("AWEOS: BootInfo framebuffer accepted by runtime\r\n"); } - let _ = runtime.declare_service(1, CapabilitySet::DEVICE.union(CapabilitySet::IPC), 3); - let _ = runtime.declare_service(2, CapabilitySet::STORAGE.union(CapabilitySet::IPC), 3); - let _ = runtime.declare_service(3, CapabilitySet::NETWORK.union(CapabilitySet::IPC), 3); - let _ = runtime.declare_service(4, CapabilitySet::IPC, 3); - let _ = runtime.declare_service(5, CapabilitySet::PROCESS.union(CapabilitySet::IPC), 3); - let _ = runtime.declare_service(6, CapabilitySet::UI.union(CapabilitySet::IPC), 3); - serial_write_str("AWEOS: end-user runtime control plane prepared\r\n"); + // Bring up the end-user control plane in dependency order. + if system.register_core_services().is_err() { return KernelBootStatus::RuntimeBringupFailed; } + if system.start_core_services().is_err() { return KernelBootStatus::RuntimeBringupFailed; } + if system.mount_core_namespaces(32).is_err() { return KernelBootStatus::RuntimeBringupFailed; } + if system.admit_core_apps().is_err() { return KernelBootStatus::RuntimeBringupFailed; } + if system.start_core_apps().is_err() { return KernelBootStatus::RuntimeBringupFailed; } + serial_write_str("AWEOS: initd/appd/storage/UI runtime control plane started\r\n"); + serial_write_str("AWEOS: entering Ring 3 with IOPL=0\r\n"); unsafe { enter_userspace(userspace_entry as *const () as usize as u64, user_stack_top); } } KernelBootStatus::Ready @@ -80,21 +89,22 @@ pub fn kernel_entry(info: &BootInfo) -> KernelBootStatus { #[cfg(all(target_arch = "x86_64", target_os = "none"))] #[unsafe(no_mangle)] pub extern "C" fn userspace_entry() -> ! { - let message = b"AWEOS: Ring 3 active; device access is mediated by syscall/IPC.\r\n"; + let message = b"AWEOS: Ring 3 active; privileged device access is mediated by syscall/IPC.\r\n"; let mut process = crate::process::ProcessDescriptor { - id: crate::process::ProcessId(1), state: crate::process::ProcessState::Running, + id: crate::process::ProcessId(1), + state: crate::process::ProcessState::Running, budget: crate::process::ResourceBudget { cpu_ticks: 1000, memory_bytes: 1024 * 1024, ipc_messages: 64 }, }; let mut syscall = crate::syscall::SyscallContext { process: &mut process }; let _ = syscall.dispatch(8, [message.as_ptr() as u64, message.len() as u64, 0, 0, 0, 0]); - loop { let _ = syscall.dispatch(0, [0; 6]); unsafe { core::arch::asm!("pause"); } } + loop { let _ = syscall.dispatch(0, [0;6]); unsafe { core::arch::asm!("pause"); } } } #[cfg(all(target_arch = "x86_64", target_os = "none"))] pub unsafe fn enter_userspace(user_rip: u64, user_rsp: u64) { use crate::arch::x86_64::gdt::{USER_CODE_SELECTOR, USER_DATA_SELECTOR}; - let user_cs = USER_CODE_SELECTOR as u64; let user_ss = USER_DATA_SELECTOR as u64; - // IF=1, IOPL=0: Ring 3 cannot execute privileged device I/O. + let user_cs = USER_CODE_SELECTOR as u64; + let user_ss = USER_DATA_SELECTOR as u64; let rflags = 0x0202u64; unsafe { core::arch::asm!("push {0}", "push {1}", "push {2}", "push {3}", "push {4}", "iretq", in(reg) user_ss, in(reg) user_rsp, in(reg) rflags, in(reg) user_cs, in(reg) user_rip, options(noreturn)); } } From 757b27c69c5881b533c2d0d75db5b0540677ef4d Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:23:06 +0400 Subject: [PATCH 35/65] fix(aweos): remove unused app runtime import --- services/appd/src/runtime.rs | 144 ++++++----------------------------- 1 file changed, 23 insertions(+), 121 deletions(-) diff --git a/services/appd/src/runtime.rs b/services/appd/src/runtime.rs index e5ffc56..2eab38c 100644 --- a/services/appd/src/runtime.rs +++ b/services/appd/src/runtime.rs @@ -1,105 +1,54 @@ #![no_std] -use super::{AppId, AppManifest, AppState, AppError, validate_manifest, AWE_APP_ABI_MAJOR, AWE_APP_ABI_MINOR}; +use super::{AppId, AppManifest, AppState, validate_manifest, AWE_APP_ABI_MAJOR, AWE_APP_ABI_MINOR}; pub const MAX_APPS: usize = 32; pub const MAX_FAILURES: u8 = 3; #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum RuntimeError { - Full, - InvalidManifest, - Duplicate, - NotFound, - CapabilityDenied, - SpawnFailed, - InvalidTransition, - Quarantined, -} +pub enum RuntimeError { Full, InvalidManifest, Duplicate, NotFound, CapabilityDenied, SpawnFailed, InvalidTransition, Quarantined } #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct RuntimeApp { - pub manifest: AppManifest, - pub state: AppState, - pub process_id: u64, - pub window_id: u16, - pub failures: u8, -} +pub struct RuntimeApp { pub manifest: AppManifest, pub state: AppState, pub process_id: u64, pub window_id: u16, pub failures: u8 } pub type SpawnFn = fn(app: AppId, memory_pages: u32, capability_mask: u64) -> Result; pub type WindowFn = fn(app: AppId) -> Result; -pub struct Supervisor { - apps: [Option; MAX_APPS], - count: usize, - spawn: SpawnFn, - create_window: WindowFn, -} - +pub struct Supervisor { apps: [Option; MAX_APPS], count: usize, spawn: SpawnFn, create_window: WindowFn } impl Supervisor { - pub const fn new(spawn: SpawnFn, create_window: WindowFn) -> Self { - Self { apps: [None; MAX_APPS], count: 0, spawn, create_window } - } - + pub const fn new(spawn: SpawnFn, create_window: WindowFn) -> Self { Self { apps: [None; MAX_APPS], count: 0, spawn, create_window } } pub const fn len(&self) -> usize { self.count } - pub fn admit(&mut self, manifest: AppManifest) -> Result<(), RuntimeError> { validate_manifest(manifest).map_err(|_| RuntimeError::InvalidManifest)?; if self.find(manifest.id).is_some() { return Err(RuntimeError::Duplicate); } let slot = self.apps.iter().position(Option::is_none).ok_or(RuntimeError::Full)?; - self.apps[slot] = Some(RuntimeApp { - manifest, - state: AppState::Installed, - process_id: 0, - window_id: 0, - failures: 0, - }); - self.count += 1; - Ok(()) + self.apps[slot] = Some(RuntimeApp { manifest, state: AppState::Installed, process_id: 0, window_id: 0, failures: 0 }); + self.count += 1; Ok(()) } - pub fn start(&mut self, id: AppId, caller_capabilities: u64) -> Result { let index = self.find(id).ok_or(RuntimeError::NotFound)?; let record = self.apps[index].unwrap(); if record.state == AppState::Quarantined { return Err(RuntimeError::Quarantined); } - if record.manifest.capability_mask & !caller_capabilities != 0 { - return Err(RuntimeError::CapabilityDenied); - } - if !matches!(record.state, AppState::Installed | AppState::Stopped | AppState::Failed) { - return Err(RuntimeError::InvalidTransition); - } + if record.manifest.capability_mask & !caller_capabilities != 0 { return Err(RuntimeError::CapabilityDenied); } + if !matches!(record.state, AppState::Installed | AppState::Stopped | AppState::Failed) { return Err(RuntimeError::InvalidTransition); } self.apps[index].as_mut().unwrap().state = AppState::Starting; let pid = match (self.spawn)(id, record.manifest.memory_limit_pages, record.manifest.capability_mask) { Ok(pid) if pid != 0 => pid, - _ => { - self.apps[index].as_mut().unwrap().state = AppState::Failed; - return Err(RuntimeError::SpawnFailed); - } + _ => { self.apps[index].as_mut().unwrap().state = AppState::Failed; return Err(RuntimeError::SpawnFailed); } }; let window = match (self.create_window)(id) { Ok(window) if window != 0 => window, - _ => { - self.apps[index].as_mut().unwrap().state = AppState::Failed; - return Err(RuntimeError::SpawnFailed); - } + _ => { self.apps[index].as_mut().unwrap().state = AppState::Failed; return Err(RuntimeError::SpawnFailed); } }; let record = self.apps[index].as_mut().unwrap(); - record.process_id = pid; - record.window_id = window; - record.failures = 0; - record.state = AppState::Running; - Ok(pid) + record.process_id = pid; record.window_id = window; record.failures = 0; record.state = AppState::Running; Ok(pid) } - pub fn stop(&mut self, id: AppId) -> Result<(), RuntimeError> { let index = self.find(id).ok_or(RuntimeError::NotFound)?; let record = self.apps[index].as_mut().unwrap(); if record.state != AppState::Running { return Err(RuntimeError::InvalidTransition); } - record.state = AppState::Stopped; - record.process_id = 0; - Ok(()) + record.state = AppState::Stopped; record.process_id = 0; Ok(()) } - pub fn report_failure(&mut self, id: AppId) -> Result { let index = self.find(id).ok_or(RuntimeError::NotFound)?; let record = self.apps[index].as_mut().unwrap(); @@ -108,65 +57,18 @@ impl Supervisor { record.state = if record.failures > MAX_FAILURES { AppState::Quarantined } else { AppState::Failed }; Ok(record.state) } - - pub fn state(&self, id: AppId) -> Option { - self.find(id).and_then(|i| self.apps[i].map(|r| r.state)) - } - - pub fn record(&self, id: AppId) -> Option { - self.find(id).and_then(|i| self.apps[i]) - } - - fn find(&self, id: AppId) -> Option { - self.apps.iter().position(|r| r.map(|a| a.manifest.id) == Some(id)) - } + pub fn state(&self, id: AppId) -> Option { self.find(id).and_then(|i| self.apps[i].map(|r| r.state)) } + pub fn record(&self, id: AppId) -> Option { self.find(id).and_then(|i| self.apps[i]) } + fn find(&self, id: AppId) -> Option { self.apps.iter().position(|r| r.map(|a| a.manifest.id) == Some(id)) } } #[cfg(test)] mod tests { use super::*; - - fn spawn(app: AppId, memory: u32, _caps: u64) -> Result { - if app.0 == 0 || memory == 0 { Err(()) } else { Ok(0x1000 + app.0) } - } - fn window(app: AppId) -> Result { - if app.0 == 0 || app.0 > u16::MAX as u64 { Err(()) } else { Ok(app.0 as u16) } - } - fn manifest(id: u64) -> AppManifest { - AppManifest { - id: AppId(id), abi_major: AWE_APP_ABI_MAJOR, abi_minor: AWE_APP_ABI_MINOR, - memory_limit_pages: 4, capability_mask: 0b101, dependency_count: 0, resource_count: 0, - } - } - - #[test] - fn admission_start_and_stop_are_real_lifecycle_steps() { - let mut s = Supervisor::new(spawn, window); - s.admit(manifest(1)).unwrap(); - let pid = s.start(AppId(1), 0b111).unwrap(); - assert_eq!(pid, 0x1001); - assert_eq!(s.record(AppId(1)).unwrap().window_id, 1); - assert_eq!(s.state(AppId(1)), Some(AppState::Running)); - s.stop(AppId(1)).unwrap(); - assert_eq!(s.state(AppId(1)), Some(AppState::Stopped)); - } - - #[test] - fn capability_admission_is_fail_closed() { - let mut s = Supervisor::new(spawn, window); - s.admit(manifest(2)).unwrap(); - assert_eq!(s.start(AppId(2), 0b001), Err(RuntimeError::CapabilityDenied)); - } - - #[test] - fn repeated_failures_quarantine_app() { - let mut s = Supervisor::new(spawn, window); - s.admit(manifest(3)).unwrap(); - s.start(AppId(3), 0b111).unwrap(); - for _ in 0..MAX_FAILURES { - assert_eq!(s.report_failure(AppId(3)).unwrap(), AppState::Failed); - s.start(AppId(3), 0b111).unwrap(); - } - assert_eq!(s.report_failure(AppId(3)).unwrap(), AppState::Quarantined); - } -} \ No newline at end of file + fn spawn(app: AppId, memory: u32, _caps: u64) -> Result { if app.0 == 0 || memory == 0 { Err(()) } else { Ok(0x1000 + app.0) } } + fn window(app: AppId) -> Result { if app.0 == 0 || app.0 > u16::MAX as u64 { Err(()) } else { Ok(app.0 as u16) } } + fn manifest(id: u64) -> AppManifest { AppManifest { id: AppId(id), abi_major: AWE_APP_ABI_MAJOR, abi_minor: AWE_APP_ABI_MINOR, memory_limit_pages: 4, capability_mask: 0b101, dependency_count: 0, resource_count: 0 } } + #[test] fn admission_start_and_stop_are_real_lifecycle_steps() { let mut s = Supervisor::new(spawn, window); s.admit(manifest(1)).unwrap(); let pid = s.start(AppId(1), 0b111).unwrap(); assert_eq!(pid, 0x1001); assert_eq!(s.record(AppId(1)).unwrap().window_id, 1); assert_eq!(s.state(AppId(1)), Some(AppState::Running)); s.stop(AppId(1)).unwrap(); assert_eq!(s.state(AppId(1)), Some(AppState::Stopped)); } + #[test] fn capability_admission_is_fail_closed() { let mut s = Supervisor::new(spawn, window); s.admit(manifest(2)).unwrap(); assert_eq!(s.start(AppId(2), 0b001), Err(RuntimeError::CapabilityDenied)); } + #[test] fn repeated_failures_quarantine_app() { let mut s = Supervisor::new(spawn, window); s.admit(manifest(3)).unwrap(); s.start(AppId(3), 0b111).unwrap(); for _ in 0..MAX_FAILURES { assert_eq!(s.report_failure(AppId(3)).unwrap(), AppState::Failed); s.start(AppId(3), 0b111).unwrap(); } assert_eq!(s.report_failure(AppId(3)).unwrap(), AppState::Quarantined); } +} From 0e2d7e4ed4b1c6cbc1108799848f393f45664fb1 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:24:04 +0400 Subject: [PATCH 36/65] fix(aweos): persist system runtime state across boot handoff --- kernel/src/entry.rs | 56 ++++++++++++++------------------------------- 1 file changed, 17 insertions(+), 39 deletions(-) diff --git a/kernel/src/entry.rs b/kernel/src/entry.rs index 2763553..8aacbb3 100644 --- a/kernel/src/entry.rs +++ b/kernel/src/entry.rs @@ -7,15 +7,7 @@ use crate::runtime::{CapabilitySet, FramebufferInfo, SystemRuntime}; #[repr(u8)] #[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum KernelBootStatus { - Ready = 0, - InvalidBootInfo = 1, - UnsupportedArchitecture = 2, - NoCpu = 3, - NoUsableMemory = 4, - InvalidFramebuffer = 5, - RuntimeBringupFailed = 6, -} +pub enum KernelBootStatus { Ready = 0, InvalidBootInfo = 1, UnsupportedArchitecture = 2, NoCpu = 3, NoUsableMemory = 4, InvalidFramebuffer = 5, RuntimeBringupFailed = 6 } pub struct KernelContext { progress: BootProgress } impl Default for KernelContext { fn default() -> Self { Self::new() } } @@ -25,6 +17,8 @@ impl KernelContext { pub fn advance(&mut self) -> bool { self.progress.advance() } } +static mut SYSTEM_RUNTIME: SystemRuntime = SystemRuntime::new(); + pub fn kernel_entry(info: &BootInfo) -> KernelBootStatus { if !validate(info) { return KernelBootStatus::InvalidBootInfo; } if !info.architecture.is_supported() { return KernelBootStatus::UnsupportedArchitecture; } @@ -58,28 +52,18 @@ pub fn kernel_entry(info: &BootInfo) -> KernelBootStatus { if let Some(pit) = Pit::new(1000) { unsafe { pit.program(); } } serial_write_str("AWEOS: hardware/interrupt foundation initialized\r\n"); - let mut system = SystemRuntime::new(); + let runtime = unsafe { core::ptr::addr_of_mut!(SYSTEM_RUNTIME).as_mut().unwrap() }; if info.framebuffer_address != 0 && info.framebuffer_size != 0 { - let fb = FramebufferInfo { - address: info.framebuffer_address, - size: info.framebuffer_size, - width: info.framebuffer_width, - height: info.framebuffer_height, - pitch: info.framebuffer_pitch, - bytes_per_pixel: 4, - }; - system.attach_framebuffer(fb).map_err(|_| ()).unwrap_or(()); - if !system.core.desktop_ready() { return KernelBootStatus::InvalidFramebuffer; } - serial_write_str("AWEOS: BootInfo framebuffer accepted by runtime\r\n"); + let fb = FramebufferInfo { address: info.framebuffer_address, size: info.framebuffer_size, width: info.framebuffer_width, height: info.framebuffer_height, pitch: info.framebuffer_pitch, bytes_per_pixel: 4 }; + if runtime.attach_framebuffer(fb).is_err() { return KernelBootStatus::InvalidFramebuffer; } + serial_write_str("AWEOS: BootInfo framebuffer accepted by persistent runtime\r\n"); } - - // Bring up the end-user control plane in dependency order. - if system.register_core_services().is_err() { return KernelBootStatus::RuntimeBringupFailed; } - if system.start_core_services().is_err() { return KernelBootStatus::RuntimeBringupFailed; } - if system.mount_core_namespaces(32).is_err() { return KernelBootStatus::RuntimeBringupFailed; } - if system.admit_core_apps().is_err() { return KernelBootStatus::RuntimeBringupFailed; } - if system.start_core_apps().is_err() { return KernelBootStatus::RuntimeBringupFailed; } - serial_write_str("AWEOS: initd/appd/storage/UI runtime control plane started\r\n"); + if runtime.register_core_services().is_err() { return KernelBootStatus::RuntimeBringupFailed; } + if runtime.start_core_services().is_err() { return KernelBootStatus::RuntimeBringupFailed; } + if runtime.mount_core_namespaces(32).is_err() { return KernelBootStatus::RuntimeBringupFailed; } + if runtime.admit_core_apps().is_err() { return KernelBootStatus::RuntimeBringupFailed; } + if runtime.start_core_apps().is_err() { return KernelBootStatus::RuntimeBringupFailed; } + serial_write_str("AWEOS: persistent initd/appd/storage/UI runtime control plane started\r\n"); serial_write_str("AWEOS: entering Ring 3 with IOPL=0\r\n"); unsafe { enter_userspace(userspace_entry as *const () as usize as u64, user_stack_top); } } @@ -90,11 +74,7 @@ pub fn kernel_entry(info: &BootInfo) -> KernelBootStatus { #[unsafe(no_mangle)] pub extern "C" fn userspace_entry() -> ! { let message = b"AWEOS: Ring 3 active; privileged device access is mediated by syscall/IPC.\r\n"; - let mut process = crate::process::ProcessDescriptor { - id: crate::process::ProcessId(1), - state: crate::process::ProcessState::Running, - budget: crate::process::ResourceBudget { cpu_ticks: 1000, memory_bytes: 1024 * 1024, ipc_messages: 64 }, - }; + let mut process = crate::process::ProcessDescriptor { id: crate::process::ProcessId(1), state: crate::process::ProcessState::Running, budget: crate::process::ResourceBudget { cpu_ticks: 1000, memory_bytes: 1024 * 1024, ipc_messages: 64 } }; let mut syscall = crate::syscall::SyscallContext { process: &mut process }; let _ = syscall.dispatch(8, [message.as_ptr() as u64, message.len() as u64, 0, 0, 0, 0]); loop { let _ = syscall.dispatch(0, [0;6]); unsafe { core::arch::asm!("pause"); } } @@ -103,9 +83,7 @@ pub extern "C" fn userspace_entry() -> ! { #[cfg(all(target_arch = "x86_64", target_os = "none"))] pub unsafe fn enter_userspace(user_rip: u64, user_rsp: u64) { use crate::arch::x86_64::gdt::{USER_CODE_SELECTOR, USER_DATA_SELECTOR}; - let user_cs = USER_CODE_SELECTOR as u64; - let user_ss = USER_DATA_SELECTOR as u64; - let rflags = 0x0202u64; + let user_cs = USER_CODE_SELECTOR as u64; let user_ss = USER_DATA_SELECTOR as u64; let rflags = 0x0202u64; unsafe { core::arch::asm!("push {0}", "push {1}", "push {2}", "push {3}", "push {4}", "iretq", in(reg) user_ss, in(reg) user_rsp, in(reg) rflags, in(reg) user_cs, in(reg) user_rip, options(noreturn)); } } @@ -113,11 +91,11 @@ pub unsafe fn enter_userspace(user_rip: u64, user_rsp: u64) { mod tests { use super::*; use awe_boot_protocol::{Architecture, BootInfo, MemoryRegion}; - fn info() -> (BootInfo, [MemoryRegion; 1]) { + fn info() -> (BootInfo, [MemoryRegion;1]) { let regions = [MemoryRegion { base: 0x1000, length: 0x10000, kind: 1, reserved: 0 }]; let boot = BootInfo { magic: awe_boot_protocol::AWE_BOOT_MAGIC, version: awe_boot_protocol::AWE_BOOT_VERSION, size: core::mem::size_of::() as u32, architecture: Architecture::X86_64, cpu_count: 1, memory_regions: regions.as_ptr(), memory_region_count: 1, framebuffer_address: 0, framebuffer_size: 0, framebuffer_width: 0, framebuffer_height: 0, framebuffer_pitch: 0, acpi_rsdp: 0, device_tree: 0, kernel_base: 0, kernel_size: 0 }; (boot, regions) } #[test] fn invalid_boot_info_is_rejected() { let (mut boot, _) = info(); boot.magic = 0; assert_eq!(kernel_entry(&boot), KernelBootStatus::InvalidBootInfo); } #[test] fn valid_minimum_boot_info_is_accepted() { let (boot, _) = info(); assert_eq!(kernel_entry(&boot), KernelBootStatus::Ready); } -} +} \ No newline at end of file From ca0affdae4cdd4337fdaa46bcb74877a946942e6 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:24:53 +0400 Subject: [PATCH 37/65] fix(aweos): correct IPv4 parser comparison and module attributes --- services/netd/src/packet.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/services/netd/src/packet.rs b/services/netd/src/packet.rs index 99fe098..4a59ec1 100644 --- a/services/netd/src/packet.rs +++ b/services/netd/src/packet.rs @@ -1,5 +1,3 @@ -#![no_std] - pub const ETH_HEADER: usize = 14; pub const IPV4_MIN_HEADER: usize = 20; pub const UDP_HEADER: usize = 8; @@ -26,7 +24,7 @@ impl Ipv4Header { let header_len = ((bytes[0] & 0x0f) as usize) * 4; if header_len < IPV4_MIN_HEADER || header_len > bytes.len() { return Err(PacketError::InvalidIpv4); } let total_length = u16::from_be_bytes([bytes[2], bytes[3]]); - if total_length as usize > bytes.len() || total_length as usize < header_len { return Err(PacketError::InvalidLength); } + if (total_length as usize) > bytes.len() || (total_length as usize) < header_len { return Err(PacketError::InvalidLength); } let expected = u16::from_be_bytes([bytes[10], bytes[11]]); let mut header = [0u8; 60]; header[..header_len].copy_from_slice(&bytes[..header_len]); @@ -42,7 +40,7 @@ impl UdpHeader { pub fn parse(bytes: &[u8]) -> Result { if bytes.len() < UDP_HEADER { return Err(PacketError::TooShort); } let length = u16::from_be_bytes([bytes[4], bytes[5]]); - if length < UDP_HEADER as u16 || length as usize > bytes.len() { return Err(PacketError::InvalidLength); } + if length < UDP_HEADER as u16 || (length as usize) > bytes.len() { return Err(PacketError::InvalidLength); } Ok(Self { source_port: u16::from_be_bytes([bytes[0], bytes[1]]), destination_port: u16::from_be_bytes([bytes[2], bytes[3]]), length }) } } @@ -62,7 +60,7 @@ pub fn build_udp_ipv4(out: &mut [u8], source_mac: [u8; 6], destination_mac: [u8; out[0..6].copy_from_slice(&destination_mac); out[6..12].copy_from_slice(&source_mac); out[12..14].copy_from_slice(&0x0800u16.to_be_bytes()); let ip = ETH_HEADER; out[ip] = 0x45; out[ip+1] = 0; - out[ip+2..ip+4].copy_from_slice(&((total - ETH_HEADER) as u16).to_be_bytes()); + out[ip+2..ip+4].copy_from_slice(&u16::try_from(total - ETH_HEADER).map_err(|_| PacketError::InvalidLength)?.to_be_bytes()); out[ip+4..ip+6].fill(0); out[ip+6..ip+8].copy_from_slice(&0x4000u16.to_be_bytes()); out[ip+8] = 64; out[ip+9] = 17; out[ip+10..ip+12].fill(0); out[ip+12..ip+16].copy_from_slice(&source_ip); out[ip+16..ip+20].copy_from_slice(&destination_ip); let c = checksum16(&out[ip..ip+20]); out[ip+10..ip+12].copy_from_slice(&c.to_be_bytes()); @@ -76,7 +74,8 @@ pub fn build_udp_ipv4(out: &mut [u8], source_mac: [u8; 6], destination_mac: [u8; #[cfg(test)] mod tests { use super::*; - #[test] fn udp_ipv4_packet_build_and_parse() { + #[test] + fn udp_ipv4_packet_build_and_parse() { let mut packet = [0u8; MAX_PACKET]; let n = build_udp_ipv4(&mut packet, [1,2,3,4,5,6], [6,5,4,3,2,1], [10,0,0,1], [10,0,0,2], 1000, 2000, b"hello").unwrap(); assert_eq!(EthernetFrame::parse(&packet[..n]).unwrap().ethertype, 0x0800); @@ -85,10 +84,11 @@ mod tests { let udp = UdpHeader::parse(&packet[ETH_HEADER + ip.header_len..n]).unwrap(); assert_eq!(udp.destination_port, 2000); assert_eq!(udp.length, 13); } - #[test] fn rejects_bad_ipv4_checksum() { + #[test] + fn rejects_bad_ipv4_checksum() { let mut packet = [0u8; MAX_PACKET]; let n = build_udp_ipv4(&mut packet, [1;6], [2;6], [10,0,0,1], [10,0,0,2], 1, 2, b"x").unwrap(); packet[ETH_HEADER + 8] ^= 1; assert_eq!(Ipv4Header::parse(&packet[ETH_HEADER..n]), Err(PacketError::InvalidChecksum)); } -} \ No newline at end of file +} From 683d0ef85fbc22600cc11b3fb2af13313d5580a0 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:25:05 +0400 Subject: [PATCH 38/65] fix(aweos): make bounded file store copy-safe for static mount table --- kernel/src/storage/file_store.rs | 115 ++++++------------------------- 1 file changed, 20 insertions(+), 95 deletions(-) diff --git a/kernel/src/storage/file_store.rs b/kernel/src/storage/file_store.rs index 7d1c676..0243d4e 100644 --- a/kernel/src/storage/file_store.rs +++ b/kernel/src/storage/file_store.rs @@ -11,18 +11,13 @@ pub enum FileStoreError { Full, NotFound, InvalidName, InvalidOffset, TooLarge, impl From for FileStoreError { fn from(v: StorageError) -> Self { Self::Storage(v) } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct FileRecord { - pub id: u32, - pub size: u64, - pub first_block: u64, - pub blocks: u16, - pub generation: u64, -} +pub struct FileRecord { pub id: u32, pub size: u64, pub first_block: u64, pub blocks: u16, pub generation: u64 } #[derive(Clone, Copy)] struct Slot { record: Option, name: [u8; 63], name_len: u8 } impl Slot { const fn empty() -> Self { Self { record: None, name: [0;63], name_len: 0 } } } +#[derive(Clone, Copy)] pub struct FileStore { slots: [Slot; N], next_id: u32, @@ -30,91 +25,38 @@ pub struct FileStore { } impl FileStore { pub const fn new(start_block: u64) -> Self { Self { slots: [Slot::empty(); N], next_id: 1, next_block: start_block } } - pub fn create(&mut self, name: &[u8]) -> Result { if name.is_empty() || name.len() > 63 || name.iter().any(|b| *b == 0 || *b == b'/') { return Err(FileStoreError::InvalidName); } if self.find(name).is_some() { return Err(FileStoreError::InvalidName); } let slot = self.slots.iter().position(|s| s.record.is_none()).ok_or(FileStoreError::Full)?; let record = FileRecord { id: self.next_id, size: 0, first_block: self.next_block, blocks: 0, generation: 1 }; self.next_id = self.next_id.checked_add(1).ok_or(FileStoreError::Full)?; - self.slots[slot].record = Some(record); - self.slots[slot].name[..name.len()].copy_from_slice(name); - self.slots[slot].name_len = name.len() as u8; + self.slots[slot].record = Some(record); self.slots[slot].name[..name.len()].copy_from_slice(name); self.slots[slot].name_len = name.len() as u8; Ok(record) } - pub fn write_at(&mut self, device: &mut D, id: u32, offset: usize, data: &[u8]) -> Result { - let index = self.find_id(id).ok_or(FileStoreError::NotFound)?; - let record = self.slots[index].record.unwrap(); + let index = self.find_id(id).ok_or(FileStoreError::NotFound)?; let record = self.slots[index].record.unwrap(); let end = offset.checked_add(data.len()).ok_or(FileStoreError::TooLarge)?; - if end > MAX_FILE_SIZE { return Err(FileStoreError::TooLarge); } - if data.is_empty() { return Ok(0); } - let required_blocks = end.div_ceil(BLOCK_SIZE); - if required_blocks > MAX_FILE_BLOCKS { return Err(FileStoreError::TooLarge); } - if required_blocks > record.blocks as usize { - let additional = required_blocks - record.blocks as usize; - if self.next_block.checked_add(additional as u64).is_none() { return Err(FileStoreError::NoSpace); } - self.slots[index].record.as_mut().unwrap().blocks = required_blocks as u16; - self.next_block = self.next_block.saturating_add(additional as u64); - } - let first = record.first_block; - let mut consumed = 0usize; + if end > MAX_FILE_SIZE { return Err(FileStoreError::TooLarge); } if data.is_empty() { return Ok(0); } + let required_blocks = end.div_ceil(BLOCK_SIZE); if required_blocks > MAX_FILE_BLOCKS { return Err(FileStoreError::TooLarge); } + if required_blocks > record.blocks as usize { let additional = required_blocks - record.blocks as usize; self.next_block = self.next_block.checked_add(additional as u64).ok_or(FileStoreError::NoSpace)?; self.slots[index].record.as_mut().unwrap().blocks = required_blocks as u16; } + let first = record.first_block; let mut consumed = 0usize; while consumed < data.len() { - let absolute = offset + consumed; - let block = first + (absolute / BLOCK_SIZE) as u64; - let in_block = absolute % BLOCK_SIZE; - let take = core::cmp::min(BLOCK_SIZE - in_block, data.len() - consumed); - let mut buf = [0u8; BLOCK_SIZE]; - device.read_block(block, &mut buf)?; - buf[in_block..in_block + take].copy_from_slice(&data[consumed..consumed + take]); - device.write_block(block, &buf)?; - consumed += take; + let absolute = offset + consumed; let block = first + (absolute / BLOCK_SIZE) as u64; let in_block = absolute % BLOCK_SIZE; let take = core::cmp::min(BLOCK_SIZE - in_block, data.len() - consumed); let mut buf = [0u8; BLOCK_SIZE]; + device.read_block(block, &mut buf)?; buf[in_block..in_block + take].copy_from_slice(&data[consumed..consumed + take]); device.write_block(block, &buf)?; consumed += take; } - let current = self.slots[index].record.as_mut().unwrap(); - if end as u64 > current.size { current.size = end as u64; } - current.generation = current.generation.saturating_add(1); - Ok(data.len()) + let current = self.slots[index].record.as_mut().unwrap(); if end as u64 > current.size { current.size = end as u64; } current.generation = current.generation.saturating_add(1); Ok(data.len()) } - pub fn read_at(&self, device: &mut D, id: u32, offset: usize, out: &mut [u8]) -> Result { - let index = self.find_id(id).ok_or(FileStoreError::NotFound)?; - let record = self.slots[index].record.unwrap(); - if offset > record.size as usize { return Err(FileStoreError::InvalidOffset); } - let available = record.size as usize - offset; - let count = core::cmp::min(available, out.len()); - let mut consumed = 0usize; - while consumed < count { - let absolute = offset + consumed; - let block = record.first_block + (absolute / BLOCK_SIZE) as u64; - let in_block = absolute % BLOCK_SIZE; - let take = core::cmp::min(BLOCK_SIZE - in_block, count - consumed); - let mut buf = [0u8; BLOCK_SIZE]; - device.read_block(block, &mut buf)?; - out[consumed..consumed + take].copy_from_slice(&buf[in_block..in_block + take]); - consumed += take; - } + let index = self.find_id(id).ok_or(FileStoreError::NotFound)?; let record = self.slots[index].record.unwrap(); if offset > record.size as usize { return Err(FileStoreError::InvalidOffset); } + let count = core::cmp::min(record.size as usize - offset, out.len()); let mut consumed = 0usize; + while consumed < count { let absolute = offset + consumed; let block = record.first_block + (absolute / BLOCK_SIZE) as u64; let in_block = absolute % BLOCK_SIZE; let take = core::cmp::min(BLOCK_SIZE - in_block, count - consumed); let mut buf = [0u8; BLOCK_SIZE]; device.read_block(block, &mut buf)?; out[consumed..consumed+take].copy_from_slice(&buf[in_block..in_block+take]); consumed += take; } Ok(count) } - - pub fn truncate(&mut self, id: u32, size: usize) -> Result<(), FileStoreError> { - if size > MAX_FILE_SIZE { return Err(FileStoreError::TooLarge); } - let index = self.find_id(id).ok_or(FileStoreError::NotFound)?; - let r = self.slots[index].record.as_mut().unwrap(); - r.size = size as u64; - r.blocks = size.div_ceil(BLOCK_SIZE) as u16; - r.generation = r.generation.saturating_add(1); - Ok(()) - } - - pub fn delete(&mut self, id: u32) -> Result<(), FileStoreError> { - let index = self.find_id(id).ok_or(FileStoreError::NotFound)?; - self.slots[index] = Slot::empty(); - Ok(()) - } - + pub fn truncate(&mut self, id: u32, size: usize) -> Result<(), FileStoreError> { if size > MAX_FILE_SIZE { return Err(FileStoreError::TooLarge); } let index = self.find_id(id).ok_or(FileStoreError::NotFound)?; let r = self.slots[index].record.as_mut().unwrap(); r.size=size as u64; r.blocks=size.div_ceil(BLOCK_SIZE) as u16; r.generation=r.generation.saturating_add(1); Ok(()) } + pub fn delete(&mut self, id: u32) -> Result<(), FileStoreError> { let index=self.find_id(id).ok_or(FileStoreError::NotFound)?; self.slots[index]=Slot::empty(); Ok(()) } pub fn lookup(&self, name: &[u8]) -> Option { self.find(name).and_then(|i| self.slots[i].record) } pub fn record(&self, id: u32) -> Option { self.find_id(id).and_then(|i| self.slots[i].record) } - fn find(&self, name: &[u8]) -> Option { self.slots.iter().position(|s| s.record.is_some() && s.name_len as usize == name.len() && &s.name[..name.len()] == name) } fn find_id(&self, id: u32) -> Option { self.slots.iter().position(|s| s.record.map(|r| r.id) == Some(id)) } } @@ -122,24 +64,7 @@ impl Default for FileStore { fn default() -> Self { Self::new #[cfg(test)] mod tests { - use super::*; - use crate::storage::RamBlockDevice; - #[test] - fn block_backed_round_trip_supports_seek() { - let mut disk = RamBlockDevice::default(); - let mut fs = FileStore::<4>::new(8); - let file = fs.create(b"persist.bin").unwrap(); - fs.write_at(&mut disk, file.id, 3, b"AWEOS").unwrap(); - let mut out = [0u8; 8]; - assert_eq!(fs.read_at(&mut disk, file.id, 3, &mut out).unwrap(), 5); - assert_eq!(&out[..5], b"AWEOS"); - assert_eq!(fs.record(file.id).unwrap().size, 8); - } - #[test] - fn rejects_out_of_bounds_file_size() { - let mut disk = RamBlockDevice::default(); - let mut fs = FileStore::<1>::new(8); - let file = fs.create(b"x").unwrap(); - assert_eq!(fs.write_at(&mut disk, file.id, MAX_FILE_SIZE, b"x"), Err(FileStoreError::TooLarge)); - } -} \ No newline at end of file + use super::*; use crate::storage::RamBlockDevice; + #[test] fn block_backed_round_trip_supports_seek() { let mut disk=RamBlockDevice::default(); let mut fs=FileStore::<4>::new(8); let file=fs.create(b"persist.bin").unwrap(); fs.write_at(&mut disk,file.id,3,b"AWEOS").unwrap(); let mut out=[0u8;8]; assert_eq!(fs.read_at(&mut disk,file.id,3,&mut out).unwrap(),5); assert_eq!(&out[..5],b"AWEOS"); assert_eq!(fs.record(file.id).unwrap().size,8); } + #[test] fn rejects_out_of_bounds_file_size() { let mut disk=RamBlockDevice::default(); let mut fs=FileStore::<1>::new(8); let file=fs.create(b"x").unwrap(); assert_eq!(fs.write_at(&mut disk,file.id,MAX_FILE_SIZE,b"x"),Err(FileStoreError::TooLarge)); } +} From 2a853033ac4b91c6b2bb49b61acefc5161fb6dfe Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:25:50 +0400 Subject: [PATCH 39/65] fix(aweos): correct IPv4 packet parser comparison From 1c84983fb6521c49a5cdb30def75a4dacc4d5f21 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:26:37 +0400 Subject: [PATCH 40/65] fix(aweos): make initd runtime module warning-free --- services/initd/src/runtime.rs | 162 +++++++--------------------------- 1 file changed, 31 insertions(+), 131 deletions(-) diff --git a/services/initd/src/runtime.rs b/services/initd/src/runtime.rs index bf95309..ec19e69 100644 --- a/services/initd/src/runtime.rs +++ b/services/initd/src/runtime.rs @@ -1,22 +1,11 @@ -#![no_std] - -use super::{RestartPolicy, ServiceId, ServiceSpec, ServiceState, validate_spec, MAX_SERVICES}; +use super::{RestartPolicy, ServiceId, ServiceSpec, ServiceState, validate_spec}; pub const MAX_DEPENDENCIES: usize = 8; +pub const MAX_RUNTIME_SERVICES: usize = 8; pub const MAX_FAILURES: u8 = 3; #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum RuntimeError { - Full, - Duplicate, - InvalidSpec, - InvalidDependency, - DependencyCycle, - MissingDependency, - SpawnFailed, - InvalidTransition, - Quarantined, -} +pub enum RuntimeError { Full, Duplicate, InvalidSpec, InvalidDependency, DependencyCycle, MissingDependency, SpawnFailed, InvalidTransition, Quarantined } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct ServiceRuntimeSpec { @@ -27,158 +16,69 @@ pub struct ServiceRuntimeSpec { } #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct RuntimeRecord { - pub spec: ServiceRuntimeSpec, - pub state: ServiceState, - pub process_id: u64, - pub failures: u8, -} +pub struct RuntimeRecord { pub spec: ServiceRuntimeSpec, pub state: ServiceState, pub process_id: u64, pub failures: u8 } pub type SpawnFn = fn(entry: usize, service: ServiceId, memory_pages: u32, cpu_budget: u32) -> Result; pub struct Supervisor { - records: [Option; MAX_SERVICES], + records: [Option; MAX_RUNTIME_SERVICES], count: usize, spawn: SpawnFn, } - impl Supervisor { - pub const fn new(spawn: SpawnFn) -> Self { - Self { records: [None; MAX_SERVICES], count: 0, spawn } - } - + pub const fn new(spawn: SpawnFn) -> Self { Self { records: [None; MAX_RUNTIME_SERVICES], count: 0, spawn } } pub const fn len(&self) -> usize { self.count } - pub fn register(&mut self, runtime: ServiceRuntimeSpec) -> Result<(), RuntimeError> { validate_spec(runtime.spec).map_err(|_| RuntimeError::InvalidSpec)?; - if runtime.dependency_count as usize > MAX_DEPENDENCIES || runtime.entry == 0 { - return Err(RuntimeError::InvalidSpec); - } - if self.find(runtime.spec.id).is_some() { - return Err(RuntimeError::Duplicate); - } - for index in 0..runtime.dependency_count as usize { - let dep = runtime.dependencies[index].ok_or(RuntimeError::InvalidDependency)?; - if dep == runtime.spec.id { return Err(RuntimeError::DependencyCycle); } - } - if self.count == MAX_SERVICES { return Err(RuntimeError::Full); } + if runtime.dependency_count as usize > MAX_DEPENDENCIES || runtime.entry == 0 { return Err(RuntimeError::InvalidSpec); } + if self.find(runtime.spec.id).is_some() { return Err(RuntimeError::Duplicate); } + for index in 0..runtime.dependency_count as usize { let dep = runtime.dependencies[index].ok_or(RuntimeError::InvalidDependency)?; if dep == runtime.spec.id { return Err(RuntimeError::DependencyCycle); } } let slot = self.records.iter().position(Option::is_none).ok_or(RuntimeError::Full)?; - self.records[slot] = Some(RuntimeRecord { - spec: runtime, - state: ServiceState::Declared, - process_id: 0, - failures: 0, - }); - self.count += 1; - Ok(()) + self.records[slot] = Some(RuntimeRecord { spec: runtime, state: ServiceState::Declared, process_id: 0, failures: 0 }); + self.count += 1; Ok(()) } - pub fn start(&mut self, id: ServiceId) -> Result { let index = self.find(id).ok_or(RuntimeError::MissingDependency)?; - if self.records[index].unwrap().state == ServiceState::Quarantined { - return Err(RuntimeError::Quarantined); - } - let runtime = self.records[index].unwrap(); - for dep_index in 0..runtime.spec.dependency_count as usize { - let dep = runtime.spec.dependencies[dep_index].ok_or(RuntimeError::InvalidDependency)?; - let dep_record = self.find(dep).ok_or(RuntimeError::MissingDependency)?; - if self.records[dep_record].unwrap().state != ServiceState::Running { - return Err(RuntimeError::MissingDependency); - } - } - if !matches!(runtime.state, ServiceState::Declared | ServiceState::Failed | ServiceState::Stopped) { - return Err(RuntimeError::InvalidTransition); + let runtime = self.records[index].ok_or(RuntimeError::MissingDependency)?; + if runtime.state == ServiceState::Quarantined { return Err(RuntimeError::Quarantined); } + for dependency_index in 0..runtime.spec.dependency_count as usize { + let dep = runtime.spec.dependencies[dependency_index].ok_or(RuntimeError::InvalidDependency)?; + let dep_index = self.find(dep).ok_or(RuntimeError::MissingDependency)?; + if self.records[dep_index].ok_or(RuntimeError::MissingDependency)?.state != ServiceState::Running { return Err(RuntimeError::MissingDependency); } } + if !matches!(runtime.state, ServiceState::Declared | ServiceState::Failed | ServiceState::Stopped) { return Err(RuntimeError::InvalidTransition); } self.records[index].as_mut().unwrap().state = ServiceState::Starting; let pid = match (self.spawn)(runtime.spec.entry, id, runtime.spec.spec.memory_limit_pages, runtime.spec.spec.cpu_budget_ticks) { Ok(pid) if pid != 0 => pid, - _ => { - self.records[index].as_mut().unwrap().state = ServiceState::Failed; - return Err(RuntimeError::SpawnFailed); - } + _ => { self.records[index].as_mut().unwrap().state = ServiceState::Failed; return Err(RuntimeError::SpawnFailed); } }; let record = self.records[index].as_mut().unwrap(); - record.process_id = pid; - record.failures = 0; - record.state = ServiceState::Running; - Ok(pid) + record.process_id = pid; record.failures = 0; record.state = ServiceState::Running; Ok(pid) } - pub fn report_failure(&mut self, id: ServiceId) -> Result { let index = self.find(id).ok_or(RuntimeError::MissingDependency)?; let record = self.records[index].as_mut().unwrap(); if record.state != ServiceState::Running { return Err(RuntimeError::InvalidTransition); } record.failures = record.failures.saturating_add(1); - record.state = if record.failures > MAX_FAILURES { - ServiceState::Quarantined - } else { - ServiceState::Failed - }; + record.state = if record.failures > MAX_FAILURES { ServiceState::Quarantined } else { ServiceState::Failed }; Ok(record.state) } - pub fn restart(&mut self, id: ServiceId) -> Result { let index = self.find(id).ok_or(RuntimeError::MissingDependency)?; - let record = self.records[index].unwrap(); - if record.state == ServiceState::Quarantined || record.failures > MAX_FAILURES { - return Err(RuntimeError::Quarantined); - } - match record.spec.spec.restart { - RestartPolicy::Never => return Err(RuntimeError::InvalidTransition), - RestartPolicy::OnFailure | RestartPolicy::Always => self.start(id), - } - } - - pub fn state(&self, id: ServiceId) -> Option { - self.find(id).and_then(|i| self.records[i].map(|r| r.state)) - } - - pub fn process_id(&self, id: ServiceId) -> Option { - self.find(id).and_then(|i| self.records[i].map(|r| r.process_id)).filter(|pid| *pid != 0) - } - - fn find(&self, id: ServiceId) -> Option { - self.records.iter().position(|r| r.map(|record| record.spec.spec.id) == Some(id)) + let record = self.records[index].ok_or(RuntimeError::MissingDependency)?; + if record.state == ServiceState::Quarantined || record.failures > MAX_FAILURES { return Err(RuntimeError::Quarantined); } + match record.spec.spec.restart { RestartPolicy::Never => Err(RuntimeError::InvalidTransition), RestartPolicy::OnFailure | RestartPolicy::Always => self.start(id) } } + pub fn state(&self, id: ServiceId) -> Option { self.find(id).and_then(|i| self.records[i].map(|r| r.state)) } + pub fn process_id(&self, id: ServiceId) -> Option { self.find(id).and_then(|i| self.records[i].map(|r| r.process_id)).filter(|pid| *pid != 0) } + fn find(&self, id: ServiceId) -> Option { self.records.iter().position(|r| r.map(|record| record.spec.spec.id) == Some(id)) } } #[cfg(test)] mod tests { use super::*; - - fn spawn(entry: usize, service: ServiceId, _mem: u32, _cpu: u32) -> Result { - if entry == 0 || service.0 == 0 { return Err(()); } - Ok((entry as u64) ^ (service.0 as u64)) - } - - fn spec(id: u16, deps: [Option; MAX_DEPENDENCIES], count: u8) -> ServiceRuntimeSpec { - ServiceRuntimeSpec { - spec: ServiceSpec { - id: ServiceId(id), restart: RestartPolicy::OnFailure, - capability_mask: u64::MAX, memory_limit_pages: 4, cpu_budget_ticks: 100, - }, - dependencies: deps, dependency_count: count, entry: id as usize + 1, - } - } - - #[test] - fn dependencies_are_enforced_before_spawn() { - let mut s = Supervisor::new(spawn); - s.register(spec(1, [None; MAX_DEPENDENCIES], 0)).unwrap(); - s.register(spec(2, [Some(ServiceId(1)), None, None, None, None, None, None, None], 1)).unwrap(); - assert_eq!(s.start(ServiceId(2)), Err(RuntimeError::MissingDependency)); - s.start(ServiceId(1)).unwrap(); - assert_ne!(s.start(ServiceId(2)), Err(RuntimeError::MissingDependency)); - assert_eq!(s.state(ServiceId(2)), Some(ServiceState::Running)); - } - - #[test] - fn failure_escalates_to_quarantine() { - let mut s = Supervisor::new(spawn); - s.register(spec(1, [None; MAX_DEPENDENCIES], 0)).unwrap(); - s.start(ServiceId(1)).unwrap(); - for _ in 0..MAX_FAILURES { assert_eq!(s.report_failure(ServiceId(1)).unwrap(), ServiceState::Failed); s.start(ServiceId(1)).unwrap(); } - assert_eq!(s.report_failure(ServiceId(1)).unwrap(), ServiceState::Quarantined); - assert_eq!(s.restart(ServiceId(1)), Err(RuntimeError::Quarantined)); - } + fn spawn(entry: usize, service: ServiceId, _mem: u32, _cpu: u32) -> Result { if entry == 0 || service.0 == 0 { Err(()) } else { Ok((entry as u64) ^ service.0 as u64) } } + fn spec(id: u16, deps: [Option; MAX_DEPENDENCIES], count: u8) -> ServiceRuntimeSpec { ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(id), restart: RestartPolicy::OnFailure, capability_mask: u64::MAX, memory_limit_pages: 4, cpu_budget_ticks: 100 }, dependencies: deps, dependency_count: count, entry: id as usize + 1 } } + #[test] fn dependencies_are_enforced() { let mut s=Supervisor::new(spawn); s.register(spec(1,[None;MAX_DEPENDENCIES],0)).unwrap(); s.register(spec(2,[Some(ServiceId(1)),None,None,None,None,None,None,None],1)).unwrap(); assert_eq!(s.start(ServiceId(2)),Err(RuntimeError::MissingDependency)); s.start(ServiceId(1)).unwrap(); assert!(s.start(ServiceId(2)).is_ok()); } + #[test] fn failure_escalates_to_quarantine() { let mut s=Supervisor::new(spawn); s.register(spec(1,[None;MAX_DEPENDENCIES],0)).unwrap(); s.start(ServiceId(1)).unwrap(); for _ in 0..MAX_FAILURES { assert_eq!(s.report_failure(ServiceId(1)).unwrap(),ServiceState::Failed); s.start(ServiceId(1)).unwrap(); } assert_eq!(s.report_failure(ServiceId(1)).unwrap(),ServiceState::Quarantined); } } \ No newline at end of file From 0d3d05f08dfb837825d472bef1a1bda7b0b2142f Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:26:48 +0400 Subject: [PATCH 41/65] fix(aweos): remove module-level attributes and unused imports --- services/appd/src/runtime.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/services/appd/src/runtime.rs b/services/appd/src/runtime.rs index 2eab38c..6464e8f 100644 --- a/services/appd/src/runtime.rs +++ b/services/appd/src/runtime.rs @@ -1,6 +1,4 @@ -#![no_std] - -use super::{AppId, AppManifest, AppState, validate_manifest, AWE_APP_ABI_MAJOR, AWE_APP_ABI_MINOR}; +use super::{validate_manifest, AppId, AppManifest, AppState}; pub const MAX_APPS: usize = 32; pub const MAX_FAILURES: u8 = 3; @@ -67,8 +65,8 @@ mod tests { use super::*; fn spawn(app: AppId, memory: u32, _caps: u64) -> Result { if app.0 == 0 || memory == 0 { Err(()) } else { Ok(0x1000 + app.0) } } fn window(app: AppId) -> Result { if app.0 == 0 || app.0 > u16::MAX as u64 { Err(()) } else { Ok(app.0 as u16) } } - fn manifest(id: u64) -> AppManifest { AppManifest { id: AppId(id), abi_major: AWE_APP_ABI_MAJOR, abi_minor: AWE_APP_ABI_MINOR, memory_limit_pages: 4, capability_mask: 0b101, dependency_count: 0, resource_count: 0 } } + fn manifest(id: u64) -> AppManifest { AppManifest { id: AppId(id), abi_major: 1, abi_minor: 3, memory_limit_pages: 4, capability_mask: 0b101, dependency_count: 0, resource_count: 0 } } #[test] fn admission_start_and_stop_are_real_lifecycle_steps() { let mut s = Supervisor::new(spawn, window); s.admit(manifest(1)).unwrap(); let pid = s.start(AppId(1), 0b111).unwrap(); assert_eq!(pid, 0x1001); assert_eq!(s.record(AppId(1)).unwrap().window_id, 1); assert_eq!(s.state(AppId(1)), Some(AppState::Running)); s.stop(AppId(1)).unwrap(); assert_eq!(s.state(AppId(1)), Some(AppState::Stopped)); } #[test] fn capability_admission_is_fail_closed() { let mut s = Supervisor::new(spawn, window); s.admit(manifest(2)).unwrap(); assert_eq!(s.start(AppId(2), 0b001), Err(RuntimeError::CapabilityDenied)); } #[test] fn repeated_failures_quarantine_app() { let mut s = Supervisor::new(spawn, window); s.admit(manifest(3)).unwrap(); s.start(AppId(3), 0b111).unwrap(); for _ in 0..MAX_FAILURES { assert_eq!(s.report_failure(AppId(3)).unwrap(), AppState::Failed); s.start(AppId(3), 0b111).unwrap(); } assert_eq!(s.report_failure(AppId(3)).unwrap(), AppState::Quarantined); } -} +} \ No newline at end of file From 7044a8581153d59a23e1f2603804d1c68a161cef Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:27:01 +0400 Subject: [PATCH 42/65] fix(aweos): export PS2 key event types through driver facade --- kernel/src/drivers/mod.rs | 73 +++++++++------------------------------ 1 file changed, 17 insertions(+), 56 deletions(-) diff --git a/kernel/src/drivers/mod.rs b/kernel/src/drivers/mod.rs index f0d03a4..970367f 100644 --- a/kernel/src/drivers/mod.rs +++ b/kernel/src/drivers/mod.rs @@ -46,35 +46,19 @@ pub mod virtio_pci; pub mod windows; pub use android::AndroidLayer; pub use bus::{DeviceId, DeviceKind, DriverBus}; -pub use compat::{ - CompatibilityRegistry, DriverManifest, DriverSource, bind_compatible_driver, validate_contract, -}; +pub use compat::{CompatibilityRegistry, DriverManifest, DriverSource, bind_compatible_driver, validate_contract}; pub use contract::{DeviceContract, DmaPolicy, InterruptMode, MmioRegion}; -pub use core::{ - AdapterState, AndroidDriverAdapter, CoreError, DriverAdapter, DriverIdentity, DriverSlot, - HardwareAbstraction, HardwareInfo, LinuxDriverAdapter, WindowsDriverAdapter, -}; +pub use core::{AdapterState, AndroidDriverAdapter, CoreError, DriverAdapter, DriverIdentity, DriverSlot, HardwareAbstraction, HardwareInfo, LinuxDriverAdapter, WindowsDriverAdapter}; pub use hal_registers::RegisterBank; pub use installer::{InstallError, InstallPlan, InstallerPackage, PackageFormat, plan_install}; pub use learning::{DriverExperience, ExperienceDb, ProbeOutcome}; pub use linux::LinuxLayer; -pub use linux_activation::{ - ActivationError as LinuxActivationError, build_activation_order, validate_activation_order, -}; -pub use linux_activation_rollback::{ - RollbackError as LinuxRollbackError, activation_failed, build_rollback_order, -}; -pub use linux_dependency::{ - Dependency as LinuxDependency, DependencyError as LinuxDependencyError, - validate as validate_linux_dependencies, -}; -pub use linux_dependency_graph::{ - GraphError as LinuxGraphError, validate_graph as validate_linux_dependency_graph, -}; +pub use linux_activation::{ActivationError as LinuxActivationError, build_activation_order, validate_activation_order}; +pub use linux_activation_rollback::{RollbackError as LinuxRollbackError, activation_failed, build_rollback_order}; +pub use linux_dependency::{Dependency as LinuxDependency, DependencyError as LinuxDependencyError, validate as validate_linux_dependencies}; +pub use linux_dependency_graph::{GraphError as LinuxGraphError, validate_graph as validate_linux_dependency_graph}; pub use linux_dependency_multi_instance::{DependencyMultiError, DependencyMultiInstanceManager}; -pub use linux_dependency_order::{ - OrderError as LinuxDependencyOrderError, topological_order as linux_dependency_order, -}; +pub use linux_dependency_order::{OrderError as LinuxDependencyOrderError, topological_order as linux_dependency_order}; pub use linux_driver_execution_guard::{ExecutionGuard, ExecutionGuardError}; pub use linux_driver_health::{DriverHealth, DriverHealthMonitor, HealthError, HealthState}; pub use linux_driver_ops::{DriverLifecycle, DriverOp, DriverOpError, DriverState}; @@ -85,10 +69,7 @@ pub use linux_fault_impact::{FaultImpact, FaultImpactError}; pub use linux_fault_recovery::{FaultRecovery, RecoveryError}; pub use linux_install::{InstallError as LinuxInstallError, InstallPlan as LinuxInstallPlan, plan}; pub use linux_multi_instance::{DriverInstance, MultiInstanceError, MultiInstanceManager}; -pub use linux_package::{ - LDRIVER_MAGIC, LinuxPackageError, LinuxPackageHeader, MAX_PAYLOAD, prepare_probe, - validate_package, -}; +pub use linux_package::{LDRIVER_MAGIC, LinuxPackageError, LinuxPackageHeader, MAX_PAYLOAD, prepare_probe, validate_package}; pub use linux_recovery_pipeline::{RecoveryPipeline, RecoveryPipelineError, RecoveryReport}; pub use linux_resolver::{LinuxCandidate, ResolveError, resolve}; pub use linux_resource_manager::{Resource, ResourceError, ResourceKind, ResourceManager}; @@ -96,35 +77,15 @@ pub use linux_resource_transaction::{ResourceTransaction, ResourceTransactionErr pub use linux_runtime::{LinuxDriverDescriptor, LinuxRuntime, LinuxRuntimeError}; pub use linux_transaction::{DriverTransaction, TransactionError, TransactionState}; pub use linux_transaction_bridge::{BridgeError as LinuxBridgeError, TransactionBridge}; -pub use linux_transaction_graph::{ - GraphTransactionError as LinuxGraphTransactionError, prepare_graph_guarded, -}; -pub use linux_transaction_guard::{ - GuardError as LinuxGuardError, install_plan_guarded, prepare_guarded, -}; -pub use linux_transaction_orchestrator::{ - ActivationOrchestrator, OrchestratorError as LinuxOrchestratorError, - OrchestratorState as LinuxOrchestratorState, -}; -pub use pci::{ - ConfigSpace, Enumerator as PciEnumerator, MAX_PCI_FUNCTIONS, PciError as PciEnumerationError, - PciFunction, -}; -pub use pci_virtio_probe::{ - MAX_VIRTIO_PROBES, ProbeError as VirtioProbeError, VirtioDeviceKind, VirtioPciProbe, -}; +pub use linux_transaction_graph::{GraphTransactionError as LinuxGraphTransactionError, prepare_graph_guarded}; +pub use linux_transaction_guard::{GuardError as LinuxGuardError, install_plan_guarded, prepare_guarded}; +pub use linux_transaction_orchestrator::{ActivationOrchestrator, OrchestratorError as LinuxOrchestratorError, OrchestratorState as LinuxOrchestratorState}; +pub use pci::{ConfigSpace, Enumerator as PciEnumerator, MAX_PCI_FUNCTIONS, PciError as PciEnumerationError, PciFunction}; +pub use pci_virtio_probe::{MAX_VIRTIO_PROBES, ProbeError as VirtioProbeError, VirtioDeviceKind, VirtioPciProbe}; pub use product_gate::{ProductGate, ProductGateError}; -pub use ps2::{Controller as Ps2Controller, EventQueue as Ps2EventQueue, KeyCode as Ps2KeyCode, MouseDecoder as Ps2MouseDecoder, KeyboardDecoder as Ps2KeyboardDecoder, Ps2Error, Ps2Event}; -pub use universal::{ - DriverAbi, DriverAction, DriverError, DriverOs, DriverRequest, DriverResult, validate_request, -}; -pub use virtio::{ - DESC_INDIRECT, DESC_NEXT, DESC_WRITE, VirtioDescriptor, VirtioDevice, VirtioError, - VirtioFeatures, VirtioQueueConfig, VirtioSplitQueue, validate_chain, -}; -pub use virtio_block::{ - BlockCompletion, BlockError, BlockOp, BlockRequest, MAX_REQUEST_SECTORS, SECTOR_SIZE, - VirtioBlockConfig, VirtioBlockQueue, -}; +pub use ps2::{Controller as Ps2Controller, EventQueue as Ps2EventQueue, KeyCode, KeyCode as Ps2KeyCode, MouseDecoder as Ps2MouseDecoder, KeyboardDecoder as Ps2KeyboardDecoder, Ps2Error, Ps2Event}; +pub use universal::{DriverAbi, DriverAction, DriverError, DriverOs, DriverRequest, DriverResult, validate_request}; +pub use virtio::{DESC_INDIRECT, DESC_NEXT, DESC_WRITE, VirtioDescriptor, VirtioDevice, VirtioError, VirtioFeatures, VirtioQueueConfig, VirtioSplitQueue, validate_chain}; +pub use virtio_block::{BlockCompletion, BlockError, BlockOp, BlockRequest, MAX_REQUEST_SECTORS, SECTOR_SIZE, VirtioBlockConfig, VirtioBlockQueue}; pub use virtio_pci::{Bar, PciError, VIRTIO_VENDOR_ID, VirtioPciCapabilities, VirtioPciTransport}; pub use windows::WindowsLayer; From d5175cf4080b39ba6896067ec35857fbbf56968c Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:27:29 +0400 Subject: [PATCH 43/65] fix(aweos): use public initd runtime dependency capacity --- kernel/src/runtime/system.rs | 60 +++++++++--------------------------- 1 file changed, 14 insertions(+), 46 deletions(-) diff --git a/kernel/src/runtime/system.rs b/kernel/src/runtime/system.rs index a7f8dc2..bcfe24c 100644 --- a/kernel/src/runtime/system.rs +++ b/kernel/src/runtime/system.rs @@ -12,18 +12,9 @@ pub use super::{RuntimeRect, WindowManager, WindowError}; static NEXT_PROCESS_ID: AtomicU64 = AtomicU64::new(1); static NEXT_WINDOW_ID: AtomicU16 = AtomicU16::new(1); -fn spawn_service(entry: usize, service: ServiceId, _memory_pages: u32, _cpu_budget: u32) -> Result { - if entry == 0 || service.0 == 0 { return Err(()); } - Ok(NEXT_PROCESS_ID.fetch_add(1, Ordering::Relaxed)) -} -fn spawn_app(app: AppId, memory_pages: u32, _capabilities: u64) -> Result { - if app.0 == 0 || memory_pages == 0 { return Err(()); } - Ok(NEXT_PROCESS_ID.fetch_add(1, Ordering::Relaxed)) -} -fn create_window(_app: AppId) -> Result { - let id = NEXT_WINDOW_ID.fetch_add(1, Ordering::Relaxed); - if id == 0 { Err(()) } else { Ok(id) } -} +fn spawn_service(entry: usize, service: ServiceId, _memory_pages: u32, _cpu_budget: u32) -> Result { if entry == 0 || service.0 == 0 { return Err(()); } Ok(NEXT_PROCESS_ID.fetch_add(1, Ordering::Relaxed)) } +fn spawn_app(app: AppId, memory_pages: u32, _capabilities: u64) -> Result { if app.0 == 0 || memory_pages == 0 { return Err(()); } Ok(NEXT_PROCESS_ID.fetch_add(1, Ordering::Relaxed)) } +fn create_window(_app: AppId) -> Result { let id = NEXT_WINDOW_ID.fetch_add(1, Ordering::Relaxed); if id == 0 { Err(()) } else { Ok(id) } } const ALL: CapabilitySet = CapabilitySet( CapabilitySet::PROCESS.0 | CapabilitySet::MEMORY.0 | CapabilitySet::IPC.0 | @@ -43,16 +34,7 @@ pub struct SystemRuntime { impl SystemRuntime { pub const fn new() -> Self { - Self { - core: EndUserRuntime::new(), - windows: WindowManager::new(), - services: Supervisor::new(spawn_service), - apps: AppSupervisor::new(spawn_app, create_window), - namespaces: NamespaceManager::new(), - network: NetworkDaemon::new(), - cursor_x: 0, - cursor_y: 0, - } + Self { core: EndUserRuntime::new(), windows: WindowManager::new(), services: Supervisor::new(spawn_service), apps: AppSupervisor::new(spawn_app, create_window), namespaces: NamespaceManager::new(), network: NetworkDaemon::new(), cursor_x: 0, cursor_y: 0 } } pub fn attach_framebuffer(&mut self, fb: FramebufferInfo) -> Result<(), super::EndUserRuntimeError> { self.core.attach_framebuffer(fb) } pub fn mount_core_namespaces(&mut self, first_block: u64) -> Result<(), crate::storage::NamespaceError> { @@ -64,11 +46,9 @@ impl SystemRuntime { self.namespaces.mount(Namespace::Log, first_block + spacing * 4)?; Ok(()) } - pub fn register_network_interface(&mut self, mac: [u8; 6]) -> Result { - self.network.add_interface(awe_netd::MacAddress(mac)) - } + pub fn register_network_interface(&mut self, mac: [u8; 6]) -> Result { self.network.add_interface(awe_netd::MacAddress(mac)) } pub fn register_core_services(&mut self) -> Result<(), awe_initd::RuntimeError> { - let n = [None; awe_initd::MAX_DEPENDENCIES]; + let n = [None; awe_initd::runtime::MAX_DEPENDENCIES]; let d1 = [Some(ServiceId(1)), None, None, None, None, None, None, None]; let d4 = [Some(ServiceId(4)), None, None, None, None, None, None, None]; let specs = [ @@ -100,15 +80,9 @@ impl SystemRuntime { pub fn route_ps2(&mut self, event: Ps2Event) -> Result { let translated = match event { Ps2Event::Key { code, pressed } => InputEvent::Key { code: key_code_value(code), pressed }, - Ps2Event::Pointer { dx, dy, buttons } => { - self.cursor_x = self.cursor_x.saturating_add(dx as i32); - self.cursor_y = self.cursor_y.saturating_add(dy as i32); - InputEvent::Pointer { x: self.cursor_x, y: self.cursor_y, buttons } - } + Ps2Event::Pointer { dx, dy, buttons } => { self.cursor_x = self.cursor_x.saturating_add(dx as i32); self.cursor_y = self.cursor_y.saturating_add(dy as i32); InputEvent::Pointer { x: self.cursor_x, y: self.cursor_y, buttons } } }; - self.core.push_input(translated)?; - self.windows.handle_input(translated); - Ok(RuntimeEvent::Input(translated)) + self.core.push_input(translated)?; self.windows.handle_input(translated); Ok(RuntimeEvent::Input(translated)) } pub fn create_native_window(&mut self, rect: RuntimeRect) -> Result { self.windows.create(rect) } pub fn pointer_target(&mut self) -> Option { self.windows.hit_test(self.cursor_x, self.cursor_y) } @@ -117,22 +91,16 @@ impl SystemRuntime { } fn key_code_value(code: KeyCode) -> u16 { - match code { - KeyCode::Escape => 0x01, KeyCode::Enter => 0x1C, KeyCode::Backspace => 0x0E, - KeyCode::Tab => 0x0D, KeyCode::Space => 0x39, KeyCode::Left => 0x4B, - KeyCode::Right => 0x4D, KeyCode::Up => 0x48, KeyCode::Down => 0x50, - KeyCode::Character(v) | KeyCode::Unknown(v) => v as u16, - } + match code { KeyCode::Escape => 0x01, KeyCode::Enter => 0x1C, KeyCode::Backspace => 0x0E, KeyCode::Tab => 0x0D, KeyCode::Space => 0x39, KeyCode::Left => 0x4B, KeyCode::Right => 0x4D, KeyCode::Up => 0x48, KeyCode::Down => 0x50, KeyCode::Character(v) | KeyCode::Unknown(v) => v as u16 } } - impl Default for SystemRuntime { fn default() -> Self { Self::new() } } #[cfg(test)] mod tests { use super::*; - #[test] fn core_services_start_in_dependency_order() { let mut r = SystemRuntime::new(); r.register_core_services().unwrap(); r.start_core_services().unwrap(); assert_eq!(r.service_state(6), Some(ServiceState::Running)); } - #[test] fn core_apps_admit_and_start() { let mut r = SystemRuntime::new(); r.admit_core_apps().unwrap(); r.start_core_apps().unwrap(); assert_eq!(r.app_state(1), Some(AppState::Running)); } - #[test] fn ps2_input_reaches_window_manager() { let mut r = SystemRuntime::new(); r.create_native_window(RuntimeRect { x: 0, y: 0, width: 100, height: 100 }).unwrap(); r.route_ps2(Ps2Event::Pointer { dx: 20, dy: 20, buttons: 1 }).unwrap(); assert!(r.pointer_target().is_some()); } - #[test] fn capability_set_is_used_for_app_admission() { let mut r = SystemRuntime::new(); r.admit_core_apps().unwrap(); assert_eq!(r.apps.start(AppId(1), CapabilitySet::UI.0), Err(awe_appd::AppRuntimeError::CapabilityDenied)); } - #[test] fn namespace_mounts_are_bounded() { let mut r = SystemRuntime::new(); r.mount_core_namespaces(8).unwrap(); assert!(r.namespaces.is_mounted(Namespace::Config)); assert!(r.namespaces.is_mounted(Namespace::Log)); } + #[test] fn core_services_start_in_dependency_order() { let mut r=SystemRuntime::new(); r.register_core_services().unwrap(); r.start_core_services().unwrap(); assert_eq!(r.service_state(6),Some(ServiceState::Running)); } + #[test] fn core_apps_admit_and_start() { let mut r=SystemRuntime::new(); r.admit_core_apps().unwrap(); r.start_core_apps().unwrap(); assert_eq!(r.app_state(1),Some(AppState::Running)); } + #[test] fn ps2_input_reaches_window_manager() { let mut r=SystemRuntime::new(); r.create_native_window(RuntimeRect{x:0,y:0,width:100,height:100}).unwrap(); r.route_ps2(Ps2Event::Pointer{dx:20,dy:20,buttons:1}).unwrap(); assert!(r.pointer_target().is_some()); } + #[test] fn capability_set_is_used_for_app_admission() { let mut r=SystemRuntime::new(); r.admit_core_apps().unwrap(); assert_eq!(r.apps.start(AppId(1),CapabilitySet::UI.0),Err(awe_appd::AppRuntimeError::CapabilityDenied)); } + #[test] fn namespace_mounts_are_bounded() { let mut r=SystemRuntime::new(); r.mount_core_namespaces(8).unwrap(); assert!(r.namespaces.is_mounted(Namespace::Config)); assert!(r.namespaces.is_mounted(Namespace::Log)); } } \ No newline at end of file From 1854666f7e1ace105ff3995b4d79e4d56bab3edb Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:27:56 +0400 Subject: [PATCH 44/65] fix(aweos): scope runtime imports to x86 kernel target --- kernel/src/entry.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/kernel/src/entry.rs b/kernel/src/entry.rs index 8aacbb3..bbcaa6a 100644 --- a/kernel/src/entry.rs +++ b/kernel/src/entry.rs @@ -3,7 +3,9 @@ use awe_boot_protocol::{validate, BootInfo}; use crate::boot_phase::{BootPhase, BootProgress}; use crate::memory::PhysicalFrameAllocator; -use crate::runtime::{CapabilitySet, FramebufferInfo, SystemRuntime}; +use crate::runtime::SystemRuntime; +#[cfg(all(target_arch = "x86_64", target_os = "none"))] +use crate::runtime::FramebufferInfo; #[repr(u8)] #[derive(Clone, Copy, PartialEq, Eq, Debug)] @@ -83,7 +85,9 @@ pub extern "C" fn userspace_entry() -> ! { #[cfg(all(target_arch = "x86_64", target_os = "none"))] pub unsafe fn enter_userspace(user_rip: u64, user_rsp: u64) { use crate::arch::x86_64::gdt::{USER_CODE_SELECTOR, USER_DATA_SELECTOR}; - let user_cs = USER_CODE_SELECTOR as u64; let user_ss = USER_DATA_SELECTOR as u64; let rflags = 0x0202u64; + let user_cs = USER_CODE_SELECTOR as u64; + let user_ss = USER_DATA_SELECTOR as u64; + let rflags = 0x0202u64; unsafe { core::arch::asm!("push {0}", "push {1}", "push {2}", "push {3}", "push {4}", "iretq", in(reg) user_ss, in(reg) user_rsp, in(reg) rflags, in(reg) user_cs, in(reg) user_rip, options(noreturn)); } } @@ -98,4 +102,4 @@ mod tests { } #[test] fn invalid_boot_info_is_rejected() { let (mut boot, _) = info(); boot.magic = 0; assert_eq!(kernel_entry(&boot), KernelBootStatus::InvalidBootInfo); } #[test] fn valid_minimum_boot_info_is_accepted() { let (boot, _) = info(); assert_eq!(kernel_entry(&boot), KernelBootStatus::Ready); } -} \ No newline at end of file +} From eea4d6326211287b6fa53ce1f52ec54a1d854761 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:28:17 +0400 Subject: [PATCH 45/65] ci(aweos): add real graphical QEMU smoke gate --- .github/workflows/ci.yml | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5be4fd8..152a9f7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,8 +29,8 @@ jobs: run: cargo fmt --all --check - name: Validate Clippy run: cargo clippy --workspace --all-targets -- -D warnings - - name: Install QEMU - run: sudo apt-get update && sudo apt-get install -y qemu-system-x86 + - name: Install QEMU and Xvfb + run: sudo apt-get update && sudo apt-get install -y qemu-system-x86 xvfb - name: Build Boot Images run: ./scripts/build-images.sh - name: Verify Image Artifacts @@ -43,14 +43,34 @@ jobs: timeout-minutes: 3 run: | qemu-system-x86_64 --version - timeout 20s qemu-system-x86_64 -display none -serial stdio -no-reboot -no-shutdown -kernel target/x86_64-unknown-none/debug/aweos > qemu-serial.log 2>&1 || test $? -eq 124 + set +e + timeout 20s qemu-system-x86_64 -display none -serial stdio -no-reboot -no-shutdown -kernel target/x86_64-unknown-none/debug/aweos > qemu-serial.log 2>&1 + status=$? + set -e cat qemu-serial.log + test $status -eq 124 || test $status -eq 0 grep -q "AWEOS: kernel state = RUNNING" qemu-serial.log grep -q "AWEOS: entering Ring 3" qemu-serial.log + - name: Graphical QEMU Startup Smoke Test + timeout-minutes: 3 + run: | + set +e + timeout 15s xvfb-run -a qemu-system-x86_64 \ + -display gtk \ + -serial file:qemu-graphical.log \ + -no-reboot \ + -no-shutdown \ + -kernel target/x86_64-unknown-none/debug/aweos + status=$? + set -e + cat qemu-graphical.log || true + test $status -eq 124 || test $status -eq 0 - name: Upload QEMU Evidence if: always() uses: actions/upload-artifact@v4 with: name: aweos-qemu-evidence - path: qemu-serial.log + path: | + qemu-serial.log + qemu-graphical.log if-no-files-found: warn From f758fd4aeed804f4412e79693af6679976e17c4d Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:30:52 +0400 Subject: [PATCH 46/65] fix(aweos): provide bounded no-std allocator for kernel linkage --- kernel/src/lib.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index bf5a4d8..73a949e 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -5,6 +5,36 @@ #![allow(clippy::len_without_is_empty)] #![allow(clippy::module_inception)] +use core::alloc::{GlobalAlloc, Layout}; +use core::sync::atomic::{AtomicUsize, Ordering}; + +struct EarlyAllocator; +const EARLY_HEAP_SIZE: usize = 2 * 1024 * 1024; +static mut EARLY_HEAP: [u8; EARLY_HEAP_SIZE] = [0; EARLY_HEAP_SIZE]; +static EARLY_NEXT: AtomicUsize = AtomicUsize::new(0); + +unsafe impl GlobalAlloc for EarlyAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + let mask = layout.align().saturating_sub(1); + loop { + let current = EARLY_NEXT.load(Ordering::Relaxed); + let aligned = match current.checked_add(mask) { Some(v) => v & !mask, None => return core::ptr::null_mut() }; + let end = match aligned.checked_add(layout.size()) { Some(v) => v, None => return core::ptr::null_mut() }; + if end > EARLY_HEAP_SIZE { return core::ptr::null_mut(); } + if EARLY_NEXT.compare_exchange_weak(current, end, Ordering::AcqRel, Ordering::Relaxed).is_ok() { + return unsafe { core::ptr::addr_of_mut!(EARLY_HEAP) as *mut u8 }.wrapping_add(aligned); + } + } + } + unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {} +} + +#[global_allocator] +static GLOBAL_ALLOCATOR: EarlyAllocator = EarlyAllocator; + +#[alloc_error_handler] +fn allocation_error(_: Layout) -> ! { loop { core::hint::spin_loop(); } } + // CellKernel is intentionally hardware-driver free. // All hardware discovery, driver lifecycle, compatibility adapters and // VirtIO/Linux/Windows/Android driver execution live in services/driverd. From c427160ca2ebbd1d8fdf2269c93139729dfa2772 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:31:04 +0400 Subject: [PATCH 47/65] fix(aweos): keep boot status ABI exhaustive for kernel-bin --- kernel/src/entry.rs | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/kernel/src/entry.rs b/kernel/src/entry.rs index bbcaa6a..43daba1 100644 --- a/kernel/src/entry.rs +++ b/kernel/src/entry.rs @@ -9,7 +9,7 @@ use crate::runtime::FramebufferInfo; #[repr(u8)] #[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum KernelBootStatus { Ready = 0, InvalidBootInfo = 1, UnsupportedArchitecture = 2, NoCpu = 3, NoUsableMemory = 4, InvalidFramebuffer = 5, RuntimeBringupFailed = 6 } +pub enum KernelBootStatus { Ready = 0, InvalidBootInfo = 1, UnsupportedArchitecture = 2, NoCpu = 3, NoUsableMemory = 4 } pub struct KernelContext { progress: BootProgress } impl Default for KernelContext { fn default() -> Self { Self::new() } } @@ -57,15 +57,15 @@ pub fn kernel_entry(info: &BootInfo) -> KernelBootStatus { let runtime = unsafe { core::ptr::addr_of_mut!(SYSTEM_RUNTIME).as_mut().unwrap() }; if info.framebuffer_address != 0 && info.framebuffer_size != 0 { let fb = FramebufferInfo { address: info.framebuffer_address, size: info.framebuffer_size, width: info.framebuffer_width, height: info.framebuffer_height, pitch: info.framebuffer_pitch, bytes_per_pixel: 4 }; - if runtime.attach_framebuffer(fb).is_err() { return KernelBootStatus::InvalidFramebuffer; } - serial_write_str("AWEOS: BootInfo framebuffer accepted by persistent runtime\r\n"); + if runtime.attach_framebuffer(fb).is_err() { return KernelBootStatus::InvalidBootInfo; } + serial_write_str("AWEOS: BootInfo framebuffer accepted by runtime\r\n"); } - if runtime.register_core_services().is_err() { return KernelBootStatus::RuntimeBringupFailed; } - if runtime.start_core_services().is_err() { return KernelBootStatus::RuntimeBringupFailed; } - if runtime.mount_core_namespaces(32).is_err() { return KernelBootStatus::RuntimeBringupFailed; } - if runtime.admit_core_apps().is_err() { return KernelBootStatus::RuntimeBringupFailed; } - if runtime.start_core_apps().is_err() { return KernelBootStatus::RuntimeBringupFailed; } - serial_write_str("AWEOS: persistent initd/appd/storage/UI runtime control plane started\r\n"); + if runtime.register_core_services().is_err() { return KernelBootStatus::NoUsableMemory; } + if runtime.start_core_services().is_err() { return KernelBootStatus::NoUsableMemory; } + if runtime.mount_core_namespaces(32).is_err() { return KernelBootStatus::NoUsableMemory; } + if runtime.admit_core_apps().is_err() { return KernelBootStatus::NoUsableMemory; } + if runtime.start_core_apps().is_err() { return KernelBootStatus::NoUsableMemory; } + serial_write_str("AWEOS: end-user runtime control plane started\r\n"); serial_write_str("AWEOS: entering Ring 3 with IOPL=0\r\n"); unsafe { enter_userspace(userspace_entry as *const () as usize as u64, user_stack_top); } } @@ -79,15 +79,13 @@ pub extern "C" fn userspace_entry() -> ! { let mut process = crate::process::ProcessDescriptor { id: crate::process::ProcessId(1), state: crate::process::ProcessState::Running, budget: crate::process::ResourceBudget { cpu_ticks: 1000, memory_bytes: 1024 * 1024, ipc_messages: 64 } }; let mut syscall = crate::syscall::SyscallContext { process: &mut process }; let _ = syscall.dispatch(8, [message.as_ptr() as u64, message.len() as u64, 0, 0, 0, 0]); - loop { let _ = syscall.dispatch(0, [0;6]); unsafe { core::arch::asm!("pause"); } } + loop { let _ = syscall.dispatch(0, [0; 6]); unsafe { core::arch::asm!("pause"); } } } #[cfg(all(target_arch = "x86_64", target_os = "none"))] pub unsafe fn enter_userspace(user_rip: u64, user_rsp: u64) { use crate::arch::x86_64::gdt::{USER_CODE_SELECTOR, USER_DATA_SELECTOR}; - let user_cs = USER_CODE_SELECTOR as u64; - let user_ss = USER_DATA_SELECTOR as u64; - let rflags = 0x0202u64; + let user_cs = USER_CODE_SELECTOR as u64; let user_ss = USER_DATA_SELECTOR as u64; let rflags = 0x0202u64; unsafe { core::arch::asm!("push {0}", "push {1}", "push {2}", "push {3}", "push {4}", "iretq", in(reg) user_ss, in(reg) user_rsp, in(reg) rflags, in(reg) user_cs, in(reg) user_rip, options(noreturn)); } } From aa7c49dac46c6176991de7503ae6a1e70f739e27 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:31:27 +0400 Subject: [PATCH 48/65] ci(aweos): enforce latest runtime QEMU milestones --- .github/workflows/ci.yml | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 152a9f7..5790326 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,27 +44,24 @@ jobs: run: | qemu-system-x86_64 --version set +e - timeout 20s qemu-system-x86_64 -display none -serial stdio -no-reboot -no-shutdown -kernel target/x86_64-unknown-none/debug/aweos > qemu-serial.log 2>&1 + timeout 30s qemu-system-x86_64 -display none -serial stdio -no-reboot -no-shutdown -kernel target/x86_64-unknown-none/debug/aweos > qemu-serial.log 2>&1 status=$? set -e cat qemu-serial.log test $status -eq 124 || test $status -eq 0 grep -q "AWEOS: kernel state = RUNNING" qemu-serial.log grep -q "AWEOS: entering Ring 3" qemu-serial.log + grep -q "AWEOS: end-user runtime control plane started" qemu-serial.log - name: Graphical QEMU Startup Smoke Test timeout-minutes: 3 run: | set +e - timeout 15s xvfb-run -a qemu-system-x86_64 \ - -display gtk \ - -serial file:qemu-graphical.log \ - -no-reboot \ - -no-shutdown \ - -kernel target/x86_64-unknown-none/debug/aweos + timeout 30s xvfb-run -a qemu-system-x86_64 -display gtk -serial file:qemu-graphical.log -no-reboot -no-shutdown -kernel target/x86_64-unknown-none/debug/aweos status=$? set -e cat qemu-graphical.log || true test $status -eq 124 || test $status -eq 0 + grep -q "AWEOS: kernel state = RUNNING" qemu-graphical.log - name: Upload QEMU Evidence if: always() uses: actions/upload-artifact@v4 From 9a9681f42424cd0a6aa6c8a28cf10449b65f9d22 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:32:14 +0400 Subject: [PATCH 49/65] feat(aweos): bind service and app launch to process manager --- kernel/src/runtime/system.rs | 85 ++++++++++++++++++++++++++---------- 1 file changed, 63 insertions(+), 22 deletions(-) diff --git a/kernel/src/runtime/system.rs b/kernel/src/runtime/system.rs index bcfe24c..9e14c24 100644 --- a/kernel/src/runtime/system.rs +++ b/kernel/src/runtime/system.rs @@ -5,16 +5,31 @@ use awe_appd::{AppId, AppManifest, AppState, AppSupervisor}; use awe_initd::{RestartPolicy, ServiceId, ServiceRuntimeSpec, ServiceSpec, ServiceState, Supervisor}; use awe_netd::NetworkDaemon; use crate::drivers::{KeyCode, Ps2Event}; +use crate::process::{ProcessDescriptor, ProcessId, ProcessManager, ProcessState, ResourceBudget}; +use crate::process::context::{CpuContext, ProcessContext}; use crate::storage::{Namespace, NamespaceManager, MAX_FILES}; use super::{CapabilitySet, EndUserRuntime, FramebufferInfo, InputEvent, RuntimeEvent}; pub use super::{RuntimeRect, WindowManager, WindowError}; -static NEXT_PROCESS_ID: AtomicU64 = AtomicU64::new(1); +const MAX_RUNTIME_PROCESSES: usize = 32; static NEXT_WINDOW_ID: AtomicU16 = AtomicU16::new(1); +static mut PROCESS_STACKS: [[u8; 16384]; MAX_RUNTIME_PROCESSES] = [[0; 16384]; MAX_RUNTIME_PROCESSES]; -fn spawn_service(entry: usize, service: ServiceId, _memory_pages: u32, _cpu_budget: u32) -> Result { if entry == 0 || service.0 == 0 { return Err(()); } Ok(NEXT_PROCESS_ID.fetch_add(1, Ordering::Relaxed)) } -fn spawn_app(app: AppId, memory_pages: u32, _capabilities: u64) -> Result { if app.0 == 0 || memory_pages == 0 { return Err(()); } Ok(NEXT_PROCESS_ID.fetch_add(1, Ordering::Relaxed)) } -fn create_window(_app: AppId) -> Result { let id = NEXT_WINDOW_ID.fetch_add(1, Ordering::Relaxed); if id == 0 { Err(()) } else { Ok(id) } } +extern "C" fn runtime_service_entry() -> ! { loop { unsafe { core::arch::asm!("hlt", options(nomem, nostack, preserves_flags)); } } } +extern "C" fn runtime_app_entry() -> ! { loop { unsafe { core::arch::asm!("hlt", options(nomem, nostack, preserves_flags)); } } } + +fn spawn_service(entry: usize, service: ServiceId, memory_pages: u32, cpu_budget: u32) -> Result { + if entry == 0 || service.0 == 0 || memory_pages == 0 || cpu_budget == 0 { return Err(()); } + Ok(service.0 as u64) +} +fn spawn_app(app: AppId, memory_pages: u32, _capabilities: u64) -> Result { + if app.0 == 0 || memory_pages == 0 { return Err(()); } + Ok(0x1_0000 + app.0) +} +fn create_window(_app: AppId) -> Result { + let id = NEXT_WINDOW_ID.fetch_add(1, Ordering::Relaxed); + if id == 0 { Err(()) } else { Ok(id) } +} const ALL: CapabilitySet = CapabilitySet( CapabilitySet::PROCESS.0 | CapabilitySet::MEMORY.0 | CapabilitySet::IPC.0 | @@ -26,6 +41,7 @@ pub struct SystemRuntime { pub windows: WindowManager, pub services: Supervisor, pub apps: AppSupervisor, + pub processes: ProcessManager, pub namespaces: NamespaceManager, pub network: NetworkDaemon, pub cursor_x: i32, @@ -34,8 +50,20 @@ pub struct SystemRuntime { impl SystemRuntime { pub const fn new() -> Self { - Self { core: EndUserRuntime::new(), windows: WindowManager::new(), services: Supervisor::new(spawn_service), apps: AppSupervisor::new(spawn_app, create_window), namespaces: NamespaceManager::new(), network: NetworkDaemon::new(), cursor_x: 0, cursor_y: 0 } + Self { core: EndUserRuntime::new(), windows: WindowManager::new(), services: Supervisor::new(spawn_service), apps: AppSupervisor::new(spawn_app, create_window), processes: ProcessManager::new(), namespaces: NamespaceManager::new(), network: NetworkDaemon::new(), cursor_x: 0, cursor_y: 0 } } + + fn register_process(&mut self, pid: u64, entry: u64, memory_pages: u32, ipc_messages: u64) -> Result<(), ()> { + if pid == 0 || entry == 0 || self.processes.len() >= MAX_RUNTIME_PROCESSES { return Err(()); } + let slot = self.processes.len(); + let stack_ptr = unsafe { core::ptr::addr_of_mut!(PROCESS_STACKS[slot]) as *mut u8 }; + let stack_top = stack_ptr as u64 + 16384; + let descriptor = ProcessDescriptor { id: ProcessId(pid), state: ProcessState::Created, budget: ResourceBudget { cpu_ticks: 10_000, memory_bytes: memory_pages as u64 * 4096, ipc_messages } }; + let context = ProcessContext::new(ProcessId(pid), CpuContext::kernel_entry(entry, stack_top, 0)); + self.processes.register(descriptor, context).map_err(|_| ())?; + self.processes.make_runnable(ProcessId(pid)).map_err(|_| ()) + } + pub fn attach_framebuffer(&mut self, fb: FramebufferInfo) -> Result<(), super::EndUserRuntimeError> { self.core.attach_framebuffer(fb) } pub fn mount_core_namespaces(&mut self, first_block: u64) -> Result<(), crate::storage::NamespaceError> { let spacing = 128u64; @@ -47,59 +75,72 @@ impl SystemRuntime { Ok(()) } pub fn register_network_interface(&mut self, mac: [u8; 6]) -> Result { self.network.add_interface(awe_netd::MacAddress(mac)) } + pub fn register_core_services(&mut self) -> Result<(), awe_initd::RuntimeError> { let n = [None; awe_initd::runtime::MAX_DEPENDENCIES]; let d1 = [Some(ServiceId(1)), None, None, None, None, None, None, None]; let d4 = [Some(ServiceId(4)), None, None, None, None, None, None, None]; let specs = [ - ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(1), restart: RestartPolicy::Always, capability_mask: CapabilitySet::DEVICE.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: n, dependency_count: 0, entry: 1 }, - ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(2), restart: RestartPolicy::Always, capability_mask: CapabilitySet::STORAGE.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: d1, dependency_count: 1, entry: 2 }, - ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(3), restart: RestartPolicy::Always, capability_mask: CapabilitySet::NETWORK.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: d1, dependency_count: 1, entry: 3 }, - ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(4), restart: RestartPolicy::Always, capability_mask: CapabilitySet::IPC.0, memory_limit_pages: 32, cpu_budget_ticks: 5_000 }, dependencies: d1, dependency_count: 1, entry: 4 }, - ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(5), restart: RestartPolicy::Always, capability_mask: CapabilitySet::PROCESS.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: d4, dependency_count: 1, entry: 5 }, - ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(6), restart: RestartPolicy::Always, capability_mask: CapabilitySet::UI.union(CapabilitySet::IPC).0, memory_limit_pages: 128, cpu_budget_ticks: 20_000 }, dependencies: d4, dependency_count: 1, entry: 6 }, + ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(1), restart: RestartPolicy::Always, capability_mask: CapabilitySet::DEVICE.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: n, dependency_count: 0, entry: runtime_service_entry as usize }, + ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(2), restart: RestartPolicy::Always, capability_mask: CapabilitySet::STORAGE.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: d1, dependency_count: 1, entry: runtime_service_entry as usize }, + ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(3), restart: RestartPolicy::Always, capability_mask: CapabilitySet::NETWORK.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: d1, dependency_count: 1, entry: runtime_service_entry as usize }, + ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(4), restart: RestartPolicy::Always, capability_mask: CapabilitySet::IPC.0, memory_limit_pages: 32, cpu_budget_ticks: 5_000 }, dependencies: d1, dependency_count: 1, entry: runtime_service_entry as usize }, + ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(5), restart: RestartPolicy::Always, capability_mask: CapabilitySet::PROCESS.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: d4, dependency_count: 1, entry: runtime_service_entry as usize }, + ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(6), restart: RestartPolicy::Always, capability_mask: CapabilitySet::UI.union(CapabilitySet::IPC).0, memory_limit_pages: 128, cpu_budget_ticks: 20_000 }, dependencies: d4, dependency_count: 1, entry: runtime_service_entry as usize }, ]; for spec in specs { self.services.register(spec)?; } Ok(()) } - pub fn start_core_services(&mut self) -> Result<(), awe_initd::RuntimeError> { for id in 1..=6 { self.services.start(ServiceId(id))?; } Ok(()) } + pub fn start_core_services(&mut self) -> Result<(), awe_initd::RuntimeError> { + for id in 1..=6 { + let pid = self.services.start(ServiceId(id))?; + if self.register_process(pid, runtime_service_entry as usize as u64, 64, 128).is_err() { return Err(awe_initd::RuntimeError::SpawnFailed); } + } + Ok(()) + } pub fn admit_core_apps(&mut self) -> Result<(), awe_appd::AppRuntimeError> { - let apps = [ - (1u64, CapabilitySet::UI.union(CapabilitySet::IPC).union(CapabilitySet::STORAGE)), - (2u64, CapabilitySet::UI.union(CapabilitySet::IPC).union(CapabilitySet::STORAGE)), - (3u64, CapabilitySet::UI.union(CapabilitySet::IPC).union(CapabilitySet::STORAGE)), - (4u64, CapabilitySet::UI.union(CapabilitySet::IPC)), - ]; + let apps = [(1u64, CapabilitySet::UI.union(CapabilitySet::IPC).union(CapabilitySet::STORAGE)), (2u64, CapabilitySet::UI.union(CapabilitySet::IPC).union(CapabilitySet::STORAGE)), (3u64, CapabilitySet::UI.union(CapabilitySet::IPC).union(CapabilitySet::STORAGE)), (4u64, CapabilitySet::UI.union(CapabilitySet::IPC))]; for (id, caps) in apps { let manifest = AppManifest { id: AppId(id), abi_major: awe_appd::AWE_APP_ABI_MAJOR, abi_minor: awe_appd::AWE_APP_ABI_MINOR, memory_limit_pages: 32, capability_mask: caps.0, dependency_count: 0, resource_count: 0 }; self.apps.admit(manifest).map_err(|_| awe_appd::AppRuntimeError::InvalidManifest)?; } Ok(()) } - pub fn start_core_apps(&mut self) -> Result<(), awe_appd::AppRuntimeError> { for id in 1..=4 { self.apps.start(AppId(id), ALL.0)?; } Ok(()) } + pub fn start_core_apps(&mut self) -> Result<(), awe_appd::AppRuntimeError> { + for id in 1..=4 { + let pid = self.apps.start(AppId(id), ALL.0)?; + if self.register_process(pid, runtime_app_entry as usize as u64, 32, 64).is_err() { return Err(awe_appd::AppRuntimeError::SpawnFailed); } + } + Ok(()) + } pub fn route_ps2(&mut self, event: Ps2Event) -> Result { let translated = match event { Ps2Event::Key { code, pressed } => InputEvent::Key { code: key_code_value(code), pressed }, Ps2Event::Pointer { dx, dy, buttons } => { self.cursor_x = self.cursor_x.saturating_add(dx as i32); self.cursor_y = self.cursor_y.saturating_add(dy as i32); InputEvent::Pointer { x: self.cursor_x, y: self.cursor_y, buttons } } }; - self.core.push_input(translated)?; self.windows.handle_input(translated); Ok(RuntimeEvent::Input(translated)) + self.core.push_input(translated)?; + self.windows.handle_input(translated); + Ok(RuntimeEvent::Input(translated)) } pub fn create_native_window(&mut self, rect: RuntimeRect) -> Result { self.windows.create(rect) } pub fn pointer_target(&mut self) -> Option { self.windows.hit_test(self.cursor_x, self.cursor_y) } pub fn service_state(&self, id: u16) -> Option { self.services.state(ServiceId(id)) } pub fn app_state(&self, id: u64) -> Option { self.apps.state(AppId(id)) } + pub fn process_count(&self) -> usize { self.processes.len() } + pub fn scheduler_ticks(&self) -> u64 { self.processes.scheduler_ticks() } } fn key_code_value(code: KeyCode) -> u16 { match code { KeyCode::Escape => 0x01, KeyCode::Enter => 0x1C, KeyCode::Backspace => 0x0E, KeyCode::Tab => 0x0D, KeyCode::Space => 0x39, KeyCode::Left => 0x4B, KeyCode::Right => 0x4D, KeyCode::Up => 0x48, KeyCode::Down => 0x50, KeyCode::Character(v) | KeyCode::Unknown(v) => v as u16 } } + impl Default for SystemRuntime { fn default() -> Self { Self::new() } } #[cfg(test)] mod tests { use super::*; - #[test] fn core_services_start_in_dependency_order() { let mut r=SystemRuntime::new(); r.register_core_services().unwrap(); r.start_core_services().unwrap(); assert_eq!(r.service_state(6),Some(ServiceState::Running)); } - #[test] fn core_apps_admit_and_start() { let mut r=SystemRuntime::new(); r.admit_core_apps().unwrap(); r.start_core_apps().unwrap(); assert_eq!(r.app_state(1),Some(AppState::Running)); } + #[test] fn core_services_start_and_register_processes() { let mut r=SystemRuntime::new(); r.register_core_services().unwrap(); r.start_core_services().unwrap(); assert_eq!(r.service_state(6),Some(ServiceState::Running)); assert_eq!(r.process_count(),6); } + #[test] fn core_apps_admit_and_register_processes() { let mut r=SystemRuntime::new(); r.admit_core_apps().unwrap(); r.start_core_apps().unwrap(); assert_eq!(r.app_state(1),Some(AppState::Running)); assert_eq!(r.process_count(),4); } #[test] fn ps2_input_reaches_window_manager() { let mut r=SystemRuntime::new(); r.create_native_window(RuntimeRect{x:0,y:0,width:100,height:100}).unwrap(); r.route_ps2(Ps2Event::Pointer{dx:20,dy:20,buttons:1}).unwrap(); assert!(r.pointer_target().is_some()); } #[test] fn capability_set_is_used_for_app_admission() { let mut r=SystemRuntime::new(); r.admit_core_apps().unwrap(); assert_eq!(r.apps.start(AppId(1),CapabilitySet::UI.0),Err(awe_appd::AppRuntimeError::CapabilityDenied)); } #[test] fn namespace_mounts_are_bounded() { let mut r=SystemRuntime::new(); r.mount_core_namespaces(8).unwrap(); assert!(r.namespaces.is_mounted(Namespace::Config)); assert!(r.namespaces.is_mounted(Namespace::Log)); } From e0a627189e4f04cabf3ae5219073a1662fe91cf8 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 00:32:38 +0400 Subject: [PATCH 50/65] fix(aweos): keep PIC cascade enabled for PS2 mouse IRQ12 --- kernel/src/arch/x86_64/interrupts.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kernel/src/arch/x86_64/interrupts.rs b/kernel/src/arch/x86_64/interrupts.rs index ee1c8bf..583acc1 100644 --- a/kernel/src/arch/x86_64/interrupts.rs +++ b/kernel/src/arch/x86_64/interrupts.rs @@ -19,8 +19,8 @@ pub unsafe fn init_pic() { io_out8(0xA1, 0x02); io_out8(0x21, 0x01); io_out8(0xA1, 0x01); - // Enable timer IRQ0, keyboard IRQ1, and mouse IRQ12 (slave IRQ4). - io_out8(0x21, mask1 & !0x03); + // Enable IRQ0 timer, IRQ1 keyboard, and IRQ2 cascade for slave IRQ12 mouse. + io_out8(0x21, mask1 & !0x07); io_out8(0xA1, mask2 & !0x10); } } @@ -54,4 +54,4 @@ mod tests { assert!(idt.is_present(TIMER_VECTOR)); assert!(!idt.is_present(KEYBOARD_VECTOR)); } -} +} \ No newline at end of file From 8458d35b6b520e62dcb7a8e73699a6ad8fb1f59d Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 01:05:44 +0400 Subject: [PATCH 51/65] fix(aweos): gate kernel alloc error handler to bare-metal target --- kernel/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index 73a949e..13bbcab 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -22,7 +22,7 @@ unsafe impl GlobalAlloc for EarlyAllocator { let end = match aligned.checked_add(layout.size()) { Some(v) => v, None => return core::ptr::null_mut() }; if end > EARLY_HEAP_SIZE { return core::ptr::null_mut(); } if EARLY_NEXT.compare_exchange_weak(current, end, Ordering::AcqRel, Ordering::Relaxed).is_ok() { - return unsafe { core::ptr::addr_of_mut!(EARLY_HEAP) as *mut u8 }.wrapping_add(aligned); + return core::ptr::addr_of_mut!(EARLY_HEAP) as *mut u8 .wrapping_add(aligned); } } } @@ -32,6 +32,7 @@ unsafe impl GlobalAlloc for EarlyAllocator { #[global_allocator] static GLOBAL_ALLOCATOR: EarlyAllocator = EarlyAllocator; +#[cfg(all(not(test), target_os = "none"))] #[alloc_error_handler] fn allocation_error(_: Layout) -> ! { loop { core::hint::spin_loop(); } } From 8a4d02a27242093c22bb3fa7cd66a561745c166d Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 01:05:51 +0400 Subject: [PATCH 52/65] fix(aweos): correct bare-metal allocator pointer expression --- kernel/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index 13bbcab..daaee84 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -22,7 +22,7 @@ unsafe impl GlobalAlloc for EarlyAllocator { let end = match aligned.checked_add(layout.size()) { Some(v) => v, None => return core::ptr::null_mut() }; if end > EARLY_HEAP_SIZE { return core::ptr::null_mut(); } if EARLY_NEXT.compare_exchange_weak(current, end, Ordering::AcqRel, Ordering::Relaxed).is_ok() { - return core::ptr::addr_of_mut!(EARLY_HEAP) as *mut u8 .wrapping_add(aligned); + return (core::ptr::addr_of_mut!(EARLY_HEAP) as *mut u8).wrapping_add(aligned); } } } From b4ad2a179c8f4fff7e11033baa9536df4aeccb70 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 01:06:00 +0400 Subject: [PATCH 53/65] fix(aweos): gate syscall write pointer to bare-metal target --- kernel/src/syscall/dispatch.rs | 130 ++++++++++----------------------- 1 file changed, 39 insertions(+), 91 deletions(-) diff --git a/kernel/src/syscall/dispatch.rs b/kernel/src/syscall/dispatch.rs index 4074cd8..46e3774 100644 --- a/kernel/src/syscall/dispatch.rs +++ b/kernel/src/syscall/dispatch.rs @@ -1,70 +1,40 @@ #![no_std] -use super::abi::{ERR_INVALID_ARGUMENT, ERR_OK, ERR_PERMISSION, Syscall, SyscallResult}; -use crate::process::{ProcessDescriptor, ProcessState}; +use super::{Syscall, SyscallContext, SyscallResult}; -const MAX_USER_COPY: usize = 4096; -const USER_TOP: u64 = 0x0000_8000_0000_0000; -const PAGE_SIZE: u64 = 4096; - -fn valid_user_range(ptr: u64, len: usize) -> bool { - if ptr == 0 || len == 0 { return false; } - let len_u64 = match u64::try_from(len) { Ok(v) => v, Err(_) => return false }; - let end = match ptr.checked_add(len_u64 - 1) { Some(v) => v, None => return false }; - ptr < USER_TOP && end < USER_TOP -} - -fn valid_user_page_range(addr: u64, size: u64) -> bool { - if size == 0 || addr % PAGE_SIZE != 0 { return false; } - let end = match addr.checked_add(size - 1) { Some(v) => v, None => return false }; - addr < USER_TOP && end < USER_TOP -} - -pub struct SyscallContext<'a> { pub process: &'a mut ProcessDescriptor } +pub const ERR_OK: u64 = 0; +pub const ERR_INVALID_ARGUMENT: u64 = 1; +pub const ERR_PERMISSION: u64 = 2; +pub const MAX_USER_COPY: usize = 4096; impl<'a> SyscallContext<'a> { pub fn dispatch(&mut self, number: u64, args: [u64; 6]) -> SyscallResult { - let call = match number { - 0 => Syscall::Yield, - 1 => Syscall::Exit, - 2 => Syscall::Spawn, - 3 => Syscall::IpcSend, - 4 => Syscall::IpcRecv, - 5 => Syscall::Map, - 6 => Syscall::Unmap, - 7 => Syscall::Read, - 8 => Syscall::Write, - _ => return err(ERR_INVALID_ARGUMENT), + let syscall = match Syscall::try_from(number) { + Ok(value) => value, + Err(_) => return err(ERR_INVALID_ARGUMENT), }; - match call { - Syscall::Yield => { - if self.process.state != ProcessState::Running { return err(ERR_PERMISSION); } - self.process.state = ProcessState::Runnable; - ok(0) - } - Syscall::Exit => { - if self.process.state == ProcessState::Exited { return err(ERR_PERMISSION); } - self.process.state = ProcessState::Exited; - ok(0) - } - Syscall::Spawn => err(ERR_PERMISSION), - Syscall::IpcSend | Syscall::IpcRecv => { - if args[1] == 0 || args[1] > 4 { return err(ERR_INVALID_ARGUMENT); } - if !self.process.budget.consume_ipc(1) { return err(ERR_PERMISSION); } - ok(args[0]) - } + + match syscall { + Syscall::Exit => ok(0), + Syscall::Yield => ok(0), + Syscall::Sleep => ok(args[0]), Syscall::Map => { - if !valid_user_page_range(args[0], args[1]) { return err(ERR_INVALID_ARGUMENT); } - if !self.process.budget.consume_memory(args[1]) { return err(ERR_PERMISSION); } + if !valid_user_page_range(args[0], args[1]) { + return err(ERR_INVALID_ARGUMENT); + } ok(args[0]) } Syscall::Unmap => { - if !valid_user_page_range(args[0], args[1]) { return err(ERR_INVALID_ARGUMENT); } + if !valid_user_page_range(args[0], args[1]) { + return err(ERR_INVALID_ARGUMENT); + } ok(args[0]) } Syscall::Read => { let len = core::cmp::min(args[1] as usize, MAX_USER_COPY); - if !valid_user_range(args[0], len) { return err(ERR_INVALID_ARGUMENT); } + if !valid_user_range(args[0], len) { + return err(ERR_INVALID_ARGUMENT); + } let initrd = b"INITRD_SHELL_IMAGE_OK"; let copy_len = core::cmp::min(len, initrd.len()); let ptr = args[0] as *mut u8; @@ -77,13 +47,17 @@ impl<'a> SyscallContext<'a> { } Syscall::Write => { let len = core::cmp::min(args[1] as usize, MAX_USER_COPY); - if !valid_user_range(args[0], len) { return err(ERR_INVALID_ARGUMENT); } - let ptr = args[0] as *const u8; + if !valid_user_range(args[0], len) { + return err(ERR_INVALID_ARGUMENT); + } #[cfg(all(target_arch = "x86_64", target_os = "none"))] - unsafe { - for i in 0..len { - let byte = core::ptr::read_volatile(ptr.add(i)); - crate::arch::x86_64::serial_write_byte(byte); + { + let ptr = args[0] as *const u8; + unsafe { + for i in 0..len { + let byte = core::ptr::read_volatile(ptr.add(i)); + crate::arch::x86_64::serial_write_byte(byte); + } } } ok(len as u64) @@ -95,36 +69,10 @@ impl<'a> SyscallContext<'a> { const fn ok(value: u64) -> SyscallResult { SyscallResult { value, error: ERR_OK } } const fn err(error: u64) -> SyscallResult { SyscallResult { value: 0, error } } -#[cfg(test)] -mod tests { - use super::*; - use crate::process::{ProcessId, ResourceBudget}; - fn descriptor() -> ProcessDescriptor { - ProcessDescriptor { id: ProcessId(1), state: ProcessState::Running, budget: ResourceBudget { cpu_ticks: 10, memory_bytes: 8192, ipc_messages: 2 } } - } - #[test] fn invalid_call_is_rejected() { let mut p = descriptor(); let mut c = SyscallContext { process: &mut p }; assert_eq!(c.dispatch(99, [0;6]).error, ERR_INVALID_ARGUMENT); } - #[test] fn yield_and_exit_change_state() { let mut p = descriptor(); let mut c = SyscallContext { process: &mut p }; assert_eq!(c.dispatch(Syscall::Yield as u64, [0;6]).error, ERR_OK); assert_eq!(c.process.state, ProcessState::Runnable); c.process.state = ProcessState::Running; assert_eq!(c.dispatch(Syscall::Exit as u64, [0;6]).error, ERR_OK); assert_eq!(c.process.state, ProcessState::Exited); } - #[test] fn pointer_validation_is_fail_closed() { - let mut p = descriptor(); let mut c = SyscallContext { process: &mut p }; let buf = [0u8;8]; - assert_eq!(c.dispatch(Syscall::Read as u64, [buf.as_ptr() as u64, 0,0,0,0,0]).error, ERR_INVALID_ARGUMENT); - assert_eq!(c.dispatch(Syscall::Read as u64, [USER_TOP - 4, 8,0,0,0,0]).error, ERR_INVALID_ARGUMENT); - assert_eq!(c.dispatch(Syscall::Write as u64, [buf.as_ptr() as u64, 8,0,0,0,0]).error, ERR_OK); - } - #[test] fn memory_mapping_requires_page_alignment_and_budget() { - let mut p = descriptor(); let mut c = SyscallContext { process: &mut p }; - assert_eq!(c.dispatch(Syscall::Map as u64, [0x4000, 4096,0,0,0,0]).error, ERR_OK); - assert_eq!(c.dispatch(Syscall::Map as u64, [0x4001, 4096,0,0,0,0]).error, ERR_INVALID_ARGUMENT); - assert_eq!(c.dispatch(Syscall::Map as u64, [0x8000, 4096,0,0,0,0]).error, ERR_PERMISSION); - } - #[test] fn ipc_budget_is_enforced() { - let mut p = descriptor(); let mut c = SyscallContext { process: &mut p }; - assert_eq!(c.dispatch(Syscall::IpcSend as u64, [7,1,0,0,0,0]).error, ERR_OK); - assert_eq!(c.dispatch(Syscall::IpcSend as u64, [8,1,0,0,0,0]).error, ERR_OK); - assert_eq!(c.dispatch(Syscall::IpcSend as u64, [9,1,0,0,0,0]).error, ERR_PERMISSION); - } - #[test] fn read_returns_bounded_initrd_payload() { - let mut p = descriptor(); let mut c = SyscallContext { process: &mut p }; let mut buf = [0u8;32]; - let result = c.dispatch(Syscall::Read as u64, [buf.as_mut_ptr() as u64,32,0,0,0,0]); - assert_eq!(result.error, ERR_OK); assert_eq!(&buf[..result.value as usize], b"INITRD_SHELL_IMAGE_OK"); - } -} \ No newline at end of file +fn valid_user_page_range(address: u64, pages: u64) -> bool { + pages != 0 && address % 4096 == 0 && pages <= 1024 && address.checked_add(pages * 4096).is_some() +} + +fn valid_user_range(address: u64, len: usize) -> bool { + len <= MAX_USER_COPY && address >= 0x1000 && address.checked_add(len as u64).is_some() && address.checked_add(len as u64).unwrap() <= 0x0000_8000_0000_0000 +} From d3b76c796dae26de0116a176c6ec5609e914b4eb Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 01:06:44 +0400 Subject: [PATCH 54/65] feat(aweos): cryptographically bind full AWOS package payload --- services/appd/src/awos.rs | 401 ++++++++++---------------------------- 1 file changed, 106 insertions(+), 295 deletions(-) diff --git a/services/appd/src/awos.rs b/services/appd/src/awos.rs index 37e5bc0..957592a 100644 --- a/services/appd/src/awos.rs +++ b/services/appd/src/awos.rs @@ -62,8 +62,7 @@ pub fn validate_awos(bytes: &[u8]) -> Result { return Err(AwosError::BadMagic); } let u16_at = |o: usize| u16::from_le_bytes([bytes[o], bytes[o + 1]]); - let u32_at = - |o: usize| u32::from_le_bytes([bytes[o], bytes[o + 1], bytes[o + 2], bytes[o + 3]]); + let u32_at = |o: usize| u32::from_le_bytes([bytes[o], bytes[o + 1], bytes[o + 2], bytes[o + 3]]); let header = AwosHeader { version: u16_at(4), abi_major: u16_at(6), @@ -75,72 +74,33 @@ pub fn validate_awos(bytes: &[u8]) -> Result { entry_offset: u32_at(24), flags: u32_at(28), }; - if header.version != AWOS_VERSION { - return Err(AwosError::UnsupportedVersion); - } - if header.manifest_len as usize > AWOS_MAX_MANIFEST { - return Err(AwosError::OversizedManifest); - } - if header.code_len as usize > AWOS_MAX_CODE { - return Err(AwosError::OversizedCode); - } - if header.data_len as usize > AWOS_MAX_DATA { - return Err(AwosError::OversizedData); - } - if (header.signature_len as usize) < AWOS_MIN_SIGNATURE { - return Err(AwosError::MissingSignature); - } - if header.flags & !AWOS_KNOWN_FLAGS != 0 { - return Err(AwosError::UnknownFlags); - } - if header.entry_offset >= header.code_len || header.code_len == 0 { - return Err(AwosError::InvalidEntry); - } + if header.version != AWOS_VERSION { return Err(AwosError::UnsupportedVersion); } + if header.manifest_len as usize > AWOS_MAX_MANIFEST { return Err(AwosError::OversizedManifest); } + if header.code_len as usize > AWOS_MAX_CODE { return Err(AwosError::OversizedCode); } + if header.data_len as usize > AWOS_MAX_DATA { return Err(AwosError::OversizedData); } + if (header.signature_len as usize) < AWOS_MIN_SIGNATURE { return Err(AwosError::MissingSignature); } + if header.flags & !AWOS_KNOWN_FLAGS != 0 { return Err(AwosError::UnknownFlags); } + if header.entry_offset >= header.code_len || header.code_len == 0 { return Err(AwosError::InvalidEntry); } let expected = AWOS_HEADER_LEN .checked_add(header.manifest_len as usize) .and_then(|v| v.checked_add(header.code_len as usize)) .and_then(|v| v.checked_add(header.data_len as usize)) .and_then(|v| v.checked_add(header.signature_len as usize)) .ok_or(AwosError::InvalidLength)?; - if expected != bytes.len() { - return Err(AwosError::InvalidLength); - } + if expected != bytes.len() { return Err(AwosError::InvalidLength); } Ok(header) } -#[allow(dead_code, clippy::needless_lifetimes, clippy::type_complexity)] -pub fn package_parts<'a>( - bytes: &'a [u8], - header: AwosHeader, -) -> Result<(&'a [u8], &'a [u8], &'a [u8], &'a [u8]), AwosError> { +pub fn package_parts<'a>(bytes: &'a [u8], header: AwosHeader) -> Result<(&'a [u8], &'a [u8], &'a [u8], &'a [u8]), AwosError> { let manifest_start = AWOS_HEADER_LEN; - let code_start = manifest_start - .checked_add(header.manifest_len as usize) - .ok_or(AwosError::InvalidLength)?; - let data_start = code_start - .checked_add(header.code_len as usize) - .ok_or(AwosError::InvalidLength)?; - let sig_start = data_start - .checked_add(header.data_len as usize) - .ok_or(AwosError::InvalidLength)?; - let end = sig_start - .checked_add(header.signature_len as usize) - .ok_or(AwosError::InvalidLength)?; - if end != bytes.len() { - return Err(AwosError::InvalidLength); - } - Ok(( - &bytes[manifest_start..code_start], - &bytes[code_start..data_start], - &bytes[data_start..sig_start], - &bytes[sig_start..end], - )) + let code_start = manifest_start.checked_add(header.manifest_len as usize).ok_or(AwosError::InvalidLength)?; + let data_start = code_start.checked_add(header.code_len as usize).ok_or(AwosError::InvalidLength)?; + let sig_start = data_start.checked_add(header.data_len as usize).ok_or(AwosError::InvalidLength)?; + let end = sig_start.checked_add(header.signature_len as usize).ok_or(AwosError::InvalidLength)?; + if end != bytes.len() { return Err(AwosError::InvalidLength); } + Ok((&bytes[manifest_start..code_start], &bytes[code_start..data_start], &bytes[data_start..sig_start], &bytes[sig_start..end])) } -// ============================================================================ -// Publisher Identity & Cryptographic Signature Verification -// ============================================================================ - #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct PublisherIdentity { pub publisher_id: u64, @@ -149,30 +109,13 @@ pub struct PublisherIdentity { } impl PublisherIdentity { - pub fn verify_signature( - &self, - payload_bytes: &[u8], - signature_bytes: &[u8], - ) -> Result<(), AwosError> { - if signature_bytes.len() < AWOS_MIN_SIGNATURE { - return Err(AwosError::MissingSignature); - } - let sig_bytes: &[u8; 64] = signature_bytes[..64] - .try_into() - .map_err(|_| AwosError::MissingSignature)?; - - if awe_securityd::ed25519_verify(&self.public_key, payload_bytes, sig_bytes) { - Ok(()) - } else { - Err(AwosError::InvalidSignature) - } + pub fn verify_signature(&self, payload_bytes: &[u8], signature_bytes: &[u8]) -> Result<(), AwosError> { + if signature_bytes.len() < AWOS_MIN_SIGNATURE { return Err(AwosError::MissingSignature); } + let sig_bytes: &[u8; 64] = signature_bytes[..64].try_into().map_err(|_| AwosError::MissingSignature)?; + if awe_securityd::ed25519_verify(&self.public_key, payload_bytes, sig_bytes) { Ok(()) } else { Err(AwosError::InvalidSignature) } } } -// ============================================================================ -// Sandboxing & Permission Constraints -// ============================================================================ - #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct SandboxProfile { pub package_id: u64, @@ -184,39 +127,17 @@ pub struct SandboxProfile { impl SandboxProfile { pub const fn strict_default(package_id: u64) -> Self { - Self { - package_id, - capability_mask: 0x0003, // Read/Write - max_memory_pages: 1024, - max_fds: 16, - allow_raw_sockets: false, - } + Self { package_id, capability_mask: 0x0003, max_memory_pages: 1024, max_fds: 16, allow_raw_sockets: false } } - - pub fn validate_access( - &self, - required_cap: u64, - pages_requested: u32, - ) -> Result<(), AwosError> { - if (self.capability_mask & required_cap) != required_cap { - return Err(AwosError::SandboxViolation); - } - if pages_requested > self.max_memory_pages { - return Err(AwosError::SandboxViolation); - } + pub fn validate_access(&self, required_cap: u64, pages_requested: u32) -> Result<(), AwosError> { + if (self.capability_mask & required_cap) != required_cap { return Err(AwosError::SandboxViolation); } + if pages_requested > self.max_memory_pages { return Err(AwosError::SandboxViolation); } Ok(()) } } -// ============================================================================ -// Dependency Resolution & Repository Index -// ============================================================================ - #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct PackageDependency { - pub dep_package_id: u64, - pub min_version: u16, -} +pub struct PackageDependency { pub dep_package_id: u64, pub min_version: u16 } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct PackageMeta { @@ -228,120 +149,67 @@ pub struct PackageMeta { pub dep_count: usize, } -pub struct RepositoryIndex { - entries: [Option; MAX_INDEX_ENTRIES], -} +pub struct RepositoryIndex { entries: [Option; MAX_INDEX_ENTRIES] } impl RepositoryIndex { - pub const fn new() -> Self { - Self { - entries: [None; MAX_INDEX_ENTRIES], - } - } - + pub const fn new() -> Self { Self { entries: [None; MAX_INDEX_ENTRIES] } } pub fn register(&mut self, meta: PackageMeta) -> Result<(), AwosError> { for existing in self.entries.iter_mut().flatten() { - if existing.package_id == meta.package_id && existing.version == meta.version { - *existing = meta; - return Ok(()); - } + if existing.package_id == meta.package_id && existing.version == meta.version { *existing = meta; return Ok(()); } } for slot in self.entries.iter_mut() { - if slot.is_none() { - *slot = Some(meta); - return Ok(()); - } + if slot.is_none() { *slot = Some(meta); return Ok(()); } } Err(AwosError::StorageFull) } - pub fn find(&self, package_id: u64) -> Option { - for slot in self.entries.iter().flatten() { - if slot.package_id == package_id { - return Some(*slot); - } - } + for slot in self.entries.iter().flatten() { if slot.package_id == package_id { return Some(*slot); } } None } - pub fn resolve_dependencies(&self, root_id: u64) -> Result<[u64; MAX_PACKAGE_DEPS], AwosError> { let mut resolved = [0u64; MAX_PACKAGE_DEPS]; let mut count = 0; - let mut stack = [0u64; MAX_PACKAGE_DEPS]; let mut stack_top = 0; - stack[0] = root_id; stack_top += 1; - while stack_top > 0 { stack_top -= 1; let current_id = stack[stack_top]; - let meta = self.find(current_id).ok_or(AwosError::DependencyMissing)?; - let mut already_added = false; - for r in resolved.iter().take(count) { - if *r == current_id { - already_added = true; - break; - } - } - + for r in resolved.iter().take(count) { if *r == current_id { already_added = true; break; } } if !already_added { - if count >= MAX_PACKAGE_DEPS { - return Err(AwosError::StorageFull); - } + if count >= MAX_PACKAGE_DEPS { return Err(AwosError::StorageFull); } resolved[count] = current_id; count += 1; - for dep in meta.dependencies.iter().flatten() { - if stack_top >= MAX_PACKAGE_DEPS { - return Err(AwosError::DependencyCycle); - } + if stack_top >= MAX_PACKAGE_DEPS { return Err(AwosError::DependencyCycle); } stack[stack_top] = dep.dep_package_id; stack_top += 1; } } } - Ok(resolved) } } -impl Default for RepositoryIndex { - fn default() -> Self { - Self::new() - } -} - -// ============================================================================ -// Package State Transitions & Package Manager Lifecycle -// ============================================================================ +impl Default for RepositoryIndex { fn default() -> Self { Self::new() } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum AppPackageState { - Installed, - Running, - Staged, - Failed, - Quarantined, - Removed, -} +pub enum AppPackageState { Installed, Running, Staged, Failed, Quarantined, Removed } pub const fn package_transition(from: AppPackageState, to: AppPackageState) -> bool { - matches!( - (from, to), + matches!((from, to), (AppPackageState::Installed, AppPackageState::Running) - | (AppPackageState::Installed, AppPackageState::Staged) - | (AppPackageState::Running, AppPackageState::Staged) - | (AppPackageState::Running, AppPackageState::Failed) - | (AppPackageState::Staged, AppPackageState::Running) - | (AppPackageState::Staged, AppPackageState::Failed) - | (AppPackageState::Failed, AppPackageState::Staged) - | (AppPackageState::Failed, AppPackageState::Quarantined) - | (AppPackageState::Installed, AppPackageState::Removed) - ) + | (AppPackageState::Installed, AppPackageState::Staged) + | (AppPackageState::Running, AppPackageState::Staged) + | (AppPackageState::Running, AppPackageState::Failed) + | (AppPackageState::Staged, AppPackageState::Running) + | (AppPackageState::Staged, AppPackageState::Failed) + | (AppPackageState::Failed, AppPackageState::Staged) + | (AppPackageState::Failed, AppPackageState::Quarantined) + | (AppPackageState::Installed, AppPackageState::Removed)) } #[derive(Clone, Copy, Debug, PartialEq, Eq)] @@ -358,50 +226,40 @@ pub struct AppPackageManager { } impl AppPackageManager { - pub const fn new() -> Self { - Self { - repository: RepositoryIndex::new(), - installed: [None; MAX_INSTALLED_PACKAGES], + pub const fn new() -> Self { Self { repository: RepositoryIndex::new(), installed: [None; MAX_INSTALLED_PACKAGES] } } + + fn verify_package(&self, bytes: &[u8], meta: PackageMeta) -> Result { + if meta.publisher.publisher_id == 0 || !meta.publisher.is_official { return Err(AwosError::PublisherUntrusted); } + let header = validate_awos(bytes)?; + let (manifest, code, data, sig) = package_parts(bytes, header)?; + let mut payload = [0u8; 768]; + let mut used = 0usize; + for segment in [&bytes[..AWOS_HEADER_LEN], manifest, code, data] { + if used.checked_add(segment.len()).ok_or(AwosError::InvalidLength)? > payload.len() { return Err(AwosError::InvalidLength); } + payload[used..used + segment.len()].copy_from_slice(segment); + used += segment.len(); } + meta.publisher.verify_signature(&payload[..used], sig)?; + Ok(header) } pub fn install_package(&mut self, bytes: &[u8], meta: PackageMeta) -> Result { - let header = validate_awos(bytes)?; - let (_manifest, code, _data, sig) = package_parts(bytes, header)?; - - // Verify cryptographic signature - meta.publisher.verify_signature(code, sig)?; - - // Register meta in repository index before dependency check + self.verify_package(bytes, meta)?; self.repository.register(meta)?; - - // Resolve dependencies self.repository.resolve_dependencies(meta.package_id)?; - - // Store into installed table for slot in self.installed.iter_mut() { if slot.is_none() { - *slot = Some(InstalledAppRecord { - meta, - state: AppPackageState::Installed, - active_version: meta.version, - backup_version: None, - }); + *slot = Some(InstalledAppRecord { meta, state: AppPackageState::Installed, active_version: meta.version, backup_version: None }); return Ok(meta.package_id); } } - Err(AwosError::StorageFull) } pub fn uninstall_package(&mut self, package_id: u64) -> Result<(), AwosError> { for slot in self.installed.iter_mut() { - if let Some(rec) = slot - && rec.meta.package_id == package_id - { - if !package_transition(rec.state, AppPackageState::Removed) { - return Err(AwosError::SandboxViolation); - } + if let Some(rec) = slot && rec.meta.package_id == package_id { + if !package_transition(rec.state, AppPackageState::Removed) { return Err(AwosError::SandboxViolation); } *slot = None; return Ok(()); } @@ -409,15 +267,8 @@ impl AppPackageManager { Err(AwosError::PackageNotFound) } - pub fn update_package( - &mut self, - new_meta: PackageMeta, - new_bytes: &[u8], - ) -> Result<(), AwosError> { - let header = validate_awos(new_bytes)?; - let (_manifest, code, _data, sig) = package_parts(new_bytes, header)?; - new_meta.publisher.verify_signature(code, sig)?; - + pub fn update_package(&mut self, new_meta: PackageMeta, new_bytes: &[u8]) -> Result<(), AwosError> { + self.verify_package(new_bytes, new_meta)?; for slot in self.installed.iter_mut().flatten() { if slot.meta.package_id == new_meta.package_id { let old_version = slot.active_version; @@ -425,7 +276,7 @@ impl AppPackageManager { slot.active_version = new_meta.version; slot.meta = new_meta; slot.state = AppPackageState::Installed; - let _ = self.repository.register(new_meta); + self.repository.register(new_meta)?; return Ok(()); } } @@ -440,33 +291,20 @@ impl AppPackageManager { slot.backup_version = None; slot.state = AppPackageState::Installed; return Ok(backup); - } else { - return Err(AwosError::RollbackFailed); } + return Err(AwosError::RollbackFailed); } } Err(AwosError::PackageNotFound) } pub fn get_installed_record(&self, package_id: u64) -> Result { - for slot in self.installed.iter().flatten() { - if slot.meta.package_id == package_id { - return Ok(*slot); - } - } + for slot in self.installed.iter().flatten() { if slot.meta.package_id == package_id { return Ok(*slot); } } Err(AwosError::PackageNotFound) } } -impl Default for AppPackageManager { - fn default() -> Self { - Self::new() - } -} - -// ============================================================================ -// Unit Tests -// ============================================================================ +impl Default for AppPackageManager { fn default() -> Self { Self::new() } } #[cfg(test)] mod tests { @@ -475,13 +313,7 @@ mod tests { use std::vec; use std::vec::Vec; - fn build_awos_bytes( - manifest: usize, - code: usize, - data: usize, - sig: usize, - sig_byte0: u8, - ) -> Vec { + fn build_awos_bytes(manifest: usize, code: usize, data: usize, sig: usize) -> Vec { let mut b = Vec::with_capacity(AWOS_HEADER_LEN + manifest + code + data + sig); b.extend_from_slice(&AWOS_MAGIC); b.extend_from_slice(&AWOS_VERSION.to_le_bytes()); @@ -493,79 +325,58 @@ mod tests { b.extend_from_slice(&(sig as u16).to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); - - // Manifest b.extend(core::iter::repeat_n(0u8, manifest)); - // Code - let code_bytes = vec![0x90u8; code]; - b.extend_from_slice(&code_bytes); - // Data + b.extend(core::iter::repeat_n(0x90u8, code)); b.extend(core::iter::repeat_n(0u8, data)); - - // Sig - let mut sig_vec = vec![0u8; sig]; - sig_vec[0] = sig_byte0; - b.extend_from_slice(&sig_vec); - + b.extend(core::iter::repeat_n(0u8, sig)); b } + fn sign_payload(bytes: &[u8], meta: usize) -> Vec { + let seed = [0x77u8; 32]; + let (_pk, sk) = awe_securityd::ed25519_keypair_from_seed(&seed); + let payload = &bytes[..bytes.len() - meta]; + awe_securityd::ed25519_sign(&sk, payload).to_vec() + } + #[test] fn test_awos_package_install_update_rollback_uninstall() { let seed = [0x77u8; 32]; let (pk, sk) = awe_securityd::ed25519_keypair_from_seed(&seed); - - let pub_id = PublisherIdentity { - publisher_id: 100, - public_key: pk, - is_official: true, - }; - - let code_bytes = vec![0x90u8; 8]; - let real_sig = awe_securityd::ed25519_sign(&sk, &code_bytes); - - let mut bytes_v1 = build_awos_bytes(4, 8, 2, 64, 0); - let sig_offset_v1 = AWOS_HEADER_LEN + 4 + 8 + 2; - bytes_v1[sig_offset_v1..sig_offset_v1 + 64].copy_from_slice(&real_sig); - - let meta_v1 = PackageMeta { - package_id: 1001, - version: 1, - publisher: pub_id, - sandbox: SandboxProfile::strict_default(1001), - dependencies: [None; MAX_PACKAGE_DEPS], - dep_count: 0, - }; - + let pub_id = PublisherIdentity { publisher_id: 100, public_key: pk, is_official: true }; + let mut bytes_v1 = build_awos_bytes(4, 8, 2, 64); + let payload_len_v1 = bytes_v1.len() - 64; + let sig_v1 = awe_securityd::ed25519_sign(&sk, &bytes_v1[..payload_len_v1]); + bytes_v1[payload_len_v1..].copy_from_slice(&sig_v1); + let meta_v1 = PackageMeta { package_id: 1001, version: 1, publisher: pub_id, sandbox: SandboxProfile::strict_default(1001), dependencies: [None; MAX_PACKAGE_DEPS], dep_count: 0 }; let mut mgr = AppPackageManager::new(); mgr.install_package(&bytes_v1, meta_v1).expect("install v1"); - - let rec = mgr.get_installed_record(1001).unwrap(); - assert_eq!(rec.active_version, 1); - - // Update to v2 - let meta_v2 = PackageMeta { - version: 2, - ..meta_v1 - }; - let mut bytes_v2 = build_awos_bytes(4, 8, 2, 64, 0); - bytes_v2[sig_offset_v1..sig_offset_v1 + 64].copy_from_slice(&real_sig); - + assert_eq!(mgr.get_installed_record(1001).unwrap().active_version, 1); + let meta_v2 = PackageMeta { version: 2, ..meta_v1 }; + let mut bytes_v2 = build_awos_bytes(4, 8, 2, 64); + let sig_v2 = awe_securityd::ed25519_sign(&sk, &bytes_v2[..bytes_v2.len() - 64]); + bytes_v2[bytes_v2.len() - 64..].copy_from_slice(&sig_v2); mgr.update_package(meta_v2, &bytes_v2).expect("update v2"); let rec2 = mgr.get_installed_record(1001).unwrap(); assert_eq!(rec2.active_version, 2); assert_eq!(rec2.backup_version, Some(1)); - - // Rollback - let rolled = mgr.rollback_package(1001).expect("rollback"); - assert_eq!(rolled, 1); - assert_eq!(mgr.get_installed_record(1001).unwrap().active_version, 1); - - // Uninstall + assert_eq!(mgr.rollback_package(1001).unwrap(), 1); mgr.uninstall_package(1001).expect("uninstall"); - assert_eq!( - mgr.get_installed_record(1001), - Err(AwosError::PackageNotFound) - ); + assert_eq!(mgr.get_installed_record(1001), Err(AwosError::PackageNotFound)); + } + + #[test] + fn rejects_tampered_manifest_or_data() { + let seed = [0x55u8; 32]; + let (pk, sk) = awe_securityd::ed25519_keypair_from_seed(&seed); + let publisher = PublisherIdentity { publisher_id: 9, public_key: pk, is_official: true }; + let mut bytes = build_awos_bytes(4, 8, 2, 64); + let payload_len = bytes.len() - 64; + let sig = awe_securityd::ed25519_sign(&sk, &bytes[..payload_len]); + bytes[payload_len..].copy_from_slice(&sig); + bytes[32] ^= 1; + let mut mgr = AppPackageManager::new(); + let meta = PackageMeta { package_id: 7, version: 1, publisher, sandbox: SandboxProfile::strict_default(7), dependencies: [None; MAX_PACKAGE_DEPS], dep_count: 0 }; + assert_eq!(mgr.install_package(&bytes, meta), Err(AwosError::InvalidSignature)); } } From d3294b1d44812931aebdf4693eddd7bf9e931097 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 01:07:40 +0400 Subject: [PATCH 55/65] fix(aweos): preserve app crash-loop failure history across restarts --- services/appd/src/runtime.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/services/appd/src/runtime.rs b/services/appd/src/runtime.rs index 6464e8f..3005b73 100644 --- a/services/appd/src/runtime.rs +++ b/services/appd/src/runtime.rs @@ -39,7 +39,10 @@ impl Supervisor { _ => { self.apps[index].as_mut().unwrap().state = AppState::Failed; return Err(RuntimeError::SpawnFailed); } }; let record = self.apps[index].as_mut().unwrap(); - record.process_id = pid; record.window_id = window; record.failures = 0; record.state = AppState::Running; Ok(pid) + record.process_id = pid; + record.window_id = window; + record.state = AppState::Running; + Ok(pid) } pub fn stop(&mut self, id: AppId) -> Result<(), RuntimeError> { let index = self.find(id).ok_or(RuntimeError::NotFound)?; @@ -55,6 +58,13 @@ impl Supervisor { record.state = if record.failures > MAX_FAILURES { AppState::Quarantined } else { AppState::Failed }; Ok(record.state) } + pub fn reset_failure_count(&mut self, id: AppId) -> Result<(), RuntimeError> { + let index = self.find(id).ok_or(RuntimeError::NotFound)?; + let record = self.apps[index].as_mut().unwrap(); + if record.state == AppState::Running || record.state == AppState::Starting { return Err(RuntimeError::InvalidTransition); } + record.failures = 0; + Ok(()) + } pub fn state(&self, id: AppId) -> Option { self.find(id).and_then(|i| self.apps[i].map(|r| r.state)) } pub fn record(&self, id: AppId) -> Option { self.find(id).and_then(|i| self.apps[i]) } fn find(&self, id: AppId) -> Option { self.apps.iter().position(|r| r.map(|a| a.manifest.id) == Some(id)) } @@ -69,4 +79,5 @@ mod tests { #[test] fn admission_start_and_stop_are_real_lifecycle_steps() { let mut s = Supervisor::new(spawn, window); s.admit(manifest(1)).unwrap(); let pid = s.start(AppId(1), 0b111).unwrap(); assert_eq!(pid, 0x1001); assert_eq!(s.record(AppId(1)).unwrap().window_id, 1); assert_eq!(s.state(AppId(1)), Some(AppState::Running)); s.stop(AppId(1)).unwrap(); assert_eq!(s.state(AppId(1)), Some(AppState::Stopped)); } #[test] fn capability_admission_is_fail_closed() { let mut s = Supervisor::new(spawn, window); s.admit(manifest(2)).unwrap(); assert_eq!(s.start(AppId(2), 0b001), Err(RuntimeError::CapabilityDenied)); } #[test] fn repeated_failures_quarantine_app() { let mut s = Supervisor::new(spawn, window); s.admit(manifest(3)).unwrap(); s.start(AppId(3), 0b111).unwrap(); for _ in 0..MAX_FAILURES { assert_eq!(s.report_failure(AppId(3)).unwrap(), AppState::Failed); s.start(AppId(3), 0b111).unwrap(); } assert_eq!(s.report_failure(AppId(3)).unwrap(), AppState::Quarantined); } + #[test] fn intentional_reset_is_explicit() { let mut s = Supervisor::new(spawn, window); s.admit(manifest(4)).unwrap(); s.start(AppId(4), 0b111).unwrap(); s.report_failure(AppId(4)).unwrap(); s.start(AppId(4), 0b111).unwrap(); assert_eq!(s.record(AppId(4)).unwrap().failures, 1); s.reset_failure_count(AppId(4)).unwrap(); assert_eq!(s.record(AppId(4)).unwrap().failures, 0); } } \ No newline at end of file From 2010cf613ead8f59f715e817ff81f76421861fc2 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 01:08:12 +0400 Subject: [PATCH 56/65] fix(aweos): preserve service crash-loop failure history across restarts --- services/initd/src/runtime.rs | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/services/initd/src/runtime.rs b/services/initd/src/runtime.rs index ec19e69..f9a9569 100644 --- a/services/initd/src/runtime.rs +++ b/services/initd/src/runtime.rs @@ -8,23 +8,12 @@ pub const MAX_FAILURES: u8 = 3; pub enum RuntimeError { Full, Duplicate, InvalidSpec, InvalidDependency, DependencyCycle, MissingDependency, SpawnFailed, InvalidTransition, Quarantined } #[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct ServiceRuntimeSpec { - pub spec: ServiceSpec, - pub dependencies: [Option; MAX_DEPENDENCIES], - pub dependency_count: u8, - pub entry: usize, -} - +pub struct ServiceRuntimeSpec { pub spec: ServiceSpec, pub dependencies: [Option; MAX_DEPENDENCIES], pub dependency_count: u8, pub entry: usize } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct RuntimeRecord { pub spec: ServiceRuntimeSpec, pub state: ServiceState, pub process_id: u64, pub failures: u8 } - pub type SpawnFn = fn(entry: usize, service: ServiceId, memory_pages: u32, cpu_budget: u32) -> Result; -pub struct Supervisor { - records: [Option; MAX_RUNTIME_SERVICES], - count: usize, - spawn: SpawnFn, -} +pub struct Supervisor { records: [Option; MAX_RUNTIME_SERVICES], count: usize, spawn: SpawnFn } impl Supervisor { pub const fn new(spawn: SpawnFn) -> Self { Self { records: [None; MAX_RUNTIME_SERVICES], count: 0, spawn } } pub const fn len(&self) -> usize { self.count } @@ -53,7 +42,9 @@ impl Supervisor { _ => { self.records[index].as_mut().unwrap().state = ServiceState::Failed; return Err(RuntimeError::SpawnFailed); } }; let record = self.records[index].as_mut().unwrap(); - record.process_id = pid; record.failures = 0; record.state = ServiceState::Running; Ok(pid) + record.process_id = pid; + record.state = ServiceState::Running; + Ok(pid) } pub fn report_failure(&mut self, id: ServiceId) -> Result { let index = self.find(id).ok_or(RuntimeError::MissingDependency)?; @@ -63,6 +54,13 @@ impl Supervisor { record.state = if record.failures > MAX_FAILURES { ServiceState::Quarantined } else { ServiceState::Failed }; Ok(record.state) } + pub fn reset_failure_count(&mut self, id: ServiceId) -> Result<(), RuntimeError> { + let index = self.find(id).ok_or(RuntimeError::MissingDependency)?; + let record = self.records[index].as_mut().unwrap(); + if record.state == ServiceState::Running || record.state == ServiceState::Starting { return Err(RuntimeError::InvalidTransition); } + record.failures = 0; + Ok(()) + } pub fn restart(&mut self, id: ServiceId) -> Result { let index = self.find(id).ok_or(RuntimeError::MissingDependency)?; let record = self.records[index].ok_or(RuntimeError::MissingDependency)?; @@ -81,4 +79,5 @@ mod tests { fn spec(id: u16, deps: [Option; MAX_DEPENDENCIES], count: u8) -> ServiceRuntimeSpec { ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(id), restart: RestartPolicy::OnFailure, capability_mask: u64::MAX, memory_limit_pages: 4, cpu_budget_ticks: 100 }, dependencies: deps, dependency_count: count, entry: id as usize + 1 } } #[test] fn dependencies_are_enforced() { let mut s=Supervisor::new(spawn); s.register(spec(1,[None;MAX_DEPENDENCIES],0)).unwrap(); s.register(spec(2,[Some(ServiceId(1)),None,None,None,None,None,None,None],1)).unwrap(); assert_eq!(s.start(ServiceId(2)),Err(RuntimeError::MissingDependency)); s.start(ServiceId(1)).unwrap(); assert!(s.start(ServiceId(2)).is_ok()); } #[test] fn failure_escalates_to_quarantine() { let mut s=Supervisor::new(spawn); s.register(spec(1,[None;MAX_DEPENDENCIES],0)).unwrap(); s.start(ServiceId(1)).unwrap(); for _ in 0..MAX_FAILURES { assert_eq!(s.report_failure(ServiceId(1)).unwrap(),ServiceState::Failed); s.start(ServiceId(1)).unwrap(); } assert_eq!(s.report_failure(ServiceId(1)).unwrap(),ServiceState::Quarantined); } + #[test] fn intentional_reset_is_explicit() { let mut s=Supervisor::new(spawn); s.register(spec(2,[None;MAX_DEPENDENCIES],0)).unwrap(); s.start(ServiceId(2)).unwrap(); s.report_failure(ServiceId(2)).unwrap(); s.start(ServiceId(2)).unwrap(); assert_eq!(s.records[s.find(ServiceId(2)).unwrap()].unwrap().failures,1); s.reset_failure_count(ServiceId(2)).unwrap(); assert_eq!(s.records[s.find(ServiceId(2)).unwrap()].unwrap().failures,0); } } \ No newline at end of file From 1fbe2dfb394c4cc5b61512bdba4d12d3e383be8a Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 01:08:34 +0400 Subject: [PATCH 57/65] fix(aweos): bind full AWOS package bytes to publisher signature --- services/appd/src/awos.rs | 326 ++++---------------------------------- 1 file changed, 33 insertions(+), 293 deletions(-) diff --git a/services/appd/src/awos.rs b/services/appd/src/awos.rs index 957592a..6fddb05 100644 --- a/services/appd/src/awos.rs +++ b/services/appd/src/awos.rs @@ -19,74 +19,25 @@ pub const MAX_INDEX_ENTRIES: usize = 32; pub const MAX_INSTALLED_PACKAGES: usize = 32; #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct AwosHeader { - pub version: u16, - pub abi_major: u16, - pub abi_minor: u16, - pub manifest_len: u32, - pub code_len: u32, - pub data_len: u32, - pub signature_len: u16, - pub entry_offset: u32, - pub flags: u32, -} +pub struct AwosHeader { pub version: u16, pub abi_major: u16, pub abi_minor: u16, pub manifest_len: u32, pub code_len: u32, pub data_len: u32, pub signature_len: u16, pub entry_offset: u32, pub flags: u32 } #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum AwosError { - TooShort, - BadMagic, - UnsupportedVersion, - OversizedManifest, - OversizedCode, - OversizedData, - MissingSignature, - InvalidLength, - InvalidEntry, - UnknownFlags, - InvalidSignature, - PublisherUntrusted, - DependencyMissing, - DependencyCycle, - PackageNotFound, - AlreadyInstalled, - SandboxViolation, - RollbackFailed, - StorageFull, -} +pub enum AwosError { TooShort, BadMagic, UnsupportedVersion, OversizedManifest, OversizedCode, OversizedData, MissingSignature, InvalidLength, InvalidEntry, UnknownFlags, InvalidSignature, PublisherUntrusted, DependencyMissing, DependencyCycle, PackageNotFound, AlreadyInstalled, SandboxViolation, RollbackFailed, StorageFull } pub fn validate_awos(bytes: &[u8]) -> Result { - if bytes.len() < AWOS_HEADER_LEN { - return Err(AwosError::TooShort); - } - if bytes[..4] != AWOS_MAGIC { - return Err(AwosError::BadMagic); - } + if bytes.len() < AWOS_HEADER_LEN { return Err(AwosError::TooShort); } + if bytes[..4] != AWOS_MAGIC { return Err(AwosError::BadMagic); } let u16_at = |o: usize| u16::from_le_bytes([bytes[o], bytes[o + 1]]); let u32_at = |o: usize| u32::from_le_bytes([bytes[o], bytes[o + 1], bytes[o + 2], bytes[o + 3]]); - let header = AwosHeader { - version: u16_at(4), - abi_major: u16_at(6), - abi_minor: u16_at(8), - manifest_len: u32_at(10), - code_len: u32_at(14), - data_len: u32_at(18), - signature_len: u16_at(22), - entry_offset: u32_at(24), - flags: u32_at(28), - }; + let header = AwosHeader { version: u16_at(4), abi_major: u16_at(6), abi_minor: u16_at(8), manifest_len: u32_at(10), code_len: u32_at(14), data_len: u32_at(18), signature_len: u16_at(22), entry_offset: u32_at(24), flags: u32_at(28) }; if header.version != AWOS_VERSION { return Err(AwosError::UnsupportedVersion); } if header.manifest_len as usize > AWOS_MAX_MANIFEST { return Err(AwosError::OversizedManifest); } if header.code_len as usize > AWOS_MAX_CODE { return Err(AwosError::OversizedCode); } if header.data_len as usize > AWOS_MAX_DATA { return Err(AwosError::OversizedData); } - if (header.signature_len as usize) < AWOS_MIN_SIGNATURE { return Err(AwosError::MissingSignature); } + if header.signature_len as usize < AWOS_MIN_SIGNATURE { return Err(AwosError::MissingSignature); } if header.flags & !AWOS_KNOWN_FLAGS != 0 { return Err(AwosError::UnknownFlags); } if header.entry_offset >= header.code_len || header.code_len == 0 { return Err(AwosError::InvalidEntry); } - let expected = AWOS_HEADER_LEN - .checked_add(header.manifest_len as usize) - .and_then(|v| v.checked_add(header.code_len as usize)) - .and_then(|v| v.checked_add(header.data_len as usize)) - .and_then(|v| v.checked_add(header.signature_len as usize)) - .ok_or(AwosError::InvalidLength)?; + let expected = AWOS_HEADER_LEN.checked_add(header.manifest_len as usize).and_then(|v| v.checked_add(header.code_len as usize)).and_then(|v| v.checked_add(header.data_len as usize)).and_then(|v| v.checked_add(header.signature_len as usize)).ok_or(AwosError::InvalidLength)?; if expected != bytes.len() { return Err(AwosError::InvalidLength); } Ok(header) } @@ -102,12 +53,7 @@ pub fn package_parts<'a>(bytes: &'a [u8], header: AwosHeader) -> Result<(&'a [u8 } #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct PublisherIdentity { - pub publisher_id: u64, - pub public_key: [u8; 32], - pub is_official: bool, -} - +pub struct PublisherIdentity { pub publisher_id: u64, pub public_key: [u8; 32], pub is_official: bool } impl PublisherIdentity { pub fn verify_signature(&self, payload_bytes: &[u8], signature_bytes: &[u8]) -> Result<(), AwosError> { if signature_bytes.len() < AWOS_MIN_SIGNATURE { return Err(AwosError::MissingSignature); } @@ -117,266 +63,60 @@ impl PublisherIdentity { } #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct SandboxProfile { - pub package_id: u64, - pub capability_mask: u64, - pub max_memory_pages: u32, - pub max_fds: u32, - pub allow_raw_sockets: bool, -} - +pub struct SandboxProfile { pub package_id: u64, pub capability_mask: u64, pub max_memory_pages: u32, pub max_fds: u32, pub allow_raw_sockets: bool } impl SandboxProfile { - pub const fn strict_default(package_id: u64) -> Self { - Self { package_id, capability_mask: 0x0003, max_memory_pages: 1024, max_fds: 16, allow_raw_sockets: false } - } - pub fn validate_access(&self, required_cap: u64, pages_requested: u32) -> Result<(), AwosError> { - if (self.capability_mask & required_cap) != required_cap { return Err(AwosError::SandboxViolation); } - if pages_requested > self.max_memory_pages { return Err(AwosError::SandboxViolation); } - Ok(()) - } + pub const fn strict_default(package_id: u64) -> Self { Self { package_id, capability_mask: 0x0003, max_memory_pages: 1024, max_fds: 16, allow_raw_sockets: false } } + pub fn validate_access(&self, required_cap: u64, pages_requested: u32) -> Result<(), AwosError> { if (self.capability_mask & required_cap) != required_cap { return Err(AwosError::SandboxViolation); } if pages_requested > self.max_memory_pages { return Err(AwosError::SandboxViolation); } Ok(()) } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct PackageDependency { pub dep_package_id: u64, pub min_version: u16 } - #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct PackageMeta { - pub package_id: u64, - pub version: u16, - pub publisher: PublisherIdentity, - pub sandbox: SandboxProfile, - pub dependencies: [Option; MAX_PACKAGE_DEPS], - pub dep_count: usize, -} +pub struct PackageMeta { pub package_id: u64, pub version: u16, pub publisher: PublisherIdentity, pub sandbox: SandboxProfile, pub dependencies: [Option; MAX_PACKAGE_DEPS], pub dep_count: usize } pub struct RepositoryIndex { entries: [Option; MAX_INDEX_ENTRIES] } - impl RepositoryIndex { pub const fn new() -> Self { Self { entries: [None; MAX_INDEX_ENTRIES] } } - pub fn register(&mut self, meta: PackageMeta) -> Result<(), AwosError> { - for existing in self.entries.iter_mut().flatten() { - if existing.package_id == meta.package_id && existing.version == meta.version { *existing = meta; return Ok(()); } - } - for slot in self.entries.iter_mut() { - if slot.is_none() { *slot = Some(meta); return Ok(()); } - } - Err(AwosError::StorageFull) - } - pub fn find(&self, package_id: u64) -> Option { - for slot in self.entries.iter().flatten() { if slot.package_id == package_id { return Some(*slot); } } - None - } + pub fn register(&mut self, meta: PackageMeta) -> Result<(), AwosError> { for existing in self.entries.iter_mut().flatten() { if existing.package_id == meta.package_id && existing.version == meta.version { *existing = meta; return Ok(()); } } for slot in self.entries.iter_mut() { if slot.is_none() { *slot = Some(meta); return Ok(()); } } Err(AwosError::StorageFull) } + pub fn find(&self, package_id: u64) -> Option { for slot in self.entries.iter().flatten() { if slot.package_id == package_id { return Some(*slot); } } None } pub fn resolve_dependencies(&self, root_id: u64) -> Result<[u64; MAX_PACKAGE_DEPS], AwosError> { - let mut resolved = [0u64; MAX_PACKAGE_DEPS]; - let mut count = 0; - let mut stack = [0u64; MAX_PACKAGE_DEPS]; - let mut stack_top = 0; - stack[0] = root_id; - stack_top += 1; - while stack_top > 0 { - stack_top -= 1; - let current_id = stack[stack_top]; - let meta = self.find(current_id).ok_or(AwosError::DependencyMissing)?; - let mut already_added = false; - for r in resolved.iter().take(count) { if *r == current_id { already_added = true; break; } } - if !already_added { - if count >= MAX_PACKAGE_DEPS { return Err(AwosError::StorageFull); } - resolved[count] = current_id; - count += 1; - for dep in meta.dependencies.iter().flatten() { - if stack_top >= MAX_PACKAGE_DEPS { return Err(AwosError::DependencyCycle); } - stack[stack_top] = dep.dep_package_id; - stack_top += 1; - } - } - } + let mut resolved = [0u64; MAX_PACKAGE_DEPS]; let mut count = 0; let mut stack = [0u64; MAX_PACKAGE_DEPS]; let mut stack_top = 0; stack[0] = root_id; stack_top += 1; + while stack_top > 0 { stack_top -= 1; let current_id = stack[stack_top]; let meta = self.find(current_id).ok_or(AwosError::DependencyMissing)?; let mut already_added = false; for r in resolved.iter().take(count) { if *r == current_id { already_added = true; break; } } if !already_added { if count >= MAX_PACKAGE_DEPS { return Err(AwosError::StorageFull); } resolved[count] = current_id; count += 1; for dep in meta.dependencies.iter().flatten() { if stack_top >= MAX_PACKAGE_DEPS { return Err(AwosError::DependencyCycle); } stack[stack_top] = dep.dep_package_id; stack_top += 1; } } } Ok(resolved) } } - impl Default for RepositoryIndex { fn default() -> Self { Self::new() } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum AppPackageState { Installed, Running, Staged, Failed, Quarantined, Removed } - -pub const fn package_transition(from: AppPackageState, to: AppPackageState) -> bool { - matches!((from, to), - (AppPackageState::Installed, AppPackageState::Running) - | (AppPackageState::Installed, AppPackageState::Staged) - | (AppPackageState::Running, AppPackageState::Staged) - | (AppPackageState::Running, AppPackageState::Failed) - | (AppPackageState::Staged, AppPackageState::Running) - | (AppPackageState::Staged, AppPackageState::Failed) - | (AppPackageState::Failed, AppPackageState::Staged) - | (AppPackageState::Failed, AppPackageState::Quarantined) - | (AppPackageState::Installed, AppPackageState::Removed)) -} +pub const fn package_transition(from: AppPackageState, to: AppPackageState) -> bool { matches!((from,to), (AppPackageState::Installed,AppPackageState::Running)|(AppPackageState::Installed,AppPackageState::Staged)|(AppPackageState::Running,AppPackageState::Staged)|(AppPackageState::Running,AppPackageState::Failed)|(AppPackageState::Staged,AppPackageState::Running)|(AppPackageState::Staged,AppPackageState::Failed)|(AppPackageState::Failed,AppPackageState::Staged)|(AppPackageState::Failed,AppPackageState::Quarantined)|(AppPackageState::Installed,AppPackageState::Removed)) } #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct InstalledAppRecord { - pub meta: PackageMeta, - pub state: AppPackageState, - pub active_version: u16, - pub backup_version: Option, -} - -pub struct AppPackageManager { - pub repository: RepositoryIndex, - installed: [Option; MAX_INSTALLED_PACKAGES], -} +pub struct InstalledAppRecord { pub meta: PackageMeta, pub state: AppPackageState, pub active_version: u16, pub backup_version: Option } +pub struct AppPackageManager { pub repository: RepositoryIndex, installed: [Option; MAX_INSTALLED_PACKAGES] } impl AppPackageManager { pub const fn new() -> Self { Self { repository: RepositoryIndex::new(), installed: [None; MAX_INSTALLED_PACKAGES] } } - fn verify_package(&self, bytes: &[u8], meta: PackageMeta) -> Result { if meta.publisher.publisher_id == 0 || !meta.publisher.is_official { return Err(AwosError::PublisherUntrusted); } let header = validate_awos(bytes)?; - let (manifest, code, data, sig) = package_parts(bytes, header)?; - let mut payload = [0u8; 768]; - let mut used = 0usize; - for segment in [&bytes[..AWOS_HEADER_LEN], manifest, code, data] { - if used.checked_add(segment.len()).ok_or(AwosError::InvalidLength)? > payload.len() { return Err(AwosError::InvalidLength); } - payload[used..used + segment.len()].copy_from_slice(segment); - used += segment.len(); - } - meta.publisher.verify_signature(&payload[..used], sig)?; + let (_manifest, _code, _data, sig) = package_parts(bytes, header)?; + let payload_end = bytes.len().checked_sub(sig.len()).ok_or(AwosError::InvalidLength)?; + meta.publisher.verify_signature(&bytes[..payload_end], sig)?; Ok(header) } - - pub fn install_package(&mut self, bytes: &[u8], meta: PackageMeta) -> Result { - self.verify_package(bytes, meta)?; - self.repository.register(meta)?; - self.repository.resolve_dependencies(meta.package_id)?; - for slot in self.installed.iter_mut() { - if slot.is_none() { - *slot = Some(InstalledAppRecord { meta, state: AppPackageState::Installed, active_version: meta.version, backup_version: None }); - return Ok(meta.package_id); - } - } - Err(AwosError::StorageFull) - } - - pub fn uninstall_package(&mut self, package_id: u64) -> Result<(), AwosError> { - for slot in self.installed.iter_mut() { - if let Some(rec) = slot && rec.meta.package_id == package_id { - if !package_transition(rec.state, AppPackageState::Removed) { return Err(AwosError::SandboxViolation); } - *slot = None; - return Ok(()); - } - } - Err(AwosError::PackageNotFound) - } - - pub fn update_package(&mut self, new_meta: PackageMeta, new_bytes: &[u8]) -> Result<(), AwosError> { - self.verify_package(new_bytes, new_meta)?; - for slot in self.installed.iter_mut().flatten() { - if slot.meta.package_id == new_meta.package_id { - let old_version = slot.active_version; - slot.backup_version = Some(old_version); - slot.active_version = new_meta.version; - slot.meta = new_meta; - slot.state = AppPackageState::Installed; - self.repository.register(new_meta)?; - return Ok(()); - } - } - Err(AwosError::PackageNotFound) - } - - pub fn rollback_package(&mut self, package_id: u64) -> Result { - for slot in self.installed.iter_mut().flatten() { - if slot.meta.package_id == package_id { - if let Some(backup) = slot.backup_version { - slot.active_version = backup; - slot.backup_version = None; - slot.state = AppPackageState::Installed; - return Ok(backup); - } - return Err(AwosError::RollbackFailed); - } - } - Err(AwosError::PackageNotFound) - } - - pub fn get_installed_record(&self, package_id: u64) -> Result { - for slot in self.installed.iter().flatten() { if slot.meta.package_id == package_id { return Ok(*slot); } } - Err(AwosError::PackageNotFound) - } + pub fn install_package(&mut self, bytes: &[u8], meta: PackageMeta) -> Result { self.verify_package(bytes,meta)?; self.repository.register(meta)?; self.repository.resolve_dependencies(meta.package_id)?; for slot in self.installed.iter_mut() { if slot.is_none() { *slot=Some(InstalledAppRecord{meta,state:AppPackageState::Installed,active_version:meta.version,backup_version:None}); return Ok(meta.package_id); } } Err(AwosError::StorageFull) } + pub fn uninstall_package(&mut self, package_id:u64)->Result<(),AwosError>{ for slot in self.installed.iter_mut(){ if let Some(rec)=slot && rec.meta.package_id==package_id { if !package_transition(rec.state,AppPackageState::Removed){return Err(AwosError::SandboxViolation);} *slot=None; return Ok(()); }} Err(AwosError::PackageNotFound)} + pub fn update_package(&mut self,new_meta:PackageMeta,new_bytes:&[u8])->Result<(),AwosError>{ self.verify_package(new_bytes,new_meta)?; for slot in self.installed.iter_mut().flatten(){ if slot.meta.package_id==new_meta.package_id{ let old_version=slot.active_version; slot.backup_version=Some(old_version); slot.active_version=new_meta.version; slot.meta=new_meta; slot.state=AppPackageState::Installed; self.repository.register(new_meta)?; return Ok(()); }} Err(AwosError::PackageNotFound)} + pub fn rollback_package(&mut self,package_id:u64)->Result{ for slot in self.installed.iter_mut().flatten(){ if slot.meta.package_id==package_id{ if let Some(backup)=slot.backup_version{slot.active_version=backup;slot.backup_version=None;slot.state=AppPackageState::Installed;return Ok(backup);} return Err(AwosError::RollbackFailed); }} Err(AwosError::PackageNotFound)} + pub fn get_installed_record(&self,package_id:u64)->Result{ for slot in self.installed.iter().flatten(){ if slot.meta.package_id==package_id{return Ok(*slot);} } Err(AwosError::PackageNotFound) } } - -impl Default for AppPackageManager { fn default() -> Self { Self::new() } } +impl Default for AppPackageManager { fn default()->Self{Self::new()} } #[cfg(test)] -mod tests { - use super::*; - extern crate std; - use std::vec; - use std::vec::Vec; - - fn build_awos_bytes(manifest: usize, code: usize, data: usize, sig: usize) -> Vec { - let mut b = Vec::with_capacity(AWOS_HEADER_LEN + manifest + code + data + sig); - b.extend_from_slice(&AWOS_MAGIC); - b.extend_from_slice(&AWOS_VERSION.to_le_bytes()); - b.extend_from_slice(&1u16.to_le_bytes()); - b.extend_from_slice(&0u16.to_le_bytes()); - b.extend_from_slice(&(manifest as u32).to_le_bytes()); - b.extend_from_slice(&(code as u32).to_le_bytes()); - b.extend_from_slice(&(data as u32).to_le_bytes()); - b.extend_from_slice(&(sig as u16).to_le_bytes()); - b.extend_from_slice(&0u32.to_le_bytes()); - b.extend_from_slice(&0u32.to_le_bytes()); - b.extend(core::iter::repeat_n(0u8, manifest)); - b.extend(core::iter::repeat_n(0x90u8, code)); - b.extend(core::iter::repeat_n(0u8, data)); - b.extend(core::iter::repeat_n(0u8, sig)); - b - } - - fn sign_payload(bytes: &[u8], meta: usize) -> Vec { - let seed = [0x77u8; 32]; - let (_pk, sk) = awe_securityd::ed25519_keypair_from_seed(&seed); - let payload = &bytes[..bytes.len() - meta]; - awe_securityd::ed25519_sign(&sk, payload).to_vec() - } - - #[test] - fn test_awos_package_install_update_rollback_uninstall() { - let seed = [0x77u8; 32]; - let (pk, sk) = awe_securityd::ed25519_keypair_from_seed(&seed); - let pub_id = PublisherIdentity { publisher_id: 100, public_key: pk, is_official: true }; - let mut bytes_v1 = build_awos_bytes(4, 8, 2, 64); - let payload_len_v1 = bytes_v1.len() - 64; - let sig_v1 = awe_securityd::ed25519_sign(&sk, &bytes_v1[..payload_len_v1]); - bytes_v1[payload_len_v1..].copy_from_slice(&sig_v1); - let meta_v1 = PackageMeta { package_id: 1001, version: 1, publisher: pub_id, sandbox: SandboxProfile::strict_default(1001), dependencies: [None; MAX_PACKAGE_DEPS], dep_count: 0 }; - let mut mgr = AppPackageManager::new(); - mgr.install_package(&bytes_v1, meta_v1).expect("install v1"); - assert_eq!(mgr.get_installed_record(1001).unwrap().active_version, 1); - let meta_v2 = PackageMeta { version: 2, ..meta_v1 }; - let mut bytes_v2 = build_awos_bytes(4, 8, 2, 64); - let sig_v2 = awe_securityd::ed25519_sign(&sk, &bytes_v2[..bytes_v2.len() - 64]); - bytes_v2[bytes_v2.len() - 64..].copy_from_slice(&sig_v2); - mgr.update_package(meta_v2, &bytes_v2).expect("update v2"); - let rec2 = mgr.get_installed_record(1001).unwrap(); - assert_eq!(rec2.active_version, 2); - assert_eq!(rec2.backup_version, Some(1)); - assert_eq!(mgr.rollback_package(1001).unwrap(), 1); - mgr.uninstall_package(1001).expect("uninstall"); - assert_eq!(mgr.get_installed_record(1001), Err(AwosError::PackageNotFound)); - } - - #[test] - fn rejects_tampered_manifest_or_data() { - let seed = [0x55u8; 32]; - let (pk, sk) = awe_securityd::ed25519_keypair_from_seed(&seed); - let publisher = PublisherIdentity { publisher_id: 9, public_key: pk, is_official: true }; - let mut bytes = build_awos_bytes(4, 8, 2, 64); - let payload_len = bytes.len() - 64; - let sig = awe_securityd::ed25519_sign(&sk, &bytes[..payload_len]); - bytes[payload_len..].copy_from_slice(&sig); - bytes[32] ^= 1; - let mut mgr = AppPackageManager::new(); - let meta = PackageMeta { package_id: 7, version: 1, publisher, sandbox: SandboxProfile::strict_default(7), dependencies: [None; MAX_PACKAGE_DEPS], dep_count: 0 }; - assert_eq!(mgr.install_package(&bytes, meta), Err(AwosError::InvalidSignature)); - } +mod tests{ + use super::*; extern crate std; use std::vec; use std::vec::Vec; + fn build_awos_bytes(manifest:usize,code:usize,data:usize,sig:usize)->Vec{ let mut b=Vec::with_capacity(AWOS_HEADER_LEN+manifest+code+data+sig); b.extend_from_slice(&AWOS_MAGIC); b.extend_from_slice(&AWOS_VERSION.to_le_bytes()); b.extend_from_slice(&1u16.to_le_bytes()); b.extend_from_slice(&0u16.to_le_bytes()); b.extend_from_slice(&(manifest as u32).to_le_bytes()); b.extend_from_slice(&(code as u32).to_le_bytes()); b.extend_from_slice(&(data as u32).to_le_bytes()); b.extend_from_slice(&(sig as u16).to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); b.extend(core::iter::repeat_n(0u8,manifest)); b.extend(core::iter::repeat_n(0x90u8,code)); b.extend(core::iter::repeat_n(0u8,data)); b.extend(core::iter::repeat_n(0u8,sig)); b } + #[test] fn test_awos_package_install_update_rollback_uninstall(){ let seed=[0x77u8;32]; let(pk,sk)=awe_securityd::ed25519_keypair_from_seed(&seed); let publisher=PublisherIdentity{publisher_id:100,public_key:pk,is_official:true}; let mut v1=build_awos_bytes(4,8,2,64); let n=v1.len()-64; let sig=awe_securityd::ed25519_sign(&sk,&v1[..n]); v1[n..].copy_from_slice(&sig); let m1=PackageMeta{package_id:1001,version:1,publisher,sandbox:SandboxProfile::strict_default(1001),dependencies:[None;MAX_PACKAGE_DEPS],dep_count:0}; let mut mgr=AppPackageManager::new(); mgr.install_package(&v1,m1).unwrap(); assert_eq!(mgr.get_installed_record(1001).unwrap().active_version,1); let m2=PackageMeta{version:2,..m1}; let mut v2=build_awos_bytes(4,8,2,64); let n2=v2.len()-64; let sig2=awe_securityd::ed25519_sign(&sk,&v2[..n2]); v2[n2..].copy_from_slice(&sig2); mgr.update_package(m2,&v2).unwrap(); assert_eq!(mgr.get_installed_record(1001).unwrap().active_version,2); assert_eq!(mgr.get_installed_record(1001).unwrap().backup_version,Some(1)); assert_eq!(mgr.rollback_package(1001).unwrap(),1); mgr.uninstall_package(1001).unwrap(); assert_eq!(mgr.get_installed_record(1001),Err(AwosError::PackageNotFound)); } + #[test] fn rejects_tampered_manifest_or_data(){ let seed=[0x55u8;32]; let(pk,sk)=awe_securityd::ed25519_keypair_from_seed(&seed); let publisher=PublisherIdentity{publisher_id:9,public_key:pk,is_official:true}; let mut bytes=build_awos_bytes(4,8,2,64); let n=bytes.len()-64; let sig=awe_securityd::ed25519_sign(&sk,&bytes[..n]); bytes[n..].copy_from_slice(&sig); bytes[32]^=1; let mut mgr=AppPackageManager::new(); let meta=PackageMeta{package_id:7,version:1,publisher,sandbox:SandboxProfile::strict_default(7),dependencies:[None;MAX_PACKAGE_DEPS],dep_count:0}; assert_eq!(mgr.install_package(&bytes,meta),Err(AwosError::InvalidSignature)); } } From f2760376a31e4423d866adb49af2d3c734b4d0c5 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 01:09:18 +0400 Subject: [PATCH 58/65] feat(aweos): expose storaged state for persistent metadata codec --- services/storaged/src/lib.rs | 294 ++++------------------------------- 1 file changed, 29 insertions(+), 265 deletions(-) diff --git a/services/storaged/src/lib.rs b/services/storaged/src/lib.rs index b646fa4..ab60f71 100644 --- a/services/storaged/src/lib.rs +++ b/services/storaged/src/lib.rs @@ -5,6 +5,8 @@ #![no_std] +pub mod persistence; + pub const MAX_VOLUMES: usize = 16; pub const MAX_MOUNTS: usize = 16; pub const MAX_SNAPSHOTS: usize = 8; @@ -13,282 +15,44 @@ pub const BLOCK_SIZE: usize = 512; pub const CACHE_BLOCKS: usize = 32; #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum VolumeType { - Ramdisk, - GptPartition, - VirtualDisk, - AweFsVolume, -} - +pub enum VolumeType { Ramdisk, GptPartition, VirtualDisk, AweFsVolume } #[derive(Debug, Clone, Copy)] -pub struct PackageFileRecord { - pub file_id: u32, - pub volume_id: u32, - pub name_hash: u64, - pub size_bytes: u32, - pub is_installed_package: bool, -} - +pub struct PackageFileRecord { pub file_id: u32, pub volume_id: u32, pub name_hash: u64, pub size_bytes: u32, pub is_installed_package: bool } #[derive(Debug, Clone, Copy)] -pub struct StorageVolume { - pub volume_id: u32, - pub volume_type: VolumeType, - pub block_count: u64, - pub start_lba: u64, - pub read_only: bool, -} - +pub struct StorageVolume { pub volume_id: u32, pub volume_type: VolumeType, pub block_count: u64, pub start_lba: u64, pub read_only: bool } #[derive(Debug, Clone, Copy)] -pub struct MountEntry { - pub mount_id: u32, - pub volume_id: u32, - pub path_hash: u64, - pub active: bool, -} - +pub struct MountEntry { pub mount_id: u32, pub volume_id: u32, pub path_hash: u64, pub active: bool } #[derive(Debug, Clone, Copy)] -pub struct SnapshotMetadata { - pub snapshot_id: u32, - pub volume_id: u32, - pub timestamp: u64, - pub block_delta_count: u32, -} - +pub struct SnapshotMetadata { pub snapshot_id: u32, pub volume_id: u32, pub timestamp: u64, pub block_delta_count: u32 } #[derive(Debug, Clone, Copy)] -pub struct CachedBlock { - pub volume_id: u32, - pub lba: u64, - pub dirty: bool, - pub data: [u8; BLOCK_SIZE], -} +pub struct CachedBlock { pub volume_id: u32, pub lba: u64, pub dirty: bool, pub data: [u8; BLOCK_SIZE] } -/// Storage Daemon Supervisor Instance. #[derive(Debug)] pub struct StorageDaemon { - volumes: [Option; MAX_VOLUMES], - mounts: [Option; MAX_MOUNTS], - snapshots: [Option; MAX_SNAPSHOTS], - files: [Option; MAX_PACKAGE_FILES], - cache: [Option; CACHE_BLOCKS], - volume_counter: u32, - mount_counter: u32, - snapshot_counter: u32, - file_counter: u32, + pub(crate) volumes: [Option; MAX_VOLUMES], + pub(crate) mounts: [Option; MAX_MOUNTS], + pub(crate) snapshots: [Option; MAX_SNAPSHOTS], + pub(crate) files: [Option; MAX_PACKAGE_FILES], + pub(crate) cache: [Option; CACHE_BLOCKS], + pub(crate) volume_counter: u32, + pub(crate) mount_counter: u32, + pub(crate) snapshot_counter: u32, + pub(crate) file_counter: u32, pub self_healed_events: usize, } impl StorageDaemon { - pub const fn new() -> Self { - Self { - volumes: [None; MAX_VOLUMES], - mounts: [None; MAX_MOUNTS], - snapshots: [None; MAX_SNAPSHOTS], - files: [None; MAX_PACKAGE_FILES], - cache: [None; CACHE_BLOCKS], - volume_counter: 1, - mount_counter: 1, - snapshot_counter: 1, - file_counter: 1, - self_healed_events: 0, - } - } - - pub fn register_volume( - &mut self, - vol_type: VolumeType, - block_count: u64, - start_lba: u64, - read_only: bool, - ) -> Result { - let vid = self.volume_counter; - for slot in self.volumes.iter_mut() { - if slot.is_none() { - *slot = Some(StorageVolume { - volume_id: vid, - volume_type: vol_type, - block_count, - start_lba, - read_only, - }); - self.volume_counter += 1; - return Ok(vid); - } - } - Err("Volume table full") - } - - pub fn mount_volume(&mut self, volume_id: u32, path_hash: u64) -> Result { - let mut found = false; - for v in self.volumes.iter().flatten() { - if v.volume_id == volume_id { - found = true; - break; - } - } - if !found { - return Err("Volume ID not found"); - } - - let mid = self.mount_counter; - for slot in self.mounts.iter_mut() { - if slot.is_none() { - *slot = Some(MountEntry { - mount_id: mid, - volume_id, - path_hash, - active: true, - }); - self.mount_counter += 1; - return Ok(mid); - } - } - Err("Mount table full") - } - - pub fn create_snapshot(&mut self, volume_id: u32, timestamp: u64) -> Result { - let sid = self.snapshot_counter; - for slot in self.snapshots.iter_mut() { - if slot.is_none() { - *slot = Some(SnapshotMetadata { - snapshot_id: sid, - volume_id, - timestamp, - block_delta_count: 0, - }); - self.snapshot_counter += 1; - return Ok(sid); - } - } - Err("Snapshot table full") - } - - pub fn cache_read(&self, volume_id: u32, lba: u64) -> Option<&[u8; BLOCK_SIZE]> { - for block in self.cache.iter().flatten() { - if block.volume_id == volume_id && block.lba == lba { - return Some(&block.data); - } - } - None - } - - pub fn cache_write( - &mut self, - volume_id: u32, - lba: u64, - data: &[u8; BLOCK_SIZE], - ) -> Result<(), &'static str> { - // Update existing cache block if present - for block in self.cache.iter_mut().flatten() { - if block.volume_id == volume_id && block.lba == lba { - block.data = *data; - block.dirty = true; - return Ok(()); - } - } - // Insert into free slot - for slot in self.cache.iter_mut() { - if slot.is_none() { - *slot = Some(CachedBlock { - volume_id, - lba, - dirty: true, - data: *data, - }); - return Ok(()); - } - } - Err("Block cache full") - } - - pub fn store_package_file( - &mut self, - volume_id: u32, - name_hash: u64, - size_bytes: u32, - ) -> Result { - let fid = self.file_counter; - for slot in self.files.iter_mut() { - if slot.is_none() { - *slot = Some(PackageFileRecord { - file_id: fid, - volume_id, - name_hash, - size_bytes, - is_installed_package: true, - }); - self.file_counter += 1; - return Ok(fid); - } - } - Err("File records table full") - } - - pub fn delete_package_file(&mut self, file_id: u32) -> Result<(), &'static str> { - for slot in self.files.iter_mut() { - if let Some(f) = slot - && f.file_id == file_id - { - *slot = None; - return Ok(()); - } - } - Err("File not found") - } - - pub fn trigger_self_healing_repair(&mut self, volume_id: u32) -> bool { - // Trigger self-healing repair from snapshots for damaged blocks - if self - .snapshots - .iter() - .flatten() - .any(|s| s.volume_id == volume_id) - { - self.self_healed_events += 1; - true - } else { - false - } - } -} - -impl Default for StorageDaemon { - fn default() -> Self { - Self::new() - } -} + pub const fn new() -> Self { Self { volumes:[None;MAX_VOLUMES], mounts:[None;MAX_MOUNTS], snapshots:[None;MAX_SNAPSHOTS], files:[None;MAX_PACKAGE_FILES], cache:[None;CACHE_BLOCKS], volume_counter:1, mount_counter:1, snapshot_counter:1, file_counter:1, self_healed_events:0 } } + pub fn register_volume(&mut self, vol_type:VolumeType, block_count:u64, start_lba:u64, read_only:bool)->Result{ let vid=self.volume_counter; for slot in self.volumes.iter_mut(){if slot.is_none(){*slot=Some(StorageVolume{volume_id:vid,volume_type:vol_type,block_count,start_lba,read_only});self.volume_counter=self.volume_counter.saturating_add(1);return Ok(vid);}} Err("Volume table full") } + pub fn mount_volume(&mut self, volume_id:u32, path_hash:u64)->Result{ if !self.volumes.iter().flatten().any(|v|v.volume_id==volume_id){return Err("Volume ID not found");} let mid=self.mount_counter; for slot in self.mounts.iter_mut(){if slot.is_none(){*slot=Some(MountEntry{mount_id:mid,volume_id,path_hash,active:true});self.mount_counter=self.mount_counter.saturating_add(1);return Ok(mid);}} Err("Mount table full") } + pub fn create_snapshot(&mut self, volume_id:u32, timestamp:u64)->Result{ if !self.volumes.iter().flatten().any(|v|v.volume_id==volume_id){return Err("Volume ID not found");} let sid=self.snapshot_counter; for slot in self.snapshots.iter_mut(){if slot.is_none(){*slot=Some(SnapshotMetadata{snapshot_id:sid,volume_id,timestamp,block_delta_count:0});self.snapshot_counter=self.snapshot_counter.saturating_add(1);return Ok(sid);}} Err("Snapshot table full") } + pub fn cache_read(&self, volume_id:u32, lba:u64)->Option<&[u8;BLOCK_SIZE]>{ for block in self.cache.iter().flatten(){if block.volume_id==volume_id&&block.lba==lba{return Some(&block.data);}} None } + pub fn cache_write(&mut self, volume_id:u32,lba:u64,data:&[u8;BLOCK_SIZE])->Result<(),&'static str>{ for block in self.cache.iter_mut().flatten(){if block.volume_id==volume_id&&block.lba==lba{block.data=*data;block.dirty=true;return Ok(());}} for slot in self.cache.iter_mut(){if slot.is_none(){*slot=Some(CachedBlock{volume_id,lba,dirty:true,data:*data});return Ok(());}} Err("Block cache full") } + pub fn store_package_file(&mut self,volume_id:u32,name_hash:u64,size_bytes:u32)->Result{ if !self.volumes.iter().flatten().any(|v|v.volume_id==volume_id){return Err("Volume ID not found");} let fid=self.file_counter; for slot in self.files.iter_mut(){if slot.is_none(){*slot=Some(PackageFileRecord{file_id:fid,volume_id,name_hash,size_bytes,is_installed_package:true});self.file_counter=self.file_counter.saturating_add(1);return Ok(fid);}} Err("File records table full") } + pub fn delete_package_file(&mut self,file_id:u32)->Result<(),&'static str>{ for slot in self.files.iter_mut(){if let Some(f)=slot&&f.file_id==file_id{*slot=None;return Ok(());}} Err("File not found") } + pub fn trigger_self_healing_repair(&mut self,volume_id:u32)->bool{ if self.snapshots.iter().flatten().any(|s|s.volume_id==volume_id){self.self_healed_events+=1;true}else{false} } +} +impl Default for StorageDaemon { fn default()->Self{Self::new()} } #[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_storaged_lifecycle() { - let mut storaged = StorageDaemon::new(); - let vid = storaged - .register_volume(VolumeType::Ramdisk, 2048, 0, false) - .unwrap(); - assert_eq!(vid, 1); - - let mid = storaged.mount_volume(vid, 0x1234_5678).unwrap(); - assert_eq!(mid, 1); - - let snap_id = storaged.create_snapshot(vid, 1000).unwrap(); - assert_eq!(snap_id, 1); - - let block_data = [0xAB; BLOCK_SIZE]; - storaged.cache_write(vid, 42, &block_data).unwrap(); - - let cached = storaged - .cache_read(vid, 42) - .expect("Should hit block cache"); - assert_eq!(cached[0], 0xAB); - - let fid = storaged.store_package_file(vid, 0x112233, 4096).unwrap(); - assert_eq!(fid, 1); - assert!(storaged.trigger_self_healing_repair(vid)); - assert_eq!(storaged.self_healed_events, 1); - - storaged.delete_package_file(fid).unwrap(); - assert!(storaged.delete_package_file(fid).is_err()); - } -} +mod tests { use super::*; #[test] fn test_storaged_lifecycle(){let mut s=StorageDaemon::new();let v=s.register_volume(VolumeType::Ramdisk,2048,0,false).unwrap();assert_eq!(v,1);assert_eq!(s.mount_volume(v,0x1234_5678).unwrap(),1);assert_eq!(s.create_snapshot(v,1000).unwrap(),1);let d=[0xAB;BLOCK_SIZE];s.cache_write(v,42,&d).unwrap();assert_eq!(s.cache_read(v,42).unwrap()[0],0xAB);let f=s.store_package_file(v,0x112233,4096).unwrap();assert_eq!(f,1);assert!(s.trigger_self_healing_repair(v));assert_eq!(s.self_healed_events,1);s.delete_package_file(f).unwrap();assert!(s.delete_package_file(f).is_err());}} From 26dd3f9c33a631e1e7209e3ccbf68b48b7e551ae Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 01:09:56 +0400 Subject: [PATCH 59/65] feat(aweos): add checksummed persistent storaged metadata codec --- services/storaged/src/persistence.rs | 226 +++++++++++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100644 services/storaged/src/persistence.rs diff --git a/services/storaged/src/persistence.rs b/services/storaged/src/persistence.rs new file mode 100644 index 0000000..6e07e35 --- /dev/null +++ b/services/storaged/src/persistence.rs @@ -0,0 +1,226 @@ +#![no_std] + +use super::{PackageFileRecord, SnapshotMetadata, StorageDaemon, StorageVolume, VolumeType, MAX_MOUNTS, MAX_PACKAGE_FILES, MAX_SNAPSHOTS, MAX_VOLUMES}; + +pub const STATE_MAGIC: [u8; 4] = *b"AWSP"; +pub const STATE_VERSION: u8 = 1; +pub const MAX_STATE_SIZE: usize = 4096; +const HEADER_LEN: usize = 16; +const VOLUME_REC_LEN: usize = 20; +const MOUNT_REC_LEN: usize = 17; +const SNAPSHOT_REC_LEN: usize = 24; +const FILE_REC_LEN: usize = 25; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum PersistError { + BufferTooSmall, + Truncated, + BadMagic, + UnsupportedVersion, + ChecksumMismatch, + InvalidRecord, + InvalidCount, + Overflow, +} + +fn checksum(bytes: &[u8]) -> u32 { + let mut hash = 0x811c_9dc5u32; + for &byte in bytes { + hash ^= byte as u32; + hash = hash.wrapping_mul(0x0100_0193); + } + hash +} + +fn put_u16(out: &mut [u8], pos: &mut usize, value: u16) -> Result<(), PersistError> { + let end = pos.checked_add(2).ok_or(PersistError::Overflow)?; + if end > out.len() { return Err(PersistError::BufferTooSmall); } + out[*pos..end].copy_from_slice(&value.to_le_bytes()); + *pos = end; + Ok(()) +} +fn put_u32(out: &mut [u8], pos: &mut usize, value: u32) -> Result<(), PersistError> { + let end = pos.checked_add(4).ok_or(PersistError::Overflow)?; + if end > out.len() { return Err(PersistError::BufferTooSmall); } + out[*pos..end].copy_from_slice(&value.to_le_bytes()); + *pos = end; + Ok(()) +} +fn put_u64(out: &mut [u8], pos: &mut usize, value: u64) -> Result<(), PersistError> { + let end = pos.checked_add(8).ok_or(PersistError::Overflow)?; + if end > out.len() { return Err(PersistError::BufferTooSmall); } + out[*pos..end].copy_from_slice(&value.to_le_bytes()); + *pos = end; + Ok(()) +} +fn put_u8(out: &mut [u8], pos: &mut usize, value: u8) -> Result<(), PersistError> { + if *pos >= out.len() { return Err(PersistError::BufferTooSmall); } + out[*pos] = value; + *pos += 1; + Ok(()) +} +fn get_u16(input: &[u8], pos: &mut usize) -> Result { + let end = pos.checked_add(2).ok_or(PersistError::Overflow)?; + if end > input.len() { return Err(PersistError::Truncated); } + let value = u16::from_le_bytes([input[*pos], input[*pos + 1]]); + *pos = end; + Ok(value) +} +fn get_u32(input: &[u8], pos: &mut usize) -> Result { + let end = pos.checked_add(4).ok_or(PersistError::Overflow)?; + if end > input.len() { return Err(PersistError::Truncated); } + let value = u32::from_le_bytes([input[*pos], input[*pos + 1], input[*pos + 2], input[*pos + 3]]); + *pos = end; + Ok(value) +} +fn get_u64(input: &[u8], pos: &mut usize) -> Result { + let end = pos.checked_add(8).ok_or(PersistError::Overflow)?; + if end > input.len() { return Err(PersistError::Truncated); } + let value = u64::from_le_bytes([ + input[*pos], input[*pos + 1], input[*pos + 2], input[*pos + 3], + input[*pos + 4], input[*pos + 5], input[*pos + 6], input[*pos + 7], + ]); + *pos = end; + Ok(value) +} +fn get_u8(input: &[u8], pos: &mut usize) -> Result { + if *pos >= input.len() { return Err(PersistError::Truncated); } + let value = input[*pos]; + *pos += 1; + Ok(value) +} + +fn volume_type_to_u8(value: VolumeType) -> u8 { + match value { VolumeType::Ramdisk => 0, VolumeType::GptPartition => 1, VolumeType::VirtualDisk => 2, VolumeType::AweFsVolume => 3 } +} +fn volume_type_from_u8(value: u8) -> Result { + match value { 0 => Ok(VolumeType::Ramdisk), 1 => Ok(VolumeType::GptPartition), 2 => Ok(VolumeType::VirtualDisk), 3 => Ok(VolumeType::AweFsVolume), _ => Err(PersistError::InvalidRecord) } +} + +pub fn export_state(storage: &StorageDaemon, out: &mut [u8]) -> Result { + if out.len() < HEADER_LEN { return Err(PersistError::BufferTooSmall); } + let volume_count = storage.volumes.iter().filter(|v| v.is_some()).count(); + let mount_count = storage.mounts.iter().filter(|v| v.is_some()).count(); + let snapshot_count = storage.snapshots.iter().filter(|v| v.is_some()).count(); + let file_count = storage.files.iter().filter(|v| v.is_some()).count(); + if volume_count > MAX_VOLUMES || mount_count > MAX_MOUNTS || snapshot_count > MAX_SNAPSHOTS || file_count > MAX_PACKAGE_FILES { return Err(PersistError::InvalidCount); } + + let payload_len = volume_count.checked_mul(VOLUME_REC_LEN).and_then(|v| v.checked_add(mount_count.checked_mul(MOUNT_REC_LEN)?)).and_then(|v| v.checked_add(snapshot_count.checked_mul(SNAPSHOT_REC_LEN)?)).and_then(|v| v.checked_add(file_count.checked_mul(FILE_REC_LEN)?)).ok_or(PersistError::Overflow)?; + let total_len = HEADER_LEN.checked_add(payload_len).ok_or(PersistError::Overflow)?; + if total_len > out.len() || total_len > MAX_STATE_SIZE { return Err(PersistError::BufferTooSmall); } + out[..total_len].fill(0); + out[..4].copy_from_slice(&STATE_MAGIC); + out[4] = STATE_VERSION; + let mut pos = 8usize; + put_u16(out, &mut pos, volume_count as u16)?; + put_u16(out, &mut pos, mount_count as u16)?; + put_u16(out, &mut pos, snapshot_count as u16)?; + put_u16(out, &mut pos, file_count as u16)?; + + for rec in storage.volumes.iter().flatten() { + put_u32(out, &mut pos, rec.volume_id)?; put_u8(out, &mut pos, volume_type_to_u8(rec.volume_type))?; put_u8(out, &mut pos, u8::from(rec.read_only))?; put_u16(out, &mut pos, 0)?; put_u64(out, &mut pos, rec.block_count)?; put_u64(out, &mut pos, rec.start_lba)?; + } + for rec in storage.mounts.iter().flatten() { + put_u32(out, &mut pos, rec.mount_id)?; put_u32(out, &mut pos, rec.volume_id)?; put_u64(out, &mut pos, rec.path_hash)?; put_u8(out, &mut pos, u8::from(rec.active))?; + } + for rec in storage.snapshots.iter().flatten() { + put_u32(out, &mut pos, rec.snapshot_id)?; put_u32(out, &mut pos, rec.volume_id)?; put_u64(out, &mut pos, rec.timestamp)?; put_u32(out, &mut pos, rec.block_delta_count)?; put_u32(out, &mut pos, 0)?; + } + for rec in storage.files.iter().flatten() { + put_u32(out, &mut pos, rec.file_id)?; put_u32(out, &mut pos, rec.volume_id)?; put_u64(out, &mut pos, rec.name_hash)?; put_u32(out, &mut pos, rec.size_bytes)?; put_u8(out, &mut pos, u8::from(rec.is_installed_package))?; + } + let sum = checksum(&out[HEADER_LEN..total_len]); + out[0..4].copy_from_slice(&STATE_MAGIC); + out[5] = 0; + out[6..8].copy_from_slice(&(total_len as u16).to_le_bytes()); + out[8..12].copy_from_slice(&sum.to_le_bytes()); + Ok(total_len) +} + +pub fn import_state(storage: &mut StorageDaemon, input: &[u8]) -> Result<(), PersistError> { + if input.len() < HEADER_LEN { return Err(PersistError::Truncated); } + if input[..4] != STATE_MAGIC { return Err(PersistError::BadMagic); } + if input[4] != STATE_VERSION { return Err(PersistError::UnsupportedVersion); } + let total_len = u16::from_le_bytes([input[6], input[7]]) as usize; + if total_len < HEADER_LEN || total_len > input.len() || total_len > MAX_STATE_SIZE { return Err(PersistError::InvalidCount); } + let expected_sum = u32::from_le_bytes([input[8], input[9], input[10], input[11]]); + if checksum(&input[HEADER_LEN..total_len]) != expected_sum { return Err(PersistError::ChecksumMismatch); } + let mut pos = 12usize; + pos += 4; + let volume_count = get_u16(input, &mut pos)? as usize; + let mount_count = get_u16(input, &mut pos)? as usize; + let snapshot_count = get_u16(input, &mut pos)? as usize; + let file_count = get_u16(input, &mut pos)? as usize; + if volume_count > MAX_VOLUMES || mount_count > MAX_MOUNTS || snapshot_count > MAX_SNAPSHOTS || file_count > MAX_PACKAGE_FILES { return Err(PersistError::InvalidCount); } + let expected_payload = volume_count.checked_mul(VOLUME_REC_LEN).and_then(|v| v.checked_add(mount_count.checked_mul(MOUNT_REC_LEN)?)).and_then(|v| v.checked_add(snapshot_count.checked_mul(SNAPSHOT_REC_LEN)?)).and_then(|v| v.checked_add(file_count.checked_mul(FILE_REC_LEN)?)).ok_or(PersistError::Overflow)?; + if HEADER_LEN.checked_add(expected_payload).ok_or(PersistError::Overflow)? != total_len { return Err(PersistError::InvalidCount); } + + let mut volumes = [None; MAX_VOLUMES]; + let mut mounts = [None; MAX_MOUNTS]; + let mut snapshots = [None; MAX_SNAPSHOTS]; + let mut files = [None; MAX_PACKAGE_FILES]; + let mut max_volume_id = 0u32; let mut max_mount_id = 0u32; let mut max_snapshot_id = 0u32; let mut max_file_id = 0u32; + + for slot in volumes.iter_mut().take(volume_count) { + let id=get_u32(input,&mut pos)?; let ty=volume_type_from_u8(get_u8(input,&mut pos)?)?; let read_only=get_u8(input,&mut pos)? != 0; let _=get_u16(input,&mut pos)?; let blocks=get_u64(input,&mut pos)?; let start=get_u64(input,&mut pos)?; + if id==0 || blocks==0 { return Err(PersistError::InvalidRecord); } max_volume_id=max_volume_id.max(id); *slot=Some(StorageVolume{volume_id:id,volume_type:ty,block_count:blocks,start_lba:start,read_only}); + } + for slot in mounts.iter_mut().take(mount_count) { + let id=get_u32(input,&mut pos)?; let volume_id=get_u32(input,&mut pos)?; let path_hash=get_u64(input,&mut pos)?; let active=get_u8(input,&mut pos)? != 0; + if id==0 || volume_id==0 { return Err(PersistError::InvalidRecord); } max_mount_id=max_mount_id.max(id); slot.replace(super::MountEntry{mount_id:id,volume_id,path_hash,active}); + } + for slot in snapshots.iter_mut().take(snapshot_count) { + let id=get_u32(input,&mut pos)?; let volume_id=get_u32(input,&mut pos)?; let timestamp=get_u64(input,&mut pos)?; let delta=get_u32(input,&mut pos)?; let _=get_u32(input,&mut pos)?; + if id==0 || volume_id==0 { return Err(PersistError::InvalidRecord); } max_snapshot_id=max_snapshot_id.max(id); slot.replace(SnapshotMetadata{snapshot_id:id,volume_id,timestamp,block_delta_count:delta}); + } + for slot in files.iter_mut().take(file_count) { + let id=get_u32(input,&mut pos)?; let volume_id=get_u32(input,&mut pos)?; let name_hash=get_u64(input,&mut pos)?; let size_bytes=get_u32(input,&mut pos)?; let installed=get_u8(input,&mut pos)? != 0; + if id==0 || volume_id==0 { return Err(PersistError::InvalidRecord); } max_file_id=max_file_id.max(id); slot.replace(PackageFileRecord{file_id:id,volume_id,name_hash,size_bytes,is_installed_package:installed}); + } + + *storage = StorageDaemon { + volumes, mounts, snapshots, files, + cache: [None; super::CACHE_BLOCKS], + volume_counter: max_volume_id.saturating_add(1).max(1), + mount_counter: max_mount_id.saturating_add(1).max(1), + snapshot_counter: max_snapshot_id.saturating_add(1).max(1), + file_counter: max_file_id.saturating_add(1).max(1), + self_healed_events: 0, + }; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn state_round_trip_restores_metadata_and_counters() { + let mut source = StorageDaemon::new(); + let volume = source.register_volume(VolumeType::AweFsVolume, 4096, 64, false).unwrap(); + source.mount_volume(volume, 0x55AA).unwrap(); + source.create_snapshot(volume, 123456).unwrap(); + source.store_package_file(volume, 0xABCDEF, 8192).unwrap(); + let mut bytes = [0u8; MAX_STATE_SIZE]; + let len = export_state(&source, &mut bytes).unwrap(); + + let mut restored = StorageDaemon::new(); + import_state(&mut restored, &bytes[..len]).unwrap(); + assert_eq!(restored.volumes[0].unwrap().block_count, 4096); + assert_eq!(restored.mounts[0].unwrap().path_hash, 0x55AA); + assert_eq!(restored.snapshots[0].unwrap().timestamp, 123456); + assert_eq!(restored.files[0].unwrap().size_bytes, 8192); + assert_eq!(restored.register_volume(VolumeType::Ramdisk, 1, 1, true).unwrap(), 2); + assert_eq!(restored.mount_volume(2, 3).unwrap(), 2); + } + + #[test] + fn tampering_is_rejected_by_checksum() { + let source = StorageDaemon::new(); + let mut bytes = [0u8; MAX_STATE_SIZE]; + let len = export_state(&source, &mut bytes).unwrap(); + bytes[len - 1] ^= 1; + let mut restored = StorageDaemon::new(); + assert_eq!(import_state(&mut restored, &bytes[..len]), Err(PersistError::ChecksumMismatch)); + } +} From bb851015ee1f4f84f8445457a42e5afc96a12427 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 01:10:16 +0400 Subject: [PATCH 60/65] feat(aweos): connect storaged persistence to system runtime --- kernel/src/runtime/system.rs | 94 +++++++++++++++++++----------------- 1 file changed, 49 insertions(+), 45 deletions(-) diff --git a/kernel/src/runtime/system.rs b/kernel/src/runtime/system.rs index 9e14c24..f23373d 100644 --- a/kernel/src/runtime/system.rs +++ b/kernel/src/runtime/system.rs @@ -1,9 +1,10 @@ #![no_std] -use core::sync::atomic::{AtomicU16, AtomicU64, Ordering}; +use core::sync::atomic::{AtomicU16, Ordering}; use awe_appd::{AppId, AppManifest, AppState, AppSupervisor}; use awe_initd::{RestartPolicy, ServiceId, ServiceRuntimeSpec, ServiceSpec, ServiceState, Supervisor}; use awe_netd::NetworkDaemon; +use awe_storaged::{StorageDaemon, MAX_PACKAGE_FILES as STORAGE_MAX_PACKAGE_FILES}; use crate::drivers::{KeyCode, Ps2Event}; use crate::process::{ProcessDescriptor, ProcessId, ProcessManager, ProcessState, ResourceBudget}; use crate::process::context::{CpuContext, ProcessContext}; @@ -18,23 +19,12 @@ static mut PROCESS_STACKS: [[u8; 16384]; MAX_RUNTIME_PROCESSES] = [[0; 16384]; M extern "C" fn runtime_service_entry() -> ! { loop { unsafe { core::arch::asm!("hlt", options(nomem, nostack, preserves_flags)); } } } extern "C" fn runtime_app_entry() -> ! { loop { unsafe { core::arch::asm!("hlt", options(nomem, nostack, preserves_flags)); } } } -fn spawn_service(entry: usize, service: ServiceId, memory_pages: u32, cpu_budget: u32) -> Result { - if entry == 0 || service.0 == 0 || memory_pages == 0 || cpu_budget == 0 { return Err(()); } - Ok(service.0 as u64) -} -fn spawn_app(app: AppId, memory_pages: u32, _capabilities: u64) -> Result { - if app.0 == 0 || memory_pages == 0 { return Err(()); } - Ok(0x1_0000 + app.0) -} -fn create_window(_app: AppId) -> Result { - let id = NEXT_WINDOW_ID.fetch_add(1, Ordering::Relaxed); - if id == 0 { Err(()) } else { Ok(id) } -} +fn entry_address(entry: extern "C" fn() -> !) -> usize { entry as *const () as usize } +fn spawn_service(entry: usize, service: ServiceId, memory_pages: u32, cpu_budget: u32) -> Result { if entry == 0 || service.0 == 0 || memory_pages == 0 || cpu_budget == 0 { return Err(()); } Ok(service.0 as u64) } +fn spawn_app(app: AppId, memory_pages: u32, _capabilities: u64) -> Result { if app.0 == 0 || memory_pages == 0 { return Err(()); } Ok(0x1_0000 + app.0) } +fn create_window(_app: AppId) -> Result { let id = NEXT_WINDOW_ID.fetch_add(1, Ordering::Relaxed); if id == 0 { Err(()) } else { Ok(id) } } -const ALL: CapabilitySet = CapabilitySet( - CapabilitySet::PROCESS.0 | CapabilitySet::MEMORY.0 | CapabilitySet::IPC.0 | - CapabilitySet::DEVICE.0 | CapabilitySet::STORAGE.0 | CapabilitySet::NETWORK.0 | CapabilitySet::UI.0, -); +const ALL: CapabilitySet = CapabilitySet(CapabilitySet::PROCESS.0 | CapabilitySet::MEMORY.0 | CapabilitySet::IPC.0 | CapabilitySet::DEVICE.0 | CapabilitySet::STORAGE.0 | CapabilitySet::NETWORK.0 | CapabilitySet::UI.0); pub struct SystemRuntime { pub core: EndUserRuntime, @@ -43,6 +33,9 @@ pub struct SystemRuntime { pub apps: AppSupervisor, pub processes: ProcessManager, pub namespaces: NamespaceManager, + pub storage: StorageDaemon, + pub storage_state: [u8; awe_storaged::persistence::MAX_STATE_SIZE], + pub storage_state_len: usize, pub network: NetworkDaemon, pub cursor_x: i32, pub cursor_y: i32, @@ -50,7 +43,7 @@ pub struct SystemRuntime { impl SystemRuntime { pub const fn new() -> Self { - Self { core: EndUserRuntime::new(), windows: WindowManager::new(), services: Supervisor::new(spawn_service), apps: AppSupervisor::new(spawn_app, create_window), processes: ProcessManager::new(), namespaces: NamespaceManager::new(), network: NetworkDaemon::new(), cursor_x: 0, cursor_y: 0 } + Self { core: EndUserRuntime::new(), windows: WindowManager::new(), services: Supervisor::new(spawn_service), apps: AppSupervisor::new(spawn_app, create_window), processes: ProcessManager::new(), namespaces: NamespaceManager::new(), storage: StorageDaemon::new(), storage_state: [0; awe_storaged::persistence::MAX_STATE_SIZE], storage_state_len: 0, network: NetworkDaemon::new(), cursor_x: 0, cursor_y: 0 } } fn register_process(&mut self, pid: u64, entry: u64, memory_pages: u32, ipc_messages: u64) -> Result<(), ()> { @@ -65,6 +58,7 @@ impl SystemRuntime { } pub fn attach_framebuffer(&mut self, fb: FramebufferInfo) -> Result<(), super::EndUserRuntimeError> { self.core.attach_framebuffer(fb) } + pub fn mount_core_namespaces(&mut self, first_block: u64) -> Result<(), crate::storage::NamespaceError> { let spacing = 128u64; self.namespaces.mount(Namespace::Config, first_block)?; @@ -72,56 +66,68 @@ impl SystemRuntime { self.namespaces.mount(Namespace::Apps, first_block + spacing * 2)?; self.namespaces.mount(Namespace::System, first_block + spacing * 3)?; self.namespaces.mount(Namespace::Log, first_block + spacing * 4)?; + let volume = self.storage.register_volume(awe_storaged::VolumeType::AweFsVolume, 8192, first_block, false).map_err(|_| crate::storage::NamespaceError::Invalid)?; + self.storage.mount_volume(volume, 0xAWE0_0001).map_err(|_| crate::storage::NamespaceError::Invalid)?; + let _ = self.storage.create_snapshot(volume, 0).map_err(|_| crate::storage::NamespaceError::Invalid)?; + self.persist_storage_state().map_err(|_| crate::storage::NamespaceError::Invalid)?; Ok(()) } + + pub fn persist_storage_state(&mut self) -> Result { + let len = awe_storaged::persistence::export_state(&self.storage, &mut self.storage_state)?; + self.storage_state_len = len; + Ok(len) + } + + pub fn restore_storage_state(&mut self) -> Result<(), awe_storaged::persistence::PersistError> { + if self.storage_state_len == 0 { return Ok(()); } + awe_storaged::persistence::import_state(&mut self.storage, &self.storage_state[..self.storage_state_len]) + } + pub fn register_network_interface(&mut self, mac: [u8; 6]) -> Result { self.network.add_interface(awe_netd::MacAddress(mac)) } pub fn register_core_services(&mut self) -> Result<(), awe_initd::RuntimeError> { let n = [None; awe_initd::runtime::MAX_DEPENDENCIES]; let d1 = [Some(ServiceId(1)), None, None, None, None, None, None, None]; let d4 = [Some(ServiceId(4)), None, None, None, None, None, None, None]; + let entry = entry_address(runtime_service_entry); let specs = [ - ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(1), restart: RestartPolicy::Always, capability_mask: CapabilitySet::DEVICE.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: n, dependency_count: 0, entry: runtime_service_entry as usize }, - ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(2), restart: RestartPolicy::Always, capability_mask: CapabilitySet::STORAGE.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: d1, dependency_count: 1, entry: runtime_service_entry as usize }, - ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(3), restart: RestartPolicy::Always, capability_mask: CapabilitySet::NETWORK.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: d1, dependency_count: 1, entry: runtime_service_entry as usize }, - ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(4), restart: RestartPolicy::Always, capability_mask: CapabilitySet::IPC.0, memory_limit_pages: 32, cpu_budget_ticks: 5_000 }, dependencies: d1, dependency_count: 1, entry: runtime_service_entry as usize }, - ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(5), restart: RestartPolicy::Always, capability_mask: CapabilitySet::PROCESS.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: d4, dependency_count: 1, entry: runtime_service_entry as usize }, - ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(6), restart: RestartPolicy::Always, capability_mask: CapabilitySet::UI.union(CapabilitySet::IPC).0, memory_limit_pages: 128, cpu_budget_ticks: 20_000 }, dependencies: d4, dependency_count: 1, entry: runtime_service_entry as usize }, + ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(1), restart: RestartPolicy::Always, capability_mask: CapabilitySet::DEVICE.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: n, dependency_count: 0, entry }, + ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(2), restart: RestartPolicy::Always, capability_mask: CapabilitySet::STORAGE.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: d1, dependency_count: 1, entry }, + ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(3), restart: RestartPolicy::Always, capability_mask: CapabilitySet::NETWORK.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: d1, dependency_count: 1, entry }, + ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(4), restart: RestartPolicy::Always, capability_mask: CapabilitySet::IPC.0, memory_limit_pages: 32, cpu_budget_ticks: 5_000 }, dependencies: d1, dependency_count: 1, entry }, + ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(5), restart: RestartPolicy::Always, capability_mask: CapabilitySet::PROCESS.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: d4, dependency_count: 1, entry }, + ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(6), restart: RestartPolicy::Always, capability_mask: CapabilitySet::UI.union(CapabilitySet::IPC).0, memory_limit_pages: 128, cpu_budget_ticks: 20_000 }, dependencies: d4, dependency_count: 1, entry }, ]; for spec in specs { self.services.register(spec)?; } Ok(()) } + pub fn start_core_services(&mut self) -> Result<(), awe_initd::RuntimeError> { - for id in 1..=6 { - let pid = self.services.start(ServiceId(id))?; - if self.register_process(pid, runtime_service_entry as usize as u64, 64, 128).is_err() { return Err(awe_initd::RuntimeError::SpawnFailed); } - } + let entry = entry_address(runtime_service_entry) as u64; + for id in 1..=6 { let pid = self.services.start(ServiceId(id))?; if self.register_process(pid, entry, 64, 128).is_err() { return Err(awe_initd::RuntimeError::SpawnFailed); } } Ok(()) } + pub fn admit_core_apps(&mut self) -> Result<(), awe_appd::AppRuntimeError> { let apps = [(1u64, CapabilitySet::UI.union(CapabilitySet::IPC).union(CapabilitySet::STORAGE)), (2u64, CapabilitySet::UI.union(CapabilitySet::IPC).union(CapabilitySet::STORAGE)), (3u64, CapabilitySet::UI.union(CapabilitySet::IPC).union(CapabilitySet::STORAGE)), (4u64, CapabilitySet::UI.union(CapabilitySet::IPC))]; - for (id, caps) in apps { - let manifest = AppManifest { id: AppId(id), abi_major: awe_appd::AWE_APP_ABI_MAJOR, abi_minor: awe_appd::AWE_APP_ABI_MINOR, memory_limit_pages: 32, capability_mask: caps.0, dependency_count: 0, resource_count: 0 }; - self.apps.admit(manifest).map_err(|_| awe_appd::AppRuntimeError::InvalidManifest)?; - } + for (id, caps) in apps { let manifest = AppManifest { id: AppId(id), abi_major: awe_appd::AWE_APP_ABI_MAJOR, abi_minor: awe_appd::AWE_APP_ABI_MINOR, memory_limit_pages: 32, capability_mask: caps.0, dependency_count: 0, resource_count: 0 }; self.apps.admit(manifest).map_err(|_| awe_appd::AppRuntimeError::InvalidManifest)?; } Ok(()) } + pub fn start_core_apps(&mut self) -> Result<(), awe_appd::AppRuntimeError> { - for id in 1..=4 { - let pid = self.apps.start(AppId(id), ALL.0)?; - if self.register_process(pid, runtime_app_entry as usize as u64, 32, 64).is_err() { return Err(awe_appd::AppRuntimeError::SpawnFailed); } - } + let entry = entry_address(runtime_app_entry) as u64; + for id in 1..=4 { let pid = self.apps.start(AppId(id), ALL.0)?; if self.register_process(pid, entry, 32, 64).is_err() { return Err(awe_appd::AppRuntimeError::SpawnFailed); } } Ok(()) } + pub fn route_ps2(&mut self, event: Ps2Event) -> Result { - let translated = match event { - Ps2Event::Key { code, pressed } => InputEvent::Key { code: key_code_value(code), pressed }, - Ps2Event::Pointer { dx, dy, buttons } => { self.cursor_x = self.cursor_x.saturating_add(dx as i32); self.cursor_y = self.cursor_y.saturating_add(dy as i32); InputEvent::Pointer { x: self.cursor_x, y: self.cursor_y, buttons } } - }; + let translated = match event { Ps2Event::Key { code, pressed } => InputEvent::Key { code: key_code_value(code), pressed }, Ps2Event::Pointer { dx, dy, buttons } => { self.cursor_x = self.cursor_x.saturating_add(dx as i32); self.cursor_y = self.cursor_y.saturating_add(dy as i32); InputEvent::Pointer { x: self.cursor_x, y: self.cursor_y, buttons } } }; self.core.push_input(translated)?; self.windows.handle_input(translated); Ok(RuntimeEvent::Input(translated)) } + pub fn create_native_window(&mut self, rect: RuntimeRect) -> Result { self.windows.create(rect) } pub fn pointer_target(&mut self) -> Option { self.windows.hit_test(self.cursor_x, self.cursor_y) } pub fn service_state(&self, id: u16) -> Option { self.services.state(ServiceId(id)) } @@ -130,9 +136,7 @@ impl SystemRuntime { pub fn scheduler_ticks(&self) -> u64 { self.processes.scheduler_ticks() } } -fn key_code_value(code: KeyCode) -> u16 { - match code { KeyCode::Escape => 0x01, KeyCode::Enter => 0x1C, KeyCode::Backspace => 0x0E, KeyCode::Tab => 0x0D, KeyCode::Space => 0x39, KeyCode::Left => 0x4B, KeyCode::Right => 0x4D, KeyCode::Up => 0x48, KeyCode::Down => 0x50, KeyCode::Character(v) | KeyCode::Unknown(v) => v as u16 } -} +fn key_code_value(code: KeyCode) -> u16 { match code { KeyCode::Escape => 0x01, KeyCode::Enter => 0x1C, KeyCode::Backspace => 0x0E, KeyCode::Tab => 0x0D, KeyCode::Space => 0x39, KeyCode::Left => 0x4B, KeyCode::Right => 0x4D, KeyCode::Up => 0x48, KeyCode::Down => 0x50, KeyCode::Character(v) | KeyCode::Unknown(v) => v as u16 } } impl Default for SystemRuntime { fn default() -> Self { Self::new() } } @@ -143,5 +147,5 @@ mod tests { #[test] fn core_apps_admit_and_register_processes() { let mut r=SystemRuntime::new(); r.admit_core_apps().unwrap(); r.start_core_apps().unwrap(); assert_eq!(r.app_state(1),Some(AppState::Running)); assert_eq!(r.process_count(),4); } #[test] fn ps2_input_reaches_window_manager() { let mut r=SystemRuntime::new(); r.create_native_window(RuntimeRect{x:0,y:0,width:100,height:100}).unwrap(); r.route_ps2(Ps2Event::Pointer{dx:20,dy:20,buttons:1}).unwrap(); assert!(r.pointer_target().is_some()); } #[test] fn capability_set_is_used_for_app_admission() { let mut r=SystemRuntime::new(); r.admit_core_apps().unwrap(); assert_eq!(r.apps.start(AppId(1),CapabilitySet::UI.0),Err(awe_appd::AppRuntimeError::CapabilityDenied)); } - #[test] fn namespace_mounts_are_bounded() { let mut r=SystemRuntime::new(); r.mount_core_namespaces(8).unwrap(); assert!(r.namespaces.is_mounted(Namespace::Config)); assert!(r.namespaces.is_mounted(Namespace::Log)); } -} \ No newline at end of file + #[test] fn namespace_mounts_are_bounded_and_storage_persisted() { let mut r=SystemRuntime::new(); r.mount_core_namespaces(32).unwrap(); assert!(r.namespaces.is_mounted(Namespace::Config)); assert!(r.namespaces.is_mounted(Namespace::Log)); assert!(r.storage_state_len > 0); let state_len = r.storage_state_len; assert!(state_len <= awe_storaged::persistence::MAX_STATE_SIZE); assert!(STORAGE_MAX_PACKAGE_FILES > 0); } +} From 0dad0474d4013274150abd5cbb90eb7bc415bf53 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 01:10:36 +0400 Subject: [PATCH 61/65] fix(aweos): align storage persistence with namespace error contract --- kernel/src/runtime/system.rs | 98 ++++++++---------------------------- 1 file changed, 22 insertions(+), 76 deletions(-) diff --git a/kernel/src/runtime/system.rs b/kernel/src/runtime/system.rs index f23373d..edab444 100644 --- a/kernel/src/runtime/system.rs +++ b/kernel/src/runtime/system.rs @@ -18,12 +18,10 @@ static mut PROCESS_STACKS: [[u8; 16384]; MAX_RUNTIME_PROCESSES] = [[0; 16384]; M extern "C" fn runtime_service_entry() -> ! { loop { unsafe { core::arch::asm!("hlt", options(nomem, nostack, preserves_flags)); } } } extern "C" fn runtime_app_entry() -> ! { loop { unsafe { core::arch::asm!("hlt", options(nomem, nostack, preserves_flags)); } } } - fn entry_address(entry: extern "C" fn() -> !) -> usize { entry as *const () as usize } fn spawn_service(entry: usize, service: ServiceId, memory_pages: u32, cpu_budget: u32) -> Result { if entry == 0 || service.0 == 0 || memory_pages == 0 || cpu_budget == 0 { return Err(()); } Ok(service.0 as u64) } fn spawn_app(app: AppId, memory_pages: u32, _capabilities: u64) -> Result { if app.0 == 0 || memory_pages == 0 { return Err(()); } Ok(0x1_0000 + app.0) } fn create_window(_app: AppId) -> Result { let id = NEXT_WINDOW_ID.fetch_add(1, Ordering::Relaxed); if id == 0 { Err(()) } else { Ok(id) } } - const ALL: CapabilitySet = CapabilitySet(CapabilitySet::PROCESS.0 | CapabilitySet::MEMORY.0 | CapabilitySet::IPC.0 | CapabilitySet::DEVICE.0 | CapabilitySet::STORAGE.0 | CapabilitySet::NETWORK.0 | CapabilitySet::UI.0); pub struct SystemRuntime { @@ -42,10 +40,7 @@ pub struct SystemRuntime { } impl SystemRuntime { - pub const fn new() -> Self { - Self { core: EndUserRuntime::new(), windows: WindowManager::new(), services: Supervisor::new(spawn_service), apps: AppSupervisor::new(spawn_app, create_window), processes: ProcessManager::new(), namespaces: NamespaceManager::new(), storage: StorageDaemon::new(), storage_state: [0; awe_storaged::persistence::MAX_STATE_SIZE], storage_state_len: 0, network: NetworkDaemon::new(), cursor_x: 0, cursor_y: 0 } - } - + pub const fn new() -> Self { Self { core: EndUserRuntime::new(), windows: WindowManager::new(), services: Supervisor::new(spawn_service), apps: AppSupervisor::new(spawn_app, create_window), processes: ProcessManager::new(), namespaces: NamespaceManager::new(), storage: StorageDaemon::new(), storage_state: [0; awe_storaged::persistence::MAX_STATE_SIZE], storage_state_len: 0, network: NetworkDaemon::new(), cursor_x: 0, cursor_y: 0 } } fn register_process(&mut self, pid: u64, entry: u64, memory_pages: u32, ipc_messages: u64) -> Result<(), ()> { if pid == 0 || entry == 0 || self.processes.len() >= MAX_RUNTIME_PROCESSES { return Err(()); } let slot = self.processes.len(); @@ -56,9 +51,7 @@ impl SystemRuntime { self.processes.register(descriptor, context).map_err(|_| ())?; self.processes.make_runnable(ProcessId(pid)).map_err(|_| ()) } - pub fn attach_framebuffer(&mut self, fb: FramebufferInfo) -> Result<(), super::EndUserRuntimeError> { self.core.attach_framebuffer(fb) } - pub fn mount_core_namespaces(&mut self, first_block: u64) -> Result<(), crate::storage::NamespaceError> { let spacing = 128u64; self.namespaces.mount(Namespace::Config, first_block)?; @@ -66,31 +59,17 @@ impl SystemRuntime { self.namespaces.mount(Namespace::Apps, first_block + spacing * 2)?; self.namespaces.mount(Namespace::System, first_block + spacing * 3)?; self.namespaces.mount(Namespace::Log, first_block + spacing * 4)?; - let volume = self.storage.register_volume(awe_storaged::VolumeType::AweFsVolume, 8192, first_block, false).map_err(|_| crate::storage::NamespaceError::Invalid)?; - self.storage.mount_volume(volume, 0xAWE0_0001).map_err(|_| crate::storage::NamespaceError::Invalid)?; - let _ = self.storage.create_snapshot(volume, 0).map_err(|_| crate::storage::NamespaceError::Invalid)?; - self.persist_storage_state().map_err(|_| crate::storage::NamespaceError::Invalid)?; + let volume = self.storage.register_volume(awe_storaged::VolumeType::AweFsVolume, 8192, first_block, false).map_err(|_| crate::storage::NamespaceError::Capacity)?; + self.storage.mount_volume(volume, 0xAWE0_0001).map_err(|_| crate::storage::NamespaceError::Capacity)?; + self.storage.create_snapshot(volume, 0).map_err(|_| crate::storage::NamespaceError::Capacity)?; + self.persist_storage_state().map_err(|_| crate::storage::NamespaceError::Capacity)?; Ok(()) } - - pub fn persist_storage_state(&mut self) -> Result { - let len = awe_storaged::persistence::export_state(&self.storage, &mut self.storage_state)?; - self.storage_state_len = len; - Ok(len) - } - - pub fn restore_storage_state(&mut self) -> Result<(), awe_storaged::persistence::PersistError> { - if self.storage_state_len == 0 { return Ok(()); } - awe_storaged::persistence::import_state(&mut self.storage, &self.storage_state[..self.storage_state_len]) - } - + pub fn persist_storage_state(&mut self) -> Result { let len = awe_storaged::persistence::export_state(&self.storage, &mut self.storage_state)?; self.storage_state_len = len; Ok(len) } + pub fn restore_storage_state(&mut self) -> Result<(), awe_storaged::persistence::PersistError> { if self.storage_state_len == 0 { return Ok(()); } awe_storaged::persistence::import_state(&mut self.storage, &self.storage_state[..self.storage_state_len]) } pub fn register_network_interface(&mut self, mac: [u8; 6]) -> Result { self.network.add_interface(awe_netd::MacAddress(mac)) } - pub fn register_core_services(&mut self) -> Result<(), awe_initd::RuntimeError> { - let n = [None; awe_initd::runtime::MAX_DEPENDENCIES]; - let d1 = [Some(ServiceId(1)), None, None, None, None, None, None, None]; - let d4 = [Some(ServiceId(4)), None, None, None, None, None, None, None]; - let entry = entry_address(runtime_service_entry); + let n = [None; awe_initd::runtime::MAX_DEPENDENCIES]; let d1 = [Some(ServiceId(1)), None, None, None, None, None, None, None]; let d4 = [Some(ServiceId(4)), None, None, None, None, None, None, None]; let entry = entry_address(runtime_service_entry); let specs = [ ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(1), restart: RestartPolicy::Always, capability_mask: CapabilitySet::DEVICE.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: n, dependency_count: 0, entry }, ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(2), restart: RestartPolicy::Always, capability_mask: CapabilitySet::STORAGE.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: d1, dependency_count: 1, entry }, @@ -99,53 +78,20 @@ impl SystemRuntime { ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(5), restart: RestartPolicy::Always, capability_mask: CapabilitySet::PROCESS.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: d4, dependency_count: 1, entry }, ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(6), restart: RestartPolicy::Always, capability_mask: CapabilitySet::UI.union(CapabilitySet::IPC).0, memory_limit_pages: 128, cpu_budget_ticks: 20_000 }, dependencies: d4, dependency_count: 1, entry }, ]; - for spec in specs { self.services.register(spec)?; } - Ok(()) + for spec in specs { self.services.register(spec)?; } Ok(()) } - - pub fn start_core_services(&mut self) -> Result<(), awe_initd::RuntimeError> { - let entry = entry_address(runtime_service_entry) as u64; - for id in 1..=6 { let pid = self.services.start(ServiceId(id))?; if self.register_process(pid, entry, 64, 128).is_err() { return Err(awe_initd::RuntimeError::SpawnFailed); } } - Ok(()) - } - - pub fn admit_core_apps(&mut self) -> Result<(), awe_appd::AppRuntimeError> { - let apps = [(1u64, CapabilitySet::UI.union(CapabilitySet::IPC).union(CapabilitySet::STORAGE)), (2u64, CapabilitySet::UI.union(CapabilitySet::IPC).union(CapabilitySet::STORAGE)), (3u64, CapabilitySet::UI.union(CapabilitySet::IPC).union(CapabilitySet::STORAGE)), (4u64, CapabilitySet::UI.union(CapabilitySet::IPC))]; - for (id, caps) in apps { let manifest = AppManifest { id: AppId(id), abi_major: awe_appd::AWE_APP_ABI_MAJOR, abi_minor: awe_appd::AWE_APP_ABI_MINOR, memory_limit_pages: 32, capability_mask: caps.0, dependency_count: 0, resource_count: 0 }; self.apps.admit(manifest).map_err(|_| awe_appd::AppRuntimeError::InvalidManifest)?; } - Ok(()) - } - - pub fn start_core_apps(&mut self) -> Result<(), awe_appd::AppRuntimeError> { - let entry = entry_address(runtime_app_entry) as u64; - for id in 1..=4 { let pid = self.apps.start(AppId(id), ALL.0)?; if self.register_process(pid, entry, 32, 64).is_err() { return Err(awe_appd::AppRuntimeError::SpawnFailed); } } - Ok(()) - } - - pub fn route_ps2(&mut self, event: Ps2Event) -> Result { - let translated = match event { Ps2Event::Key { code, pressed } => InputEvent::Key { code: key_code_value(code), pressed }, Ps2Event::Pointer { dx, dy, buttons } => { self.cursor_x = self.cursor_x.saturating_add(dx as i32); self.cursor_y = self.cursor_y.saturating_add(dy as i32); InputEvent::Pointer { x: self.cursor_x, y: self.cursor_y, buttons } } }; - self.core.push_input(translated)?; - self.windows.handle_input(translated); - Ok(RuntimeEvent::Input(translated)) - } - - pub fn create_native_window(&mut self, rect: RuntimeRect) -> Result { self.windows.create(rect) } - pub fn pointer_target(&mut self) -> Option { self.windows.hit_test(self.cursor_x, self.cursor_y) } - pub fn service_state(&self, id: u16) -> Option { self.services.state(ServiceId(id)) } - pub fn app_state(&self, id: u64) -> Option { self.apps.state(AppId(id)) } - pub fn process_count(&self) -> usize { self.processes.len() } - pub fn scheduler_ticks(&self) -> u64 { self.processes.scheduler_ticks() } + pub fn start_core_services(&mut self) -> Result<(), awe_initd::RuntimeError> { let entry=entry_address(runtime_service_entry) as u64; for id in 1..=6 { let pid=self.services.start(ServiceId(id))?; if self.register_process(pid,entry,64,128).is_err(){return Err(awe_initd::RuntimeError::SpawnFailed);} } Ok(()) } + pub fn admit_core_apps(&mut self) -> Result<(), awe_appd::AppRuntimeError> { let apps=[(1u64,CapabilitySet::UI.union(CapabilitySet::IPC).union(CapabilitySet::STORAGE)),(2u64,CapabilitySet::UI.union(CapabilitySet::IPC).union(CapabilitySet::STORAGE)),(3u64,CapabilitySet::UI.union(CapabilitySet::IPC).union(CapabilitySet::STORAGE)),(4u64,CapabilitySet::UI.union(CapabilitySet::IPC))]; for(id,caps)in apps{let manifest=AppManifest{id:AppId(id),abi_major:awe_appd::AWE_APP_ABI_MAJOR,abi_minor:awe_appd::AWE_APP_ABI_MINOR,memory_limit_pages:32,capability_mask:caps.0,dependency_count:0,resource_count:0};self.apps.admit(manifest).map_err(|_|awe_appd::AppRuntimeError::InvalidManifest)?;}Ok(()) } + pub fn start_core_apps(&mut self) -> Result<(), awe_appd::AppRuntimeError> { let entry=entry_address(runtime_app_entry) as u64; for id in 1..=4{let pid=self.apps.start(AppId(id),ALL.0)?;if self.register_process(pid,entry,32,64).is_err(){return Err(awe_appd::AppRuntimeError::SpawnFailed);}}Ok(()) } + pub fn route_ps2(&mut self,event:Ps2Event)->Result{let translated=match event{Ps2Event::Key{code,pressed}=>InputEvent::Key{code:key_code_value(code),pressed},Ps2Event::Pointer{dx,dy,buttons}=>{self.cursor_x=self.cursor_x.saturating_add(dx as i32);self.cursor_y=self.cursor_y.saturating_add(dy as i32);InputEvent::Pointer{x:self.cursor_x,y:self.cursor_y,buttons}}};self.core.push_input(translated)?;self.windows.handle_input(translated);Ok(RuntimeEvent::Input(translated))} + pub fn create_native_window(&mut self,rect:RuntimeRect)->Result{self.windows.create(rect)} + pub fn pointer_target(&mut self)->Option{self.windows.hit_test(self.cursor_x,self.cursor_y)} + pub fn service_state(&self,id:u16)->Option{self.services.state(ServiceId(id))} + pub fn app_state(&self,id:u64)->Option{self.apps.state(AppId(id))} + pub fn process_count(&self)->usize{self.processes.len()} + pub fn scheduler_ticks(&self)->u64{self.processes.scheduler_ticks()} } - -fn key_code_value(code: KeyCode) -> u16 { match code { KeyCode::Escape => 0x01, KeyCode::Enter => 0x1C, KeyCode::Backspace => 0x0E, KeyCode::Tab => 0x0D, KeyCode::Space => 0x39, KeyCode::Left => 0x4B, KeyCode::Right => 0x4D, KeyCode::Up => 0x48, KeyCode::Down => 0x50, KeyCode::Character(v) | KeyCode::Unknown(v) => v as u16 } } - -impl Default for SystemRuntime { fn default() -> Self { Self::new() } } - +fn key_code_value(code:KeyCode)->u16{match code{KeyCode::Escape=>0x01,KeyCode::Enter=>0x1C,KeyCode::Backspace=>0x0E,KeyCode::Tab=>0x0D,KeyCode::Space=>0x39,KeyCode::Left=>0x4B,KeyCode::Right=>0x4D,KeyCode::Up=>0x48,KeyCode::Down=>0x50,KeyCode::Character(v)|KeyCode::Unknown(v)=>v as u16}} +impl Default for SystemRuntime{fn default()->Self{Self::new()}} #[cfg(test)] -mod tests { - use super::*; - #[test] fn core_services_start_and_register_processes() { let mut r=SystemRuntime::new(); r.register_core_services().unwrap(); r.start_core_services().unwrap(); assert_eq!(r.service_state(6),Some(ServiceState::Running)); assert_eq!(r.process_count(),6); } - #[test] fn core_apps_admit_and_register_processes() { let mut r=SystemRuntime::new(); r.admit_core_apps().unwrap(); r.start_core_apps().unwrap(); assert_eq!(r.app_state(1),Some(AppState::Running)); assert_eq!(r.process_count(),4); } - #[test] fn ps2_input_reaches_window_manager() { let mut r=SystemRuntime::new(); r.create_native_window(RuntimeRect{x:0,y:0,width:100,height:100}).unwrap(); r.route_ps2(Ps2Event::Pointer{dx:20,dy:20,buttons:1}).unwrap(); assert!(r.pointer_target().is_some()); } - #[test] fn capability_set_is_used_for_app_admission() { let mut r=SystemRuntime::new(); r.admit_core_apps().unwrap(); assert_eq!(r.apps.start(AppId(1),CapabilitySet::UI.0),Err(awe_appd::AppRuntimeError::CapabilityDenied)); } - #[test] fn namespace_mounts_are_bounded_and_storage_persisted() { let mut r=SystemRuntime::new(); r.mount_core_namespaces(32).unwrap(); assert!(r.namespaces.is_mounted(Namespace::Config)); assert!(r.namespaces.is_mounted(Namespace::Log)); assert!(r.storage_state_len > 0); let state_len = r.storage_state_len; assert!(state_len <= awe_storaged::persistence::MAX_STATE_SIZE); assert!(STORAGE_MAX_PACKAGE_FILES > 0); } -} +mod tests{use super::*;#[test]fn core_services_start_and_register_processes(){let mut r=SystemRuntime::new();r.register_core_services().unwrap();r.start_core_services().unwrap();assert_eq!(r.service_state(6),Some(ServiceState::Running));assert_eq!(r.process_count(),6);}#[test]fn core_apps_admit_and_register_processes(){let mut r=SystemRuntime::new();r.admit_core_apps().unwrap();r.start_core_apps().unwrap();assert_eq!(r.app_state(1),Some(AppState::Running));assert_eq!(r.process_count(),4);}#[test]fn ps2_input_reaches_window_manager(){let mut r=SystemRuntime::new();r.create_native_window(RuntimeRect{x:0,y:0,width:100,height:100}).unwrap();r.route_ps2(Ps2Event::Pointer{dx:20,dy:20,buttons:1}).unwrap();assert!(r.pointer_target().is_some());}#[test]fn capability_set_is_used_for_app_admission(){let mut r=SystemRuntime::new();r.admit_core_apps().unwrap();assert_eq!(r.apps.start(AppId(1),CapabilitySet::UI.0),Err(awe_appd::AppRuntimeError::CapabilityDenied));}#[test]fn namespace_mounts_are_bounded_and_storage_persisted(){let mut r=SystemRuntime::new();r.mount_core_namespaces(32).unwrap();assert!(r.namespaces.is_mounted(Namespace::Config));assert!(r.namespaces.is_mounted(Namespace::Log));assert!(r.storage_state_len>0);assert!(r.storage_state_len<=awe_storaged::persistence::MAX_STATE_SIZE);assert!(STORAGE_MAX_PACKAGE_FILES>0);}} From dcf4e8389bd85a955c4ed91e0475f80436158e8d Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 01:11:02 +0400 Subject: [PATCH 62/65] fix(aweos): correct persistent storaged state header layout --- services/storaged/src/persistence.rs | 256 ++++++--------------------- 1 file changed, 52 insertions(+), 204 deletions(-) diff --git a/services/storaged/src/persistence.rs b/services/storaged/src/persistence.rs index 6e07e35..4635a41 100644 --- a/services/storaged/src/persistence.rs +++ b/services/storaged/src/persistence.rs @@ -1,226 +1,74 @@ #![no_std] -use super::{PackageFileRecord, SnapshotMetadata, StorageDaemon, StorageVolume, VolumeType, MAX_MOUNTS, MAX_PACKAGE_FILES, MAX_SNAPSHOTS, MAX_VOLUMES}; +use super::{MountEntry, PackageFileRecord, SnapshotMetadata, StorageDaemon, StorageVolume, VolumeType, CACHE_BLOCKS, MAX_MOUNTS, MAX_PACKAGE_FILES, MAX_SNAPSHOTS, MAX_VOLUMES}; pub const STATE_MAGIC: [u8; 4] = *b"AWSP"; pub const STATE_VERSION: u8 = 1; pub const MAX_STATE_SIZE: usize = 4096; -const HEADER_LEN: usize = 16; -const VOLUME_REC_LEN: usize = 20; +const HEADER_LEN: usize = 20; +const VOLUME_REC_LEN: usize = 24; const MOUNT_REC_LEN: usize = 17; const SNAPSHOT_REC_LEN: usize = 24; const FILE_REC_LEN: usize = 25; #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum PersistError { - BufferTooSmall, - Truncated, - BadMagic, - UnsupportedVersion, - ChecksumMismatch, - InvalidRecord, - InvalidCount, - Overflow, -} +pub enum PersistError { BufferTooSmall, Truncated, BadMagic, UnsupportedVersion, ChecksumMismatch, InvalidRecord, InvalidCount, Overflow } fn checksum(bytes: &[u8]) -> u32 { let mut hash = 0x811c_9dc5u32; - for &byte in bytes { - hash ^= byte as u32; - hash = hash.wrapping_mul(0x0100_0193); - } + for &byte in bytes { hash ^= byte as u32; hash = hash.wrapping_mul(0x0100_0193); } hash } - -fn put_u16(out: &mut [u8], pos: &mut usize, value: u16) -> Result<(), PersistError> { - let end = pos.checked_add(2).ok_or(PersistError::Overflow)?; - if end > out.len() { return Err(PersistError::BufferTooSmall); } - out[*pos..end].copy_from_slice(&value.to_le_bytes()); - *pos = end; - Ok(()) -} -fn put_u32(out: &mut [u8], pos: &mut usize, value: u32) -> Result<(), PersistError> { - let end = pos.checked_add(4).ok_or(PersistError::Overflow)?; - if end > out.len() { return Err(PersistError::BufferTooSmall); } - out[*pos..end].copy_from_slice(&value.to_le_bytes()); - *pos = end; - Ok(()) -} -fn put_u64(out: &mut [u8], pos: &mut usize, value: u64) -> Result<(), PersistError> { - let end = pos.checked_add(8).ok_or(PersistError::Overflow)?; - if end > out.len() { return Err(PersistError::BufferTooSmall); } - out[*pos..end].copy_from_slice(&value.to_le_bytes()); - *pos = end; - Ok(()) -} -fn put_u8(out: &mut [u8], pos: &mut usize, value: u8) -> Result<(), PersistError> { - if *pos >= out.len() { return Err(PersistError::BufferTooSmall); } - out[*pos] = value; - *pos += 1; - Ok(()) -} -fn get_u16(input: &[u8], pos: &mut usize) -> Result { - let end = pos.checked_add(2).ok_or(PersistError::Overflow)?; - if end > input.len() { return Err(PersistError::Truncated); } - let value = u16::from_le_bytes([input[*pos], input[*pos + 1]]); - *pos = end; - Ok(value) -} -fn get_u32(input: &[u8], pos: &mut usize) -> Result { - let end = pos.checked_add(4).ok_or(PersistError::Overflow)?; - if end > input.len() { return Err(PersistError::Truncated); } - let value = u32::from_le_bytes([input[*pos], input[*pos + 1], input[*pos + 2], input[*pos + 3]]); - *pos = end; - Ok(value) -} -fn get_u64(input: &[u8], pos: &mut usize) -> Result { - let end = pos.checked_add(8).ok_or(PersistError::Overflow)?; - if end > input.len() { return Err(PersistError::Truncated); } - let value = u64::from_le_bytes([ - input[*pos], input[*pos + 1], input[*pos + 2], input[*pos + 3], - input[*pos + 4], input[*pos + 5], input[*pos + 6], input[*pos + 7], - ]); - *pos = end; - Ok(value) -} -fn get_u8(input: &[u8], pos: &mut usize) -> Result { - if *pos >= input.len() { return Err(PersistError::Truncated); } - let value = input[*pos]; - *pos += 1; - Ok(value) -} - -fn volume_type_to_u8(value: VolumeType) -> u8 { - match value { VolumeType::Ramdisk => 0, VolumeType::GptPartition => 1, VolumeType::VirtualDisk => 2, VolumeType::AweFsVolume => 3 } -} -fn volume_type_from_u8(value: u8) -> Result { - match value { 0 => Ok(VolumeType::Ramdisk), 1 => Ok(VolumeType::GptPartition), 2 => Ok(VolumeType::VirtualDisk), 3 => Ok(VolumeType::AweFsVolume), _ => Err(PersistError::InvalidRecord) } +fn put_u16(out:&mut [u8],pos:&mut usize,value:u16)->Result<(),PersistError>{let end=pos.checked_add(2).ok_or(PersistError::Overflow)?;if end>out.len(){return Err(PersistError::BufferTooSmall)}out[*pos..end].copy_from_slice(&value.to_le_bytes());*pos=end;Ok(())} +fn put_u32(out:&mut [u8],pos:&mut usize,value:u32)->Result<(),PersistError>{let end=pos.checked_add(4).ok_or(PersistError::Overflow)?;if end>out.len(){return Err(PersistError::BufferTooSmall)}out[*pos..end].copy_from_slice(&value.to_le_bytes());*pos=end;Ok(())} +fn put_u64(out:&mut [u8],pos:&mut usize,value:u64)->Result<(),PersistError>{let end=pos.checked_add(8).ok_or(PersistError::Overflow)?;if end>out.len(){return Err(PersistError::BufferTooSmall)}out[*pos..end].copy_from_slice(&value.to_le_bytes());*pos=end;Ok(())} +fn put_u8(out:&mut [u8],pos:&mut usize,value:u8)->Result<(),PersistError>{if *pos>=out.len(){return Err(PersistError::BufferTooSmall)}out[*pos]=value;*pos+=1;Ok(())} +fn get_u16(input:&[u8],pos:&mut usize)->Result{let end=pos.checked_add(2).ok_or(PersistError::Overflow)?;if end>input.len(){return Err(PersistError::Truncated)}let v=u16::from_le_bytes([input[*pos],input[*pos+1]]);*pos=end;Ok(v)} +fn get_u32(input:&[u8],pos:&mut usize)->Result{let end=pos.checked_add(4).ok_or(PersistError::Overflow)?;if end>input.len(){return Err(PersistError::Truncated)}let v=u32::from_le_bytes([input[*pos],input[*pos+1],input[*pos+2],input[*pos+3]]);*pos=end;Ok(v)} +fn get_u64(input:&[u8],pos:&mut usize)->Result{let end=pos.checked_add(8).ok_or(PersistError::Overflow)?;if end>input.len(){return Err(PersistError::Truncated)}let v=u64::from_le_bytes([input[*pos],input[*pos+1],input[*pos+2],input[*pos+3],input[*pos+4],input[*pos+5],input[*pos+6],input[*pos+7]]);*pos=end;Ok(v)} +fn get_u8(input:&[u8],pos:&mut usize)->Result{if *pos>=input.len(){return Err(PersistError::Truncated)}let v=input[*pos];*pos+=1;Ok(v)} +fn volume_type_to_u8(v:VolumeType)->u8{match v{VolumeType::Ramdisk=>0,VolumeType::GptPartition=>1,VolumeType::VirtualDisk=>2,VolumeType::AweFsVolume=>3}} +fn volume_type_from_u8(v:u8)->Result{match v{0=>Ok(VolumeType::Ramdisk),1=>Ok(VolumeType::GptPartition),2=>Ok(VolumeType::VirtualDisk),3=>Ok(VolumeType::AweFsVolume),_=>Err(PersistError::InvalidRecord)}} + +pub fn export_state(storage:&StorageDaemon,out:&mut [u8])->Result{ + if out.len()MAX_VOLUMES||mc>MAX_MOUNTS||sc>MAX_SNAPSHOTS||fc>MAX_PACKAGE_FILES{return Err(PersistError::InvalidCount)} + let payload_len=vc.checked_mul(VOLUME_REC_LEN).and_then(|v|v.checked_add(mc.checked_mul(MOUNT_REC_LEN)?)).and_then(|v|v.checked_add(sc.checked_mul(SNAPSHOT_REC_LEN)?)).and_then(|v|v.checked_add(fc.checked_mul(FILE_REC_LEN)?)).ok_or(PersistError::Overflow)?; + let total_len=HEADER_LEN.checked_add(payload_len).ok_or(PersistError::Overflow)?; + if total_len>out.len()||total_len>MAX_STATE_SIZE||total_len>u16::MAX as usize{return Err(PersistError::BufferTooSmall)} + out[..total_len].fill(0);out[..4].copy_from_slice(&STATE_MAGIC);out[4]=STATE_VERSION;out[5]=0;out[6..8].copy_from_slice(&(total_len as u16).to_le_bytes()); + let mut pos=12usize;put_u16(out,&mut pos,vc as u16)?;put_u16(out,&mut pos,mc as u16)?;put_u16(out,&mut pos,sc as u16)?;put_u16(out,&mut pos,fc as u16)?; + for r in storage.volumes.iter().flatten(){put_u32(out,&mut pos,r.volume_id)?;put_u8(out,&mut pos,volume_type_to_u8(r.volume_type))?;put_u8(out,&mut pos,u8::from(r.read_only))?;put_u16(out,&mut pos,0)?;put_u64(out,&mut pos,r.block_count)?;put_u64(out,&mut pos,r.start_lba)?} + for r in storage.mounts.iter().flatten(){put_u32(out,&mut pos,r.mount_id)?;put_u32(out,&mut pos,r.volume_id)?;put_u64(out,&mut pos,r.path_hash)?;put_u8(out,&mut pos,u8::from(r.active))?} + for r in storage.snapshots.iter().flatten(){put_u32(out,&mut pos,r.snapshot_id)?;put_u32(out,&mut pos,r.volume_id)?;put_u64(out,&mut pos,r.timestamp)?;put_u32(out,&mut pos,r.block_delta_count)?;put_u32(out,&mut pos,0)?} + for r in storage.files.iter().flatten(){put_u32(out,&mut pos,r.file_id)?;put_u32(out,&mut pos,r.volume_id)?;put_u64(out,&mut pos,r.name_hash)?;put_u32(out,&mut pos,r.size_bytes)?;put_u8(out,&mut pos,u8::from(r.is_installed_package))?} + out[8..12].copy_from_slice(&checksum(&out[HEADER_LEN..total_len]).to_le_bytes());Ok(total_len) } -pub fn export_state(storage: &StorageDaemon, out: &mut [u8]) -> Result { - if out.len() < HEADER_LEN { return Err(PersistError::BufferTooSmall); } - let volume_count = storage.volumes.iter().filter(|v| v.is_some()).count(); - let mount_count = storage.mounts.iter().filter(|v| v.is_some()).count(); - let snapshot_count = storage.snapshots.iter().filter(|v| v.is_some()).count(); - let file_count = storage.files.iter().filter(|v| v.is_some()).count(); - if volume_count > MAX_VOLUMES || mount_count > MAX_MOUNTS || snapshot_count > MAX_SNAPSHOTS || file_count > MAX_PACKAGE_FILES { return Err(PersistError::InvalidCount); } - - let payload_len = volume_count.checked_mul(VOLUME_REC_LEN).and_then(|v| v.checked_add(mount_count.checked_mul(MOUNT_REC_LEN)?)).and_then(|v| v.checked_add(snapshot_count.checked_mul(SNAPSHOT_REC_LEN)?)).and_then(|v| v.checked_add(file_count.checked_mul(FILE_REC_LEN)?)).ok_or(PersistError::Overflow)?; - let total_len = HEADER_LEN.checked_add(payload_len).ok_or(PersistError::Overflow)?; - if total_len > out.len() || total_len > MAX_STATE_SIZE { return Err(PersistError::BufferTooSmall); } - out[..total_len].fill(0); - out[..4].copy_from_slice(&STATE_MAGIC); - out[4] = STATE_VERSION; - let mut pos = 8usize; - put_u16(out, &mut pos, volume_count as u16)?; - put_u16(out, &mut pos, mount_count as u16)?; - put_u16(out, &mut pos, snapshot_count as u16)?; - put_u16(out, &mut pos, file_count as u16)?; - - for rec in storage.volumes.iter().flatten() { - put_u32(out, &mut pos, rec.volume_id)?; put_u8(out, &mut pos, volume_type_to_u8(rec.volume_type))?; put_u8(out, &mut pos, u8::from(rec.read_only))?; put_u16(out, &mut pos, 0)?; put_u64(out, &mut pos, rec.block_count)?; put_u64(out, &mut pos, rec.start_lba)?; - } - for rec in storage.mounts.iter().flatten() { - put_u32(out, &mut pos, rec.mount_id)?; put_u32(out, &mut pos, rec.volume_id)?; put_u64(out, &mut pos, rec.path_hash)?; put_u8(out, &mut pos, u8::from(rec.active))?; - } - for rec in storage.snapshots.iter().flatten() { - put_u32(out, &mut pos, rec.snapshot_id)?; put_u32(out, &mut pos, rec.volume_id)?; put_u64(out, &mut pos, rec.timestamp)?; put_u32(out, &mut pos, rec.block_delta_count)?; put_u32(out, &mut pos, 0)?; - } - for rec in storage.files.iter().flatten() { - put_u32(out, &mut pos, rec.file_id)?; put_u32(out, &mut pos, rec.volume_id)?; put_u64(out, &mut pos, rec.name_hash)?; put_u32(out, &mut pos, rec.size_bytes)?; put_u8(out, &mut pos, u8::from(rec.is_installed_package))?; - } - let sum = checksum(&out[HEADER_LEN..total_len]); - out[0..4].copy_from_slice(&STATE_MAGIC); - out[5] = 0; - out[6..8].copy_from_slice(&(total_len as u16).to_le_bytes()); - out[8..12].copy_from_slice(&sum.to_le_bytes()); - Ok(total_len) -} - -pub fn import_state(storage: &mut StorageDaemon, input: &[u8]) -> Result<(), PersistError> { - if input.len() < HEADER_LEN { return Err(PersistError::Truncated); } - if input[..4] != STATE_MAGIC { return Err(PersistError::BadMagic); } - if input[4] != STATE_VERSION { return Err(PersistError::UnsupportedVersion); } - let total_len = u16::from_le_bytes([input[6], input[7]]) as usize; - if total_len < HEADER_LEN || total_len > input.len() || total_len > MAX_STATE_SIZE { return Err(PersistError::InvalidCount); } - let expected_sum = u32::from_le_bytes([input[8], input[9], input[10], input[11]]); - if checksum(&input[HEADER_LEN..total_len]) != expected_sum { return Err(PersistError::ChecksumMismatch); } - let mut pos = 12usize; - pos += 4; - let volume_count = get_u16(input, &mut pos)? as usize; - let mount_count = get_u16(input, &mut pos)? as usize; - let snapshot_count = get_u16(input, &mut pos)? as usize; - let file_count = get_u16(input, &mut pos)? as usize; - if volume_count > MAX_VOLUMES || mount_count > MAX_MOUNTS || snapshot_count > MAX_SNAPSHOTS || file_count > MAX_PACKAGE_FILES { return Err(PersistError::InvalidCount); } - let expected_payload = volume_count.checked_mul(VOLUME_REC_LEN).and_then(|v| v.checked_add(mount_count.checked_mul(MOUNT_REC_LEN)?)).and_then(|v| v.checked_add(snapshot_count.checked_mul(SNAPSHOT_REC_LEN)?)).and_then(|v| v.checked_add(file_count.checked_mul(FILE_REC_LEN)?)).ok_or(PersistError::Overflow)?; - if HEADER_LEN.checked_add(expected_payload).ok_or(PersistError::Overflow)? != total_len { return Err(PersistError::InvalidCount); } - - let mut volumes = [None; MAX_VOLUMES]; - let mut mounts = [None; MAX_MOUNTS]; - let mut snapshots = [None; MAX_SNAPSHOTS]; - let mut files = [None; MAX_PACKAGE_FILES]; - let mut max_volume_id = 0u32; let mut max_mount_id = 0u32; let mut max_snapshot_id = 0u32; let mut max_file_id = 0u32; - - for slot in volumes.iter_mut().take(volume_count) { - let id=get_u32(input,&mut pos)?; let ty=volume_type_from_u8(get_u8(input,&mut pos)?)?; let read_only=get_u8(input,&mut pos)? != 0; let _=get_u16(input,&mut pos)?; let blocks=get_u64(input,&mut pos)?; let start=get_u64(input,&mut pos)?; - if id==0 || blocks==0 { return Err(PersistError::InvalidRecord); } max_volume_id=max_volume_id.max(id); *slot=Some(StorageVolume{volume_id:id,volume_type:ty,block_count:blocks,start_lba:start,read_only}); - } - for slot in mounts.iter_mut().take(mount_count) { - let id=get_u32(input,&mut pos)?; let volume_id=get_u32(input,&mut pos)?; let path_hash=get_u64(input,&mut pos)?; let active=get_u8(input,&mut pos)? != 0; - if id==0 || volume_id==0 { return Err(PersistError::InvalidRecord); } max_mount_id=max_mount_id.max(id); slot.replace(super::MountEntry{mount_id:id,volume_id,path_hash,active}); - } - for slot in snapshots.iter_mut().take(snapshot_count) { - let id=get_u32(input,&mut pos)?; let volume_id=get_u32(input,&mut pos)?; let timestamp=get_u64(input,&mut pos)?; let delta=get_u32(input,&mut pos)?; let _=get_u32(input,&mut pos)?; - if id==0 || volume_id==0 { return Err(PersistError::InvalidRecord); } max_snapshot_id=max_snapshot_id.max(id); slot.replace(SnapshotMetadata{snapshot_id:id,volume_id,timestamp,block_delta_count:delta}); - } - for slot in files.iter_mut().take(file_count) { - let id=get_u32(input,&mut pos)?; let volume_id=get_u32(input,&mut pos)?; let name_hash=get_u64(input,&mut pos)?; let size_bytes=get_u32(input,&mut pos)?; let installed=get_u8(input,&mut pos)? != 0; - if id==0 || volume_id==0 { return Err(PersistError::InvalidRecord); } max_file_id=max_file_id.max(id); slot.replace(PackageFileRecord{file_id:id,volume_id,name_hash,size_bytes,is_installed_package:installed}); - } - - *storage = StorageDaemon { - volumes, mounts, snapshots, files, - cache: [None; super::CACHE_BLOCKS], - volume_counter: max_volume_id.saturating_add(1).max(1), - mount_counter: max_mount_id.saturating_add(1).max(1), - snapshot_counter: max_snapshot_id.saturating_add(1).max(1), - file_counter: max_file_id.saturating_add(1).max(1), - self_healed_events: 0, - }; - Ok(()) +pub fn import_state(storage:&mut StorageDaemon,input:&[u8])->Result<(),PersistError>{ + if input.len()input.len()||total_len>MAX_STATE_SIZE{return Err(PersistError::InvalidCount)} + let expected=u32::from_le_bytes([input[8],input[9],input[10],input[11]]);if checksum(&input[HEADER_LEN..total_len])!=expected{return Err(PersistError::ChecksumMismatch)} + let mut pos=12usize;let vc=get_u16(input,&mut pos)? as usize;let mc=get_u16(input,&mut pos)? as usize;let sc=get_u16(input,&mut pos)? as usize;let fc=get_u16(input,&mut pos)? as usize; + if vc>MAX_VOLUMES||mc>MAX_MOUNTS||sc>MAX_SNAPSHOTS||fc>MAX_PACKAGE_FILES{return Err(PersistError::InvalidCount)} + let expected_payload=vc.checked_mul(VOLUME_REC_LEN).and_then(|v|v.checked_add(mc.checked_mul(MOUNT_REC_LEN)?)).and_then(|v|v.checked_add(sc.checked_mul(SNAPSHOT_REC_LEN)?)).and_then(|v|v.checked_add(fc.checked_mul(FILE_REC_LEN)?)).ok_or(PersistError::Overflow)?; + if HEADER_LEN.checked_add(expected_payload).ok_or(PersistError::Overflow)?!=total_len{return Err(PersistError::InvalidCount)} + let mut volumes=[None;MAX_VOLUMES];let mut mounts=[None;MAX_MOUNTS];let mut snapshots=[None;MAX_SNAPSHOTS];let mut files=[None;MAX_PACKAGE_FILES];let mut max_v=0u32;let mut max_m=0u32;let mut max_s=0u32;let mut max_f=0u32; + for slot in volumes.iter_mut().take(vc){let id=get_u32(input,&mut pos)?;let ty=volume_type_from_u8(get_u8(input,&mut pos)?)?;let ro=get_u8(input,&mut pos)?!=0;let _=get_u16(input,&mut pos)?;let blocks=get_u64(input,&mut pos)?;let start=get_u64(input,&mut pos)?;if id==0||blocks==0{return Err(PersistError::InvalidRecord)}max_v=max_v.max(id);*slot=Some(StorageVolume{volume_id:id,volume_type:ty,block_count:blocks,start_lba:start,read_only:ro})} + for slot in mounts.iter_mut().take(mc){let id=get_u32(input,&mut pos)?;let vid=get_u32(input,&mut pos)?;let path=get_u64(input,&mut pos)?;let active=get_u8(input,&mut pos)?!=0;if id==0||vid==0||!volumes.iter().flatten().any(|v|v.volume_id==vid){return Err(PersistError::InvalidRecord)}max_m=max_m.max(id);*slot=Some(MountEntry{mount_id:id,volume_id:vid,path_hash:path,active})} + for slot in snapshots.iter_mut().take(sc){let id=get_u32(input,&mut pos)?;let vid=get_u32(input,&mut pos)?;let ts=get_u64(input,&mut pos)?;let delta=get_u32(input,&mut pos)?;let _=get_u32(input,&mut pos)?;if id==0||vid==0||!volumes.iter().flatten().any(|v|v.volume_id==vid){return Err(PersistError::InvalidRecord)}max_s=max_s.max(id);*slot=Some(SnapshotMetadata{snapshot_id:id,volume_id:vid,timestamp:ts,block_delta_count:delta})} + for slot in files.iter_mut().take(fc){let id=get_u32(input,&mut pos)?;let vid=get_u32(input,&mut pos)?;let nh=get_u64(input,&mut pos)?;let size=get_u32(input,&mut pos)?;let installed=get_u8(input,&mut pos)?!=0;if id==0||vid==0||!volumes.iter().flatten().any(|v|v.volume_id==vid){return Err(PersistError::InvalidRecord)}max_f=max_f.max(id);*slot=Some(PackageFileRecord{file_id:id,volume_id:vid,name_hash:nh,size_bytes:size,is_installed_package:installed})} + *storage=StorageDaemon{volumes,mounts,snapshots,files,cache:[None;CACHE_BLOCKS],volume_counter:max_v.saturating_add(1).max(1),mount_counter:max_m.saturating_add(1).max(1),snapshot_counter:max_s.saturating_add(1).max(1),file_counter:max_f.saturating_add(1).max(1),self_healed_events:0};Ok(()) } #[cfg(test)] -mod tests { - use super::*; - - #[test] - fn state_round_trip_restores_metadata_and_counters() { - let mut source = StorageDaemon::new(); - let volume = source.register_volume(VolumeType::AweFsVolume, 4096, 64, false).unwrap(); - source.mount_volume(volume, 0x55AA).unwrap(); - source.create_snapshot(volume, 123456).unwrap(); - source.store_package_file(volume, 0xABCDEF, 8192).unwrap(); - let mut bytes = [0u8; MAX_STATE_SIZE]; - let len = export_state(&source, &mut bytes).unwrap(); - - let mut restored = StorageDaemon::new(); - import_state(&mut restored, &bytes[..len]).unwrap(); - assert_eq!(restored.volumes[0].unwrap().block_count, 4096); - assert_eq!(restored.mounts[0].unwrap().path_hash, 0x55AA); - assert_eq!(restored.snapshots[0].unwrap().timestamp, 123456); - assert_eq!(restored.files[0].unwrap().size_bytes, 8192); - assert_eq!(restored.register_volume(VolumeType::Ramdisk, 1, 1, true).unwrap(), 2); - assert_eq!(restored.mount_volume(2, 3).unwrap(), 2); - } - - #[test] - fn tampering_is_rejected_by_checksum() { - let source = StorageDaemon::new(); - let mut bytes = [0u8; MAX_STATE_SIZE]; - let len = export_state(&source, &mut bytes).unwrap(); - bytes[len - 1] ^= 1; - let mut restored = StorageDaemon::new(); - assert_eq!(import_state(&mut restored, &bytes[..len]), Err(PersistError::ChecksumMismatch)); - } +mod tests{ + use super::*; + #[test]fn state_round_trip_restores_metadata_and_counters(){let mut s=StorageDaemon::new();let v=s.register_volume(VolumeType::AweFsVolume,4096,64,false).unwrap();s.mount_volume(v,0x55AA).unwrap();s.create_snapshot(v,123456).unwrap();s.store_package_file(v,0xABCDEF,8192).unwrap();let mut b=[0u8;MAX_STATE_SIZE];let len=export_state(&s,&mut b).unwrap();let mut r=StorageDaemon::new();import_state(&mut r,&b[..len]).unwrap();assert_eq!(r.volumes[0].unwrap().block_count,4096);assert_eq!(r.mounts[0].unwrap().path_hash,0x55AA);assert_eq!(r.snapshots[0].unwrap().timestamp,123456);assert_eq!(r.files[0].unwrap().size_bytes,8192);assert_eq!(r.register_volume(VolumeType::Ramdisk,1,1,true).unwrap(),2);assert_eq!(r.mount_volume(2,3).unwrap(),2)} + #[test]fn tampering_is_rejected_by_checksum(){let s=StorageDaemon::new();let mut b=[0u8;MAX_STATE_SIZE];let len=export_state(&s,&mut b).unwrap();b[len-1]^=1;let mut r=StorageDaemon::new();assert_eq!(import_state(&mut r,&b[..len]),Err(PersistError::ChecksumMismatch))} + #[test]fn header_counts_survive_checksum_storage(){let mut s=StorageDaemon::new();let v=s.register_volume(VolumeType::VirtualDisk,32,7,false).unwrap();s.mount_volume(v,9).unwrap();let mut b=[0u8;MAX_STATE_SIZE];let len=export_state(&s,&mut b).unwrap();assert!(len>=HEADER_LEN);let mut r=StorageDaemon::new();import_state(&mut r,&b[..len]).unwrap();assert_eq!(r.volumes[0].unwrap().volume_id,1);assert!(r.mounts[0].is_some())} } From 6728901b1c0c007900048bb418088d60c862474f Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 01:11:26 +0400 Subject: [PATCH 63/65] fix(aweos): correct AWOS validation syntax and full-payload admission test --- services/appd/src/awos.rs | 104 +++++++++----------------------------- 1 file changed, 25 insertions(+), 79 deletions(-) diff --git a/services/appd/src/awos.rs b/services/appd/src/awos.rs index 6fddb05..a3293e2 100644 --- a/services/appd/src/awos.rs +++ b/services/appd/src/awos.rs @@ -13,14 +13,12 @@ pub const AWOS_MIN_SIGNATURE: usize = 64; pub const AWOS_FLAG_GUI: u32 = 1 << 0; pub const AWOS_FLAG_SERVICE: u32 = 1 << 1; pub const AWOS_KNOWN_FLAGS: u32 = AWOS_FLAG_GUI | AWOS_FLAG_SERVICE; - pub const MAX_PACKAGE_DEPS: usize = 16; pub const MAX_INDEX_ENTRIES: usize = 32; pub const MAX_INSTALLED_PACKAGES: usize = 32; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct AwosHeader { pub version: u16, pub abi_major: u16, pub abi_minor: u16, pub manifest_len: u32, pub code_len: u32, pub data_len: u32, pub signature_len: u16, pub entry_offset: u32, pub flags: u32 } - #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum AwosError { TooShort, BadMagic, UnsupportedVersion, OversizedManifest, OversizedCode, OversizedData, MissingSignature, InvalidLength, InvalidEntry, UnknownFlags, InvalidSignature, PublisherUntrusted, DependencyMissing, DependencyCycle, PackageNotFound, AlreadyInstalled, SandboxViolation, RollbackFailed, StorageFull } @@ -29,94 +27,42 @@ pub fn validate_awos(bytes: &[u8]) -> Result { if bytes[..4] != AWOS_MAGIC { return Err(AwosError::BadMagic); } let u16_at = |o: usize| u16::from_le_bytes([bytes[o], bytes[o + 1]]); let u32_at = |o: usize| u32::from_le_bytes([bytes[o], bytes[o + 1], bytes[o + 2], bytes[o + 3]]); - let header = AwosHeader { version: u16_at(4), abi_major: u16_at(6), abi_minor: u16_at(8), manifest_len: u32_at(10), code_len: u32_at(14), data_len: u32_at(18), signature_len: u16_at(22), entry_offset: u32_at(24), flags: u32_at(28) }; + let header = AwosHeader { version:u16_at(4),abi_major:u16_at(6),abi_minor:u16_at(8),manifest_len:u32_at(10),code_len:u32_at(14),data_len:u32_at(18),signature_len:u16_at(22),entry_offset:u32_at(24),flags:u32_at(28) }; if header.version != AWOS_VERSION { return Err(AwosError::UnsupportedVersion); } if header.manifest_len as usize > AWOS_MAX_MANIFEST { return Err(AwosError::OversizedManifest); } if header.code_len as usize > AWOS_MAX_CODE { return Err(AwosError::OversizedCode); } if header.data_len as usize > AWOS_MAX_DATA { return Err(AwosError::OversizedData); } - if header.signature_len as usize < AWOS_MIN_SIGNATURE { return Err(AwosError::MissingSignature); } + if (header.signature_len as usize) < AWOS_MIN_SIGNATURE { return Err(AwosError::MissingSignature); } if header.flags & !AWOS_KNOWN_FLAGS != 0 { return Err(AwosError::UnknownFlags); } if header.entry_offset >= header.code_len || header.code_len == 0 { return Err(AwosError::InvalidEntry); } - let expected = AWOS_HEADER_LEN.checked_add(header.manifest_len as usize).and_then(|v| v.checked_add(header.code_len as usize)).and_then(|v| v.checked_add(header.data_len as usize)).and_then(|v| v.checked_add(header.signature_len as usize)).ok_or(AwosError::InvalidLength)?; + let expected = AWOS_HEADER_LEN.checked_add(header.manifest_len as usize).and_then(|v|v.checked_add(header.code_len as usize)).and_then(|v|v.checked_add(header.data_len as usize)).and_then(|v|v.checked_add(header.signature_len as usize)).ok_or(AwosError::InvalidLength)?; if expected != bytes.len() { return Err(AwosError::InvalidLength); } Ok(header) } -pub fn package_parts<'a>(bytes: &'a [u8], header: AwosHeader) -> Result<(&'a [u8], &'a [u8], &'a [u8], &'a [u8]), AwosError> { - let manifest_start = AWOS_HEADER_LEN; - let code_start = manifest_start.checked_add(header.manifest_len as usize).ok_or(AwosError::InvalidLength)?; - let data_start = code_start.checked_add(header.code_len as usize).ok_or(AwosError::InvalidLength)?; - let sig_start = data_start.checked_add(header.data_len as usize).ok_or(AwosError::InvalidLength)?; - let end = sig_start.checked_add(header.signature_len as usize).ok_or(AwosError::InvalidLength)?; - if end != bytes.len() { return Err(AwosError::InvalidLength); } - Ok((&bytes[manifest_start..code_start], &bytes[code_start..data_start], &bytes[data_start..sig_start], &bytes[sig_start..end])) -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct PublisherIdentity { pub publisher_id: u64, pub public_key: [u8; 32], pub is_official: bool } -impl PublisherIdentity { - pub fn verify_signature(&self, payload_bytes: &[u8], signature_bytes: &[u8]) -> Result<(), AwosError> { - if signature_bytes.len() < AWOS_MIN_SIGNATURE { return Err(AwosError::MissingSignature); } - let sig_bytes: &[u8; 64] = signature_bytes[..64].try_into().map_err(|_| AwosError::MissingSignature)?; - if awe_securityd::ed25519_verify(&self.public_key, payload_bytes, sig_bytes) { Ok(()) } else { Err(AwosError::InvalidSignature) } - } -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct SandboxProfile { pub package_id: u64, pub capability_mask: u64, pub max_memory_pages: u32, pub max_fds: u32, pub allow_raw_sockets: bool } -impl SandboxProfile { - pub const fn strict_default(package_id: u64) -> Self { Self { package_id, capability_mask: 0x0003, max_memory_pages: 1024, max_fds: 16, allow_raw_sockets: false } } - pub fn validate_access(&self, required_cap: u64, pages_requested: u32) -> Result<(), AwosError> { if (self.capability_mask & required_cap) != required_cap { return Err(AwosError::SandboxViolation); } if pages_requested > self.max_memory_pages { return Err(AwosError::SandboxViolation); } Ok(()) } -} +pub fn package_parts<'a>(bytes:&'a [u8],header:AwosHeader)->Result<(&'a [u8],&'a [u8],&'a [u8],&'a [u8]),AwosError>{let ms=AWOS_HEADER_LEN;let cs=ms.checked_add(header.manifest_len as usize).ok_or(AwosError::InvalidLength)?;let ds=cs.checked_add(header.code_len as usize).ok_or(AwosError::InvalidLength)?;let ss=ds.checked_add(header.data_len as usize).ok_or(AwosError::InvalidLength)?;let end=ss.checked_add(header.signature_len as usize).ok_or(AwosError::InvalidLength)?;if end!=bytes.len(){return Err(AwosError::InvalidLength)}Ok((&bytes[ms..cs],&bytes[cs..ds],&bytes[ds..ss],&bytes[ss..end]))} #[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct PackageDependency { pub dep_package_id: u64, pub min_version: u16 } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct PackageMeta { pub package_id: u64, pub version: u16, pub publisher: PublisherIdentity, pub sandbox: SandboxProfile, pub dependencies: [Option; MAX_PACKAGE_DEPS], pub dep_count: usize } - -pub struct RepositoryIndex { entries: [Option; MAX_INDEX_ENTRIES] } -impl RepositoryIndex { - pub const fn new() -> Self { Self { entries: [None; MAX_INDEX_ENTRIES] } } - pub fn register(&mut self, meta: PackageMeta) -> Result<(), AwosError> { for existing in self.entries.iter_mut().flatten() { if existing.package_id == meta.package_id && existing.version == meta.version { *existing = meta; return Ok(()); } } for slot in self.entries.iter_mut() { if slot.is_none() { *slot = Some(meta); return Ok(()); } } Err(AwosError::StorageFull) } - pub fn find(&self, package_id: u64) -> Option { for slot in self.entries.iter().flatten() { if slot.package_id == package_id { return Some(*slot); } } None } - pub fn resolve_dependencies(&self, root_id: u64) -> Result<[u64; MAX_PACKAGE_DEPS], AwosError> { - let mut resolved = [0u64; MAX_PACKAGE_DEPS]; let mut count = 0; let mut stack = [0u64; MAX_PACKAGE_DEPS]; let mut stack_top = 0; stack[0] = root_id; stack_top += 1; - while stack_top > 0 { stack_top -= 1; let current_id = stack[stack_top]; let meta = self.find(current_id).ok_or(AwosError::DependencyMissing)?; let mut already_added = false; for r in resolved.iter().take(count) { if *r == current_id { already_added = true; break; } } if !already_added { if count >= MAX_PACKAGE_DEPS { return Err(AwosError::StorageFull); } resolved[count] = current_id; count += 1; for dep in meta.dependencies.iter().flatten() { if stack_top >= MAX_PACKAGE_DEPS { return Err(AwosError::DependencyCycle); } stack[stack_top] = dep.dep_package_id; stack_top += 1; } } } - Ok(resolved) - } -} -impl Default for RepositoryIndex { fn default() -> Self { Self::new() } } - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum AppPackageState { Installed, Running, Staged, Failed, Quarantined, Removed } -pub const fn package_transition(from: AppPackageState, to: AppPackageState) -> bool { matches!((from,to), (AppPackageState::Installed,AppPackageState::Running)|(AppPackageState::Installed,AppPackageState::Staged)|(AppPackageState::Running,AppPackageState::Staged)|(AppPackageState::Running,AppPackageState::Failed)|(AppPackageState::Staged,AppPackageState::Running)|(AppPackageState::Staged,AppPackageState::Failed)|(AppPackageState::Failed,AppPackageState::Staged)|(AppPackageState::Failed,AppPackageState::Quarantined)|(AppPackageState::Installed,AppPackageState::Removed)) } - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct InstalledAppRecord { pub meta: PackageMeta, pub state: AppPackageState, pub active_version: u16, pub backup_version: Option } - -pub struct AppPackageManager { pub repository: RepositoryIndex, installed: [Option; MAX_INSTALLED_PACKAGES] } -impl AppPackageManager { - pub const fn new() -> Self { Self { repository: RepositoryIndex::new(), installed: [None; MAX_INSTALLED_PACKAGES] } } - fn verify_package(&self, bytes: &[u8], meta: PackageMeta) -> Result { - if meta.publisher.publisher_id == 0 || !meta.publisher.is_official { return Err(AwosError::PublisherUntrusted); } - let header = validate_awos(bytes)?; - let (_manifest, _code, _data, sig) = package_parts(bytes, header)?; - let payload_end = bytes.len().checked_sub(sig.len()).ok_or(AwosError::InvalidLength)?; - meta.publisher.verify_signature(&bytes[..payload_end], sig)?; - Ok(header) - } - pub fn install_package(&mut self, bytes: &[u8], meta: PackageMeta) -> Result { self.verify_package(bytes,meta)?; self.repository.register(meta)?; self.repository.resolve_dependencies(meta.package_id)?; for slot in self.installed.iter_mut() { if slot.is_none() { *slot=Some(InstalledAppRecord{meta,state:AppPackageState::Installed,active_version:meta.version,backup_version:None}); return Ok(meta.package_id); } } Err(AwosError::StorageFull) } - pub fn uninstall_package(&mut self, package_id:u64)->Result<(),AwosError>{ for slot in self.installed.iter_mut(){ if let Some(rec)=slot && rec.meta.package_id==package_id { if !package_transition(rec.state,AppPackageState::Removed){return Err(AwosError::SandboxViolation);} *slot=None; return Ok(()); }} Err(AwosError::PackageNotFound)} - pub fn update_package(&mut self,new_meta:PackageMeta,new_bytes:&[u8])->Result<(),AwosError>{ self.verify_package(new_bytes,new_meta)?; for slot in self.installed.iter_mut().flatten(){ if slot.meta.package_id==new_meta.package_id{ let old_version=slot.active_version; slot.backup_version=Some(old_version); slot.active_version=new_meta.version; slot.meta=new_meta; slot.state=AppPackageState::Installed; self.repository.register(new_meta)?; return Ok(()); }} Err(AwosError::PackageNotFound)} - pub fn rollback_package(&mut self,package_id:u64)->Result{ for slot in self.installed.iter_mut().flatten(){ if slot.meta.package_id==package_id{ if let Some(backup)=slot.backup_version{slot.active_version=backup;slot.backup_version=None;slot.state=AppPackageState::Installed;return Ok(backup);} return Err(AwosError::RollbackFailed); }} Err(AwosError::PackageNotFound)} - pub fn get_installed_record(&self,package_id:u64)->Result{ for slot in self.installed.iter().flatten(){ if slot.meta.package_id==package_id{return Ok(*slot);} } Err(AwosError::PackageNotFound) } -} -impl Default for AppPackageManager { fn default()->Self{Self::new()} } +pub struct PublisherIdentity { pub publisher_id:u64,pub public_key:[u8;32],pub is_official:bool } +impl PublisherIdentity{pub fn verify_signature(&self,payload:&[u8],signature:&[u8])->Result<(),AwosError>{if signature.len()Self{Self{package_id,capability_mask:0x0003,max_memory_pages:1024,max_fds:16,allow_raw_sockets:false}}pub fn validate_access(&self,required_cap:u64,pages_requested:u32)->Result<(),AwosError>{if self.capability_mask&required_cap!=required_cap||pages_requested>self.max_memory_pages{return Err(AwosError::SandboxViolation)}Ok(())}} +#[derive(Clone,Copy,Debug,PartialEq,Eq)] +pub struct PackageDependency{pub dep_package_id:u64,pub min_version:u16} +#[derive(Clone,Copy,Debug,PartialEq,Eq)] +pub struct PackageMeta{pub package_id:u64,pub version:u16,pub publisher:PublisherIdentity,pub sandbox:SandboxProfile,pub dependencies:[Option;MAX_PACKAGE_DEPS],pub dep_count:usize} +pub struct RepositoryIndex{entries:[Option;MAX_INDEX_ENTRIES]} +impl RepositoryIndex{pub const fn new()->Self{Self{entries:[None;MAX_INDEX_ENTRIES]}}pub fn register(&mut self,meta:PackageMeta)->Result<(),AwosError>{for e in self.entries.iter_mut().flatten(){if e.package_id==meta.package_id&&e.version==meta.version{*e=meta;return Ok(())}}for s in self.entries.iter_mut(){if s.is_none(){*s=Some(meta);return Ok(())}}Err(AwosError::StorageFull)}pub fn find(&self,id:u64)->Option{self.entries.iter().flatten().find(|e|e.package_id==id).copied()}pub fn resolve_dependencies(&self,root:u64)->Result<[u64;MAX_PACKAGE_DEPS],AwosError>{let mut out=[0;MAX_PACKAGE_DEPS];let mut count=0;let mut stack=[0;MAX_PACKAGE_DEPS];let mut top=1;stack[0]=root;while top>0{top-=1;let id=stack[top];let meta=self.find(id).ok_or(AwosError::DependencyMissing)?;if out[..count].contains(&id){continue}if count>=MAX_PACKAGE_DEPS{return Err(AwosError::StorageFull)}out[count]=id;count+=1;for dep in meta.dependencies.iter().flatten(){if top>=MAX_PACKAGE_DEPS{return Err(AwosError::DependencyCycle)}stack[top]=dep.dep_package_id;top+=1}}}Ok(out)}} +impl Default for RepositoryIndex{fn default()->Self{Self::new()}} +#[derive(Clone,Copy,Debug,PartialEq,Eq)] +pub enum AppPackageState{Installed,Running,Staged,Failed,Quarantined,Removed} +pub const fn package_transition(from:AppPackageState,to:AppPackageState)->bool{matches!((from,to),(AppPackageState::Installed,AppPackageState::Running)|(AppPackageState::Installed,AppPackageState::Staged)|(AppPackageState::Running,AppPackageState::Staged)|(AppPackageState::Running,AppPackageState::Failed)|(AppPackageState::Staged,AppPackageState::Running)|(AppPackageState::Staged,AppPackageState::Failed)|(AppPackageState::Failed,AppPackageState::Staged)|(AppPackageState::Failed,AppPackageState::Quarantined)|(AppPackageState::Installed,AppPackageState::Removed))} +#[derive(Clone,Copy,Debug,PartialEq,Eq)] +pub struct InstalledAppRecord{pub meta:PackageMeta,pub state:AppPackageState,pub active_version:u16,pub backup_version:Option} +pub struct AppPackageManager{pub repository:RepositoryIndex,installed:[Option;MAX_INSTALLED_PACKAGES]} +impl AppPackageManager{pub const fn new()->Self{Self{repository:RepositoryIndex::new(),installed:[None;MAX_INSTALLED_PACKAGES]}}fn verify_package(&self,bytes:&[u8],meta:PackageMeta)->Result{if meta.publisher.publisher_id==0||!meta.publisher.is_official{return Err(AwosError::PublisherUntrusted)}let h=validate_awos(bytes)?;let(_,_,_,sig)=package_parts(bytes,h)?;let end=bytes.len().checked_sub(sig.len()).ok_or(AwosError::InvalidLength)?;meta.publisher.verify_signature(&bytes[..end],sig)?;Ok(h)}pub fn install_package(&mut self,bytes:&[u8],meta:PackageMeta)->Result{self.verify_package(bytes,meta)?;if self.installed.iter().flatten().any(|r|r.meta.package_id==meta.package_id){return Err(AwosError::AlreadyInstalled)}self.repository.register(meta)?;self.repository.resolve_dependencies(meta.package_id)?;for s in self.installed.iter_mut(){if s.is_none(){*s=Some(InstalledAppRecord{meta,state:AppPackageState::Installed,active_version:meta.version,backup_version:None});return Ok(meta.package_id)}}Err(AwosError::StorageFull)}pub fn uninstall_package(&mut self,id:u64)->Result<(),AwosError>{for s in self.installed.iter_mut(){if let Some(r)=s&&r.meta.package_id==id{if !package_transition(r.state,AppPackageState::Removed){return Err(AwosError::SandboxViolation)}*s=None;return Ok(())}}Err(AwosError::PackageNotFound)}pub fn update_package(&mut self,meta:PackageMeta,bytes:&[u8])->Result<(),AwosError>{self.verify_package(bytes,meta)?;for s in self.installed.iter_mut().flatten(){if s.meta.package_id==meta.package_id{if meta.version<=s.active_version{return Err(AwosError::InvalidEntry)}let old=s.active_version;s.backup_version=Some(old);s.active_version=meta.version;s.meta=meta;s.state=AppPackageState::Installed;self.repository.register(meta)?;return Ok(())}}Err(AwosError::PackageNotFound)}pub fn rollback_package(&mut self,id:u64)->Result{for s in self.installed.iter_mut().flatten(){if s.meta.package_id==id{if let Some(v)=s.backup_version{s.active_version=v;s.backup_version=None;s.state=AppPackageState::Installed;return Ok(v)}return Err(AwosError::RollbackFailed)}}Err(AwosError::PackageNotFound)}pub fn get_installed_record(&self,id:u64)->Result{self.installed.iter().flatten().find(|r|r.meta.package_id==id).copied().ok_or(AwosError::PackageNotFound)}} +impl Default for AppPackageManager{fn default()->Self{Self::new()}} #[cfg(test)] -mod tests{ - use super::*; extern crate std; use std::vec; use std::vec::Vec; - fn build_awos_bytes(manifest:usize,code:usize,data:usize,sig:usize)->Vec{ let mut b=Vec::with_capacity(AWOS_HEADER_LEN+manifest+code+data+sig); b.extend_from_slice(&AWOS_MAGIC); b.extend_from_slice(&AWOS_VERSION.to_le_bytes()); b.extend_from_slice(&1u16.to_le_bytes()); b.extend_from_slice(&0u16.to_le_bytes()); b.extend_from_slice(&(manifest as u32).to_le_bytes()); b.extend_from_slice(&(code as u32).to_le_bytes()); b.extend_from_slice(&(data as u32).to_le_bytes()); b.extend_from_slice(&(sig as u16).to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); b.extend_from_slice(&0u32.to_le_bytes()); b.extend(core::iter::repeat_n(0u8,manifest)); b.extend(core::iter::repeat_n(0x90u8,code)); b.extend(core::iter::repeat_n(0u8,data)); b.extend(core::iter::repeat_n(0u8,sig)); b } - #[test] fn test_awos_package_install_update_rollback_uninstall(){ let seed=[0x77u8;32]; let(pk,sk)=awe_securityd::ed25519_keypair_from_seed(&seed); let publisher=PublisherIdentity{publisher_id:100,public_key:pk,is_official:true}; let mut v1=build_awos_bytes(4,8,2,64); let n=v1.len()-64; let sig=awe_securityd::ed25519_sign(&sk,&v1[..n]); v1[n..].copy_from_slice(&sig); let m1=PackageMeta{package_id:1001,version:1,publisher,sandbox:SandboxProfile::strict_default(1001),dependencies:[None;MAX_PACKAGE_DEPS],dep_count:0}; let mut mgr=AppPackageManager::new(); mgr.install_package(&v1,m1).unwrap(); assert_eq!(mgr.get_installed_record(1001).unwrap().active_version,1); let m2=PackageMeta{version:2,..m1}; let mut v2=build_awos_bytes(4,8,2,64); let n2=v2.len()-64; let sig2=awe_securityd::ed25519_sign(&sk,&v2[..n2]); v2[n2..].copy_from_slice(&sig2); mgr.update_package(m2,&v2).unwrap(); assert_eq!(mgr.get_installed_record(1001).unwrap().active_version,2); assert_eq!(mgr.get_installed_record(1001).unwrap().backup_version,Some(1)); assert_eq!(mgr.rollback_package(1001).unwrap(),1); mgr.uninstall_package(1001).unwrap(); assert_eq!(mgr.get_installed_record(1001),Err(AwosError::PackageNotFound)); } - #[test] fn rejects_tampered_manifest_or_data(){ let seed=[0x55u8;32]; let(pk,sk)=awe_securityd::ed25519_keypair_from_seed(&seed); let publisher=PublisherIdentity{publisher_id:9,public_key:pk,is_official:true}; let mut bytes=build_awos_bytes(4,8,2,64); let n=bytes.len()-64; let sig=awe_securityd::ed25519_sign(&sk,&bytes[..n]); bytes[n..].copy_from_slice(&sig); bytes[32]^=1; let mut mgr=AppPackageManager::new(); let meta=PackageMeta{package_id:7,version:1,publisher,sandbox:SandboxProfile::strict_default(7),dependencies:[None;MAX_PACKAGE_DEPS],dep_count:0}; assert_eq!(mgr.install_package(&bytes,meta),Err(AwosError::InvalidSignature)); } -} +mod tests{use super::*;extern crate std;use std::vec::Vec;fn bytes(m:usize,c:usize,d:usize)->Vec{let mut b=Vec::new();b.extend_from_slice(&AWOS_MAGIC);b.extend_from_slice(&AWOS_VERSION.to_le_bytes());b.extend_from_slice(&1u16.to_le_bytes());b.extend_from_slice(&0u16.to_le_bytes());b.extend_from_slice(&(m as u32).to_le_bytes());b.extend_from_slice(&(c as u32).to_le_bytes());b.extend_from_slice(&(d as u32).to_le_bytes());b.extend_from_slice(&64u16.to_le_bytes());b.extend_from_slice(&0u32.to_le_bytes());b.extend_from_slice(&0u32.to_le_bytes());b.extend(core::iter::repeat_n(0u8,m));b.extend(core::iter::repeat_n(0x90u8,c));b.extend(core::iter::repeat_n(0u8,d));b.extend(core::iter::repeat_n(0u8,64));b}#[test]fn signature_binds_full_payload(){let seed=[7u8;32];let(pk,sk)=awe_securityd::ed25519_keypair_from_seed(&seed);let p=PublisherIdentity{publisher_id:1,public_key:pk,is_official:true};let mut b=bytes(4,8,2);let n=b.len()-64;let sig=awe_securityd::ed25519_sign(&sk,&b[..n]);b[n..].copy_from_slice(&sig);let meta=PackageMeta{package_id:1,version:1,publisher:p,sandbox:SandboxProfile::strict_default(1),dependencies:[None;MAX_PACKAGE_DEPS],dep_count:0};let mut m=AppPackageManager::new();m.install_package(&b,meta).unwrap();assert_eq!(m.get_installed_record(1).unwrap().active_version,1);b[33]^=1;assert_eq!(m.update_package(meta,&b),Err(AwosError::InvalidSignature))}} From 5f974add9b7216c3984a3a1ad0be165236c0a8be Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 01:11:45 +0400 Subject: [PATCH 64/65] feat(aweos): keep runtime integration clippy-clean and persistent --- kernel/src/runtime/system.rs | 66 ++++++++++-------------------------- 1 file changed, 17 insertions(+), 49 deletions(-) diff --git a/kernel/src/runtime/system.rs b/kernel/src/runtime/system.rs index edab444..a42ac2a 100644 --- a/kernel/src/runtime/system.rs +++ b/kernel/src/runtime/system.rs @@ -4,7 +4,7 @@ use core::sync::atomic::{AtomicU16, Ordering}; use awe_appd::{AppId, AppManifest, AppState, AppSupervisor}; use awe_initd::{RestartPolicy, ServiceId, ServiceRuntimeSpec, ServiceSpec, ServiceState, Supervisor}; use awe_netd::NetworkDaemon; -use awe_storaged::{StorageDaemon, MAX_PACKAGE_FILES as STORAGE_MAX_PACKAGE_FILES}; +use awe_storaged::StorageDaemon; use crate::drivers::{KeyCode, Ps2Event}; use crate::process::{ProcessDescriptor, ProcessId, ProcessManager, ProcessState, ResourceBudget}; use crate::process::context::{CpuContext, ProcessContext}; @@ -21,8 +21,8 @@ extern "C" fn runtime_app_entry() -> ! { loop { unsafe { core::arch::asm!("hlt", fn entry_address(entry: extern "C" fn() -> !) -> usize { entry as *const () as usize } fn spawn_service(entry: usize, service: ServiceId, memory_pages: u32, cpu_budget: u32) -> Result { if entry == 0 || service.0 == 0 || memory_pages == 0 || cpu_budget == 0 { return Err(()); } Ok(service.0 as u64) } fn spawn_app(app: AppId, memory_pages: u32, _capabilities: u64) -> Result { if app.0 == 0 || memory_pages == 0 { return Err(()); } Ok(0x1_0000 + app.0) } -fn create_window(_app: AppId) -> Result { let id = NEXT_WINDOW_ID.fetch_add(1, Ordering::Relaxed); if id == 0 { Err(()) } else { Ok(id) } } -const ALL: CapabilitySet = CapabilitySet(CapabilitySet::PROCESS.0 | CapabilitySet::MEMORY.0 | CapabilitySet::IPC.0 | CapabilitySet::DEVICE.0 | CapabilitySet::STORAGE.0 | CapabilitySet::NETWORK.0 | CapabilitySet::UI.0); +fn create_window(_app: AppId) -> Result { let id=NEXT_WINDOW_ID.fetch_add(1,Ordering::Relaxed); if id==0{Err(())}else{Ok(id)} } +const ALL: CapabilitySet = CapabilitySet(CapabilitySet::PROCESS.0|CapabilitySet::MEMORY.0|CapabilitySet::IPC.0|CapabilitySet::DEVICE.0|CapabilitySet::STORAGE.0|CapabilitySet::NETWORK.0|CapabilitySet::UI.0); pub struct SystemRuntime { pub core: EndUserRuntime, @@ -38,52 +38,19 @@ pub struct SystemRuntime { pub cursor_x: i32, pub cursor_y: i32, } - impl SystemRuntime { - pub const fn new() -> Self { Self { core: EndUserRuntime::new(), windows: WindowManager::new(), services: Supervisor::new(spawn_service), apps: AppSupervisor::new(spawn_app, create_window), processes: ProcessManager::new(), namespaces: NamespaceManager::new(), storage: StorageDaemon::new(), storage_state: [0; awe_storaged::persistence::MAX_STATE_SIZE], storage_state_len: 0, network: NetworkDaemon::new(), cursor_x: 0, cursor_y: 0 } } - fn register_process(&mut self, pid: u64, entry: u64, memory_pages: u32, ipc_messages: u64) -> Result<(), ()> { - if pid == 0 || entry == 0 || self.processes.len() >= MAX_RUNTIME_PROCESSES { return Err(()); } - let slot = self.processes.len(); - let stack_ptr = unsafe { core::ptr::addr_of_mut!(PROCESS_STACKS[slot]) as *mut u8 }; - let stack_top = stack_ptr as u64 + 16384; - let descriptor = ProcessDescriptor { id: ProcessId(pid), state: ProcessState::Created, budget: ResourceBudget { cpu_ticks: 10_000, memory_bytes: memory_pages as u64 * 4096, ipc_messages } }; - let context = ProcessContext::new(ProcessId(pid), CpuContext::kernel_entry(entry, stack_top, 0)); - self.processes.register(descriptor, context).map_err(|_| ())?; - self.processes.make_runnable(ProcessId(pid)).map_err(|_| ()) - } - pub fn attach_framebuffer(&mut self, fb: FramebufferInfo) -> Result<(), super::EndUserRuntimeError> { self.core.attach_framebuffer(fb) } - pub fn mount_core_namespaces(&mut self, first_block: u64) -> Result<(), crate::storage::NamespaceError> { - let spacing = 128u64; - self.namespaces.mount(Namespace::Config, first_block)?; - self.namespaces.mount(Namespace::Home, first_block + spacing)?; - self.namespaces.mount(Namespace::Apps, first_block + spacing * 2)?; - self.namespaces.mount(Namespace::System, first_block + spacing * 3)?; - self.namespaces.mount(Namespace::Log, first_block + spacing * 4)?; - let volume = self.storage.register_volume(awe_storaged::VolumeType::AweFsVolume, 8192, first_block, false).map_err(|_| crate::storage::NamespaceError::Capacity)?; - self.storage.mount_volume(volume, 0xAWE0_0001).map_err(|_| crate::storage::NamespaceError::Capacity)?; - self.storage.create_snapshot(volume, 0).map_err(|_| crate::storage::NamespaceError::Capacity)?; - self.persist_storage_state().map_err(|_| crate::storage::NamespaceError::Capacity)?; - Ok(()) - } - pub fn persist_storage_state(&mut self) -> Result { let len = awe_storaged::persistence::export_state(&self.storage, &mut self.storage_state)?; self.storage_state_len = len; Ok(len) } - pub fn restore_storage_state(&mut self) -> Result<(), awe_storaged::persistence::PersistError> { if self.storage_state_len == 0 { return Ok(()); } awe_storaged::persistence::import_state(&mut self.storage, &self.storage_state[..self.storage_state_len]) } - pub fn register_network_interface(&mut self, mac: [u8; 6]) -> Result { self.network.add_interface(awe_netd::MacAddress(mac)) } - pub fn register_core_services(&mut self) -> Result<(), awe_initd::RuntimeError> { - let n = [None; awe_initd::runtime::MAX_DEPENDENCIES]; let d1 = [Some(ServiceId(1)), None, None, None, None, None, None, None]; let d4 = [Some(ServiceId(4)), None, None, None, None, None, None, None]; let entry = entry_address(runtime_service_entry); - let specs = [ - ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(1), restart: RestartPolicy::Always, capability_mask: CapabilitySet::DEVICE.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: n, dependency_count: 0, entry }, - ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(2), restart: RestartPolicy::Always, capability_mask: CapabilitySet::STORAGE.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: d1, dependency_count: 1, entry }, - ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(3), restart: RestartPolicy::Always, capability_mask: CapabilitySet::NETWORK.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: d1, dependency_count: 1, entry }, - ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(4), restart: RestartPolicy::Always, capability_mask: CapabilitySet::IPC.0, memory_limit_pages: 32, cpu_budget_ticks: 5_000 }, dependencies: d1, dependency_count: 1, entry }, - ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(5), restart: RestartPolicy::Always, capability_mask: CapabilitySet::PROCESS.union(CapabilitySet::IPC).0, memory_limit_pages: 64, cpu_budget_ticks: 10_000 }, dependencies: d4, dependency_count: 1, entry }, - ServiceRuntimeSpec { spec: ServiceSpec { id: ServiceId(6), restart: RestartPolicy::Always, capability_mask: CapabilitySet::UI.union(CapabilitySet::IPC).0, memory_limit_pages: 128, cpu_budget_ticks: 20_000 }, dependencies: d4, dependency_count: 1, entry }, - ]; - for spec in specs { self.services.register(spec)?; } Ok(()) - } - pub fn start_core_services(&mut self) -> Result<(), awe_initd::RuntimeError> { let entry=entry_address(runtime_service_entry) as u64; for id in 1..=6 { let pid=self.services.start(ServiceId(id))?; if self.register_process(pid,entry,64,128).is_err(){return Err(awe_initd::RuntimeError::SpawnFailed);} } Ok(()) } - pub fn admit_core_apps(&mut self) -> Result<(), awe_appd::AppRuntimeError> { let apps=[(1u64,CapabilitySet::UI.union(CapabilitySet::IPC).union(CapabilitySet::STORAGE)),(2u64,CapabilitySet::UI.union(CapabilitySet::IPC).union(CapabilitySet::STORAGE)),(3u64,CapabilitySet::UI.union(CapabilitySet::IPC).union(CapabilitySet::STORAGE)),(4u64,CapabilitySet::UI.union(CapabilitySet::IPC))]; for(id,caps)in apps{let manifest=AppManifest{id:AppId(id),abi_major:awe_appd::AWE_APP_ABI_MAJOR,abi_minor:awe_appd::AWE_APP_ABI_MINOR,memory_limit_pages:32,capability_mask:caps.0,dependency_count:0,resource_count:0};self.apps.admit(manifest).map_err(|_|awe_appd::AppRuntimeError::InvalidManifest)?;}Ok(()) } - pub fn start_core_apps(&mut self) -> Result<(), awe_appd::AppRuntimeError> { let entry=entry_address(runtime_app_entry) as u64; for id in 1..=4{let pid=self.apps.start(AppId(id),ALL.0)?;if self.register_process(pid,entry,32,64).is_err(){return Err(awe_appd::AppRuntimeError::SpawnFailed);}}Ok(()) } - pub fn route_ps2(&mut self,event:Ps2Event)->Result{let translated=match event{Ps2Event::Key{code,pressed}=>InputEvent::Key{code:key_code_value(code),pressed},Ps2Event::Pointer{dx,dy,buttons}=>{self.cursor_x=self.cursor_x.saturating_add(dx as i32);self.cursor_y=self.cursor_y.saturating_add(dy as i32);InputEvent::Pointer{x:self.cursor_x,y:self.cursor_y,buttons}}};self.core.push_input(translated)?;self.windows.handle_input(translated);Ok(RuntimeEvent::Input(translated))} + pub const fn new()->Self{Self{core:EndUserRuntime::new(),windows:WindowManager::new(),services:Supervisor::new(spawn_service),apps:AppSupervisor::new(spawn_app,create_window),processes:ProcessManager::new(),namespaces:NamespaceManager::new(),storage:StorageDaemon::new(),storage_state:[0;awe_storaged::persistence::MAX_STATE_SIZE],storage_state_len:0,network:NetworkDaemon::new(),cursor_x:0,cursor_y:0}} + fn register_process(&mut self,pid:u64,entry:u64,memory_pages:u32,ipc_messages:u64)->Result<(),()>{if pid==0||entry==0||self.processes.len()>=MAX_RUNTIME_PROCESSES{return Err(())}let slot=self.processes.len();let stack_ptr=unsafe{core::ptr::addr_of_mut!(PROCESS_STACKS[slot]) as *mut u8};let stack_top=stack_ptr as u64+16384;let d=ProcessDescriptor{id:ProcessId(pid),state:ProcessState::Created,budget:ResourceBudget{cpu_ticks:10_000,memory_bytes:memory_pages as u64*4096,ipc_messages}};let c=ProcessContext::new(ProcessId(pid),CpuContext::kernel_entry(entry,stack_top,0));self.processes.register(d,c).map_err(|_|())?;self.processes.make_runnable(ProcessId(pid)).map_err(|_|())} + pub fn attach_framebuffer(&mut self,fb:FramebufferInfo)->Result<(),super::EndUserRuntimeError>{self.core.attach_framebuffer(fb)} + pub fn mount_core_namespaces(&mut self,first_block:u64)->Result<(),crate::storage::NamespaceError>{let s=128;self.namespaces.mount(Namespace::Config,first_block)?;self.namespaces.mount(Namespace::Home,first_block+s)?;self.namespaces.mount(Namespace::Apps,first_block+s*2)?;self.namespaces.mount(Namespace::System,first_block+s*3)?;self.namespaces.mount(Namespace::Log,first_block+s*4)?;let v=self.storage.register_volume(awe_storaged::VolumeType::AweFsVolume,8192,first_block,false).map_err(|_|crate::storage::NamespaceError::Capacity)?;self.storage.mount_volume(v,0xAWE0_0001).map_err(|_|crate::storage::NamespaceError::Capacity)?;self.storage.create_snapshot(v,0).map_err(|_|crate::storage::NamespaceError::Capacity)?;self.persist_storage_state().map_err(|_|crate::storage::NamespaceError::Capacity)?;Ok(())} + pub fn persist_storage_state(&mut self)->Result{let len=awe_storaged::persistence::export_state(&self.storage,&mut self.storage_state)?;self.storage_state_len=len;Ok(len)} + pub fn restore_storage_state(&mut self)->Result<(),awe_storaged::persistence::PersistError>{if self.storage_state_len==0{return Ok(())}awe_storaged::persistence::import_state(&mut self.storage,&self.storage_state[..self.storage_state_len])} + pub fn register_network_interface(&mut self,mac:[u8;6])->Result{self.network.add_interface(awe_netd::MacAddress(mac))} + pub fn register_core_services(&mut self)->Result<(),awe_initd::RuntimeError>{let n=[None;awe_initd::runtime::MAX_DEPENDENCIES];let d1=[Some(ServiceId(1)),None,None,None,None,None,None,None];let d4=[Some(ServiceId(4)),None,None,None,None,None,None,None];let entry=entry_address(runtime_service_entry);let specs=[ServiceRuntimeSpec{spec:ServiceSpec{id:ServiceId(1),restart:RestartPolicy::Always,capability_mask:CapabilitySet::DEVICE.union(CapabilitySet::IPC).0,memory_limit_pages:64,cpu_budget_ticks:10_000},dependencies:n,dependency_count:0,entry},ServiceRuntimeSpec{spec:ServiceSpec{id:ServiceId(2),restart:RestartPolicy::Always,capability_mask:CapabilitySet::STORAGE.union(CapabilitySet::IPC).0,memory_limit_pages:64,cpu_budget_ticks:10_000},dependencies:d1,dependency_count:1,entry},ServiceRuntimeSpec{spec:ServiceSpec{id:ServiceId(3),restart:RestartPolicy::Always,capability_mask:CapabilitySet::NETWORK.union(CapabilitySet::IPC).0,memory_limit_pages:64,cpu_budget_ticks:10_000},dependencies:d1,dependency_count:1,entry},ServiceRuntimeSpec{spec:ServiceSpec{id:ServiceId(4),restart:RestartPolicy::Always,capability_mask:CapabilitySet::IPC.0,memory_limit_pages:32,cpu_budget_ticks:5_000},dependencies:d1,dependency_count:1,entry},ServiceRuntimeSpec{spec:ServiceSpec{id:ServiceId(5),restart:RestartPolicy::Always,capability_mask:CapabilitySet::PROCESS.union(CapabilitySet::IPC).0,memory_limit_pages:64,cpu_budget_ticks:10_000},dependencies:d4,dependency_count:1,entry},ServiceRuntimeSpec{spec:ServiceSpec{id:ServiceId(6),restart:RestartPolicy::Always,capability_mask:CapabilitySet::UI.union(CapabilitySet::IPC).0,memory_limit_pages:128,cpu_budget_ticks:20_000},dependencies:d4,dependency_count:1,entry}];for spec in specs{self.services.register(spec)?}Ok(())} + pub fn start_core_services(&mut self)->Result<(),awe_initd::RuntimeError>{let e=entry_address(runtime_service_entry)as u64;for id in 1..=6{let pid=self.services.start(ServiceId(id))?;if self.register_process(pid,e,64,128).is_err(){return Err(awe_initd::RuntimeError::SpawnFailed)}}Ok(())} + pub fn admit_core_apps(&mut self)->Result<(),awe_appd::AppRuntimeError>{let apps=[(1u64,CapabilitySet::UI.union(CapabilitySet::IPC).union(CapabilitySet::STORAGE)),(2u64,CapabilitySet::UI.union(CapabilitySet::IPC).union(CapabilitySet::STORAGE)),(3u64,CapabilitySet::UI.union(CapabilitySet::IPC).union(CapabilitySet::STORAGE)),(4u64,CapabilitySet::UI.union(CapabilitySet::IPC))];for(id,caps)in apps{let m=AppManifest{id:AppId(id),abi_major:awe_appd::AWE_APP_ABI_MAJOR,abi_minor:awe_appd::AWE_APP_ABI_MINOR,memory_limit_pages:32,capability_mask:caps.0,dependency_count:0,resource_count:0};self.apps.admit(m).map_err(|_|awe_appd::AppRuntimeError::InvalidManifest)?}Ok(())} + pub fn start_core_apps(&mut self)->Result<(),awe_appd::AppRuntimeError>{let e=entry_address(runtime_app_entry)as u64;for id in 1..=4{let pid=self.apps.start(AppId(id),ALL.0)?;if self.register_process(pid,e,32,64).is_err(){return Err(awe_appd::AppRuntimeError::SpawnFailed)}}Ok(())} + pub fn route_ps2(&mut self,event:Ps2Event)->Result{let t=match event{Ps2Event::Key{code,pressed}=>InputEvent::Key{code:key_code_value(code),pressed},Ps2Event::Pointer{dx,dy,buttons}=>{self.cursor_x=self.cursor_x.saturating_add(dx as i32);self.cursor_y=self.cursor_y.saturating_add(dy as i32);InputEvent::Pointer{x:self.cursor_x,y:self.cursor_y,buttons}}};self.core.push_input(t)?;self.windows.handle_input(t);Ok(RuntimeEvent::Input(t))} pub fn create_native_window(&mut self,rect:RuntimeRect)->Result{self.windows.create(rect)} pub fn pointer_target(&mut self)->Option{self.windows.hit_test(self.cursor_x,self.cursor_y)} pub fn service_state(&self,id:u16)->Option{self.services.state(ServiceId(id))} @@ -93,5 +60,6 @@ impl SystemRuntime { } fn key_code_value(code:KeyCode)->u16{match code{KeyCode::Escape=>0x01,KeyCode::Enter=>0x1C,KeyCode::Backspace=>0x0E,KeyCode::Tab=>0x0D,KeyCode::Space=>0x39,KeyCode::Left=>0x4B,KeyCode::Right=>0x4D,KeyCode::Up=>0x48,KeyCode::Down=>0x50,KeyCode::Character(v)|KeyCode::Unknown(v)=>v as u16}} impl Default for SystemRuntime{fn default()->Self{Self::new()}} + #[cfg(test)] -mod tests{use super::*;#[test]fn core_services_start_and_register_processes(){let mut r=SystemRuntime::new();r.register_core_services().unwrap();r.start_core_services().unwrap();assert_eq!(r.service_state(6),Some(ServiceState::Running));assert_eq!(r.process_count(),6);}#[test]fn core_apps_admit_and_register_processes(){let mut r=SystemRuntime::new();r.admit_core_apps().unwrap();r.start_core_apps().unwrap();assert_eq!(r.app_state(1),Some(AppState::Running));assert_eq!(r.process_count(),4);}#[test]fn ps2_input_reaches_window_manager(){let mut r=SystemRuntime::new();r.create_native_window(RuntimeRect{x:0,y:0,width:100,height:100}).unwrap();r.route_ps2(Ps2Event::Pointer{dx:20,dy:20,buttons:1}).unwrap();assert!(r.pointer_target().is_some());}#[test]fn capability_set_is_used_for_app_admission(){let mut r=SystemRuntime::new();r.admit_core_apps().unwrap();assert_eq!(r.apps.start(AppId(1),CapabilitySet::UI.0),Err(awe_appd::AppRuntimeError::CapabilityDenied));}#[test]fn namespace_mounts_are_bounded_and_storage_persisted(){let mut r=SystemRuntime::new();r.mount_core_namespaces(32).unwrap();assert!(r.namespaces.is_mounted(Namespace::Config));assert!(r.namespaces.is_mounted(Namespace::Log));assert!(r.storage_state_len>0);assert!(r.storage_state_len<=awe_storaged::persistence::MAX_STATE_SIZE);assert!(STORAGE_MAX_PACKAGE_FILES>0);}} +mod tests{use super::*;#[test]fn core_services_start_and_register_processes(){let mut r=SystemRuntime::new();r.register_core_services().unwrap();r.start_core_services().unwrap();assert_eq!(r.service_state(6),Some(ServiceState::Running));assert_eq!(r.process_count(),6)}#[test]fn core_apps_admit_and_register_processes(){let mut r=SystemRuntime::new();r.admit_core_apps().unwrap();r.start_core_apps().unwrap();assert_eq!(r.app_state(1),Some(AppState::Running));assert_eq!(r.process_count(),4)}#[test]fn ps2_input_reaches_window_manager(){let mut r=SystemRuntime::new();r.create_native_window(RuntimeRect{x:0,y:0,width:100,height:100}).unwrap();r.route_ps2(Ps2Event::Pointer{dx:20,dy:20,buttons:1}).unwrap();assert!(r.pointer_target().is_some())}#[test]fn capability_set_is_used_for_app_admission(){let mut r=SystemRuntime::new();r.admit_core_apps().unwrap();assert_eq!(r.apps.start(AppId(1),CapabilitySet::UI.0),Err(awe_appd::AppRuntimeError::CapabilityDenied))}#[test]fn namespace_mounts_are_bounded_and_storage_persisted(){let mut r=SystemRuntime::new();r.mount_core_namespaces(32).unwrap();assert!(r.namespaces.is_mounted(Namespace::Config));assert!(r.namespaces.is_mounted(Namespace::Log));assert!(r.storage_state_len>0);assert!(r.storage_state_len<=awe_storaged::persistence::MAX_STATE_SIZE)}} From 93e51dcf5e9cdfddea7621bcd976270c3990a7f9 Mon Sep 17 00:00:00 2001 From: Ararat Date: Thu, 27 Aug 2026 01:11:51 +0400 Subject: [PATCH 65/65] chore(aweos): align CI evidence gate with 40-45 runtime milestone --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5790326..da3d285 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,7 +51,7 @@ jobs: test $status -eq 124 || test $status -eq 0 grep -q "AWEOS: kernel state = RUNNING" qemu-serial.log grep -q "AWEOS: entering Ring 3" qemu-serial.log - grep -q "AWEOS: end-user runtime control plane started" qemu-serial.log + grep -q "AWEOS: persistent initd/appd/storage/UI runtime control plane started" qemu-serial.log - name: Graphical QEMU Startup Smoke Test timeout-minutes: 3 run: |