crosvm/sys_util/src/file_traits.rs
Noah Gold ee02cb6f36 [data_model] VolatileSlice use IoSlice.
Switches from using libc::iovec directly to using our own IoSliceMut.
This also renames IntoIovec to IntoIobuf, along with the associated
methods. Effectively this pushes conversion into iovec closer to
where it is used.

Test: FEATURES=test emerge-eve crosvm
Change-Id: I2f907aa321a43d751a82e8430c74ac08b95772f7
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/crosvm/+/2300842
Tested-by: kokoro <noreply+kokoro@google.com>
Commit-Queue: Noah Gold <nkgold@google.com>
Reviewed-by: Chirantan Ekbote <chirantan@chromium.org>
2020-07-21 23:20:22 +00:00

495 lines
18 KiB
Rust

// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::fs::File;
use std::io::{Error, ErrorKind, Result};
use std::os::unix::io::{AsRawFd, RawFd};
use data_model::VolatileSlice;
use crate::{fallocate, FallocateMode};
/// A trait for flushing the contents of a file to disk.
/// This is equivalent to File's `sync_all` method, but
/// wrapped in a trait so that it can be implemented for
/// other types.
pub trait FileSync {
// Flush buffers related to this file to disk.
fn fsync(&mut self) -> Result<()>;
}
impl FileSync for File {
fn fsync(&mut self) -> Result<()> {
self.sync_all()
}
}
/// A trait for setting the size of a file.
/// This is equivalent to File's `set_len` method, but
/// wrapped in a trait so that it can be implemented for
/// other types.
pub trait FileSetLen {
// Set the size of this file.
// This is the moral equivalent of `ftruncate()`.
fn set_len(&self, _len: u64) -> Result<()>;
}
impl FileSetLen for File {
fn set_len(&self, len: u64) -> Result<()> {
File::set_len(self, len)
}
}
/// A trait for getting the size of a file.
/// This is equivalent to File's metadata().len() method,
/// but wrapped in a trait so that it can be implemented for
/// other types.
pub trait FileGetLen {
/// Get the current length of the file in bytes.
fn get_len(&self) -> Result<u64>;
}
impl FileGetLen for File {
fn get_len(&self) -> Result<u64> {
Ok(self.metadata()?.len())
}
}
/// A trait for allocating disk space in a sparse file.
/// This is equivalent to fallocate() with no special flags.
pub trait FileAllocate {
/// Allocate storage for the region of the file starting at `offset` and extending `len` bytes.
fn allocate(&mut self, offset: u64, len: u64) -> Result<()>;
}
impl FileAllocate for File {
fn allocate(&mut self, offset: u64, len: u64) -> Result<()> {
fallocate(self, FallocateMode::Allocate, true, offset, len)
.map_err(|e| Error::from_raw_os_error(e.errno()))
}
}
/// A trait similar to `Read` and `Write`, but uses volatile memory as buffers.
pub trait FileReadWriteVolatile {
/// Read bytes from this file into the given slice, returning the number of bytes read on
/// success.
fn read_volatile(&mut self, slice: VolatileSlice) -> Result<usize>;
/// Like `read_volatile`, except it reads to a slice of buffers. Data is copied to fill each
/// buffer in order, with the final buffer written to possibly being only partially filled. This
/// method must behave as a single call to `read_volatile` with the buffers concatenated would.
/// The default implementation calls `read_volatile` with either the first nonempty buffer
/// provided, or returns `Ok(0)` if none exists.
fn read_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
bufs.iter()
.find(|b| b.size() > 0)
.map(|&b| self.read_volatile(b))
.unwrap_or(Ok(0))
}
/// Reads bytes from this into the given slice until all bytes in the slice are written, or an
/// error is returned.
fn read_exact_volatile(&mut self, mut slice: VolatileSlice) -> Result<()> {
while slice.size() > 0 {
let bytes_read = self.read_volatile(slice)?;
if bytes_read == 0 {
return Err(Error::from(ErrorKind::UnexpectedEof));
}
// Will panic if read_volatile read more bytes than we gave it, which would be worthy of
// a panic.
slice = slice.offset(bytes_read).unwrap();
}
Ok(())
}
/// Write bytes from the slice to the given file, returning the number of bytes written on
/// success.
fn write_volatile(&mut self, slice: VolatileSlice) -> Result<usize>;
/// Like `write_volatile`, except that it writes from a slice of buffers. Data is copied from
/// each buffer in order, with the final buffer read from possibly being only partially
/// consumed. This method must behave as a call to `write_volatile` with the buffers
/// concatenated would. The default implementation calls `write_volatile` with either the first
/// nonempty buffer provided, or returns `Ok(0)` if none exists.
fn write_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
bufs.iter()
.find(|b| b.size() > 0)
.map(|&b| self.write_volatile(b))
.unwrap_or(Ok(0))
}
/// Write bytes from the slice to the given file until all the bytes from the slice have been
/// written, or an error is returned.
fn write_all_volatile(&mut self, mut slice: VolatileSlice) -> Result<()> {
while slice.size() > 0 {
let bytes_written = self.write_volatile(slice)?;
if bytes_written == 0 {
return Err(Error::from(ErrorKind::WriteZero));
}
// Will panic if read_volatile read more bytes than we gave it, which would be worthy of
// a panic.
slice = slice.offset(bytes_written).unwrap();
}
Ok(())
}
}
impl<'a, T: FileReadWriteVolatile + ?Sized> FileReadWriteVolatile for &'a mut T {
fn read_volatile(&mut self, slice: VolatileSlice) -> Result<usize> {
(**self).read_volatile(slice)
}
fn read_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
(**self).read_vectored_volatile(bufs)
}
fn read_exact_volatile(&mut self, slice: VolatileSlice) -> Result<()> {
(**self).read_exact_volatile(slice)
}
fn write_volatile(&mut self, slice: VolatileSlice) -> Result<usize> {
(**self).write_volatile(slice)
}
fn write_vectored_volatile(&mut self, bufs: &[VolatileSlice]) -> Result<usize> {
(**self).write_vectored_volatile(bufs)
}
fn write_all_volatile(&mut self, slice: VolatileSlice) -> Result<()> {
(**self).write_all_volatile(slice)
}
}
/// A trait similar to the unix `ReadExt` and `WriteExt` traits, but for volatile memory.
pub trait FileReadWriteAtVolatile {
/// Reads bytes from this file at `offset` into the given slice, returning the number of bytes
/// read on success.
fn read_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize>;
/// Like `read_at_volatile`, except it reads to a slice of buffers. Data is copied to fill each
/// buffer in order, with the final buffer written to possibly being only partially filled. This
/// method must behave as a single call to `read_at_volatile` with the buffers concatenated
/// would. The default implementation calls `read_at_volatile` with either the first nonempty
/// buffer provided, or returns `Ok(0)` if none exists.
fn read_vectored_at_volatile(&mut self, bufs: &[VolatileSlice], offset: u64) -> Result<usize> {
if let Some(&slice) = bufs.first() {
self.read_at_volatile(slice, offset)
} else {
Ok(0)
}
}
/// Reads bytes from this file at `offset` into the given slice until all bytes in the slice are
/// read, or an error is returned.
fn read_exact_at_volatile(&mut self, mut slice: VolatileSlice, mut offset: u64) -> Result<()> {
while slice.size() > 0 {
match self.read_at_volatile(slice, offset) {
Ok(0) => return Err(Error::from(ErrorKind::UnexpectedEof)),
Ok(n) => {
slice = slice.offset(n).unwrap();
offset = offset.checked_add(n as u64).unwrap();
}
Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
Err(e) => return Err(e),
}
}
Ok(())
}
/// Writes bytes from this file at `offset` into the given slice, returning the number of bytes
/// written on success.
fn write_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize>;
/// Like `write_at_at_volatile`, except that it writes from a slice of buffers. Data is copied
/// from each buffer in order, with the final buffer read from possibly being only partially
/// consumed. This method must behave as a call to `write_at_volatile` with the buffers
/// concatenated would. The default implementation calls `write_at_volatile` with either the
/// first nonempty buffer provided, or returns `Ok(0)` if none exists.
fn write_vectored_at_volatile(&mut self, bufs: &[VolatileSlice], offset: u64) -> Result<usize> {
if let Some(&slice) = bufs.first() {
self.write_at_volatile(slice, offset)
} else {
Ok(0)
}
}
/// Writes bytes from this file at `offset` into the given slice until all bytes in the slice
/// are written, or an error is returned.
fn write_all_at_volatile(&mut self, mut slice: VolatileSlice, mut offset: u64) -> Result<()> {
while slice.size() > 0 {
match self.write_at_volatile(slice, offset) {
Ok(0) => return Err(Error::from(ErrorKind::WriteZero)),
Ok(n) => {
slice = slice.offset(n).unwrap();
offset = offset.checked_add(n as u64).unwrap();
}
Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
Err(e) => return Err(e),
}
}
Ok(())
}
}
impl<'a, T: FileReadWriteAtVolatile + ?Sized> FileReadWriteAtVolatile for &'a mut T {
fn read_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize> {
(**self).read_at_volatile(slice, offset)
}
fn read_vectored_at_volatile(&mut self, bufs: &[VolatileSlice], offset: u64) -> Result<usize> {
(**self).read_vectored_at_volatile(bufs, offset)
}
fn read_exact_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<()> {
(**self).read_exact_at_volatile(slice, offset)
}
fn write_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<usize> {
(**self).write_at_volatile(slice, offset)
}
fn write_vectored_at_volatile(&mut self, bufs: &[VolatileSlice], offset: u64) -> Result<usize> {
(**self).write_vectored_at_volatile(bufs, offset)
}
fn write_all_at_volatile(&mut self, slice: VolatileSlice, offset: u64) -> Result<()> {
(**self).write_all_at_volatile(slice, offset)
}
}
// This module allows the below macros to refer to $crate::file_traits::lib::X and ensures other
// crates don't need to add additional crates to their Cargo.toml.
pub mod lib {
pub use libc::{
c_int, c_void, iovec, off64_t, pread64, preadv64, pwrite64, pwritev64, read, readv, size_t,
write, writev,
};
pub use data_model::VolatileSlice;
}
#[macro_export]
macro_rules! volatile_impl {
($ty:ty) => {
impl FileReadWriteVolatile for $ty {
fn read_volatile(
&mut self,
slice: $crate::file_traits::lib::VolatileSlice,
) -> std::io::Result<usize> {
// Safe because only bytes inside the slice are accessed and the kernel is expected
// to handle arbitrary memory for I/O.
let ret = unsafe {
$crate::file_traits::lib::read(
self.as_raw_fd(),
slice.as_mut_ptr() as *mut std::ffi::c_void,
slice.size() as usize,
)
};
if ret >= 0 {
Ok(ret as usize)
} else {
Err(std::io::Error::last_os_error())
}
}
fn read_vectored_volatile(
&mut self,
bufs: &[$crate::file_traits::lib::VolatileSlice],
) -> std::io::Result<usize> {
let iovecs = $crate::file_traits::lib::VolatileSlice::as_iobufs(bufs);
if iovecs.is_empty() {
return Ok(0);
}
// Safe because only bytes inside the buffers are accessed and the kernel is
// expected to handle arbitrary memory for I/O.
let ret = unsafe {
$crate::file_traits::lib::readv(
self.as_raw_fd(),
iovecs.as_ptr(),
iovecs.len() as std::os::raw::c_int,
)
};
if ret >= 0 {
Ok(ret as usize)
} else {
Err(std::io::Error::last_os_error())
}
}
fn write_volatile(
&mut self,
slice: $crate::file_traits::lib::VolatileSlice,
) -> std::io::Result<usize> {
// Safe because only bytes inside the slice are accessed and the kernel is expected
// to handle arbitrary memory for I/O.
let ret = unsafe {
$crate::file_traits::lib::write(
self.as_raw_fd(),
slice.as_ptr() as *const std::ffi::c_void,
slice.size() as usize,
)
};
if ret >= 0 {
Ok(ret as usize)
} else {
Err(std::io::Error::last_os_error())
}
}
fn write_vectored_volatile(
&mut self,
bufs: &[$crate::file_traits::lib::VolatileSlice],
) -> std::io::Result<usize> {
let iovecs = $crate::file_traits::lib::VolatileSlice::as_iobufs(bufs);
if iovecs.is_empty() {
return Ok(0);
}
// Safe because only bytes inside the buffers are accessed and the kernel is
// expected to handle arbitrary memory for I/O.
let ret = unsafe {
$crate::file_traits::lib::writev(
self.as_raw_fd(),
iovecs.as_ptr(),
iovecs.len() as std::os::raw::c_int,
)
};
if ret >= 0 {
Ok(ret as usize)
} else {
Err(std::io::Error::last_os_error())
}
}
}
};
}
#[macro_export]
macro_rules! volatile_at_impl {
($ty:ty) => {
impl FileReadWriteAtVolatile for $ty {
fn read_at_volatile(
&mut self,
slice: $crate::file_traits::lib::VolatileSlice,
offset: u64,
) -> std::io::Result<usize> {
// Safe because only bytes inside the slice are accessed and the kernel is expected
// to handle arbitrary memory for I/O.
let ret = unsafe {
$crate::file_traits::lib::pread64(
self.as_raw_fd(),
slice.as_mut_ptr() as *mut std::ffi::c_void,
slice.size() as usize,
offset as $crate::file_traits::lib::off64_t,
)
};
if ret >= 0 {
Ok(ret as usize)
} else {
Err(std::io::Error::last_os_error())
}
}
fn read_vectored_at_volatile(
&mut self,
bufs: &[$crate::file_traits::lib::VolatileSlice],
offset: u64,
) -> std::io::Result<usize> {
let iovecs = $crate::file_traits::lib::VolatileSlice::as_iobufs(bufs);
if iovecs.is_empty() {
return Ok(0);
}
// Safe because only bytes inside the buffers are accessed and the kernel is
// expected to handle arbitrary memory for I/O.
let ret = unsafe {
$crate::file_traits::lib::preadv64(
self.as_raw_fd(),
iovecs.as_ptr(),
iovecs.len() as std::os::raw::c_int,
offset as $crate::file_traits::lib::off64_t,
)
};
if ret >= 0 {
Ok(ret as usize)
} else {
Err(std::io::Error::last_os_error())
}
}
fn write_at_volatile(
&mut self,
slice: $crate::file_traits::lib::VolatileSlice,
offset: u64,
) -> std::io::Result<usize> {
// Safe because only bytes inside the slice are accessed and the kernel is expected
// to handle arbitrary memory for I/O.
let ret = unsafe {
$crate::file_traits::lib::pwrite64(
self.as_raw_fd(),
slice.as_ptr() as *const std::ffi::c_void,
slice.size() as usize,
offset as $crate::file_traits::lib::off64_t,
)
};
if ret >= 0 {
Ok(ret as usize)
} else {
Err(std::io::Error::last_os_error())
}
}
fn write_vectored_at_volatile(
&mut self,
bufs: &[$crate::file_traits::lib::VolatileSlice],
offset: u64,
) -> std::io::Result<usize> {
let iovecs = $crate::file_traits::lib::VolatileSlice::as_iobufs(bufs);
if iovecs.is_empty() {
return Ok(0);
}
// Safe because only bytes inside the buffers are accessed and the kernel is
// expected to handle arbitrary memory for I/O.
let ret = unsafe {
$crate::file_traits::lib::pwritev64(
self.as_raw_fd(),
iovecs.as_ptr(),
iovecs.len() as std::os::raw::c_int,
offset as $crate::file_traits::lib::off64_t,
)
};
if ret >= 0 {
Ok(ret as usize)
} else {
Err(std::io::Error::last_os_error())
}
}
}
};
}
volatile_impl!(File);
volatile_at_impl!(File);
/// A trait similar to `AsRawFd` but supports an arbitrary number of file descriptors.
pub trait AsRawFds {
fn as_raw_fds(&self) -> Vec<RawFd>;
}
impl<T> AsRawFds for T
where
T: AsRawFd,
{
fn as_raw_fds(&self) -> Vec<RawFd> {
vec![self.as_raw_fd()]
}
}