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 => {
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
147// ---------------------------------------------------------------------------
148// Command parsing helpers
149// ---------------------------------------------------------------------------
150
151/// Implements trim.
152fn 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
165/// Implements split first word.
166fn 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
181/// Parses usize.
182fn 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// ---------------------------------------------------------------------------
198// Commands
199// ---------------------------------------------------------------------------
200
201#[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    /// Implements user default.
222    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
245/// Implements cmd help.
246fn 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
261/// Implements cmd pid.
262fn 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
277/// Implements cmd silo create.
278fn 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
303/// Implements cmd silo start.
304fn 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
323/// Implements cmd silo stop.
324fn 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
343/// Implements cmd silo kill.
344fn 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
363/// Load an ELF from initfs into a kernel module (blob mode).
364fn 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    // Read into a static scratch buffer (128KB max).
387    // Uses a raw pointer to avoid creating references to the mutable static.
388    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
435/// Load ELF + attach to new silo in one step.
436fn 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    // Load the module first
443    cmd_mod_load(path);
444    // TODO: auto-attach once we store the last module handle
445    write_str("Use 'silo-create' + 'silo-attach <silo> <mod>' to wire it up.\n");
446}
447
448/// Implements cmd silo attach.
449fn 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
471/// Implements cmd silos.
472fn cmd_silos() {
473    // TODO: implement silo listing via a query syscall or /proc/silos
474    write_str("Silo listing not yet implemented.\n");
475    write_str("(Requires a silo_list or silo_query syscall.)\n");
476}
477
478// ---------------------------------------------------------------------------
479// Main loop
480// ---------------------------------------------------------------------------
481
482/// Implements dispatch.
483fn 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
514/// Implements prompt.
515fn prompt() {
516    write_str("admin> ");
517}
518
519#[panic_handler]
520/// Implements panic.
521fn panic(_info: &PanicInfo) -> ! {
522    write_str("[console-admin] PANIC!\n");
523    call::exit(255)
524}
525
526#[no_mangle]
527/// Implements start.
528pub 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    // TODO: replace with IPC event loop once IPC channels are wired up.
536    // For now, idle so we don't steal keyboard input from the chevron shell.
537    loop {
538        let _ = call::sched_yield();
539    }
540}