Skip to content

Commit 5a5a32e

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

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
@@ -831,215 +831,6 @@ impl FsSandbox {
831831
Ok(out)
832832
}
833833

834-
/// Register all FS tool handlers on `registry`:
835-
///
836-
/// - `fs_read` / `fs_write` — UTF-8 text read/write (whole-file).
837-
/// - `fs_read_bytes` / `fs_write_bytes` — binary read/write with
838-
/// optional offset/length/append, base64-encoded payloads.
839-
/// - `fs_list` — directory enumeration as `{name, is_dir, is_file, is_symlink}`.
840-
/// - `fs_stat` — size + file/dir metadata.
841-
/// - `fs_mkdir` / `fs_unlink` — create/remove directory or file.
842-
/// - `fs_truncate` — set file length.
843-
///
844-
/// Every handler resolves its `path` argument under [`root`](Self::root)
845-
/// via `FsSandbox::resolve`, which rejects `..` escapes, absolute
846-
/// paths that climb outside the root, and symlinks pointing outside.
847-
///
848-
/// The handlers call through `std::fs`, which behaves differently on
849-
/// Linux and Windows — `normalize_fs_error` smooths out the error
850-
/// wording before responses go back to the guest, so the Unikraft
851-
/// guest's substring-matching classifier works on both hosts.
852-
pub fn register(self, registry: &mut ToolRegistry) {
853-
use serde_json::json;
854-
855-
let s = self.clone();
856-
registry.register("fs_read", move |args| {
857-
let path = args["path"]
858-
.as_str()
859-
.ok_or_else(|| anyhow!("fs_read: missing 'path'"))?;
860-
let target = s.resolve(path)?;
861-
let text = std::fs::read_to_string(&target)
862-
.map_err(|e| anyhow!("fs_read {:?}: {}", path, e))?;
863-
Ok(json!({ "text": text }))
864-
});
865-
866-
let s = self.clone();
867-
registry.register("fs_write", move |args| {
868-
let path = args["path"]
869-
.as_str()
870-
.ok_or_else(|| anyhow!("fs_write: missing 'path'"))?;
871-
let text = args["text"]
872-
.as_str()
873-
.ok_or_else(|| anyhow!("fs_write: missing 'text'"))?;
874-
let append = args["append"].as_bool().unwrap_or(false);
875-
let target = s.resolve(path)?;
876-
// Create parent dirs? No — guest must fs_mkdir explicitly.
877-
use std::io::Write;
878-
let mut f = std::fs::OpenOptions::new()
879-
.write(true)
880-
.create(true)
881-
.truncate(!append)
882-
.append(append)
883-
.open(&target)
884-
.map_err(|e| anyhow!("fs_write {:?}: {}", path, e))?;
885-
f.write_all(text.as_bytes())
886-
.map_err(|e| anyhow!("fs_write {:?}: {}", path, e))?;
887-
Ok(json!({ "bytes_written": text.len() }))
888-
});
889-
890-
let s = self.clone();
891-
registry.register("fs_list", move |args| {
892-
let path = args["path"].as_str().unwrap_or("");
893-
let target = s.resolve(path)?;
894-
let mut entries = Vec::new();
895-
for entry in
896-
std::fs::read_dir(&target).map_err(|e| anyhow!("fs_list {:?}: {}", path, e))?
897-
{
898-
let entry = entry?;
899-
let name = entry.file_name().to_string_lossy().into_owned();
900-
let ft = entry.file_type()?;
901-
entries.push(json!({
902-
"name": name,
903-
"is_dir": ft.is_dir(),
904-
"is_file": ft.is_file(),
905-
"is_symlink": ft.is_symlink(),
906-
}));
907-
}
908-
Ok(json!({ "entries": entries }))
909-
});
910-
911-
let s = self.clone();
912-
registry.register("fs_stat", move |args| {
913-
let path = args["path"]
914-
.as_str()
915-
.ok_or_else(|| anyhow!("fs_stat: missing 'path'"))?;
916-
let target = s.resolve(path)?;
917-
let md =
918-
std::fs::metadata(&target).map_err(|e| anyhow!("fs_stat {:?}: {}", path, e))?;
919-
Ok(json!({
920-
"size": md.len(),
921-
"is_dir": md.is_dir(),
922-
"is_file": md.is_file(),
923-
}))
924-
});
925-
926-
let s = self.clone();
927-
registry.register("fs_mkdir", move |args| {
928-
let path = args["path"]
929-
.as_str()
930-
.ok_or_else(|| anyhow!("fs_mkdir: missing 'path'"))?;
931-
let parents = args["parents"].as_bool().unwrap_or(false);
932-
let target = s.resolve(path)?;
933-
if parents {
934-
std::fs::create_dir_all(&target)
935-
} else {
936-
std::fs::create_dir(&target)
937-
}
938-
.map_err(|e| anyhow!("fs_mkdir {:?}: {}", path, e))?;
939-
Ok(json!({}))
940-
});
941-
942-
// fs_read_bytes / fs_write_bytes — binary variants for the Phase B
943-
// transparent POSIX shim. Bytes are base64-encoded in the JSON
944-
// payload so arbitrary binary content round-trips intact.
945-
//
946-
// fs_read_bytes args: { path, offset?, len? } → { data: "<base64>", eof: bool }
947-
// fs_write_bytes args: { path, data: "<base64>", offset?, append? } → { bytes_written }
948-
let s = self.clone();
949-
registry.register("fs_read_bytes", move |args| {
950-
use base64::Engine;
951-
use std::io::{Read, Seek, SeekFrom};
952-
let path = args["path"]
953-
.as_str()
954-
.ok_or_else(|| anyhow!("fs_read_bytes: missing 'path'"))?;
955-
let offset = args["offset"].as_u64().unwrap_or(0);
956-
let want = args["len"].as_u64().unwrap_or(65536).min(MAX_FS_READ);
957-
let target = s.resolve(path)?;
958-
let mut f = std::fs::File::open(&target)
959-
.map_err(|e| anyhow!("fs_read_bytes {:?}: {}", path, e))?;
960-
if offset > 0 {
961-
f.seek(SeekFrom::Start(offset))
962-
.map_err(|e| anyhow!("fs_read_bytes seek {:?}: {}", path, e))?;
963-
}
964-
let mut buf = vec![0u8; want as usize];
965-
let n = f
966-
.read(&mut buf)
967-
.map_err(|e| anyhow!("fs_read_bytes {:?}: {}", path, e))?;
968-
buf.truncate(n);
969-
let eof = n < want as usize;
970-
let encoded = base64::engine::general_purpose::STANDARD.encode(&buf);
971-
Ok(json!({ "data": encoded, "eof": eof, "bytes_read": n }))
972-
});
973-
974-
let s = self.clone();
975-
registry.register("fs_write_bytes", move |args| {
976-
use base64::Engine;
977-
use std::io::{Seek, SeekFrom, Write};
978-
let path = args["path"]
979-
.as_str()
980-
.ok_or_else(|| anyhow!("fs_write_bytes: missing 'path'"))?;
981-
let data_b64 = args["data"]
982-
.as_str()
983-
.ok_or_else(|| anyhow!("fs_write_bytes: missing 'data'"))?;
984-
let data = base64::engine::general_purpose::STANDARD
985-
.decode(data_b64)
986-
.map_err(|e| anyhow!("fs_write_bytes: bad base64: {}", e))?;
987-
let offset = args["offset"].as_u64();
988-
let append = args["append"].as_bool().unwrap_or(false);
989-
let target = s.resolve(path)?;
990-
let mut f = std::fs::OpenOptions::new()
991-
.write(true)
992-
.create(true)
993-
.truncate(offset.is_none() && !append)
994-
.append(append)
995-
.open(&target)
996-
.map_err(|e| anyhow!("fs_write_bytes {:?}: {}", path, e))?;
997-
if let Some(off) = offset {
998-
if !append {
999-
f.seek(SeekFrom::Start(off))
1000-
.map_err(|e| anyhow!("fs_write_bytes seek {:?}: {}", path, e))?;
1001-
}
1002-
}
1003-
f.write_all(&data)
1004-
.map_err(|e| anyhow!("fs_write_bytes {:?}: {}", path, e))?;
1005-
Ok(json!({ "bytes_written": data.len() }))
1006-
});
1007-
1008-
let s = self.clone();
1009-
registry.register("fs_truncate", move |args| {
1010-
let path = args["path"]
1011-
.as_str()
1012-
.ok_or_else(|| anyhow!("fs_truncate: missing 'path'"))?;
1013-
let length = args["length"]
1014-
.as_u64()
1015-
.ok_or_else(|| anyhow!("fs_truncate: missing 'length'"))?;
1016-
let target = s.resolve(path)?;
1017-
let f = std::fs::OpenOptions::new()
1018-
.write(true)
1019-
.open(&target)
1020-
.map_err(|e| anyhow!("fs_truncate {:?}: {}", path, e))?;
1021-
f.set_len(length)
1022-
.map_err(|e| anyhow!("fs_truncate {:?}: {}", path, e))?;
1023-
Ok(json!({}))
1024-
});
1025-
1026-
let s = self.clone();
1027-
registry.register("fs_unlink", move |args| {
1028-
let path = args["path"]
1029-
.as_str()
1030-
.ok_or_else(|| anyhow!("fs_unlink: missing 'path'"))?;
1031-
let target = s.resolve(path)?;
1032-
let md =
1033-
std::fs::metadata(&target).map_err(|e| anyhow!("fs_unlink {:?}: {}", path, e))?;
1034-
if md.is_dir() {
1035-
std::fs::remove_dir(&target)
1036-
} else {
1037-
std::fs::remove_file(&target)
1038-
}
1039-
.map_err(|e| anyhow!("fs_unlink {:?}: {}", path, e))?;
1040-
Ok(json!({}))
1041-
});
1042-
}
1043834
}
1044835

1045836
/// Internal helper: assemble the final tool registry from caller-supplied
@@ -2657,10 +2448,11 @@ mod tests {
26572448
// End-to-end through the tool registry: the error surface the
26582449
// guest actually sees.
26592450
let root = tmpdir("dispatch");
2451+
let preopens = vec![Preopen::new(&root, "/host").unwrap()];
26602452
let mut reg = ToolRegistry::new();
2661-
FsSandbox::new(&root).unwrap().register(&mut reg);
2453+
FsRouter::new(&preopens).unwrap().register(&mut reg);
26622454

2663-
let req = br#"{"name":"fs_read","args":{"path":"../outside.txt"}}"#;
2455+
let req = br#"{"name":"fs_read","args":{"path":"/host/../outside.txt"}}"#;
26642456
let resp = reg.dispatch(req);
26652457
let s = std::str::from_utf8(&resp).unwrap();
26662458
assert!(s.contains("\"error\""), "{s}");
@@ -2740,15 +2532,16 @@ mod tests {
27402532
#[test]
27412533
fn fs_write_then_read_roundtrip() {
27422534
let root = tmpdir("roundtrip");
2535+
let preopens = vec![Preopen::new(&root, "/host").unwrap()];
27432536
let mut reg = ToolRegistry::new();
2744-
FsSandbox::new(&root).unwrap().register(&mut reg);
2537+
FsRouter::new(&preopens).unwrap().register(&mut reg);
27452538

2746-
let w = br#"{"name":"fs_write","args":{"path":"hello.txt","text":"hi"}}"#;
2539+
let w = br#"{"name":"fs_write","args":{"path":"/host/hello.txt","text":"hi"}}"#;
27472540
let resp = reg.dispatch(w);
27482541
let s = std::str::from_utf8(&resp).unwrap();
27492542
assert!(s.contains("\"bytes_written\":2"), "{s}");
27502543

2751-
let r = br#"{"name":"fs_read","args":{"path":"hello.txt"}}"#;
2544+
let r = br#"{"name":"fs_read","args":{"path":"/host/hello.txt"}}"#;
27522545
let resp = reg.dispatch(r);
27532546
let s = std::str::from_utf8(&resp).unwrap();
27542547
assert!(s.contains("\"text\":\"hi\""), "{s}");

0 commit comments

Comments
 (0)