Skip to content

Commit 8b4c529

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 763e21c commit 8b4c529

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

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)