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 => {
130 if line.push(b) {
131 let _ = call::write(1, &byte);
132 }
133 }
134 _ => {}
135 }
136 }
137 Ok(_) => {
138 let _ = call::sched_yield();
139 }
140 Err(_) => {
141 let _ = call::sched_yield();
142 }
143 }
144 }
145}
146
147fn trim(s: &str) -> &str {
153 let bytes = s.as_bytes();
154 let mut start = 0;
155 while start < bytes.len() && bytes[start] == b' ' {
156 start += 1;
157 }
158 let mut end = bytes.len();
159 while end > start && bytes[end - 1] == b' ' {
160 end -= 1;
161 }
162 unsafe { core::str::from_utf8_unchecked(&bytes[start..end]) }
163}
164
165fn split_first_word(s: &str) -> (&str, &str) {
167 let bytes = s.as_bytes();
168 let mut i = 0;
169 while i < bytes.len() && bytes[i] != b' ' {
170 i += 1;
171 }
172 let cmd = unsafe { core::str::from_utf8_unchecked(&bytes[..i]) };
173 let mut rest_start = i;
174 while rest_start < bytes.len() && bytes[rest_start] == b' ' {
175 rest_start += 1;
176 }
177 let rest = unsafe { core::str::from_utf8_unchecked(&bytes[rest_start..]) };
178 (cmd, rest)
179}
180
181fn parse_usize(s: &str) -> Option<usize> {
183 if s.is_empty() {
184 return None;
185 }
186 let bytes = s.as_bytes();
187 let mut value: usize = 0;
188 for &b in bytes {
189 if b < b'0' || b > b'9' {
190 return None;
191 }
192 value = value.checked_mul(10)?.checked_add((b - b'0') as usize)?;
193 }
194 Some(value)
195}
196
197#[repr(C)]
202struct SiloConfig {
203 mem_min: u64,
204 mem_max: u64,
205 cpu_shares: u32,
206 cpu_quota_us: u64,
207 cpu_period_us: u64,
208 cpu_affinity_mask: u64,
209 max_tasks: u32,
210 io_bw_read: u64,
211 io_bw_write: u64,
212 caps_ptr: u64,
213 caps_len: u64,
214 flags: u64,
215 sid: u32,
216 mode: u16,
217 family: u8,
218}
219
220impl SiloConfig {
221 const fn user_default(sid: u32) -> Self {
223 Self {
224 mem_min: 0,
225 mem_max: 0,
226 cpu_shares: 0,
227 cpu_quota_us: 0,
228 cpu_period_us: 0,
229 cpu_affinity_mask: 0,
230 max_tasks: 0,
231 io_bw_read: 0,
232 io_bw_write: 0,
233 caps_ptr: 0,
234 caps_len: 0,
235 flags: 0,
236 sid,
237 mode: 0o000,
238 family: 5,
239 }
240 }
241}
242
243static NEXT_SID: AtomicU32 = AtomicU32::new(1000);
244
245fn cmd_help() {
247 write_str("Available commands:\n");
248 write_str(" help - Show this help\n");
249 write_str(" silos - List known silos\n");
250 write_str(" silo-create - Create a new silo\n");
251 write_str(" silo-load <path> - Load ELF from initfs into a module\n");
252 write_str(" silo-attach <silo> <mod> - Attach a module to a silo\n");
253 write_str(" silo-start <id> - Start a silo\n");
254 write_str(" silo-stop <id> - Stop a silo\n");
255 write_str(" silo-kill <id> - Force-kill a silo\n");
256 write_str(" mod-load <path> - Load a module from initfs path\n");
257 write_str(" pid - Show current PID\n");
258 write_str(" exit - Exit console-admin\n");
259}
260
261fn cmd_pid() {
263 match call::getpid() {
264 Ok(pid) => {
265 write_str("PID: ");
266 write_u64(pid as u64);
267 write_str("\n");
268 }
269 Err(e) => {
270 write_str("getpid failed: ");
271 write_str(e.name());
272 write_str("\n");
273 }
274 }
275}
276
277fn cmd_silo_create() {
279 for _ in 0..32 {
280 let sid = NEXT_SID.fetch_add(1, Ordering::Relaxed);
281 let cfg = SiloConfig::user_default(sid);
282 match call::silo_create((&cfg as *const SiloConfig) as usize) {
283 Ok(handle) => {
284 write_str("Silo created, handle=");
285 write_u64(handle as u64);
286 write_str(" sid=");
287 write_u64(sid as u64);
288 write_str("\n");
289 return;
290 }
291 Err(strat9_syscall::error::Error::AlreadyExists) => continue,
292 Err(e) => {
293 write_str("silo_create failed: ");
294 write_str(e.name());
295 write_str("\n");
296 return;
297 }
298 }
299 }
300 write_str("silo_create failed: no free SID range\n");
301}
302
303fn cmd_silo_start(args: &str) {
305 let id_str = trim(args);
306 match parse_usize(id_str) {
307 Some(id) => match call::silo_start(id) {
308 Ok(_) => {
309 write_str("Silo ");
310 write_u64(id as u64);
311 write_str(" started.\n");
312 }
313 Err(e) => {
314 write_str("silo_start failed: ");
315 write_str(e.name());
316 write_str("\n");
317 }
318 },
319 None => write_str("Usage: silo-start <handle>\n"),
320 }
321}
322
323fn cmd_silo_stop(args: &str) {
325 let id_str = trim(args);
326 match parse_usize(id_str) {
327 Some(id) => match call::silo_stop(id) {
328 Ok(_) => {
329 write_str("Silo ");
330 write_u64(id as u64);
331 write_str(" stopped.\n");
332 }
333 Err(e) => {
334 write_str("silo_stop failed: ");
335 write_str(e.name());
336 write_str("\n");
337 }
338 },
339 None => write_str("Usage: silo-stop <handle>\n"),
340 }
341}
342
343fn cmd_silo_kill(args: &str) {
345 let id_str = trim(args);
346 match parse_usize(id_str) {
347 Some(id) => match call::silo_kill(id) {
348 Ok(_) => {
349 write_str("Silo ");
350 write_u64(id as u64);
351 write_str(" killed.\n");
352 }
353 Err(e) => {
354 write_str("silo_kill failed: ");
355 write_str(e.name());
356 write_str("\n");
357 }
358 },
359 None => write_str("Usage: silo-kill <handle>\n"),
360 }
361}
362
363fn cmd_mod_load(args: &str) {
365 let path = trim(args);
366 if path.is_empty() {
367 write_str("Usage: mod-load <initfs-path>\n");
368 write_str(" e.g.: mod-load /initfs/strate-fs-ramfs\n");
369 return;
370 }
371
372 write_str("Opening ");
373 write_str(path);
374 write_str("...\n");
375
376 let fd = match call::openat(0, path, 0x1, 0) {
377 Ok(fd) => fd,
378 Err(e) => {
379 write_str("open failed: ");
380 write_str(e.name());
381 write_str("\n");
382 return;
383 }
384 };
385
386 const SCRATCH_SIZE: usize = 128 * 1024;
389 static mut SCRATCH: [u8; SCRATCH_SIZE] = [0u8; SCRATCH_SIZE];
390 let scratch_ptr = core::ptr::addr_of_mut!(SCRATCH) as *mut u8;
391 let mut total = 0usize;
392
393 loop {
394 let remaining = SCRATCH_SIZE - total;
395 if remaining == 0 {
396 break;
397 }
398 let chunk_size = if remaining > 4096 { 4096 } else { remaining };
399 let chunk = unsafe { core::slice::from_raw_parts_mut(scratch_ptr.add(total), chunk_size) };
400 match call::read(fd as usize, chunk) {
401 Ok(0) => break,
402 Ok(n) => total += n,
403 Err(e) => {
404 write_str("read failed: ");
405 write_str(e.name());
406 write_str("\n");
407 let _ = call::close(fd as usize);
408 return;
409 }
410 }
411 }
412 let _ = call::close(fd as usize);
413
414 write_str("Read ");
415 write_u64(total as u64);
416 write_str(" bytes. Loading module...\n");
417
418 let result =
419 unsafe { strat9_syscall::syscall2(number::SYS_MODULE_LOAD, scratch_ptr as usize, total) };
420
421 match result {
422 Ok(handle) => {
423 write_str("Module loaded, handle=");
424 write_u64(handle as u64);
425 write_str("\n");
426 }
427 Err(e) => {
428 write_str("module_load failed: ");
429 write_str(e.name());
430 write_str("\n");
431 }
432 }
433}
434
435fn cmd_silo_load(args: &str) {
437 let path = trim(args);
438 if path.is_empty() {
439 write_str("Usage: silo-load <initfs-path>\n");
440 return;
441 }
442 cmd_mod_load(path);
444 write_str("Use 'silo-create' + 'silo-attach <silo> <mod>' to wire it up.\n");
446}
447
448fn cmd_silo_attach(args: &str) {
450 let (silo_str, rest) = split_first_word(args);
451 let (mod_str, _) = split_first_word(rest);
452 match (parse_usize(silo_str), parse_usize(mod_str)) {
453 (Some(silo_id), Some(mod_id)) => match call::silo_attach_module(silo_id, mod_id) {
454 Ok(_) => {
455 write_str("Module ");
456 write_u64(mod_id as u64);
457 write_str(" attached to silo ");
458 write_u64(silo_id as u64);
459 write_str(".\n");
460 }
461 Err(e) => {
462 write_str("silo_attach_module failed: ");
463 write_str(e.name());
464 write_str("\n");
465 }
466 },
467 _ => write_str("Usage: silo-attach <silo-handle> <module-handle>\n"),
468 }
469}
470
471fn cmd_silos() {
473 write_str("Silo listing not yet implemented.\n");
475 write_str("(Requires a silo_list or silo_query syscall.)\n");
476}
477
478fn dispatch(line: &str) {
484 let input = trim(line);
485 if input.is_empty() {
486 return;
487 }
488
489 let (cmd, args) = split_first_word(input);
490
491 match cmd {
492 "help" | "?" => cmd_help(),
493 "pid" => cmd_pid(),
494 "silos" | "silo-list" => cmd_silos(),
495 "silo-create" => cmd_silo_create(),
496 "silo-load" => cmd_silo_load(args),
497 "silo-attach" => cmd_silo_attach(args),
498 "silo-start" => cmd_silo_start(args),
499 "silo-stop" => cmd_silo_stop(args),
500 "silo-kill" => cmd_silo_kill(args),
501 "mod-load" => cmd_mod_load(args),
502 "exit" | "quit" => {
503 write_str("Exiting console-admin.\n");
504 call::exit(0);
505 }
506 _ => {
507 write_str("Unknown command: ");
508 write_str(cmd);
509 write_str("\nType 'help' for available commands.\n");
510 }
511 }
512}
513
514fn prompt() {
516 write_str("admin> ");
517}
518
519#[panic_handler]
520fn panic(_info: &PanicInfo) -> ! {
522 write_str("[console-admin] PANIC!\n");
523 call::exit(255)
524}
525
526#[no_mangle]
527pub extern "C" fn _start() -> ! {
529 write_str("\n");
530 write_str("============================================================\n");
531 write_str("[console-admin] strat9-os: silo console admin\n");
532 write_str("[console-admin] Ready (IPC mode — use chevron shell to interact).\n");
533 write_str("============================================================\n");
534
535 loop {
538 let _ = call::sched_yield();
539 }
540}