1use std::path::Path;
6use std::sync::Arc;
7
8use fusabi_host::ExecutionContext;
9use fusabi_host::Value;
10
11use crate::safety::SafetyConfig;
12
13pub fn read_file(
15 safety: &Arc<SafetyConfig>,
16 args: &[Value],
17 _ctx: &ExecutionContext,
18) -> fusabi_host::Result<Value> {
19 let path_str = args
20 .first()
21 .and_then(|v| v.as_str())
22 .ok_or_else(|| fusabi_host::Error::host_function("fs.read: missing path argument"))?;
23
24 let path = Path::new(path_str);
25
26 safety.paths.check_read(path).map_err(|e| {
28 fusabi_host::Error::host_function(e.to_string())
29 })?;
30
31 let content = std::fs::read_to_string(path)
33 .map_err(|e| fusabi_host::Error::host_function(format!("fs.read: {}", e)))?;
34
35 Ok(Value::String(content))
36}
37
38pub fn write_file(
40 safety: &Arc<SafetyConfig>,
41 args: &[Value],
42 _ctx: &ExecutionContext,
43) -> fusabi_host::Result<Value> {
44 let path_str = args
45 .first()
46 .and_then(|v| v.as_str())
47 .ok_or_else(|| fusabi_host::Error::host_function("fs.write: missing path argument"))?;
48
49 let content = args
50 .get(1)
51 .and_then(|v| v.as_str())
52 .ok_or_else(|| fusabi_host::Error::host_function("fs.write: missing content argument"))?;
53
54 let path = Path::new(path_str);
55
56 safety.paths.check_write(path).map_err(|e| {
58 fusabi_host::Error::host_function(e.to_string())
59 })?;
60
61 std::fs::write(path, content)
63 .map_err(|e| fusabi_host::Error::host_function(format!("fs.write: {}", e)))?;
64
65 Ok(Value::Null)
66}
67
68pub fn exists(
70 safety: &Arc<SafetyConfig>,
71 args: &[Value],
72 _ctx: &ExecutionContext,
73) -> fusabi_host::Result<Value> {
74 let path_str = args
75 .first()
76 .and_then(|v| v.as_str())
77 .ok_or_else(|| fusabi_host::Error::host_function("fs.exists: missing path argument"))?;
78
79 let path = Path::new(path_str);
80
81 safety.paths.check_read(path).map_err(|e| {
83 fusabi_host::Error::host_function(e.to_string())
84 })?;
85
86 Ok(Value::Bool(path.exists()))
87}
88
89pub fn list_dir(
91 safety: &Arc<SafetyConfig>,
92 args: &[Value],
93 _ctx: &ExecutionContext,
94) -> fusabi_host::Result<Value> {
95 let path_str = args
96 .first()
97 .and_then(|v| v.as_str())
98 .ok_or_else(|| fusabi_host::Error::host_function("fs.list: missing path argument"))?;
99
100 let path = Path::new(path_str);
101
102 safety.paths.check_read(path).map_err(|e| {
104 fusabi_host::Error::host_function(e.to_string())
105 })?;
106
107 let entries: Vec<Value> = std::fs::read_dir(path)
109 .map_err(|e| fusabi_host::Error::host_function(format!("fs.list: {}", e)))?
110 .filter_map(|entry| entry.ok())
111 .map(|entry| Value::String(entry.file_name().to_string_lossy().into_owned()))
112 .collect();
113
114 Ok(Value::List(entries))
115}
116
117pub fn mkdir(
119 safety: &Arc<SafetyConfig>,
120 args: &[Value],
121 _ctx: &ExecutionContext,
122) -> fusabi_host::Result<Value> {
123 let path_str = args
124 .first()
125 .and_then(|v| v.as_str())
126 .ok_or_else(|| fusabi_host::Error::host_function("fs.mkdir: missing path argument"))?;
127
128 let path = Path::new(path_str);
129
130 safety.paths.check_write(path).map_err(|e| {
132 fusabi_host::Error::host_function(e.to_string())
133 })?;
134
135 std::fs::create_dir_all(path)
137 .map_err(|e| fusabi_host::Error::host_function(format!("fs.mkdir: {}", e)))?;
138
139 Ok(Value::Null)
140}
141
142pub fn remove(
144 safety: &Arc<SafetyConfig>,
145 args: &[Value],
146 _ctx: &ExecutionContext,
147) -> fusabi_host::Result<Value> {
148 let path_str = args
149 .first()
150 .and_then(|v| v.as_str())
151 .ok_or_else(|| fusabi_host::Error::host_function("fs.remove: missing path argument"))?;
152
153 let path = Path::new(path_str);
154
155 safety.paths.check_write(path).map_err(|e| {
157 fusabi_host::Error::host_function(e.to_string())
158 })?;
159
160 if path.is_dir() {
162 std::fs::remove_dir_all(path)
163 .map_err(|e| fusabi_host::Error::host_function(format!("fs.remove: {}", e)))?;
164 } else {
165 std::fs::remove_file(path)
166 .map_err(|e| fusabi_host::Error::host_function(format!("fs.remove: {}", e)))?;
167 }
168
169 Ok(Value::Null)
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175 use fusabi_host::Capabilities;
176 use fusabi_host::{Sandbox, SandboxConfig};
177 use fusabi_host::Limits;
178 use crate::safety::PathAllowlist;
179
180 fn create_test_ctx() -> ExecutionContext {
181 let sandbox = Sandbox::new(SandboxConfig::default()).unwrap();
182 ExecutionContext::new(1, Capabilities::none(), Limits::default(), sandbox)
183 }
184
185 #[test]
186 fn test_read_safety_check() {
187 let safety = Arc::new(SafetyConfig::strict());
188 let ctx = create_test_ctx();
189
190 let result = read_file(&safety, &[Value::String("/etc/passwd".into())], &ctx);
191 assert!(result.is_err()); }
193
194 #[test]
195 fn test_exists_with_permission() {
196 let safety = Arc::new(
197 SafetyConfig::new()
198 .with_paths(PathAllowlist::none().allow_read("/tmp"))
199 );
200 let ctx = create_test_ctx();
201
202 let result = exists(&safety, &[Value::String("/tmp".into())], &ctx);
203 assert!(result.is_ok());
204 assert_eq!(result.unwrap(), Value::Bool(true));
205 }
206}