Skip to main content

talos_api_rs/resources/
dmesg.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! Typed wrappers for the Dmesg API.
4//!
5//! Provides access to the kernel message buffer (dmesg) for diagnostics.
6
7use crate::api::generated::machine::DmesgRequest as ProtoDmesgRequest;
8
9/// Request for kernel message buffer (dmesg).
10#[derive(Debug, Clone, Default)]
11pub struct DmesgRequest {
12    /// If true, follow the log output (like `dmesg -w`).
13    pub follow: bool,
14    /// If true, only return the last messages.
15    pub tail: bool,
16}
17
18impl DmesgRequest {
19    /// Create a new dmesg request.
20    #[must_use]
21    pub fn new() -> Self {
22        Self::default()
23    }
24
25    /// Create a request to follow dmesg output.
26    #[must_use]
27    pub fn follow() -> Self {
28        Self {
29            follow: true,
30            tail: false,
31        }
32    }
33
34    /// Create a request for tail output only.
35    #[must_use]
36    pub fn tail() -> Self {
37        Self {
38            follow: false,
39            tail: true,
40        }
41    }
42
43    /// Create a builder.
44    #[must_use]
45    pub fn builder() -> DmesgRequestBuilder {
46        DmesgRequestBuilder::default()
47    }
48}
49
50impl From<DmesgRequest> for ProtoDmesgRequest {
51    fn from(req: DmesgRequest) -> Self {
52        Self {
53            follow: req.follow,
54            tail: req.tail,
55        }
56    }
57}
58
59/// Builder for DmesgRequest.
60#[derive(Debug, Clone, Default)]
61pub struct DmesgRequestBuilder {
62    follow: bool,
63    tail: bool,
64}
65
66impl DmesgRequestBuilder {
67    /// Set whether to follow output.
68    #[must_use]
69    pub fn follow(mut self, follow: bool) -> Self {
70        self.follow = follow;
71        self
72    }
73
74    /// Set whether to tail output.
75    #[must_use]
76    pub fn tail(mut self, tail: bool) -> Self {
77        self.tail = tail;
78        self
79    }
80
81    /// Build the request.
82    #[must_use]
83    pub fn build(self) -> DmesgRequest {
84        DmesgRequest {
85            follow: self.follow,
86            tail: self.tail,
87        }
88    }
89}
90
91/// Response containing kernel message buffer content.
92#[derive(Debug, Clone)]
93pub struct DmesgResponse {
94    /// Raw dmesg data.
95    data: Vec<u8>,
96    /// Node that returned this dmesg.
97    pub node: Option<String>,
98}
99
100impl DmesgResponse {
101    /// Create a new response from raw data.
102    #[must_use]
103    pub fn new(data: Vec<u8>, node: Option<String>) -> Self {
104        Self { data, node }
105    }
106
107    /// Get the raw bytes.
108    #[must_use]
109    pub fn as_bytes(&self) -> &[u8] {
110        &self.data
111    }
112
113    /// Try to convert to a UTF-8 string.
114    pub fn as_str(&self) -> Result<&str, std::str::Utf8Error> {
115        std::str::from_utf8(&self.data)
116    }
117
118    /// Convert to string, replacing invalid UTF-8 with replacement character.
119    #[must_use]
120    pub fn as_string_lossy(&self) -> std::borrow::Cow<'_, str> {
121        String::from_utf8_lossy(&self.data)
122    }
123
124    /// Get the length in bytes.
125    #[must_use]
126    pub fn len(&self) -> usize {
127        self.data.len()
128    }
129
130    /// Check if the response is empty.
131    #[must_use]
132    pub fn is_empty(&self) -> bool {
133        self.data.is_empty()
134    }
135
136    /// Get individual lines from dmesg output.
137    #[must_use]
138    pub fn lines(&self) -> Vec<&str> {
139        self.as_str()
140            .map(|s| s.lines().collect())
141            .unwrap_or_default()
142    }
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148
149    #[test]
150    fn test_dmesg_request_new() {
151        let req = DmesgRequest::new();
152        assert!(!req.follow);
153        assert!(!req.tail);
154    }
155
156    #[test]
157    fn test_dmesg_request_follow() {
158        let req = DmesgRequest::follow();
159        assert!(req.follow);
160        assert!(!req.tail);
161    }
162
163    #[test]
164    fn test_dmesg_request_tail() {
165        let req = DmesgRequest::tail();
166        assert!(!req.follow);
167        assert!(req.tail);
168    }
169
170    #[test]
171    fn test_dmesg_request_builder() {
172        let req = DmesgRequest::builder().follow(true).tail(true).build();
173        assert!(req.follow);
174        assert!(req.tail);
175    }
176
177    #[test]
178    fn test_proto_conversion() {
179        let req = DmesgRequest::builder().follow(true).tail(false).build();
180        let proto: ProtoDmesgRequest = req.into();
181        assert!(proto.follow);
182        assert!(!proto.tail);
183    }
184
185    #[test]
186    fn test_dmesg_response() {
187        let data = b"[    0.000000] Linux version 5.15.0\n[    0.000001] Command line: talos.platform=metal".to_vec();
188        let response = DmesgResponse::new(data.clone(), Some("node1".to_string()));
189
190        assert_eq!(response.len(), data.len());
191        assert!(!response.is_empty());
192        assert!(response.as_str().is_ok());
193        assert_eq!(response.lines().len(), 2);
194        assert!(response.lines()[0].contains("Linux version"));
195    }
196}