use std::fs::OpenOptions;
use std::io::{Read, Seek, SeekFrom};
use std::time::{Duration, Instant};
use io_buffers::AlignedBuf;
use super::QUEUE;
use crate::dioring::{DeliveryMode, Dioring, Setup, TargetSetup};
use crate::scratch_volume::ScratchVolume;
use crate::windows_backend::{
OpenedVolume, Unbuffered, VolumeOptions, Win32FileTarget, disk_flush_domain,
};
use durable_ioring::backend::RefusalCause;
use durable_ioring::{
DurableRing, Entry, EntryOf, IoTarget, IoTargetKey, LineageHandle, Outcome, PushRefusal,
WarrantyClass, WriteCaching, WriteOptions,
};
const VOLUME: IoTargetKey = IoTargetKey(1);
const BOUND: Duration = Duration::from_secs(30);
const MEGABYTES: u32 = 64;
type RawRing = Dioring<AlignedBuf, u64, u32>;
fn raw_instance(volume: OpenedVolume<Unbuffered>) -> (RawRing, LineageHandle) {
RawRing::new(Setup {
submission_queue_size: QUEUE,
completion_queue_size: QUEUE,
files: vec![TargetSetup {
key: VOLUME,
target: Win32FileTarget::from_volume(volume),
}],
buffers: Vec::<Vec<u8>>::new(),
provider: None,
diagnostics: None,
delivery: DeliveryMode::Pool(None),
})
.expect("build an instance over the raw volume")
}
fn next_entry(ring: &mut RawRing) -> EntryOf<RawRing> {
let started = Instant::now();
loop {
if let Some(entry) = ring.pop().expect("pop") {
return entry;
}
assert!(started.elapsed() < BOUND, "nothing arrived");
std::thread::sleep(Duration::from_millis(1));
}
}
fn sector_of(fill: u8, sector: usize) -> AlignedBuf {
AlignedBuf::from_bytes(&vec![fill; sector], sector).expect("an aligned buffer")
}
fn transferred(entry: EntryOf<RawRing>) -> u32 {
match entry {
Entry::Op(completion) => match completion.outcome {
Outcome::Transferred(n) => n,
other => panic!("expected a transfer, got {other:?}"),
},
other => panic!("expected a completion, got {other:?}"),
}
}
fn read_back(device_path: &str, length: usize) -> Vec<u8> {
let mut volume = OpenOptions::new()
.read(true)
.open(device_path)
.expect("open the volume for reading");
volume.seek(SeekFrom::Start(0)).expect("seek");
let mut bytes = vec![0; length];
volume.read_exact(&mut bytes).expect("read the volume");
bytes
}
#[test]
#[ignore = "needs an administrator: creates and attaches a virtual disk"]
fn a_raw_volumes_disks_and_sector_are_found_and_its_target_reports_them() {
let scratch = ScratchVolume::create(MEGABYTES).expect("a scratch volume");
let volume = OpenedVolume::<Unbuffered>::open(&scratch.path(), VolumeOptions::new())
.expect("open the raw volume");
assert!(!volume.is_locked());
let extents = volume.disk_extents();
assert_eq!(
extents.len(),
1,
"a volume on one virtual disk: {extents:?}"
);
assert!(extents[0].length > 0);
let disk = extents[0].disk_number;
let sector = volume.geometry().offset_alignment();
assert!(sector.is_power_of_two() && sector >= 512, "{sector}");
let target = Win32FileTarget::from_volume(volume);
assert_eq!(target.warranty(), WarrantyClass::SharedDevice);
assert_eq!(target.flush_domains(), [disk_flush_domain(disk)]);
assert!(target.capabilities().write_through());
}
#[test]
#[ignore = "needs an administrator: creates and attaches a virtual disk"]
fn a_sector_written_to_a_raw_volume_is_made_durable_and_is_on_the_volume() {
let scratch = ScratchVolume::create(MEGABYTES).expect("a scratch volume");
let volume = OpenedVolume::<Unbuffered>::open(&scratch.path(), VolumeOptions::new())
.expect("open the raw volume");
let sector = volume.geometry().offset_alignment() as usize;
let (mut ring, lineage) = raw_instance(volume);
let region = 3 * sector as u64;
ring.write_with(
VOLUME,
region,
sector_of(0xAB, sector),
lineage.at(1),
1,
WriteOptions::new(),
)
.expect("push an aligned write");
assert_eq!(transferred(next_entry(&mut ring)) as usize, sector);
for offset in [1, sector as u64 / 2] {
let refused = ring
.write(
VOLUME,
region + offset,
sector_of(1, sector),
lineage.at(1),
2,
)
.expect_err("off the sector");
assert!(matches!(
refused.reason,
PushRefusal::Backend(RefusalCause::Invalid)
));
}
ring.make_durable_through(lineage.at(1))
.expect("seal the epoch");
match next_entry(&mut ring) {
Entry::Durable { through } => assert_eq!(through.id, 1),
other => panic!("expected Durable, got {other:?}"),
}
let on_volume = read_back(&scratch.device_path(), region as usize + sector);
assert!(on_volume[..region as usize].iter().all(|&b| b == 0));
assert!(on_volume[region as usize..].iter().all(|&b| b == 0xAB));
}
#[test]
#[ignore = "needs an administrator: creates and attaches a virtual disk"]
fn a_write_through_write_completes_on_a_raw_volume() {
let scratch = ScratchVolume::create(MEGABYTES).expect("a scratch volume");
let volume = OpenedVolume::<Unbuffered>::open(&scratch.path(), VolumeOptions::new())
.expect("open the raw volume");
let sector = volume.geometry().offset_alignment() as usize;
let (mut ring, lineage) = raw_instance(volume);
ring.write_with(
VOLUME,
0,
sector_of(0x5A, sector),
lineage.at(1),
7,
WriteOptions::new().caching(WriteCaching::WriteThrough),
)
.expect("push a write-through write");
assert_eq!(transferred(next_entry(&mut ring)) as usize, sector);
assert!(
read_back(&scratch.device_path(), sector)
.iter()
.all(|&b| b == 0x5A)
);
}
#[test]
#[ignore = "needs an administrator: creates and attaches a virtual disk"]
fn a_locked_raw_volume_is_written_and_the_lock_is_given_up_when_it_closes() {
let scratch = ScratchVolume::create(MEGABYTES).expect("a scratch volume");
let path = scratch.path();
let locked = OpenedVolume::<Unbuffered>::open(&path, VolumeOptions::new().lock(true))
.expect("open and lock");
assert!(locked.is_locked());
let sector = locked.geometry().offset_alignment() as usize;
let (mut ring, lineage) = raw_instance(locked);
ring.write_with(
VOLUME,
0,
sector_of(0x11, sector),
lineage.at(1),
1,
WriteOptions::new(),
)
.expect("push a write to the locked volume");
assert_eq!(transferred(next_entry(&mut ring)) as usize, sector);
drop(ring);
OpenedVolume::<Unbuffered>::open(&path, VolumeOptions::new().lock(true))
.expect("the lock was released when the handle closed");
}
#[test]
#[ignore = "needs an administrator: creates and attaches a virtual disk"]
fn a_volume_with_another_handle_open_cannot_be_locked() {
let scratch = ScratchVolume::create(MEGABYTES).expect("a scratch volume");
let path = scratch.path();
let held = OpenedVolume::<Unbuffered>::open(&path, VolumeOptions::new()).expect("open");
let refused = OpenedVolume::<Unbuffered>::open(&path, VolumeOptions::new().lock(true))
.expect_err("another handle is open");
assert!(
refused.raw_os_error().is_some(),
"the lock's own error: {refused}"
);
drop(held);
OpenedVolume::<Unbuffered>::open(&path, VolumeOptions::new().lock(true))
.expect("nothing else is open now");
}