Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions base/init/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,13 @@ const WAIT4_RETURN_MSG: &[u8] = b"init: wait4-return\n";
/// then falls back to Limine boot modules (basename match).
const HELLO_PATH: &[u8] = b"/boot/userspace_hello.elf\0";

/// Path to the POSIX shell. Resolved via VFS from the ext2 rootfs;
/// only available when booting with `root=/dev/vda`.
const SH_PATH: &[u8] = b"/bin/sh\0";

/// Smoke-test marker — emitted before exec-ing /bin/sh.
const SH_LAUNCH_MSG: &[u8] = b"init: launching /bin/sh\n";

#[no_mangle]
pub extern "C" fn _start() -> ! {
// Pre-write diagnostic marker — see #478. Emitted on fd=2 so it
Expand Down Expand Up @@ -216,6 +223,89 @@ pub extern "C" fn _start() -> ! {
}
}

// Launch /bin/sh — the POSIX shell. This is only meaningful when
// the ext2 rootfs is mounted (root=/dev/vda), which places the sh
// binary at /bin/sh. When booting from the ISO-only path (no ext2),
// execve will fail and the child exits with status 1; init continues
// to its idle loop regardless.
write(1, SH_LAUNCH_MSG);

let sh_fork_ret: i64;
unsafe {
core::arch::asm!(
"syscall",
inlateout("rax") 57u64 => sh_fork_ret,
lateout("rcx") _,
lateout("rdx") _,
lateout("rdi") _,
lateout("rsi") _,
lateout("r8") _,
lateout("r9") _,
lateout("r10") _,
lateout("r11") _,
options(nostack, preserves_flags),
);
}

if sh_fork_ret == 0 {
// Child: exec /bin/sh.
unsafe {
core::arch::asm!(
"syscall",
inlateout("rax") 59u64 => _, // execve
inlateout("rdi") SH_PATH.as_ptr() as u64 => _, // path
inlateout("rsi") 0u64 => _, // argv (NULL = empty)
inlateout("rdx") 0u64 => _, // envp (NULL = empty)
lateout("rcx") _,
lateout("r8") _,
lateout("r9") _,
lateout("r10") _,
lateout("r11") _,
options(nostack, preserves_flags),
);
}
// execve only returns on failure — exit with status 1.
unsafe {
core::arch::asm!(
"syscall",
inlateout("rax") 60u64 => _, // exit
inlateout("rdi") 1u64 => _, // status 1 (exec failed)
lateout("rcx") _,
lateout("rdx") _,
lateout("rsi") _,
lateout("r8") _,
lateout("r9") _,
lateout("r10") _,
lateout("r11") _,
options(nostack, preserves_flags),
);
}
loop {
core::hint::spin_loop();
}
}

// Parent: wait for the shell to exit, then idle.
if sh_fork_ret > 0 {
let sh_pid = sh_fork_ret as u64;
let mut sh_wstatus: i32 = 0;
unsafe {
core::arch::asm!(
"syscall",
inlateout("rax") 61u64 => _, // wait4
inlateout("rdi") sh_pid => _, // pid
inlateout("rsi") &mut sh_wstatus as *mut i32 as u64 => _, // *wstatus
inlateout("rdx") 0u64 => _, // options
inlateout("r10") 0u64 => _, // rusage
lateout("rcx") _,
lateout("r8") _,
lateout("r9") _,
lateout("r11") _,
options(nostack),
);
}
}

// Loop forever.
loop {
core::hint::spin_loop();
Expand Down
27 changes: 21 additions & 6 deletions base/sh/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
#![feature(restricted_std)]

// Provide #[no_mangle] C-ABI shims for POSIX functions that the shell's
// `extern "C"` declarations link against. On vibix, these delegate to
// raw syscalls via `vibix_abi`. The module is excluded from host tests
// which mock these symbols.
#[cfg(not(test))]
mod syscalls;

mod builtins;
mod exec;
mod expand;
Expand All @@ -26,6 +33,9 @@ fn main() {
env.arg0 = "sh".to_string();

// Import environment variables from the process environment.
// On vibix, std::env::vars() is not yet supported (panics),
// so we skip import on that target. $PATH is set above.
#[cfg(not(target_os = "vibix"))]
for (key, value) in std::env::vars() {
env.set(&key, &value, Some(true));
}
Expand All @@ -50,10 +60,11 @@ fn main() {
use std::io::BufRead;
let stdin = std::io::stdin();

// Detect if stdin is a terminal. On vibix, isatty may not be
// available via std, so we check the TERM variable or fall back
// to assuming interactive when stdin is not redirected.
let is_tty = std::env::var("TERM").is_ok();
// On vibix, the serial console is not a POSIX tty, so `isatty()`
// would return false even for the primary interactive session.
// Always treat stdin-mode as interactive to ensure the prompt
// appears over the serial console.
let is_tty = true;
Comment on lines +63 to +67
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Gate the forced interactive mode to vibix only.

This now makes every stdin-driven host session look interactive too, so pipes/files get prompts and interactive signal handling. Keep the forced true on vibix, but preserve normal tty detection everywhere else.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@base/sh/src/main.rs` around lines 63 - 67, The code unconditionally forces
stdin interactive by setting let is_tty = true; which breaks non-vibix sessions;
change this to only force true when running on vibix and otherwise use normal
tty detection (e.g. replace the assignment with a conditional such as: if
running_on_vibix { true } else { atty::is(atty::Stream::Stdin) } ), where
"running_on_vibix" can be implemented by checking an environment variable
(std::env::var("VIBIX") == Ok("1")) or by inspecting the host name
(gethostname()/std::env::var("HOSTNAME") contains "vibix"); keep the variable
name is_tty and preserve the existing comment about vibix.


// Create the job table for interactive mode.
let mut jobs = JobTable::new();
Expand All @@ -66,7 +77,9 @@ fn main() {
eprint!("$ ");
}

for line in stdin.lock().lines() {
let locked = stdin.lock();

for line in locked.lines() {
match line {
Ok(input) => {
if input.is_empty() {
Expand All @@ -89,7 +102,9 @@ fn main() {
eprint!("$ ");
}
}
Err(_) => break,
Err(_) => {
break;
}
}
}

Expand Down
Loading