|
5 | 5 | // Use of this source code is governed by a BSD-style license that can be |
6 | 6 | // found in the THIRD-PARTY file. |
7 | 7 |
|
8 | | -use std::io::Error as IoError; |
| 8 | +use std::io::{Error as IoError, ErrorKind}; |
9 | 9 | use std::os::unix::io::AsRawFd; |
10 | 10 |
|
11 | 11 | use vm_memory::bitmap::AtomicBitmap; |
12 | | -pub use vm_memory::bitmap::Bitmap; |
| 12 | +pub use vm_memory::bitmap::{Bitmap, BitmapSlice}; |
13 | 13 | use vm_memory::mmap::{check_file_offset, NewBitmap}; |
14 | 14 | pub use vm_memory::mmap::{MmapRegionBuilder, MmapRegionError}; |
15 | 15 | pub use vm_memory::{ |
16 | 16 | address, Address, ByteValued, Bytes, Error, FileOffset, GuestAddress, GuestMemory, |
17 | 17 | GuestMemoryError, GuestMemoryRegion, GuestUsize, MemoryRegionAddress, MmapRegion, |
18 | | - VolatileMemory, VolatileMemoryError, |
| 18 | + VolatileMemory, VolatileMemoryError, VolatileSlice, |
19 | 19 | }; |
20 | 20 |
|
21 | 21 | pub type GuestMemoryMmap = vm_memory::GuestMemoryMmap<Option<AtomicBitmap>>; |
@@ -142,6 +142,212 @@ pub fn mark_dirty_mem(mem: &GuestMemoryMmap, addr: GuestAddress, len: usize) { |
142 | 142 | }); |
143 | 143 | } |
144 | 144 |
|
| 145 | +pub trait ReadVolatile { |
| 146 | + fn read_volatile<B: BitmapSlice>( |
| 147 | + &mut self, |
| 148 | + buf: &mut VolatileSlice<B>, |
| 149 | + ) -> Result<usize, VolatileMemoryError>; |
| 150 | + |
| 151 | + fn read_exact_volatile<B: BitmapSlice>( |
| 152 | + &mut self, |
| 153 | + buf: &mut VolatileSlice<B>, |
| 154 | + ) -> Result<(), VolatileMemoryError> { |
| 155 | + // Implementation based on https://github.com/rust-lang/rust/blob/7e7483d26e3cec7a44ef00cf7ae6c9c8c918bec6/library/std/src/io/mod.rs#L465 |
| 156 | + |
| 157 | + // Doing the trick the stdlib does where in each loop, the given slice is adjusted does not |
| 158 | + // work for us as VolatileSlice::offset returns an owned copy, but `buf` is a |
| 159 | + // reference. This means the first iteration of the loop would have to work with a |
| 160 | + // reference, and all following iterations with an owned copy. This is not (easily) |
| 161 | + // expressible in rust's type system, which is why we use this approach. Note that the total |
| 162 | + // number of `.offset` calls are the same, meaning we do not incur a performance impact (as |
| 163 | + // VolatileSlice::offset is a O(1) operation). |
| 164 | + let mut bytes_read_so_far = 0; |
| 165 | + |
| 166 | + while bytes_read_so_far < buf.len() { |
| 167 | + match self.read_volatile(&mut buf.offset(bytes_read_so_far)?) { |
| 168 | + Err(VolatileMemoryError::IOError(err)) if err.kind() == ErrorKind::Interrupted => { |
| 169 | + continue |
| 170 | + } |
| 171 | + Ok(0) => { |
| 172 | + return Err(VolatileMemoryError::IOError(std::io::Error::new( |
| 173 | + ErrorKind::UnexpectedEof, |
| 174 | + "failed to fill whole buffer", |
| 175 | + ))) |
| 176 | + } |
| 177 | + Ok(bytes_read) => bytes_read_so_far += bytes_read, |
| 178 | + Err(err) => return Err(err), |
| 179 | + } |
| 180 | + } |
| 181 | + |
| 182 | + Ok(()) |
| 183 | + } |
| 184 | +} |
| 185 | + |
| 186 | +pub trait WriteVolatile { |
| 187 | + fn write_volatile<B: BitmapSlice>( |
| 188 | + &mut self, |
| 189 | + buf: &VolatileSlice<B>, |
| 190 | + ) -> Result<usize, VolatileMemoryError>; |
| 191 | + |
| 192 | + fn write_all_volatile<B: BitmapSlice>( |
| 193 | + &mut self, |
| 194 | + buf: &VolatileSlice<B>, |
| 195 | + ) -> Result<(), VolatileMemoryError> { |
| 196 | + // Based on https://github.com/rust-lang/rust/blob/7e7483d26e3cec7a44ef00cf7ae6c9c8c918bec6/library/std/src/io/mod.rs#L1570 |
| 197 | + |
| 198 | + // See comment in ReadVolatile::read_exact_volatile for reasons for this variable. |
| 199 | + let mut bytes_written_so_far = 0; |
| 200 | + |
| 201 | + while bytes_written_so_far < buf.len() { |
| 202 | + match self.write_volatile(&buf.offset(bytes_written_so_far)?) { |
| 203 | + Err(VolatileMemoryError::IOError(err)) if err.kind() == ErrorKind::Interrupted => { |
| 204 | + continue |
| 205 | + } |
| 206 | + Ok(0) => { |
| 207 | + return Err(VolatileMemoryError::IOError(std::io::Error::new( |
| 208 | + ErrorKind::WriteZero, |
| 209 | + "failed to write whole buffer", |
| 210 | + ))) |
| 211 | + } |
| 212 | + Ok(bytes_written) => bytes_written_so_far += bytes_written, |
| 213 | + Err(err) => return Err(err), |
| 214 | + } |
| 215 | + } |
| 216 | + |
| 217 | + Ok(()) |
| 218 | + } |
| 219 | +} |
| 220 | + |
| 221 | +impl ReadVolatile for std::fs::File { |
| 222 | + fn read_volatile<B: BitmapSlice>( |
| 223 | + &mut self, |
| 224 | + buf: &mut VolatileSlice<B>, |
| 225 | + ) -> Result<usize, VolatileMemoryError> { |
| 226 | + read_volatile_raw_fd(self, buf) |
| 227 | + } |
| 228 | +} |
| 229 | + |
| 230 | +impl ReadVolatile for std::os::unix::net::UnixStream { |
| 231 | + fn read_volatile<B: BitmapSlice>( |
| 232 | + &mut self, |
| 233 | + buf: &mut VolatileSlice<B>, |
| 234 | + ) -> Result<usize, VolatileMemoryError> { |
| 235 | + read_volatile_raw_fd(self, buf) |
| 236 | + } |
| 237 | +} |
| 238 | + |
| 239 | +fn read_volatile_raw_fd( |
| 240 | + raw_fd: &mut impl AsRawFd, |
| 241 | + buf: &mut VolatileSlice<impl BitmapSlice>, |
| 242 | +) -> Result<usize, VolatileMemoryError> { |
| 243 | + let fd = raw_fd.as_raw_fd(); |
| 244 | + let dst = buf.as_ptr().cast::<libc::c_void>(); |
| 245 | + |
| 246 | + buf.bitmap().mark_dirty(0, buf.len()); |
| 247 | + |
| 248 | + // SAFETY: We got a valid file descriptor from `AsRawFd`. The memory pointed to by `dst` is |
| 249 | + // valid for writes of length `buf.len() by the invariants upheld by the constructor |
| 250 | + // of `VolatileSlice`. |
| 251 | + let bytes_read = unsafe { libc::read(fd, dst, buf.len()) }; |
| 252 | + |
| 253 | + if bytes_read < 0 { |
| 254 | + Err(VolatileMemoryError::IOError(std::io::Error::last_os_error())) |
| 255 | + } else { |
| 256 | + Ok(bytes_read.try_into().unwrap()) |
| 257 | + } |
| 258 | +} |
| 259 | + |
| 260 | +impl WriteVolatile for std::fs::File { |
| 261 | + fn write_volatile<B: BitmapSlice>( |
| 262 | + &mut self, |
| 263 | + buf: &VolatileSlice<B>, |
| 264 | + ) -> Result<usize, VolatileMemoryError> { |
| 265 | + write_volatile_raw_fd(self, buf) |
| 266 | + } |
| 267 | +} |
| 268 | + |
| 269 | +impl WriteVolatile for std::os::unix::net::UnixStream { |
| 270 | + fn write_volatile<B: BitmapSlice>( |
| 271 | + &mut self, |
| 272 | + buf: &VolatileSlice<B>, |
| 273 | + ) -> Result<usize, VolatileMemoryError> { |
| 274 | + write_volatile_raw_fd(self, buf) |
| 275 | + } |
| 276 | +} |
| 277 | + |
| 278 | +fn write_volatile_raw_fd( |
| 279 | + raw_fd: &mut impl AsRawFd, |
| 280 | + buf: &VolatileSlice<impl BitmapSlice>, |
| 281 | +) -> Result<usize, VolatileMemoryError> { |
| 282 | + let fd = raw_fd.as_raw_fd(); |
| 283 | + let src = buf.as_ptr().cast::<libc::c_void>(); |
| 284 | + |
| 285 | + // SAFETY: We got a valid file descriptor from `AsRawFd`. The memory pointed to by `src` is |
| 286 | + // valid for reads of length `buf.len() by the invariants upheld by the constructor |
| 287 | + // of `VolatileSlice`. |
| 288 | + let bytes_written = unsafe { libc::write(fd, src, buf.len()) }; |
| 289 | + |
| 290 | + if bytes_written < 0 { |
| 291 | + Err(VolatileMemoryError::IOError(std::io::Error::last_os_error())) |
| 292 | + } else { |
| 293 | + Ok(bytes_written.try_into().unwrap()) |
| 294 | + } |
| 295 | +} |
| 296 | + |
| 297 | +impl WriteVolatile for &mut [u8] { |
| 298 | + fn write_volatile<B: BitmapSlice>( |
| 299 | + &mut self, |
| 300 | + buf: &VolatileSlice<B>, |
| 301 | + ) -> Result<usize, VolatileMemoryError> { |
| 302 | + // NOTE: The duality of read <-> write here is correct. This is because we translate a call |
| 303 | + // "slice.write(buf)" (e.g. write into slice from buf) into "buf.read(slice)" (e.g. read |
| 304 | + // from buffer into slice). Both express data transfer from the buffer to the slice |
| 305 | + buf.read(self, 0) |
| 306 | + } |
| 307 | + |
| 308 | + fn write_all_volatile<B: BitmapSlice>( |
| 309 | + &mut self, |
| 310 | + buf: &VolatileSlice<B>, |
| 311 | + ) -> Result<(), VolatileMemoryError> { |
| 312 | + // Based on https://github.com/rust-lang/rust/blob/f7b831ac8a897273f78b9f47165cf8e54066ce4b/library/std/src/io/impls.rs#L376-L382 |
| 313 | + if self.write_volatile(buf)? == buf.len() { |
| 314 | + Ok(()) |
| 315 | + } else { |
| 316 | + Err(VolatileMemoryError::IOError(std::io::Error::new( |
| 317 | + ErrorKind::WriteZero, |
| 318 | + "failed to write whole buffer", |
| 319 | + ))) |
| 320 | + } |
| 321 | + } |
| 322 | +} |
| 323 | + |
| 324 | +impl ReadVolatile for &[u8] { |
| 325 | + fn read_volatile<B: BitmapSlice>( |
| 326 | + &mut self, |
| 327 | + buf: &mut VolatileSlice<B>, |
| 328 | + ) -> Result<usize, VolatileMemoryError> { |
| 329 | + // NOTE: the duality of read <-> write here is correct. This is because we translate a call |
| 330 | + // "slice.read(buf)" (e.g. "read from slice into buf") into "buf.write(slice)" (e.g. write |
| 331 | + // into buf from slice) |
| 332 | + buf.write(self, 0) |
| 333 | + } |
| 334 | + |
| 335 | + fn read_exact_volatile<B: BitmapSlice>( |
| 336 | + &mut self, |
| 337 | + buf: &mut VolatileSlice<B>, |
| 338 | + ) -> Result<(), VolatileMemoryError> { |
| 339 | + // Based on https://github.com/rust-lang/rust/blob/f7b831ac8a897273f78b9f47165cf8e54066ce4b/library/std/src/io/impls.rs#L282-L302 |
| 340 | + if buf.len() > self.len() { |
| 341 | + return Err(VolatileMemoryError::IOError(std::io::Error::new( |
| 342 | + ErrorKind::UnexpectedEof, |
| 343 | + "failed to fill whole buffer", |
| 344 | + ))); |
| 345 | + } |
| 346 | + |
| 347 | + self.read_volatile(buf).map(|_| ()) |
| 348 | + } |
| 349 | +} |
| 350 | + |
145 | 351 | pub mod test_utils { |
146 | 352 | use super::*; |
147 | 353 |
|
@@ -193,6 +399,8 @@ pub mod test_utils { |
193 | 399 | #[cfg(test)] |
194 | 400 | mod tests { |
195 | 401 | #![allow(clippy::undocumented_unsafe_blocks)] |
| 402 | + use std::io::{Read, Seek, Write}; |
| 403 | + |
196 | 404 | use super::*; |
197 | 405 | use crate::get_page_size; |
198 | 406 | use crate::tempfile::TempFile; |
@@ -444,4 +652,147 @@ mod tests { |
444 | 652 | .unwrap(); |
445 | 653 | } |
446 | 654 | } |
| 655 | + |
| 656 | + #[test] |
| 657 | + fn test_read_volatile() { |
| 658 | + let test_cases = [ |
| 659 | + ([1u8, 2].as_ref(), [1u8, 2, 0, 0, 0]), |
| 660 | + ([1, 2, 3, 4].as_ref(), [1, 2, 3, 4, 0]), |
| 661 | + // ensure we don't have a buffer overrun |
| 662 | + ([5, 6, 7, 8, 9].as_ref(), [5, 6, 7, 8, 0]), |
| 663 | + ]; |
| 664 | + |
| 665 | + for (mut input, output) in test_cases { |
| 666 | + // ---- Test ReadVolatile for &[u8] ---- |
| 667 | + // |
| 668 | + // Test read_volatile for &[u8] works |
| 669 | + let mut memory = vec![0u8; 5]; |
| 670 | + |
| 671 | + assert_eq!( |
| 672 | + input |
| 673 | + .read_volatile(&mut VolatileSlice::from(&mut memory[..4])) |
| 674 | + .unwrap(), |
| 675 | + input.len().min(4) |
| 676 | + ); |
| 677 | + assert_eq!(&memory, &output); |
| 678 | + |
| 679 | + // Test read_exact_volatile for &[u8] works |
| 680 | + let mut memory = vec![0u8; 5]; |
| 681 | + let result = input.read_exact_volatile(&mut VolatileSlice::from(&mut memory[..4])); |
| 682 | + |
| 683 | + // read_exact fails if there are not enough bytes in input to completely fill |
| 684 | + // memory[..4] |
| 685 | + if input.len() < 4 { |
| 686 | + result.unwrap_err(); |
| 687 | + assert_eq!(memory, vec![0u8; 5]); |
| 688 | + } else { |
| 689 | + result.unwrap(); |
| 690 | + assert_eq!(&memory, &output); |
| 691 | + } |
| 692 | + |
| 693 | + // ---- Test ReadVolatile for File ---- |
| 694 | + |
| 695 | + let mut temp_file = TempFile::new().unwrap().into_file(); |
| 696 | + temp_file.write_all(input).unwrap(); |
| 697 | + temp_file.rewind().unwrap(); |
| 698 | + |
| 699 | + // Test read_volatile for File works |
| 700 | + let mut memory = vec![0u8; 5]; |
| 701 | + |
| 702 | + assert_eq!( |
| 703 | + temp_file |
| 704 | + .read_volatile(&mut VolatileSlice::from(&mut memory[..4])) |
| 705 | + .unwrap(), |
| 706 | + input.len().min(4) |
| 707 | + ); |
| 708 | + assert_eq!(&memory, &output); |
| 709 | + |
| 710 | + temp_file.rewind().unwrap(); |
| 711 | + |
| 712 | + // Test read_exact_volatile for File works |
| 713 | + let mut memory = vec![0u8; 5]; |
| 714 | + |
| 715 | + let read_exact_result = |
| 716 | + temp_file.read_exact_volatile(&mut VolatileSlice::from(&mut memory[..4])); |
| 717 | + |
| 718 | + if input.len() < 4 { |
| 719 | + read_exact_result.unwrap_err(); |
| 720 | + } else { |
| 721 | + read_exact_result.unwrap(); |
| 722 | + } |
| 723 | + assert_eq!(&memory, &output); |
| 724 | + } |
| 725 | + } |
| 726 | + |
| 727 | + #[test] |
| 728 | + fn test_write_volatile() { |
| 729 | + let test_cases = [ |
| 730 | + (vec![1u8, 2], [1u8, 2, 0, 0, 0]), |
| 731 | + (vec![1, 2, 3, 4], [1, 2, 3, 4, 0]), |
| 732 | + // ensure we don't have a buffer overrun |
| 733 | + (vec![5, 6, 7, 8, 9], [5, 6, 7, 8, 0]), |
| 734 | + ]; |
| 735 | + |
| 736 | + for (mut input, output) in test_cases { |
| 737 | + // ---- Test WriteVolatile for &mut [u8] ---- |
| 738 | + // |
| 739 | + // Test write_volatile for &mut [u8] works |
| 740 | + let mut memory = vec![0u8; 5]; |
| 741 | + |
| 742 | + assert_eq!( |
| 743 | + (&mut memory[..4]) |
| 744 | + .write_volatile(&VolatileSlice::from(input.as_mut_slice())) |
| 745 | + .unwrap(), |
| 746 | + input.len().min(4) |
| 747 | + ); |
| 748 | + assert_eq!(&memory, &output); |
| 749 | + |
| 750 | + // Test write_all_volatile for &mut [u8] works |
| 751 | + let mut memory = vec![0u8; 5]; |
| 752 | + |
| 753 | + let result = |
| 754 | + (&mut memory[..4]).write_all_volatile(&VolatileSlice::from(input.as_mut_slice())); |
| 755 | + |
| 756 | + if input.len() > 4 { |
| 757 | + result.unwrap_err(); |
| 758 | + // This quirky behavior of writing to the slice even in the case of failure is also |
| 759 | + // exhibited by the stdlib |
| 760 | + assert_eq!(&memory, &output); |
| 761 | + } else { |
| 762 | + result.unwrap(); |
| 763 | + assert_eq!(&memory, &output); |
| 764 | + } |
| 765 | + |
| 766 | + // ---- Test ẂriteVolatile for File works |
| 767 | + // Test write_volatile for File works |
| 768 | + let mut temp_file = TempFile::new().unwrap().into_file(); |
| 769 | + |
| 770 | + temp_file |
| 771 | + .write_volatile(&VolatileSlice::from(input.as_mut_slice())) |
| 772 | + .unwrap(); |
| 773 | + temp_file.rewind().unwrap(); |
| 774 | + |
| 775 | + let mut written = vec![0u8; input.len()]; |
| 776 | + temp_file.read_exact(written.as_mut_slice()).unwrap(); |
| 777 | + |
| 778 | + assert_eq!(input, written); |
| 779 | + // check no excess bytes were written to the file |
| 780 | + assert_eq!(temp_file.read(&mut [0u8]).unwrap(), 0); |
| 781 | + |
| 782 | + // Test write_all_volatile for File works |
| 783 | + let mut temp_file = TempFile::new().unwrap().into_file(); |
| 784 | + |
| 785 | + temp_file |
| 786 | + .write_all_volatile(&VolatileSlice::from(input.as_mut_slice())) |
| 787 | + .unwrap(); |
| 788 | + temp_file.rewind().unwrap(); |
| 789 | + |
| 790 | + let mut written = vec![0u8; input.len()]; |
| 791 | + temp_file.read_exact(written.as_mut_slice()).unwrap(); |
| 792 | + |
| 793 | + assert_eq!(input, written); |
| 794 | + // check no excess bytes were written to the file |
| 795 | + assert_eq!(temp_file.read(&mut [0u8]).unwrap(), 0); |
| 796 | + } |
| 797 | + } |
447 | 798 | } |
0 commit comments