1#![no_std]
2#![no_main]
3#![allow(dead_code)]
4
5use core::{
6 panic::PanicInfo,
7 sync::atomic::{AtomicU32, Ordering},
8};
9use strat9_syscall::{call, number};
10
11fn write_str(msg: &str) {
17 let _ = call::write(1, msg.as_bytes());
18}
19
20fn write_u64(mut value: u64) {
22 let mut buf = [0u8; 21];
23 if value == 0 {
24 write_str("0");
25 return;
26 }
27 let mut i = buf.len();
28 while value > 0 {
29 i -= 1;
30 buf[i] = b'0' + (value % 10) as u8;
31 value /= 10;
32 }
33 let s = unsafe { core::str::from_utf8_unchecked(&buf[i..]) };
34 write_str(s);
35}
36
37#[allow(dead_code)]
38fn write_hex(mut value: u64) {
40 let mut buf = [0u8; 16];
41 for i in (0..16).rev() {
42 let nibble = (value & 0xF) as u8;
43 buf[i] = if nibble < 10 {
44 b'0' + nibble
45 } else {
46 b'a' + (nibble - 10)
47 };
48 value >>= 4;
49 }
50 write_str("0x");
51 let s = unsafe { core::str::from_utf8_unchecked(&buf) };
52 write_str(s);
53}
54
55const LINE_BUF_SIZE: usize = 256;
60
61struct LineBuf {
62 buf: [u8; LINE_BUF_SIZE],
63 len: usize,
64}
65
66impl LineBuf {
67 const fn new() -> Self {
69 LineBuf {
70 buf: [0u8; LINE_BUF_SIZE],
71 len: 0,
72 }
73 }
74
75 fn clear(&mut self) {
77 self.len = 0;
78 }
79
80 fn push(&mut self, b: u8) -> bool {
82 if self.len < LINE_BUF_SIZE {
83 self.buf[self.len] = b;
84 self.len += 1;
85 true
86 } else {
87 false
88 }
89 }
90
91 fn pop(&mut self) -> bool {
93 if self.len > 0 {
94 self.len -= 1;
95 true
96 } else {
97 false
98 }
99 }
100
101 fn as_str(&self) -> &str {
103 unsafe { core::str::from_utf8_unchecked(&self.buf[..self.len]) }
104 }
105}
106
107fn read_line(line: &mut LineBuf) {
113 line.clear();
114 let mut byte = [0u8; 1];
115 loop {
116 match call::read(0, &mut byte) {
117 Ok(1) => {
118 let b = byte[0];
119 match b {
120 b'\n' | b'\r' => {
121 write_str("\n");
122 return;
123 }
124 0x7F | 0x08 => {
125 if line.pop() {
126 write_str("\x08 \x08");
127 }
128 }
129 0x20..=0x7E if line.push(b) => {
130 let _ = call::write(1, &byte);
131 }
132 _ => {}
133 }
134 }
135 Ok(_) => {
136 let _ = call::sched_yield();
137 }
138 Err(_) => {
139 let _ = call::sched_yield();
140 }
141 }
142 }
143}
144
145fn trim(s: &str) -> &str {
151 let bytes = s.as_bytes();
152 let mut start = 0;
153 while start < bytes.len() && bytes[start] == b' ' {
154 start += 1;
155 }
156 let mut end = bytes.len();
157 while end > start && bytes[end - 1] == b' ' {
158 end -= 1;
159 }
160 unsafe { core::str::from_utf8_unchecked(&bytes[start..end]) }
161}
162
163fn split_first_word(s: &str) -> (&str, &str) {
165 let bytes = s.as_bytes();
166 let mut i = 0;
167 while i < bytes.len() && bytes[i] != b' ' {
168 i += 1;
169 }
170 let cmd = unsafe { core::str::from_utf8_unchecked(&bytes[..i]) };
171 let mut rest_start = i;
172 while rest_start < bytes.len() && bytes[rest_start] == b' ' {
173 rest_start += 1;
174 }
175 let rest = unsafe { core::str::from_utf8_unchecked(&bytes[rest_start..]) };
176 (cmd, rest)
177}
178
179fn parse_usize(s: &str) -> Option<usize> {
181 if s.is_empty() {
182 return None;
183 }
184 let bytes = s.as_bytes();
185 let mut value: usize = 0;
186 for &b in bytes {
187 if !(b'0'..=b'9').contains(&b) {
188 return None;
189 }
190 value = value.checked_mul(10)?.checked_add((b - b'0') as usize)?;
191 }
192 Some(value)
193}
194
195#[repr(C)]
200struct SiloConfig {
201 mem_min: u64,
202 mem_max: u64,
203 cpu_shares: u32,
204 cpu_quota_us: u64,
205 cpu_period_us: u64,
206 cpu_affinity_mask: u64,
207 max_tasks: u32,
208 io_bw_read: u64,
209 io_bw_write: u64,
210 caps_ptr: u64,
211 caps_len: u64,
212 flags: u64,
213 sid: u32,
214 mode: u16,
215 family: u8,
216}
217
218impl SiloConfig {
219 const fn user_default(sid: u32) -> Self {
221 Self {
222 mem_min: 0,
223 mem_max: 0,
224 cpu_shares: 0,
225 cpu_quota_us: 0,
226 cpu_period_us: 0,
227 cpu_affinity_mask: 0,
228 max_tasks: 0,
229 io_bw_read: 0,
230 io_bw_write: 0,
231 caps_ptr: 0,
232 caps_len: 0,
233 flags: 0,
234 sid,
235 mode: 0o000,
236 family: 5,
237 }
238 }
239}
240
241static NEXT_SID: AtomicU32 = AtomicU32::new(1000);
242
243fn cmd_help() {
245 write_str("Available commands:\n");
246 write_str(" help - Show this help\n");
247 write_str(" silos - List known silos\n");
248 write_str(" silo-create - Create a new silo\n");
249 write_str(" silo-load <path> - Load ELF from initfs into a module\n");
250 write_str(" silo-attach <silo> <mod> - Attach a module to a silo\n");
251 write_str(" silo-start <id> - Start a silo\n");
252 write_str(" silo-stop <id> - Stop a silo\n");
253 write_str(" silo-kill <id> - Force-kill a silo\n");
254 write_str(" mod-load <path> - Load a module from initfs path\n");
255 write_str(" pid - Show current PID\n");
256 write_str(" exit - Exit console-admin\n");
257}
258
259fn cmd_pid() {
261 match call::getpid() {
262 Ok(pid) => {
263 write_str("PID: ");
264 write_u64(pid as u64);
265 write_str("\n");
266 }
267 Err(e) => {
268 write_str("getpid failed: ");
269 write_str(e.name());
270 write_str("\n");
271 }
272 }
273}
274
275fn cmd_silo_create() {
277 for _ in 0..32 {
278 let sid = NEXT_SID.fetch_add(1, Ordering::Relaxed);
279 let cfg = SiloConfig::user_default(sid);
280 match call::silo_create((&cfg as *const SiloConfig) as usize) {
281 Ok(handle) => {
282 write_str("Silo created, handle=");
283 write_u64(handle as u64);
284 write_str(" sid=");
285 write_u64(sid as u64);
286 write_str("\n");
287 return;
288 }
289 Err(strat9_syscall::error::Error::AlreadyExists) => continue,
290 Err(e) => {
291 write_str("silo_create failed: ");
292 write_str(e.name());
293 write_str("\n");
294 return;
295 }
296 }
297 }
298 write_str("silo_create failed: no free SID range\n");
299}
300
301fn cmd_silo_start(args: &str) {
303 let id_str = trim(args);
304 match parse_usize(id_str) {
305 Some(id) => match call::silo_start(id) {
306 Ok(_) => {
307 write_str("Silo ");
308 write_u64(id as u64);
309 write_str(" started.\n");
310 }
311 Err(e) => {
312 write_str("silo_start failed: ");
313 write_str(e.name());
314 write_str("\n");
315 }
316 },
317 None => write_str("Usage: silo-start <handle>\n"),
318 }
319}
320
321fn cmd_silo_stop(args: &str) {
323 let id_str = trim(args);
324 match parse_usize(id_str) {
325 Some(id) => match call::silo_stop(id) {
326 Ok(_) => {
327 write_str("Silo ");
328 write_u64(id as u64);
329 write_str(" stopped.\n");
330 }
331 Err(e) => {
332 write_str("silo_stop failed: ");
333 write_str(e.name());
334 write_str("\n");
335 }
336 },
337 None => write_str("Usage: silo-stop <handle>\n"),
338 }
339}
340
341fn cmd_silo_kill(args: &str) {
343 let id_str = trim(args);
344 match parse_usize(id_str) {
345 Some(id) => match call::silo_kill(id) {
346 Ok(_) => {
347 write_str("Silo ");
348 write_u64(id as u64);
349 write_str(" killed.\n");
350 }
351 Err(e) => {
352 write_str("silo_kill failed: ");
353 write_str(e.name());
354 write_str("\n");
355 }
356 },
357 None => write_str("Usage: silo-kill <handle>\n"),
358 }
359}
360
361fn cmd_mod_load(args: &str) {
363 let path = trim(args);
364 if path.is_empty() {
365 write_str("Usage: mod-load <initfs-path>\n");
366 write_str(" e.g.: mod-load /initfs/strate-fs-ramfs\n");
367 return;
368 }
369
370 write_str("Opening ");
371 write_str(path);
372 write_str("...\n");
373
374 let fd = match call::openat(0, path, 0x1, 0) {
375 Ok(fd) => fd,
376 Err(e) => {
377 write_str("open failed: ");
378 write_str(e.name());
379 write_str("\n");
380 return;
381 }
382 };
383
384 const SCRATCH_SIZE: usize = 128 * 1024;
387 static mut SCRATCH: [u8; SCRATCH_SIZE] = [0u8; SCRATCH_SIZE];
388 let scratch_ptr = core::ptr::addr_of_mut!(SCRATCH) as *mut u8;
389 let mut total = 0usize;
390
391 loop {
392 let remaining = SCRATCH_SIZE - total;
393 if remaining == 0 {
394 break;
395 }
396 let chunk_size = if remaining > 4096 { 4096 } else { remaining };
397 let chunk = unsafe { core::slice::from_raw_parts_mut(scratch_ptr.add(total), chunk_size) };
398 match call::read(fd as usize, chunk) {
399 Ok(0) => break,
400 Ok(n) => total += n,
401 Err(e) => {
402 write_str("read failed: ");
403 write_str(e.name());
404 write_str("\n");
405 let _ = call::close(fd as usize);
406 return;
407 }
408 }
409 }
410 let _ = call::close(fd as usize);
411
412 write_str("Read ");
413 write_u64(total as u64);
414 write_str(" bytes. Loading module...\n");
415
416 let result =
417 unsafe { strat9_syscall::syscall2(number::SYS_MODULE_LOAD, scratch_ptr as usize, total) };
418
419 match result {
420 Ok(handle) => {
421 write_str("Module loaded, handle=");
422 write_u64(handle as u64);
423 write_str("\n");
424 }
425 Err(e) => {
426 write_str("module_load failed: ");
427 write_str(e.name());
428 write_str("\n");
429 }
430 }
431}
432
433fn cmd_silo_load(args: &str) {
435 let path = trim(args);
436 if path.is_empty() {
437 write_str("Usage: silo-load <initfs-path>\n");
438 return;
439 }
440 cmd_mod_load(path);
442 write_str("Use 'silo-create' + 'silo-attach <silo> <mod>' to wire it up.\n");
444}
445
446fn cmd_silo_attach(args: &str) {
448 let (silo_str, rest) = split_first_word(args);
449 let (mod_str, _) = split_first_word(rest);
450 match (parse_usize(silo_str), parse_usize(mod_str)) {
451 (Some(silo_id), Some(mod_id)) => match call::silo_attach_module(silo_id, mod_id) {
452 Ok(_) => {
453 write_str("Module ");
454 write_u64(mod_id as u64);
455 write_str(" attached to silo ");
456 write_u64(silo_id as u64);
457 write_str(".\n");
458 }
459 Err(e) => {
460 write_str("silo_attach_module failed: ");
461 write_str(e.name());
462 write_str("\n");
463 }
464 },
465 _ => write_str("Usage: silo-attach <silo-handle> <module-handle>\n"),
466 }
467}
468
469fn cmd_silos() {
471 write_str("Silo listing not yet implemented.\n");
473 write_str("(Requires a silo_list or silo_query syscall.)\n");
474}
475
476fn dispatch(line: &str) {
482 let input = trim(line);
483 if input.is_empty() {
484 return;
485 }
486
487 let (cmd, args) = split_first_word(input);
488
489 match cmd {
490 "help" | "?" => cmd_help(),
491 "pid" => cmd_pid(),
492 "silos" | "silo-list" => cmd_silos(),
493 "silo-create" => cmd_silo_create(),
494 "silo-load" => cmd_silo_load(args),
495 "silo-attach" => cmd_silo_attach(args),
496 "silo-start" => cmd_silo_start(args),
497 "silo-stop" => cmd_silo_stop(args),
498 "silo-kill" => cmd_silo_kill(args),
499 "mod-load" => cmd_mod_load(args),
500 "exit" | "quit" => {
501 write_str("Exiting console-admin.\n");
502 call::exit(0);
503 }
504 _ => {
505 write_str("Unknown command: ");
506 write_str(cmd);
507 write_str("\nType 'help' for available commands.\n");
508 }
509 }
510}
511
512fn prompt() {
514 write_str("admin> ");
515}
516
517#[panic_handler]
518fn panic(info: &PanicInfo) -> ! {
519 call::handle_panic("console-admin", info);
520
521 #[no_mangle]
522 pub extern "C" fn _start() -> ! {
524 write_str("\n");
525 write_str("============================================================\n");
526 write_str("[console-admin] strat9-os: silo console admin\n");
527 write_str("[console-admin] Ready (IPC mode : use chevron shell to interact).\n");
528 write_str("============================================================\n");
529
530 loop {
533 let _ = call::sched_yield();
534 }
535 }
536}