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            Ok(_) => {
137                let _ = call::sched_yield();
138            }
139            Err(_) => {
140                let _ = call::sched_yield();
141            }
142        }
143    }
144}
145
146// ---------------------------------------------------------------------------
147// Command parsing helpers
148// ---------------------------------------------------------------------------
149
150/// Implements trim.
151fn 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
164/// Implements split first word.
165fn 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
180/// Parses usize.
181fn 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// ---------------------------------------------------------------------------
197// Commands
198// ---------------------------------------------------------------------------
199
200#[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    /// Implements user default.
221    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
244/// Implements cmd help.
245fn 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
260/// Implements cmd pid.
261fn 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
276/// Implements cmd silo create.
277fn 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
302/// Implements cmd silo start.
303fn 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
322/// Implements cmd silo stop.
323fn 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
342/// Implements cmd silo kill.
343fn 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
362/// Load an ELF from initfs into a kernel module (blob mode).
363fn 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    // Read into a static scratch buffer (128KB max).
386    // Uses a raw pointer to avoid creating references to the mutable static.
387    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
434/// Load ELF + attach to new silo in one step.
435fn 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    // Load the module first
442    cmd_mod_load(path);
443    // TODO: auto-attach once we store the last module handle
444    write_str("Use 'silo-create' + 'silo-attach <silo> <mod>' to wire it up.\n");
445}
446
447/// Implements cmd silo attach.
448fn 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
470/// Implements cmd silos.
471fn cmd_silos() {
472    // TODO: implement silo listing via a query syscall or /proc/silos
473    write_str("Silo listing not yet implemented.\n");
474    write_str("(Requires a silo_list or silo_query syscall.)\n");
475}
476
477// ---------------------------------------------------------------------------
478// Main loop
479// ---------------------------------------------------------------------------
480
481/// Implements dispatch.
482fn 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
513/// Implements prompt.
514fn 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    /// Implements start.
524    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        // TODO: replace with IPC event loop once IPC channels are wired up.
532        // For now, idle so we don't steal keyboard input from the chevron shell.
533        loop {
534            let _ = call::sched_yield();
535        }
536    }
537}