fusabi_stdlib_ext/
process.rs

1//! Process execution module.
2//!
3//! Provides functions for executing system processes with safety controls.
4
5use std::sync::Arc;
6use std::time::Duration;
7
8use fusabi_host::ExecutionContext;
9use fusabi_host::Value;
10
11use crate::error::{Error, Result};
12use crate::safety::SafetyConfig;
13
14/// Execute a command and wait for completion.
15pub fn exec(
16    safety: &Arc<SafetyConfig>,
17    timeout: Option<Duration>,
18    args: &[Value],
19    _ctx: &ExecutionContext,
20) -> fusabi_host::Result<Value> {
21    let command = args
22        .first()
23        .and_then(|v| v.as_str())
24        .ok_or_else(|| fusabi_host::Error::host_function("exec: missing command argument"))?;
25
26    // Check safety
27    safety.check_execute(command).map_err(|e| {
28        fusabi_host::Error::host_function(e.to_string())
29    })?;
30
31    // Get arguments
32    let cmd_args: Vec<String> = args
33        .iter()
34        .skip(1)
35        .filter_map(|v| v.as_str().map(String::from))
36        .collect();
37
38    // Apply timeout
39    let timeout = timeout
40        .map(|t| safety.clamp_timeout(t))
41        .unwrap_or(safety.default_timeout);
42
43    // Execute command (simulated)
44    tracing::info!("Executing: {} {:?} (timeout: {:?})", command, cmd_args, timeout);
45
46    // In real implementation, would use tokio::process::Command
47    let output = format!("Executed: {} {}", command, cmd_args.join(" "));
48
49    Ok(Value::Map({
50        let mut m = std::collections::HashMap::new();
51        m.insert("stdout".into(), Value::String(output));
52        m.insert("stderr".into(), Value::String(String::new()));
53        m.insert("exit_code".into(), Value::Int(0));
54        m
55    }))
56}
57
58/// Spawn a command without waiting.
59pub fn spawn(
60    args: &[Value],
61    _ctx: &ExecutionContext,
62) -> fusabi_host::Result<Value> {
63    let command = args
64        .first()
65        .and_then(|v| v.as_str())
66        .ok_or_else(|| fusabi_host::Error::host_function("spawn: missing command argument"))?;
67
68    // In real implementation, would spawn the process and return a handle
69    tracing::info!("Spawning: {}", command);
70
71    Ok(Value::Map({
72        let mut m = std::collections::HashMap::new();
73        m.insert("pid".into(), Value::Int(12345));
74        m.insert("command".into(), Value::String(command.to_string()));
75        m
76    }))
77}
78
79/// Options for process execution.
80#[derive(Debug, Clone)]
81pub struct ExecOptions {
82    /// Working directory.
83    pub cwd: Option<String>,
84    /// Environment variables.
85    pub env: std::collections::HashMap<String, String>,
86    /// Timeout.
87    pub timeout: Option<Duration>,
88    /// Capture stdout.
89    pub capture_stdout: bool,
90    /// Capture stderr.
91    pub capture_stderr: bool,
92}
93
94impl Default for ExecOptions {
95    fn default() -> Self {
96        Self {
97            cwd: None,
98            env: std::collections::HashMap::new(),
99            timeout: Some(Duration::from_secs(30)),
100            capture_stdout: true,
101            capture_stderr: true,
102        }
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109    use fusabi_host::Capabilities;
110    use fusabi_host::{Sandbox, SandboxConfig};
111    use fusabi_host::Limits;
112
113    fn create_test_ctx() -> ExecutionContext {
114        let sandbox = Sandbox::new(SandboxConfig::default()).unwrap();
115        ExecutionContext::new(1, Capabilities::none(), Limits::default(), sandbox)
116    }
117
118    #[test]
119    fn test_exec_safety_check() {
120        let safety = Arc::new(SafetyConfig::strict());
121        let ctx = create_test_ctx();
122
123        let result = exec(&safety, None, &[Value::String("ls".into())], &ctx);
124        assert!(result.is_err()); // Should fail - process not allowed
125    }
126
127    #[test]
128    fn test_exec_with_permission() {
129        let safety = Arc::new(
130            SafetyConfig::new()
131                .with_allow_process(true)
132                .with_allowed_commands(["ls"])
133        );
134        let ctx = create_test_ctx();
135
136        let result = exec(&safety, None, &[Value::String("ls".into())], &ctx);
137        assert!(result.is_ok());
138    }
139
140    #[test]
141    fn test_exec_command_not_allowed() {
142        let safety = Arc::new(
143            SafetyConfig::new()
144                .with_allow_process(true)
145                .with_allowed_commands(["ls"])
146        );
147        let ctx = create_test_ctx();
148
149        let result = exec(&safety, None, &[Value::String("rm".into())], &ctx);
150        assert!(result.is_err()); // rm not in allowed list
151    }
152}