1#![no_std]
7#![no_main]
8#![feature(alloc_error_handler)]
9
10extern crate alloc;
11
12mod syscalls;
13
14use alloc::{format, string::String, vec, vec::Vec};
15use core::{alloc::Layout, panic::PanicInfo};
16use fs_ext4::{BlockDevice, BlockDeviceError, Ext4FileSystem};
17use strat9_syscall::error::{EINVAL, ENOSYS};
18use syscalls::*;
19
20alloc_freelist::define_freelist_allocator!(pub struct BumpAllocator; heap_size = 1024 * 1024;);
25
26#[global_allocator]
27static GLOBAL_ALLOCATOR: BumpAllocator = BumpAllocator;
28
29#[alloc_error_handler]
30fn alloc_error(_layout: Layout) -> ! {
32 debug_log("[fs-ext4] OOM\n");
33 exit(12);
34}
35
36use strat9_syscall::data::IpcMessage;
37
38const OPCODE_OPEN: u32 = 0x01;
39const OPCODE_READ: u32 = 0x02;
40const OPCODE_WRITE: u32 = 0x03;
41const OPCODE_CLOSE: u32 = 0x04;
42const OPCODE_CREATE_FILE: u32 = 0x05;
43const OPCODE_CREATE_DIR: u32 = 0x06;
44const OPCODE_UNLINK: u32 = 0x07;
45const OPCODE_READDIR: u32 = 0x08;
46const OPCODE_BOOTSTRAP: u32 = 0x10;
47const REPLY_MSG_TYPE: u32 = 0x80;
48const STATUS_OK: u32 = 0;
49const INITIAL_BIND_PATH: &[u8] = b"/srv/strate-fs-ext4/default";
50
51const MAX_OPEN_PATH: usize = 42;
52const MAX_WRITE_DATA: usize = 30;
53
54struct BootstrapInfo {
55 handle: u64,
56 label: String,
57}
58
59fn sanitize_label(raw: &str) -> String {
61 let mut out = String::new();
62 for b in raw.bytes().take(31) {
63 let ok = (b as char).is_ascii_alphanumeric() || b == b'-' || b == b'_' || b == b'.';
64 out.push(if ok { b as char } else { '_' });
65 }
66 if out.is_empty() {
67 String::from("default")
68 } else {
69 out
70 }
71}
72
73fn parse_bootstrap_label(payload: &[u8]) -> String {
75 let len = payload.first().copied().unwrap_or(0) as usize;
76 if len == 0 {
77 return String::from("default");
78 }
79 let end = 1usize.saturating_add(len);
80 let Some(bytes) = payload.get(1..end) else {
81 return String::from("default");
82 };
83 match core::str::from_utf8(bytes) {
84 Ok(s) => sanitize_label(s),
85 Err(_) => String::from("default"),
86 }
87}
88
89fn bind_srv_alias(port_handle: u64, label: &str) {
91 let path = format!("/srv/strate-fs-ext4/{}", label);
92 match call::ipc_bind_port(port_handle as usize, path.as_bytes()) {
93 Ok(_) => {
94 let msg = format!("[fs-ext4] Port alias bound to {}\n", path);
95 debug_log(&msg);
96 }
97 Err(e) => {
98 let msg = format!(
99 "[fs-ext4] Failed to bind port alias {}: {}\n",
100 path,
101 e.name()
102 );
103 debug_log(&msg);
104 }
105 }
106}
107
108struct Ext4Strate {
110 _fs: Ext4FileSystem,
111}
112
113impl Ext4Strate {
114 fn new(fs: Ext4FileSystem) -> Self {
116 Ext4Strate { _fs: fs }
117 }
118
119 fn ok_reply(sender: u64) -> IpcMessage {
121 let mut reply = IpcMessage::new(REPLY_MSG_TYPE);
122 reply.sender = sender;
123 reply.payload[0..4].copy_from_slice(&STATUS_OK.to_le_bytes());
124 reply
125 }
126
127 fn err_reply(sender: u64, status: u32) -> IpcMessage {
129 let mut reply = IpcMessage::new(REPLY_MSG_TYPE);
130 reply.sender = sender;
131 reply.payload[0..4].copy_from_slice(&status.to_le_bytes());
132 reply
133 }
134
135 fn read_u16(payload: &[u8], start: usize) -> core::result::Result<u16, u32> {
137 let end = start.checked_add(2).ok_or(EINVAL as u32)?;
138 let bytes = payload.get(start..end).ok_or(EINVAL as u32)?;
139 Ok(u16::from_le_bytes([bytes[0], bytes[1]]))
140 }
141
142 fn read_u32(payload: &[u8], start: usize) -> core::result::Result<u32, u32> {
144 let end = start.checked_add(4).ok_or(EINVAL as u32)?;
145 let bytes = payload.get(start..end).ok_or(EINVAL as u32)?;
146 Ok(u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]))
147 }
148
149 fn read_u64(payload: &[u8], start: usize) -> core::result::Result<u64, u32> {
151 let end = start.checked_add(8).ok_or(EINVAL as u32)?;
152 let bytes = payload.get(start..end).ok_or(EINVAL as u32)?;
153 Ok(u64::from_le_bytes([
154 bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
155 ]))
156 }
157
158 fn parse_path<'a>(
159 payload: &'a [u8],
160 len_offset: usize,
161 data_offset: usize,
162 max_len: usize,
163 ) -> core::result::Result<&'a str, u32> {
164 let path_len = Self::read_u16(payload, len_offset)? as usize;
165 if path_len > max_len {
166 return Err(EINVAL as u32);
167 }
168 let end = data_offset.checked_add(path_len).ok_or(EINVAL as u32)?;
169 let path_bytes = payload.get(data_offset..end).ok_or(EINVAL as u32)?;
170 core::str::from_utf8(path_bytes).map_err(|_| EINVAL as u32)
171 }
172
173 fn handle_open(&mut self, sender: u64, payload: &[u8]) -> IpcMessage {
175 let _flags = match Self::read_u32(payload, 0) {
176 Ok(v) => v,
177 Err(code) => return Self::err_reply(sender, code),
178 };
179 let _path = match Self::parse_path(payload, 4, 6, MAX_OPEN_PATH) {
180 Ok(v) => v,
181 Err(code) => return Self::err_reply(sender, code),
182 };
183 Self::err_reply(sender, ENOSYS as u32)
184 }
185
186 fn handle_read(&mut self, sender: u64, payload: &[u8]) -> IpcMessage {
188 let _file_id = match Self::read_u64(payload, 0) {
189 Ok(v) => v,
190 Err(code) => return Self::err_reply(sender, code),
191 };
192 let _offset = match Self::read_u64(payload, 8) {
193 Ok(v) => v,
194 Err(code) => return Self::err_reply(sender, code),
195 };
196 let _requested = match Self::read_u32(payload, 16) {
197 Ok(v) => v as usize,
198 Err(code) => return Self::err_reply(sender, code),
199 };
200 Self::err_reply(sender, ENOSYS as u32)
201 }
202
203 fn handle_write(&mut self, sender: u64, payload: &[u8]) -> IpcMessage {
205 let _file_id = match Self::read_u64(payload, 0) {
206 Ok(v) => v,
207 Err(code) => return Self::err_reply(sender, code),
208 };
209 let _offset = match Self::read_u64(payload, 8) {
210 Ok(v) => v,
211 Err(code) => return Self::err_reply(sender, code),
212 };
213 let len = match Self::read_u16(payload, 16) {
214 Ok(v) => v as usize,
215 Err(code) => return Self::err_reply(sender, code),
216 };
217 if len > MAX_WRITE_DATA {
218 return Self::err_reply(sender, EINVAL as u32);
219 }
220 let end = 18 + len;
221 let _data = match payload.get(18..end) {
222 Some(s) => s,
223 None => return Self::err_reply(sender, EINVAL as u32),
224 };
225 Self::err_reply(sender, ENOSYS as u32)
226 }
227
228 fn handle_close(&mut self, sender: u64, payload: &[u8]) -> IpcMessage {
230 let _file_id = match Self::read_u64(payload, 0) {
231 Ok(v) => v,
232 Err(code) => return Self::err_reply(sender, code),
233 };
234 Self::err_reply(sender, ENOSYS as u32)
235 }
236
237 fn serve(&mut self, port_handle: u64) -> ! {
239 loop {
240 let mut msg = IpcMessage::new(0);
241 if call::ipc_recv(port_handle as usize, &mut msg).is_err() {
242 let _ = call::sched_yield();
243 continue;
244 }
245
246 let reply = match msg.msg_type {
247 OPCODE_BOOTSTRAP => {
248 let label = parse_bootstrap_label(&msg.payload);
249 bind_srv_alias(port_handle, &label);
250 Self::ok_reply(msg.sender)
251 }
252 OPCODE_OPEN => self.handle_open(msg.sender, &msg.payload),
253 OPCODE_READ => self.handle_read(msg.sender, &msg.payload),
254 OPCODE_WRITE => self.handle_write(msg.sender, &msg.payload),
255 OPCODE_CLOSE => self.handle_close(msg.sender, &msg.payload),
256 OPCODE_CREATE_FILE | OPCODE_CREATE_DIR | OPCODE_UNLINK | OPCODE_READDIR => {
257 Self::err_reply(msg.sender, ENOSYS as u32)
258 }
259 _ => Self::err_reply(msg.sender, ENOSYS as u32),
260 };
261 let _ = call::ipc_reply(&reply);
262 }
263 }
264}
265
266const SECTOR_SIZE: usize = 512;
271const BLOCK_SIZE: usize = 4096;
272
273struct VolumeBlockDevice {
274 handle: u64,
275 sector_count: u64,
276}
277
278impl VolumeBlockDevice {
279 fn new(handle: u64) -> core::result::Result<Self, BlockDeviceError> {
281 let sector_count = volume_info(handle).map_err(map_sys_err)?;
282 Ok(Self {
283 handle,
284 sector_count,
285 })
286 }
287}
288
289fn map_sys_err(err: Error) -> BlockDeviceError {
291 match err {
292 Error::Again => BlockDeviceError::NotReady,
293 Error::IoError => BlockDeviceError::Io,
294 Error::InvalidArgument => BlockDeviceError::InvalidOffset,
295 _ => BlockDeviceError::Other,
296 }
297}
298
299fn log_sys_err(prefix: &str, err: Error) {
301 let msg = format!("[fs-ext4] {}: {} ({})\n", prefix, err.name(), err);
302 debug_log(&msg);
303}
304
305fn validate_volume_handle(handle: u64) -> Result<u64> {
307 let sectors = volume_info(handle)?;
308 if sectors == 0 {
309 return Err(Error::InvalidArgument);
310 }
311 let mut probe = [0u8; SECTOR_SIZE];
312 let _ = volume_read(handle, 0, &mut probe, 1)?;
313 Ok(sectors)
314}
315
316fn discover_volume_handle_local() -> Option<u64> {
318 for h in 0u64..256u64 {
321 if let Ok(sectors) = validate_volume_handle(h) {
322 let msg = format!(
323 "[fs-ext4] Discovered local volume handle={} sectors={}\n",
324 h, sectors
325 );
326 debug_log(&msg);
327 return Some(h);
328 }
329 }
330 None
331}
332
333impl BlockDevice for VolumeBlockDevice {
334 fn read_offset(&self, offset: usize) -> core::result::Result<Vec<u8>, BlockDeviceError> {
336 if offset % SECTOR_SIZE != 0 {
337 return Err(BlockDeviceError::InvalidOffset);
338 }
339 let sector = (offset / SECTOR_SIZE) as u64;
340 let sector_count = (BLOCK_SIZE / SECTOR_SIZE) as u64;
341 if sector_count == 0 {
342 return Err(BlockDeviceError::InvalidOffset);
343 }
344 let mut buf = vec![0u8; (sector_count as usize) * SECTOR_SIZE];
345 volume_read(self.handle, sector, &mut buf, sector_count).map_err(map_sys_err)?;
346 Ok(buf)
347 }
348
349 fn write_offset(
351 &mut self,
352 offset: usize,
353 data: &[u8],
354 ) -> core::result::Result<(), BlockDeviceError> {
355 if offset % SECTOR_SIZE != 0 || data.len() % SECTOR_SIZE != 0 {
356 return Err(BlockDeviceError::InvalidOffset);
357 }
358 let sector = (offset / SECTOR_SIZE) as u64;
359 let sector_count = (data.len() / SECTOR_SIZE) as u64;
360 if sector_count == 0 {
361 return Err(BlockDeviceError::InvalidOffset);
362 }
363 volume_write(self.handle, sector, data, sector_count).map_err(map_sys_err)?;
364 Ok(())
365 }
366
367 fn size(&self) -> core::result::Result<usize, BlockDeviceError> {
369 Ok(self.sector_count as usize * SECTOR_SIZE)
370 }
371}
372
373fn wait_for_bootstrap(port_handle: u64) -> BootstrapInfo {
375 debug_log("[fs-ext4] Waiting for volume bootstrap...\n");
376 loop {
377 let mut msg = IpcMessage::new(0);
378 if call::ipc_recv(port_handle as usize, &mut msg).is_err() {
379 let _ = call::sched_yield();
380 continue;
381 }
382
383 if msg.msg_type == OPCODE_BOOTSTRAP && msg.flags != 0 {
384 let reply = Ext4Strate::ok_reply(msg.sender);
385 let _ = call::ipc_reply(&reply);
386 return BootstrapInfo {
387 handle: msg.flags as u64,
388 label: parse_bootstrap_label(&msg.payload),
389 };
390 }
391
392 let reply = Ext4Strate::err_reply(msg.sender, ENOSYS as u32);
393 let _ = call::ipc_reply(&reply);
394 }
395}
396
397fn try_wait_for_bootstrap(port_handle: u64, attempts: usize) -> Option<BootstrapInfo> {
399 for _ in 0..attempts {
400 let mut msg = IpcMessage::new(0);
401 match call::ipc_try_recv(port_handle as usize, &mut msg) {
402 Ok(_) => {
403 if msg.msg_type == OPCODE_BOOTSTRAP && msg.flags != 0 {
404 let reply = Ext4Strate::ok_reply(msg.sender);
405 let _ = call::ipc_reply(&reply);
406 return Some(BootstrapInfo {
407 handle: msg.flags as u64,
408 label: parse_bootstrap_label(&msg.payload),
409 });
410 }
411 let reply = Ext4Strate::err_reply(msg.sender, ENOSYS as u32);
412 let _ = call::ipc_reply(&reply);
413 }
414 Err(Error::Again) => {}
415 Err(err) => {
416 log_sys_err("try_recv bootstrap failed", err);
417 }
418 }
419 let _ = call::sched_yield();
420 }
421 None
422}
423
424#[unsafe(no_mangle)]
425pub extern "C" fn _start(bootstrap_handle: u64) -> ! {
427 debug_log("[fs-ext4] Starting EXT4 filesystem strate\n");
431
432 let port_handle = match call::ipc_create_port(0) {
433 Ok(h) => h as u64,
434 Err(_) => {
435 debug_log("[fs-ext4] Failed to create IPC port\n");
436 exit(1);
437 }
438 };
439
440 debug_log("[fs-ext4] IPC port created\n");
441
442 if call::ipc_bind_port(port_handle as usize, INITIAL_BIND_PATH).is_err() {
443 debug_log("[fs-ext4] Failed to bind initial port alias\n");
444 exit(2);
445 }
446
447 debug_log("[fs-ext4] Port bound to /srv/strate-fs-ext4/default\n");
448
449 let mut volume_handle = bootstrap_handle;
450 let mut bootstrap_label = String::from("default");
451 if volume_handle == 0 {
452 debug_log("[fs-ext4] Waiting for early bootstrap message...\n");
453 if let Some(info) = try_wait_for_bootstrap(port_handle, 2048) {
454 let msg = format!(
455 "[fs-ext4] Received bootstrap handle: {} label: {}\n",
456 info.handle, info.label
457 );
458 debug_log(&msg);
459 volume_handle = info.handle;
460 bootstrap_label = info.label;
461 } else if let Some(h) = discover_volume_handle_local() {
462 debug_log("[fs-ext4] Bootstrap message timeout, using local discovery fallback\n");
463 volume_handle = h;
464 } else {
465 debug_log("[fs-ext4] Bootstrap message timeout, switching to blocking wait\n");
466 let info = wait_for_bootstrap(port_handle);
467 volume_handle = info.handle;
468 bootstrap_label = info.label;
469 }
470 }
471 {
472 let msg = format!(
473 "[fs-ext4] Volume handle ready: {} label: {}\n",
474 volume_handle, bootstrap_label
475 );
476 debug_log(&msg);
477 }
478 bind_srv_alias(port_handle, &bootstrap_label);
479
480 let mut attempts: u64 = 0;
482 loop {
483 attempts = attempts.wrapping_add(1);
484 match validate_volume_handle(volume_handle) {
485 Ok(sectors) => {
486 let msg = format!(
487 "[fs-ext4] volume probe OK: handle={} sectors={} (attempt={})\n",
488 volume_handle, sectors, attempts
489 );
490 debug_log(&msg);
491 }
492 Err(err) => {
493 log_sys_err("volume probe failed", err);
494 if bootstrap_handle == 0 && attempts % 8 == 0 {
496 debug_log("[fs-ext4] Waiting for refreshed bootstrap handle...\n");
497 let info = wait_for_bootstrap(port_handle);
498 volume_handle = info.handle;
499 bootstrap_label = info.label;
500 let msg = format!(
501 "[fs-ext4] Refreshed volume handle: {} label: {}\n",
502 volume_handle, bootstrap_label
503 );
504 debug_log(&msg);
505 }
506 for _ in 0..2048 {
507 let _ = call::sched_yield();
508 }
509 continue;
510 }
511 }
512
513 let device = match VolumeBlockDevice::new(volume_handle) {
514 Ok(dev) => alloc::sync::Arc::new(dev),
515 Err(e) => {
516 let msg = format!(
517 "[fs-ext4] Failed to init volume device (attempt={}): {:?}\n",
518 attempts, e
519 );
520 debug_log(&msg);
521 for _ in 0..2048 {
522 let _ = call::sched_yield();
523 }
524 continue;
525 }
526 };
527
528 let fs = match Ext4FileSystem::mount(device) {
529 Ok(fs) => fs,
530 Err(e) => {
531 let msg = format!(
532 "[fs-ext4] Failed to mount EXT4 filesystem (attempt={}): {:?}\n",
533 attempts, e
534 );
535 debug_log(&msg);
536 for _ in 0..2048 {
537 let _ = call::sched_yield();
538 }
539 continue;
540 }
541 };
542
543 debug_log("[fs-ext4] EXT4 mounted successfully\n");
544 debug_log("[fs-ext4] Strate ready, waiting for requests...\n");
545
546 let mut strate = Ext4Strate::new(fs);
548 strate.serve(port_handle);
549 }
550}
551
552#[panic_handler]
553fn panic(_info: &PanicInfo) -> ! {
555 debug_log("[fs-ext4] PANIC!\n");
556 exit(255);
557}