2023-01-19 01:47:14 +00:00
|
|
|
use std::{
|
2023-01-20 20:21:20 +00:00
|
|
|
env,
|
2023-01-19 01:47:14 +00:00
|
|
|
process::{exit, Stdio},
|
|
|
|
};
|
|
|
|
|
2023-01-20 20:21:20 +00:00
|
|
|
use mlua::{
|
|
|
|
Error, Function, Lua, MetaMethod, Result, Table, UserData, UserDataFields, UserDataMethods,
|
|
|
|
Value,
|
|
|
|
};
|
|
|
|
use os_str_bytes::RawOsString;
|
2023-01-19 01:47:14 +00:00
|
|
|
use tokio::process::Command;
|
|
|
|
|
2023-01-21 02:05:51 +00:00
|
|
|
pub struct Process {
|
2023-01-20 20:21:20 +00:00
|
|
|
args: Vec<String>,
|
|
|
|
}
|
2023-01-19 01:47:14 +00:00
|
|
|
|
2023-01-21 03:01:02 +00:00
|
|
|
impl Default for Process {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self::new(vec![])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-21 02:05:51 +00:00
|
|
|
impl Process {
|
2023-01-20 20:21:20 +00:00
|
|
|
pub fn new(args: Vec<String>) -> Self {
|
|
|
|
Self { args }
|
2023-01-19 01:47:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-21 02:05:51 +00:00
|
|
|
impl UserData for Process {
|
2023-01-20 20:21:20 +00:00
|
|
|
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
|
|
|
|
fields.add_field_method_get("args", |lua, this| {
|
|
|
|
// TODO: Use the same strategy as env uses below to avoid
|
|
|
|
// copying each time args are accessed? is it worth it?
|
|
|
|
let tab = lua.create_table()?;
|
|
|
|
for arg in &this.args {
|
2023-01-21 02:05:51 +00:00
|
|
|
tab.push(arg.clone())?;
|
2023-01-20 20:21:20 +00:00
|
|
|
}
|
2023-01-21 04:40:31 +00:00
|
|
|
tab.set_readonly(true);
|
2023-01-20 20:21:20 +00:00
|
|
|
Ok(tab)
|
|
|
|
});
|
|
|
|
fields.add_field_method_get("env", |lua, _| {
|
|
|
|
let meta = lua.create_table()?;
|
|
|
|
meta.raw_set(
|
|
|
|
MetaMethod::Index.name(),
|
|
|
|
lua.create_function(process_env_get)?,
|
|
|
|
)?;
|
|
|
|
meta.raw_set(
|
|
|
|
MetaMethod::NewIndex.name(),
|
|
|
|
lua.create_function(process_env_set)?,
|
|
|
|
)?;
|
|
|
|
meta.raw_set(
|
|
|
|
MetaMethod::Iter.name(),
|
|
|
|
lua.create_function(process_env_iter)?,
|
|
|
|
)?;
|
|
|
|
let tab = lua.create_table()?;
|
|
|
|
tab.set_metatable(Some(meta));
|
|
|
|
tab.set_readonly(true);
|
|
|
|
Ok(tab)
|
2023-01-21 02:05:51 +00:00
|
|
|
});
|
2023-01-20 20:21:20 +00:00
|
|
|
}
|
|
|
|
|
2023-01-19 01:47:14 +00:00
|
|
|
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
|
|
|
methods.add_function("exit", process_exit);
|
|
|
|
methods.add_async_function("spawn", process_spawn);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-20 20:21:20 +00:00
|
|
|
fn process_env_get<'lua>(lua: &'lua Lua, (_, key): (Value<'lua>, String)) -> Result<Value<'lua>> {
|
|
|
|
match env::var_os(key) {
|
|
|
|
Some(value) => {
|
|
|
|
let raw_value = RawOsString::new(value);
|
|
|
|
Ok(Value::String(lua.create_string(raw_value.as_raw_bytes())?))
|
|
|
|
}
|
|
|
|
None => Ok(Value::Nil),
|
2023-01-19 01:47:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-21 04:40:31 +00:00
|
|
|
fn process_env_set(_: &Lua, (_, key, value): (Value, String, Option<String>)) -> Result<()> {
|
2023-01-20 20:21:20 +00:00
|
|
|
// Make sure key is valid, otherwise set_var will panic
|
|
|
|
if key.is_empty() {
|
|
|
|
return Err(Error::RuntimeError("Key must not be empty".to_string()));
|
|
|
|
} else if key.contains('=') {
|
|
|
|
return Err(Error::RuntimeError(
|
|
|
|
"Key must not contain the equals character '='".to_string(),
|
|
|
|
));
|
|
|
|
} else if key.contains('\0') {
|
|
|
|
return Err(Error::RuntimeError(
|
|
|
|
"Key must not contain the NUL character".to_string(),
|
|
|
|
));
|
2023-01-19 01:47:14 +00:00
|
|
|
}
|
2023-01-21 04:40:31 +00:00
|
|
|
match value {
|
|
|
|
Some(value) => {
|
|
|
|
// Make sure value is valid, otherwise set_var will panic
|
|
|
|
if value.contains('\0') {
|
|
|
|
return Err(Error::RuntimeError(
|
|
|
|
"Value must not contain the NUL character".to_string(),
|
|
|
|
));
|
|
|
|
}
|
|
|
|
env::set_var(&key, &value);
|
|
|
|
}
|
|
|
|
None => env::remove_var(&key),
|
2023-01-20 20:21:20 +00:00
|
|
|
}
|
|
|
|
Ok(())
|
2023-01-19 01:47:14 +00:00
|
|
|
}
|
|
|
|
|
2023-01-20 20:21:20 +00:00
|
|
|
fn process_env_iter<'lua>(lua: &'lua Lua, (_, _): (Value<'lua>, ())) -> Result<Function<'lua>> {
|
|
|
|
let mut vars = env::vars_os();
|
|
|
|
lua.create_function_mut(move |lua, _: ()| match vars.next() {
|
|
|
|
Some((key, value)) => {
|
|
|
|
let raw_key = RawOsString::new(key);
|
|
|
|
let raw_value = RawOsString::new(value);
|
|
|
|
Ok((
|
|
|
|
Value::String(lua.create_string(raw_key.as_raw_bytes())?),
|
|
|
|
Value::String(lua.create_string(raw_value.as_raw_bytes())?),
|
|
|
|
))
|
|
|
|
}
|
|
|
|
None => Ok((Value::Nil, Value::Nil)),
|
|
|
|
})
|
2023-01-19 01:47:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn process_exit(_: &Lua, exit_code: Option<i32>) -> Result<()> {
|
2023-01-21 07:05:16 +00:00
|
|
|
// TODO: Exit gracefully to the root with an Ok
|
|
|
|
// result instead of completely exiting the process
|
2023-01-19 01:47:14 +00:00
|
|
|
if let Some(code) = exit_code {
|
|
|
|
exit(code);
|
|
|
|
} else {
|
|
|
|
exit(0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn process_spawn(lua: &Lua, (program, args): (String, Option<Vec<String>>)) -> Result<Table> {
|
|
|
|
// Create and spawn a child process, and
|
|
|
|
// wait for it to terminate with output
|
|
|
|
let mut cmd = Command::new(program);
|
|
|
|
if let Some(args) = args {
|
|
|
|
cmd.args(args);
|
|
|
|
}
|
|
|
|
let child = cmd
|
|
|
|
.current_dir(env::current_dir().map_err(mlua::Error::external)?)
|
|
|
|
.stdin(Stdio::null())
|
|
|
|
.stdout(Stdio::piped())
|
|
|
|
.stderr(Stdio::piped())
|
|
|
|
.spawn()
|
|
|
|
.map_err(mlua::Error::external)?;
|
|
|
|
let output = child
|
|
|
|
.wait_with_output()
|
|
|
|
.await
|
|
|
|
.map_err(mlua::Error::external)?;
|
2023-01-21 02:09:08 +00:00
|
|
|
// NOTE: If an exit code was not given by the child process,
|
|
|
|
// we default to 1 if it yielded any error output, otherwise 0
|
2023-01-19 01:47:14 +00:00
|
|
|
let code = output
|
|
|
|
.status
|
|
|
|
.code()
|
2023-01-21 02:09:08 +00:00
|
|
|
.unwrap_or(match output.stderr.is_empty() {
|
|
|
|
true => 0,
|
|
|
|
false => 1,
|
|
|
|
});
|
2023-01-19 01:47:14 +00:00
|
|
|
// Construct and return a readonly lua table with results
|
|
|
|
let table = lua.create_table()?;
|
|
|
|
table.raw_set("ok", code == 0)?;
|
|
|
|
table.raw_set("code", code)?;
|
|
|
|
table.raw_set("stdout", lua.create_string(&output.stdout)?)?;
|
|
|
|
table.raw_set("stderr", lua.create_string(&output.stderr)?)?;
|
|
|
|
table.set_readonly(true);
|
|
|
|
Ok(table)
|
|
|
|
}
|