Volt VMM (Neutron Stardust): source-available under AGPSL v5.0

KVM-based microVMM for the Volt platform:
- Sub-second VM boot times
- Minimal memory footprint
- Landlock LSM + seccomp security
- Virtio device support
- Custom kernel management

Copyright (c) Armored Gates LLC. All rights reserved.
Licensed under AGPSL v5.0
This commit is contained in:
Karl Clinger
2026-03-21 01:04:35 -05:00
commit 40ed108dd5
143 changed files with 50300 additions and 0 deletions

92
rootfs/build-initramfs.sh Executable file
View File

@@ -0,0 +1,92 @@
#!/bin/bash
# Build the Volt custom initramfs (no Alpine, no BusyBox)
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
BINARY="$PROJECT_DIR/target/x86_64-unknown-linux-musl/release/volt-init"
OUTPUT="$SCRIPT_DIR/initramfs.cpio.gz"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
CYAN='\033[0;36m'
NC='\033[0m'
echo -e "${CYAN}=== Building Volt Initramfs ===${NC}"
# Build volt-init if needed
if [ ! -f "$BINARY" ] || [ "$1" = "--rebuild" ]; then
echo -e "${CYAN}Building volt-init...${NC}"
cd "$PROJECT_DIR"
source ~/.cargo/env
RUSTFLAGS="-C target-feature=+crt-static -C relocation-model=static -C target-cpu=x86-64" \
cargo build --release --target x86_64-unknown-linux-musl -p volt-init
fi
if [ ! -f "$BINARY" ]; then
echo -e "${RED}ERROR: volt-init binary not found at $BINARY${NC}"
echo "Run: cd rootfs/volt-init && cargo build --release --target x86_64-unknown-linux-musl"
exit 1
fi
echo -e "${GREEN}Binary: $(ls -lh "$BINARY" | awk '{print $5}')${NC}"
# Create rootfs structure
WORK=$(mktemp -d)
trap "rm -rf $WORK" EXIT
mkdir -p "$WORK"/{bin,dev,proc,sys,etc,tmp,run,var/log}
# Our init binary — the ONLY binary in the entire rootfs
cp "$BINARY" "$WORK/init"
chmod 755 "$WORK/init"
# Create /dev/console node (required for kernel to set up stdin/stdout/stderr)
# console = char device, major 5, minor 1
sudo mknod "$WORK/dev/console" c 5 1
sudo chmod 600 "$WORK/dev/console"
# Create /dev/ttyS0 for serial console
sudo mknod "$WORK/dev/ttyS0" c 4 64
sudo chmod 660 "$WORK/dev/ttyS0"
# Create /dev/null
sudo mknod "$WORK/dev/null" c 1 3
sudo chmod 666 "$WORK/dev/null"
# Minimal /etc
echo "volt-vmm" > "$WORK/etc/hostname"
cat > "$WORK/etc/os-release" << 'EOF'
NAME="Volt"
ID=volt-vmm
VERSION="0.1.0"
PRETTY_NAME="Volt VM (Custom Rust Userspace)"
HOME_URL="https://github.com/volt-vmm/volt-vmm"
EOF
# Build cpio archive (need root to preserve device nodes)
cd "$WORK"
sudo find . -print0 | sudo cpio --null -o -H newc --quiet 2>/dev/null | gzip -9 > "$OUTPUT"
# Report
SIZE=$(stat -c %s "$OUTPUT" 2>/dev/null || stat -f %z "$OUTPUT")
SIZE_KB=$((SIZE / 1024))
echo -e "${GREEN}=== Initramfs Built ===${NC}"
echo -e " Output: $OUTPUT"
echo -e " Size: ${SIZE_KB}KB ($(ls -lh "$OUTPUT" | awk '{print $5}'))"
echo -e " Binary: $(ls -lh "$BINARY" | awk '{print $5}') (static musl)"
echo -e " Contents: $(find . | wc -l) files"
# Check goals
if [ "$SIZE_KB" -lt 500 ]; then
echo -e " ${GREEN}✓ Under 500KB goal${NC}"
else
echo -e " ${RED}✗ Over 500KB goal (${SIZE_KB}KB)${NC}"
fi
echo ""
echo "Test with:"
echo " ./target/release/volt-vmm --kernel kernels/vmlinux --initrd rootfs/initramfs.cpio.gz -m 128M --cmdline \"console=ttyS0 reboot=k panic=1\""

View File

@@ -0,0 +1,11 @@
[package]
name = "volt-init"
version.workspace = true
edition.workspace = true
authors.workspace = true
license.workspace = true
description = "Minimal PID 1 init process for Volt VMs"
# No external dependencies — pure Rust + libc syscalls
[dependencies]
libc = "0.2"

View File

@@ -0,0 +1,158 @@
// volt-init: Minimal PID 1 for Volt VMs
// No BusyBox, no Alpine, no external binaries. Pure Rust.
mod mount;
mod net;
mod shell;
mod sys;
use std::ffi::CString;
use std::io::Write;
/// Write a message to /dev/kmsg (kernel log buffer)
/// This works even when stdout isn't connected.
#[allow(dead_code)]
fn klog(msg: &str) {
let path = CString::new("/dev/kmsg").unwrap();
let fd = unsafe { libc::open(path.as_ptr(), libc::O_WRONLY) };
if fd >= 0 {
let formatted = format!("<6>volt-init: {}\n", msg);
let bytes = formatted.as_bytes();
unsafe {
libc::write(fd, bytes.as_ptr() as *const libc::c_void, bytes.len());
libc::close(fd);
}
}
}
/// Direct write to a file descriptor (bypass Rust's I/O layer)
#[allow(dead_code)]
fn write_fd(fd: i32, msg: &str) {
let bytes = msg.as_bytes();
unsafe {
libc::write(fd, bytes.as_ptr() as *const libc::c_void, bytes.len());
}
}
fn main() {
// === PHASE 1: Mount filesystems (no I/O possible yet) ===
mount::mount_essentials();
// === PHASE 2: Set up console I/O ===
sys::setup_console();
// === PHASE 3: Signal handlers ===
sys::install_signal_handlers();
// === PHASE 4: System configuration ===
let cmdline = sys::read_kernel_cmdline();
let hostname = sys::parse_cmdline_value(&cmdline, "hostname")
.unwrap_or_else(|| "volt-vmm".to_string());
sys::set_hostname(&hostname);
// === PHASE 5: Boot banner ===
print_banner(&hostname);
// === PHASE 6: Networking ===
let ip_config = sys::parse_cmdline_value(&cmdline, "ip");
net::configure_network(ip_config.as_deref());
// === PHASE 7: Shell ===
println!("\n[volt-init] Starting shell on console...");
println!("Type 'help' for available commands.\n");
shell::run_shell();
// === PHASE 8: Shutdown ===
println!("[volt-init] Shutting down...");
shutdown();
}
fn print_banner(hostname: &str) {
println!();
println!("╔══════════════════════════════════════╗");
println!("║ === VOLT VM READY === ║");
println!("╚══════════════════════════════════════╝");
println!();
println!("[volt-init] Hostname: {}", hostname);
if let Ok(version) = std::fs::read_to_string("/proc/version") {
let short = version
.split_whitespace()
.take(3)
.collect::<Vec<_>>()
.join(" ");
println!("[volt-init] Kernel: {}", short);
}
if let Ok(uptime) = std::fs::read_to_string("/proc/uptime") {
if let Some(secs) = uptime.split_whitespace().next() {
if let Ok(s) = secs.parse::<f64>() {
println!("[volt-init] Uptime: {:.3}s", s);
}
}
}
if let Ok(meminfo) = std::fs::read_to_string("/proc/meminfo") {
let mut total = 0u64;
let mut free = 0u64;
let mut available = 0u64;
for line in meminfo.lines() {
if let Some(val) = extract_meminfo_kb(line, "MemTotal:") {
total = val;
} else if let Some(val) = extract_meminfo_kb(line, "MemFree:") {
free = val;
} else if let Some(val) = extract_meminfo_kb(line, "MemAvailable:") {
available = val;
}
}
println!(
"[volt-init] Memory: {}MB total, {}MB available, {}MB free",
total / 1024,
available / 1024,
free / 1024
);
}
if let Ok(cpuinfo) = std::fs::read_to_string("/proc/cpuinfo") {
let mut model = None;
let mut count = 0u32;
for line in cpuinfo.lines() {
if line.starts_with("processor") {
count += 1;
}
if model.is_none() && line.starts_with("model name") {
if let Some(val) = line.split(':').nth(1) {
model = Some(val.trim().to_string());
}
}
}
if let Some(m) = model {
println!("[volt-init] CPU: {} x {}", count, m);
} else {
println!("[volt-init] CPU: {} processor(s)", count);
}
}
let _ = std::io::stdout().flush();
}
fn extract_meminfo_kb(line: &str, key: &str) -> Option<u64> {
if line.starts_with(key) {
line[key.len()..]
.trim()
.trim_end_matches("kB")
.trim()
.parse()
.ok()
} else {
None
}
}
fn shutdown() {
unsafe { libc::sync() };
mount::umount_all();
unsafe {
libc::reboot(libc::RB_AUTOBOOT);
}
}

View File

@@ -0,0 +1,93 @@
// Filesystem mounting for PID 1
// ALL functions are panic-free — we cannot panic as PID 1.
use std::ffi::CString;
use std::path::Path;
pub fn mount_essentials() {
// Mount /proc first (needed for everything else)
do_mount("proc", "/proc", "proc", libc::MS_NOSUID | libc::MS_NODEV | libc::MS_NOEXEC, None);
// Mount /sys
do_mount("sysfs", "/sys", "sysfs", libc::MS_NOSUID | libc::MS_NODEV | libc::MS_NOEXEC, None);
// Mount /dev (devtmpfs)
if !do_mount("devtmpfs", "/dev", "devtmpfs", libc::MS_NOSUID, Some("mode=0755")) {
// Fallback: mount tmpfs on /dev and create device nodes manually
do_mount("tmpfs", "/dev", "tmpfs", libc::MS_NOSUID, Some("mode=0755,size=4m"));
create_dev_nodes();
}
// Mount /tmp
do_mount("tmpfs", "/tmp", "tmpfs", libc::MS_NOSUID | libc::MS_NODEV, Some("size=16m"));
}
fn do_mount(source: &str, target: &str, fstype: &str, flags: libc::c_ulong, data: Option<&str>) -> bool {
// Ensure mount target directory exists
if !Path::new(target).exists() {
let _ = std::fs::create_dir_all(target);
}
let c_source = match CString::new(source) {
Ok(s) => s,
Err(_) => return false,
};
let c_target = match CString::new(target) {
Ok(s) => s,
Err(_) => return false,
};
let c_fstype = match CString::new(fstype) {
Ok(s) => s,
Err(_) => return false,
};
let c_data = data.map(|d| CString::new(d).ok()).flatten();
let data_ptr = c_data
.as_ref()
.map(|d| d.as_ptr() as *const libc::c_void)
.unwrap_or(std::ptr::null());
let ret = unsafe {
libc::mount(
c_source.as_ptr(),
c_target.as_ptr(),
c_fstype.as_ptr(),
flags,
data_ptr,
)
};
ret == 0
}
fn create_dev_nodes() {
let devices: &[(&str, libc::mode_t, u32, u32)] = &[
("/dev/null", libc::S_IFCHR | 0o666, 1, 3),
("/dev/zero", libc::S_IFCHR | 0o666, 1, 5),
("/dev/random", libc::S_IFCHR | 0o444, 1, 8),
("/dev/urandom", libc::S_IFCHR | 0o444, 1, 9),
("/dev/tty", libc::S_IFCHR | 0o666, 5, 0),
("/dev/console", libc::S_IFCHR | 0o600, 5, 1),
("/dev/ttyS0", libc::S_IFCHR | 0o660, 4, 64),
];
for &(path, mode, major, minor) in devices {
if let Ok(c_path) = CString::new(path) {
let dev = libc::makedev(major, minor);
unsafe {
libc::mknod(c_path.as_ptr(), mode, dev);
}
}
}
}
pub fn umount_all() {
let targets = ["/tmp", "/dev", "/sys", "/proc"];
for target in &targets {
if let Ok(c_target) = CString::new(*target) {
unsafe {
libc::umount2(c_target.as_ptr(), libc::MNT_DETACH);
}
}
}
}

336
rootfs/volt-init/src/net.rs Normal file
View File

@@ -0,0 +1,336 @@
// Network configuration using raw socket ioctls
// No `ip` command needed — we do it all ourselves.
use std::ffi::CString;
use std::mem;
use std::net::Ipv4Addr;
// ioctl request codes (libc::Ioctl = c_int on musl, c_ulong on glibc)
const SIOCSIFADDR: libc::Ioctl = 0x8916;
const SIOCSIFNETMASK: libc::Ioctl = 0x891C;
const SIOCSIFFLAGS: libc::Ioctl = 0x8914;
const SIOCGIFFLAGS: libc::Ioctl = 0x8913;
const SIOCADDRT: libc::Ioctl = 0x890B;
const SIOCSIFMTU: libc::Ioctl = 0x8922;
// Interface flags
const IFF_UP: libc::c_short = libc::IFF_UP as libc::c_short;
const IFF_RUNNING: libc::c_short = libc::IFF_RUNNING as libc::c_short;
#[repr(C)]
struct Ifreq {
ifr_name: [libc::c_char; libc::IFNAMSIZ],
ifr_ifru: IfreqData,
}
#[repr(C)]
union IfreqData {
ifr_addr: libc::sockaddr,
ifr_flags: libc::c_short,
ifr_mtu: libc::c_int,
_pad: [u8; 24],
}
#[repr(C)]
struct Rtentry {
rt_pad1: libc::c_ulong,
rt_dst: libc::sockaddr,
rt_gateway: libc::sockaddr,
rt_genmask: libc::sockaddr,
rt_flags: libc::c_ushort,
rt_pad2: libc::c_short,
rt_pad3: libc::c_ulong,
rt_pad4: *mut libc::c_void,
rt_metric: libc::c_short,
rt_dev: *mut libc::c_char,
rt_mtu: libc::c_ulong,
rt_window: libc::c_ulong,
rt_irtt: libc::c_ushort,
}
pub fn configure_network(ip_config: Option<&str>) {
// Detect network interfaces
let interfaces = detect_interfaces();
if interfaces.is_empty() {
println!("[volt-init] No network interfaces detected");
return;
}
println!("[volt-init] Network interfaces: {:?}", interfaces);
// Bring up loopback
if interfaces.contains(&"lo".to_string()) {
configure_interface("lo", "127.0.0.1", "255.0.0.0");
}
// Find the primary interface (eth0, ens*, enp*)
let primary = interfaces
.iter()
.find(|i| i.starts_with("eth") || i.starts_with("ens") || i.starts_with("enp"))
.cloned();
if let Some(iface) = primary {
// Parse IP configuration
let (ip, mask, gateway) = parse_ip_config(ip_config);
println!(
"[volt-init] Configuring {} with IP {}/{}",
iface, ip, mask
);
configure_interface(&iface, &ip, &mask);
set_mtu(&iface, 1500);
// Set default route
if let Some(gw) = gateway {
println!("[volt-init] Setting default route via {}", gw);
add_default_route(&gw, &iface);
}
} else {
println!("[volt-init] No primary network interface found");
}
}
fn detect_interfaces() -> Vec<String> {
let mut interfaces = Vec::new();
if let Ok(entries) = std::fs::read_dir("/sys/class/net") {
for entry in entries.flatten() {
if let Some(name) = entry.file_name().to_str() {
interfaces.push(name.to_string());
}
}
}
interfaces.sort();
interfaces
}
fn parse_ip_config(config: Option<&str>) -> (String, String, Option<String>) {
// Kernel cmdline ip= format: ip=<client-ip>:<server-ip>:<gw-ip>:<netmask>:<hostname>:<device>:<autoconf>
// Or simple: ip=172.16.0.2/24 or ip=172.16.0.2::172.16.0.1:255.255.255.0
if let Some(cfg) = config {
// Simple CIDR: ip=172.16.0.2/24
if cfg.contains('/') {
let parts: Vec<&str> = cfg.split('/').collect();
let ip = parts[0].to_string();
let prefix: u32 = parts.get(1).and_then(|p| p.parse().ok()).unwrap_or(24);
let mask = prefix_to_mask(prefix);
// Default gateway: assume .1
let gw = default_gateway_for(&ip);
return (ip, mask, Some(gw));
}
// Kernel format: ip=client:server:gw:mask:hostname:device:autoconf
let parts: Vec<&str> = cfg.split(':').collect();
if parts.len() >= 4 {
let ip = parts[0].to_string();
let gw = if !parts[2].is_empty() {
Some(parts[2].to_string())
} else {
None
};
let mask = if !parts[3].is_empty() {
parts[3].to_string()
} else {
"255.255.255.0".to_string()
};
return (ip, mask, gw);
}
// Bare IP
return (
cfg.to_string(),
"255.255.255.0".to_string(),
Some(default_gateway_for(cfg)),
);
}
// Defaults
(
"172.16.0.2".to_string(),
"255.255.255.0".to_string(),
Some("172.16.0.1".to_string()),
)
}
fn prefix_to_mask(prefix: u32) -> String {
let mask: u32 = if prefix == 0 {
0
} else {
!0u32 << (32 - prefix)
};
format!(
"{}.{}.{}.{}",
(mask >> 24) & 0xFF,
(mask >> 16) & 0xFF,
(mask >> 8) & 0xFF,
mask & 0xFF
)
}
fn default_gateway_for(ip: &str) -> String {
if let Ok(addr) = ip.parse::<Ipv4Addr>() {
let octets = addr.octets();
format!("{}.{}.{}.1", octets[0], octets[1], octets[2])
} else {
"172.16.0.1".to_string()
}
}
fn make_sockaddr_in(ip: &str) -> libc::sockaddr {
let addr: Ipv4Addr = ip.parse().unwrap_or(Ipv4Addr::new(0, 0, 0, 0));
let mut sa: libc::sockaddr_in = unsafe { mem::zeroed() };
sa.sin_family = libc::AF_INET as libc::sa_family_t;
sa.sin_addr.s_addr = u32::from_ne_bytes(addr.octets());
unsafe { mem::transmute(sa) }
}
fn configure_interface(name: &str, ip: &str, mask: &str) {
let sock = unsafe { libc::socket(libc::AF_INET, libc::SOCK_DGRAM, 0) };
if sock < 0 {
eprintln!(
"[volt-init] Failed to create socket: {}",
std::io::Error::last_os_error()
);
return;
}
let mut ifr: Ifreq = unsafe { mem::zeroed() };
let name_bytes = name.as_bytes();
let copy_len = name_bytes.len().min(libc::IFNAMSIZ - 1);
for i in 0..copy_len {
ifr.ifr_name[i] = name_bytes[i] as libc::c_char;
}
// Set IP address
ifr.ifr_ifru.ifr_addr = make_sockaddr_in(ip);
let ret = unsafe { libc::ioctl(sock, SIOCSIFADDR, &ifr) };
if ret < 0 {
eprintln!(
"[volt-init] Failed to set IP on {}: {}",
name,
std::io::Error::last_os_error()
);
}
// Set netmask
ifr.ifr_ifru.ifr_addr = make_sockaddr_in(mask);
let ret = unsafe { libc::ioctl(sock, SIOCSIFNETMASK, &ifr) };
if ret < 0 {
eprintln!(
"[volt-init] Failed to set netmask on {}: {}",
name,
std::io::Error::last_os_error()
);
}
// Get current flags
let ret = unsafe { libc::ioctl(sock, SIOCGIFFLAGS, &ifr) };
if ret < 0 {
eprintln!(
"[volt-init] Failed to get flags for {}: {}",
name,
std::io::Error::last_os_error()
);
}
// Bring interface up
unsafe {
ifr.ifr_ifru.ifr_flags |= IFF_UP | IFF_RUNNING;
}
let ret = unsafe { libc::ioctl(sock, SIOCSIFFLAGS, &ifr) };
if ret < 0 {
eprintln!(
"[volt-init] Failed to bring up {}: {}",
name,
std::io::Error::last_os_error()
);
} else {
println!("[volt-init] Interface {} is UP with IP {}", name, ip);
}
unsafe { libc::close(sock) };
}
fn set_mtu(name: &str, mtu: i32) {
let sock = unsafe { libc::socket(libc::AF_INET, libc::SOCK_DGRAM, 0) };
if sock < 0 {
return;
}
let mut ifr: Ifreq = unsafe { mem::zeroed() };
let name_bytes = name.as_bytes();
let copy_len = name_bytes.len().min(libc::IFNAMSIZ - 1);
for i in 0..copy_len {
ifr.ifr_name[i] = name_bytes[i] as libc::c_char;
}
ifr.ifr_ifru.ifr_mtu = mtu;
let ret = unsafe { libc::ioctl(sock, SIOCSIFMTU, &ifr) };
if ret < 0 {
eprintln!(
"[volt-init] Failed to set MTU on {}: {}",
name,
std::io::Error::last_os_error()
);
}
unsafe { libc::close(sock) };
}
fn add_default_route(gateway: &str, _iface: &str) {
let sock = unsafe { libc::socket(libc::AF_INET, libc::SOCK_DGRAM, 0) };
if sock < 0 {
eprintln!(
"[volt-init] Failed to create socket for routing: {}",
std::io::Error::last_os_error()
);
return;
}
let mut rt: Rtentry = unsafe { mem::zeroed() };
rt.rt_dst = make_sockaddr_in("0.0.0.0");
rt.rt_gateway = make_sockaddr_in(gateway);
rt.rt_genmask = make_sockaddr_in("0.0.0.0");
rt.rt_flags = (libc::RTF_UP | libc::RTF_GATEWAY) as libc::c_ushort;
rt.rt_metric = 100;
// Use interface name
let iface_c = CString::new(_iface).unwrap();
rt.rt_dev = iface_c.as_ptr() as *mut libc::c_char;
let ret = unsafe { libc::ioctl(sock, SIOCADDRT, &rt) };
if ret < 0 {
let err = std::io::Error::last_os_error();
// EEXIST is fine — route might already exist
if err.raw_os_error() != Some(libc::EEXIST) {
eprintln!("[volt-init] Failed to add default route: {}", err);
}
} else {
println!("[volt-init] Default route via {} set", gateway);
}
unsafe { libc::close(sock) };
}
/// Get interface IP address (for `ip` command display)
pub fn get_interface_info() -> Vec<(String, String)> {
let mut result = Vec::new();
if let Ok(entries) = std::fs::read_dir("/sys/class/net") {
for entry in entries.flatten() {
let name = entry.file_name().to_string_lossy().to_string();
// Read operstate
let state_path = format!("/sys/class/net/{}/operstate", name);
let state = std::fs::read_to_string(&state_path)
.unwrap_or_default()
.trim()
.to_string();
// Read address
let addr_path = format!("/sys/class/net/{}/address", name);
let mac = std::fs::read_to_string(&addr_path)
.unwrap_or_default()
.trim()
.to_string();
result.push((name, format!("state={} mac={}", state, mac)));
}
}
result.sort();
result
}

View File

@@ -0,0 +1,445 @@
// Built-in shell for Volt VMs
// All commands are built-in — no external binaries needed.
use std::io::{self, BufRead, Write};
use std::net::Ipv4Addr;
use std::time::Duration;
use crate::net;
pub fn run_shell() {
let stdin = io::stdin();
let mut stdout = io::stdout();
loop {
print!("volt-vmm# ");
let _ = stdout.flush();
let mut line = String::new();
match stdin.lock().read_line(&mut line) {
Ok(0) => {
// EOF
println!();
break;
}
Ok(_) => {}
Err(e) => {
eprintln!("Read error: {}", e);
break;
}
}
let line = line.trim();
if line.is_empty() {
continue;
}
let parts: Vec<&str> = line.split_whitespace().collect();
let cmd = parts[0];
let args = &parts[1..];
match cmd {
"help" => cmd_help(),
"ip" => cmd_ip(),
"ping" => cmd_ping(args),
"cat" => cmd_cat(args),
"ls" => cmd_ls(args),
"echo" => cmd_echo(args),
"uptime" => cmd_uptime(),
"free" => cmd_free(),
"hostname" => cmd_hostname(),
"dmesg" => cmd_dmesg(args),
"env" | "printenv" => cmd_env(),
"uname" => cmd_uname(),
"exit" | "poweroff" | "reboot" | "halt" => {
println!("Shutting down...");
break;
}
_ => {
eprintln!("{}: command not found. Type 'help' for available commands.", cmd);
}
}
}
}
fn cmd_help() {
println!("Volt VM Built-in Shell");
println!("===========================");
println!(" help Show this help");
println!(" ip Show network interfaces");
println!(" ping <host> Ping a host (ICMP echo)");
println!(" cat <file> Display file contents");
println!(" ls [dir] List directory contents");
println!(" echo [text] Print text");
println!(" uptime Show system uptime");
println!(" free Show memory usage");
println!(" hostname Show hostname");
println!(" uname Show system info");
println!(" dmesg [N] Show kernel log (last N lines)");
println!(" env Show environment variables");
println!(" exit Shutdown VM");
}
fn cmd_ip() {
let interfaces = net::get_interface_info();
if interfaces.is_empty() {
println!("No network interfaces found");
return;
}
for (name, info) in interfaces {
println!(" {}: {}", name, info);
}
}
fn cmd_ping(args: &[&str]) {
if args.is_empty() {
eprintln!("Usage: ping <host>");
return;
}
let target = args[0];
// Parse as IPv4 address
let addr: Ipv4Addr = match target.parse() {
Ok(a) => a,
Err(_) => {
// No DNS resolver — only IP addresses
eprintln!("ping: {} — only IP addresses supported (no DNS)", target);
return;
}
};
// Create raw ICMP socket
let sock = unsafe { libc::socket(libc::AF_INET, libc::SOCK_DGRAM, libc::IPPROTO_ICMP) };
if sock < 0 {
eprintln!(
"ping: failed to create ICMP socket: {}",
io::Error::last_os_error()
);
return;
}
// Set timeout
let tv = libc::timeval {
tv_sec: 2,
tv_usec: 0,
};
unsafe {
libc::setsockopt(
sock,
libc::SOL_SOCKET,
libc::SO_RCVTIMEO,
&tv as *const _ as *const libc::c_void,
std::mem::size_of::<libc::timeval>() as libc::socklen_t,
);
}
println!("PING {} — 3 packets", addr);
let mut dest: libc::sockaddr_in = unsafe { std::mem::zeroed() };
dest.sin_family = libc::AF_INET as libc::sa_family_t;
dest.sin_addr.s_addr = u32::from_ne_bytes(addr.octets());
let mut sent = 0u32;
let mut received = 0u32;
for seq in 0..3u16 {
// ICMP echo request packet
let mut packet = [0u8; 64];
packet[0] = 8; // Type: Echo Request
packet[1] = 0; // Code
packet[2] = 0; // Checksum (will fill)
packet[3] = 0;
packet[4] = 0; // ID
packet[5] = 1;
packet[6] = (seq >> 8) as u8; // Sequence
packet[7] = (seq & 0xff) as u8;
// Fill payload with pattern
for i in 8..64 {
packet[i] = (i as u8) & 0xff;
}
// Compute checksum
let cksum = icmp_checksum(&packet);
packet[2] = (cksum >> 8) as u8;
packet[3] = (cksum & 0xff) as u8;
let start = std::time::Instant::now();
let ret = unsafe {
libc::sendto(
sock,
packet.as_ptr() as *const libc::c_void,
packet.len(),
0,
&dest as *const libc::sockaddr_in as *const libc::sockaddr,
std::mem::size_of::<libc::sockaddr_in>() as libc::socklen_t,
)
};
if ret < 0 {
eprintln!("ping: send failed: {}", io::Error::last_os_error());
sent += 1;
continue;
}
sent += 1;
// Receive reply
let mut buf = [0u8; 1024];
let ret = unsafe {
libc::recvfrom(
sock,
buf.as_mut_ptr() as *mut libc::c_void,
buf.len(),
0,
std::ptr::null_mut(),
std::ptr::null_mut(),
)
};
let elapsed = start.elapsed();
if ret > 0 {
received += 1;
println!(
" {} bytes from {}: seq={} time={:.1}ms",
ret,
addr,
seq,
elapsed.as_secs_f64() * 1000.0
);
} else {
println!(" Request timeout for seq={}", seq);
}
if seq < 2 {
std::thread::sleep(Duration::from_secs(1));
}
}
unsafe { libc::close(sock) };
let loss = if sent > 0 {
((sent - received) as f64 / sent as f64) * 100.0
} else {
100.0
};
println!(
"--- {} ping statistics ---\n{} transmitted, {} received, {:.0}% loss",
addr, sent, received, loss
);
}
fn icmp_checksum(data: &[u8]) -> u16 {
let mut sum: u32 = 0;
let mut i = 0;
while i + 1 < data.len() {
sum += ((data[i] as u32) << 8) | (data[i + 1] as u32);
i += 2;
}
if i < data.len() {
sum += (data[i] as u32) << 8;
}
while (sum >> 16) != 0 {
sum = (sum & 0xFFFF) + (sum >> 16);
}
!sum as u16
}
fn cmd_cat(args: &[&str]) {
if args.is_empty() {
eprintln!("Usage: cat <file>");
return;
}
for path in args {
match std::fs::read_to_string(path) {
Ok(contents) => print!("{}", contents),
Err(e) => eprintln!("cat: {}: {}", path, e),
}
}
}
fn cmd_ls(args: &[&str]) {
let dir = if args.is_empty() { "." } else { args[0] };
match std::fs::read_dir(dir) {
Ok(entries) => {
let mut names: Vec<String> = entries
.filter_map(|e| e.ok())
.map(|e| {
let name = e.file_name().to_string_lossy().to_string();
let meta = e.metadata().ok();
if let Some(m) = meta {
if m.is_dir() {
format!("{}/ ", name)
} else {
let size = m.len();
format!("{} ({}) ", name, human_size(size))
}
} else {
format!("{} ", name)
}
})
.collect();
names.sort();
for name in &names {
println!(" {}", name);
}
}
Err(e) => eprintln!("ls: {}: {}", dir, e),
}
}
fn human_size(bytes: u64) -> String {
if bytes >= 1024 * 1024 * 1024 {
format!("{:.1}G", bytes as f64 / (1024.0 * 1024.0 * 1024.0))
} else if bytes >= 1024 * 1024 {
format!("{:.1}M", bytes as f64 / (1024.0 * 1024.0))
} else if bytes >= 1024 {
format!("{:.1}K", bytes as f64 / 1024.0)
} else {
format!("{}B", bytes)
}
}
fn cmd_echo(args: &[&str]) {
println!("{}", args.join(" "));
}
fn cmd_uptime() {
if let Ok(uptime) = std::fs::read_to_string("/proc/uptime") {
if let Some(secs) = uptime.split_whitespace().next() {
if let Ok(s) = secs.parse::<f64>() {
let hours = (s / 3600.0) as u64;
let mins = ((s % 3600.0) / 60.0) as u64;
let secs_remaining = s % 60.0;
if hours > 0 {
println!("up {}h {}m {:.0}s", hours, mins, secs_remaining);
} else if mins > 0 {
println!("up {}m {:.0}s", mins, secs_remaining);
} else {
println!("up {:.2}s", s);
}
}
}
} else {
eprintln!("uptime: cannot read /proc/uptime");
}
}
fn cmd_free() {
if let Ok(meminfo) = std::fs::read_to_string("/proc/meminfo") {
println!(
"{:<16} {:>12} {:>12} {:>12}",
"", "total", "used", "free"
);
let mut total = 0u64;
let mut free = 0u64;
let mut available = 0u64;
let mut buffers = 0u64;
let mut cached = 0u64;
let mut swap_total = 0u64;
let mut swap_free = 0u64;
for line in meminfo.lines() {
if let Some(v) = extract_kb(line, "MemTotal:") {
total = v;
} else if let Some(v) = extract_kb(line, "MemFree:") {
free = v;
} else if let Some(v) = extract_kb(line, "MemAvailable:") {
available = v;
} else if let Some(v) = extract_kb(line, "Buffers:") {
buffers = v;
} else if let Some(v) = extract_kb(line, "Cached:") {
cached = v;
} else if let Some(v) = extract_kb(line, "SwapTotal:") {
swap_total = v;
} else if let Some(v) = extract_kb(line, "SwapFree:") {
swap_free = v;
}
}
let used = total.saturating_sub(free).saturating_sub(buffers).saturating_sub(cached);
println!(
"{:<16} {:>10}K {:>10}K {:>10}K",
"Mem:", total, used, free
);
if available > 0 {
println!("Available: {:>10}K", available);
}
if swap_total > 0 {
println!(
"{:<16} {:>10}K {:>10}K {:>10}K",
"Swap:",
swap_total,
swap_total - swap_free,
swap_free
);
}
} else {
eprintln!("free: cannot read /proc/meminfo");
}
}
fn extract_kb(line: &str, key: &str) -> Option<u64> {
if line.starts_with(key) {
line[key.len()..]
.trim()
.trim_end_matches("kB")
.trim()
.parse()
.ok()
} else {
None
}
}
fn cmd_hostname() {
if let Ok(name) = std::fs::read_to_string("/etc/hostname") {
println!("{}", name.trim());
} else {
println!("volt-vmm");
}
}
fn cmd_dmesg(args: &[&str]) {
let limit: usize = args
.first()
.and_then(|a| a.parse().ok())
.unwrap_or(20);
match std::fs::read_to_string("/dev/kmsg") {
Ok(content) => {
let lines: Vec<&str> = content.lines().collect();
let start = lines.len().saturating_sub(limit);
for line in &lines[start..] {
// kmsg format: priority,sequence,timestamp;message
if let Some(msg) = line.split(';').nth(1) {
println!("{}", msg);
} else {
println!("{}", line);
}
}
}
Err(_) => {
// Fall back to /proc/kmsg or printk buffer via syslog
eprintln!("dmesg: kernel log not available");
}
}
}
fn cmd_env() {
for (key, value) in std::env::vars() {
println!("{}={}", key, value);
}
}
fn cmd_uname() {
if let Ok(version) = std::fs::read_to_string("/proc/version") {
println!("{}", version.trim());
} else {
println!("Volt VM");
}
}

109
rootfs/volt-init/src/sys.rs Normal file
View File

@@ -0,0 +1,109 @@
// System utilities: signal handling, hostname, kernel cmdline, console
use std::ffi::CString;
/// Set up console I/O by ensuring fd 0/1/2 point to /dev/console or /dev/ttyS0
pub fn setup_console() {
// Try /dev/console first, then /dev/ttyS0
let consoles = ["/dev/console", "/dev/ttyS0"];
for console in &consoles {
let c_path = CString::new(*console).unwrap();
let fd = unsafe { libc::open(c_path.as_ptr(), libc::O_RDWR | libc::O_NOCTTY | libc::O_NONBLOCK) };
if fd >= 0 {
// Clear O_NONBLOCK now that the open succeeded
unsafe {
let flags = libc::fcntl(fd, libc::F_GETFL);
if flags >= 0 {
libc::fcntl(fd, libc::F_SETFL, flags & !libc::O_NONBLOCK);
}
}
// Close existing fds and dup console to 0, 1, 2
if fd != 0 {
unsafe {
libc::close(0);
libc::dup2(fd, 0);
}
}
unsafe {
libc::close(1);
libc::dup2(fd, 1);
libc::close(2);
libc::dup2(fd, 2);
}
if fd > 2 {
unsafe {
libc::close(fd);
}
}
// Make this our controlling terminal
unsafe {
libc::ioctl(0, libc::TIOCSCTTY as libc::Ioctl, 1);
}
return;
}
}
// If we get here, no console device available — output will be lost
}
/// Install signal handlers for PID 1
pub fn install_signal_handlers() {
unsafe {
// SIGCHLD: reap zombies
libc::signal(
libc::SIGCHLD,
sigchld_handler as *const () as libc::sighandler_t,
);
// SIGTERM: ignore (PID 1 handles shutdown via shell)
libc::signal(libc::SIGTERM, libc::SIG_IGN);
// SIGINT: ignore (Ctrl+C shouldn't kill init)
libc::signal(libc::SIGINT, libc::SIG_IGN);
}
}
extern "C" fn sigchld_handler(_sig: libc::c_int) {
// Reap all zombie children
unsafe {
loop {
let ret = libc::waitpid(-1, std::ptr::null_mut(), libc::WNOHANG);
if ret <= 0 {
break;
}
}
}
}
/// Read kernel command line
pub fn read_kernel_cmdline() -> String {
std::fs::read_to_string("/proc/cmdline")
.unwrap_or_default()
.trim()
.to_string()
}
/// Parse a key=value from kernel cmdline
pub fn parse_cmdline_value(cmdline: &str, key: &str) -> Option<String> {
let prefix = format!("{}=", key);
for param in cmdline.split_whitespace() {
if let Some(value) = param.strip_prefix(&prefix) {
return Some(value.to_string());
}
}
None
}
/// Set system hostname
pub fn set_hostname(name: &str) {
let c_name = CString::new(name).unwrap();
let ret = unsafe { libc::sethostname(c_name.as_ptr(), name.len()) };
if ret != 0 {
eprintln!(
"[volt-init] Failed to set hostname: {}",
std::io::Error::last_os_error()
);
}
}