Skip to content

Commit 0dbf112

Browse files
authored
Merge pull request #37 from hyperlight-dev/fix/cleanup
refactor: remove dead code, fix silent socket option no-ops, fix tempdir leak
2 parents 2ad29b8 + 6c578d8 commit 0dbf112

2 files changed

Lines changed: 27 additions & 229 deletions

File tree

demos/pptx-gen/src/main.rs

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ fn execute_in_sandbox(
197197
debug!("script: {:?}", script_path);
198198

199199
let cpio_start = std::time::Instant::now();
200-
let modified_rootfs = inject_script_into_rootfs(rootfs, &script_path)?;
200+
let (modified_rootfs, _rootfs_tmpdir) = inject_script_into_rootfs(rootfs, &script_path)?;
201201
if timing {
202202
info!(" cpio inject: {:?}", cpio_start.elapsed());
203203
}
@@ -228,7 +228,7 @@ fn execute_in_sandbox(
228228
Ok(vm_output.output)
229229
}
230230

231-
fn inject_script_into_rootfs(original_rootfs: &Path, script_path: &Path) -> Result<PathBuf> {
231+
fn inject_script_into_rootfs(original_rootfs: &Path, script_path: &Path) -> Result<(PathBuf, tempfile::TempDir)> {
232232
let temp_dir = tempfile::tempdir()?;
233233
let extract_dir = temp_dir.path().join("rootfs");
234234
let new_cpio = temp_dir.path().join("rootfs_with_script.cpio");
@@ -270,11 +270,7 @@ fn inject_script_into_rootfs(original_rootfs: &Path, script_path: &Path) -> Resu
270270
anyhow::bail!("cpio create failed");
271271
}
272272

273-
// Leak tempdir so file persists
274-
let path = new_cpio.clone();
275-
std::mem::forget(temp_dir);
276-
277-
Ok(path)
273+
Ok((new_cpio, temp_dir))
278274
}
279275

280276
fn extract_pptx_from_output(output: &str) -> Result<Vec<u8>> {

host/src/lib.rs

Lines changed: 24 additions & 222 deletions
Original file line numberDiff line numberDiff line change
@@ -830,216 +830,6 @@ impl FsSandbox {
830830
}
831831
Ok(out)
832832
}
833-
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-
}
1043833
}
1044834

1045835
/// Internal helper: assemble the final tool registry from caller-supplied
@@ -1389,14 +1179,18 @@ fn register_net_tools(
13891179
// SOL_SOCKET=1, SO_REUSEADDR=2
13901180
if level == 1 && optname == 2 {
13911181
sock.set_reuse_address(value != 0)?;
1392-
}
13931182
// SOL_SOCKET=1, SO_KEEPALIVE=9
1394-
if level == 1 && optname == 9 {
1183+
} else if level == 1 && optname == 9 {
13951184
sock.set_keepalive(value != 0)?;
1396-
}
13971185
// IPPROTO_TCP=6, TCP_NODELAY=1
1398-
if level == 6 && optname == 1 {
1186+
} else if level == 6 && optname == 1 {
13991187
sock.set_nodelay(value != 0)?;
1188+
} else {
1189+
return Err(anyhow!(
1190+
"unsupported socket option: level={}, optname={}",
1191+
level,
1192+
optname
1193+
));
14001194
}
14011195
Ok(json!({}))
14021196
});
@@ -1417,7 +1211,11 @@ fn register_net_tools(
14171211
} else if level == 6 && optname == 1 {
14181212
sock.nodelay()? as i32
14191213
} else {
1420-
0
1214+
return Err(anyhow!(
1215+
"unsupported socket option: level={}, optname={}",
1216+
level,
1217+
optname
1218+
));
14211219
};
14221220
Ok(json!({ "value": val }))
14231221
});
@@ -2657,10 +2455,11 @@ mod tests {
26572455
// End-to-end through the tool registry: the error surface the
26582456
// guest actually sees.
26592457
let root = tmpdir("dispatch");
2458+
let preopens = vec![Preopen::new(&root, "/host").unwrap()];
26602459
let mut reg = ToolRegistry::new();
2661-
FsSandbox::new(&root).unwrap().register(&mut reg);
2460+
FsRouter::new(&preopens).unwrap().register(&mut reg);
26622461

2663-
let req = br#"{"name":"fs_read","args":{"path":"../outside.txt"}}"#;
2462+
let req = br#"{"name":"fs_read","args":{"path":"/host/../outside.txt"}}"#;
26642463
let resp = reg.dispatch(req);
26652464
let s = std::str::from_utf8(&resp).unwrap();
26662465
assert!(s.contains("\"error\""), "{s}");
@@ -2740,15 +2539,16 @@ mod tests {
27402539
#[test]
27412540
fn fs_write_then_read_roundtrip() {
27422541
let root = tmpdir("roundtrip");
2542+
let preopens = vec![Preopen::new(&root, "/host").unwrap()];
27432543
let mut reg = ToolRegistry::new();
2744-
FsSandbox::new(&root).unwrap().register(&mut reg);
2544+
FsRouter::new(&preopens).unwrap().register(&mut reg);
27452545

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

2751-
let r = br#"{"name":"fs_read","args":{"path":"hello.txt"}}"#;
2551+
let r = br#"{"name":"fs_read","args":{"path":"/host/hello.txt"}}"#;
27522552
let resp = reg.dispatch(r);
27532553
let s = std::str::from_utf8(&resp).unwrap();
27542554
assert!(s.contains("\"text\":\"hi\""), "{s}");
@@ -3006,10 +2806,12 @@ mod tests {
30062806
fn test_fs_read_bytes_capped() {
30072807
let root = tmpdir("readcap");
30082808
fs::write(root.join("small.bin"), b"hello").unwrap();
2809+
let preopens = vec![Preopen::new(&root, "/host").unwrap()];
30092810
let mut reg = ToolRegistry::new();
3010-
FsSandbox::new(&root).unwrap().register(&mut reg);
2811+
FsRouter::new(&preopens).unwrap().register(&mut reg);
30112812

3012-
let req = br#"{"name":"fs_read_bytes","args":{"path":"small.bin","len":1099511627776}}"#;
2813+
let req =
2814+
br#"{"name":"fs_read_bytes","args":{"path":"/host/small.bin","len":1099511627776}}"#;
30132815
let resp = reg.dispatch(req);
30142816
let s = std::str::from_utf8(&resp).unwrap();
30152817
assert!(!s.contains("\"error\""), "should succeed: {s}");

0 commit comments

Comments
 (0)