@@ -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}
0 commit comments