mirror of
https://github.com/cloud-hypervisor/cloud-hypervisor.git
synced 2026-08-05 02:19:16 +00:00
block: aligned: Add seeking cursor and drop RawFile
RawFile wrapped AlignedFile only to add a seek position and the file trait impls that the qcow and vhost_user_block code expects. Fold that position and every impl onto AlignedFile so the wrapper layer goes away and callers work with a single O_DIRECT aware file type. AlignedFile now tracks a cursor and implements Read, Write, Seek, WriteZeroesAt, PunchHole, FileSync, SeekHole, BlockBackend, Clone, AsRawFd and AsFd in addition to the positional FileExt path. The direct_io flag is dropped because alignment already encodes it, where a zero alignment means the file was not opened with O_DIRECT. All RawFile uses in the qcow internals and vhost_user_block move to AlignedFile, and raw_file.rs is removed. Signed-off-by: Anatol Belski <anbelski@linux.microsoft.com>
This commit is contained in:
committed by
Rob Bradford
parent
4c7e2b83c1
commit
516f4e447d
@@ -2,13 +2,19 @@
|
||||
//
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::fs::File;
|
||||
use std::io;
|
||||
use std::fs::{File, Metadata};
|
||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||
use std::os::fd::{AsFd, BorrowedFd};
|
||||
use std::os::unix::fs::FileExt;
|
||||
use std::os::unix::io::AsRawFd;
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use std::result;
|
||||
|
||||
use vmm_sys_util::file_traits::FileSync;
|
||||
use vmm_sys_util::seek_hole::SeekHole;
|
||||
use vmm_sys_util::write_zeroes::{PunchHole, WriteZeroesAt};
|
||||
|
||||
use crate::aligned_buffer::AlignedBuffer;
|
||||
use crate::{SECTOR_SIZE, probe_direct_alignment};
|
||||
use crate::{BlockBackend, SECTOR_SIZE, probe_direct_alignment, query_device_size};
|
||||
|
||||
/// True when `buf_ptr`/`len`/`offset` already satisfy `alignment`
|
||||
/// (`alignment == 0` means no O_DIRECT, so everything is "aligned").
|
||||
@@ -25,9 +31,10 @@ fn is_aligned(alignment: usize, buf_ptr: usize, len: usize, offset: u64) -> bool
|
||||
/// For unaligned requests under O_DIRECT, I/O is bounced through an
|
||||
/// `AlignedBuffer` (read-modify-write for writes).
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct AlignedFile {
|
||||
pub struct AlignedFile {
|
||||
file: File,
|
||||
alignment: usize,
|
||||
position: u64,
|
||||
}
|
||||
|
||||
impl AlignedFile {
|
||||
@@ -38,7 +45,11 @@ impl AlignedFile {
|
||||
} else {
|
||||
0
|
||||
};
|
||||
AlignedFile { file, alignment }
|
||||
AlignedFile {
|
||||
file,
|
||||
alignment,
|
||||
position: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn alignment(&self) -> usize {
|
||||
@@ -57,14 +68,49 @@ impl AlignedFile {
|
||||
Ok(AlignedFile {
|
||||
file: self.file.try_clone()?,
|
||||
alignment: self.alignment,
|
||||
position: self.position,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_len(&self, size: u64) -> io::Result<()> {
|
||||
self.file.set_len(size)
|
||||
}
|
||||
|
||||
pub fn metadata(&self) -> io::Result<Metadata> {
|
||||
self.file.metadata()
|
||||
}
|
||||
|
||||
pub fn sync_all(&self) -> io::Result<()> {
|
||||
self.file.sync_all()
|
||||
}
|
||||
|
||||
pub fn sync_data(&self) -> io::Result<()> {
|
||||
self.file.sync_data()
|
||||
}
|
||||
|
||||
pub fn is_direct(&self) -> bool {
|
||||
self.alignment != 0
|
||||
}
|
||||
|
||||
pub fn is_writable(&self) -> bool {
|
||||
// SAFETY: fcntl with F_GETFL is safe and doesn't modify the file descriptor
|
||||
let flags = unsafe { libc::fcntl(self.file.as_raw_fd(), libc::F_GETFL) };
|
||||
if flags < 0 {
|
||||
return false;
|
||||
}
|
||||
let access_mode = flags & libc::O_ACCMODE;
|
||||
access_mode == libc::O_WRONLY || access_mode == libc::O_RDWR
|
||||
}
|
||||
|
||||
/// Wrap `file` with an explicit alignment, bypassing the probe. Used by
|
||||
/// tests to force the bounce/RMW path without a real O_DIRECT fd.
|
||||
#[cfg(test)]
|
||||
pub fn with_alignment(file: File, alignment: usize) -> Self {
|
||||
AlignedFile { file, alignment }
|
||||
AlignedFile {
|
||||
file,
|
||||
alignment,
|
||||
position: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,9 +143,123 @@ impl FileExt for AlignedFile {
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for AlignedFile {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
let n = self.read_at(buf, self.position)?;
|
||||
self.position += n as u64;
|
||||
Ok(n)
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for AlignedFile {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
let n = self.write_at(buf, self.position)?;
|
||||
self.position += n as u64;
|
||||
Ok(n)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.file.sync_all()
|
||||
}
|
||||
}
|
||||
|
||||
impl Seek for AlignedFile {
|
||||
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
|
||||
let newpos = match pos {
|
||||
SeekFrom::Start(o) => o,
|
||||
SeekFrom::Current(d) => self
|
||||
.position
|
||||
.checked_add_signed(d)
|
||||
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "invalid seek"))?,
|
||||
SeekFrom::End(d) => query_device_size(&self.file)?
|
||||
.0
|
||||
.checked_add_signed(d)
|
||||
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "invalid seek"))?,
|
||||
};
|
||||
self.position = newpos;
|
||||
Ok(newpos)
|
||||
}
|
||||
}
|
||||
|
||||
impl WriteZeroesAt for AlignedFile {
|
||||
fn write_zeroes_at(&mut self, offset: u64, length: usize) -> io::Result<usize> {
|
||||
self.file.write_zeroes_at(offset, length)
|
||||
}
|
||||
}
|
||||
|
||||
impl PunchHole for AlignedFile {
|
||||
fn punch_hole(&mut self, offset: u64, length: u64) -> io::Result<()> {
|
||||
self.file.punch_hole(offset, length)
|
||||
}
|
||||
}
|
||||
|
||||
impl FileSync for AlignedFile {
|
||||
fn fsync(&mut self) -> io::Result<()> {
|
||||
self.file.fsync()
|
||||
}
|
||||
}
|
||||
|
||||
impl SeekHole for AlignedFile {
|
||||
fn seek_hole(&mut self, offset: u64) -> io::Result<Option<u64>> {
|
||||
match self.file.seek_hole(offset) {
|
||||
Ok(pos) => {
|
||||
if let Some(p) = pos {
|
||||
self.position = p;
|
||||
}
|
||||
Ok(pos)
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
fn seek_data(&mut self, offset: u64) -> io::Result<Option<u64>> {
|
||||
match self.file.seek_data(offset) {
|
||||
Ok(pos) => {
|
||||
if let Some(p) = pos {
|
||||
self.position = p;
|
||||
}
|
||||
Ok(pos)
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockBackend for AlignedFile {
|
||||
fn logical_size(&self) -> result::Result<u64, crate::Error> {
|
||||
Ok(query_device_size(&self.file)
|
||||
.map_err(crate::Error::RawFileError)?
|
||||
.0)
|
||||
}
|
||||
|
||||
fn physical_size(&self) -> result::Result<u64, crate::Error> {
|
||||
Ok(query_device_size(&self.file)
|
||||
.map_err(crate::Error::RawFileError)?
|
||||
.1)
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for AlignedFile {
|
||||
fn clone(&self) -> Self {
|
||||
self.try_clone().expect("AlignedFile cloning failed")
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRawFd for AlignedFile {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.file.as_raw_fd()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsFd for AlignedFile {
|
||||
fn as_fd(&self) -> BorrowedFd<'_> {
|
||||
self.file.as_fd()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::io::Write;
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use std::os::unix::fs::FileExt;
|
||||
|
||||
use vmm_sys_util::tempfile::TempFile;
|
||||
@@ -115,14 +275,17 @@ mod tests {
|
||||
}
|
||||
|
||||
fn forced(file: File, alignment: usize) -> AlignedFile {
|
||||
AlignedFile { file, alignment }
|
||||
AlignedFile {
|
||||
file,
|
||||
alignment,
|
||||
position: 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_probes_alignment_and_accessors() {
|
||||
let tf = pattern_file(8192);
|
||||
// A tempfile is not O_DIRECT, so probe_direct_alignment reports
|
||||
// None and new() falls back to SECTOR_SIZE (512).
|
||||
// Not O_DIRECT, so new() falls back to SECTOR_SIZE (512).
|
||||
let mut af = AlignedFile::new(tf.as_file().try_clone().unwrap(), true);
|
||||
assert_eq!(af.alignment(), 512);
|
||||
let _ = af.file();
|
||||
@@ -184,4 +347,65 @@ mod tests {
|
||||
let mut buf = vec![0u8; 50];
|
||||
assert_eq!(af.read_at(&mut buf, 10).unwrap(), 50);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unaligned_read_returns_short_read_at_eof() {
|
||||
let file_size = 100usize;
|
||||
let tf = pattern_file(file_size);
|
||||
let mut af = forced(tf.as_file().try_clone().unwrap(), 512);
|
||||
af.seek(SeekFrom::Start(10)).unwrap();
|
||||
|
||||
let mut buf = vec![0u8; 200];
|
||||
let bytes_read = af.read(&mut buf).unwrap();
|
||||
|
||||
let expected: Vec<u8> = (10..file_size).map(|i| (i % 251) as u8).collect();
|
||||
assert_eq!(bytes_read, expected.len());
|
||||
assert_eq!(&buf[..bytes_read], &expected[..]);
|
||||
assert_eq!(af.position, file_size as u64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unaligned_read_beyond_eof_returns_zero() {
|
||||
let tf = pattern_file(100);
|
||||
let mut af = forced(tf.as_file().try_clone().unwrap(), 512);
|
||||
af.seek(SeekFrom::Start(200)).unwrap();
|
||||
|
||||
let mut buf = vec![0u8; 16];
|
||||
let bytes_read = af.read(&mut buf).unwrap();
|
||||
|
||||
assert_eq!(bytes_read, 0);
|
||||
assert_eq!(af.position, 200);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unaligned_write_extends_at_eof() {
|
||||
let file_size = 100usize;
|
||||
let tf = pattern_file(file_size);
|
||||
let mut af = forced(tf.as_file().try_clone().unwrap(), 512);
|
||||
af.seek(SeekFrom::Start(file_size as u64)).unwrap();
|
||||
|
||||
let data = b"xyz";
|
||||
let bytes_written = af.write(data).unwrap();
|
||||
|
||||
assert_eq!(bytes_written, data.len());
|
||||
assert_eq!(af.position, (file_size + data.len()) as u64);
|
||||
|
||||
let mut readback = vec![0u8; file_size + data.len()];
|
||||
tf.as_file().read_exact_at(&mut readback, 0).unwrap();
|
||||
let expected_prefix: Vec<u8> = (0..file_size).map(|i| (i % 251) as u8).collect();
|
||||
assert_eq!(&readback[..file_size], &expected_prefix[..]);
|
||||
assert_eq!(&readback[file_size..], data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_unaligned_io_is_noop() {
|
||||
let tf = pattern_file(100);
|
||||
let mut af = forced(tf.as_file().try_clone().unwrap(), 512);
|
||||
af.seek(SeekFrom::Start(1)).unwrap();
|
||||
|
||||
let mut read_buf = [];
|
||||
assert_eq!(af.read(&mut read_buf).unwrap(), 0);
|
||||
assert_eq!(af.write(&[]).unwrap(), 0);
|
||||
assert_eq!(af.position, 1);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user