Skip to content

Commit 9ee6231

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

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
@@ -766,215 +766,6 @@ impl FsSandbox {
766766
Ok(out)
767767
}
768768

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

980771
/// Internal helper: assemble the final tool registry from caller-supplied
@@ -2496,10 +2287,11 @@ mod tests {
24962287
// End-to-end through the tool registry: the error surface the
24972288
// guest actually sees.
24982289
let root = tmpdir("dispatch");
2290+
let preopens = vec![Preopen::new(&root, "/host").unwrap()];
24992291
let mut reg = ToolRegistry::new();
2500-
FsSandbox::new(&root).unwrap().register(&mut reg);
2292+
FsRouter::new(&preopens).unwrap().register(&mut reg);
25012293

2502-
let req = br#"{"name":"fs_read","args":{"path":"../outside.txt"}}"#;
2294+
let req = br#"{"name":"fs_read","args":{"path":"/host/../outside.txt"}}"#;
25032295
let resp = reg.dispatch(req);
25042296
let s = std::str::from_utf8(&resp).unwrap();
25052297
assert!(s.contains("\"error\""), "{s}");
@@ -2579,15 +2371,16 @@ mod tests {
25792371
#[test]
25802372
fn fs_write_then_read_roundtrip() {
25812373
let root = tmpdir("roundtrip");
2374+
let preopens = vec![Preopen::new(&root, "/host").unwrap()];
25822375
let mut reg = ToolRegistry::new();
2583-
FsSandbox::new(&root).unwrap().register(&mut reg);
2376+
FsRouter::new(&preopens).unwrap().register(&mut reg);
25842377

2585-
let w = br#"{"name":"fs_write","args":{"path":"hello.txt","text":"hi"}}"#;
2378+
let w = br#"{"name":"fs_write","args":{"path":"/host/hello.txt","text":"hi"}}"#;
25862379
let resp = reg.dispatch(w);
25872380
let s = std::str::from_utf8(&resp).unwrap();
25882381
assert!(s.contains("\"bytes_written\":2"), "{s}");
25892382

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

0 commit comments

Comments
 (0)