Skip to content

Commit 2c033e4

Browse files
committed
feat(block): handle VIRTIO_BLK_T_WRITE_ZEROES requests
Wire up Request::parse() validation and Request::process() handling for write-zeroes requests, mirroring the discard path: - parse(): reject write-only data descriptors and require data_len to be a non-zero multiple of DISCARD_SEGMENT_SIZE (the DiscardWriteZeroes struct is shared between both ops). - parse_write_zeroes_segment(): validate flags (only bit 0, VIRTIO_BLK_WRITE_ZEROES_FLAG_UNMAP, is permitted), reject num_sectors=0, check sector range, and return the unmap flag so process() can pick the fallocate mode. - process(): short-circuit to VIRTIO_BLK_S_UNSUPP if a previous EOPNOTSUPP set disk.write_zeroes_unsupported; otherwise dispatch to FileEngine::write_zeroes(). - the (Ok(_), RequestType::WriteZeroes) finish() arm now increments the write_zeroes_count metric and returns VIRTIO_BLK_S_OK. - the EOPNOTSUPP detection in the error path also handles RequestType::WriteZeroes, setting the cache and returning Status::WriteZeroesUnsupported (silent UNSUPP). Add the write_zeroes_count metric and aggregate() line. The feature flag is not yet advertised — that lands in the next commit, after the request handling is fully in place. Signed-off-by: Nikita Kalyazin <nikita.kalyazin@e2b.dev>
1 parent cea19fe commit 2c033e4

3 files changed

Lines changed: 100 additions & 9 deletions

File tree

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,29 @@ impl VirtioBlock {
596596
});
597597
continue;
598598
}
599+
if is_eopnotsupp && pending.request_type() == RequestType::WriteZeroes {
600+
if !self.disk.write_zeroes_unsupported {
601+
warn!(
602+
"Block write_zeroes not supported by host filesystem; disabling \
603+
write_zeroes"
604+
);
605+
self.disk.write_zeroes_unsupported = true;
606+
}
607+
let finished = pending.finish(
608+
&active_state.mem,
609+
Err(IoErr::WriteZeroesUnsupported),
610+
&self.metrics,
611+
);
612+
queue
613+
.add_used(finished.desc_idx, finished.num_bytes_to_mem)
614+
.unwrap_or_else(|err| {
615+
error!(
616+
"Failed to add available descriptor head {}: {}",
617+
finished.desc_idx, err
618+
)
619+
});
620+
continue;
621+
}
599622

600623
let res = match cqe_result {
601624
Ok(count) => Ok(count),

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,8 @@ pub struct BlockDeviceMetrics {
186186
pub remaining_reqs_count: SharedIncMetric,
187187
/// Number of successful discard (TRIM) operations.
188188
pub discard_count: SharedIncMetric,
189+
/// Number of successful write-zeroes operations.
190+
pub write_zeroes_count: SharedIncMetric,
189191
}
190192

191193
impl BlockDeviceMetrics {
@@ -233,6 +235,8 @@ impl BlockDeviceMetrics {
233235
self.remaining_reqs_count
234236
.add(other.remaining_reqs_count.fetch_diff());
235237
self.discard_count.add(other.discard_count.fetch_diff());
238+
self.write_zeroes_count
239+
.add(other.write_zeroes_count.fetch_diff());
236240
}
237241
}
238242

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

Lines changed: 73 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use crate::devices::virtio::block::virtio::metrics::BlockDeviceMetrics;
1515
pub use crate::devices::virtio::generated::virtio_blk::{
1616
VIRTIO_BLK_ID_BYTES, VIRTIO_BLK_S_IOERR, VIRTIO_BLK_S_OK, VIRTIO_BLK_S_UNSUPP,
1717
VIRTIO_BLK_T_DISCARD, VIRTIO_BLK_T_FLUSH, VIRTIO_BLK_T_GET_ID, VIRTIO_BLK_T_IN,
18-
VIRTIO_BLK_T_OUT, VIRTIO_BLK_T_WRITE_ZEROES,
18+
VIRTIO_BLK_T_OUT, VIRTIO_BLK_T_WRITE_ZEROES, VIRTIO_BLK_WRITE_ZEROES_FLAG_UNMAP,
1919
};
2020
use crate::devices::virtio::queue::DescriptorChain;
2121
use crate::logger::{IncMetric, error, warn};
@@ -228,8 +228,10 @@ impl PendingRequest {
228228
}
229229
}
230230
(Ok(_), RequestType::WriteZeroes) => {
231-
// Placeholder: replaced when feature is wired up.
232-
Status::WriteZeroesUnsupported
231+
block_metrics.write_zeroes_count.inc();
232+
Status::Ok {
233+
num_bytes_to_mem: 0,
234+
}
233235
}
234236
(Err(IoErr::DiscardUnsupported), _) => Status::DiscardUnsupported,
235237
(Err(IoErr::WriteZeroesUnsupported), _) => Status::WriteZeroesUnsupported,
@@ -267,6 +269,32 @@ fn parse_discard_segment(
267269
))
268270
}
269271

272+
fn parse_write_zeroes_segment(
273+
data_addr: GuestAddress,
274+
nsectors: u64,
275+
mem: &GuestMemoryMmap,
276+
) -> Result<(u64, u64, bool), IoErr> {
277+
// max_write_zeroes_seg = 1 guarantees exactly one segment per request.
278+
let seg: DiscardWriteZeroes = mem.read_obj(data_addr).map_err(IoErr::GetId)?;
279+
// Only bit 0 (UNMAP) is defined; all other bits MUST be 0 per virtio spec
280+
// §5.2.6.14.
281+
if seg.flags & !VIRTIO_BLK_WRITE_ZEROES_FLAG_UNMAP != 0 {
282+
return Err(IoErr::InvalidFlags);
283+
}
284+
if seg.num_sectors == 0 {
285+
return Err(IoErr::InvalidOffset);
286+
}
287+
seg.sector
288+
.checked_add(u64::from(seg.num_sectors))
289+
.filter(|&top| top <= nsectors)
290+
.ok_or(IoErr::InvalidOffset)?;
291+
Ok((
292+
seg.sector << SECTOR_SHIFT,
293+
u64::from(seg.num_sectors) << SECTOR_SHIFT,
294+
seg.flags & VIRTIO_BLK_WRITE_ZEROES_FLAG_UNMAP != 0,
295+
))
296+
}
297+
270298
/// The request header represents the mandatory fields of each block device request.
271299
///
272300
/// A request header contains the following fields:
@@ -364,6 +392,9 @@ impl Request {
364392
if data_desc.is_write_only() && req.r#type == RequestType::Discard {
365393
return Err(VirtioBlockError::UnexpectedWriteOnlyDescriptor);
366394
}
395+
if data_desc.is_write_only() && req.r#type == RequestType::WriteZeroes {
396+
return Err(VirtioBlockError::UnexpectedWriteOnlyDescriptor);
397+
}
367398
if !data_desc.is_write_only() && req.r#type == RequestType::In {
368399
return Err(VirtioBlockError::UnexpectedReadOnlyDescriptor);
369400
}
@@ -401,6 +432,11 @@ impl Request {
401432
return Err(VirtioBlockError::InvalidDataLength);
402433
}
403434
}
435+
RequestType::WriteZeroes => {
436+
if req.data_len == 0 || req.data_len % DISCARD_SEGMENT_SIZE != 0 {
437+
return Err(VirtioBlockError::InvalidDataLength);
438+
}
439+
}
404440
_ => {}
405441
}
406442

@@ -498,12 +534,25 @@ impl Request {
498534
}
499535
}
500536
RequestType::WriteZeroes => {
501-
// Placeholder: replaced when feature is wired up in a later commit.
502-
return ProcessingResult::Executed(pending.finish(
503-
mem,
504-
Err(IoErr::WriteZeroesUnsupported),
505-
block_metrics,
506-
));
537+
if disk.write_zeroes_unsupported {
538+
return ProcessingResult::Executed(pending.finish(
539+
mem,
540+
Err(IoErr::WriteZeroesUnsupported),
541+
block_metrics,
542+
));
543+
}
544+
match parse_write_zeroes_segment(self.data_addr, disk.nsectors, mem) {
545+
Err(io_err) => {
546+
return ProcessingResult::Executed(pending.finish(
547+
mem,
548+
Err(io_err),
549+
block_metrics,
550+
));
551+
}
552+
Ok((offset, len, unmap)) => {
553+
disk.file_engine.write_zeroes(offset, len, unmap, pending)
554+
}
555+
}
507556
}
508557
RequestType::Unsupported(_) => {
509558
return ProcessingResult::Executed(pending.finish(mem, Ok(0), block_metrics));
@@ -530,6 +579,21 @@ impl Request {
530579
Err(IoErr::DiscardUnsupported),
531580
block_metrics,
532581
))
582+
} else if err.error.is_eopnotsupp()
583+
&& err.req.request_type() == RequestType::WriteZeroes
584+
{
585+
if !disk.write_zeroes_unsupported {
586+
warn!(
587+
"Block write_zeroes not supported by host filesystem; disabling \
588+
write_zeroes"
589+
);
590+
disk.write_zeroes_unsupported = true;
591+
}
592+
ProcessingResult::Executed(err.req.finish(
593+
mem,
594+
Err(IoErr::WriteZeroesUnsupported),
595+
block_metrics,
596+
))
533597
} else {
534598
ProcessingResult::Executed(err.req.finish(
535599
mem,

0 commit comments

Comments
 (0)