Revamp handling of process args and env with fully featured newtypes

This commit is contained in:
Filip Tibell 2025-05-01 21:08:58 +02:00
parent b1fc60023d
commit 2d8e58b028
No known key found for this signature in database
11 changed files with 637 additions and 112 deletions

2
Cargo.lock generated
View file

@ -1798,7 +1798,6 @@ dependencies = [
"lune-utils", "lune-utils",
"mlua", "mlua",
"mlua-luau-scheduler", "mlua-luau-scheduler",
"os_str_bytes",
"pin-project", "pin-project",
] ]
@ -1880,6 +1879,7 @@ dependencies = [
"console", "console",
"dunce", "dunce",
"mlua", "mlua",
"os_str_bytes",
"parking_lot", "parking_lot",
"path-clean", "path-clean",
"pathdiff", "pathdiff",

View file

@ -18,7 +18,6 @@ mlua-luau-scheduler = { version = "0.1.2", path = "../mlua-luau-scheduler" }
directories = "6.0" directories = "6.0"
pin-project = "1.0" pin-project = "1.0"
os_str_bytes = { version = "7.0", features = ["conversions"] }
bstr = "1.9" bstr = "1.9"
bytes = "1.6.0" bytes = "1.6.0"

View file

@ -1,10 +1,7 @@
#![allow(clippy::cargo_common_metadata)] #![allow(clippy::cargo_common_metadata)]
use std::{ use std::{
env::{ env::consts::{ARCH, OS},
self,
consts::{ARCH, OS},
},
path::MAIN_SEPARATOR, path::MAIN_SEPARATOR,
process::Stdio, process::Stdio,
}; };
@ -12,9 +9,11 @@ use std::{
use mlua::prelude::*; use mlua::prelude::*;
use mlua_luau_scheduler::Functions; use mlua_luau_scheduler::Functions;
use os_str_bytes::RawOsString; use lune_utils::{
path::get_current_dir,
use lune_utils::{path::get_current_dir, TableBuilder}; process::{ProcessArgs, ProcessEnv},
TableBuilder,
};
mod create; mod create;
mod exec; mod exec;
@ -58,25 +57,19 @@ pub fn module(lua: Lua) -> LuaResult<LuaTable> {
"little" "little"
})?; })?;
// Create readonly args array // Find the readonly args array
let args_vec = lua let args_vec = lua
.app_data_ref::<Vec<String>>() .app_data_ref::<Vec<String>>()
.ok_or_else(|| LuaError::runtime("Missing args vec in Lua app data"))? .ok_or_else(|| LuaError::runtime("Missing args vec in Lua app data"))?
.clone(); .clone();
let args_tab = TableBuilder::new(lua.clone())?
.with_sequential_values(args_vec)?
.build_readonly()?;
// Create proxied table for env that gets & sets real env vars // Create userdatas for args + env
let env_tab = TableBuilder::new(lua.clone())? // TODO: Move this up into the runtime creation instead,
.with_metatable( // and set it as app data there to later fetch here
TableBuilder::new(lua.clone())? let process_args = ProcessArgs::from_iter(args_vec);
.with_function(LuaMetaMethod::Index.name(), process_env_get)? let process_env = ProcessEnv::current();
.with_function(LuaMetaMethod::NewIndex.name(), process_env_set)? lua.set_app_data(process_args.clone());
.with_function(LuaMetaMethod::Iter.name(), process_env_iter)? lua.set_app_data(process_env.clone());
.build_readonly()?,
)?
.build_readonly()?;
// Create our process exit function, the scheduler crate provides this // Create our process exit function, the scheduler crate provides this
let fns = Functions::new(lua.clone())?; let fns = Functions::new(lua.clone())?;
@ -87,73 +80,18 @@ pub fn module(lua: Lua) -> LuaResult<LuaTable> {
.with_value("os", os)? .with_value("os", os)?
.with_value("arch", arch)? .with_value("arch", arch)?
.with_value("endianness", endianness)? .with_value("endianness", endianness)?
.with_value("args", args_tab)? .with_value("args", process_args)?
.with_value("cwd", cwd_str)? .with_value("cwd", cwd_str)?
.with_value("env", env_tab)? .with_value("env", process_env)?
.with_value("exit", process_exit)? .with_value("exit", process_exit)?
.with_async_function("exec", process_exec)? .with_async_function("exec", process_exec)?
.with_function("create", process_create)? .with_function("create", process_create)?
.build_readonly() .build_readonly()
} }
fn process_env_get(lua: &Lua, (_, key): (LuaValue, String)) -> LuaResult<LuaValue> {
match env::var_os(key) {
Some(value) => {
let raw_value = RawOsString::new(value);
Ok(LuaValue::String(
lua.create_string(raw_value.to_raw_bytes())?,
))
}
None => Ok(LuaValue::Nil),
}
}
fn process_env_set(_: &Lua, (_, key, value): (LuaValue, String, Option<String>)) -> LuaResult<()> {
// Make sure key is valid, otherwise set_var will panic
if key.is_empty() {
Err(LuaError::RuntimeError("Key must not be empty".to_string()))
} else if key.contains('=') {
Err(LuaError::RuntimeError(
"Key must not contain the equals character '='".to_string(),
))
} else if key.contains('\0') {
Err(LuaError::RuntimeError(
"Key must not contain the NUL character".to_string(),
))
} else if let Some(value) = value {
// Make sure value is valid, otherwise set_var will panic
if value.contains('\0') {
Err(LuaError::RuntimeError(
"Value must not contain the NUL character".to_string(),
))
} else {
env::set_var(&key, &value);
Ok(())
}
} else {
env::remove_var(&key);
Ok(())
}
}
fn process_env_iter(lua: &Lua, (_, ()): (LuaValue, ())) -> LuaResult<LuaFunction> {
let mut vars = env::vars_os().collect::<Vec<_>>().into_iter();
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((
LuaValue::String(lua.create_string(raw_key.to_raw_bytes())?),
LuaValue::String(lua.create_string(raw_value.to_raw_bytes())?),
))
}
None => Ok((LuaValue::Nil, LuaValue::Nil)),
})
}
async fn process_exec( async fn process_exec(
lua: Lua, lua: Lua,
(program, args, mut options): (String, Option<Vec<String>>, ProcessSpawnOptions), (program, args, mut options): (String, ProcessArgs, ProcessSpawnOptions),
) -> LuaResult<LuaTable> { ) -> LuaResult<LuaTable> {
let stdin = options.stdio.stdin.take(); let stdin = options.stdio.stdin.take();
let stdout = options.stdio.stdout; let stdout = options.stdio.stdout;
@ -171,7 +109,7 @@ async fn process_exec(
fn process_create( fn process_create(
lua: &Lua, lua: &Lua,
(program, args, options): (String, Option<Vec<String>>, ProcessSpawnOptions), (program, args, options): (String, ProcessArgs, ProcessSpawnOptions),
) -> LuaResult<LuaValue> { ) -> LuaResult<LuaValue> {
let child = options let child = options
.into_command(program, args) .into_command(program, args)

View file

@ -1,9 +1,11 @@
use std::{ use std::{
collections::HashMap, collections::HashMap,
env::{self}, env::{self},
ffi::OsString,
path::PathBuf, path::PathBuf,
}; };
use lune_utils::process::ProcessArgs;
use mlua::prelude::*; use mlua::prelude::*;
use async_process::Command; use async_process::Command;
@ -129,31 +131,24 @@ impl FromLua for ProcessSpawnOptions {
} }
impl ProcessSpawnOptions { impl ProcessSpawnOptions {
pub fn into_command(self, program: impl Into<String>, args: Option<Vec<String>>) -> Command { pub fn into_command(self, program: impl Into<OsString>, args: ProcessArgs) -> Command {
let mut program = program.into(); let mut program: OsString = program.into();
let mut args = args.into_iter().collect::<Vec<_>>();
// Run a shell using the command param if wanted // Run a shell using the command param if wanted
let pargs = match self.shell { if let Some(shell) = self.shell {
None => args, let mut shell_command = program.clone();
Some(shell) => { for arg in args {
let shell_args = match args { shell_command.push(" ");
Some(args) => vec!["-c".to_string(), format!("{} {}", program, args.join(" "))], shell_command.push(arg);
None => vec!["-c".to_string(), program.to_string()],
};
program = shell.to_string();
Some(shell_args)
} }
}; args = vec![OsString::from("-c"), shell_command];
program = shell.into();
}
// Create command with the wanted options // Create command with the wanted options
let mut cmd = match pargs { let mut cmd = Command::new(program);
None => Command::new(program), cmd.args(args);
Some(args) => {
let mut cmd = Command::new(program);
cmd.args(args);
cmd
}
};
// Set dir to run in and env variables // Set dir to run in and env variables
if let Some(cwd) = self.cwd { if let Some(cwd) = self.cwd {

View file

@ -17,6 +17,7 @@ mlua = { version = "0.10.3", features = ["luau", "async"] }
console = "0.15" console = "0.15"
dunce = "1.0" dunce = "1.0"
os_str_bytes = { version = "7.0", features = ["conversions"] }
path-clean = "1.0" path-clean = "1.0"
pathdiff = "0.2" pathdiff = "0.2"
parking_lot = "0.12.3" parking_lot = "0.12.3"

View file

@ -4,8 +4,13 @@ mod table_builder;
mod version_string; mod version_string;
pub mod fmt; pub mod fmt;
pub mod jit;
pub mod path; pub mod path;
pub mod process;
pub use self::table_builder::TableBuilder; pub use self::table_builder::TableBuilder;
pub use self::version_string::get_version_string; pub use self::version_string::get_version_string;
// TODO: Remove this in the next major semver
pub mod jit {
pub use super::process::ProcessJitEnablement as JitEnablement;
}

View file

@ -0,0 +1,252 @@
#![allow(clippy::missing_panics_doc)]
use std::{
env::args_os,
ffi::OsString,
sync::{Arc, Mutex},
};
use mlua::prelude::*;
use os_str_bytes::OsStringBytes;
// Inner (shared) struct
#[derive(Debug, Default)]
struct ProcessArgsInner {
values: Vec<OsString>,
}
impl FromIterator<OsString> for ProcessArgsInner {
fn from_iter<T: IntoIterator<Item = OsString>>(iter: T) -> Self {
Self {
values: iter.into_iter().collect(),
}
}
}
/**
A struct that can be easily shared, stored in Lua app data,
and that also guarantees the values are valid OS strings
that can be used for process arguments.
Usable directly from Lua, implementing both `FromLua` and `LuaUserData`.
Also provides convenience methods for working with the arguments
as either `OsString` or `Vec<u8>`, where using the latter implicitly
converts to an `OsString` and fails if the conversion is not possible.
*/
#[derive(Debug, Clone)]
pub struct ProcessArgs {
inner: Arc<Mutex<ProcessArgsInner>>,
}
impl ProcessArgs {
#[must_use]
pub fn empty() -> Self {
Self {
inner: Arc::new(Mutex::new(ProcessArgsInner::default())),
}
}
#[must_use]
pub fn current() -> Self {
Self {
inner: Arc::new(Mutex::new(args_os().collect())),
}
}
#[must_use]
pub fn len(&self) -> usize {
let inner = self.inner.lock().unwrap();
inner.values.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
let inner = self.inner.lock().unwrap();
inner.values.is_empty()
}
// OS strings
#[must_use]
pub fn all(&self) -> Vec<OsString> {
let inner = self.inner.lock().unwrap();
inner.values.clone()
}
#[must_use]
pub fn get(&self, index: usize) -> Option<OsString> {
let inner = self.inner.lock().unwrap();
inner.values.get(index).cloned()
}
pub fn set(&self, index: usize, val: impl Into<OsString>) {
let mut inner = self.inner.lock().unwrap();
if let Some(arg) = inner.values.get_mut(index) {
*arg = val.into();
}
}
pub fn push(&self, val: impl Into<OsString>) {
let mut inner = self.inner.lock().unwrap();
inner.values.push(val.into());
}
#[must_use]
pub fn pop(&self) -> Option<OsString> {
let mut inner = self.inner.lock().unwrap();
inner.values.pop()
}
pub fn insert(&self, index: usize, val: impl Into<OsString>) {
let mut inner = self.inner.lock().unwrap();
if index <= inner.values.len() {
inner.values.insert(index, val.into());
}
}
#[must_use]
pub fn remove(&self, index: usize) -> Option<OsString> {
let mut inner = self.inner.lock().unwrap();
if index < inner.values.len() {
Some(inner.values.remove(index))
} else {
None
}
}
// Bytes wrappers
#[must_use]
pub fn all_bytes(&self) -> Vec<Vec<u8>> {
self.all()
.into_iter()
.filter_map(OsString::into_io_vec)
.collect()
}
#[must_use]
pub fn get_bytes(&self, index: usize) -> Option<Vec<u8>> {
let val = self.get(index)?;
val.into_io_vec()
}
pub fn set_bytes(&self, index: usize, val: impl Into<Vec<u8>>) {
if let Some(val_os) = OsString::from_io_vec(val.into()) {
self.set(index, val_os);
}
}
pub fn push_bytes(&self, val: impl Into<Vec<u8>>) {
if let Some(val_os) = OsString::from_io_vec(val.into()) {
self.push(val_os);
}
}
#[must_use]
pub fn pop_bytes(&self) -> Option<Vec<u8>> {
self.pop().and_then(OsString::into_io_vec)
}
pub fn insert_bytes(&self, index: usize, val: impl Into<Vec<u8>>) {
if let Some(val_os) = OsString::from_io_vec(val.into()) {
self.insert(index, val_os);
}
}
pub fn remove_bytes(&self, index: usize) -> Option<Vec<u8>> {
self.remove(index).and_then(OsString::into_io_vec)
}
}
// Iterator implementations
impl IntoIterator for ProcessArgs {
type Item = OsString;
type IntoIter = std::vec::IntoIter<OsString>;
fn into_iter(self) -> Self::IntoIter {
let inner = self.inner.lock().unwrap();
inner.values.clone().into_iter()
}
}
impl<S: Into<OsString>> FromIterator<S> for ProcessArgs {
fn from_iter<T: IntoIterator<Item = S>>(iter: T) -> Self {
Self {
inner: Arc::new(Mutex::new(iter.into_iter().map(Into::into).collect())),
}
}
}
impl<S: Into<OsString>> Extend<S> for ProcessArgs {
fn extend<T: IntoIterator<Item = S>>(&mut self, iter: T) {
let mut inner = self.inner.lock().unwrap();
inner.values.extend(iter.into_iter().map(Into::into));
}
}
// Lua implementations
impl FromLua for ProcessArgs {
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
if let LuaValue::Nil = value {
Ok(Self::from_iter([] as [OsString; 0]))
} else if let LuaValue::Boolean(true) = value {
Ok(Self::current())
} else if let Some(u) = value.as_userdata().and_then(|u| u.borrow::<Self>().ok()) {
Ok(u.clone())
} else if let LuaValue::Table(arr) = value {
let mut args = Vec::new();
for pair in arr.pairs::<LuaValue, LuaValue>() {
let val_res = pair.map(|p| p.1.clone());
let val = super::lua_value_to_os_string(val_res, "ProcessArgs")?;
super::validate_os_value(&val)?;
args.push(val);
}
Ok(Self::from_iter(args))
} else {
Err(LuaError::FromLuaConversionError {
from: value.type_name(),
to: String::from("ProcessArgs"),
message: Some(format!(
"Invalid type for process args - expected table or nil, got '{}'",
value.type_name()
)),
})
}
}
}
impl LuaUserData for ProcessArgs {
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::Len, |_, this, (): ()| Ok(this.len()));
methods.add_meta_method(LuaMetaMethod::Index, |_, this, index: usize| {
if index == 0 {
Ok(None)
} else {
Ok(this.get(index - 1))
}
});
methods.add_meta_method(LuaMetaMethod::NewIndex, |_, _, (): ()| {
Err::<(), _>(LuaError::runtime("ProcessArgs is read-only"))
});
methods.add_meta_method(LuaMetaMethod::Iter, |lua, this, (): ()| {
let mut vars = this
.clone()
.into_iter()
.filter_map(OsStringBytes::into_io_vec)
.enumerate();
lua.create_function_mut(move |lua, (): ()| match vars.next() {
None => Ok((LuaValue::Nil, LuaValue::Nil)),
Some((index, value)) => Ok((
LuaValue::Integer(index as i32),
LuaValue::String(lua.create_string(value)?),
)),
})
});
}
}

View file

@ -0,0 +1,254 @@
#![allow(clippy::missing_panics_doc)]
use std::{
collections::BTreeMap,
env::vars_os,
ffi::{OsStr, OsString},
sync::{Arc, Mutex},
};
use mlua::prelude::*;
use os_str_bytes::{OsStrBytes, OsStringBytes};
// Inner (shared) struct
#[derive(Debug, Default)]
struct ProcessEnvInner {
values: BTreeMap<OsString, OsString>,
}
impl FromIterator<(OsString, OsString)> for ProcessEnvInner {
fn from_iter<T: IntoIterator<Item = (OsString, OsString)>>(iter: T) -> Self {
Self {
values: iter.into_iter().collect(),
}
}
}
/**
A struct that can be easily shared, stored in Lua app data,
and that also guarantees the pairs are valid OS strings
that can be used for process environment variables.
Usable directly from Lua, implementing both `FromLua` and `LuaUserData`.
Also provides convenience methods for working with the variables
as either `OsString` or `Vec<u8>`, where using the latter implicitly
converts to an `OsString` and fails if the conversion is not possible.
*/
#[derive(Debug, Clone)]
pub struct ProcessEnv {
inner: Arc<Mutex<ProcessEnvInner>>,
}
impl ProcessEnv {
#[must_use]
pub fn empty() -> Self {
Self {
inner: Arc::new(Mutex::new(ProcessEnvInner::default())),
}
}
#[must_use]
pub fn current() -> Self {
Self {
inner: Arc::new(Mutex::new(vars_os().collect())),
}
}
#[must_use]
pub fn len(&self) -> usize {
let inner = self.inner.lock().unwrap();
inner.values.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
let inner = self.inner.lock().unwrap();
inner.values.is_empty()
}
// OS strings
#[must_use]
pub fn get_all(&self) -> Vec<(OsString, OsString)> {
let inner = self.inner.lock().unwrap();
inner.values.clone().into_iter().collect()
}
#[must_use]
pub fn get_value(&self, key: impl AsRef<OsStr>) -> Option<OsString> {
let key = key.as_ref();
super::validate_os_key(key).ok()?;
let inner = self.inner.lock().unwrap();
inner.values.get(key).cloned()
}
pub fn set_value(&self, key: impl Into<OsString>, val: impl Into<OsString>) {
let key = key.into();
let val = val.into();
if super::validate_os_pair((&key, &val)).is_err() {
return;
}
let mut inner = self.inner.lock().unwrap();
inner.values.insert(key, val);
}
pub fn remove_value(&self, key: impl AsRef<OsStr>) {
let key = key.as_ref();
if super::validate_os_key(key).is_err() {
return;
}
let mut inner = self.inner.lock().unwrap();
inner.values.remove(key);
}
// Bytes wrappers
#[must_use]
pub fn get_all_bytes(&self) -> Vec<(Vec<u8>, Vec<u8>)> {
self.get_all()
.into_iter()
.filter_map(|(k, v)| Some((k.into_io_vec()?, v.into_io_vec()?)))
.collect()
}
#[must_use]
pub fn get_value_bytes(&self, key: impl AsRef<[u8]>) -> Option<Vec<u8>> {
let key = OsStr::from_io_bytes(key.as_ref())?;
let val = self.get_value(key)?;
val.into_io_vec()
}
pub fn set_value_bytes(&self, key: impl AsRef<[u8]>, val: impl Into<Vec<u8>>) {
let key = OsStr::from_io_bytes(key.as_ref());
let val = OsString::from_io_vec(val.into());
if let (Some(key), Some(val)) = (key, val) {
self.set_value(key, val);
}
}
pub fn remove_value_bytes(&self, key: impl AsRef<[u8]>) {
let key = OsStr::from_io_bytes(key.as_ref());
if let Some(key) = key {
self.remove_value(key);
}
}
}
// Iterator implementations
impl IntoIterator for ProcessEnv {
type Item = (OsString, OsString);
type IntoIter = std::collections::btree_map::IntoIter<OsString, OsString>;
fn into_iter(self) -> Self::IntoIter {
let inner = self.inner.lock().unwrap();
inner.values.clone().into_iter()
}
}
impl<K: Into<OsString>, V: Into<OsString>> FromIterator<(K, V)> for ProcessEnv {
fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
Self {
inner: Arc::new(Mutex::new(
iter.into_iter()
.map(|(k, v)| (k.into(), v.into()))
.filter(|(k, v)| super::validate_os_pair((k, v)).is_ok())
.collect(),
)),
}
}
}
impl<K: Into<OsString>, V: Into<OsString>> Extend<(K, V)> for ProcessEnv {
fn extend<T: IntoIterator<Item = (K, V)>>(&mut self, iter: T) {
let mut inner = self.inner.lock().unwrap();
inner.values.extend(
iter.into_iter()
.map(|(k, v)| (k.into(), v.into()))
.filter(|(k, v)| super::validate_os_pair((k, v)).is_ok()),
);
}
}
// Lua implementations
impl FromLua for ProcessEnv {
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
if let LuaValue::Nil = value {
Ok(Self::from_iter([] as [(OsString, OsString); 0]))
} else if let LuaValue::Boolean(true) = value {
Ok(Self::current())
} else if let Some(u) = value.as_userdata().and_then(|u| u.borrow::<Self>().ok()) {
Ok(u.clone())
} else if let LuaValue::Table(arr) = value {
let mut args = Vec::new();
for pair in arr.pairs::<LuaValue, LuaValue>() {
let (key_res, val_res) = match pair {
Ok((key, val)) => (Ok(key), Ok(val)),
Err(err) => (Err(err.clone()), Err(err)),
};
let key = super::lua_value_to_os_string(key_res, "ProcessEnv")?;
let val = super::lua_value_to_os_string(val_res, "ProcessEnv")?;
super::validate_os_pair((&key, &val))?;
args.push((key, val));
}
Ok(Self::from_iter(args))
} else {
Err(LuaError::FromLuaConversionError {
from: value.type_name(),
to: String::from("ProcessEnv"),
message: Some(format!(
"Invalid type for process env - expected table or nil, got '{}'",
value.type_name()
)),
})
}
}
}
impl LuaUserData for ProcessEnv {
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::Len, |_, this, (): ()| Ok(this.len()));
methods.add_meta_method(LuaMetaMethod::Index, |_, this, key: LuaValue| {
let key = super::lua_value_to_os_string(Ok(key), "OsString")?;
Ok(this.get_value(key))
});
methods.add_meta_method(
LuaMetaMethod::NewIndex,
|_, this, (key, val): (LuaValue, Option<LuaValue>)| {
let key = super::lua_value_to_os_string(Ok(key), "OsString")?;
if let Some(val) = val {
let val = super::lua_value_to_os_string(Ok(val), "OsString")?;
this.set_value(key, val);
} else {
this.remove_value(key);
}
Ok(())
},
);
methods.add_meta_method(LuaMetaMethod::Iter, |lua, this, (): ()| {
let mut vars = this
.clone()
.into_iter()
.filter_map(|(key, val)| Some((key.into_io_vec()?, val.into_io_vec()?)));
lua.create_function_mut(move |lua, (): ()| match vars.next() {
None => Ok((LuaValue::Nil, LuaValue::Nil)),
Some((key, val)) => Ok((
LuaValue::String(lua.create_string(key)?),
LuaValue::String(lua.create_string(val)?),
)),
})
});
}
}

View file

@ -1,29 +1,31 @@
#[derive(Debug, Clone, Copy, Default)] #[derive(Debug, Clone, Copy, Default)]
pub struct JitEnablement(bool); pub struct ProcessJitEnablement {
enabled: bool,
}
impl JitEnablement { impl ProcessJitEnablement {
#[must_use] #[must_use]
pub fn new(enabled: bool) -> Self { pub fn new(enabled: bool) -> Self {
Self(enabled) Self { enabled }
} }
pub fn set_status(&mut self, enabled: bool) { pub fn set_status(&mut self, enabled: bool) {
self.0 = enabled; self.enabled = enabled;
} }
#[must_use] #[must_use]
pub fn enabled(self) -> bool { pub fn enabled(self) -> bool {
self.0 self.enabled
} }
} }
impl From<JitEnablement> for bool { impl From<ProcessJitEnablement> for bool {
fn from(val: JitEnablement) -> Self { fn from(val: ProcessJitEnablement) -> Self {
val.enabled() val.enabled()
} }
} }
impl From<bool> for JitEnablement { impl From<bool> for ProcessJitEnablement {
fn from(val: bool) -> Self { fn from(val: bool) -> Self {
Self::new(val) Self::new(val)
} }

View file

@ -0,0 +1,78 @@
use std::ffi::{OsStr, OsString};
use mlua::prelude::*;
use os_str_bytes::{OsStrBytes, OsStringBytes};
mod args;
mod env;
mod jit;
pub use self::args::ProcessArgs;
pub use self::env::ProcessEnv;
pub use self::jit::ProcessJitEnablement;
fn lua_value_to_os_string(res: LuaResult<LuaValue>, to: &'static str) -> LuaResult<OsString> {
let (btype, bs) = match res {
Ok(LuaValue::String(s)) => ("string", s.as_bytes().to_vec()),
Ok(LuaValue::Buffer(b)) => ("buffer", b.to_vec()),
res => {
let vtype = match res {
Ok(v) => v.type_name(),
Err(_) => "unknown",
};
return Err(LuaError::FromLuaConversionError {
from: vtype,
to: String::from(to),
message: Some(format!(
"Expected value to be a string or buffer, got '{vtype}'",
)),
});
}
};
let Some(s) = OsString::from_io_vec(bs) else {
return Err(LuaError::FromLuaConversionError {
from: btype,
to: String::from(to),
message: Some(String::from("Expected {btype} to contain valid OS bytes")),
});
};
Ok(s)
}
fn validate_os_key(key: &OsStr) -> LuaResult<()> {
let Some(key) = key.to_io_bytes() else {
return Err(LuaError::runtime("Key must be IO-safe"));
};
if key.is_empty() {
Err(LuaError::runtime("Key must not be empty"))
} else if key.contains(&b'=') {
Err(LuaError::runtime(
"Key must not contain the equals character '='",
))
} else if key.contains(&b'\0') {
Err(LuaError::runtime("Key must not contain the NUL character"))
} else {
Ok(())
}
}
fn validate_os_value(val: &OsStr) -> LuaResult<()> {
let Some(val) = val.to_io_bytes() else {
return Err(LuaError::runtime("Value must be IO-safe"));
};
if val.contains(&b'\0') {
Err(LuaError::runtime(
"Value must not contain the NUL character",
))
} else {
Ok(())
}
}
fn validate_os_pair((key, value): (&OsStr, &OsStr)) -> LuaResult<()> {
validate_os_key(key)?;
validate_os_value(value)?;
Ok(())
}

View file

@ -5,9 +5,10 @@ assert(#process.args > 0, "No process arguments found")
assert(process.args[1] == "Foo", "Invalid first argument to process") assert(process.args[1] == "Foo", "Invalid first argument to process")
assert(process.args[2] == "Bar", "Invalid second argument to process") assert(process.args[2] == "Bar", "Invalid second argument to process")
local success, message = pcall(function() local success, err = pcall(function()
process.args[1] = "abc" process.args[1] = "abc"
end) end)
local message = if err == nil then nil else tostring(err)
assert( assert(
success == false and type(message) == "string" and #message > 0, success == false and type(message) == "string" and #message > 0,
"Trying to set process arguments should throw an error with a message" "Trying to set process arguments should throw an error with a message"