use std::io::Read;
use crate::error::BlktraceError;
use crate::uapi::{
blk_io_trace, blktrace_cat_BLK_TC_DISCARD, blktrace_cat_BLK_TC_READ, blktrace_cat_BLK_TC_WRITE,
BLK_IO_TRACE_MAGIC, BLK_IO_TRACE_VERSION, BLK_TC_SHIFT,
};
pub struct BlkTraceEvent {
pub event: blk_io_trace,
pub data: Vec<u8>,
}
impl std::fmt::Debug for BlkTraceEvent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BlkTraceEvent")
.field("event", &self.event)
.field("data_len", &self.data.len())
.finish()
}
}
impl BlkTraceEvent {
pub fn is_read(&self) -> bool {
((self.event.action >> BLK_TC_SHIFT) & blktrace_cat_BLK_TC_READ) == blktrace_cat_BLK_TC_READ
}
pub fn is_write(&self) -> bool {
((self.event.action >> BLK_TC_SHIFT) & blktrace_cat_BLK_TC_WRITE)
== blktrace_cat_BLK_TC_WRITE
}
pub fn is_discard(&self) -> bool {
((self.event.action >> BLK_TC_SHIFT) & blktrace_cat_BLK_TC_DISCARD)
== blktrace_cat_BLK_TC_DISCARD
}
}
pub(crate) fn read_event(reader: &mut impl Read) -> Result<BlkTraceEvent, BlktraceError> {
let mut blk_event = std::mem::MaybeUninit::<blk_io_trace>::uninit();
let ptr = unsafe {
&mut *blk_event
.as_mut_ptr()
.cast::<[u8; std::mem::size_of::<blk_io_trace>()]>()
};
reader.read_exact(ptr)?;
let blk_event = unsafe { blk_event.assume_init() };
if blk_event.magic != BLK_IO_TRACE_MAGIC + BLK_IO_TRACE_VERSION {
return Err(BlktraceError::MagicMismatch);
}
let mut data = vec![0; blk_event.pdu_len as usize];
reader.read_exact(&mut data)?;
Ok(BlkTraceEvent {
event: blk_event,
data,
})
}
#[cfg(test)]
mod tests {
use super::*;
fn trace(action: u32, pdu_len: u16, magic: u32) -> blk_io_trace {
blk_io_trace {
magic,
action,
pdu_len,
..Default::default()
}
}
fn valid_trace(action: u32, pdu_len: u16) -> blk_io_trace {
trace(action, pdu_len, BLK_IO_TRACE_MAGIC + BLK_IO_TRACE_VERSION)
}
fn to_bytes(trace: &blk_io_trace, pdu: &[u8]) -> Vec<u8> {
let header = unsafe {
std::slice::from_raw_parts(
trace as *const _ as *const u8,
std::mem::size_of::<blk_io_trace>(),
)
};
let mut bytes = header.to_vec();
bytes.extend_from_slice(pdu);
bytes
}
#[test]
fn read_event_parses_header_and_pdu() {
let read_action = blktrace_cat_BLK_TC_READ << BLK_TC_SHIFT;
let bytes = to_bytes(&valid_trace(read_action, 3), b"xyz");
let mut reader = &bytes[..];
let event = read_event(&mut reader).unwrap();
assert_eq!(event.data, b"xyz");
assert!(event.is_read());
assert!(!event.is_write());
assert!(!event.is_discard());
assert_eq!(reader.len(), 0);
}
#[test]
fn read_event_rejects_bad_magic() {
let bytes = to_bytes(&trace(0, 0, 0), b"");
let mut reader = &bytes[..];
assert!(matches!(
read_event(&mut reader),
Err(BlktraceError::MagicMismatch)
));
}
#[test]
fn read_event_reports_eof_on_empty_input() {
let mut reader = &b""[..];
let err = read_event(&mut reader).unwrap_err();
let kind = match &err {
BlktraceError::Io(e) => e.kind(),
_ => panic!("expected an io error, got {err:?}"),
};
assert_eq!(kind, std::io::ErrorKind::UnexpectedEof);
}
#[test]
fn read_event_reports_eof_on_truncated_pdu() {
let bytes = to_bytes(&valid_trace(0, 4), b"xy");
let mut reader = &bytes[..];
let err = read_event(&mut reader).unwrap_err();
let kind = match &err {
BlktraceError::Io(e) => e.kind(),
_ => panic!("expected an io error, got {err:?}"),
};
assert_eq!(kind, std::io::ErrorKind::UnexpectedEof);
}
#[test]
fn event_classification_by_action_bits() {
let read = blk_io_trace {
action: blktrace_cat_BLK_TC_READ << BLK_TC_SHIFT,
..Default::default()
};
let write = blk_io_trace {
action: blktrace_cat_BLK_TC_WRITE << BLK_TC_SHIFT,
..Default::default()
};
let discard = blk_io_trace {
action: blktrace_cat_BLK_TC_DISCARD << BLK_TC_SHIFT,
..Default::default()
};
assert!(BlkTraceEvent {
event: read,
data: vec![]
}
.is_read());
assert!(BlkTraceEvent {
event: write,
data: vec![]
}
.is_write());
assert!(BlkTraceEvent {
event: discard,
data: vec![]
}
.is_discard());
}
}