Skip to content

Commit e3bc25c

Browse files
authored
Extract out trigger sigpipe helper and test it (#1235)
1 parent a7c8765 commit e3bc25c

File tree

3 files changed

+106
-42
lines changed

3 files changed

+106
-42
lines changed

bin_tests/src/modes/behavior.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ use std::path::Path;
1010
use std::sync::atomic::{AtomicPtr, Ordering};
1111

1212
use crate::modes::unix::*;
13+
use nix::sys::socket;
14+
use std::os::unix::io::AsRawFd;
1315

1416
/// Defines the additional behavior for a given crashtracking test
1517
pub trait Behavior {
@@ -89,6 +91,32 @@ pub fn remove_permissive(filepath: &Path) {
8991
let _ = std::fs::remove_file(filepath);
9092
}
9193

94+
// This helper function is used to trigger a SIGPIPE signal. This is useful to
95+
// verify that the crashtracker correctly suppresses the SIGPIPE signal while it
96+
// emitts information to the collector, and that the SIGPIPE signal can be emitted
97+
// and used normally afterwards, as tested in the following tests:
98+
// - test_001_sigpipe
99+
// - test_005_sigpipe_sigstack
100+
pub fn trigger_sigpipe() -> Result<()> {
101+
let (reader_fd, writer_fd) = socket::socketpair(
102+
socket::AddressFamily::Unix,
103+
socket::SockType::Stream,
104+
None,
105+
socket::SockFlag::empty(),
106+
)?;
107+
drop(reader_fd);
108+
109+
let writer_raw_fd = writer_fd.as_raw_fd();
110+
let write_result =
111+
unsafe { libc::write(writer_raw_fd, b"Hello".as_ptr() as *const libc::c_void, 5) };
112+
113+
if write_result != -1 {
114+
anyhow::bail!("Expected write to fail with SIGPIPE, but it succeeded");
115+
}
116+
117+
Ok(())
118+
}
119+
92120
pub fn get_behavior(mode_str: &str) -> Box<dyn Behavior> {
93121
match mode_str {
94122
"donothing" => Box::new(test_000_donothing::Test),
@@ -104,3 +132,77 @@ pub fn get_behavior(mode_str: &str) -> Box<dyn Behavior> {
104132
_ => panic!("Unknown mode: {mode_str}"),
105133
}
106134
}
135+
136+
#[cfg(test)]
137+
mod tests {
138+
use super::*;
139+
use std::sync::atomic::{AtomicBool, Ordering};
140+
141+
static SIGPIPE_CAUGHT: AtomicBool = AtomicBool::new(false);
142+
143+
extern "C" fn sigpipe_handler(_: libc::c_int) {
144+
SIGPIPE_CAUGHT.store(true, Ordering::SeqCst);
145+
}
146+
147+
#[test]
148+
#[cfg_attr(miri, ignore)]
149+
fn test_trigger_sigpipe() {
150+
use std::mem;
151+
use std::thread;
152+
153+
let result = thread::spawn(|| {
154+
SIGPIPE_CAUGHT.store(false, Ordering::SeqCst);
155+
156+
let mut sigset: libc::sigset_t = unsafe { mem::zeroed() };
157+
unsafe {
158+
libc::sigemptyset(&mut sigset);
159+
}
160+
161+
let sigpipe_action = libc::sigaction {
162+
sa_sigaction: sigpipe_handler as usize,
163+
sa_mask: sigset,
164+
sa_flags: libc::SA_RESTART | libc::SA_SIGINFO,
165+
#[cfg(target_os = "linux")]
166+
sa_restorer: None,
167+
};
168+
169+
let mut old_action: libc::sigaction = unsafe { mem::zeroed() };
170+
let install_result =
171+
unsafe { libc::sigaction(libc::SIGPIPE, &sigpipe_action, &mut old_action) };
172+
173+
if install_result != 0 {
174+
return Err("Failed to set up SIGPIPE handler".to_string());
175+
}
176+
177+
let trigger_result = trigger_sigpipe();
178+
179+
thread::sleep(std::time::Duration::from_millis(10));
180+
181+
let handler_called = SIGPIPE_CAUGHT.load(Ordering::SeqCst);
182+
183+
unsafe {
184+
libc::sigaction(libc::SIGPIPE, &old_action, std::ptr::null_mut());
185+
}
186+
187+
if trigger_result.is_err() {
188+
return Err(format!(
189+
"trigger_sigpipe should succeed: {:?}",
190+
trigger_result
191+
));
192+
}
193+
194+
if !handler_called {
195+
return Err("SIGPIPE handler should have been called".to_string());
196+
}
197+
198+
Ok(())
199+
})
200+
.join();
201+
202+
match result {
203+
Ok(Ok(())) => {} // Test passed
204+
Ok(Err(e)) => panic!("{}", e),
205+
Err(_) => panic!("Thread panicked during SIGPIPE test"),
206+
}
207+
}
208+
}

bin_tests/src/modes/unix/test_001_sigpipe.rs

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,11 @@
1919
use crate::modes::behavior::Behavior;
2020
use crate::modes::behavior::{
2121
atom_to_clone, file_append_msg, fileat_content_equals, remove_permissive, removeat_permissive,
22-
set_atomic,
22+
set_atomic, trigger_sigpipe,
2323
};
2424

2525
use datadog_crashtracker::CrashtrackerConfiguration;
2626
use libc;
27-
use nix::sys::socket;
28-
use std::os::unix::io::AsRawFd;
2927
use std::path::{Path, PathBuf};
3028
use std::sync::atomic::AtomicPtr;
3129

@@ -79,24 +77,7 @@ fn inner(output_dir: &Path, filename: &str) -> anyhow::Result<()> {
7977
set_atomic(&OUTPUT_FILE, output_dir.join(filename));
8078
let ofile = atom_to_clone(&OUTPUT_FILE)?;
8179

82-
// Cause a SIGPIPE to occur by opening a socketpair, closing the read side, and writing into
83-
// the write side. Use raw write() syscall to bypass Rust's MSG_NOSIGNAL protection.
84-
let (reader_fd, writer_fd) = socket::socketpair(
85-
socket::AddressFamily::Unix,
86-
socket::SockType::Stream,
87-
None,
88-
socket::SockFlag::empty(),
89-
)?;
90-
drop(reader_fd);
91-
92-
// Use raw write() syscall instead of Rust's write_all() to avoid MSG_NOSIGNAL
93-
let writer_raw_fd = writer_fd.as_raw_fd();
94-
let write_result =
95-
unsafe { libc::write(writer_raw_fd, b"Hello".as_ptr() as *const libc::c_void, 5) };
96-
97-
if write_result != -1 {
98-
anyhow::bail!("Expected write to fail with SIGPIPE, but it succeeded");
99-
}
80+
trigger_sigpipe()?;
10081

10182
// Now check the output file. Strongly assumes that nothing happened to change the value of
10283
// OUTPUT_FILE within the handler.

bin_tests/src/modes/unix/test_005_sigpipe_sigstack.rs

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,11 @@
1010
use crate::modes::behavior::Behavior;
1111
use crate::modes::behavior::{
1212
atom_to_clone, file_append_msg, file_content_equals, fileat_content_equals, remove_permissive,
13-
removeat_permissive, set_atomic,
13+
removeat_permissive, set_atomic, trigger_sigpipe,
1414
};
1515

1616
use datadog_crashtracker::CrashtrackerConfiguration;
1717
use libc;
18-
use nix::sys::socket;
19-
use std::os::unix::io::AsRawFd;
2018
use std::path::{Path, PathBuf};
2119
use std::sync::atomic::AtomicPtr;
2220

@@ -70,24 +68,7 @@ fn inner(output_dir: &Path, filename: &str) -> anyhow::Result<()> {
7068
set_atomic(&OUTPUT_FILE, output_dir.join(filename));
7169
let ofile = atom_to_clone(&OUTPUT_FILE)?;
7270

73-
// Cause a SIGPIPE to occur by opening a socketpair, closing the read side, and writing into
74-
// the write side. Use raw write() syscall to bypass Rust's MSG_NOSIGNAL protection.
75-
let (reader_fd, writer_fd) = socket::socketpair(
76-
socket::AddressFamily::Unix,
77-
socket::SockType::Stream,
78-
None,
79-
socket::SockFlag::empty(),
80-
)?;
81-
drop(reader_fd);
82-
83-
// Use raw write() syscall instead of Rust's write_all() to avoid MSG_NOSIGNAL
84-
let writer_raw_fd = writer_fd.as_raw_fd();
85-
let write_result =
86-
unsafe { libc::write(writer_raw_fd, b"Hello".as_ptr() as *const libc::c_void, 5) };
87-
88-
if write_result != -1 {
89-
anyhow::bail!("Expected write to fail with SIGPIPE, but it succeeded");
90-
}
71+
trigger_sigpipe()?;
9172

9273
// Now check the output file. Strongly assumes that nothing happened to change the value of
9374
// OUTPUT_FILE within the handler.

0 commit comments

Comments
 (0)