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