use super::{
FileHint, Fs, FsCapabilities, FsDirEntry, FsFile, FsMetadata, FsOpenOptions, SyncMode,
};
use crate::io;
use crate::path::{Path, PathBuf};
use alloc::boxed::Box;
use alloc::sync::Arc;
use alloc::vec::Vec;
use hashbrown::{HashMap, HashSet};
use std::path::Component;
#[derive(Default)]
struct CrashState {
durable: HashMap<PathBuf, Vec<u8>>,
touched: HashSet<PathBuf>,
pending_entries: HashSet<PathBuf>,
link_group: HashMap<PathBuf, u64>,
next_link_group: u64,
links: HashMap<PathBuf, Link>,
unlinked: HashMap<PathBuf, u64>,
ordered: Vec<Ordered>,
}
#[derive(Clone)]
struct Link {
target: PathBuf,
to_dir: bool,
}
enum Ordered {
Image(PathBuf, Vec<u8>),
Entries(Vec<(PathBuf, bool)>),
Removed(PathBuf, bool),
Renamed {
from: PathBuf,
to: PathBuf,
from_pending: bool,
to_pending: bool,
bytes: Option<Vec<u8>>,
},
}
impl CrashState {
fn settle(&mut self, count: usize) {
let ordered = core::mem::take(&mut self.ordered);
let renamed_durable: Vec<PathBuf> = ordered
.iter()
.enumerate()
.take(count)
.filter_map(|(at, sync)| match sync {
Ordered::Renamed { to, .. } => Some((at, to)),
_ => None,
})
.filter(|&(at, to)| {
!ordered
.iter()
.take(count)
.skip(at + 1)
.any(|later| match later {
Ordered::Removed(name, _) => name == to,
Ordered::Renamed {
from: source,
to: target,
..
} => source == to || target == to,
_ => false,
})
})
.map(|(_, to)| to.clone())
.collect();
for to in &renamed_durable {
self.pending_entries.remove(to);
}
let mut ordered = ordered.into_iter();
for sync in ordered.by_ref().take(count) {
match sync {
Ordered::Image(path, bytes) => self.set_durable(&path, bytes),
Ordered::Entries(entries) => {
for (entry, file) in &entries {
self.entry_made_durable(entry, *file);
}
}
Ordered::Removed(path, _) => {
self.durable.remove(&path);
self.links.remove(&path);
self.unlinked.remove(&path);
}
Ordered::Renamed {
from, to, bytes, ..
} => {
self.durable.remove(&to);
self.durable.remove(&from);
self.links.remove(&to);
let link = self.links.remove(&from);
if let Some(bytes) = bytes {
self.set_durable(&to, bytes);
} else if let Some(target) = link {
self.links.insert(to, target);
}
}
}
}
for sync in ordered {
match sync {
Ordered::Removed(path, false) => {
self.pending_entries.remove(&path);
}
Ordered::Renamed {
from,
to,
from_pending,
to_pending,
..
} => {
if !from_pending {
self.pending_entries.remove(&from);
}
if !to_pending {
self.pending_entries.remove(&to);
}
}
_ => {}
}
}
}
fn settle_all(&mut self) {
self.settle(self.ordered.len());
}
fn entry_made_durable(&mut self, path: &Path, file: bool) {
self.pending_entries.remove(path);
if file && !self.durable.contains_key(path) {
self.set_durable(path, Vec::new());
}
}
fn forget_ordered(&mut self, path: &Path) {
self.ordered.retain_mut(|sync| match sync {
Ordered::Image(name, _) => name != path,
Ordered::Entries(entries) => {
entries.retain(|(entry, _)| entry != path);
true
}
Ordered::Removed(..) | Ordered::Renamed { .. } => true,
});
}
fn names_of(&self, path: &Path) -> Vec<PathBuf> {
match self.link_group.get(path) {
Some(&group) => self
.link_group
.iter()
.filter(|&(_, &other)| other == group)
.map(|(name, _)| name.clone())
.collect(),
None => alloc::vec![path.to_path_buf()],
}
}
fn set_durable(&mut self, path: &Path, bytes: Vec<u8>) {
let mut names = self.names_of(path);
let last = names.pop().unwrap_or_else(|| path.to_path_buf());
for name in names {
self.durable.insert(name, bytes.clone());
}
self.durable.insert(last, bytes);
}
fn forget_name(&mut self, path: &Path) {
self.forget_ordered(path);
self.durable.remove(path);
self.links.remove(path);
self.unlinked.remove(path);
self.pending_entries.remove(path);
let touched = self.touched.remove(path);
if let Some(group) = self.link_group.remove(path)
&& touched
{
let survivors: Vec<PathBuf> = self
.link_group
.iter()
.filter(|&(_, &other)| other == group)
.map(|(name, _)| name.clone())
.collect();
self.touched.extend(survivors);
}
}
fn forget_under(&mut self, path: &Path) {
let linked: Vec<PathBuf> = self
.link_group
.keys()
.filter(|k| k.starts_with(path))
.cloned()
.collect();
for name in &linked {
self.forget_name(name);
}
self.durable.retain(|k, _| !k.starts_with(path));
self.touched.retain(|k| !k.starts_with(path));
self.links.retain(|k, _| !k.starts_with(path));
self.unlinked.retain(|k, _| !k.starts_with(path));
self.pending_entries.retain(|k| !k.starts_with(path));
self.ordered.retain_mut(|sync| match sync {
Ordered::Image(name, _) | Ordered::Removed(name, _) => !name.starts_with(path),
Ordered::Renamed { from, to, .. } => !from.starts_with(path) && !to.starts_with(path),
Ordered::Entries(entries) => {
entries.retain(|(entry, _)| !entry.starts_with(path));
true
}
});
}
fn link(&mut self, src: &Path, dst: &Path) {
let group = if let Some(&group) = self.link_group.get(src) {
group
} else {
let group = self.next_link_group;
self.next_link_group += 1;
self.link_group.insert(src.to_path_buf(), group);
group
};
self.link_group.insert(dst.to_path_buf(), group);
}
}
const MAX_SYMLINKS: usize = 40;
fn components_reversed(path: &Path) -> Vec<PathBuf> {
path.components()
.rev()
.map(|component| PathBuf::from(component.as_os_str()))
.collect()
}
fn folded(path: &Path) -> PathBuf {
let mut out = PathBuf::new();
for component in path.components() {
match component {
Component::CurDir => {}
Component::ParentDir => match out.components().next_back() {
Some(Component::Normal(_)) => {
out.pop();
}
Some(Component::RootDir | Component::Prefix(_)) => {}
_ => out.push(component.as_os_str()),
},
other => out.push(other.as_os_str()),
}
}
if out.as_os_str().is_empty() {
out.push(".");
}
out
}
#[derive(Clone)]
pub struct CrashFs {
inner: Arc<dyn Fs>,
state: Arc<spin::Mutex<CrashState>>,
namespace: Arc<parking_lot::Mutex<()>>,
folders: bool,
}
impl CrashFs {
#[must_use]
pub fn new<F: Fs>(inner: F) -> Self {
Self::from_shared(Arc::new(inner))
}
#[must_use]
pub fn from_shared(inner: Arc<dyn Fs>) -> Self {
Self {
inner,
state: Arc::new(spin::Mutex::new(CrashState::default())),
namespace: Arc::new(parking_lot::Mutex::new(())),
folders: false,
}
}
#[must_use]
pub fn tracking_folders(mut self) -> Self {
self.folders = true;
self
}
#[must_use]
pub fn inner(&self) -> Arc<dyn Fs> {
Arc::clone(&self.inner)
}
pub fn crash(&self) {
self.crash_keeping(0);
}
#[must_use]
pub fn ordered_steps(&self) -> usize {
self.state.lock().ordered.len()
}
pub fn crash_keeping(&self, kept: usize) {
let mut state = self.state.lock();
state.settle(kept);
let lost: Vec<PathBuf> = state.pending_entries.drain().collect();
let mut lost_folders: Vec<&PathBuf> = lost
.iter()
.filter(|path| {
!matches!(self.inner.read_link(path), Ok(Some(_)))
&& self.inner.metadata(path).is_ok_and(|meta| meta.is_dir)
})
.collect();
lost_folders.sort_by_key(|path| path.components().count());
for folder in lost_folders {
match self.inner.remove_dir_all(folder) {
Ok(()) => {}
Err(e) if e.kind() == io::ErrorKind::NotFound => {}
Err(e) => panic!(
"crash(): removing the un-synced folder {} failed: {e}",
folder.display()
),
}
state.forget_under(folder);
}
for path in &lost {
state.durable.remove(path);
state.links.remove(path);
state.link_group.remove(path);
state.touched.insert(path.clone());
}
let paths: Vec<PathBuf> = state
.touched
.iter()
.chain(state.durable.keys())
.chain(state.links.keys())
.cloned()
.collect();
for path in paths {
if let Some(link) = state.links.get(&path) {
self.restore_link(&path, link);
continue;
}
if state.unlinked.contains_key(&path) {
continue;
}
match state.durable.get(&path) {
Some(bytes) => self.restore_durable(&path, bytes),
None => {
match self.inner.remove_file(&path) {
Ok(()) => {}
Err(e) if e.kind() == io::ErrorKind::NotFound => {}
Err(e) => {
panic!("crash(): removing un-synced {} failed: {e}", path.display())
}
}
}
}
}
let unlinked: Vec<(PathBuf, u64)> = state.unlinked.drain().collect();
for (path, group) in unlinked {
self.relink(&state, &path, group);
}
state.touched = state.durable.keys().cloned().collect();
state.links.clear();
let CrashState {
durable,
link_group,
..
} = &mut *state;
link_group.retain(|name, _| durable.contains_key(name));
}
fn forget_folder(&self, path: &Path) {
let name = self.name_after(path);
self.state.lock().forget_under(&name);
}
fn capture_name(&self, name: &Path) -> io::Result<()> {
match self.inner.read_link(name) {
Ok(None) => self.capture_first_touch(name),
Ok(Some(target)) => {
let to_dir = self.inner.symlink_to_dir(name)?;
let mut state = self.state.lock();
if !state.touched.contains(name) && !state.links.contains_key(name) {
state
.links
.insert(name.to_path_buf(), Link { target, to_dir });
}
state.touched.insert(name.to_path_buf());
Ok(())
}
Err(_) => Ok(()),
}
}
fn relink(&self, state: &CrashState, path: &Path, group: u64) {
let source = state
.link_group
.iter()
.filter(|&(_, &other)| other == group)
.map(|(name, _)| name.clone())
.find(|name| matches!(self.inner.exists(name), Ok(true)));
match self.inner.remove_file(path) {
Ok(()) => {}
Err(e) if e.kind() == io::ErrorKind::NotFound => {}
Err(e) => panic!(
"crash(): clearing {} for its link failed: {e}",
path.display()
),
}
let Some(source) = source else {
return;
};
self.inner.hard_link(&source, path).unwrap_or_else(|e| {
panic!(
"crash(): linking {} back to {} failed: {e}",
path.display(),
source.display()
)
});
}
fn restore_link(&self, path: &Path, link: &Link) {
let target = &link.target;
if matches!(self.inner.read_link(path), Ok(Some(now)) if now == *target)
&& matches!(self.inner.symlink_to_dir(path), Ok(kind) if kind == link.to_dir)
{
return;
}
match self.inner.remove_file(path) {
Ok(()) => {}
Err(e) if e.kind() == io::ErrorKind::NotFound => {}
Err(e) => panic!(
"crash(): clearing {} for its link failed: {e}",
path.display()
),
}
self.inner
.symlink(target, path, link.to_dir)
.unwrap_or_else(|e| {
panic!(
"crash(): putting back the link {} failed: {e}",
path.display()
)
});
}
fn restore_durable(&self, path: &Path, bytes: &[u8]) {
let mut file = self
.inner
.open(
path,
&FsOpenOptions::new().write(true).create(true).truncate(true),
)
.unwrap_or_else(|e| {
panic!(
"crash(): reopening {} for rollback failed: {e}",
path.display()
)
});
std::io::Write::write_all(&mut file, bytes).unwrap_or_else(|e| {
panic!(
"crash(): rewriting durable image of {} failed: {e}",
path.display()
)
});
}
fn read_baseline(&self, path: &Path) -> io::Result<Option<Vec<u8>>> {
let mut f = match self.inner.open(path, &FsOpenOptions::new().read(true)) {
Ok(f) => f,
Err(e) if e.kind() == io::ErrorKind::NotFound => return Ok(None),
Err(e) => return Err(e),
};
let mut buf = Vec::new();
std::io::Read::read_to_end(&mut f, &mut buf)?;
Ok(Some(buf))
}
fn capture_first_touch(&self, path: &Path) -> io::Result<()> {
let pb = path.to_path_buf();
let first_touch = {
let state = self.state.lock();
!state
.names_of(path)
.iter()
.any(|name| state.touched.contains(name))
};
if first_touch {
let baseline = self.read_baseline(path)?;
if let Some(bytes) = baseline {
self.state.lock().set_durable(path, bytes);
}
}
self.state.lock().touched.insert(pb);
Ok(())
}
fn entry_of(&self, path: &Path) -> io::Result<PathBuf> {
self.resolve(path, true)
}
fn name_of(&self, path: &Path) -> io::Result<PathBuf> {
self.resolve(path, false)
}
fn name_after(&self, path: &Path) -> PathBuf {
self.name_of(path).unwrap_or_else(|_| path.to_path_buf())
}
fn resolve(&self, path: &Path, follow_final: bool) -> io::Result<PathBuf> {
let mut resolved = PathBuf::new();
let mut rest: Vec<PathBuf> = components_reversed(path);
let mut followed = 0;
while let Some(part) = rest.pop() {
if !matches!(part.components().next(), Some(Component::Normal(_))) {
if !matches!(part.components().next(), Some(Component::CurDir)) {
resolved.push(part);
}
continue;
}
let candidate = resolved.join(&part);
if rest.is_empty() && !follow_final {
resolved = candidate;
break;
}
match self.inner.read_link(&candidate)? {
Some(target) => {
if followed == MAX_SYMLINKS {
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"too many levels of symbolic links",
));
}
followed += 1;
rest.extend(components_reversed(&target));
}
None => resolved = candidate,
}
}
if resolved.as_os_str().is_empty() {
resolved.push(".");
}
let folded = folded(&resolved);
if folded == resolved {
return Ok(resolved);
}
let (spelled, plain) = match (resolved.components().next_back(), folded.file_name()) {
(Some(Component::Normal(_)), Some(_)) => (
crate::file::entry_directory(&resolved).to_path_buf(),
crate::file::entry_directory(&folded).to_path_buf(),
),
_ => (resolved.clone(), folded.clone()),
};
Ok(
if matches!(self.inner.same_file(&spelled, &plain), Ok(true)) {
folded
} else {
resolved
},
)
}
fn listed_as_spelled(&self, path: &Path) -> bool {
let Some(name) = path.file_name() else {
return false;
};
self.inner
.read_dir(crate::file::entry_directory(path))
.is_ok_and(|entries| {
entries
.iter()
.any(|entry| std::ffi::OsStr::new(&entry.file_name) == name)
})
}
fn new_entry(&self, path: &Path) {
self.state.lock().pending_entries.insert(path.to_path_buf());
}
fn sync_entries_of(
&self,
directory: &Path,
mode: SyncMode,
sync: impl FnOnce() -> io::Result<()>,
) -> io::Result<()> {
let _namespace = self.namespace.lock();
sync()?;
let directory = self
.entry_of(directory)
.unwrap_or_else(|_| directory.to_path_buf());
let pending = self.state.lock().pending_entries.clone();
let covered: Vec<(PathBuf, bool)> = pending
.into_iter()
.filter(|entry| {
let parent = crate::file::entry_directory(entry);
parent == directory || matches!(self.inner.same_file(parent, &directory), Ok(true))
})
.map(|entry| {
let file = !matches!(self.inner.read_link(&entry), Ok(Some(_)))
&& self.inner.metadata(&entry).is_ok_and(|meta| meta.is_file);
(entry, file)
})
.collect();
let mut state = self.state.lock();
if mode == SyncMode::Barrier {
state.ordered.push(Ordered::Entries(covered));
return Ok(());
}
state.settle_all();
for (entry, file) in &covered {
state.entry_made_durable(entry, *file);
}
Ok(())
}
fn track_copy(&self, src: &Path, dst: &Path) -> io::Result<()> {
let (src_durable, src_touched) = {
let mut state = self.state.lock();
state.touched.insert(dst.to_path_buf());
state.pending_entries.insert(dst.to_path_buf());
(state.durable.get(src).cloned(), state.touched.contains(src))
};
let dst_image = match src_durable {
Some(bytes) => Some(bytes),
None if !src_touched => self.read_baseline(src)?,
None => None,
};
if let Some(bytes) = dst_image {
self.state.lock().durable.insert(dst.to_path_buf(), bytes);
}
Ok(())
}
}
impl Fs for CrashFs {
fn open(&self, path: &Path, opts: &FsOpenOptions) -> io::Result<Box<dyn FsFile>> {
let writable = opts.write || opts.create || opts.create_new || opts.append || opts.truncate;
let may_create = opts.create || opts.create_new;
let namespace = self.namespace.lock();
if opts.create_new {
let inner = self.inner.open(path, opts)?;
let entry = self.name_after(path);
self.state.lock().touched.insert(entry.clone());
self.new_entry(&entry);
drop(namespace);
return Ok(Box::new(CrashFile {
inner,
path: entry,
fs: Arc::clone(&self.inner),
state: Arc::clone(&self.state),
}));
}
if !writable {
let inner = self.inner.open(path, opts)?;
let entry = self.entry_of(path).unwrap_or_else(|_| path.to_path_buf());
drop(namespace);
return Ok(Box::new(CrashFile {
inner,
path: entry,
fs: Arc::clone(&self.inner),
state: Arc::clone(&self.state),
}));
}
let entry = self.entry_of(path)?;
let creates = may_create && !self.inner.exists(&entry)?;
self.capture_first_touch(&entry)?;
let inner = self.inner.open(path, opts)?;
if creates {
self.new_entry(&entry);
}
drop(namespace);
Ok(Box::new(CrashFile {
inner,
path: entry,
fs: Arc::clone(&self.inner),
state: Arc::clone(&self.state),
}))
}
fn create_dir_all(&self, path: &Path) -> io::Result<()> {
if !self.folders {
return self.inner.create_dir_all(path);
}
let _namespace = self.namespace.lock();
let mut made = Vec::new();
let mut at = path;
while !self.inner.exists(at)? {
let Some(parent) = at.parent() else {
break;
};
made.push(at.to_path_buf());
if parent.as_os_str().is_empty() {
break;
}
at = parent;
}
self.inner.create_dir_all(path)?;
for folder in made {
self.new_entry(&self.name_after(&folder));
}
Ok(())
}
fn create_dir(&self, path: &Path) -> io::Result<()> {
if !self.folders {
return self.inner.create_dir(path);
}
let _namespace = self.namespace.lock();
self.inner.create_dir(path)?;
self.new_entry(&self.name_after(path));
Ok(())
}
fn read_dir(&self, path: &Path) -> io::Result<Vec<FsDirEntry>> {
self.inner.read_dir(path)
}
fn remove_file(&self, path: &Path) -> io::Result<()> {
let _namespace = self.namespace.lock();
let name = self.name_after(path);
self.capture_name(&name)?;
self.inner.remove_file(path)?;
let name = self.name_after(path);
let mut state = self.state.lock();
if state.ordered.is_empty() {
state.forget_name(&name);
return Ok(());
}
let was_pending = state.pending_entries.contains(&name);
if state.names_of(&name).len() > 1
&& let Some(group) = state.link_group.remove(&name)
{
if state.touched.contains(&name) {
let survivors: Vec<PathBuf> = state
.link_group
.iter()
.filter(|&(_, &other)| other == group)
.map(|(other, _)| other.clone())
.collect();
state.touched.extend(survivors);
}
state.durable.remove(&name);
state.unlinked.insert(name.clone(), group);
}
state.touched.insert(name.clone());
state.ordered.push(Ordered::Removed(name, was_pending));
Ok(())
}
fn remove_dir(&self, path: &Path) -> io::Result<()> {
let _namespace = self.namespace.lock();
self.inner.remove_dir(path)?;
self.forget_folder(path);
Ok(())
}
fn remove_dir_all(&self, path: &Path) -> io::Result<()> {
let _namespace = self.namespace.lock();
self.inner.remove_dir_all(path)?;
self.forget_folder(path);
Ok(())
}
fn same_file(&self, a: &Path, b: &Path) -> io::Result<bool> {
self.inner.same_file(a, b)
}
fn read_link(&self, path: &Path) -> io::Result<Option<PathBuf>> {
self.inner.read_link(path)
}
fn rename(&self, from: &Path, to: &Path) -> io::Result<()> {
let _namespace = self.namespace.lock();
let spelled = || (self.listed_as_spelled(from), self.listed_as_spelled(to));
let before = matches!(self.inner.same_file(from, to), Ok(true)).then(spelled);
if from != to {
for name in [self.name_after(from), self.name_after(to)] {
self.capture_name(&name)?;
}
}
self.inner.rename(from, to)?;
let unchanged = match before {
Some(before) => before == spelled(),
None => {
(matches!(self.inner.read_link(from), Ok(Some(_)))
|| matches!(self.inner.exists(from), Ok(true)))
&& self.listed_as_spelled(from)
}
};
if from == to || unchanged {
return Ok(());
}
let (from, to) = (self.name_after(from), self.name_after(to));
let (from, to) = (from.as_path(), to.as_path());
let mut state = self.state.lock();
if !state.ordered.is_empty()
&& state.names_of(from).len() == 1
&& state.names_of(to).len() <= 1
{
let since = state.ordered.iter().rposition(|sync| match sync {
Ordered::Removed(name, _) => name.as_path() == from,
Ordered::Renamed {
from: source,
to: target,
..
} => source.as_path() == from || target.as_path() == from,
_ => false,
});
let queued = state
.ordered
.iter()
.skip(since.unwrap_or(0))
.rev()
.find_map(|sync| match sync {
Ordered::Image(name, bytes) if name.as_path() == from => Some(bytes.clone()),
Ordered::Renamed {
to: target,
bytes: Some(bytes),
..
} if target.as_path() == from => Some(bytes.clone()),
_ => None,
});
let bytes = queued.or_else(|| {
since
.is_none()
.then(|| state.durable.get(from).cloned())
.flatten()
});
let to_pending = state.pending_entries.contains(to);
let from_pending = state.pending_entries.contains(from);
state.ordered.push(Ordered::Renamed {
from: from.to_path_buf(),
to: to.to_path_buf(),
from_pending,
to_pending,
bytes,
});
state.touched.insert(from.to_path_buf());
state.touched.insert(to.to_path_buf());
state.pending_entries.insert(to.to_path_buf());
if let Some(group) = state.link_group.remove(from) {
state.link_group.insert(to.to_path_buf(), group);
}
return Ok(());
}
let from_durable = state.durable.remove(from);
let from_link = state.links.remove(from);
let from_touched = state.touched.remove(from);
let from_group = state.link_group.remove(from);
state.pending_entries.remove(from);
state.forget_name(to);
for sync in &mut state.ordered {
match sync {
Ordered::Image(name, _) if name.as_path() == from => *name = to.to_path_buf(),
Ordered::Image(..) | Ordered::Removed(..) | Ordered::Renamed { .. } => {}
Ordered::Entries(entries) => entries.retain(|(entry, _)| entry != from),
}
}
if let Some(bytes) = from_durable {
state.durable.insert(to.to_path_buf(), bytes);
}
if let Some(target) = from_link {
state.links.insert(to.to_path_buf(), target);
}
if from_touched {
state.touched.insert(to.to_path_buf());
}
state.pending_entries.insert(to.to_path_buf());
if let Some(group) = from_group {
state.link_group.insert(to.to_path_buf(), group);
}
Ok(())
}
fn metadata(&self, path: &Path) -> io::Result<FsMetadata> {
self.inner.metadata(path)
}
fn sync_directory(&self, path: &Path) -> io::Result<()> {
self.sync_entries_of(path, SyncMode::Full, || self.inner.sync_directory(path))
}
fn sync_directory_with(&self, path: &Path, mode: SyncMode) -> io::Result<()> {
self.sync_entries_of(path, mode, || self.inner.sync_directory_with(path, mode))
}
fn sync_device(&self, path: &Path) -> io::Result<()> {
let _namespace = self.namespace.lock();
self.inner.sync_device(path)?;
self.state.lock().settle_all();
Ok(())
}
fn exists(&self, path: &Path) -> io::Result<bool> {
self.inner.exists(path)
}
fn hard_link(&self, src: &Path, dst: &Path) -> io::Result<()> {
let _namespace = self.namespace.lock();
self.inner.hard_link(src, dst)?;
let link = self.name_after(dst);
if matches!(self.inner.read_link(dst), Ok(Some(_))) {
self.new_entry(&link);
return Ok(());
}
let file = self.entry_of(src).unwrap_or_else(|_| self.name_after(src));
self.track_copy(&file, &link)?;
if matches!(self.inner.same_file(&file, &link), Ok(true)) {
let mut state = self.state.lock();
state.link(&file, &link);
if let Some(bytes) = state.durable.get(&link).cloned() {
state.set_durable(&link, bytes);
}
}
Ok(())
}
fn backend_id(&self) -> Option<u64> {
self.inner.backend_id()
}
fn write_io_stats(&self) -> Option<&Arc<super::WriteIoStats>> {
self.inner.write_io_stats()
}
fn write_budget(&self) -> Option<&Arc<crate::write_budget::WriteBudget>> {
self.inner.write_budget()
}
fn volume_id(&self, path: &Path) -> Option<u64> {
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<()> {
let _namespace = self.namespace.lock();
self.capture_first_touch(&self.entry_of(path)?)?;
self.inner.punch_hole(path, offset, len)
}
fn reflink_file(&self, src: &Path, dst: &Path) -> io::Result<()> {
let _namespace = self.namespace.lock();
self.inner.reflink_file(src, dst)?;
let file = self.entry_of(src).unwrap_or_else(|_| self.name_after(src));
self.track_copy(&file, &self.name_after(dst))?;
Ok(())
}
fn truncate_file(&self, path: &Path) -> io::Result<()> {
let _namespace = self.namespace.lock();
self.capture_first_touch(&self.entry_of(path)?)?;
self.inner.truncate_file(path)
}
fn hard_link_count(&self, path: &Path) -> io::Result<u64> {
self.inner.hard_link_count(path)
}
fn available_space(&self, path: &Path) -> io::Result<u64> {
self.inner.available_space(path)
}
fn allocated_size(&self, path: &Path) -> io::Result<Option<u64>> {
self.inner.allocated_size(path)
}
fn extent_is_hole(&self, path: &Path, offset: u64, len: u64) -> io::Result<Option<bool>> {
self.inner.extent_is_hole(path, offset, len)
}
fn extent_contains_hole(&self, path: &Path, offset: u64, len: u64) -> io::Result<Option<bool>> {
self.inner.extent_contains_hole(path, offset, len)
}
}
struct CrashFile {
inner: Box<dyn FsFile>,
path: PathBuf,
fs: Arc<dyn Fs>,
state: Arc<spin::Mutex<CrashState>>,
}
impl CrashFile {
fn snapshot(&self, mode: SyncMode) -> io::Result<()> {
let mut rf = self.fs.open(&self.path, &FsOpenOptions::new().read(true))?;
let mut buf = Vec::new();
std::io::Read::read_to_end(&mut rf, &mut buf)?;
let mut state = self.state.lock();
if mode == SyncMode::Barrier {
state.ordered.push(Ordered::Image(self.path.clone(), buf));
} else {
state.settle_all();
state.set_durable(&self.path, buf);
}
Ok(())
}
}
impl std::io::Read for CrashFile {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.inner.read(buf)
}
}
impl std::io::Write for CrashFile {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.inner.write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
self.inner.flush()
}
}
impl std::io::Seek for CrashFile {
fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result<u64> {
self.inner.seek(pos)
}
}
impl FsFile for CrashFile {
fn sync_all(&self) -> io::Result<()> {
self.inner.sync_all()?;
self.snapshot(SyncMode::Full)
}
fn sync_data(&self) -> io::Result<()> {
self.inner.sync_data()?;
self.snapshot(SyncMode::Full)
}
fn sync_all_with(&self, mode: SyncMode) -> io::Result<()> {
self.inner.sync_all_with(mode)?;
self.snapshot(mode)
}
fn sync_data_with(&self, mode: SyncMode) -> io::Result<()> {
self.inner.sync_data_with(mode)?;
self.snapshot(mode)
}
fn start_writeback(&self, offset: u64, len: u64) -> io::Result<()> {
self.inner.start_writeback(offset, len)
}
fn metadata(&self) -> io::Result<FsMetadata> {
self.inner.metadata()
}
fn hard_link_count(&self) -> io::Result<u64> {
self.inner.hard_link_count()
}
fn set_len(&self, size: u64) -> io::Result<()> {
self.inner.set_len(size)
}
fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
self.inner.read_at(buf, offset)
}
fn read_at_buf(&self, buf: &mut super::ReadBuf<'_>, offset: u64) -> io::Result<()> {
self.inner.read_at_buf(buf, offset)
}
fn read_many_buf(&self, regions: &mut [(u64, super::ReadBuf<'_>)]) -> io::Result<()> {
super::read_buf::read_each(self, regions)
}
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)
}
}
#[cfg(test)]
#[expect(clippy::unwrap_used, clippy::expect_used, reason = "test code")]
mod tests;