fusabi_stdlib_ext/
process.rs1use 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
14pub 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 safety.check_execute(command).map_err(|e| {
28 fusabi_host::Error::host_function(e.to_string())
29 })?;
30
31 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 let timeout = timeout
40 .map(|t| safety.clamp_timeout(t))
41 .unwrap_or(safety.default_timeout);
42
43 tracing::info!("Executing: {} {:?} (timeout: {:?})", command, cmd_args, timeout);
45
46 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
58pub 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 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#[derive(Debug, Clone)]
81pub struct ExecOptions {
82 pub cwd: Option<String>,
84 pub env: std::collections::HashMap<String, String>,
86 pub timeout: Option<Duration>,
88 pub capture_stdout: bool,
90 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()); }
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()); }
152}