Skip to content

Commit 98235b7

Browse files
committed
refractor: flesh out semantics of IoVecBuffer[Mut]::read/write_at
Before this patch series, IoVecBuffer[Mut] only ever had to deal with u8 slices, which inherently have an "all or nothing" semanticfor Read and Write. Thus the semantics of read_at and write_at were "return None if the given offset is too large this for IoVecBuffer[Mut], otherwise copy either buf.len() or iovec.len() - offset bytes, whichever is less". This commit changes the second part of this behavior to "copy either buf.len() bytes, or fail", which is how these functions were used in praxis. It also brings the semantics more in line with read and write functions offered elsewhere in the standard library. For now, it still only operates on u8-slices, but should the future need arise to generalize to Read/WriteVolatile, all the pieces are there. Signed-off-by: Patrick Roy <[email protected]>
1 parent 332fb58 commit 98235b7

File tree

4 files changed

+113
-52
lines changed

4 files changed

+113
-52
lines changed

src/vmm/src/devices/virtio/iovec.rs

Lines changed: 87 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -98,16 +98,29 @@ impl IoVecBuffer {
9898
///
9999
/// # Returns
100100
///
101-
/// The number of bytes read (if any)
102-
pub fn read_at(&self, mut buf: &mut [u8], offset: usize) -> Option<usize> {
101+
/// `Ok(())` if `buf` was filled by reading from this [`IoVecBuffer`],
102+
/// `Err(VolatileMemoryError::PartialBuffer)` if only part of `buf` could not be filled, and
103+
/// `Err(VolatileMemoryError::OutOfBounds)` if `offset >= self.len()`.
104+
pub fn read_exact_volatile_at(
105+
&self,
106+
mut buf: &mut [u8],
107+
offset: usize,
108+
) -> Result<(), VolatileMemoryError> {
103109
if offset < self.len() {
104-
// Make sure we only read up to the end of the `IoVecBuffer`.
105-
let size = buf.len().min(self.len() - offset);
106-
// write_volatile for &mut [u8] is infallible
107-
self.read_volatile_at(&mut buf, offset, size).ok()
110+
let expected = buf.len();
111+
let bytes_read = self.read_volatile_at(&mut buf, offset, expected)?;
112+
113+
if bytes_read != expected {
114+
return Err(VolatileMemoryError::PartialBuffer {
115+
expected,
116+
completed: bytes_read,
117+
});
118+
}
119+
120+
Ok(())
108121
} else {
109122
// If `offset` is past size, there's nothing to read.
110-
None
123+
Err(VolatileMemoryError::OutOfBounds { addr: offset })
111124
}
112125
}
113126

@@ -223,15 +236,29 @@ impl IoVecBufferMut {
223236
///
224237
/// # Returns
225238
///
226-
/// The number of bytes written (if any)
227-
pub fn write_at(&mut self, mut buf: &[u8], offset: usize) -> Option<usize> {
239+
/// `Ok(())` if the entire contents of `buf` could be written to this [`IoVecBufferMut`],
240+
/// `Err(VolatileMemoryError::PartialBuffer)` if only part of `buf` could be transferred, and
241+
/// `Err(VolatileMemoryError::OutOfBounds)` if `offset >= self.len()`.
242+
pub fn write_all_volatile_at(
243+
&mut self,
244+
mut buf: &[u8],
245+
offset: usize,
246+
) -> Result<(), VolatileMemoryError> {
228247
if offset < self.len() {
229-
// Make sure we only write up to the end of the `IoVecBufferMut`.
230-
let size = buf.len().min(self.len() - offset);
231-
self.write_volatile_at(&mut buf, offset, size).ok()
248+
let expected = buf.len();
249+
let bytes_written = self.write_volatile_at(&mut buf, offset, expected)?;
250+
251+
if bytes_written != expected {
252+
return Err(VolatileMemoryError::PartialBuffer {
253+
expected,
254+
completed: bytes_written,
255+
});
256+
}
257+
258+
Ok(())
232259
} else {
233260
// We cannot write past the end of the `IoVecBufferMut`.
234-
None
261+
Err(VolatileMemoryError::OutOfBounds { addr: offset })
235262
}
236263
}
237264

@@ -292,6 +319,7 @@ impl IoVecBufferMut {
292319
#[cfg(test)]
293320
mod tests {
294321
use libc::{c_void, iovec};
322+
use vm_memory::VolatileMemoryError;
295323

296324
use super::{IoVecBuffer, IoVecBufferMut};
297325
use crate::devices::virtio::queue::{Queue, VIRTQ_DESC_F_NEXT, VIRTQ_DESC_F_WRITE};
@@ -397,7 +425,7 @@ mod tests {
397425
let mem = default_mem();
398426
let (mut q, _) = read_only_chain(&mem);
399427
let head = q.pop(&mem).unwrap();
400-
assert!(IoVecBuffer::from_descriptor_chain(head).is_ok());
428+
IoVecBuffer::from_descriptor_chain(head).unwrap();
401429

402430
let (mut q, _) = write_only_chain(&mem);
403431
let head = q.pop(&mem).unwrap();
@@ -409,7 +437,7 @@ mod tests {
409437

410438
let (mut q, _) = write_only_chain(&mem);
411439
let head = q.pop(&mem).unwrap();
412-
assert!(IoVecBufferMut::from_descriptor_chain(head).is_ok());
440+
IoVecBufferMut::from_descriptor_chain(head).unwrap();
413441
}
414442

415443
#[test]
@@ -440,28 +468,48 @@ mod tests {
440468

441469
let iovec = IoVecBuffer::from_descriptor_chain(head).unwrap();
442470

443-
let mut buf = vec![0; 257];
444-
assert_eq!(iovec.read_at(&mut buf[..], 0), Some(256));
471+
let mut buf = vec![0u8; 257];
472+
assert_eq!(
473+
iovec
474+
.read_volatile_at(&mut buf.as_mut_slice(), 0, 257)
475+
.unwrap(),
476+
256
477+
);
445478
assert_eq!(buf[0..256], (0..=255).collect::<Vec<_>>());
446479
assert_eq!(buf[256], 0);
447480

448481
let mut buf = vec![0; 5];
449-
assert_eq!(iovec.read_at(&mut buf[..4], 0), Some(4));
482+
iovec.read_exact_volatile_at(&mut buf[..4], 0).unwrap();
450483
assert_eq!(buf, vec![0u8, 1, 2, 3, 0]);
451484

452-
assert_eq!(iovec.read_at(&mut buf, 0), Some(5));
485+
iovec.read_exact_volatile_at(&mut buf, 0).unwrap();
453486
assert_eq!(buf, vec![0u8, 1, 2, 3, 4]);
454487

455-
assert_eq!(iovec.read_at(&mut buf, 1), Some(5));
488+
iovec.read_exact_volatile_at(&mut buf, 1).unwrap();
456489
assert_eq!(buf, vec![1u8, 2, 3, 4, 5]);
457490

458-
assert_eq!(iovec.read_at(&mut buf, 60), Some(5));
491+
iovec.read_exact_volatile_at(&mut buf, 60).unwrap();
459492
assert_eq!(buf, vec![60u8, 61, 62, 63, 64]);
460493

461-
assert_eq!(iovec.read_at(&mut buf, 252), Some(4));
494+
assert_eq!(
495+
iovec
496+
.read_volatile_at(&mut buf.as_mut_slice(), 252, 5)
497+
.unwrap(),
498+
4
499+
);
462500
assert_eq!(buf[0..4], vec![252u8, 253, 254, 255]);
463501

464-
assert_eq!(iovec.read_at(&mut buf, 256), None);
502+
assert!(matches!(
503+
iovec.read_exact_volatile_at(&mut buf, 252),
504+
Err(VolatileMemoryError::PartialBuffer {
505+
expected: 5,
506+
completed: 4
507+
})
508+
));
509+
assert!(matches!(
510+
iovec.read_exact_volatile_at(&mut buf, 256),
511+
Err(VolatileMemoryError::OutOfBounds { addr: 256 })
512+
));
465513
}
466514

467515
#[test]
@@ -482,10 +530,10 @@ mod tests {
482530
let mut test_vec4 = vec![0u8; 64];
483531

484532
// Control test: Initially all three regions should be zero
485-
assert_eq!(iovec.write_at(&test_vec1, 0), Some(64));
486-
assert_eq!(iovec.write_at(&test_vec2, 64), Some(64));
487-
assert_eq!(iovec.write_at(&test_vec3, 128), Some(64));
488-
assert_eq!(iovec.write_at(&test_vec4, 192), Some(64));
533+
iovec.write_all_volatile_at(&test_vec1, 0).unwrap();
534+
iovec.write_all_volatile_at(&test_vec2, 64).unwrap();
535+
iovec.write_all_volatile_at(&test_vec3, 128).unwrap();
536+
iovec.write_all_volatile_at(&test_vec4, 192).unwrap();
489537
vq.dtable[0].check_data(&test_vec1);
490538
vq.dtable[1].check_data(&test_vec2);
491539
vq.dtable[2].check_data(&test_vec3);
@@ -494,7 +542,7 @@ mod tests {
494542
// Let's initialize test_vec1 with our buffer.
495543
test_vec1[..buf.len()].copy_from_slice(&buf);
496544
// And write just a part of it
497-
assert_eq!(iovec.write_at(&buf[..3], 0), Some(3));
545+
iovec.write_all_volatile_at(&buf[..3], 0).unwrap();
498546
// Not all 5 bytes from buf should be written in memory,
499547
// just 3 of them.
500548
vq.dtable[0].check_data(&[0u8, 1, 2, 0, 0]);
@@ -503,7 +551,7 @@ mod tests {
503551
vq.dtable[3].check_data(&test_vec4);
504552
// But if we write the whole `buf` in memory then all
505553
// of it should be observable.
506-
assert_eq!(iovec.write_at(&buf, 0), Some(5));
554+
iovec.write_all_volatile_at(&buf, 0).unwrap();
507555
vq.dtable[0].check_data(&test_vec1);
508556
vq.dtable[1].check_data(&test_vec2);
509557
vq.dtable[2].check_data(&test_vec3);
@@ -512,7 +560,7 @@ mod tests {
512560
// We are now writing with an offset of 1. So, initialize
513561
// the corresponding part of `test_vec1`
514562
test_vec1[1..buf.len() + 1].copy_from_slice(&buf);
515-
assert_eq!(iovec.write_at(&buf, 1), Some(5));
563+
iovec.write_all_volatile_at(&buf, 1).unwrap();
516564
vq.dtable[0].check_data(&test_vec1);
517565
vq.dtable[1].check_data(&test_vec2);
518566
vq.dtable[2].check_data(&test_vec3);
@@ -523,7 +571,7 @@ mod tests {
523571
// first region and one byte on the second
524572
test_vec1[60..64].copy_from_slice(&buf[0..4]);
525573
test_vec2[0] = 4;
526-
assert_eq!(iovec.write_at(&buf, 60), Some(5));
574+
iovec.write_all_volatile_at(&buf, 60).unwrap();
527575
vq.dtable[0].check_data(&test_vec1);
528576
vq.dtable[1].check_data(&test_vec2);
529577
vq.dtable[2].check_data(&test_vec3);
@@ -535,14 +583,20 @@ mod tests {
535583
// Now perform a write that does not fit in the buffer. Try writing
536584
// 5 bytes at offset 252 (only 4 bytes left).
537585
test_vec4[60..64].copy_from_slice(&buf[0..4]);
538-
assert_eq!(iovec.write_at(&buf, 252), Some(4));
586+
assert_eq!(
587+
iovec.write_volatile_at(&mut &*buf, 252, buf.len()).unwrap(),
588+
4
589+
);
539590
vq.dtable[0].check_data(&test_vec1);
540591
vq.dtable[1].check_data(&test_vec2);
541592
vq.dtable[2].check_data(&test_vec3);
542593
vq.dtable[3].check_data(&test_vec4);
543594

544595
// Trying to add past the end of the buffer should not write anything
545-
assert_eq!(iovec.write_at(&buf, 256), None);
596+
assert!(matches!(
597+
iovec.write_all_volatile_at(&buf, 256),
598+
Err(VolatileMemoryError::OutOfBounds { addr: 256 })
599+
));
546600
vq.dtable[0].check_data(&test_vec1);
547601
vq.dtable[1].check_data(&test_vec2);
548602
vq.dtable[2].check_data(&test_vec3);

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

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -444,13 +444,15 @@ impl Net {
444444
guest_mac: Option<MacAddr>,
445445
net_metrics: &NetDeviceMetrics,
446446
) -> Result<bool, NetError> {
447-
// Read the frame headers from the IoVecBuffer. This will return None
448-
// if the frame_iovec is empty.
449-
let header_len = frame_iovec.read_at(headers, 0).ok_or_else(|| {
450-
error!("Received empty TX buffer");
451-
net_metrics.tx_malformed_frames.inc();
452-
NetError::VnetHeaderMissing
453-
})?;
447+
// Read the frame headers from the IoVecBuffer
448+
let max_header_len = headers.len();
449+
let header_len = frame_iovec
450+
.read_volatile_at(&mut &mut *headers, 0, max_header_len)
451+
.map_err(|err| {
452+
error!("Received malformed TX buffer: {:?}", err);
453+
net_metrics.tx_malformed_frames.inc();
454+
NetError::VnetHeaderMissing
455+
})?;
454456

455457
let headers = frame_bytes_from_buf(&headers[..header_len]).map_err(|e| {
456458
error!("VNET headers missing in TX frame");
@@ -463,7 +465,9 @@ impl Net {
463465
let mut frame = vec![0u8; frame_iovec.len() - vnet_hdr_len()];
464466
// Ok to unwrap here, because we are passing a buffer that has the exact size
465467
// of the `IoVecBuffer` minus the VNET headers.
466-
frame_iovec.read_at(&mut frame, vnet_hdr_len()).unwrap();
468+
frame_iovec
469+
.read_exact_volatile_at(&mut frame, vnet_hdr_len())
470+
.unwrap();
467471
let _ = ns.detour_frame(&frame);
468472
METRICS.mmds.rx_accepted.inc();
469473

@@ -1510,7 +1514,7 @@ pub mod tests {
15101514
let buffer = IoVecBuffer::from(&frame_buf[..frame_len]);
15111515

15121516
let mut headers = vec![0; frame_hdr_len()];
1513-
buffer.read_at(&mut headers, 0).unwrap();
1517+
buffer.read_exact_volatile_at(&mut headers, 0).unwrap();
15141518

15151519
// Call the code which sends the packet to the host or MMDS.
15161520
// Validate the frame was consumed by MMDS and that the metrics reflect that.

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@ impl Entropy {
119119
})?;
120120

121121
// It is ok to unwrap here. We are writing `iovec.len()` bytes at offset 0.
122-
Ok(iovec.write_at(&rand_bytes, 0).unwrap().try_into().unwrap())
122+
iovec.write_all_volatile_at(&rand_bytes, 0).unwrap();
123+
Ok(iovec.len().try_into().unwrap())
123124
}
124125

125126
fn process_entropy_queue(&mut self) {

src/vmm/src/devices/virtio/vsock/packet.rs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
1818
use std::fmt::Debug;
1919

20+
use vm_memory::volatile_memory::Error;
2021
use vm_memory::{GuestMemoryError, ReadVolatile, WriteVolatile};
2122

2223
use super::{defs, VsockError};
@@ -126,9 +127,12 @@ impl VsockPacket {
126127
let buffer = IoVecBuffer::from_descriptor_chain(chain)?;
127128

128129
let mut hdr = VsockPacketHeader::default();
129-
let header_bytes_read = buffer.read_at(hdr.as_mut_slice(), 0).unwrap_or(0);
130-
if header_bytes_read < VSOCK_PKT_HDR_SIZE as usize {
131-
return Err(VsockError::DescChainTooShortForHeader(header_bytes_read));
130+
match buffer.read_exact_volatile_at(hdr.as_mut_slice(), 0) {
131+
Ok(()) => (),
132+
Err(Error::PartialBuffer { completed, .. }) => {
133+
return Err(VsockError::DescChainTooShortForHeader(completed))
134+
}
135+
Err(err) => return Err(VsockError::GuestMemoryMmap(err.into())),
132136
}
133137

134138
if hdr.len > defs::MAX_PKT_BUF_SIZE {
@@ -190,12 +194,10 @@ impl VsockPacket {
190194
return Err(VsockError::InvalidPktLen(self.hdr.len));
191195
}
192196

193-
let bytes_written = buffer.write_at(self.hdr.as_slice(), 0);
194-
195-
// We check the the buffer has sufficient size in from_rx_virtq_head
196-
debug_assert_eq!(bytes_written, Some(VSOCK_PKT_HDR_SIZE as usize));
197-
198-
Ok(())
197+
buffer
198+
.write_all_volatile_at(self.hdr.as_slice(), 0)
199+
.map_err(GuestMemoryError::from)
200+
.map_err(VsockError::GuestMemoryMmap)
199201
}
200202
}
201203
}

0 commit comments

Comments
 (0)