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 msg = format!("[fs-ext4] Probing volume handle={}\n", handle);
308 debug_log(&msg);
309 let sectors = volume_info(handle)?;
310 if sectors == 0 {
311 return Err(Error::InvalidArgument);
312 }
313 let msg = format!(
314 "[fs-ext4] Volume info OK: handle={} sectors={}\n",
315 handle, sectors
316 );
317 debug_log(&msg);
318 let mut probe = [0u8; SECTOR_SIZE];
319 let _ = volume_read(handle, 0, &mut probe, 1)?;
320 let msg = format!("[fs-ext4] Sector-0 probe OK: handle={}\n", handle);
321 debug_log(&msg);
322 Ok(sectors)
323}
324
325fn discover_volume_handle_local() -> Option<u64> {
327 for h in 0u64..256u64 {
330 if let Ok(sectors) = validate_volume_handle(h) {
331 let msg = format!(
332 "[fs-ext4] Discovered local volume handle={} sectors={}\n",
333 h, sectors
334 );
335 debug_log(&msg);
336 return Some(h);
337 }
338 }
339 None
340}
341
342impl BlockDevice for VolumeBlockDevice {
343 fn read_offset(&self, offset: usize) -> core::result::Result<Vec<u8>, BlockDeviceError> {
345 if offset % SECTOR_SIZE != 0 {
346 return Err(BlockDeviceError::InvalidOffset);
347 }
348 let sector = (offset / SECTOR_SIZE) as u64;
349 let sector_count = (BLOCK_SIZE / SECTOR_SIZE) as u64;
350 if sector_count == 0 {
351 return Err(BlockDeviceError::InvalidOffset);
352 }
353 let mut buf = vec![0u8; (sector_count as usize) * SECTOR_SIZE];
354 volume_read(self.handle, sector, &mut buf, sector_count).map_err(map_sys_err)?;
355 Ok(buf)
356 }
357
358 fn write_offset(
360 &mut self,
361 offset: usize,
362 data: &[u8],
363 ) -> core::result::Result<(), BlockDeviceError> {
364 if offset % SECTOR_SIZE != 0 || data.len() % SECTOR_SIZE != 0 {
365 return Err(BlockDeviceError::InvalidOffset);
366 }
367 let sector = (offset / SECTOR_SIZE) as u64;
368 let sector_count = (data.len() / SECTOR_SIZE) as u64;
369 if sector_count == 0 {
370 return Err(BlockDeviceError::InvalidOffset);
371 }
372 volume_write(self.handle, sector, data, sector_count).map_err(map_sys_err)?;
373 Ok(())
374 }
375
376 fn size(&self) -> core::result::Result<usize, BlockDeviceError> {
378 Ok(self.sector_count as usize * SECTOR_SIZE)
379 }
380}
381
382fn wait_for_bootstrap(port_handle: u64) -> BootstrapInfo {
384 debug_log("[fs-ext4] Waiting for volume bootstrap...\n");
385 loop {
386 let mut msg = IpcMessage::new(0);
387 if call::ipc_recv(port_handle as usize, &mut msg).is_err() {
388 let _ = call::sched_yield();
389 continue;
390 }
391
392 if msg.msg_type == OPCODE_BOOTSTRAP && msg.flags != 0 {
393 let reply = Ext4Strate::ok_reply(msg.sender);
394 let _ = call::ipc_reply(&reply);
395 return BootstrapInfo {
396 handle: msg.flags as u64,
397 label: parse_bootstrap_label(&msg.payload),
398 };
399 }
400
401 let reply = Ext4Strate::err_reply(msg.sender, ENOSYS as u32);
402 let _ = call::ipc_reply(&reply);
403 }
404}
405
406fn try_wait_for_bootstrap(port_handle: u64, attempts: usize) -> Option<BootstrapInfo> {
408 for _ in 0..attempts {
409 let mut msg = IpcMessage::new(0);
410 match call::ipc_try_recv(port_handle as usize, &mut msg) {
411 Ok(_) => {
412 if msg.msg_type == OPCODE_BOOTSTRAP && msg.flags != 0 {
413 let reply = Ext4Strate::ok_reply(msg.sender);
414 let _ = call::ipc_reply(&reply);
415 return Some(BootstrapInfo {
416 handle: msg.flags as u64,
417 label: parse_bootstrap_label(&msg.payload),
418 });
419 }
420 let reply = Ext4Strate::err_reply(msg.sender, ENOSYS as u32);
421 let _ = call::ipc_reply(&reply);
422 }
423 Err(Error::Again) => {}
424 Err(err) => {
425 log_sys_err("try_recv bootstrap failed", err);
426 }
427 }
428 let _ = call::sched_yield();
429 }
430 None
431}
432
433#[unsafe(no_mangle)]
434pub extern "C" fn _start(bootstrap_handle: u64) -> ! {
436 debug_log("[fs-ext4] Starting EXT4 filesystem strate\n");
440
441 let port_handle = match call::ipc_create_port(0) {
442 Ok(h) => h as u64,
443 Err(_) => {
444 debug_log("[fs-ext4] Failed to create IPC port\n");
445 exit(1);
446 }
447 };
448
449 debug_log("[fs-ext4] IPC port created\n");
450
451 if call::ipc_bind_port(port_handle as usize, INITIAL_BIND_PATH).is_err() {
452 debug_log("[fs-ext4] Failed to bind initial port alias\n");
453 exit(2);
454 }
455
456 debug_log("[fs-ext4] Port bound to /srv/strate-fs-ext4/default\n");
457
458 let mut volume_handle = bootstrap_handle;
459 let mut bootstrap_label = String::from("default");
460 if volume_handle == 0 {
461 debug_log("[fs-ext4] Waiting for early bootstrap message...\n");
462 if let Some(info) = try_wait_for_bootstrap(port_handle, 2048) {
463 let msg = format!(
464 "[fs-ext4] Received bootstrap handle: {} label: {}\n",
465 info.handle, info.label
466 );
467 debug_log(&msg);
468 volume_handle = info.handle;
469 bootstrap_label = info.label;
470 } else if let Some(h) = discover_volume_handle_local() {
471 debug_log("[fs-ext4] Bootstrap message timeout, using local discovery fallback\n");
472 volume_handle = h;
473 } else {
474 debug_log("[fs-ext4] Bootstrap message timeout, switching to blocking wait\n");
475 let info = wait_for_bootstrap(port_handle);
476 volume_handle = info.handle;
477 bootstrap_label = info.label;
478 }
479 }
480 {
481 let msg = format!(
482 "[fs-ext4] Volume handle ready: {} label: {}\n",
483 volume_handle, bootstrap_label
484 );
485 debug_log(&msg);
486 }
487 bind_srv_alias(port_handle, &bootstrap_label);
488
489 let mut attempts: u64 = 0;
491 loop {
492 attempts = attempts.wrapping_add(1);
493 if attempts <= 4 || attempts % 16 == 0 {
494 let msg = format!(
495 "[fs-ext4] Mount loop attempt={} handle={} label={}\n",
496 attempts, volume_handle, bootstrap_label
497 );
498 debug_log(&msg);
499 }
500 match validate_volume_handle(volume_handle) {
501 Ok(sectors) => {
502 let msg = format!(
503 "[fs-ext4] volume probe OK: handle={} sectors={} (attempt={})\n",
504 volume_handle, sectors, attempts
505 );
506 debug_log(&msg);
507 }
508 Err(err) => {
509 log_sys_err("volume probe failed", err);
510 if bootstrap_handle == 0 && attempts % 8 == 0 {
512 debug_log("[fs-ext4] Waiting for refreshed bootstrap handle...\n");
513 let info = wait_for_bootstrap(port_handle);
514 volume_handle = info.handle;
515 bootstrap_label = info.label;
516 let msg = format!(
517 "[fs-ext4] Refreshed volume handle: {} label: {}\n",
518 volume_handle, bootstrap_label
519 );
520 debug_log(&msg);
521 }
522 for _ in 0..2048 {
523 let _ = call::sched_yield();
524 }
525 continue;
526 }
527 }
528
529 let device = match VolumeBlockDevice::new(volume_handle) {
530 Ok(dev) => alloc::sync::Arc::new(dev),
531 Err(e) => {
532 let msg = format!(
533 "[fs-ext4] Failed to init volume device (attempt={}): {:?}\n",
534 attempts, e
535 );
536 debug_log(&msg);
537 for _ in 0..2048 {
538 let _ = call::sched_yield();
539 }
540 continue;
541 }
542 };
543
544 let msg = format!(
545 "[fs-ext4] Block device ready: handle={} (attempt={})\n",
546 volume_handle, attempts
547 );
548 debug_log(&msg);
549
550 let fs = match Ext4FileSystem::mount(device) {
551 Ok(fs) => fs,
552 Err(e) => {
553 let msg = format!(
554 "[fs-ext4] Failed to mount EXT4 filesystem (attempt={}): {:?}\n",
555 attempts, e
556 );
557 debug_log(&msg);
558 for _ in 0..2048 {
559 let _ = call::sched_yield();
560 }
561 continue;
562 }
563 };
564
565 debug_log("[fs-ext4] EXT4 mounted successfully\n");
566 debug_log("[fs-ext4] Strate ready, waiting for requests...\n");
567
568 let mut strate = Ext4Strate::new(fs);
570 strate.serve(port_handle);
571 }
572}
573
574#[panic_handler]
575fn panic(_info: &PanicInfo) -> ! {
577 debug_log("[fs-ext4] PANIC!\n");
578 exit(255);
579}