use alloc::{format, string::String, vec::Vec};
use rt::os::{syscall3, syscall6, AT_FDCWD, O_CLOEXEC, O_NONBLOCK, O_RDWR, SYS_CHDIR, SYS_CLOSE, SYS_CLOSE_RANGE, SYS_EXECVE, SYS_EXIT_GROUP, SYS_FACCESSAT, SYS_FCNTL, SYS_GETEUID, SYS_KILL, SYS_OPENAT, SYS_PIPE2, SYS_READ, SYS_RT_SIGACTION, SYS_RT_SIGPROCMASK, SYS_SETSID, SYS_WAIT4, SYS_WAITID, SYS_WRITE};
use super::Command;
struct Fd(i32);
impl Drop for Fd {
fn drop(&mut self) {
if self.0 >= 0 { unsafe { syscall3(SYS_CLOSE, self.0 as usize, 0, 0); } }
}
}
fn pipe() -> Result<(Fd, Fd), u32> {
let mut pair = [-1i32; 2];
let r = unsafe { syscall3(SYS_PIPE2, pair.as_mut_ptr() as usize, O_CLOEXEC, 0) };
if r < 0 { return Err((-r) as u32); }
let mut ends = (Fd(pair[0]), Fd(pair[1]));
for end in [&mut ends.0, &mut ends.1] {
if end.0 < 3 {
let replacement = unsafe { syscall3(SYS_FCNTL, end.0 as usize, 1030, 3) };
if replacement < 0 { return Err((-replacement) as u32); }
*end = Fd(replacement as i32);
}
}
Ok(ends)
}
fn z(s: &str) -> Vec<u8> { let mut v = s.as_bytes().to_vec(); v.push(0); v }
fn nonblock(fd: &Fd) {
unsafe { syscall3(SYS_FCNTL, fd.0 as usize, 4, O_NONBLOCK); }
}
pub struct Child {
pid: i32, input: Option<Fd>, output: Fd, error: Fd, status: Option<i32>, reaped: bool,
}
fn resolve(program: &str) -> Result<String, u32> {
if program.contains('/') { return Ok(String::from(program)); }
let executable = |p: &String| {
let c = z(p);
crate::file_exists_raw(p) == 1 && unsafe { syscall3(SYS_FACCESSAT, AT_FDCWD, c.as_ptr() as usize, 1) } == 0
};
crate::env("PATH").unwrap_or_else(|| String::from("/usr/bin:/bin")).split(':')
.filter(|p| !p.is_empty())
.map(|p| format!("{}/{}", p, program))
.find(executable).ok_or(2u32)
}
fn environment(env: &[(&str, Option<&str>)]) -> Vec<Vec<u8>> {
let mut envs = crate::linux::process_environment();
for (key, value) in env {
let prefix = format!("{key}="); envs.retain(|v| !v.starts_with(prefix.as_bytes()));
if let Some(v) = value { envs.push(format!("{prefix}{v}").into_bytes()); }
}
for s in &mut envs { s.push(0); }
envs
}
pub(super) fn spawn(c: &Command<'_>) -> Result<Child, u32> {
let program = resolve(c.program)?;
let exe = z(&program); let dir = c.cwd.map(z);
let strs: Vec<Vec<u8>> = core::iter::once(c.program).chain(c.args.iter().copied()).map(z).collect();
let mut argv: Vec<usize> = strs.iter().map(|s| s.as_ptr() as usize).collect(); argv.push(0);
let envs = environment(c.env);
let mut envp: Vec<usize> = envs.iter().map(|s| s.as_ptr() as usize).collect(); envp.push(0);
let (ir, iw) = pipe()?; let (or, ow) = pipe()?; let (er, ew) = pipe()?;
let (failr, failw) = pipe()?;
let (dfl, nomask) = ([0usize; 4], [0u64; 1]); let pid = unsafe {
let pid = rt::os::fork();
if pid == 0 {
let mut failure = syscall3(SYS_SETSID, 0, 0, 0);
if failure >= 0 {
for (fd, dst) in [(ir.0, 0), (ow.0, 1), (ew.0, 2)] {
let r = rt::os::dup2(fd as usize, dst); if r < 0 { failure = r; break; }
}
}
if failure >= 0 { if let Some(d) = &dir { failure = syscall3(SYS_CHDIR, d.as_ptr() as usize, 0, 0); } }
if failure >= 0 { syscall3(SYS_CLOSE_RANGE, 3, 0xFFFF_FFFF, 4); }
if failure >= 0 {
syscall6(SYS_RT_SIGACTION, 13, dfl.as_ptr() as usize, 0, 8, 0, 0); syscall6(SYS_RT_SIGPROCMASK, 2, nomask.as_ptr() as usize, 0, 8, 0, 0); }
if failure >= 0 { failure = syscall3(SYS_EXECVE, exe.as_ptr() as usize, argv.as_ptr() as usize, envp.as_ptr() as usize); }
let code = (-failure) as u32;
syscall3(SYS_WRITE, failw.0 as usize, &code as *const u32 as usize, 4);
syscall3(SYS_EXIT_GROUP, 127, 0, 0);
loop { core::hint::spin_loop(); }
}
pid
};
if pid < 0 { return Err((-pid) as u32); }
drop(ir); drop(ow); drop(ew); drop(failw);
let mut error = 0u32;
let n = loop { let r = unsafe { syscall3(SYS_READ, failr.0 as usize, &mut error as *mut u32 as usize, 4) }; if r != -4 { break r; } };
nonblock(&iw); nonblock(&or); nonblock(&er);
let mut child = Child { pid: pid as i32, input: Some(iw), output: or, error: er, status: None, reaped: false };
if n != 0 { child.terminate(); return Err(if error == 0 { 5 } else { error }); }
Ok(child)
}
fn read(fd: &Fd, bytes: &mut [u8]) -> Result<Option<usize>, u32> {
if bytes.is_empty() { return Ok(None); } loop {
let r = unsafe { syscall3(SYS_READ, fd.0 as usize, bytes.as_mut_ptr() as usize, bytes.len()) };
if r == -4 { continue; }
return if r == -11 { Ok(None) } else if r < 0 { Err((-r) as u32) } else { Ok(Some(r as usize)) };
}
}
impl Child {
pub fn read_stdout(&mut self, bytes: &mut [u8]) -> Result<Option<usize>, u32> { read(&self.output, bytes) }
pub fn read_stderr(&mut self, bytes: &mut [u8]) -> Result<Option<usize>, u32> { read(&self.error, bytes) }
pub fn write_stdin(&mut self, bytes: &[u8]) -> Result<Option<usize>, u32> {
let fd = self.input.as_ref().ok_or(32u32)?;
let r = unsafe { syscall3(SYS_WRITE, fd.0 as usize, bytes.as_ptr() as usize, bytes.len()) };
if r == -11 || r == -4 { Ok(None) } else if r < 0 { Err((-r) as u32) } else { Ok(Some(r as usize)) }
}
pub fn close_stdin(&mut self) { self.input = None; }
pub fn try_wait(&mut self) -> Result<Option<i32>, u32> {
if self.status.is_some() { return Ok(self.status); }
let mut info = [0u64; 16];
let r = unsafe { syscall6(SYS_WAITID, 1, self.pid as usize, info.as_mut_ptr() as usize, 0x1000005, 0, 0) };
if r < 0 { return Err((-r) as u32); }
if info[2] as u32 != 0 {
let code = info[1] as u32;
let status = info[3] as u32 as i32;
self.status = Some(if code == 1 { status } else { 128 + status });
}
Ok(self.status)
}
pub fn terminate(&mut self) {
if self.reaped { return; }
unsafe { syscall3(SYS_KILL, (-self.pid) as isize as usize, 9, 0); }
let mut status = 0i32;
while unsafe { syscall6(SYS_WAIT4, self.pid as usize, &mut status as *mut i32 as usize, 0, 0, 0, 0) } == -4 {}
if self.status.is_none() { self.status = Some(137); }
self.reaped = true;
}
}
impl Drop for Child { fn drop(&mut self) { self.terminate(); } }
pub(super) fn elevated() -> bool {
unsafe { syscall3(SYS_GETEUID, 0, 0, 0) == 0 }
}
pub(super) fn exec(program: &str, args: &[&str], env: &[(&str, Option<&str>)]) -> u32 {
let path = match resolve(program) { Ok(p) => p, Err(e) => return e };
let exe = z(&path);
let strs: Vec<Vec<u8>> = core::iter::once(program).chain(args.iter().copied()).map(z).collect();
let mut argv: Vec<usize> = strs.iter().map(|s| s.as_ptr() as usize).collect(); argv.push(0);
let envs = environment(env);
let mut envp: Vec<usize> = envs.iter().map(|s| s.as_ptr() as usize).collect(); envp.push(0);
let dfl = [0usize; 4];
let r = unsafe {
syscall6(SYS_RT_SIGACTION, 13, dfl.as_ptr() as usize, 0, 8, 0, 0);
syscall3(SYS_EXECVE, exe.as_ptr() as usize, argv.as_ptr() as usize, envp.as_ptr() as usize)
};
let ign = [1usize, 0, 0, 0];
unsafe { syscall6(SYS_RT_SIGACTION, 13, ign.as_ptr() as usize, 0, 8, 0, 0); }
(-r) as u32
}
pub(super) fn find(name: &str) -> Option<String> { resolve(name).ok() }
pub(super) fn launch(program: &str, args: &[&str]) -> Result<(), u32> {
let path = resolve(program)?;
let exe = z(&path); let null = z("/dev/null");
let strs: Vec<Vec<u8>> = core::iter::once(program).chain(args.iter().copied()).map(z).collect();
let mut argv: Vec<usize> = strs.iter().map(|s| s.as_ptr() as usize).collect(); argv.push(0);
let envs = environment(&[]);
let mut envp: Vec<usize> = envs.iter().map(|s| s.as_ptr() as usize).collect(); envp.push(0);
let (failr, failw) = pipe()?;
let (dfl, nomask) = ([0usize; 4], [0u64; 1]); let pid = unsafe {
let pid = rt::os::fork();
if pid == 0 {
let mut failure = syscall3(SYS_SETSID, 0, 0, 0); if failure >= 0 {
let grand = rt::os::fork();
if grand == 0 {
let fd = syscall6(SYS_OPENAT, AT_FDCWD, null.as_ptr() as usize, O_RDWR, 0, 0, 0); if fd >= 0 {
for dst in 0..3 { rt::os::dup2(fd as usize, dst); }
if fd > 2 { syscall3(SYS_CLOSE, fd as usize, 0, 0); }
}
syscall3(SYS_CLOSE_RANGE, 3, 0xFFFF_FFFF, 4); syscall6(SYS_RT_SIGACTION, 13, dfl.as_ptr() as usize, 0, 8, 0, 0); syscall6(SYS_RT_SIGPROCMASK, 2, nomask.as_ptr() as usize, 0, 8, 0, 0); failure = syscall3(SYS_EXECVE, exe.as_ptr() as usize, argv.as_ptr() as usize, envp.as_ptr() as usize);
let code = (-failure) as u32;
syscall3(SYS_WRITE, failw.0 as usize, &code as *const u32 as usize, 4);
syscall3(SYS_EXIT_GROUP, 127, 0, 0);
loop { core::hint::spin_loop(); }
}
failure = grand.min(0);
}
if failure < 0 {
let code = (-failure) as u32;
syscall3(SYS_WRITE, failw.0 as usize, &code as *const u32 as usize, 4);
}
syscall3(SYS_EXIT_GROUP, 0, 0, 0);
loop { core::hint::spin_loop(); }
}
pid
};
if pid < 0 { return Err((-pid) as u32); }
drop(failw);
let mut status = 0i32;
while unsafe { syscall6(SYS_WAIT4, pid as usize, &mut status as *mut i32 as usize, 0, 0, 0, 0) } == -4 {}
let mut error = 0u32;
let n = loop { let r = unsafe { syscall3(SYS_READ, failr.0 as usize, &mut error as *mut u32 as usize, 4) }; if r != -4 { break r; } };
if n != 0 { return Err(if error == 0 { 5 } else { error }); }
Ok(())
}
pub(super) fn run_elevated(program: &str, args: &[&str]) -> Result<i32, String> {
let mut all: Vec<&str> = Vec::new();
let mut pkexec = false;
let tool = if elevated() {
program
} else {
let Some(found) = ["/usr/bin/pkexec", "/bin/pkexec"].into_iter().find(|p| crate::file_exists_raw(p) == 1) else {
return Err(format!("Root-Rechte nötig: sudo {} {}", program, args.join(" ")));
};
all.push(program);
pkexec = true;
found
};
all.extend_from_slice(args);
let c = Command { program: tool, args: &all, cwd: None, env: &[] };
let mut child = spawn(&c).map_err(|e| format!("{} startet nicht (Fehler {})", tool, e))?;
let mut b = [0u8; 4096];
loop {
while let Ok(Some(n)) = child.read_stdout(&mut b) { if n == 0 { break; } }
while let Ok(Some(n)) = child.read_stderr(&mut b) { if n == 0 { break; } }
match child.try_wait() {
Ok(Some(126)) if pkexec => return Err(String::from("Die Rechteabfrage wurde abgelehnt")),
Ok(Some(127)) if pkexec => return Err(format!("pkexec konnte {} nicht ausführen", program)),
Ok(Some(code)) => return Ok(code),
Ok(None) => crate::sleep_ms(100),
Err(e) => return Err(format!("Fehler {}", e)),
}
}
}