1use std::io::Read;
4
5use crate::error::BlktraceError;
6use crate::uapi::{
7 blk_io_trace, blktrace_cat_BLK_TC_DISCARD, blktrace_cat_BLK_TC_READ, blktrace_cat_BLK_TC_WRITE,
8 BLK_IO_TRACE_MAGIC, BLK_IO_TRACE_VERSION, BLK_TC_SHIFT,
9};
10
11pub struct BlkTraceEvent {
13 pub event: blk_io_trace,
15 pub data: Vec<u8>,
17}
18
19impl std::fmt::Debug for BlkTraceEvent {
20 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21 f.debug_struct("BlkTraceEvent")
22 .field("event", &self.event)
23 .field("data_len", &self.data.len())
24 .finish()
25 }
26}
27
28impl BlkTraceEvent {
29 pub fn is_read(&self) -> bool {
31 ((self.event.action >> BLK_TC_SHIFT) & blktrace_cat_BLK_TC_READ) == blktrace_cat_BLK_TC_READ
32 }
33
34 pub fn is_write(&self) -> bool {
36 ((self.event.action >> BLK_TC_SHIFT) & blktrace_cat_BLK_TC_WRITE)
37 == blktrace_cat_BLK_TC_WRITE
38 }
39
40 pub fn is_discard(&self) -> bool {
42 ((self.event.action >> BLK_TC_SHIFT) & blktrace_cat_BLK_TC_DISCARD)
43 == blktrace_cat_BLK_TC_DISCARD
44 }
45}
46
47pub(crate) fn read_event(reader: &mut impl Read) -> Result<BlkTraceEvent, BlktraceError> {
54 let mut blk_event = std::mem::MaybeUninit::<blk_io_trace>::uninit();
57 let ptr = unsafe {
58 &mut *blk_event
59 .as_mut_ptr()
60 .cast::<[u8; std::mem::size_of::<blk_io_trace>()]>()
61 };
62 reader.read_exact(ptr)?;
63
64 let blk_event = unsafe { blk_event.assume_init() };
65 if blk_event.magic != BLK_IO_TRACE_MAGIC + BLK_IO_TRACE_VERSION {
66 return Err(BlktraceError::MagicMismatch);
67 }
68
69 let mut data = vec![0; blk_event.pdu_len as usize];
70 reader.read_exact(&mut data)?;
71
72 Ok(BlkTraceEvent {
73 event: blk_event,
74 data,
75 })
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81
82 fn trace(action: u32, pdu_len: u16, magic: u32) -> blk_io_trace {
83 blk_io_trace {
84 magic,
85 action,
86 pdu_len,
87 ..Default::default()
88 }
89 }
90
91 fn valid_trace(action: u32, pdu_len: u16) -> blk_io_trace {
92 trace(action, pdu_len, BLK_IO_TRACE_MAGIC + BLK_IO_TRACE_VERSION)
93 }
94
95 fn to_bytes(trace: &blk_io_trace, pdu: &[u8]) -> Vec<u8> {
98 let header = unsafe {
99 std::slice::from_raw_parts(
100 trace as *const _ as *const u8,
101 std::mem::size_of::<blk_io_trace>(),
102 )
103 };
104 let mut bytes = header.to_vec();
105 bytes.extend_from_slice(pdu);
106 bytes
107 }
108
109 #[test]
110 fn read_event_parses_header_and_pdu() {
111 let read_action = blktrace_cat_BLK_TC_READ << BLK_TC_SHIFT;
112 let bytes = to_bytes(&valid_trace(read_action, 3), b"xyz");
113 let mut reader = &bytes[..];
114
115 let event = read_event(&mut reader).unwrap();
116 assert_eq!(event.data, b"xyz");
117 assert!(event.is_read());
118 assert!(!event.is_write());
119 assert!(!event.is_discard());
120 assert_eq!(reader.len(), 0);
122 }
123
124 #[test]
125 fn read_event_rejects_bad_magic() {
126 let bytes = to_bytes(&trace(0, 0, 0), b"");
127 let mut reader = &bytes[..];
128
129 assert!(matches!(
130 read_event(&mut reader),
131 Err(BlktraceError::MagicMismatch)
132 ));
133 }
134
135 #[test]
136 fn read_event_reports_eof_on_empty_input() {
137 let mut reader = &b""[..];
138
139 let err = read_event(&mut reader).unwrap_err();
140 let kind = match &err {
141 BlktraceError::Io(e) => e.kind(),
142 _ => panic!("expected an io error, got {err:?}"),
143 };
144 assert_eq!(kind, std::io::ErrorKind::UnexpectedEof);
145 }
146
147 #[test]
148 fn read_event_reports_eof_on_truncated_pdu() {
149 let bytes = to_bytes(&valid_trace(0, 4), b"xy");
150 let mut reader = &bytes[..];
151
152 let err = read_event(&mut reader).unwrap_err();
153 let kind = match &err {
154 BlktraceError::Io(e) => e.kind(),
155 _ => panic!("expected an io error, got {err:?}"),
156 };
157 assert_eq!(kind, std::io::ErrorKind::UnexpectedEof);
158 }
159
160 #[test]
161 fn event_classification_by_action_bits() {
162 let read = blk_io_trace {
163 action: blktrace_cat_BLK_TC_READ << BLK_TC_SHIFT,
164 ..Default::default()
165 };
166 let write = blk_io_trace {
167 action: blktrace_cat_BLK_TC_WRITE << BLK_TC_SHIFT,
168 ..Default::default()
169 };
170 let discard = blk_io_trace {
171 action: blktrace_cat_BLK_TC_DISCARD << BLK_TC_SHIFT,
172 ..Default::default()
173 };
174
175 assert!(BlkTraceEvent {
176 event: read,
177 data: vec![]
178 }
179 .is_read());
180 assert!(BlkTraceEvent {
181 event: write,
182 data: vec![]
183 }
184 .is_write());
185 assert!(BlkTraceEvent {
186 event: discard,
187 data: vec![]
188 }
189 .is_discard());
190 }
191}