use super::{
BlockRead, FileHint, Fs, FsCapabilities, FsDirEntry, FsFile, FsMetadata, FsOpenOptions,
QueuedRead, ReadDone, ReadQueue, ReadWake, SyncMode,
};
use crate::io;
use crate::path::{Path, PathBuf};
use alloc::boxed::Box;
use alloc::string::String;
use alloc::sync::Arc;
use alloc::vec::Vec;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum FaultOp {
Open,
CreateDirAll,
CreateDir,
RemoveFile,
Rename,
SyncDirectory,
Write,
Flush,
ReadAt,
Read,
SyncAll,
SyncData,
SetLen,
Metadata,
HardLinkCount,
HardLink,
AvailableSpace,
AllocatedSize,
PunchHole,
SameFile,
Reflink,
StartWriteback,
VolumeId,
RemoveDirAll,
RemoveDir,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[non_exhaustive]
pub enum SyncKind {
File,
FileData,
Directory,
Device,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Fault {
Error(io::ErrorKind),
ShortWrite(usize),
}
#[derive(Clone, Debug)]
pub struct FaultRule {
op: FaultOp,
path_substr: Option<String>,
offset: Option<u64>,
skip: u64,
count: u64,
fault: Fault,
}
impl FaultRule {
#[must_use]
pub const fn new(op: FaultOp, fault: Fault) -> Self {
Self {
op,
path_substr: None,
offset: None,
skip: 0,
count: u64::MAX,
fault,
}
}
#[must_use]
pub fn on_path(mut self, substr: impl Into<String>) -> Self {
self.path_substr = Some(substr.into());
self
}
#[must_use]
pub fn at_offset(mut self, offset: u64) -> Self {
debug_assert!(
matches!(self.op, FaultOp::ReadAt),
"at_offset only applies to FaultOp::ReadAt; setting it on {:?} makes the rule \
unmatchable",
self.op,
);
self.offset = Some(offset);
self
}
#[must_use]
pub const fn skip(mut self, n: u64) -> Self {
self.skip = n;
self
}
#[must_use]
pub const fn times(mut self, n: u64) -> Self {
self.count = n;
self
}
#[must_use]
pub const fn once(self) -> Self {
self.times(1)
}
fn matches(&self, op: FaultOp, path: Option<&Path>) -> bool {
if self.op != op || self.offset.is_some() {
return false;
}
self.matches_path(path)
}
fn matches_read_at(&self, path: Option<&Path>, offset: u64) -> bool {
if self.op != FaultOp::ReadAt {
return false;
}
if self.offset.is_some_and(|want| want != offset) {
return false;
}
self.matches_path(path)
}
fn matches_path(&self, path: Option<&Path>) -> bool {
match (&self.path_substr, path) {
(None, _) => true,
(Some(sub), Some(p)) => p.to_string_lossy().contains(sub.as_str()),
(Some(_), None) => false,
}
}
}
#[derive(Default)]
pub struct FaultInjector {
rules: spin::Mutex<Vec<FaultRule>>,
sync_log: spin::Mutex<Vec<(PathBuf, SyncMode)>>,
directory_sync_log: spin::Mutex<Vec<(PathBuf, SyncMode)>>,
syncs: spin::Mutex<Vec<(PathBuf, SyncKind)>>,
writeback_log: spin::Mutex<Vec<(PathBuf, u64, u64)>>,
reads: core::sync::atomic::AtomicUsize,
generation: core::sync::atomic::AtomicU64,
opens: core::sync::atomic::AtomicUsize,
open_handles: spin::Mutex<Vec<OpenHandle>>,
next_handle: core::sync::atomic::AtomicU64,
refuse_open_removal: core::sync::atomic::AtomicBool,
#[cfg(test)]
read_hooks: spin::Mutex<Vec<(String, u64, ReadHook)>>,
}
#[cfg(test)]
pub type ReadHook = Arc<dyn Fn() + Send + Sync>;
std::thread_local! {
static LENT: core::cell::RefCell<Vec<usize>> = const { core::cell::RefCell::new(Vec::new()) };
static PASSING: core::cell::Cell<u32> = const { core::cell::Cell::new(0) };
}
fn loan_key(injector: &FaultInjector) -> usize {
core::ptr::from_ref(injector) as usize
}
fn lend_descriptors<R>(injector: &FaultInjector, f: impl FnOnce() -> R) -> R {
struct Loan(usize);
impl Drop for Loan {
fn drop(&mut self) {
LENT.with(|lent| {
let mut lent = lent.borrow_mut();
if let Some(at) = lent.iter().rposition(|key| *key == self.0) {
lent.remove(at);
}
});
}
}
let key = loan_key(injector);
LENT.with(|lent| lent.borrow_mut().push(key));
let _loan = Loan(key);
f()
}
fn pass_through<R>(f: impl FnOnce() -> R) -> R {
struct Pass;
impl Drop for Pass {
fn drop(&mut self) {
PASSING.with(|depth| depth.set(depth.get() - 1));
}
}
PASSING.with(|depth| depth.set(depth.get() + 1));
let _pass = Pass;
f()
}
fn descriptors_lent(injector: &FaultInjector) -> bool {
PASSING.with(|depth| depth.get() > 0)
|| LENT.with(|lent| lent.borrow().contains(&loan_key(injector)))
}
struct OpenHandle {
id: u64,
fd: Option<i32>,
opened: PathBuf,
current: PathBuf,
}
impl FaultInjector {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[cfg(test)]
pub(crate) fn refuse_removing_open_files(&self) {
self.refuse_open_removal
.store(true, core::sync::atomic::Ordering::Relaxed);
}
fn removal_refused(&self, path: &Path) -> bool {
self.refuse_open_removal
.load(core::sync::atomic::Ordering::Relaxed)
&& self
.open_handles
.lock()
.iter()
.any(|open| open.current == path)
}
fn note_opened(&self, path: &Path, fd: Option<i32>) -> u64 {
let id = self
.next_handle
.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
self.open_handles.lock().push(OpenHandle {
id,
fd,
opened: path.to_path_buf(),
current: path.to_path_buf(),
});
id
}
fn note_closed(&self, handle: u64) {
let mut open = self.open_handles.lock();
if let Some(at) = open.iter().position(|open| open.id == handle) {
open.swap_remove(at);
}
}
fn note_renamed(&self, from: &Path, to: &Path) {
for open in self.open_handles.lock().iter_mut() {
if open.current == from {
open.current = to.to_path_buf();
}
}
}
fn path_of(&self, fd: i32) -> Option<PathBuf> {
self.open_handles
.lock()
.iter()
.find(|open| open.fd == Some(fd))
.map(|open| open.opened.clone())
}
fn screen_read(&self, path: Option<&Path>, offset: u64) -> Option<io::Error> {
self.count_read();
#[cfg(test)]
if let Some(path) = path {
self.run_read_hooks(path, offset);
}
match self.check_read_at(path, offset) {
Some(Fault::Error(kind)) => Some(fault_error(kind, FaultOp::ReadAt)),
_ => None,
}
}
fn screen_by_fd(
&self,
file: &dyn FsFile,
offset: u64,
len: usize,
) -> (Option<io::Error>, Screened) {
let fd = if len == 0 { None } else { file.backing_fd() };
let Some(fd) = fd else {
return (None, Screened::Not);
};
let path = self.path_of(fd);
#[cfg(test)]
if let Some(path) = &path {
self.run_read_hooks(path, offset);
}
let (fault, rule, generation) = self.check_read_at_counted(path.as_deref(), offset);
let error = match fault {
Some(Fault::Error(kind)) => Some(fault_error(kind, FaultOp::ReadAt)),
_ => None,
};
(error, Screened::Counted { rule, generation })
}
fn screen_batch(&self, reqs: &[BlockRead<'_>]) -> (usize, Option<io::Error>, Vec<Screened>) {
let resume = |req: &BlockRead<'_>| req.offset.checked_add(req.buf.filled() as u64);
let mut spent = Vec::with_capacity(reqs.len());
for (at, req) in reqs.iter().enumerate() {
let offset = resume(req).unwrap_or(req.offset);
let len = req.buf.capacity() - req.buf.filled();
let fd = if len == 0 {
None
} else {
req.file.backing_fd()
};
let Some(fd) = fd else {
spent.push(Screened::Not);
continue;
};
let path = self.path_of(fd);
#[cfg(test)]
if let Some(path) = &path {
self.run_read_hooks(path, offset);
}
let (fault, rule, generation) = self.check_read_at_counted(path.as_deref(), offset);
spent.push(Screened::Counted { rule, generation });
if let Some(Fault::Error(kind)) = fault {
return (at, Some(fault_error(kind, FaultOp::ReadAt)), spent);
}
}
(reqs.len(), None, spent)
}
fn refund(&self, spent: &[Screened], unread: impl IntoIterator<Item = usize>) {
let mut rules = self.rules.lock();
let now = self.generation.load(core::sync::atomic::Ordering::Relaxed);
for at in unread {
let Some(Screened::Counted { rule, generation }) = spent.get(at) else {
continue;
};
if *generation != now {
continue;
}
self.reads
.fetch_sub(1, core::sync::atomic::Ordering::Relaxed);
match rule {
Some((index, Spent::Skip)) => {
if let Some(rule) = rules.get_mut(*index) {
rule.skip += 1;
}
}
Some((index, Spent::Count)) => {
if let Some(rule) = rules.get_mut(*index) {
rule.count += 1;
}
}
Some((_, Spent::Nothing)) | None => {}
}
}
}
pub fn arm(&self, rule: FaultRule) {
self.rules.lock().push(rule);
}
#[cfg(test)]
pub(crate) fn hook_read_at(&self, path: impl Into<String>, offset: u64, hook: ReadHook) {
self.read_hooks.lock().push((path.into(), offset, hook));
}
#[cfg(test)]
fn run_read_hooks(&self, path: &Path, offset: u64) {
let path = path.to_string_lossy();
let hooks: Vec<ReadHook> = self
.read_hooks
.lock()
.iter()
.filter(|(within, at, _)| *at == offset && path.contains(within.as_str()))
.map(|(_, _, hook)| Arc::clone(hook))
.collect();
for hook in hooks {
hook();
}
}
pub(crate) fn count_read(&self) {
self.reads
.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
#[must_use]
pub fn read_count(&self) -> usize {
self.reads.load(core::sync::atomic::Ordering::Relaxed)
}
pub(crate) fn count_open(&self) {
self.opens
.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
}
#[must_use]
pub fn open_count(&self) -> usize {
self.opens.load(core::sync::atomic::Ordering::Relaxed)
}
pub fn clear(&self) {
{
let mut rules = self.rules.lock();
rules.clear();
self.generation
.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
self.reads.store(0, core::sync::atomic::Ordering::Relaxed);
}
self.sync_log.lock().clear();
self.directory_sync_log.lock().clear();
self.syncs.lock().clear();
self.writeback_log.lock().clear();
#[cfg(test)]
self.read_hooks.lock().clear();
self.opens.store(0, core::sync::atomic::Ordering::Relaxed);
}
fn note_sync(&self, path: &Path, kind: SyncKind) {
self.syncs.lock().push((path.to_path_buf(), kind));
}
#[must_use]
pub fn take_syncs(&self) -> Vec<(PathBuf, SyncKind)> {
core::mem::take(&mut *self.syncs.lock())
}
fn record_sync(&self, path: &Path, mode: SyncMode) {
self.sync_log.lock().push((path.to_path_buf(), mode));
}
#[must_use]
pub fn sync_modes_for(&self, substr: &str) -> Vec<SyncMode> {
self.sync_log
.lock()
.iter()
.filter(|(p, _)| p.to_string_lossy().contains(substr))
.map(|(_, m)| *m)
.collect()
}
#[must_use]
pub fn directory_sync_modes_for(&self, substr: &str) -> Vec<SyncMode> {
self.directory_sync_log
.lock()
.iter()
.filter(|(p, _)| p.to_string_lossy().contains(substr))
.map(|(_, m)| *m)
.collect()
}
#[must_use]
pub fn writebacks_for(&self, substr: &str) -> Vec<(u64, u64)> {
self.writeback_log
.lock()
.iter()
.filter(|(p, ..)| p.to_string_lossy().contains(substr))
.map(|&(_, offset, len)| (offset, len))
.collect()
}
fn check(&self, op: FaultOp, path: Option<&Path>) -> Option<Fault> {
let mut rules = self.rules.lock();
for rule in rules.iter_mut() {
if !rule.matches(op, path) {
continue;
}
return Self::fire(rule);
}
None
}
fn check_read_at(&self, path: Option<&Path>, offset: u64) -> Option<Fault> {
let mut rules = self.rules.lock();
for rule in rules.iter_mut() {
if !rule.matches_read_at(path, offset) {
continue;
}
return Self::fire(rule);
}
None
}
fn check_read_at_counted(
&self,
path: Option<&Path>,
offset: u64,
) -> (Option<Fault>, Option<(usize, Spent)>, u64) {
let mut rules = self.rules.lock();
self.count_read();
let generation = self.generation.load(core::sync::atomic::Ordering::Relaxed);
for (index, rule) in rules.iter_mut().enumerate() {
if !rule.matches_read_at(path, offset) {
continue;
}
let (fault, spent) = Self::fire_traced(rule);
return (fault, Some((index, spent)), generation);
}
(None, None, generation)
}
fn fire(rule: &mut FaultRule) -> Option<Fault> {
Self::fire_traced(rule).0
}
fn fire_traced(rule: &mut FaultRule) -> (Option<Fault>, Spent) {
if rule.skip > 0 {
rule.skip -= 1;
return (None, Spent::Skip);
}
if rule.count == 0 {
return (None, Spent::Nothing);
}
if rule.count == u64::MAX {
return (Some(rule.fault), Spent::Nothing);
}
rule.count -= 1;
(Some(rule.fault), Spent::Count)
}
}
type OnRead<'f> = &'f mut dyn for<'a, 'b> FnMut(usize, &'a BlockRead<'b>);
#[derive(Clone, Copy)]
enum Spent {
Skip,
Count,
Nothing,
}
enum Screened {
Not,
Counted {
rule: Option<(usize, Spent)>,
generation: u64,
},
}
fn fault_error(kind: io::ErrorKind, op: FaultOp) -> io::Error {
io::Error::new(kind, alloc::format!("injected fault on {op:?}"))
}
fn fault_error_std(kind: io::ErrorKind, op: FaultOp) -> std::io::Error {
fault_error(kind, op).into()
}
pub struct FaultFs<F> {
inner: F,
injector: Arc<FaultInjector>,
}
impl<F: Fs> FaultFs<F> {
#[must_use]
pub fn new(inner: F) -> Self {
Self {
inner,
injector: Arc::new(FaultInjector::new()),
}
}
#[must_use]
pub fn with_injector(inner: F, injector: Arc<FaultInjector>) -> Self {
Self { inner, injector }
}
#[must_use]
pub fn injector(&self) -> Arc<FaultInjector> {
Arc::clone(&self.injector)
}
fn read_screened(
&self,
reqs: &mut [BlockRead<'_>],
on_read: Option<OnRead<'_>>,
) -> io::Result<()> {
lend_descriptors(&self.injector, || {
if self.inner.check_descriptor_batch(reqs).is_err() {
return match on_read {
Some(on_read) => self.inner.read_blocks_batched_each(reqs, on_read),
None => self.inner.read_blocks_batched(reqs),
};
}
let (clean, failure, spent) = self.injector.screen_batch(reqs);
let (head, _) = reqs.split_at_mut(clean);
let result = match on_read {
Some(on_read) => self.inner.read_blocks_batched_each(head, on_read),
None => self.inner.read_blocks_batched(head),
};
if let Err(e) = result {
if failure.is_some() {
self.injector.refund(&spent, [clean]);
}
return Err(e);
}
failure.map_or(Ok(()), Err)
})
}
}
impl<F: Fs> Fs for FaultFs<F> {
fn open(&self, path: &Path, opts: &FsOpenOptions) -> io::Result<Box<dyn FsFile>> {
self.injector.count_open();
if let Some(Fault::Error(kind)) = self.injector.check(FaultOp::Open, Some(path)) {
return Err(fault_error(kind, FaultOp::Open));
}
let inner = self.inner.open(path, opts)?;
let fd = pass_through(|| inner.backing_fd());
let handle = self.injector.note_opened(path, fd);
Ok(Box::new(FaultFile {
inner,
path: path.to_path_buf(),
handle,
injector: Arc::clone(&self.injector),
}))
}
fn read_blocks_batched(&self, reqs: &mut [BlockRead<'_>]) -> io::Result<()> {
if !self.inner.reads_by_descriptor() {
return self.inner.read_blocks_batched(reqs);
}
self.read_screened(reqs, None)
}
fn read_blocks_batched_each(
&self,
reqs: &mut [BlockRead<'_>],
on_read: &mut dyn FnMut(usize, &BlockRead<'_>),
) -> io::Result<()> {
if !self.inner.reads_by_descriptor() {
return self.inner.read_blocks_batched_each(reqs, on_read);
}
self.read_screened(reqs, Some(on_read))
}
fn check_descriptor_batch(&self, reqs: &[BlockRead<'_>]) -> io::Result<()> {
lend_descriptors(&self.injector, || self.inner.check_descriptor_batch(reqs))
}
fn read_queue(&self) -> Box<dyn ReadQueue + '_> {
if !self.inner.reads_by_descriptor() {
return self.inner.read_queue();
}
Box::new(FaultReadQueue {
inner: self.inner.read_queue(),
injector: &self.injector,
submitted: Vec::new(),
failed: Vec::new(),
screened: Vec::new(),
})
}
fn reads_by_descriptor(&self) -> bool {
self.inner.reads_by_descriptor()
}
fn create_dir_all(&self, path: &Path) -> io::Result<()> {
if let Some(Fault::Error(kind)) = self.injector.check(FaultOp::CreateDirAll, Some(path)) {
return Err(fault_error(kind, FaultOp::CreateDirAll));
}
self.inner.create_dir_all(path)
}
fn create_dir(&self, path: &Path) -> io::Result<()> {
if let Some(Fault::Error(kind)) = self.injector.check(FaultOp::CreateDir, Some(path)) {
return Err(fault_error(kind, FaultOp::CreateDir));
}
self.inner.create_dir(path)
}
fn read_dir(&self, path: &Path) -> io::Result<Vec<FsDirEntry>> {
self.inner.read_dir(path)
}
fn remove_file(&self, path: &Path) -> io::Result<()> {
if let Some(Fault::Error(kind)) = self.injector.check(FaultOp::RemoveFile, Some(path)) {
return Err(fault_error(kind, FaultOp::RemoveFile));
}
if self.injector.removal_refused(path) {
return Err(io::Error::new(
io::ErrorKind::PermissionDenied,
"removal of a file with an open handle",
));
}
self.inner.remove_file(path)
}
fn remove_dir_all(&self, path: &Path) -> io::Result<()> {
if let Some(Fault::Error(kind)) = self.injector.check(FaultOp::RemoveDirAll, Some(path)) {
return Err(fault_error(kind, FaultOp::RemoveDirAll));
}
self.inner.remove_dir_all(path)
}
fn remove_dir(&self, path: &Path) -> io::Result<()> {
if let Some(Fault::Error(kind)) = self.injector.check(FaultOp::RemoveDir, Some(path)) {
return Err(fault_error(kind, FaultOp::RemoveDir));
}
self.inner.remove_dir(path)
}
fn same_file(&self, a: &Path, b: &Path) -> io::Result<bool> {
if let Some(Fault::Error(kind)) = self.injector.check(FaultOp::SameFile, Some(a)) {
return Err(fault_error(kind, FaultOp::SameFile));
}
self.inner.same_file(a, b)
}
fn read_link(&self, path: &Path) -> io::Result<Option<PathBuf>> {
if let Some(Fault::Error(kind)) = self.injector.check(FaultOp::Metadata, Some(path)) {
return Err(fault_error(kind, FaultOp::Metadata));
}
self.inner.read_link(path)
}
fn symlink(&self, target: &Path, link: &Path, to_dir: bool) -> io::Result<()> {
self.inner.symlink(target, link, to_dir)
}
fn symlink_to_dir(&self, path: &Path) -> io::Result<bool> {
self.inner.symlink_to_dir(path)
}
fn rename(&self, from: &Path, to: &Path) -> io::Result<()> {
if let Some(Fault::Error(kind)) = self.injector.check(FaultOp::Rename, Some(to)) {
return Err(fault_error(kind, FaultOp::Rename));
}
self.inner.rename(from, to)?;
self.injector.note_renamed(from, to);
Ok(())
}
fn metadata(&self, path: &Path) -> io::Result<FsMetadata> {
if let Some(Fault::Error(kind)) = self.injector.check(FaultOp::Metadata, Some(path)) {
return Err(fault_error(kind, FaultOp::Metadata));
}
self.inner.metadata(path)
}
fn sync_directory(&self, path: &Path) -> io::Result<()> {
self.injector.note_sync(path, SyncKind::Directory);
if let Some(Fault::Error(kind)) = self.injector.check(FaultOp::SyncDirectory, Some(path)) {
return Err(fault_error(kind, FaultOp::SyncDirectory));
}
self.inner.sync_directory(path)
}
fn sync_directory_with(&self, path: &Path, mode: SyncMode) -> io::Result<()> {
self.injector.note_sync(path, SyncKind::Directory);
self.injector
.directory_sync_log
.lock()
.push((path.to_path_buf(), mode));
if let Some(Fault::Error(kind)) = self.injector.check(FaultOp::SyncDirectory, Some(path)) {
return Err(fault_error(kind, FaultOp::SyncDirectory));
}
self.inner.sync_directory_with(path, mode)
}
fn sync_device(&self, path: &Path) -> io::Result<()> {
self.injector.note_sync(path, SyncKind::Device);
self.inner.sync_device(path)
}
fn exists(&self, path: &Path) -> io::Result<bool> {
if let Some(Fault::Error(_)) = self.injector.check(FaultOp::Metadata, Some(path)) {
return Ok(false);
}
self.inner.exists(path)
}
fn hard_link(&self, src: &Path, dst: &Path) -> io::Result<()> {
if let Some(Fault::Error(kind)) = self.injector.check(FaultOp::HardLink, Some(dst)) {
return Err(fault_error(kind, FaultOp::HardLink));
}
self.inner.hard_link(src, dst)
}
fn backend_id(&self) -> Option<u64> {
self.inner.backend_id()
}
fn volume_id(&self, path: &Path) -> Option<u64> {
if self.injector.check(FaultOp::VolumeId, Some(path)).is_some() {
return None;
}
self.inner.volume_id(path)
}
fn capabilities(&self, path: &Path) -> FsCapabilities {
self.inner.capabilities(path)
}
fn try_disable_cow(&self, path: &Path) -> io::Result<()> {
self.inner.try_disable_cow(path)
}
fn punch_hole(&self, path: &Path, offset: u64, len: u64) -> io::Result<()> {
if let Some(Fault::Error(kind)) = self.injector.check(FaultOp::PunchHole, Some(path)) {
return Err(fault_error(kind, FaultOp::PunchHole));
}
self.inner.punch_hole(path, offset, len)
}
fn reflink_file(&self, src: &Path, dst: &Path) -> io::Result<()> {
if let Some(Fault::Error(kind)) = self.injector.check(FaultOp::Reflink, Some(dst)) {
return Err(fault_error(kind, FaultOp::Reflink));
}
self.inner.reflink_file(src, dst)
}
fn truncate_file(&self, path: &Path) -> io::Result<()> {
self.inner.truncate_file(path)
}
fn hard_link_count(&self, path: &Path) -> io::Result<u64> {
if let Some(Fault::Error(kind)) = self.injector.check(FaultOp::HardLinkCount, Some(path)) {
return Err(fault_error(kind, FaultOp::HardLinkCount));
}
self.inner.hard_link_count(path)
}
fn available_space(&self, path: &Path) -> io::Result<u64> {
if let Some(Fault::Error(kind)) = self.injector.check(FaultOp::AvailableSpace, Some(path)) {
return Err(fault_error(kind, FaultOp::AvailableSpace));
}
self.inner.available_space(path)
}
fn allocated_size(&self, path: &Path) -> io::Result<Option<u64>> {
if let Some(Fault::Error(kind)) = self.injector.check(FaultOp::AllocatedSize, Some(path)) {
return Err(fault_error(kind, FaultOp::AllocatedSize));
}
self.inner.allocated_size(path)
}
fn extent_is_hole(&self, path: &Path, offset: u64, len: u64) -> io::Result<Option<bool>> {
if let Some(Fault::Error(kind)) = self.injector.check(FaultOp::AllocatedSize, Some(path)) {
return Err(fault_error(kind, FaultOp::AllocatedSize));
}
self.inner.extent_is_hole(path, offset, len)
}
fn extent_contains_hole(&self, path: &Path, offset: u64, len: u64) -> io::Result<Option<bool>> {
if let Some(Fault::Error(kind)) = self.injector.check(FaultOp::AllocatedSize, Some(path)) {
return Err(fault_error(kind, FaultOp::AllocatedSize));
}
self.inner.extent_contains_hole(path, offset, len)
}
}
struct FaultReadQueue<'a> {
inner: Box<dyn ReadQueue + 'a>,
injector: &'a FaultInjector,
submitted: Vec<QueuedRead>,
failed: Vec<ReadDone>,
screened: Vec<(usize, Screened)>,
}
impl ReadQueue for FaultReadQueue<'_> {
fn submit(&mut self, read: QueuedRead) {
self.submitted.push(read);
}
fn outstanding(&self) -> usize {
self.inner.outstanding() + self.submitted.len() + self.failed.len()
}
fn held(&self) -> usize {
self.inner.held() + self.submitted.len() + self.failed.len()
}
fn issues_at_wait_for_none(&self) -> bool {
self.inner.issues_at_wait_for_none()
}
fn take_kept(&mut self) -> Vec<QueuedRead> {
core::mem::take(&mut self.submitted)
}
fn wait(&mut self, min: usize, on_done: &mut dyn FnMut(ReadDone)) {
let injector = self.injector;
lend_descriptors(injector, || self.wait_lent(min, on_done));
}
fn set_wake(&mut self, wake: Arc<dyn ReadWake>) -> bool {
self.inner.set_wake(wake)
}
}
impl FaultReadQueue<'_> {
fn wait_lent(&mut self, min: usize, on_done: &mut dyn FnMut(ReadDone)) {
if min == 0 && !self.inner.issues_at_wait_for_none() {
self.inner.wait(0, on_done);
return;
}
let mut sent = 0usize;
for read in core::mem::take(&mut self.submitted) {
let len = read.buf.len();
let (error, spent) = self
.injector
.screen_by_fd(read.file.as_ref(), read.offset, len);
if let Some(error) = error {
self.failed.push(ReadDone {
tag: read.tag,
buf: read.buf,
result: Err(error),
});
} else {
self.screened.push((read.buf.as_ptr() as usize, spent));
self.inner.submit(read);
sent += 1;
}
}
let handed = self.failed.len();
for done in self.failed.drain(..) {
on_done(done);
}
let mut inner_min = min.saturating_sub(handed);
if inner_min == 0 && sent > 0 && !self.inner.issues_at_wait_for_none() {
inner_min = 1;
}
self.inner.wait(inner_min, on_done);
for read in self.inner.take_kept() {
let key = read.buf.as_ptr() as usize;
if let Some(at) = self.screened.iter().position(|(held, _)| *held == key) {
let (_, spent) = self.screened.swap_remove(at);
self.injector.refund(core::slice::from_ref(&spent), [0]);
}
self.submitted.push(read);
}
self.screened.clear();
}
}
struct FaultFile {
inner: Box<dyn FsFile>,
path: PathBuf,
handle: u64,
injector: Arc<FaultInjector>,
}
impl Drop for FaultFile {
fn drop(&mut self) {
self.injector.note_closed(self.handle);
}
}
impl std::io::Read for FaultFile {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
if let Some(Fault::Error(kind)) = self.injector.check(FaultOp::Read, Some(&self.path)) {
return Err(fault_error_std(kind, FaultOp::Read));
}
self.inner.read(buf)
}
}
impl std::io::Write for FaultFile {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
match self.injector.check(FaultOp::Write, Some(&self.path)) {
Some(Fault::Error(kind)) => Err(fault_error_std(kind, FaultOp::Write)),
Some(Fault::ShortWrite(n)) => {
let take = n.min(buf.len());
if take == 0 {
return Ok(0);
}
let (head, _) = buf.split_at(take);
self.inner.write_all(head)?;
Ok(take)
}
None => self.inner.write(buf),
}
}
fn flush(&mut self) -> std::io::Result<()> {
if let Some(Fault::Error(kind)) = self.injector.check(FaultOp::Flush, Some(&self.path)) {
return Err(fault_error_std(kind, FaultOp::Flush));
}
self.inner.flush()
}
}
impl std::io::Seek for FaultFile {
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
self.inner.seek(pos)
}
}
impl FsFile for FaultFile {
fn sync_all(&self) -> io::Result<()> {
self.injector.note_sync(&self.path, SyncKind::File);
if let Some(Fault::Error(kind)) = self.injector.check(FaultOp::SyncAll, Some(&self.path)) {
return Err(fault_error(kind, FaultOp::SyncAll));
}
self.inner.sync_all()
}
fn sync_data(&self) -> io::Result<()> {
self.injector.note_sync(&self.path, SyncKind::FileData);
if let Some(Fault::Error(kind)) = self.injector.check(FaultOp::SyncData, Some(&self.path)) {
return Err(fault_error(kind, FaultOp::SyncData));
}
self.inner.sync_data()
}
fn sync_all_with(&self, mode: SyncMode) -> io::Result<()> {
self.injector.note_sync(&self.path, SyncKind::File);
self.injector.record_sync(&self.path, mode);
if let Some(Fault::Error(kind)) = self.injector.check(FaultOp::SyncAll, Some(&self.path)) {
return Err(fault_error(kind, FaultOp::SyncAll));
}
self.inner.sync_all_with(mode)
}
fn sync_data_with(&self, mode: SyncMode) -> io::Result<()> {
self.injector.note_sync(&self.path, SyncKind::FileData);
self.injector.record_sync(&self.path, mode);
if let Some(Fault::Error(kind)) = self.injector.check(FaultOp::SyncData, Some(&self.path)) {
return Err(fault_error(kind, FaultOp::SyncData));
}
self.inner.sync_data_with(mode)
}
fn metadata(&self) -> io::Result<FsMetadata> {
if let Some(Fault::Error(kind)) = self.injector.check(FaultOp::Metadata, Some(&self.path)) {
return Err(fault_error(kind, FaultOp::Metadata));
}
self.inner.metadata()
}
fn hard_link_count(&self) -> io::Result<u64> {
if let Some(Fault::Error(kind)) = self
.injector
.check(FaultOp::HardLinkCount, Some(&self.path))
{
return Err(fault_error(kind, FaultOp::HardLinkCount));
}
self.inner.hard_link_count()
}
fn set_len(&self, size: u64) -> io::Result<()> {
if let Some(Fault::Error(kind)) = self.injector.check(FaultOp::SetLen, Some(&self.path)) {
return Err(fault_error(kind, FaultOp::SetLen));
}
self.inner.set_len(size)
}
fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
if let Some(error) = self.injector.screen_read(Some(&self.path), offset) {
return Err(error);
}
self.inner.read_at(buf, offset)
}
fn backing_fd(&self) -> Option<i32> {
if descriptors_lent(&self.injector) {
pass_through(|| self.inner.backing_fd())
} else {
None
}
}
fn lock_exclusive(&self) -> io::Result<()> {
self.inner.lock_exclusive()
}
fn try_lock_exclusive(&self) -> io::Result<bool> {
self.inner.try_lock_exclusive()
}
fn hint(&self, hint: FileHint) -> io::Result<()> {
self.inner.hint(hint)
}
fn set_write_lifetime(&self, lifetime: super::WriteLifetime) -> io::Result<()> {
self.inner.set_write_lifetime(lifetime)
}
fn start_writeback(&self, offset: u64, len: u64) -> io::Result<()> {
self.injector
.writeback_log
.lock()
.push((self.path.clone(), offset, len));
if let Some(Fault::Error(kind)) = self
.injector
.check(FaultOp::StartWriteback, Some(&self.path))
{
return Err(fault_error(kind, FaultOp::StartWriteback));
}
self.inner.start_writeback(offset, len)
}
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::indexing_slicing,
reason = "test code"
)]
mod tests;