Skip to content

Commit 29bfad6

Browse files
committed
refactor: remove FsSandbox::register duplication, use FsRouter in tests
Signed-off-by: danbugs <danilochiarlone@gmail.com>
1 parent f5eb506 commit 29bfad6

1 file changed

Lines changed: 7 additions & 214 deletions

File tree

host/src/lib.rs

Lines changed: 7 additions & 214 deletions
Original file line numberDiff line numberDiff line change
@@ -771,215 +771,6 @@ impl FsSandbox {
771771
Ok(out)
772772
}
773773

774-
/// Register all FS tool handlers on `registry`:
775-
///
776-
/// - `fs_read` / `fs_write` — UTF-8 text read/write (whole-file).
777-
/// - `fs_read_bytes` / `fs_write_bytes` — binary read/write with
778-
/// optional offset/length/append, base64-encoded payloads.
779-
/// - `fs_list` — directory enumeration as `{name, is_dir, is_file, is_symlink}`.
780-
/// - `fs_stat` — size + file/dir metadata.
781-
/// - `fs_mkdir` / `fs_unlink` — create/remove directory or file.
782-
/// - `fs_truncate` — set file length.
783-
///
784-
/// Every handler resolves its `path` argument under [`root`](Self::root)
785-
/// via `FsSandbox::resolve`, which rejects `..` escapes, absolute
786-
/// paths that climb outside the root, and symlinks pointing outside.
787-
///
788-
/// The handlers call through `std::fs`, which behaves differently on
789-
/// Linux and Windows — `normalize_fs_error` smooths out the error
790-
/// wording before responses go back to the guest, so the Unikraft
791-
/// guest's substring-matching classifier works on both hosts.
792-
pub fn register(self, registry: &mut ToolRegistry) {
793-
use serde_json::json;
794-
795-
let s = self.clone();
796-
registry.register("fs_read", move |args| {
797-
let path = args["path"]
798-
.as_str()
799-
.ok_or_else(|| anyhow!("fs_read: missing 'path'"))?;
800-
let target = s.resolve(path)?;
801-
let text = std::fs::read_to_string(&target)
802-
.map_err(|e| anyhow!("fs_read {:?}: {}", path, e))?;
803-
Ok(json!({ "text": text }))
804-
});
805-
806-
let s = self.clone();
807-
registry.register("fs_write", move |args| {
808-
let path = args["path"]
809-
.as_str()
810-
.ok_or_else(|| anyhow!("fs_write: missing 'path'"))?;
811-
let text = args["text"]
812-
.as_str()
813-
.ok_or_else(|| anyhow!("fs_write: missing 'text'"))?;
814-
let append = args["append"].as_bool().unwrap_or(false);
815-
let target = s.resolve(path)?;
816-
// Create parent dirs? No — guest must fs_mkdir explicitly.
817-
use std::io::Write;
818-
let mut f = std::fs::OpenOptions::new()
819-
.write(true)
820-
.create(true)
821-
.truncate(!append)
822-
.append(append)
823-
.open(&target)
824-
.map_err(|e| anyhow!("fs_write {:?}: {}", path, e))?;
825-
f.write_all(text.as_bytes())
826-
.map_err(|e| anyhow!("fs_write {:?}: {}", path, e))?;
827-
Ok(json!({ "bytes_written": text.len() }))
828-
});
829-
830-
let s = self.clone();
831-
registry.register("fs_list", move |args| {
832-
let path = args["path"].as_str().unwrap_or("");
833-
let target = s.resolve(path)?;
834-
let mut entries = Vec::new();
835-
for entry in
836-
std::fs::read_dir(&target).map_err(|e| anyhow!("fs_list {:?}: {}", path, e))?
837-
{
838-
let entry = entry?;
839-
let name = entry.file_name().to_string_lossy().into_owned();
840-
let ft = entry.file_type()?;
841-
entries.push(json!({
842-
"name": name,
843-
"is_dir": ft.is_dir(),
844-
"is_file": ft.is_file(),
845-
"is_symlink": ft.is_symlink(),
846-
}));
847-
}
848-
Ok(json!({ "entries": entries }))
849-
});
850-
851-
let s = self.clone();
852-
registry.register("fs_stat", move |args| {
853-
let path = args["path"]
854-
.as_str()
855-
.ok_or_else(|| anyhow!("fs_stat: missing 'path'"))?;
856-
let target = s.resolve(path)?;
857-
let md =
858-
std::fs::metadata(&target).map_err(|e| anyhow!("fs_stat {:?}: {}", path, e))?;
859-
Ok(json!({
860-
"size": md.len(),
861-
"is_dir": md.is_dir(),
862-
"is_file": md.is_file(),
863-
}))
864-
});
865-
866-
let s = self.clone();
867-
registry.register("fs_mkdir", move |args| {
868-
let path = args["path"]
869-
.as_str()
870-
.ok_or_else(|| anyhow!("fs_mkdir: missing 'path'"))?;
871-
let parents = args["parents"].as_bool().unwrap_or(false);
872-
let target = s.resolve(path)?;
873-
if parents {
874-
std::fs::create_dir_all(&target)
875-
} else {
876-
std::fs::create_dir(&target)
877-
}
878-
.map_err(|e| anyhow!("fs_mkdir {:?}: {}", path, e))?;
879-
Ok(json!({}))
880-
});
881-
882-
// fs_read_bytes / fs_write_bytes — binary variants for the Phase B
883-
// transparent POSIX shim. Bytes are base64-encoded in the JSON
884-
// payload so arbitrary binary content round-trips intact.
885-
//
886-
// fs_read_bytes args: { path, offset?, len? } → { data: "<base64>", eof: bool }
887-
// fs_write_bytes args: { path, data: "<base64>", offset?, append? } → { bytes_written }
888-
let s = self.clone();
889-
registry.register("fs_read_bytes", move |args| {
890-
use base64::Engine;
891-
use std::io::{Read, Seek, SeekFrom};
892-
let path = args["path"]
893-
.as_str()
894-
.ok_or_else(|| anyhow!("fs_read_bytes: missing 'path'"))?;
895-
let offset = args["offset"].as_u64().unwrap_or(0);
896-
let want = args["len"].as_u64().unwrap_or(65536).min(MAX_FS_READ);
897-
let target = s.resolve(path)?;
898-
let mut f = std::fs::File::open(&target)
899-
.map_err(|e| anyhow!("fs_read_bytes {:?}: {}", path, e))?;
900-
if offset > 0 {
901-
f.seek(SeekFrom::Start(offset))
902-
.map_err(|e| anyhow!("fs_read_bytes seek {:?}: {}", path, e))?;
903-
}
904-
let mut buf = vec![0u8; want as usize];
905-
let n = f
906-
.read(&mut buf)
907-
.map_err(|e| anyhow!("fs_read_bytes {:?}: {}", path, e))?;
908-
buf.truncate(n);
909-
let eof = n < want as usize;
910-
let encoded = base64::engine::general_purpose::STANDARD.encode(&buf);
911-
Ok(json!({ "data": encoded, "eof": eof, "bytes_read": n }))
912-
});
913-
914-
let s = self.clone();
915-
registry.register("fs_write_bytes", move |args| {
916-
use base64::Engine;
917-
use std::io::{Seek, SeekFrom, Write};
918-
let path = args["path"]
919-
.as_str()
920-
.ok_or_else(|| anyhow!("fs_write_bytes: missing 'path'"))?;
921-
let data_b64 = args["data"]
922-
.as_str()
923-
.ok_or_else(|| anyhow!("fs_write_bytes: missing 'data'"))?;
924-
let data = base64::engine::general_purpose::STANDARD
925-
.decode(data_b64)
926-
.map_err(|e| anyhow!("fs_write_bytes: bad base64: {}", e))?;
927-
let offset = args["offset"].as_u64();
928-
let append = args["append"].as_bool().unwrap_or(false);
929-
let target = s.resolve(path)?;
930-
let mut f = std::fs::OpenOptions::new()
931-
.write(true)
932-
.create(true)
933-
.truncate(offset.is_none() && !append)
934-
.append(append)
935-
.open(&target)
936-
.map_err(|e| anyhow!("fs_write_bytes {:?}: {}", path, e))?;
937-
if let Some(off) = offset {
938-
if !append {
939-
f.seek(SeekFrom::Start(off))
940-
.map_err(|e| anyhow!("fs_write_bytes seek {:?}: {}", path, e))?;
941-
}
942-
}
943-
f.write_all(&data)
944-
.map_err(|e| anyhow!("fs_write_bytes {:?}: {}", path, e))?;
945-
Ok(json!({ "bytes_written": data.len() }))
946-
});
947-
948-
let s = self.clone();
949-
registry.register("fs_truncate", move |args| {
950-
let path = args["path"]
951-
.as_str()
952-
.ok_or_else(|| anyhow!("fs_truncate: missing 'path'"))?;
953-
let length = args["length"]
954-
.as_u64()
955-
.ok_or_else(|| anyhow!("fs_truncate: missing 'length'"))?;
956-
let target = s.resolve(path)?;
957-
let f = std::fs::OpenOptions::new()
958-
.write(true)
959-
.open(&target)
960-
.map_err(|e| anyhow!("fs_truncate {:?}: {}", path, e))?;
961-
f.set_len(length)
962-
.map_err(|e| anyhow!("fs_truncate {:?}: {}", path, e))?;
963-
Ok(json!({}))
964-
});
965-
966-
let s = self.clone();
967-
registry.register("fs_unlink", move |args| {
968-
let path = args["path"]
969-
.as_str()
970-
.ok_or_else(|| anyhow!("fs_unlink: missing 'path'"))?;
971-
let target = s.resolve(path)?;
972-
let md =
973-
std::fs::metadata(&target).map_err(|e| anyhow!("fs_unlink {:?}: {}", path, e))?;
974-
if md.is_dir() {
975-
std::fs::remove_dir(&target)
976-
} else {
977-
std::fs::remove_file(&target)
978-
}
979-
.map_err(|e| anyhow!("fs_unlink {:?}: {}", path, e))?;
980-
Ok(json!({}))
981-
});
982-
}
983774
}
984775

985776
/// Internal helper: assemble the final tool registry from caller-supplied
@@ -2501,10 +2292,11 @@ mod tests {
25012292
// End-to-end through the tool registry: the error surface the
25022293
// guest actually sees.
25032294
let root = tmpdir("dispatch");
2295+
let preopens = vec![Preopen::new(&root, "/host").unwrap()];
25042296
let mut reg = ToolRegistry::new();
2505-
FsSandbox::new(&root).unwrap().register(&mut reg);
2297+
FsRouter::new(&preopens).unwrap().register(&mut reg);
25062298

2507-
let req = br#"{"name":"fs_read","args":{"path":"../outside.txt"}}"#;
2299+
let req = br#"{"name":"fs_read","args":{"path":"/host/../outside.txt"}}"#;
25082300
let resp = reg.dispatch(req);
25092301
let s = std::str::from_utf8(&resp).unwrap();
25102302
assert!(s.contains("\"error\""), "{s}");
@@ -2584,15 +2376,16 @@ mod tests {
25842376
#[test]
25852377
fn fs_write_then_read_roundtrip() {
25862378
let root = tmpdir("roundtrip");
2379+
let preopens = vec![Preopen::new(&root, "/host").unwrap()];
25872380
let mut reg = ToolRegistry::new();
2588-
FsSandbox::new(&root).unwrap().register(&mut reg);
2381+
FsRouter::new(&preopens).unwrap().register(&mut reg);
25892382

2590-
let w = br#"{"name":"fs_write","args":{"path":"hello.txt","text":"hi"}}"#;
2383+
let w = br#"{"name":"fs_write","args":{"path":"/host/hello.txt","text":"hi"}}"#;
25912384
let resp = reg.dispatch(w);
25922385
let s = std::str::from_utf8(&resp).unwrap();
25932386
assert!(s.contains("\"bytes_written\":2"), "{s}");
25942387

2595-
let r = br#"{"name":"fs_read","args":{"path":"hello.txt"}}"#;
2388+
let r = br#"{"name":"fs_read","args":{"path":"/host/hello.txt"}}"#;
25962389
let resp = reg.dispatch(r);
25972390
let s = std::str::from_utf8(&resp).unwrap();
25982391
assert!(s.contains("\"text\":\"hi\""), "{s}");

0 commit comments

Comments
 (0)