• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

NVIDIA / nvrc / 20926017676

12 Jan 2026 04:01PM UTC coverage: 88.809% (-1.3%) from 90.078%
20926017676

Pull #102

github

web-flow
Merge fea7bdc09 into 8a99e167e
Pull Request #102: hardened_std: eliminate thread::sleep from production code

10 of 40 new or added lines in 3 files covered. (25.0%)

4 existing lines in 1 file now uncovered.

1849 of 2082 relevant lines covered (88.81%)

11.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.98
/src/syslog.rs
1
// SPDX-License-Identifier: Apache-2.0
2
// Copyright (c) NVIDIA CORPORATION
3

4
//! Minimal syslog sink for ephemeral init environments.
5
//!
6
//! Programs expect /dev/log to exist for logging. As a minimal init we provide
7
//! this socket and forward messages to the kernel log via trace!(). Severity
8
//! levels are stripped since all messages go to the same destination anyway.
9

10
use log::trace;
11
use nix::poll::{PollFd, PollFlags, PollTimeout};
12
use once_cell::sync::OnceCell;
13
use std::os::fd::AsFd;
14
use std::os::unix::net::UnixDatagram;
15
use std::path::Path;
16

17
/// Global syslog socket—lazily initialized on first poll().
18
/// OnceCell ensures thread-safe one-time init. Ephemeral init runs once,
19
/// no need for reset capability.
20
static SYSLOG: OnceCell<UnixDatagram> = OnceCell::new();
21

22
const DEV_LOG: &str = "/dev/log";
23

24
/// Create and bind a Unix datagram socket at the given path.
25
fn bind(path: &Path) -> std::io::Result<UnixDatagram> {
30✔
26
    UnixDatagram::bind(path)
30✔
27
}
30✔
28

29
/// Check socket for pending messages (non-blocking).
30
/// Returns None if no data available, Some(msg) if a message was read.
31
fn poll_socket(sock: &UnixDatagram) -> std::io::Result<Option<String>> {
20✔
32
    let mut fds = [PollFd::new(sock.as_fd(), PollFlags::POLLIN)];
20✔
33
    // Non-blocking poll—init loop calls this frequently, can't afford to block
34
    let count = nix::poll::poll(&mut fds, PollTimeout::ZERO)
20✔
35
        .map_err(|e| std::io::Error::other(e.to_string()))?;
20✔
36

37
    if count == 0 {
20✔
38
        return Ok(None); // No events, no data waiting
8✔
39
    }
12✔
40

41
    let Some(revents) = fds[0].revents() else {
12✔
42
        return Ok(None); // Shouldn't happen, but handle gracefully
×
43
    };
44

45
    if !revents.contains(PollFlags::POLLIN) {
12✔
46
        return Ok(None); // Event wasn't POLLIN (e.g., error flag)
×
47
    }
12✔
48

49
    // Read the message—4KB buffer matches typical syslog max message size
50
    let mut buf = [0u8; 4096];
12✔
51
    let (len, _) = sock.recv_from(&mut buf)?;
12✔
52
    let msg = String::from_utf8_lossy(&buf[..len]);
12✔
53
    Ok(Some(strip_priority(msg.trim_end()).to_string()))
12✔
54
}
20✔
55

56
/// Poll the global /dev/log socket, logging any message via trace!().
57
/// Lazily initializes /dev/log on first call.
58
/// Drains one message per call—rate-limited to prevent DoS by syslog flooding.
59
/// Caller loops at ~2 msg/sec (500ms sleep between calls).
60
pub fn poll() -> std::io::Result<()> {
2✔
61
    poll_at(Path::new(DEV_LOG))
2✔
62
}
2✔
63

64
/// Poll with timeout (milliseconds). Blocks until data arrives or timeout.
65
/// Returns Ok(true) if a message was processed, Ok(false) on timeout.
66
/// This replaces the sleep+poll pattern, eliminating thread::sleep dependency.
67
pub fn poll_timeout(timeout_ms: i32) -> std::io::Result<bool> {
4✔
68
    poll_timeout_at(Path::new(DEV_LOG), timeout_ms)
4✔
69
}
4✔
70

71
/// Internal: poll with timeout at a specific path.
72
fn poll_timeout_at(path: &Path, timeout_ms: i32) -> std::io::Result<bool> {
4✔
73
    let sock: &UnixDatagram = if path == Path::new(DEV_LOG) {
4✔
74
        SYSLOG.get_or_try_init(|| bind(path))?
4✔
75
    } else {
NEW
76
        return poll_once_timeout(path, timeout_ms);
×
77
    };
78

NEW
79
    poll_socket_timeout(sock, timeout_ms)
×
80
}
4✔
81

82
/// Poll socket with timeout. Returns Ok(true) if message read, Ok(false) on timeout.
NEW
83
fn poll_socket_timeout(sock: &UnixDatagram, timeout_ms: i32) -> std::io::Result<bool> {
×
NEW
84
    let mut fds = [PollFd::new(sock.as_fd(), PollFlags::POLLIN)];
×
NEW
85
    let timeout = if timeout_ms < 0 {
×
NEW
86
        PollTimeout::NONE
×
87
    } else {
88
        // Clamp to u16::MAX (65535ms = ~65s) then convert infallibly
NEW
89
        let ms = (timeout_ms as u32).min(u16::MAX as u32) as u16;
×
NEW
90
        PollTimeout::from(ms)
×
91
    };
92

NEW
93
    let count =
×
NEW
94
        nix::poll::poll(&mut fds, timeout).map_err(|e| std::io::Error::other(e.to_string()))?;
×
95

NEW
96
    if count == 0 {
×
NEW
97
        return Ok(false); // Timeout
×
NEW
98
    }
×
99

NEW
100
    let Some(revents) = fds[0].revents() else {
×
NEW
101
        return Ok(false);
×
102
    };
103

NEW
104
    if !revents.contains(PollFlags::POLLIN) {
×
NEW
105
        return Ok(false);
×
NEW
106
    }
×
107

NEW
108
    let mut buf = [0u8; 4096];
×
NEW
109
    let (len, _) = sock.recv_from(&mut buf)?;
×
NEW
110
    let msg = String::from_utf8_lossy(&buf[..len]);
×
NEW
111
    trace!("{}", strip_priority(msg.trim_end()));
×
NEW
112
    Ok(true)
×
NEW
113
}
×
114

115
/// One-shot poll with timeout for testing.
NEW
116
fn poll_once_timeout(path: &Path, timeout_ms: i32) -> std::io::Result<bool> {
×
NEW
117
    let sock = bind(path)?;
×
NEW
118
    poll_socket_timeout(&sock, timeout_ms)
×
NEW
119
}
×
120

121
/// Internal: poll a specific socket path (for unit tests).
122
/// Production code uses poll() which hardcodes /dev/log.
123
fn poll_at(path: &Path) -> std::io::Result<()> {
4✔
124
    let sock: &UnixDatagram = if path == Path::new(DEV_LOG) {
4✔
125
        SYSLOG.get_or_try_init(|| bind(path))?
2✔
126
    } else {
127
        // For testing: create a one-shot socket (caller manages lifecycle)
128
        return poll_once(path);
2✔
129
    };
130

UNCOV
131
    if let Some(msg) = poll_socket(sock)? {
×
UNCOV
132
        trace!("{}", msg);
×
UNCOV
133
    }
×
134

135
    Ok(())
×
136
}
4✔
137

138
/// One-shot poll for testing: bind, poll once, return.
139
/// Socket is dropped after call—suitable for tests with temp paths.
140
fn poll_once(path: &Path) -> std::io::Result<()> {
4✔
141
    let sock = bind(path)?;
4✔
142
    if let Some(msg) = poll_socket(&sock)? {
4✔
UNCOV
143
        trace!("{}", msg);
×
144
    }
4✔
145
    Ok(())
4✔
146
}
4✔
147

148
/// Strip the syslog priority prefix <N> from a message.
149
/// Priority levels are noise for us—all messages go to trace!() equally.
150
/// Example: "<6>hello" → "hello"
151
fn strip_priority(msg: &str) -> &str {
30✔
152
    msg.strip_prefix('<')
30✔
153
        .and_then(|s| s.find('>').map(|i| &s[i + 1..]))
30✔
154
        .unwrap_or(msg)
30✔
155
}
30✔
156

157
#[cfg(test)]
158
mod tests {
159
    use super::*;
160
    use tempfile::TempDir;
161

162
    // === strip_priority tests ===
163

164
    #[test]
165
    fn test_strip_priority_normal() {
2✔
166
        assert_eq!(strip_priority("<6>test message"), "test message");
2✔
167
        assert_eq!(strip_priority("<13>another msg"), "another msg");
2✔
168
        assert_eq!(strip_priority("<191>high pri"), "high pri");
2✔
169
    }
2✔
170

171
    #[test]
172
    fn test_strip_priority_no_prefix() {
2✔
173
        assert_eq!(strip_priority("no prefix"), "no prefix");
2✔
174
    }
2✔
175

176
    #[test]
177
    fn test_strip_priority_edge_cases() {
2✔
178
        assert_eq!(strip_priority("<>empty"), "empty");
2✔
179
        assert_eq!(strip_priority("<6>"), "");
2✔
180
        assert_eq!(strip_priority(""), "");
2✔
181
        assert_eq!(strip_priority("<"), "<");
2✔
182
        assert_eq!(strip_priority("<6"), "<6"); // No closing >
2✔
183
    }
2✔
184

185
    // === bind tests ===
186

187
    #[test]
188
    fn test_bind_success() {
2✔
189
        let tmp = TempDir::new().unwrap();
2✔
190
        let path = tmp.path().join("test.sock");
2✔
191
        let sock = bind(&path);
2✔
192
        assert!(sock.is_ok());
2✔
193
    }
2✔
194

195
    #[test]
196
    fn test_bind_nonexistent_dir() {
2✔
197
        let path = Path::new("/nonexistent/dir/test.sock");
2✔
198
        let err = bind(path).unwrap_err();
2✔
199
        // Should fail with "No such file or directory" (ENOENT)
200
        assert_eq!(err.kind(), std::io::ErrorKind::NotFound);
2✔
201
    }
2✔
202

203
    #[test]
204
    fn test_bind_already_exists() {
2✔
205
        let tmp = TempDir::new().unwrap();
2✔
206
        let path = tmp.path().join("test.sock");
2✔
207
        let _sock1 = bind(&path).unwrap();
2✔
208
        // Binding again to same path should fail with "Address already in use"
209
        let err = bind(&path).unwrap_err();
2✔
210
        assert_eq!(err.kind(), std::io::ErrorKind::AddrInUse);
2✔
211
    }
2✔
212

213
    // === poll_socket tests ===
214

215
    #[test]
216
    fn test_poll_socket_no_data() {
2✔
217
        let tmp = TempDir::new().unwrap();
2✔
218
        let path = tmp.path().join("test.sock");
2✔
219
        let sock = bind(&path).unwrap();
2✔
220

221
        let result = poll_socket(&sock).unwrap();
2✔
222
        assert_eq!(result, None);
2✔
223
    }
2✔
224

225
    #[test]
226
    fn test_poll_socket_with_data() {
2✔
227
        let tmp = TempDir::new().unwrap();
2✔
228
        let path = tmp.path().join("test.sock");
2✔
229
        let server = bind(&path).unwrap();
2✔
230

231
        let client = UnixDatagram::unbound().unwrap();
2✔
232
        client.send_to(b"<6>hello world", &path).unwrap();
2✔
233

234
        let result = poll_socket(&server).unwrap();
2✔
235
        assert_eq!(result, Some("hello world".to_string()));
2✔
236
    }
2✔
237

238
    #[test]
239
    fn test_poll_socket_strips_priority() {
2✔
240
        let tmp = TempDir::new().unwrap();
2✔
241
        let path = tmp.path().join("test.sock");
2✔
242
        let server = bind(&path).unwrap();
2✔
243

244
        let client = UnixDatagram::unbound().unwrap();
2✔
245
        client.send_to(b"<3>error message", &path).unwrap();
2✔
246

247
        let result = poll_socket(&server).unwrap();
2✔
248
        assert_eq!(result, Some("error message".to_string()));
2✔
249
    }
2✔
250

251
    #[test]
252
    fn test_poll_socket_multiple_messages() {
2✔
253
        let tmp = TempDir::new().unwrap();
2✔
254
        let path = tmp.path().join("test.sock");
2✔
255
        let server = bind(&path).unwrap();
2✔
256

257
        let client = UnixDatagram::unbound().unwrap();
2✔
258
        client.send_to(b"<6>first", &path).unwrap();
2✔
259
        client.send_to(b"<6>second", &path).unwrap();
2✔
260

261
        // poll_socket drains one at a time
262
        let result1 = poll_socket(&server).unwrap();
2✔
263
        assert_eq!(result1, Some("first".to_string()));
2✔
264

265
        let result2 = poll_socket(&server).unwrap();
2✔
266
        assert_eq!(result2, Some("second".to_string()));
2✔
267

268
        // No more messages
269
        let result3 = poll_socket(&server).unwrap();
2✔
270
        assert_eq!(result3, None);
2✔
271
    }
2✔
272

273
    #[test]
274
    fn test_poll_socket_trims_trailing_whitespace() {
2✔
275
        let tmp = TempDir::new().unwrap();
2✔
276
        let path = tmp.path().join("test.sock");
2✔
277
        let server = bind(&path).unwrap();
2✔
278

279
        let client = UnixDatagram::unbound().unwrap();
2✔
280
        client.send_to(b"<6>message with newline\n", &path).unwrap();
2✔
281

282
        let result = poll_socket(&server).unwrap();
2✔
283
        assert_eq!(result, Some("message with newline".to_string()));
2✔
284
    }
2✔
285

286
    // === poll_at / poll_once tests ===
287

288
    #[test]
289
    fn test_poll_once_no_data() {
2✔
290
        let tmp = TempDir::new().unwrap();
2✔
291
        let path = tmp.path().join("test.sock");
2✔
292

293
        // poll_once will bind and poll - should succeed with no messages
294
        let result = poll_once(&path);
2✔
295
        assert!(result.is_ok());
2✔
296
    }
2✔
297

298
    #[test]
299
    fn test_poll_once_with_data() {
2✔
300
        let tmp = TempDir::new().unwrap();
2✔
301
        let path = tmp.path().join("test.sock");
2✔
302

303
        // Create server socket first
304
        let server = bind(&path).unwrap();
2✔
305

306
        // Send data
307
        let client = UnixDatagram::unbound().unwrap();
2✔
308
        client.send_to(b"<6>poll_once test", &path).unwrap();
2✔
309

310
        // poll_socket on the server
311
        let result = poll_socket(&server).unwrap();
2✔
312
        assert_eq!(result, Some("poll_once test".to_string()));
2✔
313
    }
2✔
314

315
    #[test]
316
    fn test_poll_at_custom_path() {
2✔
317
        let tmp = TempDir::new().unwrap();
2✔
318
        let path = tmp.path().join("custom.sock");
2✔
319

320
        // poll_at with non-/dev/log path uses poll_once internally
321
        let result = poll_at(&path);
2✔
322
        assert!(result.is_ok());
2✔
323
    }
2✔
324

325
    #[test]
326
    fn test_poll_dev_log() {
2✔
327
        // poll() tries to bind /dev/log - may fail if already bound or no permission
328
        // Just exercise the code path, don't assert success
329
        let _ = poll();
2✔
330
    }
2✔
331
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc