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

NVIDIA / nvrc / 20381075488

19 Dec 2025 07:59PM UTC coverage: 89.348% (+8.9%) from 80.415%
20381075488

Pull #85

github

web-flow
Merge 40e9e3e58 into 2295d6b0d
Pull Request #85: Update coverage.yaml to use cargo-llvm-cov

1233 of 1380 relevant lines covered (89.35%)

4.08 hits per line

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

95.3
/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> {
15✔
26
    UnixDatagram::bind(path)
15✔
27
}
15✔
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>> {
10✔
32
    let mut fds = [PollFd::new(sock.as_fd(), PollFlags::POLLIN)];
10✔
33
    // Non-blocking poll—init loop calls this frequently, can't afford to block
34
    let count = nix::poll::poll(&mut fds, PollTimeout::ZERO)
10✔
35
        .map_err(|e| std::io::Error::other(e.to_string()))?;
10✔
36

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

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

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

49
    // Read the message—4KB buffer matches typical syslog max message size
50
    let mut buf = [0u8; 4096];
6✔
51
    let (len, _) = sock.recv_from(&mut buf)?;
6✔
52
    let msg = String::from_utf8_lossy(&buf[..len]);
6✔
53
    Ok(Some(strip_priority(msg.trim_end()).to_string()))
6✔
54
}
10✔
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<()> {
3✔
61
    poll_at(Path::new(DEV_LOG))
3✔
62
}
3✔
63

64
/// Internal: poll a specific socket path (for unit tests).
65
/// Production code uses poll() which hardcodes /dev/log.
66
fn poll_at(path: &Path) -> std::io::Result<()> {
4✔
67
    let sock: &UnixDatagram = if path == Path::new(DEV_LOG) {
4✔
68
        SYSLOG.get_or_try_init(|| bind(path))?
3✔
69
    } else {
70
        // For testing: create a one-shot socket (caller manages lifecycle)
71
        return poll_once(path);
1✔
72
    };
73

74
    if let Some(msg) = poll_socket(sock)? {
×
75
        trace!("{}", msg);
×
76
    }
×
77

78
    Ok(())
×
79
}
4✔
80

81
/// One-shot poll for testing: bind, poll once, return.
82
/// Socket is dropped after call—suitable for tests with temp paths.
83
fn poll_once(path: &Path) -> std::io::Result<()> {
2✔
84
    let sock = bind(path)?;
2✔
85
    if let Some(msg) = poll_socket(&sock)? {
2✔
86
        trace!("{}", msg);
×
87
    }
2✔
88
    Ok(())
2✔
89
}
2✔
90

91
/// Strip the syslog priority prefix <N> from a message.
92
/// Priority levels are noise for us—all messages go to trace!() equally.
93
/// Example: "<6>hello" → "hello"
94
fn strip_priority(msg: &str) -> &str {
15✔
95
    msg.strip_prefix('<')
15✔
96
        .and_then(|s| s.find('>').map(|i| &s[i + 1..]))
15✔
97
        .unwrap_or(msg)
15✔
98
}
15✔
99

100
#[cfg(test)]
101
mod tests {
102
    use super::*;
103
    use tempfile::TempDir;
104

105
    // === strip_priority tests ===
106

107
    #[test]
108
    fn test_strip_priority_normal() {
1✔
109
        assert_eq!(strip_priority("<6>test message"), "test message");
1✔
110
        assert_eq!(strip_priority("<13>another msg"), "another msg");
1✔
111
        assert_eq!(strip_priority("<191>high pri"), "high pri");
1✔
112
    }
1✔
113

114
    #[test]
115
    fn test_strip_priority_no_prefix() {
1✔
116
        assert_eq!(strip_priority("no prefix"), "no prefix");
1✔
117
    }
1✔
118

119
    #[test]
120
    fn test_strip_priority_edge_cases() {
1✔
121
        assert_eq!(strip_priority("<>empty"), "empty");
1✔
122
        assert_eq!(strip_priority("<6>"), "");
1✔
123
        assert_eq!(strip_priority(""), "");
1✔
124
        assert_eq!(strip_priority("<"), "<");
1✔
125
        assert_eq!(strip_priority("<6"), "<6"); // No closing >
1✔
126
    }
1✔
127

128
    // === bind tests ===
129

130
    #[test]
131
    fn test_bind_success() {
1✔
132
        let tmp = TempDir::new().unwrap();
1✔
133
        let path = tmp.path().join("test.sock");
1✔
134
        let sock = bind(&path);
1✔
135
        assert!(sock.is_ok());
1✔
136
    }
1✔
137

138
    #[test]
139
    fn test_bind_nonexistent_dir() {
1✔
140
        let path = Path::new("/nonexistent/dir/test.sock");
1✔
141
        let err = bind(path).unwrap_err();
1✔
142
        // Should fail with "No such file or directory" (ENOENT)
143
        assert_eq!(err.kind(), std::io::ErrorKind::NotFound);
1✔
144
    }
1✔
145

146
    #[test]
147
    fn test_bind_already_exists() {
1✔
148
        let tmp = TempDir::new().unwrap();
1✔
149
        let path = tmp.path().join("test.sock");
1✔
150
        let _sock1 = bind(&path).unwrap();
1✔
151
        // Binding again to same path should fail with "Address already in use"
152
        let err = bind(&path).unwrap_err();
1✔
153
        assert_eq!(err.kind(), std::io::ErrorKind::AddrInUse);
1✔
154
    }
1✔
155

156
    // === poll_socket tests ===
157

158
    #[test]
159
    fn test_poll_socket_no_data() {
1✔
160
        let tmp = TempDir::new().unwrap();
1✔
161
        let path = tmp.path().join("test.sock");
1✔
162
        let sock = bind(&path).unwrap();
1✔
163

164
        let result = poll_socket(&sock).unwrap();
1✔
165
        assert_eq!(result, None);
1✔
166
    }
1✔
167

168
    #[test]
169
    fn test_poll_socket_with_data() {
1✔
170
        let tmp = TempDir::new().unwrap();
1✔
171
        let path = tmp.path().join("test.sock");
1✔
172
        let server = bind(&path).unwrap();
1✔
173

174
        let client = UnixDatagram::unbound().unwrap();
1✔
175
        client.send_to(b"<6>hello world", &path).unwrap();
1✔
176

177
        let result = poll_socket(&server).unwrap();
1✔
178
        assert_eq!(result, Some("hello world".to_string()));
1✔
179
    }
1✔
180

181
    #[test]
182
    fn test_poll_socket_strips_priority() {
1✔
183
        let tmp = TempDir::new().unwrap();
1✔
184
        let path = tmp.path().join("test.sock");
1✔
185
        let server = bind(&path).unwrap();
1✔
186

187
        let client = UnixDatagram::unbound().unwrap();
1✔
188
        client.send_to(b"<3>error message", &path).unwrap();
1✔
189

190
        let result = poll_socket(&server).unwrap();
1✔
191
        assert_eq!(result, Some("error message".to_string()));
1✔
192
    }
1✔
193

194
    #[test]
195
    fn test_poll_socket_multiple_messages() {
1✔
196
        let tmp = TempDir::new().unwrap();
1✔
197
        let path = tmp.path().join("test.sock");
1✔
198
        let server = bind(&path).unwrap();
1✔
199

200
        let client = UnixDatagram::unbound().unwrap();
1✔
201
        client.send_to(b"<6>first", &path).unwrap();
1✔
202
        client.send_to(b"<6>second", &path).unwrap();
1✔
203

204
        // poll_socket drains one at a time
205
        let result1 = poll_socket(&server).unwrap();
1✔
206
        assert_eq!(result1, Some("first".to_string()));
1✔
207

208
        let result2 = poll_socket(&server).unwrap();
1✔
209
        assert_eq!(result2, Some("second".to_string()));
1✔
210

211
        // No more messages
212
        let result3 = poll_socket(&server).unwrap();
1✔
213
        assert_eq!(result3, None);
1✔
214
    }
1✔
215

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

222
        let client = UnixDatagram::unbound().unwrap();
1✔
223
        client.send_to(b"<6>message with newline\n", &path).unwrap();
1✔
224

225
        let result = poll_socket(&server).unwrap();
1✔
226
        assert_eq!(result, Some("message with newline".to_string()));
1✔
227
    }
1✔
228

229
    // === poll_at / poll_once tests ===
230

231
    #[test]
232
    fn test_poll_once_no_data() {
1✔
233
        let tmp = TempDir::new().unwrap();
1✔
234
        let path = tmp.path().join("test.sock");
1✔
235

236
        // poll_once will bind and poll - should succeed with no messages
237
        let result = poll_once(&path);
1✔
238
        assert!(result.is_ok());
1✔
239
    }
1✔
240

241
    #[test]
242
    fn test_poll_once_with_data() {
1✔
243
        let tmp = TempDir::new().unwrap();
1✔
244
        let path = tmp.path().join("test.sock");
1✔
245

246
        // Create server socket first
247
        let server = bind(&path).unwrap();
1✔
248

249
        // Send data
250
        let client = UnixDatagram::unbound().unwrap();
1✔
251
        client.send_to(b"<6>poll_once test", &path).unwrap();
1✔
252

253
        // poll_socket on the server
254
        let result = poll_socket(&server).unwrap();
1✔
255
        assert_eq!(result, Some("poll_once test".to_string()));
1✔
256
    }
1✔
257

258
    #[test]
259
    fn test_poll_at_custom_path() {
1✔
260
        let tmp = TempDir::new().unwrap();
1✔
261
        let path = tmp.path().join("custom.sock");
1✔
262

263
        // poll_at with non-/dev/log path uses poll_once internally
264
        let result = poll_at(&path);
1✔
265
        assert!(result.is_ok());
1✔
266
    }
1✔
267

268
    #[test]
269
    fn test_poll_dev_log() {
1✔
270
        // poll() tries to bind /dev/log - may fail if already bound or no permission
271
        // Just exercise the code path, don't assert success
272
        let _ = poll();
1✔
273
    }
1✔
274
}
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