use super::image::InodeId;
use super::{invalid, IoEvent, ModelCore, ObservationBoundary};
use std::io::{self, Seek, Write};
use std::path::{Path, PathBuf};
use std::sync::Arc;
#[derive(Debug, Clone)]
pub(crate) struct NativeTrace {
core: Arc<ModelCore>,
inode: InodeId,
append: bool,
opened_path: PathBuf,
}
impl NativeTrace {
pub(crate) fn mark_append(&mut self) {
self.append = true;
}
pub(super) fn from_directory(
core: Arc<ModelCore>,
inode: InodeId,
opened_path: PathBuf,
) -> Self {
Self {
core,
inode,
opened_path,
append: false,
}
}
pub(crate) fn open(
core: Arc<ModelCore>,
path: &Path,
options: &std::fs::OpenOptions,
mutable: [bool; 5],
native_options: bool,
) -> io::Result<(std::fs::File, Self)> {
if native_options {
return Err(invalid(
"custom native open flags are outside the captured fault model",
));
}
let relative = core.relative(path)?;
let mut engine = core.lock()?;
let existing = match engine.inode(&relative) {
Ok(inode) => Some(inode),
Err(error) if error.kind() == io::ErrorKind::NotFound => None,
Err(error) => return Err(error),
};
let native_exists = match std::fs::symlink_metadata(path) {
Ok(metadata) => {
super::identity::require_direct_entry(&metadata)?;
true
}
Err(error) if error.kind() == io::ErrorKind::NotFound => false,
Err(error) => return Err(error),
};
if existing.is_some() != native_exists {
return Err(invalid(
"native open encountered an uncaptured namespace change",
));
}
let mut candidate = engine.clone();
let inode = match existing {
Some(inode) => inode,
None if mutable[3] || mutable[4] => candidate.create_file(&relative)?,
None => {
return match options.open(path) {
Err(error) => Err(error),
Ok(_) => Err(invalid(
"native open created a file without a create option",
)),
};
}
};
if mutable[2] && !(mutable[4] && existing.is_some()) {
candidate.truncate(inode, 0)?;
}
let event = if existing.is_none() {
Some((IoEvent::CreateFile, relative.as_path()))
} else if mutable[2] && !mutable[4] {
Some((IoEvent::Truncate, relative.as_path()))
} else {
None
};
core.observe_at(&engine, event, ObservationBoundary::Before)?;
let file = options.open(path)?;
*engine = candidate;
core.observe_at(&engine, event, ObservationBoundary::After)?;
drop(engine);
Ok((
file,
Self {
core,
inode,
append: mutable[1],
opened_path: relative,
},
))
}
pub(crate) fn io<T>(&self, native: impl FnOnce() -> io::Result<T>) -> io::Result<T> {
let _engine = self.core.lock()?;
native()
}
pub(crate) fn sync(&self, native: impl FnOnce() -> io::Result<()>) -> io::Result<()> {
let event = if self.core.lock()?.is_directory(self.inode) {
IoEvent::DirectorySync
} else {
IoEvent::FileSync
};
self.core.mutate_observed(
Some((event, &self.opened_path)),
|engine| {
if engine.is_directory(self.inode) {
engine.sync_directory(self.inode)
} else {
engine.sync_file(self.inode)
}
},
native,
)
}
pub(crate) fn truncate(
&self,
length: u64,
native: impl FnOnce() -> io::Result<()>,
) -> io::Result<()> {
self.core.mutate_observed(
Some((IoEvent::Truncate, &self.opened_path)),
|engine| engine.truncate(self.inode, length).map(|_| ()),
native,
)
}
pub(crate) fn write(&self, file: &std::fs::File, bytes: &[u8]) -> io::Result<usize> {
self.write_with(file, bytes, || {
let mut handle = file;
handle.write(bytes)
})
}
pub(crate) fn write_vectored(
&self,
file: &std::fs::File,
buffers: &[io::IoSlice<'_>],
) -> io::Result<usize> {
let length = buffers
.iter()
.try_fold(0_usize, |length, buffer| length.checked_add(buffer.len()))
.ok_or_else(|| invalid("captured vectored-write length overflows"))?;
if length > self.core.lock()?.maximum_write_length() {
return Err(invalid(
"captured vectored-write byte admission limit exceeded",
));
}
let bytes: Vec<u8> = buffers
.iter()
.flat_map(|buffer| buffer.iter().copied())
.collect();
self.write_with(file, &bytes, || {
let mut handle = file;
handle.write_vectored(buffers)
})
}
fn write_with(
&self,
file: &std::fs::File,
bytes: &[u8],
native: impl FnOnce() -> io::Result<usize>,
) -> io::Result<usize> {
let mut engine = self.core.lock()?;
if bytes.is_empty() {
return native();
}
let offset = if self.append {
file.metadata()?.len()
} else {
let mut handle = file;
handle.stream_position()?
};
let mut candidate = engine.clone();
candidate.write(self.inode, offset, bytes)?;
let event = Some((IoEvent::Write, self.opened_path.as_path()));
self.core
.observe_at(&engine, event, ObservationBoundary::Before)?;
let written = native()?;
if written == bytes.len() {
*engine = candidate;
} else if written > 0 {
engine.write(self.inode, offset, &bytes[..written])?;
}
self.core
.observe_at(&engine, event, ObservationBoundary::After)?;
Ok(written)
}
}