use mlua::Lua;
#[cfg(target_os = "linux")]
fn list_processes() -> Result<Vec<(i64, String, Option<String>)>, String> {
let entries = std::fs::read_dir("/proc")
.map_err(|e| format!("process.list: failed to read /proc: {e}"))?;
let mut result = Vec::new();
for entry in entries {
let entry = match entry {
Ok(e) => e,
Err(_) => continue,
};
let name = entry.file_name();
let name_str = name.to_string_lossy();
let pid: i64 = match name_str.parse() {
Ok(p) => p,
Err(_) => continue,
};
let comm_path = entry.path().join("comm");
let comm = std::fs::read_to_string(&comm_path)
.unwrap_or_default()
.trim()
.to_string();
if comm.is_empty() {
continue;
}
let cmdline_path = entry.path().join("cmdline");
let cmdline = std::fs::read_to_string(&cmdline_path)
.ok()
.map(|s| s.replace('\0', " ").trim().to_string())
.filter(|s| !s.is_empty());
result.push((pid, comm, cmdline));
}
Ok(result)
}
#[cfg(not(target_os = "linux"))]
fn list_processes() -> Result<Vec<(i64, String, Option<String>)>, String> {
let output = std::process::Command::new("ps")
.args(["-eo", "pid,comm"])
.output()
.map_err(|e| format!("process.list: failed to run ps: {e}"))?;
if !output.status.success() {
return Err(format!(
"process.list: ps failed: {}",
String::from_utf8_lossy(&output.stderr)
));
}
let stdout = String::from_utf8_lossy(&output.stdout);
let mut result = Vec::new();
for line in stdout.lines().skip(1) {
let line = line.trim();
if line.is_empty() {
continue;
}
let mut parts = line.splitn(2, char::is_whitespace);
let pid: i64 = match parts.next().and_then(|s| s.trim().parse().ok()) {
Some(p) => p,
None => continue,
};
let comm = match parts.next() {
Some(s) => s.trim().to_string(),
None => continue,
};
if comm.is_empty() {
continue;
}
let name = comm.rsplit('/').next().unwrap_or(&comm).to_string();
result.push((pid, name, Some(comm)));
}
Ok(result)
}
fn is_process_running(name: &str) -> bool {
match list_processes() {
Ok(procs) => procs.iter().any(|(_, comm, _)| comm == name),
Err(_) => false,
}
}
pub fn register_process(lua: &Lua) -> mlua::Result<()> {
let process_table = lua.create_table()?;
let list_fn = lua.create_function(|lua, ()| {
let proc_list = list_processes().map_err(mlua::Error::runtime)?;
let procs = lua.create_table()?;
for (i, (pid, name, cmdline)) in proc_list.into_iter().enumerate() {
let info = lua.create_table()?;
info.set("pid", pid)?;
info.set("name", name)?;
if let Some(cmd) = cmdline {
info.set("cmdline", cmd)?;
}
procs.set(i + 1, info)?;
}
Ok(procs)
})?;
process_table.set("list", list_fn)?;
let is_running_fn = lua.create_function(|_, name: String| Ok(is_process_running(&name)))?;
process_table.set("is_running", is_running_fn)?;
let kill_fn = lua.create_function(|_, args: mlua::MultiValue| {
let mut args_iter = args.into_iter();
let pid: i32 = args_iter
.next()
.ok_or_else(|| mlua::Error::runtime("process.kill: pid required"))
.and_then(|v| match v {
mlua::Value::Integer(n) => Ok(n as i32),
mlua::Value::Number(n) => Ok(n as i32),
_ => Err(mlua::Error::runtime("process.kill: pid must be a number")),
})?;
if pid <= 0 {
return Err(mlua::Error::runtime(format!(
"process.kill: pid must be > 0, got {pid}"
)));
}
let signal: i32 = args_iter
.next()
.map(|v| match v {
mlua::Value::Integer(n) => Ok(n as i32),
mlua::Value::Number(n) => Ok(n as i32),
mlua::Value::Nil => Ok(15), _ => Err(mlua::Error::runtime(
"process.kill: signal must be a number",
)),
})
.unwrap_or(Ok(15))?;
if signal < 0 {
return Err(mlua::Error::runtime(format!(
"process.kill: signal must be >= 0, got {signal}"
)));
}
let result = unsafe { libc::kill(pid, signal) };
if result == 0 {
Ok(true)
} else {
let err = std::io::Error::last_os_error();
Err(mlua::Error::runtime(format!(
"process.kill: failed to send signal {signal} to pid {pid}: {err}"
)))
}
})?;
process_table.set("kill", kill_fn)?;
lua.globals().set("process", process_table)?;
lua.load(
r#"
-- process.wait_idle(names, timeout, interval)
-- Wait until none of the named processes are running.
-- names: string or table of strings
-- timeout: max seconds to wait (default 30)
-- interval: poll interval in seconds (default 1)
-- Returns true if all idle, false if timed out.
function process.wait_idle(names, timeout, interval)
timeout = timeout or 30
interval = interval or 1
if type(names) == "string" then names = {names} end
local deadline = time() + timeout
while time() < deadline do
local any_running = false
for _, name in ipairs(names) do
if process.is_running(name) then
any_running = true
break
end
end
if not any_running then return true end
sleep(interval)
end
return false
end
"#,
)
.exec()?;
Ok(())
}