2 Commits

Author SHA1 Message Date
428da76a81 fix(config): 优化 layout
- 新增 从环境变量解析目录结构
2026-08-20 14:30:14 +08:00
f59040c11e fix(config): 优化配置解析
- 完善 `FromLua` 转换与边界校验,拦截空洞 (`nil`) 及非整数键
2026-08-19 11:34:16 +08:00
10 changed files with 306 additions and 592 deletions

331
Cargo.lock generated
View File

@@ -1,331 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "autocfg"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "bitflags"
version = "2.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
[[package]]
name = "bstr"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f"
dependencies = [
"memchr",
"serde_core",
]
[[package]]
name = "cc"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d262e149917187838d5b42777c8253bcb64500067342904e7d429499a6f277e"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "either"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d"
[[package]]
name = "find-msvc-tools"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de"
[[package]]
name = "fs-err"
version = "3.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b91aa448ca50d7e79433bdf3ee8d99215430d2ec02ade5aefab2a073a1822e8a"
dependencies = [
"autocfg",
]
[[package]]
name = "libc"
version = "0.2.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"scopeguard",
]
[[package]]
name = "lua-src"
version = "550.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75c110c2fa33f34e0de05448e1f3eb2e0631e7a69e2d8ae1586cffc9fc9f9949"
dependencies = [
"cc",
]
[[package]]
name = "luajit-src"
version = "210.7.2+b925b3e"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "920cf654b23d217c550ceea57c32cd2a413ea27b6d47ed77b5ee0cf655adefa6"
dependencies = [
"cc",
"which",
]
[[package]]
name = "memchr"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "mlua"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad72ffa037cf5970c9860674f32f703fda25d86cf217475fe7a79c5f9961bcaa"
dependencies = [
"bstr",
"either",
"libc",
"mlua-sys",
"num-traits",
"parking_lot",
"rustc-hash",
]
[[package]]
name = "mlua-sys"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92136787b906d4e55cfe96cd6c62e010bb1a56889d0d6cf83eb016dbad07576b"
dependencies = [
"cc",
"cfg-if",
"libc",
"lua-src",
"luajit-src",
"pkg-config",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "parking_lot"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-link",
]
[[package]]
name = "pkg-config"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
[[package]]
name = "proc-macro2"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags",
]
[[package]]
name = "rshim"
version = "0.1.0"
dependencies = [
"fs-err",
"mlua",
"thiserror",
"unicode-bom",
"winapi",
]
[[package]]
name = "rustc-hash"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde_core"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "shlex"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
[[package]]
name = "smallvec"
version = "1.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
[[package]]
name = "syn"
version = "3.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "2.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "2.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-bom"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217"
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "which"
version = "8.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f3ef584124b911bcc3875c2f1472e80f24361ceb789bd1c62b3e9a3df9ff43c"
dependencies = [
"libc",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"

View File

@@ -11,9 +11,9 @@ opt-level = "z"
panic = "abort"
[dependencies]
thiserror = "2.0.20"
anyhow = "1.0.104"
mlua = { version = "0.12.0", features = ["lua54", "vendored"] }
mlua = { version = "0.12.0", features = ["lua54", "vendored","send"] }
windows-sys = { version = "0.61.2", features = [
"Win32_Foundation",
"Win32_System_Com",

View File

@@ -34,13 +34,13 @@ fn main() {
}
// 复制文件
let source = PathBuf::from(&manifest_dir).join("shims.lua");
let source = PathBuf::from(&manifest_dir).join("mimic.lua");
let destination = output_dir.join("rshim.lua");
if source.exists() {
fs::copy(&source, &destination).expect("Failed to copy shims.lua");
println!("Copied shims.lua to: {:?}", destination);
fs::copy(&source, &destination).expect("Failed to copy mimic.lua");
println!("Copied mimic.lua to: {:?}", destination);
} else {
panic!("shims.lua not found at: {:?}", source);
panic!("mimic.lua not found at: {:?}", source);
}
}

View File

@@ -1,4 +1,4 @@
-- shims.lua (总控制台)
-- mimic.lua (总控制台)
-- __SHIM_DIR__ 已经在 Rust 中注入完毕,并且是正斜杠格式 (例如 C:/Tools)
local base_dir = __SHIM_DIR__

View File

@@ -1,9 +1,10 @@
use mlua::{FromLua, Lua, LuaString, ObjectLike, Table, Value};
use std::collections::HashMap;
use std::ffi::OsString;
use std::fmt;
use std::fmt::Display;
use std::path::PathBuf;
use std::process::Command;
/// 构造一个带上下文的 FromLua 转换错误,便于定位配置问题
fn conversion_error(message: impl Into<String>) -> mlua::Error {
mlua::Error::FromLuaConversionError {
@@ -13,8 +14,8 @@ fn conversion_error(message: impl Into<String>) -> mlua::Error {
}
}
/// 将 Lua 字符串转为 Rust 字符串;非 UTF-8 字节替换为 U+FFFD 并告警
fn lua_string_to_os_string(s: LuaString) -> Result<OsString, mlua::Error> {
/// 将 Lua 字符串转为 Rust 字符串;非 UTF-8 按系统默认的 ANSI/OEM (如 GBK) 进行安全解码
fn lua_string_2_os_string(s: LuaString) -> mlua::Result<OsString> {
let raw_bytes = &s.as_bytes().to_vec();
// 1. Unix 平台:直接零拷贝透传原始字节(无损支持任意编码)
#[cfg(unix)]
@@ -34,7 +35,7 @@ fn lua_string_to_os_string(s: LuaString) -> Result<OsString, mlua::Error> {
let (cow, _, had_errors) = encoding_rs::GBK.decode(raw_bytes);
if had_errors {
return Err(mlua::Error::RuntimeError(
"Path contains invalid/unsupported byte encoding".into(),
"路径包含无效/不支持的字节编码".into(),
));
}
Ok(OsString::from(cow.as_ref()))
@@ -42,10 +43,34 @@ fn lua_string_to_os_string(s: LuaString) -> Result<OsString, mlua::Error> {
}
/// 按连续整数下标遍历表的序列部分;存在空洞或非序列键时返回错误,避免静默截断
fn for_each_sequence_item(
/// 值分流与上下文分发枚举
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ValueShunt<'a> {
/// 命令行参数元素:仅支持一维基础标量,禁止嵌套 Table
Args,
/// 环境变量值:支持基础标量及多维嵌套 Table递归展平
Env(&'a str),
}
impl<'a> Display for ValueShunt<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Args => write!(f, "命令行参数 (args)"),
Self::Env(name) => write!(f, "环境变量 ({name})"),
}
}
}
impl<'a> ValueShunt<'a> {
fn parse_sequence(&self, tbl: &Table, out: &mut Vec<OsString>) -> mlua::Result<()> {
self.validate_each_sequence_item(tbl, |item| self.collect_into(item, out))
}
fn validate_each_sequence_item(
&self,
tbl: &Table,
mut f: impl FnMut(Value) -> mlua::Result<()>,
) -> mlua::Result<()> {
) -> mlua::Result<()> {
let mut index = 1i64;
loop {
let item: Value = tbl.raw_get(index)?;
@@ -60,19 +85,91 @@ fn for_each_sequence_item(
let (key, _) = pair?;
match key {
Value::Integer(i) if i >= 1 && i < index => {}
_ => {
Value::Integer(_) => {
return Err(conversion_error(format!(
"数组只能包含连续的整数下标 [1..{}],发现非序列键或空洞",
index - 1
"{}{} 个元素为 nil 或不存在(请检查是否漏写引号或变量未定义)",
self, index
)));
}
_ => {
// let label = match self {
// ValueShunt::Args => "命令行参数 (args)".to_string(),
// ValueShunt::Env(n) => format!("环境变量 [{n}]"),
// };
return Err(conversion_error(format!(
"{} 必须是纯列表,不能包含键值对/字典结构",
self
)));
}
}
}
Ok(())
}
}
impl<'a> ValueShunt<'a> {
/// 递归/标量收集实现
pub fn collect_into(&self, value: Value, out: &mut Vec<OsString>) -> mlua::Result<()> {
match value {
Value::Nil => {}
Value::String(s) => out.push(lua_string_2_os_string(s)?),
Value::Integer(i) => out.push(OsString::from(i.to_string())),
Value::Number(n) => {
tracing::warn!(context = %self, value = %n, "浮点数将按十进制格式转换为字符串");
out.push(OsString::from(n.to_string()));
}
Value::Boolean(b) => {
tracing::warn!(context = %self, value = %b, "布尔值将转换为字符串");
out.push(OsString::from(b.to_string()));
}
// 嵌套 Table 仅在 EnvValue 下允许递归展开(支持 get_env("PATH") 等返回的子表)
// Value::Table(tbl) if *self == Self::Env(_) => {
// self.validate_each_sequence_item(&tbl, |item| self.collect_into(item, out))?;
// }
Value::Table(tbl) => {
match self {
Self::Args => {
return Err(conversion_error(format!(
"{} 不支持 {} 类型, 仅支持 string/number/boolean",
self,
tbl.to_string()?
)));
}
Self::Env(_) => {
self.validate_each_sequence_item(&tbl, |item| {
self.collect_into(item, out)
})?;
}
}
// self.validate_each_sequence_item(&tbl, |item| self.collect_into(item, out))?;
}
other => {
return Err(conversion_error(format!(
"{} 不支持 {} 类型, 仅支持 string/number/boolean 或包含这些类型的数组/嵌套数组",
self,
other.type_name(),
)));
}
}
Ok(())
}
}
/// 校验环境变量名的合法性Windows 约束:非空、不含 '='、不含 NUL
fn validate_env_key(key: &str) -> mlua::Result<()> {
fn validate_env_var_name(key_val: &Value, val: &Value) -> mlua::Result<String> {
let key = match key_val {
Value::String(s) => s.to_str()?.to_string(),
_ => {
return Err(conversion_error(format!(
r#"env 配置解析失败:值: {} (类型: {}) 无效的键值!
示例 HOME="/home" 或 PATH = {{ "C:/tools/", "D:/Windows"}}"#,
val.to_string()?,
val.type_name()
)));
}
};
if key.is_empty() {
return Err(conversion_error("环境变量名不能为空"));
}
@@ -88,40 +185,11 @@ fn validate_env_key(key: &str) -> mlua::Result<()> {
key
)));
}
Ok(())
Ok(key)
}
/// 递归将任意 Lua Value 展开为扁平的字符串片段列表
fn collect_env_strings(value: Value, out: &mut Vec<OsString>) -> mlua::Result<()> {
match value {
// 1. 字符串
Value::String(s) => out.push(lua_string_to_os_string(s)?),
// 2. 整数与浮点数
Value::Integer(i) => out.push(OsString::from(i.to_string())),
Value::Number(n) => {
tracing::warn!(value = %n, "环境变量中的浮点数将按十进制格式转换为字符串");
out.push(OsString::from(n.to_string()));
}
// 3. 布尔值
Value::Boolean(b) => {
tracing::warn!(value = %b, "环境变量中的布尔值将转换为字符串");
out.push(OsString::from(b.to_string()));
}
// 4. 表/数组:必须是连续整数下标的纯序列,递归解包(支持任意深度的嵌套数组)
Value::Table(tbl) => for_each_sequence_item(&tbl, |item| collect_env_strings(item, out))?,
// 5. 安全忽
// 略 nil
Value::Nil => {}
// 6. 无法转为环境变量的非法类型函数、协程、UserData 等):报错而非静默忽略
other => {
return Err(conversion_error(format!(
"环境变量值不支持类型 {}(仅支持 string/number/boolean/数组)",
other.type_name()
)));
}
}
Ok(())
}
// fn collect_env_segments(value: Value, out: &mut Vec<OsString>) -> mlua::Result<()>{OK(())}
#[derive(Debug, Clone, Default)]
pub struct ShimConfig {
@@ -170,7 +238,7 @@ impl FromLua for ShimConfig {
// 必填字段: target严格限定为字符串避免数字被 mlua 宽松转为字符串后掩盖错误)
let target = match table.get::<Value>("target")? {
Value::String(s) => lua_string_to_os_string(s)?,
Value::String(s) => lua_string_2_os_string(s)?,
Value::Nil => {
return Err(conversion_error("缺少必填字段 target应为字符串路径"));
}
@@ -181,67 +249,43 @@ impl FromLua for ShimConfig {
)));
}
};
// 可选字段: args缺失或 nil 时默认空列表;保留空字符串参数,与提权路径的 "" 语义一致)
// let args = match table.get::<Option<Value>>("args")? {
// None => Vec::new(),
// Some(Value::Table(tbl)) => {
// let mut args = Vec::new();
// for_each_sequence_item(&tbl, |item| match item {
// Value::String(s) => {
// args.push(lua_string_to_string(s));
// Ok(())
// }
// other => Err(conversion_error(format!(
// "args 数组元素必须是字符串,实际是 {}",
// other.type_name()
// ))),
// })?;
// args
// }
let args = match table.get::<Option<Value>>("args")? {
None => Vec::new(),
Some(Value::Table(tbl)) => {
let mut args = Vec::new();
for_each_sequence_item(&tbl, |item| match item {
Value::String(s) => {
args.push(lua_string_to_os_string(s)?);
Ok(())
}
other => Err(conversion_error(format!(
"args 数组元素必须是字符串,实际是 {}",
other.type_name()
))),
})?;
args
}
// 可选字段: args缺失或 nil 时默认空列表;保留空字符串参数,与提权路径的 "" 语义一致)
match table.get::<Option<Value>>("args")? {
None | Some(Value::Nil) => {}
Some(Value::Table(tbl)) => ValueShunt::Args.parse_sequence(&tbl, &mut args)?,
Some(other) => {
return Err(conversion_error(format!(
"args 必须是字符串数组,实际是 {}",
"args 必须是数组列表,实际类型{}",
other.type_name()
)));
}
};
// 可选字段: env只允许缺失/nil其他类型由 Option<Table> 转换报错,不再静默忽略)
let env_table = table.get::<Option<Table>>("env")?;
let env_table = table.get::<Option<Value>>("env")?;
println!("env_table:{:?}", env_table);
let mut env = HashMap::new();
if let Some(env_table) = env_table {
for pair in env_table.pairs::<String, Value>() {
let (key, value) = pair?;
validate_env_key(&key)?;
match env_table {
None | Some(Value::Nil) => {}
Some(Value::Table(tbl)) => {
for pair in tbl.pairs::<Value, Value>() {
let (name, value) = pair?;
let name = validate_env_var_name(&name, &value)?;
println!("==========={name}:{:?}", value);
let mut parts = Vec::new();
collect_env_strings(value, &mut parts)?;
ValueShunt::Env(&name).collect_into(value, &mut parts)?;
// 空数组/空串也显式设置(空值表示清空该变量),
// 与“未配置该变量(继承宿主环境)”相区分
let joined_os_str = std::env::join_paths(parts).map_err(|e| {
conversion_error(format!(
"环境变量 [{}] 的值无法用系统路径分隔符拼接: {}",
key, e
name, e
))
})?;
println!("环境变量拼接结果:{:?}", joined_os_str);
// println!("环境变量拼接结果:{:?}", joined_os_str);
// 检查是否包含非法 NUL 字符
#[cfg(unix)]
{
@@ -259,12 +303,19 @@ impl FromLua for ShimConfig {
if joined_os_str.encode_wide().any(|c| c == 0) {
return Err(conversion_error(format!(
"环境变量 [{}] 的值不能包含 NUL 字符",
key
name
)));
}
}
env.insert(key, joined_os_str);
env.insert(name, joined_os_str);
}
}
Some(other) => {
return Err(conversion_error(format!(
"env 必须是键值表 (table),实际类型是 {}",
other.type_name()
)));
}
}
println!("环境变量结果:{:?}", env);
@@ -312,7 +363,7 @@ mod tests {
target = "C:/tools/git.exe",
args = { "--no-pager" },
env = {
PATH = { "C:/tools/git/bin", "C:/Windows",get_env("PATH") },
PATH = { "C:/tools/git/bin", "C:/Windows", get_env("PATH") },
HOME = "C:/tools/home",
CONST = 3,
BOOL = true
@@ -423,7 +474,9 @@ mod tests {
#[test]
fn rejects_non_string_args_element() {
assert!(parse(r#"return { target = "t.exe", args = { 1 } }"#).is_err());
// ValueShunt::Args 允许基础标量(数字/布尔)转字符串,仅禁止嵌套表
let cfg = parse(r#"return { target = "t.exe", args = { 1, true } }"#).unwrap();
assert_eq!(cfg.args, vec!["1", "true"]);
}
#[test]

View File

@@ -1,26 +0,0 @@
use thiserror::Error; // 推荐引入 thiserror 库,若不使用可手动实现 std::fmt::Display
#[derive(Debug, Error)]
pub enum ShimError {
#[error("路径解析失败: {0}")]
PathResolution(String),
#[error("获取环境信息失败: {0}")]
Environment(String),
#[error("配置文件未找到: {0}")]
ConfigMissing(String),
#[error("Lua 运行时/语法错误 [{file}]: {source}")]
LuaExecution {
file: String,
#[source]
source: mlua::Error,
},
#[error("配置格式非法: {0}")]
InvalidConfig(String),
#[error("IO 错误: {0}")]
Io(#[from] std::io::Error),
}

View File

@@ -1,45 +1,67 @@
use crate::error::ShimError;
use anyhow::{Context, Result, bail};
use std::env;
use std::path::{Path, PathBuf};
pub struct ShimLayout {
pub root_dir: PathBuf,
use tracing::debug;
pub struct Layout {
pub base_dir: PathBuf,
pub bin_dir: PathBuf,
pub tools_dir: PathBuf,
pub lua_file: PathBuf,
}
impl ShimLayout {
/// 从当前可执行文件解析 shim 安装目录布局
pub fn from_executable(exe_path: impl AsRef<Path>) -> Result<Self, ShimError> {
let exe_path = exe_path.as_ref();
let bin_dir = exe_path
.parent()
.ok_or_else(|| {
ShimError::PathResolution(format!(
"无法获取程序 [{}] 的父级 bin 目录",
exe_path.display()
))
})?
.to_path_buf();
eprintln!("bin_dir目录 {}", bin_dir.display());
impl Layout {
/// 自动解析目录布局
/// 1. 优先使用环境变量 RSHIM_HOME
/// 2. 兜底回退到当前垫片可执行文件所在目录推断 (exe -> bin -> root)
pub fn discover(current_exe: &Path) -> Result<Self> {
// 策略 1: 环境变量优先
if let Ok(home_val) = env::var("MIMIC_HOME") {
let trimmed = home_val.trim();
if !trimmed.is_empty() {
debug!(home = %trimmed, "检测到 MIMIC_HOME采用环境变量配置");
return Self::from_base_dir(PathBuf::from(trimmed));
}
}
let root_dir = bin_dir
.parent()
.ok_or_else(|| {
ShimError::PathResolution(format!(
"无法获取 bin 目录 [{}] 的父级 root 目录",
bin_dir.display()
))
})?
.to_path_buf();
eprintln!("root_dir 目录 {}", root_dir.display());
// 策略 2: 相对路径自动推断兜底
debug!("未配置 MIMIC_HOME尝试从当前可执行文件路径推断根目录");
Self::from_executable(current_exe)
}
let tools_dir = root_dir.join("tools");
eprintln!("tools_dir 目录 {}", tools_dir.display());
/// 基于确定的根目录构建完整布局
fn from_base_dir(base_dir: PathBuf) -> Result<Self> {
if !base_dir.is_dir() {
bail!("指定的根目录不存在或不是有效目录: [{}]", base_dir.display());
}
let bin_dir = base_dir.join("bin");
let tools_dir = base_dir.join("tools");
let lua_file = base_dir.join("mimic.lua");
Ok(Self {
root_dir,
base_dir,
bin_dir,
tools_dir,
lua_file,
})
}
/// 从当前可执行文件解析 shim 安装目录布局
fn from_executable(exe_path: &Path) -> Result<Self> {
// let exe_path = exe_path.as_ref();
let bin_dir = exe_path
.parent()
.with_context(|| format!("无法获取程序 [{}] 的父级 bin 目录", exe_path.display()))?
.to_path_buf();
debug!("bin_dir目录 {}", bin_dir.display());
let base_dir = bin_dir
.parent()
.with_context(|| format!("无法获取 bin 目录 [{}] 的父级 root 目录", bin_dir.display()))?
.to_path_buf();
debug!("base_dir 目录 {}", base_dir.display());
Self::from_base_dir(base_dir)
}
}

View File

@@ -1,12 +1,11 @@
mod config;
mod error;
mod layout;
mod logger;
mod runtime;
mod shim;
mod loader;
pub use config::ShimConfig;
pub use error::ShimError;
pub use layout::ShimLayout;
pub use layout::Layout;
pub use runtime::LuaRuntime;
pub use shim::Shim;

View File

@@ -1,5 +1,5 @@
use crate::error::ShimError;
use crate::{ShimConfig, ShimLayout};
use crate::{ShimConfig, Layout};
use anyhow::{Context, Result, anyhow, bail};
use mlua::{FromLua, Lua, StdLib, Table, Value};
use std::ffi::OsStr;
use std::path::Path;
@@ -17,24 +17,24 @@ pub struct LuaRuntime {
impl LuaRuntime {
/// 初始化限定权限的 Lua 沙箱环境
pub fn new(layout: &ShimLayout) -> Result<Self, ShimError> {
pub fn new(layout: &Layout) -> Result<Self> {
// 只加载安全的标准库,剥离 os / io 等风险模块
let lua = Lua::new_with(
StdLib::TABLE | StdLib::STRING | StdLib::MATH | StdLib::PACKAGE,
mlua::LuaOptions::default(),
)
.map_err(|e| ShimError::Environment(format!("初始化 Lua 失败: {}", e)))?;
.context("初始化 Lua 失败")?;
let globals = lua.globals();
// 统一使用 POSIX 风格路径规范化路径字符串
let root_dir = normalize_path_for_lua(&layout.root_dir);
let base_dir = normalize_path_for_lua(&layout.base_dir);
let tools_dir = normalize_path_for_lua(&layout.tools_dir);
// 1. 注入锚点变量 __SHIM_DIR__shim 安装根目录)
globals
.set("__SHIM_DIR__", root_dir.clone())
.map_err(|e| ShimError::Environment(e.to_string()))?;
.set("__SHIM_DIR__", base_dir.clone())
.context("设置 __SHIM_DIR__ 环境变量失败")?;
// 2. 安全暴露 get_env 供配置读取环境变量
// 返回按平台路径分隔符拆分后的段数组(自动剥离引号包裹),
@@ -68,11 +68,11 @@ impl LuaRuntime {
Ok(table)
})
.map_err(|e| ShimError::Environment(e.to_string()))?;
.context("注册 get_env 函数失败")?;
globals
.set("get_env", get_env)
.map_err(|e| ShimError::Environment(e.to_string()))?;
.context("挂载 get_env 全局函数失败")?;
// 3. 配置 package.path确保 require 行为正常
if let Ok(package) = globals.get::<Table>("package") {
@@ -82,20 +82,20 @@ impl LuaRuntime {
if let Ok(path) = package.get::<String>("path") {
let new_path = format!(
"{};{}/?.lua;{}/?/init.lua;{}/?.lua;{}/?/init.lua",
path, root_dir, root_dir, tools_dir, tools_dir
path, base_dir, base_dir, tools_dir, tools_dir
);
let _ = package.set("path", new_path);
}
}
// 4. 包装 require配置模块缺失/加载失败时记录日志并跳过该条目,
// 而不是让整个 shims.lua 解析失败(排查问题时日志可见)
// 而不是让整个 mimic.lua 解析失败(排查问题时日志可见)
let original_require: mlua::Function = globals
.get("require")
.map_err(|e| ShimError::Environment(format!("获取 require 失败: {}", e)))?;
.context("获取内置 require 函数失败")?;
globals
.set("_rshim_original_require", &original_require)
.map_err(|e| ShimError::Environment(e.to_string()))?;
.context("备份原始 require 函数失败")?;
let wrapped_require = lua
.create_function(|lua, module: String| -> mlua::Result<Value> {
@@ -112,26 +112,27 @@ impl LuaRuntime {
}
}
})
.map_err(|e| ShimError::Environment(e.to_string()))?;
.context("创建包装版 require 函数失败")?;
globals
.set("require", wrapped_require)
.map_err(|e| ShimError::Environment(e.to_string()))?;
.context("重载 require 函数失败")?;
Ok(Self { lua })
}
/// 执行指定脚本文件,直接返回完整的 Lua Table
pub fn eval_script<T: FromLua>(&self, path: impl AsRef<Path>) -> Result<T, ShimError> {
pub fn eval_script<T: FromLua>(&self, path: impl AsRef<Path>) -> Result<T> {
let path = path.as_ref();
// println!("path {:?}", path);
let bytes = fs::read(path)?;
let code = String::from_utf8(bytes).map_err(|e| {
ShimError::InvalidConfig(format!(
"{} 不是有效的 UTF-8 文件(请将 Lua 配置文件保存为 UTF-8 编码): {}",
path.display(),
e.utf8_error()
))
let bytes =
fs::read(path).with_context(|| format!("无法读取配置文件: {}", path.display()))?;
let code = String::from_utf8(bytes).with_context(|| {
format!(
"{} 不是有效的 UTF-8 文件(请将 Lua 配置文件保存为 UTF-8 编码)",
path.display()
)
})?;
println!("code {:?}", code);
let chunk_name = format!("@{}", path.display());
@@ -140,29 +141,24 @@ impl LuaRuntime {
.load(&code)
.set_name(&chunk_name)
.eval::<T>()
.map_err(|e| ShimError::LuaExecution {
file: path.display().to_string(),
source: e,
})
// .map_err(|e| anyhow!(e.to_string()))
.with_context(|| format!("执行 Lua 配置文件失败: {}", path.display()))
}
// /// 将 Lua Value 解析转化为 ShimConfig 数据对象
// pub fn parse_config(&self, value: Value) -> Result<ShimConfig, ShimError> {
// ShimConfig::from_lua(value, &self.lua).map_err(|e| ShimError::InvalidConfig(e.to_string()))
// }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ShimLayout;
use crate::Layout;
fn test_layout() -> ShimLayout {
fn test_layout() -> Layout {
let root = std::env::temp_dir().join("rshim-test-layout");
ShimLayout {
root_dir: root.clone(),
Layout {
base_dir: root.clone(),
bin_dir: root.join("bin"),
tools_dir: root.join("tools"),
lua_file: Default::default(),
}
}

View File

@@ -1,38 +1,29 @@
use crate::ShimError;
use crate::{LuaRuntime, ShimConfig, ShimLayout};
use crate::{LuaRuntime, ShimConfig, Layout};
use anyhow::{Context, Result, bail};
use mlua::{Table, Value};
use std::{
env,
io::{Error, ErrorKind},
};
use std::env;
use tracing::{debug, trace, warn};
pub struct Shim;
impl Shim {
pub fn load() -> Result<ShimConfig, ShimError> {
let current_exe = env::current_exe()
.map_err(|e| Error::new(ErrorKind::Other, format!("获取代理程序路径失败: {}", e)))?;
pub fn load() -> Result<ShimConfig> {
let current_exe = env::current_exe().context("获取代理程序路径失败: {}")?;
debug!("当前目录 {}", current_exe.display());
let target_name = current_exe
.file_stem()
.and_then(|s| s.to_str())
.ok_or_else(|| {
ShimError::PathResolution(format!(
"无法从路径 [{}] 提取有效的程序名称",
current_exe.display()
))
})?
.with_context(|| format!("无法从路径 [{}] 提取有效的程序名称", current_exe.display()))?
.to_lowercase();
debug!(
target_name = %target_name,
current_exe = %current_exe.display(),
"开始加载 Shim 配置"
);
let layout = Layout::discover(&current_exe)?;
let layout = ShimLayout::from_executable(current_exe)?;
debug!(
root_dir = %layout.root_dir.display(),
root_dir = %layout.base_dir.display(),
bin_dir = %layout.bin_dir.display(),
tools_dir = %layout.tools_dir.display(),
"Shim 目录布局解析完成"
@@ -45,23 +36,32 @@ impl Shim {
fn resolve_config(
runtime: &LuaRuntime,
paths: &ShimLayout,
paths: &Layout,
target_name: &str,
) -> Result<ShimConfig, ShimError> {
// 策略 1: 尝试加载全局配置文件 shims.lua
let global_config = paths.root_dir.join("rshim.lua");
) -> Result<ShimConfig> {
// 策略 1: 尝试加载全局配置文件 mimic.lua
let global_config = paths.base_dir.join("rshim.lua");
// if !global_config.is_file() {
// bail!(
// "未找到主配置文件: [{}],请确保在安装根目录创建 rshim.lua",
// global_config.display()
// );
// }
if global_config.is_file() {
trace!(path = %global_config.display(), "发现全局配置文件,尝试解析");
let root_table: Table = runtime.eval_script(&global_config)?;
// 检查 shims.lua 中是否存在以 target_name 命名的 Table 节点
// 检查 mimic.lua 中是否存在以 target_name 命名的 Table 节点
if root_table
.contains_key(target_name)
.map_err(|e| ShimError::InvalidConfig(format!("检查全局配置失败: {}", e)))?
.context("检查全局配置失败")?
{
let target_val: ShimConfig = root_table.get(target_name).map_err(|e| {
ShimError::InvalidConfig(format!("解析配置 [{}] 失败: {}", target_name, e))
})?;
let target_val: ShimConfig = root_table
.get(target_name)
.with_context(|| format!("解析配置 [{}] 失败: ", target_name))?;
debug!(
target = %target_name,
source = %global_config.display(),
@@ -78,7 +78,7 @@ impl Shim {
let target_filename = format!("{}.lua", target_name);
let candidates = [
paths.tools_dir.join(&target_filename),
paths.root_dir.join(&target_filename),
paths.base_dir.join(&target_filename),
];
for config_path in &candidates {
@@ -92,9 +92,10 @@ impl Shim {
// 策略 3: 所有查找失败,抛出错误
warn!(target = %target_name, "未找到任何匹配的配置文件");
Err(ShimError::ConfigMissing(format!(
"未找到关于 '{}' 的配置。请检查 shims.lua 或特定的 {}.lua 文件",
target_name, target_name
)))
bail!(
"未找到关于 '{}' 的配置。请检查 mimic.lua 或特定的 {}.lua 文件",
target_name,
target_name
);
}
}