Skip to main content

console_admin/
main.rs

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
11// ---------------------------------------------------------------------------
12// I/O helpers
13// ---------------------------------------------------------------------------
14
15/// Writes str.
16fn write_str(msg: &str) {
17    let _ = call::write(1, msg.as_bytes());
18}
19
20/// Writes u64.
21fn 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)]
38/// Writes hex.
39fn 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
55// ---------------------------------------------------------------------------
56// Line buffer (fixed size, no heap)
57// ---------------------------------------------------------------------------
58
59const LINE_BUF_SIZE: usize = 256;
60
61struct LineBuf {
62    buf: [u8; LINE_BUF_SIZE],
63    len: usize,
64}
65
66impl LineBuf {
67    /// Creates a new instance.
68    const fn new() -> Self {
69        LineBuf {
70            buf: [0u8; LINE_BUF_SIZE],
71            len: 0,
72        }
73    }
74
75    /// Implements clear.
76    fn clear(&mut self) {
77        self.len = 0;
78    }
79
80    /// Implements push.
81    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    /// Implements pop.
92    fn pop(&mut self) -> bool {
93        if self.len > 0 {
94            self.len -= 1;
95            true
96        } else {
97            false
98        }
99    }
100
101    /// Returns this value as str.
102    fn as_str(&self) -> &str {
103        unsafe { core::str::from_utf8_unchecked(&self.buf[..self.len]) }
104    }
105}
106
107// ---------------------------------------------------------------------------
108// Read one line from stdin (fd 0) with echo
109// ---------------------------------------------------------------------------
110
111/// Reads line.
112fn 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
145// ---------------------------------------------------------------------------
146// Command parsing helpers
147// ---------------------------------------------------------------------------
148
149/// Implements trim.
150fn 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
163/// Implements split first word.
164fn 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
179/// Parses usize.
180fn 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// ---------------------------------------------------------------------------
196// Commands
197// ---------------------------------------------------------------------------
198
199#[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    /// Implements user default.
220    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
243/// Implements cmd help.
244fn 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
259/// Implements cmd pid.
260fn 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
275/// Implements cmd silo create.
276fn 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
301/// Implements cmd silo start.
302fn 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
321/// Implements cmd silo stop.
322fn 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
341/// Implements cmd silo kill.
342fn 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
361/// Load an ELF from initfs into a kernel module (blob mode).
362fn 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    // Read into a static scratch buffer (128KB max).
385    // Uses a raw pointer to avoid creating references to the mutable static.
386    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
433/// Load ELF + attach to new silo in one step.
434fn 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    // Load the module first
441    cmd_mod_load(path);
442    // TODO: auto-attach once we store the last module handle
443    write_str("Use 'silo-create' + 'silo-attach <silo> <mod>' to wire it up.\n");
444}
445
446/// Implements cmd silo attach.
447fn 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
469/// Implements cmd silos.
470fn cmd_silos() {
471    // TODO: implement silo listing via a query syscall or /proc/silos
472    write_str("Silo listing not yet implemented.\n");
473    write_str("(Requires a silo_list or silo_query syscall.)\n");
474}
475
476// ---------------------------------------------------------------------------
477// Main loop
478// ---------------------------------------------------------------------------
479
480/// Implements dispatch.
481fn 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
512/// Implements prompt.
513fn 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    /// Implements start.
523    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        // TODO: replace with IPC event loop once IPC channels are wired up.
531        // For now, idle so we don't steal keyboard input from the chevron shell.
532        loop {
533            let _ = call::sched_yield();
534        }
535    }
536}