Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
4 changes: 4 additions & 0 deletions base/sh/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
#![feature(restricted_std)]

// Provides C-ABI symbols (fork, pipe, dup2, etc.) that the shell's
// `extern "C"` blocks reference, backed by vibix_abi syscalls.
mod syscalls;
Comment on lines +7 to +8
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

Scope syscall shims to vibix-only builds.

#[cfg(not(test))] pulls raw syscall POSIX shims into all non-test binaries. That can unintentionally override host libc symbols and introduce host-specific breakage. Restrict this module to vibix target builds.

Suggested fix
-#[cfg(not(test))]
+#[cfg(all(target_os = "vibix", not(test)))]
 mod syscalls;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#[cfg(not(test))]
mod syscalls;
#[cfg(all(target_os = "vibix", not(test)))]
mod syscalls;
🤖 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 7 - 8, The runtime shims module is
currently exposed to all non-test builds via #[cfg(not(test))] and should be
limited to vibix target builds; update the cfg on the mod syscalls declaration
(the mod syscalls in base/sh/src/main.rs) to only compile on the vibix target
(for example use a cfg like all(target_vendor = "vibix", not(test)) — or
target_os = "vibix" if your vibix target is set as the OS) so the raw syscall
shims are not pulled into non-vibix binaries.


mod builtins;
mod exec;
mod expand;
Expand Down
272 changes: 272 additions & 0 deletions base/sh/src/syscalls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
//! C-ABI syscall shims for the vibix shell.
//!
//! The shell modules (`exec.rs`, `redirect.rs`, `job.rs`) use `extern "C"`
//! blocks to call standard POSIX functions. On vibix these symbols are not
//! provided by a system libc; we implement them here using raw `syscall`
//! instructions, matching the Linux x86_64 ABI that the vibix kernel uses.
//!
//! Each function follows POSIX return conventions: -1 on error with errno
//! set.
//!
//! The errno TLS cell lives in the std fork's vibix_abi (already linked into
//! this binary via `-Z build-std`). We access it through the C-ABI
//! `__errno_location` symbol that vibix_abi exports.

use core::arch::asm;

// Syscall numbers (Linux x86_64 ABI, matching the vibix kernel).
const SYS_READ: u64 = 0;
const SYS_WRITE: u64 = 1;
const SYS_OPEN: u64 = 2;
const SYS_CLOSE: u64 = 3;
const SYS_SIGACTION: u64 = 13;
const SYS_ACCESS: u64 = 21;
const SYS_PIPE: u64 = 22;
const SYS_DUP: u64 = 32;
const SYS_DUP2: u64 = 33;
const SYS_GETPID: u64 = 39;
const SYS_FORK: u64 = 57;
const SYS_EXECVE: u64 = 59;
const SYS_WAIT4: u64 = 61;
const SYS_KILL: u64 = 62;
const SYS_FCNTL: u64 = 72;
const SYS_GETCWD: u64 = 79;
const SYS_CHDIR: u64 = 80;
const SYS_SETPGID: u64 = 109;

extern "C" {
fn __errno_location() -> *mut i32;
}

/// Raw syscall with 0-3 args. Returns the raw kernel return value.
#[inline(always)]
unsafe fn raw0(nr: u64) -> i64 {
let ret: i64;
unsafe {
asm!(
"syscall",
inlateout("rax") nr => ret,
lateout("rcx") _,
lateout("r11") _,
lateout("rdx") _,
lateout("rdi") _,
lateout("rsi") _,
lateout("r8") _,
lateout("r9") _,
lateout("r10") _,
options(nostack, preserves_flags),
);
}
ret
}

#[inline(always)]
unsafe fn raw1(nr: u64, a0: u64) -> i64 {
let ret: i64;
unsafe {
asm!(
"syscall",
inlateout("rax") nr => ret,
inlateout("rdi") a0 => _,
lateout("rcx") _,
lateout("r11") _,
lateout("rdx") _,
lateout("rsi") _,
lateout("r8") _,
lateout("r9") _,
lateout("r10") _,
options(nostack, preserves_flags),
);
}
ret
}

#[inline(always)]
unsafe fn raw2(nr: u64, a0: u64, a1: u64) -> i64 {
let ret: i64;
unsafe {
asm!(
"syscall",
inlateout("rax") nr => ret,
inlateout("rdi") a0 => _,
inlateout("rsi") a1 => _,
lateout("rcx") _,
lateout("r11") _,
lateout("rdx") _,
lateout("r8") _,
lateout("r9") _,
lateout("r10") _,
options(nostack, preserves_flags),
);
}
ret
}

#[inline(always)]
unsafe fn raw3(nr: u64, a0: u64, a1: u64, a2: u64) -> i64 {
let ret: i64;
unsafe {
asm!(
"syscall",
inlateout("rax") nr => ret,
inlateout("rdi") a0 => _,
inlateout("rsi") a1 => _,
inlateout("rdx") a2 => _,
lateout("rcx") _,
lateout("r11") _,
lateout("r8") _,
lateout("r9") _,
lateout("r10") _,
options(nostack, preserves_flags),
);
}
ret
}

#[inline(always)]
unsafe fn raw4(nr: u64, a0: u64, a1: u64, a2: u64, a3: u64) -> i64 {
let ret: i64;
unsafe {
asm!(
"syscall",
inlateout("rax") nr => ret,
inlateout("rdi") a0 => _,
inlateout("rsi") a1 => _,
inlateout("rdx") a2 => _,
inlateout("r10") a3 => _,
lateout("rcx") _,
lateout("r11") _,
lateout("r8") _,
lateout("r9") _,
options(nostack, preserves_flags),
);
}
ret
}

/// Convert raw syscall return to C convention: on error set errno, return -1.
#[inline]
unsafe fn cvt(r: i64) -> i64 {
if r < 0 {
unsafe { *__errno_location() = (-r) as i32 };
-1
} else {
r
}
}

#[no_mangle]
pub unsafe extern "C" fn fork() -> i32 {
unsafe { cvt(raw0(SYS_FORK)) as i32 }
}

#[no_mangle]
pub unsafe extern "C" fn wait4(pid: i32, wstatus: *mut i32, options: i32, rusage: *mut u8) -> i32 {
unsafe {
cvt(raw4(
SYS_WAIT4,
pid as u64,
wstatus as u64,
options as u64,
rusage as u64,
)) as i32
}
}

#[no_mangle]
pub unsafe extern "C" fn kill(pid: i32, sig: i32) -> i32 {
unsafe { cvt(raw2(SYS_KILL, pid as u64, sig as u64)) as i32 }
}

#[no_mangle]
pub unsafe extern "C" fn setpgid(pid: i32, pgid: i32) -> i32 {
unsafe { cvt(raw2(SYS_SETPGID, pid as u64, pgid as u64)) as i32 }
}

#[no_mangle]
pub unsafe extern "C" fn sigaction(signum: i32, act: *const u8, oldact: *mut u8) -> i32 {
unsafe { cvt(raw3(SYS_SIGACTION, signum as u64, act as u64, oldact as u64)) as i32 }
Comment on lines +186 to +188
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

🧩 Analysis chain

🌐 Web query:

On Linux x86_64, what syscall does number 13 map to, what arguments does rt_sigaction take, and what sigsetsize value is expected?

💡 Result:

On Linux x86_64, syscall number 13 maps to rt_sigaction. [1] Arguments taken by rt_sigaction (in order): 1) int signum 2) const struct sigaction *act 3) struct sigaction *oldact 4) size_t sigsetsize (These correspond to the documented parameter list for the system call.) [2][3] Expected sigsetsize value: the argument is currently required to have the value sizeof(sigset_t); otherwise the kernel returns EINVAL. [2]

Citations:


🏁 Script executed:

# Check SYS_SIGACTION value and verify raw4 exists
cd base/sh/src && head -50 syscalls.rs | grep -E "SYS_SIGACTION|raw"

Repository: dburkart/vibix

Length of output: 264


🏁 Script executed:

# Get the full syscall number definitions and raw helpers
cd base/sh/src && sed -n '1,150p' syscalls.rs | grep -E "SYS_SIGACTION|fn raw[0-9]"

Repository: dburkart/vibix

Length of output: 344


🏁 Script executed:

# Check the sigaction function implementation
cd base/sh/src && sed -n '180,195p' syscalls.rs

Repository: dburkart/vibix

Length of output: 533


🏁 Script executed:

# Verify job::install_interactive_signals() location in main.rs
cd base/sh/src && grep -n "install_interactive_signals" main.rs

Repository: dburkart/vibix

Length of output: 105


Pass the required fourth argument to rt_sigaction.

Line 187 is issuing syscall 13 via raw3, but on x86_64 that entry point is rt_sigaction, which takes a fourth sigsetsize argument. Without it, job::install_interactive_signals() in base/sh/src/main.rs Line 69 will fail with EINVAL, preventing the shell from installing its SIGINT/SIGTSTP handlers.

Suggested fix
 #[no_mangle]
 pub unsafe extern "C" fn sigaction(signum: i32, act: *const u8, oldact: *mut u8) -> i32 {
-    unsafe { cvt(raw3(SYS_SIGACTION, signum as u64, act as u64, oldact as u64)) as i32 }
+    const KERNEL_SIGSET_SIZE: u64 = core::mem::size_of::<u64>() as u64;
+    unsafe {
+        cvt(raw4(
+            SYS_SIGACTION,
+            signum as u64,
+            act as u64,
+            oldact as u64,
+            KERNEL_SIGSET_SIZE,
+        )) as i32
+    }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#[no_mangle]
pub unsafe extern "C" fn sigaction(signum: i32, act: *const u8, oldact: *mut u8) -> i32 {
unsafe { cvt(raw3(SYS_SIGACTION, signum as u64, act as u64, oldact as u64)) as i32 }
#[no_mangle]
pub unsafe extern "C" fn sigaction(signum: i32, act: *const u8, oldact: *mut u8) -> i32 {
const KERNEL_SIGSET_SIZE: u64 = core::mem::size_of::<u64>() as u64;
unsafe {
cvt(raw4(
SYS_SIGACTION,
signum as u64,
act as u64,
oldact as u64,
KERNEL_SIGSET_SIZE,
)) as i32
}
}
🤖 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/syscalls.rs` around lines 186 - 188, The syscall wrapper
sigaction currently calls raw3 with SYS_SIGACTION but must call the 4-argument
rt_sigaction; change sigaction to call raw4 (or the 4-arg syscall helper) and
pass the correct fourth argument: the sigset size (the size of sigset_t on
x86_64, e.g. sizeof(sigset_t)/8 bytes as used elsewhere). Update the call site
referenced by sigaction (replace raw3(SYS_SIGACTION, signum as u64, act as u64,
oldact as u64) with the 4-arg variant including the sigsetsize) so
job::install_interactive_signals() can succeed.

}

#[no_mangle]
pub unsafe extern "C" fn pipe(pipefd: *mut i32) -> i32 {
unsafe { cvt(raw1(SYS_PIPE, pipefd as u64)) as i32 }
}

#[no_mangle]
pub unsafe extern "C" fn dup(oldfd: i32) -> i32 {
unsafe { cvt(raw1(SYS_DUP, oldfd as u64)) as i32 }
}

#[no_mangle]
pub unsafe extern "C" fn dup2(oldfd: i32, newfd: i32) -> i32 {
unsafe { cvt(raw2(SYS_DUP2, oldfd as u64, newfd as u64)) as i32 }
}

#[no_mangle]
pub unsafe extern "C" fn fcntl(fd: i32, cmd: i32, arg: u64) -> i32 {
unsafe { cvt(raw3(SYS_FCNTL, fd as u64, cmd as u64, arg)) as i32 }
}

#[no_mangle]
pub unsafe extern "C" fn access(pathname: *const u8, mode: i32) -> i32 {
unsafe { cvt(raw2(SYS_ACCESS, pathname as u64, mode as u64)) as i32 }
}

#[no_mangle]
pub unsafe extern "C" fn open(pathname: *const u8, flags: i32, mode: u32) -> i32 {
unsafe { cvt(raw3(SYS_OPEN, pathname as u64, flags as u64, mode as u64)) as i32 }
}

#[no_mangle]
pub unsafe extern "C" fn close(fd: i32) -> i32 {
unsafe { cvt(raw1(SYS_CLOSE, fd as u64)) as i32 }
}

#[no_mangle]
pub unsafe extern "C" fn write(fd: i32, buf: *const u8, count: usize) -> isize {
unsafe { cvt(raw3(SYS_WRITE, fd as u64, buf as u64, count as u64)) as isize }
}

#[no_mangle]
pub unsafe extern "C" fn read(fd: i32, buf: *mut u8, count: usize) -> isize {
unsafe { cvt(raw3(SYS_READ, fd as u64, buf as u64, count as u64)) as isize }
}

#[no_mangle]
pub unsafe extern "C" fn execve(
pathname: *const u8,
argv: *const *const u8,
envp: *const *const u8,
) -> i32 {
unsafe {
cvt(raw3(
SYS_EXECVE,
pathname as u64,
argv as u64,
envp as u64,
)) as i32
}
}

#[no_mangle]
pub unsafe extern "C" fn getcwd(buf: *mut u8, size: usize) -> *mut u8 {
let r = unsafe { raw2(SYS_GETCWD, buf as u64, size as u64) };
if r < 0 {
unsafe { *__errno_location() = (-r) as i32 };
core::ptr::null_mut()
} else {
buf
}
}

#[no_mangle]
pub unsafe extern "C" fn chdir(path: *const u8) -> i32 {
unsafe { cvt(raw1(SYS_CHDIR, path as u64)) as i32 }
}

#[no_mangle]
pub unsafe extern "C" fn getpid() -> i32 {
// getpid never fails, so no errno handling needed.
unsafe { raw0(SYS_GETPID) as i32 }
}
Loading
Loading