Skip to content

Commit 9a59732

Browse files
committed
fix: add Drop for Capture, DGRAM SO_TYPE test, fix fd leak
Signed-off-by: danbugs <danilochiarlone@gmail.com>
1 parent 7c0532a commit 9a59732

2 files changed

Lines changed: 44 additions & 19 deletions

File tree

host/src/lib.rs

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2818,14 +2818,11 @@ mod tests {
28182818

28192819
#[test]
28202820
fn test_fs_read_bytes_capped() {
2821-
// Request a huge len (well above MAX_FS_READ) on a small file.
2822-
// Without the cap this would try to allocate terabytes and OOM.
28232821
let root = tmpdir("readcap");
28242822
fs::write(root.join("small.bin"), b"hello").unwrap();
28252823
let mut reg = ToolRegistry::new();
28262824
FsSandbox::new(&root).unwrap().register(&mut reg);
28272825

2828-
// Ask for 1 TiB — the cap should silently clamp to MAX_FS_READ.
28292826
let req = br#"{"name":"fs_read_bytes","args":{"path":"small.bin","len":1099511627776}}"#;
28302827
let resp = reg.dispatch(req);
28312828
let s = std::str::from_utf8(&resp).unwrap();
@@ -2835,14 +2832,8 @@ mod tests {
28352832

28362833
#[test]
28372834
fn test_sleep_capped() {
2838-
// Verify the cap constant and that sleeping with a huge value
2839-
// completes quickly (the cap brings it down to 60 s max, but we
2840-
// pass 0 to keep the test instant — the important thing is
2841-
// confirming the cap constant exists and has the right value).
28422835
assert_eq!(MAX_SLEEP_NS, 60_000_000_000);
28432836

2844-
// Dispatch a sleep with ns=0 through the real handler to confirm
2845-
// the code path works.
28462837
let mut tools = ToolRegistry::new();
28472838
let exit_code = Arc::new(AtomicI32::new(0));
28482839
register_internal_tools(&mut tools, &exit_code, None, None);
@@ -2852,4 +2843,27 @@ mod tests {
28522843
let s = std::str::from_utf8(&resp).unwrap();
28532844
assert!(!s.contains("\"error\""), "sleep(0) should succeed: {s}");
28542845
}
2846+
2847+
#[test]
2848+
fn net_getsockopt_returns_correct_type_for_dgram() {
2849+
let mut reg = ToolRegistry::new();
2850+
let policy = NetworkPolicy::AllowAll;
2851+
register_net_tools(&mut reg, &policy, None);
2852+
2853+
let req = br#"{"name":"net_socket","args":{"family":2,"type":2}}"#;
2854+
let resp = std::str::from_utf8(&reg.dispatch(req)).unwrap().to_string();
2855+
let v: serde_json::Value = serde_json::from_str(&resp).unwrap();
2856+
let fd = v["result"]["fd"].as_u64().unwrap();
2857+
2858+
let req =
2859+
format!(r#"{{"name":"net_getsockopt","args":{{"fd":{fd},"level":1,"optname":3}}}}"#);
2860+
let resp = std::str::from_utf8(&reg.dispatch(req.as_bytes()))
2861+
.unwrap()
2862+
.to_string();
2863+
let v: serde_json::Value = serde_json::from_str(&resp).unwrap();
2864+
assert_eq!(
2865+
v["result"]["value"], 2,
2866+
"SO_TYPE should return 2 (DGRAM), got: {resp}"
2867+
);
2868+
}
28552869
}

host/src/stderr_capture.rs

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
mod imp {
99
use anyhow::Result;
1010
use nix::unistd;
11-
use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd, OwnedFd};
11+
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
1212
use std::path::Path;
1313
use std::sync::{Mutex, MutexGuard};
1414

@@ -18,7 +18,7 @@ mod imp {
1818
static STDERR_LOCK: Mutex<()> = Mutex::new(());
1919

2020
pub struct Capture {
21-
original_stderr: OwnedFd,
21+
original_stderr: Option<OwnedFd>,
2222
_guard: MutexGuard<'static, ()>,
2323
}
2424

@@ -27,23 +27,34 @@ mod imp {
2727
let guard = STDERR_LOCK
2828
.lock()
2929
.unwrap_or_else(|poisoned| poisoned.into_inner());
30-
let capture_fd = std::fs::File::create(path)?.into_raw_fd();
31-
let original_stderr_raw = unistd::dup(2)?;
32-
unistd::dup2(capture_fd, 2)?;
33-
unistd::close(capture_fd)?;
34-
let original_stderr = unsafe { OwnedFd::from_raw_fd(original_stderr_raw) };
30+
let capture_file = std::fs::File::create(path)?;
31+
let original_stderr =
32+
OwnedFd::from(unistd::dup(2).map(|fd| unsafe { OwnedFd::from_raw_fd(fd) })?);
33+
unistd::dup2(capture_file.as_raw_fd(), 2)?;
34+
// capture_file dropped here — its OwnedFd closes the fd via RAII
3535
Ok(Self {
36-
original_stderr,
36+
original_stderr: Some(original_stderr),
3737
_guard: guard,
3838
})
3939
}
4040

4141
pub fn restore(self) -> Result<()> {
42-
unistd::dup2(self.original_stderr.as_raw_fd(), 2)?;
43-
// _guard is dropped here, releasing STDERR_LOCK
42+
if let Some(ref fd) = self.original_stderr {
43+
unistd::dup2(fd.as_raw_fd(), 2)?;
44+
}
45+
// Drop restores via Drop impl if restore() wasn't called,
46+
// but explicit call lets caller handle errors.
4447
Ok(())
4548
}
4649
}
50+
51+
impl Drop for Capture {
52+
fn drop(&mut self) {
53+
if let Some(ref fd) = self.original_stderr.take() {
54+
let _ = unistd::dup2(fd.as_raw_fd(), 2);
55+
}
56+
}
57+
}
4758
}
4859

4960
#[cfg(windows)]

0 commit comments

Comments
 (0)