#![cfg(test)]
#![allow(unused_imports)]
use super::testing::*;
use super::*;
use std::io::{Seek, Write};
use std::os::unix::fs::FileExt;
use std::sync::atomic::Ordering;
use tempfile::tempfile;
use vm_memory::Bytes;
#[test]
fn handle_read_pulls_bytes_from_backing_file() {
let cap = 4096u64; let f = make_backed_file_with_pattern(cap, 0xAB);
let dev = VirtioBlk::new(f, cap, DiskThrottle::default());
let mem = make_guest_mem(8192);
let data_addr = GuestAddress(0x1000);
let status_addr = GuestAddress(0x1FFF); let segs = vec![ChainDescriptor {
addr: data_addr,
len: 512,
is_write_only: true,
}];
let (status, used) = dev.handle_read(&mem, 0, &segs, status_addr);
assert_eq!(status, VIRTIO_BLK_S_OK as u8);
assert_eq!(used, 513); let mut readback = [0u8; 512];
mem.read_slice(&mut readback, data_addr).unwrap();
assert!(readback.iter().all(|&b| b == 0xAB));
let mut s = [0u8; 1];
mem.read_slice(&mut s, status_addr).unwrap();
assert_eq!(s[0], VIRTIO_BLK_S_OK as u8);
}
#[test]
fn handle_write_persists_bytes_to_backing_file() {
let cap = 4096u64;
let f = make_backed_file_with_pattern(cap, 0x00);
let f_for_verify = f.try_clone().unwrap();
let dev = VirtioBlk::new(f, cap, DiskThrottle::default());
let mem = make_guest_mem(32768);
let data_addr = GuestAddress(0x1000);
let status_addr = GuestAddress(0x2000);
let pattern = vec![0xCDu8; 512];
mem.write_slice(&pattern, data_addr).unwrap();
let segs = vec![ChainDescriptor {
addr: data_addr,
len: 512,
is_write_only: false,
}];
let (status, used) = dev.handle_write(&mem, 1, &segs, status_addr); assert_eq!(status, VIRTIO_BLK_S_OK as u8);
assert_eq!(used, 1);
let mut readback = [0u8; 512];
f_for_verify.read_at(&mut readback, 512).unwrap();
assert!(readback.iter().all(|&b| b == 0xCD));
}
#[test]
fn handle_read_rejects_out_of_range_sector() {
let cap = 4096u64; let f = make_backed_file_with_pattern(cap, 0x00);
let dev = VirtioBlk::new(f, cap, DiskThrottle::default());
let mem = make_guest_mem(8192);
let data_addr = GuestAddress(0x1000);
let status_addr = GuestAddress(0x1FFF);
let segs = vec![ChainDescriptor {
addr: data_addr,
len: 512,
is_write_only: true,
}];
let (status, _) = dev.handle_read(&mem, 9, &segs, status_addr);
assert_eq!(status, VIRTIO_BLK_S_IOERR as u8);
let c = dev.counters();
assert_eq!(c.io_errors.load(Ordering::Relaxed), 1);
assert_eq!(c.reads_completed.load(Ordering::Relaxed), 0);
}
#[test]
fn handle_write_rejects_out_of_range_sector() {
let cap = 4096u64;
let f = make_backed_file_with_pattern(cap, 0x00);
let dev = VirtioBlk::new(f, cap, DiskThrottle::default());
let mem = make_guest_mem(16384);
let data_addr = GuestAddress(0x1000);
let status_addr = GuestAddress(0x2000);
let segs = vec![ChainDescriptor {
addr: data_addr,
len: 512,
is_write_only: false,
}];
let (status, _) = dev.handle_write(&mem, 9, &segs, status_addr);
assert_eq!(status, VIRTIO_BLK_S_IOERR as u8);
}
#[test]
fn handle_flush_succeeds_on_writable_backing() {
let cap = 4096u64;
let f = make_backed_file_with_pattern(cap, 0x00);
let dev = VirtioBlk::new(f, cap, DiskThrottle::default());
let mem = make_guest_mem(8192);
let status_addr = GuestAddress(0x100);
let (status, used) = dev.handle_flush(&mem, status_addr);
assert_eq!(status, VIRTIO_BLK_S_OK as u8);
assert_eq!(used, 1);
let c = dev.counters();
assert_eq!(c.flushes_completed.load(Ordering::Relaxed), 1);
}
#[test]
fn read_only_advertises_f_ro_feature_bit() {
let cap = 4096u64;
let f = make_backed_file_with_pattern(cap, 0x00);
let dev = VirtioBlk::with_options(f, cap, DiskThrottle::default(), true);
let feats = dev.device_features();
assert_ne!(feats & (1u64 << VIRTIO_BLK_F_RO), 0);
}
#[test]
fn read_write_does_not_advertise_f_ro_feature_bit() {
let cap = 4096u64;
let f = make_backed_file_with_pattern(cap, 0x00);
let dev = VirtioBlk::new(f, cap, DiskThrottle::default());
let feats = dev.device_features();
assert_eq!(feats & (1u64 << VIRTIO_BLK_F_RO), 0);
}
#[test]
fn write_at_full_capacity_succeeds() {
let cap = 4096u64;
let f = make_backed_file_with_pattern(cap, 0x00);
let dev = VirtioBlk::new(f, cap, DiskThrottle::default());
let mem = make_guest_mem(16384);
let data_addr = GuestAddress(0x2000);
let status_addr = GuestAddress(0x2FFF);
let pattern = vec![0xEEu8; 512];
mem.write_slice(&pattern, data_addr).unwrap();
let segs = vec![ChainDescriptor {
addr: data_addr,
len: 512,
is_write_only: false,
}];
let (status, _) = dev.handle_write(&mem, 7, &segs, status_addr);
assert_eq!(status, VIRTIO_BLK_S_OK as u8);
}
#[test]
fn read_short_pads_with_zeros() {
let cap = 4096u64; let mut f = tempfile().unwrap();
f.set_len(512).unwrap(); f.write_all(&[0xAA; 512]).unwrap();
f.rewind().unwrap();
let dev = VirtioBlk::new(f, cap, DiskThrottle::default());
let mem = make_guest_mem(8192);
let data_addr = GuestAddress(0x1000);
let status_addr = GuestAddress(0x1FFF);
let segs = vec![ChainDescriptor {
addr: data_addr,
len: 512,
is_write_only: true,
}];
let (status, _) = dev.handle_read(&mem, 4, &segs, status_addr);
assert_eq!(status, VIRTIO_BLK_S_OK as u8);
let mut readback = [0u8; 512];
mem.read_slice(&mut readback, data_addr).unwrap();
assert!(
readback.iter().all(|&b| b == 0),
"out-of-data reads must zero-pad, not return stale memory"
);
}
#[test]
fn read_only_flush_returns_ok() {
let cap = 4096u64;
let f = make_backed_file_with_pattern(cap, 0x00);
let dev = VirtioBlk::with_options(f, cap, DiskThrottle::default(), true);
assert!(dev.worker.read_only);
}
#[test]
fn read_only_write_returns_ioerr() {
let counters = VirtioBlkCounters::default();
let result = VirtioBlk::classify_pre_throttle(VIRTIO_BLK_T_OUT, true, &counters);
assert_eq!(result, Some((VIRTIO_BLK_S_IOERR as u8, 1)));
assert_eq!(counters.io_errors.load(Ordering::Relaxed), 1);
assert_eq!(counters.writes_completed.load(Ordering::Relaxed), 0);
}
#[test]
fn read_only_flush_returns_ok_and_increments_counter() {
let counters = VirtioBlkCounters::default();
let result = VirtioBlk::classify_pre_throttle(VIRTIO_BLK_T_FLUSH, true, &counters);
assert_eq!(result, Some((VIRTIO_BLK_S_OK as u8, 1)));
assert_eq!(counters.flushes_completed.load(Ordering::Relaxed), 1);
assert_eq!(counters.io_errors.load(Ordering::Relaxed), 0);
}
#[test]
fn scatter_gather_read_two_segments() {
let cap = 4096u64;
let mut f = tempfile().unwrap();
f.set_len(cap).unwrap();
f.rewind().unwrap();
let mut backing_data = vec![0x11u8; 512];
backing_data.extend(vec![0x22u8; 512]);
f.write_all(&backing_data).unwrap();
f.rewind().unwrap();
let dev = VirtioBlk::new(f, cap, DiskThrottle::default());
let mem = make_guest_mem(8192);
let seg0_addr = GuestAddress(0x1000);
let seg1_addr = GuestAddress(0x1400); let status_addr = GuestAddress(0x1FFF);
let segs = vec![
ChainDescriptor {
addr: seg0_addr,
len: 512,
is_write_only: true,
},
ChainDescriptor {
addr: seg1_addr,
len: 512,
is_write_only: true,
},
];
let (status, used) = dev.handle_read(&mem, 0, &segs, status_addr);
assert_eq!(status, VIRTIO_BLK_S_OK as u8);
assert_eq!(used, 1024 + 1);
let mut buf0 = [0u8; 512];
mem.read_slice(&mut buf0, seg0_addr).unwrap();
assert!(
buf0.iter().all(|&b| b == 0x11),
"segment 0 must receive file bytes 0..512 (0x11 pattern)",
);
let mut buf1 = [0u8; 512];
mem.read_slice(&mut buf1, seg1_addr).unwrap();
assert!(
buf1.iter().all(|&b| b == 0x22),
"segment 1 must receive file bytes 512..1024 (0x22 pattern); \
a regression that didn't advance the file cursor would \
produce 0x11 here",
);
}
#[test]
fn unknown_type_returns_unsupp() {
let counters = VirtioBlkCounters::default();
let result = VirtioBlk::classify_pre_throttle(0xBEEF, false, &counters);
assert_eq!(result, Some((VIRTIO_BLK_S_UNSUPP as u8, 1)));
assert_eq!(counters.io_errors.load(Ordering::Relaxed), 0);
assert_eq!(counters.reads_completed.load(Ordering::Relaxed), 0);
assert_eq!(counters.writes_completed.load(Ordering::Relaxed), 0);
assert_eq!(counters.flushes_completed.load(Ordering::Relaxed), 0);
}
#[test]
fn handle_read_multi_segment_scatter() {
let cap = 4096u64; let mut f = tempfile().unwrap();
f.set_len(cap).unwrap();
f.write_all(&[0xAA; 512]).unwrap();
f.write_all(&[0xBB; 512]).unwrap();
f.rewind().unwrap();
let dev = VirtioBlk::new(f, cap, DiskThrottle::default());
let mem = make_guest_mem(8192);
let seg0_addr = GuestAddress(0x1000);
let seg1_addr = GuestAddress(0x1400); let status_addr = GuestAddress(0x1FFF);
let segs = vec![
ChainDescriptor {
addr: seg0_addr,
len: 512,
is_write_only: true,
},
ChainDescriptor {
addr: seg1_addr,
len: 512,
is_write_only: true,
},
];
let (status, used) = dev.handle_read(&mem, 0, &segs, status_addr);
assert_eq!(status, VIRTIO_BLK_S_OK as u8);
assert_eq!(used, 1024 + 1);
let mut readback0 = [0u8; 512];
mem.read_slice(&mut readback0, seg0_addr).unwrap();
assert!(
readback0.iter().all(|&b| b == 0xAA),
"segment 0 must hold the FIRST sector's pattern (0xAA), \
got cross-contamination: {:?}..{:?}",
&readback0[..8],
&readback0[504..],
);
let mut readback1 = [0u8; 512];
mem.read_slice(&mut readback1, seg1_addr).unwrap();
assert!(
readback1.iter().all(|&b| b == 0xBB),
"segment 1 must hold the SECOND sector's pattern (0xBB) — \
cur_offset must have advanced by 512 between segments. \
got: {:?}..{:?}",
&readback1[..8],
&readback1[504..],
);
let c = dev.counters();
assert_eq!(c.reads_completed.load(Ordering::Relaxed), 1);
assert_eq!(c.bytes_read.load(Ordering::Relaxed), 1024);
}