use anyhow::Result;use collections::{HashMap, HashSet};use futures::{ channel::{mpsc, oneshot}, pin_mut, SinkExt, StreamExt,};use gpui::{AppContext, AsyncApp, Context, Entity, Task, WeakEntity};use mlua::{Lua, MultiValue, Table, UserData, UserDataMethods};use parking_lot::Mutex;use project::{search::SearchQuery, Fs, Project};use regex::Regex;use std::{ cell::RefCell, fs::File, path::{Path, PathBuf}, process::{Command, Stdio}, sync::Arc,};use util::{paths::PathMatcher, ResultExt}; use crate::sandboxed_shell::{Operator, ShellAst, ShellCmd}; pub struct ScriptOutput { pub stdout: String,} struct ForegroundFn(Box<dyn FnOnce(WeakEntity<Session>, AsyncApp) + Send>); pub struct Session { project: Entity<Project>, // TODO Remove this fs_changes: Arc<Mutex<HashMap<PathBuf, Vec<u8>>>>, foreground_fns_tx: mpsc::Sender<ForegroundFn>, _invoke_foreground_fns: Task<()>,} impl Session { pub fn new(project: Entity<Project>, cx: &mut Context<Self>) -> Self { let (foreground_fns_tx, mut foreground_fns_rx) = mpsc::channel(128); Session { project, fs_changes: Arc::new(Mutex::new(HashMap::default())), foreground_fns_tx, _invoke_foreground_fns: cx.spawn(|this, cx| async move { while let Some(foreground_fn) = foreground_fns_rx.next().await { foreground_fn.0(this.clone(), cx.clone()); } }), } } /// Runs a Lua script in a sandboxed environment and returns the printed lines pub fn run_script( &mut self, script: String, cx: &mut Context<Self>, ) -> Task<Result<ScriptOutput>> { const SANDBOX_PREAMBLE: &str = include_str!("sandbox_preamble.lua"); // TODO Remove fs_changes let fs_changes = self.fs_changes.clone(); // TODO Honor all worktrees instead of the first one let root_dir = self .project .read(cx) .visible_worktrees(cx) .next() .map(|worktree| worktree.read(cx).abs_path()); let fs = self.project.read(cx).fs().clone(); let foreground_fns_tx = self.foreground_fns_tx.clone(); cx.background_spawn(async move { let lua = Lua::new(); lua.set_memory_limit(2 * 1024 * 1024 * 1024)?; // 2 GB let globals = lua.globals(); let stdout = Arc::new(Mutex::new(String::new())); globals.set( "sb_print", lua.create_function({ let stdout = stdout.clone(); move |_, args: MultiValue| Self::print(args, &stdout) })?, )?; globals.set( "search", lua.create_async_function({ let foreground_fns_tx = foreground_fns_tx.clone(); let fs = fs.clone(); move |lua, regex| { Self::search(lua, foreground_fns_tx.clone(), fs.clone(), regex) } })?, )?; globals.set( "sb_io_open", lua.create_function({ let fs_changes = fs_changes.clone(); let root_dir = root_dir.clone(); move |lua, (path_str, mode)| { Self::io_open(&lua, &fs_changes, root_dir.as_ref(), path_str, mode) } })?, )?; globals.set( "sb_io_popen", lua.create_function({ move |lua, shell_str| { let mut allowed_commands = HashMap::default(); // TODO persist this Self::io_popen(&lua, root_dir.as_ref(), shell_str, &mut allowed_commands) } })?, )?; globals.set("user_script", script)?; lua.load(SANDBOX_PREAMBLE).exec_async().await?; // Drop Lua instance to decrement reference count. drop(lua); let stdout = Arc::try_unwrap(stdout) .expect("no more references to stdout") .into_inner(); Ok(ScriptOutput { stdout }) }) } /// Sandboxed print() function in Lua. fn print(args: MultiValue, stdout: &Mutex<String>) -> mlua::Result<()> { for (index, arg) in args.into_iter().enumerate() { // Lua's `print()` prints tab characters between each argument. if index > 0 { stdout.lock().push('\t'); } // If the argument's to_string() fails, have the whole function call fail. stdout.lock().push_str(&arg.to_string()?); } stdout.lock().push('\n'); Ok(()) } /// Sandboxed io.popen() function in Lua. fn io_popen( lua: &Lua, root_dir: Option<&Arc<Path>>, shell_str: mlua::String, allowed_commands: &mut HashMap<String, bool>, ) -> mlua::Result<(Option<Table>, String)> { let root_dir = root_dir.ok_or_else(|| { mlua::Error::runtime("cannot execute command without a root directory") })?; // Parse the shell command into our AST let ast = ShellAst::parse(shell_str.to_str()?)?; // Create a lua file handle for the command output let file = lua.create_table()?; // Create a buffer to store the command output let output_buffer = Arc::new(Mutex::new(String::new())); // Execute the shell command based on the parsed AST match ast { ShellAst::Command(shell_cmd) => { let result = Self::execute_command(&shell_cmd, root_dir, allowed_commands)?; output_buffer.lock().push_str(&result); } ShellAst::Operation { operator, left, right, } => { // Handle compound operations by recursively executing them let left_output = Self::execute_ast_node(*left, root_dir, allowed_commands)?; match operator { Operator::Pipe => { // For pipe, use left output as input to right command let right_output = Self::execute_ast_node_with_input( *right, &left_output, root_dir, allowed_commands, )?; output_buffer.lock().push_str(&right_output); } Operator::And => { // For AND, only execute right if left was successful (non-empty output as success indicator) if !left_output.trim().is_empty() { let right_output = Self::execute_ast_node(*right, root_dir, allowed_commands)?; output_buffer.lock().push_str(&right_output); } else { output_buffer.lock().push_str(&left_output); } } Operator::Semicolon => { // For semicolon, execute both regardless of result output_buffer.lock().push_str(&left_output); let right_output = Self::execute_ast_node(*right, root_dir, allowed_commands)?; output_buffer.lock().push_str(&right_output); } } } } // Set up the file's content file.set( "__content", lua.create_userdata(FileContent(RefCell::new( output_buffer.lock().as_bytes().to_vec(), )))?, )?; file.set("__position", 0usize)?; file.set("__read_perm", true)?; file.set("__write_perm", false)?; // Implement the read method for the file let read_fn = { lua.create_function( move |_lua, (file_userdata, format): (mlua::Table, Option<mlua::Value>)| { let content = file_userdata.get::<mlua::AnyUserData>("__content")?; let mut position = file_userdata.get::<usize>("__position")?; let content_ref = content.borrow::<FileContent>()?; let content_vec = content_ref.0.borrow(); if position >= content_vec.len() { return Ok(None); // EOF } match format { Some(mlua::Value::String(s)) => { let format_str = s.to_string_lossy(); // Handle different read formats if format_str.starts_with("*a") { // Read all let result = String::from_utf8_lossy(&content_vec[position..]).to_string(); position = content_vec.len(); file_userdata.set("__position", position)?; Ok(Some(result)) } else if format_str.starts_with("*l") { // Read line let mut line = Vec::new(); let mut found_newline = false; while position < content_vec.len() { let byte = content_vec[position]; position += 1; if byte == b'\n' { found_newline = true; break; } // Handle \r\n sequence if byte == b'\r' && position < content_vec.len() && content_vec[position] == b'\n' { position += 1; found_newline = true; break; } line.push(byte); } file_userdata.set("__position", position)?; if !found_newline && line.is_empty() && position >= content_vec.len() { return Ok(None); // EOF } let result = String::from_utf8_lossy(&line).to_string(); Ok(Some(result)) } else { Err(mlua::Error::runtime(format!( "Unsupported read format: {}", format_str ))) } } Some(_) => Err(mlua::Error::runtime("Invalid format")), None => { // Default is to read a line let mut line = Vec::new(); let mut found_newline = false; while position < content_vec.len() { let byte = content_vec[position]; position += 1; if byte == b'\n' { found_newline = true; break; } if byte == b'\r' && position < content_vec.len() && content_vec[position] == b'\n' { position += 1; found_newline = true; break; } line.push(byte); } file_userdata.set("__position", position)?; if !found_newline && line.is_empty() && position >= content_vec.len() { return Ok(None); // EOF } let result = String::from_utf8_lossy(&line).to_string(); Ok(Some(result)) } } }, )? }; file.set("read", read_fn)?; // Implement close method let close_fn = lua.create_function(|_lua, _: mlua::Table| Ok(true))?; file.set("close", close_fn)?; Ok((Some(file), String::new())) } // Helper function to execute a single command fn execute_command( cmd: &ShellCmd, root_dir: &Arc<Path>, allowed_commands: &mut HashMap<String, bool>, ) -> mlua::Result<String> { // Check if command is allowed if !allowed_commands.contains_key(&cmd.command) { // If it's the first time we see this command, ask for permission // In a real application, this would prompt the user, but for simplicity // we'll just allow all commands in this sample implementation allowed_commands.insert(cmd.command.clone(), true); } if !allowed_commands[&cmd.command] { return Err(mlua::Error::runtime(format!( "Command '{}' is not allowed in this sandbox", cmd.command ))); } // Execute the command let mut command = Command::new(&cmd.command); // Set the current directory command.current_dir(root_dir); // Add arguments command.args(&cmd.args); // Configure stdio command.stdin(Stdio::piped()); command.stdout(Stdio::piped()); command.stderr(Stdio::piped()); // Execute the command let output = command .output() .map_err(|e| mlua::Error::runtime(format!("Failed to execute command: {}", e)))?; let mut result = String::new(); // Handle stdout if cmd.stdout_redirect.is_none() { result.push_str(&String::from_utf8_lossy(&output.stdout)); } else { // Handle file redirection let redirect_path = root_dir.join(cmd.stdout_redirect.as_ref().unwrap()); Self::write_to_file(&redirect_path, &output.stdout) .map_err(|e| mlua::Error::runtime(format!("Failed to redirect stdout: {}", e)))?; } // Handle stderr if cmd.stderr_redirect.is_none() { // If stderr is not redirected, append it to the result result.push_str(&String::from_utf8_lossy(&output.stderr)); } else { // Handle file redirection let redirect_path = root_dir.join(cmd.stderr_redirect.as_ref().unwrap()); Self::write_to_file(&redirect_path, &output.stderr) .map_err(|e| mlua::Error::runtime(format!("Failed to redirect stderr: {}", e)))?; } Ok(result) } // Helper function to write data to a file fn write_to_file(path: &Path, data: &[u8]) -> std::io::Result<()> { let mut file = File::create(path)?; std::io::Write::write_all(&mut file, data)?; Ok(()) } // Helper function to execute an AST node fn execute_ast_node( node: ShellAst, root_dir: &Arc<Path>, allowed_commands: &mut HashMap<String, bool>, ) -> mlua::Result<String> { match node { ShellAst::Command(cmd) => Self::execute_command(&cmd, root_dir, allowed_commands), ShellAst::Operation { operator, left, right, } => { let left_output = Self::execute_ast_node(*left, root_dir, allowed_commands)?; match operator { Operator::Pipe => Self::execute_ast_node_with_input( *right, &left_output, root_dir, allowed_commands, ), Operator::And => { if !left_output.trim().is_empty() { Self::execute_ast_node(*right, root_dir, allowed_commands) } else { Ok(left_output) } } Operator::Semicolon => { let mut result = left_output; let right_output = Self::execute_ast_node(*right, root_dir, allowed_commands)?; result.push_str(&right_output); Ok(result) } } } } } // Helper function to execute an AST node with input from a previous command fn execute_ast_node_with_input( node: ShellAst, input: &str, root_dir: &Arc<Path>, allowed_commands: &mut HashMap<String, bool>, ) -> mlua::Result<String> { match node { ShellAst::Command(cmd) => { // Check if command is allowed if !allowed_commands.contains_key(&cmd.command) { allowed_commands.insert(cmd.command.clone(), true); } if !allowed_commands[&cmd.command] { return Err(mlua::Error::runtime(format!( "Command '{}' is not allowed in this sandbox", cmd.command ))); } // Execute the command with input let mut command = Command::new(&cmd.command); command.current_dir(root_dir); command.args(&cmd.args); // Configure stdio command.stdin(Stdio::piped()); command.stdout(Stdio::piped()); command.stderr(Stdio::piped()); let mut child = command.spawn().map_err(|e| { mlua::Error::runtime(format!("Failed to execute command: {}", e)) })?; // Write input to stdin if let Some(mut stdin) = child.stdin.take() { std::io::Write::write_all(&mut stdin, input.as_bytes()).map_err(|e| { mlua::Error::runtime(format!("Failed to write to stdin: {}", e)) })?; // Stdin is closed when it goes out of scope } let output = child.wait_with_output().map_err(|e| { mlua::Error::runtime(format!("Failed to wait for command: {}", e)) })?; let mut result = String::new();Lines 1–500 of 1150. Load more
use anyhow::Result; use collections::{HashMap, HashSet}; use futures::{ channel::{mpsc, oneshot}, pin_mut, SinkExt, StreamExt, }; use gpui::{AppContext, AsyncApp, Context, Entity, Task, WeakEntity}; use mlua::{Lua, MultiValue, Table, UserData, UserDataMethods}; use parking_lot::Mutex; use project::{search::SearchQuery, Fs, Project}; use regex::Regex; use std::{ cell::RefCell, fs::File, path::{Path, PathBuf}, process::{Command, Stdio}, sync::Arc, }; use util::{paths::PathMatcher, ResultExt}; use crate::sandboxed_shell::{Operator, ShellAst, ShellCmd}; pub struct ScriptOutput { pub stdout: String, } struct ForegroundFn(Box<dyn FnOnce(WeakEntity<Session>, AsyncApp) + Send>); pub struct Session { project: Entity<Project>, // TODO Remove this fs_changes: Arc<Mutex<HashMap<PathBuf, Vec<u8>>>>, foreground_fns_tx: mpsc::Sender<ForegroundFn>, _invoke_foreground_fns: Task<()>, } impl Session { pub fn new(project: Entity<Project>, cx: &mut Context<Self>) -> Self { let (foreground_fns_tx, mut foreground_fns_rx) = mpsc::channel(128); Session { project, fs_changes: Arc::new(Mutex::new(HashMap::default())), foreground_fns_tx, _invoke_foreground_fns: cx.spawn(|this, cx| async move { while let Some(foreground_fn) = foreground_fns_rx.next().await { foreground_fn.0(this.clone(), cx.clone()); } }), } } /// Runs a Lua script in a sandboxed environment and returns the printed lines pub fn run_script( &mut self, script: String, cx: &mut Context<Self>, ) -> Task<Result<ScriptOutput>> { const SANDBOX_PREAMBLE: &str = include_str!("sandbox_preamble.lua"); // TODO Remove fs_changes let fs_changes = self.fs_changes.clone(); // TODO Honor all worktrees instead of the first one let root_dir = self .project .read(cx) .visible_worktrees(cx) .next() .map(|worktree| worktree.read(cx).abs_path()); let fs = self.project.read(cx).fs().clone(); let foreground_fns_tx = self.foreground_fns_tx.clone(); cx.background_spawn(async move { let lua = Lua::new(); lua.set_memory_limit(2 * 1024 * 1024 * 1024)?; // 2 GB let globals = lua.globals(); let stdout = Arc::new(Mutex::new(String::new())); globals.set( "sb_print", lua.create_function({ let stdout = stdout.clone(); move |_, args: MultiValue| Self::print(args, &stdout) })?, )?; globals.set( "search", lua.create_async_function({ let foreground_fns_tx = foreground_fns_tx.clone(); let fs = fs.clone(); move |lua, regex| { Self::search(lua, foreground_fns_tx.clone(), fs.clone(), regex) } })?, )?; globals.set( "sb_io_open", lua.create_function({ let fs_changes = fs_changes.clone(); let root_dir = root_dir.clone(); move |lua, (path_str, mode)| { Self::io_open(&lua, &fs_changes, root_dir.as_ref(), path_str, mode) } })?, )?; globals.set( "sb_io_popen", lua.create_function({ move |lua, shell_str| { let mut allowed_commands = HashMap::default(); // TODO persist this Self::io_popen(&lua, root_dir.as_ref(), shell_str, &mut allowed_commands) } })?, )?; globals.set("user_script", script)?; lua.load(SANDBOX_PREAMBLE).exec_async().await?; // Drop Lua instance to decrement reference count. drop(lua); let stdout = Arc::try_unwrap(stdout) .expect("no more references to stdout") .into_inner(); Ok(ScriptOutput { stdout }) }) } /// Sandboxed print() function in Lua. fn print(args: MultiValue, stdout: &Mutex<String>) -> mlua::Result<()> { for (index, arg) in args.into_iter().enumerate() { // Lua's `print()` prints tab characters between each argument. if index > 0 { stdout.lock().push('\t'); } // If the argument's to_string() fails, have the whole function call fail. stdout.lock().push_str(&arg.to_string()?); } stdout.lock().push('\n'); Ok(()) } /// Sandboxed io.popen() function in Lua. fn io_popen( lua: &Lua, root_dir: Option<&Arc<Path>>, shell_str: mlua::String, allowed_commands: &mut HashMap<String, bool>, ) -> mlua::Result<(Option<Table>, String)> { let root_dir = root_dir.ok_or_else(|| { mlua::Error::runtime("cannot execute command without a root directory") })?; // Parse the shell command into our AST let ast = ShellAst::parse(shell_str.to_str()?)?; // Create a lua file handle for the command output let file = lua.create_table()?; // Create a buffer to store the command output let output_buffer = Arc::new(Mutex::new(String::new())); // Execute the shell command based on the parsed AST match ast { ShellAst::Command(shell_cmd) => { let result = Self::execute_command(&shell_cmd, root_dir, allowed_commands)?; output_buffer.lock().push_str(&result); } ShellAst::Operation { operator, left, right, } => { // Handle compound operations by recursively executing them let left_output = Self::execute_ast_node(*left, root_dir, allowed_commands)?; match operator { Operator::Pipe => { // For pipe, use left output as input to right command let right_output = Self::execute_ast_node_with_input( *right, &left_output, root_dir, allowed_commands, )?; output_buffer.lock().push_str(&right_output); } Operator::And => { // For AND, only execute right if left was successful (non-empty output as success indicator) if !left_output.trim().is_empty() { let right_output = Self::execute_ast_node(*right, root_dir, allowed_commands)?; output_buffer.lock().push_str(&right_output); } else { output_buffer.lock().push_str(&left_output); } } Operator::Semicolon => { // For semicolon, execute both regardless of result output_buffer.lock().push_str(&left_output); let right_output = Self::execute_ast_node(*right, root_dir, allowed_commands)?; output_buffer.lock().push_str(&right_output); } } } } // Set up the file's content file.set( "__content", lua.create_userdata(FileContent(RefCell::new( output_buffer.lock().as_bytes().to_vec(), )))?, )?; file.set("__position", 0usize)?; file.set("__read_perm", true)?; file.set("__write_perm", false)?; // Implement the read method for the file let read_fn = { lua.create_function( move |_lua, (file_userdata, format): (mlua::Table, Option<mlua::Value>)| { let content = file_userdata.get::<mlua::AnyUserData>("__content")?; let mut position = file_userdata.get::<usize>("__position")?; let content_ref = content.borrow::<FileContent>()?; let content_vec = content_ref.0.borrow(); if position >= content_vec.len() { return Ok(None); // EOF } match format { Some(mlua::Value::String(s)) => { let format_str = s.to_string_lossy(); // Handle different read formats if format_str.starts_with("*a") { // Read all let result = String::from_utf8_lossy(&content_vec[position..]).to_string(); position = content_vec.len(); file_userdata.set("__position", position)?; Ok(Some(result)) } else if format_str.starts_with("*l") { // Read line let mut line = Vec::new(); let mut found_newline = false; while position < content_vec.len() { let byte = content_vec[position]; position += 1; if byte == b'\n' { found_newline = true; break; } // Handle \r\n sequence if byte == b'\r' && position < content_vec.len() && content_vec[position] == b'\n' { position += 1; found_newline = true; break; } line.push(byte); } file_userdata.set("__position", position)?; if !found_newline && line.is_empty() && position >= content_vec.len() { return Ok(None); // EOF } let result = String::from_utf8_lossy(&line).to_string(); Ok(Some(result)) } else { Err(mlua::Error::runtime(format!( "Unsupported read format: {}", format_str ))) } } Some(_) => Err(mlua::Error::runtime("Invalid format")), None => { // Default is to read a line let mut line = Vec::new(); let mut found_newline = false; while position < content_vec.len() { let byte = content_vec[position]; position += 1; if byte == b'\n' { found_newline = true; break; } if byte == b'\r' && position < content_vec.len() && content_vec[position] == b'\n' { position += 1; found_newline = true; break; } line.push(byte); } file_userdata.set("__position", position)?; if !found_newline && line.is_empty() && position >= content_vec.len() { return Ok(None); // EOF } let result = String::from_utf8_lossy(&line).to_string(); Ok(Some(result)) } } }, )? }; file.set("read", read_fn)?; // Implement close method let close_fn = lua.create_function(|_lua, _: mlua::Table| Ok(true))?; file.set("close", close_fn)?; Ok((Some(file), String::new())) } // Helper function to execute a single command fn execute_command( cmd: &ShellCmd, root_dir: &Arc<Path>, allowed_commands: &mut HashMap<String, bool>, ) -> mlua::Result<String> { // Check if command is allowed if !allowed_commands.contains_key(&cmd.command) { // If it's the first time we see this command, ask for permission // In a real application, this would prompt the user, but for simplicity // we'll just allow all commands in this sample implementation allowed_commands.insert(cmd.command.clone(), true); } if !allowed_commands[&cmd.command] { return Err(mlua::Error::runtime(format!( "Command '{}' is not allowed in this sandbox", cmd.command ))); } // Execute the command let mut command = Command::new(&cmd.command); // Set the current directory command.current_dir(root_dir); // Add arguments command.args(&cmd.args); // Configure stdio command.stdin(Stdio::piped()); command.stdout(Stdio::piped()); command.stderr(Stdio::piped()); // Execute the command let output = command .output() .map_err(|e| mlua::Error::runtime(format!("Failed to execute command: {}", e)))?; let mut result = String::new(); // Handle stdout if cmd.stdout_redirect.is_none() { result.push_str(&String::from_utf8_lossy(&output.stdout)); } else { // Handle file redirection let redirect_path = root_dir.join(cmd.stdout_redirect.as_ref().unwrap()); Self::write_to_file(&redirect_path, &output.stdout) .map_err(|e| mlua::Error::runtime(format!("Failed to redirect stdout: {}", e)))?; } // Handle stderr if cmd.stderr_redirect.is_none() { // If stderr is not redirected, append it to the result result.push_str(&String::from_utf8_lossy(&output.stderr)); } else { // Handle file redirection let redirect_path = root_dir.join(cmd.stderr_redirect.as_ref().unwrap()); Self::write_to_file(&redirect_path, &output.stderr) .map_err(|e| mlua::Error::runtime(format!("Failed to redirect stderr: {}", e)))?; } Ok(result) } // Helper function to write data to a file fn write_to_file(path: &Path, data: &[u8]) -> std::io::Result<()> { let mut file = File::create(path)?; std::io::Write::write_all(&mut file, data)?; Ok(()) } // Helper function to execute an AST node fn execute_ast_node( node: ShellAst, root_dir: &Arc<Path>, allowed_commands: &mut HashMap<String, bool>, ) -> mlua::Result<String> { match node { ShellAst::Command(cmd) => Self::execute_command(&cmd, root_dir, allowed_commands), ShellAst::Operation { operator, left, right, } => { let left_output = Self::execute_ast_node(*left, root_dir, allowed_commands)?; match operator { Operator::Pipe => Self::execute_ast_node_with_input( *right, &left_output, root_dir, allowed_commands, ), Operator::And => { if !left_output.trim().is_empty() { Self::execute_ast_node(*right, root_dir, allowed_commands) } else { Ok(left_output) } } Operator::Semicolon => { let mut result = left_output; let right_output = Self::execute_ast_node(*right, root_dir, allowed_commands)?; result.push_str(&right_output); Ok(result) } } } } } // Helper function to execute an AST node with input from a previous command fn execute_ast_node_with_input( node: ShellAst, input: &str, root_dir: &Arc<Path>, allowed_commands: &mut HashMap<String, bool>, ) -> mlua::Result<String> { match node { ShellAst::Command(cmd) => { // Check if command is allowed if !allowed_commands.contains_key(&cmd.command) { allowed_commands.insert(cmd.command.clone(), true); } if !allowed_commands[&cmd.command] { return Err(mlua::Error::runtime(format!( "Command '{}' is not allowed in this sandbox", cmd.command ))); } // Execute the command with input let mut command = Command::new(&cmd.command); command.current_dir(root_dir); command.args(&cmd.args); // Configure stdio command.stdin(Stdio::piped()); command.stdout(Stdio::piped()); command.stderr(Stdio::piped()); let mut child = command.spawn().map_err(|e| { mlua::Error::runtime(format!("Failed to execute command: {}", e)) })?; // Write input to stdin if let Some(mut stdin) = child.stdin.take() { std::io::Write::write_all(&mut stdin, input.as_bytes()).map_err(|e| { mlua::Error::runtime(format!("Failed to write to stdin: {}", e)) })?; // Stdin is closed when it goes out of scope } let output = child.wait_with_output().map_err(|e| { mlua::Error::runtime(format!("Failed to wait for command: {}", e)) })?; let mut result = String::new();