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