Skip to content

Commit 6c9e467

Browse files
committed
test(block): add unit tests for VIRTIO_BLK_F_WRITE_ZEROES
Add three tests parametrised over Sync and Async file engines: - test_write_zeroes_feature_and_config: VIRTIO_BLK_F_WRITE_ZEROES advertised on writable devices; max_write_zeroes_sectors, max_write_zeroes_seg, and write_zeroes_may_unmap populated as expected; read-only devices advertise none of these. - test_write_zeroes: end-to-end happy path (UNMAP=1 → PUNCH_HOLE, metric incremented, VIRTIO_BLK_S_OK) plus four error cases (reserved flag bit set, sector range out of bounds, num_sectors=0, invalid data length). - test_write_zeroes_unsupported_cached: pre-set the cache flag and verify subsequent requests return VIRTIO_BLK_S_UNSUPP without incrementing the success or invalid-request counters. The UNMAP=0 (ZERO_RANGE) happy path is not asserted in the unit tests because ZERO_RANGE support is filesystem-dependent (the host's tmpfs in some kernels returns EOPNOTSUPP); end-to-end UNMAP=0 correctness is left to the integration tests running on ext4. The unit tests cover wire-flag handling for UNMAP=0 via the invalid-flags and zero-sectors cases. Also extend test_request_type_from to assert the new VIRTIO_BLK_T_WRITE_ZEROES → RequestType::WriteZeroes mapping. Signed-off-by: Nikita Kalyazin <nikita.kalyazin@e2b.dev>
1 parent 7085b38 commit 6c9e467

2 files changed

Lines changed: 237 additions & 0 deletions

File tree

src/vmm/src/devices/virtio/block/virtio/device.rs

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2194,4 +2194,237 @@ mod tests {
21942194
);
21952195
}
21962196
}
2197+
2198+
#[test]
2199+
fn test_write_zeroes_feature_and_config() {
2200+
// Non-read-only block: VIRTIO_BLK_F_WRITE_ZEROES set, write-zeroes config
2201+
// fields populated.
2202+
for engine in [FileEngineType::Sync, FileEngineType::Async] {
2203+
let block = default_block(engine);
2204+
assert_ne!(
2205+
block.avail_features & (1u64 << VIRTIO_BLK_F_WRITE_ZEROES),
2206+
0
2207+
);
2208+
assert_eq!(block.avail_features & (1u64 << VIRTIO_BLK_F_RO), 0);
2209+
// default_block has 0x1000-byte backing file → 8 sectors.
2210+
assert_eq!(block.config_space.max_write_zeroes_sectors, 8);
2211+
assert_eq!(block.config_space.max_write_zeroes_seg, 1);
2212+
assert_eq!(block.config_space.write_zeroes_may_unmap, 1);
2213+
}
2214+
2215+
// Read-only block: VIRTIO_BLK_F_RO set, no write-zeroes.
2216+
let f = TempFile::new().unwrap();
2217+
f.as_file().set_len(0x1000).unwrap();
2218+
let config = VirtioBlockConfig {
2219+
drive_id: "test_ro_wz".to_string(),
2220+
path_on_host: f.as_path().to_str().unwrap().to_string(),
2221+
is_root_device: false,
2222+
partuuid: None,
2223+
is_read_only: true,
2224+
cache_type: CacheType::Unsafe,
2225+
rate_limiter: None,
2226+
file_engine_type: FileEngineType::default(),
2227+
};
2228+
let ro_block = VirtioBlock::new(config).unwrap();
2229+
assert_eq!(
2230+
ro_block.avail_features & (1u64 << VIRTIO_BLK_F_WRITE_ZEROES),
2231+
0
2232+
);
2233+
assert_ne!(ro_block.avail_features & (1u64 << VIRTIO_BLK_F_RO), 0);
2234+
assert_eq!(ro_block.config_space.max_write_zeroes_sectors, 0);
2235+
assert_eq!(ro_block.config_space.max_write_zeroes_seg, 0);
2236+
assert_eq!(ro_block.config_space.write_zeroes_may_unmap, 0);
2237+
}
2238+
2239+
#[test]
2240+
fn test_write_zeroes() {
2241+
for engine in [FileEngineType::Sync, FileEngineType::Async] {
2242+
let mut block = default_block(engine);
2243+
let mem = default_mem();
2244+
let interrupt = default_interrupt();
2245+
let vq = VirtQueue::new(GuestAddress(0), &mem, 16);
2246+
set_queue(&mut block, 0, vq.create_queue());
2247+
block.activate(mem.clone(), interrupt).unwrap();
2248+
read_blk_req_descriptors(&vq);
2249+
2250+
let request_type_addr = GuestAddress(vq.dtable[0].addr.get());
2251+
let data_addr = GuestAddress(vq.dtable[1].addr.get());
2252+
let status_addr = GuestAddress(vq.dtable[2].addr.get());
2253+
2254+
// Write-zeroes data descriptor is read-only (guest sends the segment list).
2255+
vq.dtable[1].flags.set(VIRTQ_DESC_F_NEXT);
2256+
vq.dtable[1].len.set(DISCARD_SEGMENT_SIZE);
2257+
2258+
// Valid write-zeroes with UNMAP=1 (PUNCH_HOLE — supported on tmpfs).
2259+
// The UNMAP=0 (ZERO_RANGE) happy path is not asserted here because
2260+
// ZERO_RANGE support is filesystem-dependent (e.g. older tmpfs returns
2261+
// EOPNOTSUPP); the wire-flag handling for UNMAP=0 is covered by the
2262+
// invalid-flag and zero-sectors cases below, and end-to-end correctness
2263+
// is left to the integration tests on ext4.
2264+
{
2265+
mem.write_obj::<u32>(VIRTIO_BLK_T_WRITE_ZEROES, request_type_addr)
2266+
.unwrap();
2267+
mem.write_obj(
2268+
DiscardWriteZeroes {
2269+
sector: 0,
2270+
num_sectors: 4,
2271+
flags: VIRTIO_BLK_WRITE_ZEROES_FLAG_UNMAP,
2272+
},
2273+
data_addr,
2274+
)
2275+
.unwrap();
2276+
2277+
check_metric_after_block!(
2278+
&block.metrics.write_zeroes_count,
2279+
1,
2280+
simulate_queue_and_async_completion_events(&mut block, true)
2281+
);
2282+
2283+
assert_eq!(vq.used.idx.get(), 1);
2284+
assert_eq!(vq.used.ring[0].get().id, 0);
2285+
assert_eq!(vq.used.ring[0].get().len, 1); // status byte only
2286+
assert_eq!(mem.read_obj::<u32>(status_addr).unwrap(), VIRTIO_BLK_S_OK);
2287+
}
2288+
2289+
// Invalid: reserved flag bit set (any bit other than UNMAP).
2290+
{
2291+
vq.used.idx.set(0);
2292+
set_queue(&mut block, 0, vq.create_queue());
2293+
mem.write_obj::<u32>(VIRTIO_BLK_T_WRITE_ZEROES, request_type_addr)
2294+
.unwrap();
2295+
mem.write_obj(
2296+
DiscardWriteZeroes {
2297+
sector: 0,
2298+
num_sectors: 4,
2299+
flags: 0x2, // reserved bit
2300+
},
2301+
data_addr,
2302+
)
2303+
.unwrap();
2304+
2305+
simulate_queue_event(&mut block, Some(true));
2306+
2307+
assert_eq!(vq.used.idx.get(), 1);
2308+
assert_eq!(
2309+
u32::from(mem.read_obj::<u8>(status_addr).unwrap()),
2310+
VIRTIO_BLK_S_IOERR
2311+
);
2312+
}
2313+
2314+
// Invalid: sector range exceeds disk bounds.
2315+
{
2316+
vq.used.idx.set(0);
2317+
set_queue(&mut block, 0, vq.create_queue());
2318+
mem.write_obj::<u32>(VIRTIO_BLK_T_WRITE_ZEROES, request_type_addr)
2319+
.unwrap();
2320+
// 8-sector disk; sector 7 + 4 = 11 > 8.
2321+
mem.write_obj(
2322+
DiscardWriteZeroes {
2323+
sector: 7,
2324+
num_sectors: 4,
2325+
flags: 0,
2326+
},
2327+
data_addr,
2328+
)
2329+
.unwrap();
2330+
2331+
simulate_queue_event(&mut block, Some(true));
2332+
2333+
assert_eq!(vq.used.idx.get(), 1);
2334+
assert_eq!(
2335+
u32::from(mem.read_obj::<u8>(status_addr).unwrap()),
2336+
VIRTIO_BLK_S_IOERR
2337+
);
2338+
}
2339+
2340+
// Invalid: num_sectors=0.
2341+
{
2342+
vq.used.idx.set(0);
2343+
set_queue(&mut block, 0, vq.create_queue());
2344+
mem.write_obj::<u32>(VIRTIO_BLK_T_WRITE_ZEROES, request_type_addr)
2345+
.unwrap();
2346+
mem.write_obj(
2347+
DiscardWriteZeroes {
2348+
sector: 0,
2349+
num_sectors: 0,
2350+
flags: 0,
2351+
},
2352+
data_addr,
2353+
)
2354+
.unwrap();
2355+
2356+
simulate_queue_event(&mut block, Some(true));
2357+
2358+
assert_eq!(vq.used.idx.get(), 1);
2359+
assert_eq!(
2360+
u32::from(mem.read_obj::<u8>(status_addr).unwrap()),
2361+
VIRTIO_BLK_S_IOERR
2362+
);
2363+
}
2364+
2365+
// Invalid data length: not a multiple of DISCARD_SEGMENT_SIZE → parse error.
2366+
{
2367+
vq.used.idx.set(0);
2368+
set_queue(&mut block, 0, vq.create_queue());
2369+
vq.dtable[1].len.set(DISCARD_SEGMENT_SIZE - 1);
2370+
mem.write_obj::<u32>(VIRTIO_BLK_T_WRITE_ZEROES, request_type_addr)
2371+
.unwrap();
2372+
2373+
simulate_queue_event(&mut block, Some(true));
2374+
2375+
// Parse error → descriptor chain discarded (used len = 0).
2376+
assert_eq!(vq.used.idx.get(), 1);
2377+
assert_eq!(vq.used.ring[0].get().len, 0);
2378+
}
2379+
}
2380+
}
2381+
2382+
#[test]
2383+
fn test_write_zeroes_unsupported_cached() {
2384+
for engine in [FileEngineType::Sync, FileEngineType::Async] {
2385+
let mut block = default_block(engine);
2386+
let mem = default_mem();
2387+
let interrupt = default_interrupt();
2388+
let vq = VirtQueue::new(GuestAddress(0), &mem, 16);
2389+
set_queue(&mut block, 0, vq.create_queue());
2390+
block.activate(mem.clone(), interrupt).unwrap();
2391+
read_blk_req_descriptors(&vq);
2392+
2393+
let request_type_addr = GuestAddress(vq.dtable[0].addr.get());
2394+
let data_addr = GuestAddress(vq.dtable[1].addr.get());
2395+
let status_addr = GuestAddress(vq.dtable[2].addr.get());
2396+
2397+
vq.dtable[1].flags.set(VIRTQ_DESC_F_NEXT);
2398+
vq.dtable[1].len.set(DISCARD_SEGMENT_SIZE);
2399+
2400+
mem.write_obj::<u32>(VIRTIO_BLK_T_WRITE_ZEROES, request_type_addr)
2401+
.unwrap();
2402+
mem.write_obj(
2403+
DiscardWriteZeroes {
2404+
sector: 0,
2405+
num_sectors: 4,
2406+
flags: 0,
2407+
},
2408+
data_addr,
2409+
)
2410+
.unwrap();
2411+
2412+
// Pre-set the flag as if a previous EOPNOTSUPP was already detected.
2413+
block.disk.write_zeroes_unsupported = true;
2414+
2415+
// Neither write_zeroes_count nor invalid_reqs_count should increment.
2416+
let wz_before = block.metrics.write_zeroes_count.count();
2417+
let invalid_before = block.metrics.invalid_reqs_count.count();
2418+
simulate_queue_and_async_completion_events(&mut block, true);
2419+
assert_eq!(block.metrics.write_zeroes_count.count(), wz_before);
2420+
assert_eq!(block.metrics.invalid_reqs_count.count(), invalid_before);
2421+
2422+
assert_eq!(vq.used.idx.get(), 1);
2423+
assert_eq!(vq.used.ring[0].get().len, 1); // status byte only
2424+
assert_eq!(
2425+
u32::from(mem.read_obj::<u8>(status_addr).unwrap()),
2426+
VIRTIO_BLK_S_UNSUPP
2427+
);
2428+
}
2429+
}
21972430
}

src/vmm/src/devices/virtio/block/virtio/request.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,10 @@ mod tests {
666666
RequestType::from(VIRTIO_BLK_T_GET_ID),
667667
RequestType::GetDeviceID
668668
);
669+
assert_eq!(
670+
RequestType::from(VIRTIO_BLK_T_WRITE_ZEROES),
671+
RequestType::WriteZeroes
672+
);
669673
assert_eq!(RequestType::from(42), RequestType::Unsupported(42));
670674
}
671675

0 commit comments

Comments
 (0)