use super::queries::{ResolvedVisiblePath, VisiblePathError};
use super::{
DirentryBindRecord, DirentryUnbindRecord, InodeRecord, MetadataState, SubtreeTombstoneRecord,
};
use futures::FutureExt;
use loonfs_api::{AbsolutePath, ChangeSeq, InodeId, InodeKind, NameKey, ROOT_INODE_ID};
use std::collections::BTreeSet;
use std::future::Future;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct BindingIdentity<'a> {
pub(crate) parent_inode_id: InodeId,
pub(crate) name_key: &'a NameKey,
pub(crate) child_inode_id: InodeId,
pub(crate) bind_seq: ChangeSeq,
pub(crate) bind_delta_index: u32,
}
impl<'a> From<&'a DirentryBindRecord> for BindingIdentity<'a> {
fn from(record: &'a DirentryBindRecord) -> Self {
Self {
parent_inode_id: record.parent_inode_id,
name_key: &record.name_key,
child_inode_id: record.child_inode_id,
bind_seq: record.bind_seq,
bind_delta_index: record.bind_delta_index,
}
}
}
impl<'a> From<&'a DirentryUnbindRecord> for BindingIdentity<'a> {
fn from(record: &'a DirentryUnbindRecord) -> Self {
Self {
parent_inode_id: record.parent_inode_id,
name_key: &record.name_key,
child_inode_id: record.child_inode_id,
bind_seq: record.bind_seq,
bind_delta_index: record.bind_delta_index,
}
}
}
impl DirentryBindRecord {
pub(crate) fn same_binding(&self, other: &DirentryBindRecord) -> bool {
BindingIdentity::from(self) == BindingIdentity::from(other)
}
}
pub(crate) fn unbind_matches_binding(
unbind: &DirentryUnbindRecord,
direntry: &DirentryBindRecord,
) -> bool {
BindingIdentity::from(unbind) == BindingIdentity::from(direntry)
}
pub(crate) trait MetadataVisibilityReads {
type Error;
async fn find_inode(&mut self, inode_id: InodeId) -> Result<Option<InodeRecord>, Self::Error>;
async fn find_latest_bound_child(
&mut self,
parent_inode_id: InodeId,
name_key: &NameKey,
) -> Result<Option<DirentryBindRecord>, Self::Error>;
async fn find_latest_parent_binding_for_child(
&mut self,
child_inode_id: InodeId,
) -> Result<Option<DirentryBindRecord>, Self::Error>;
async fn find_active_subtree_tombstone(
&mut self,
root_inode_id: InodeId,
) -> Result<Option<SubtreeTombstoneRecord>, Self::Error>;
async fn is_binding_unbound(
&mut self,
direntry: &DirentryBindRecord,
) -> Result<bool, Self::Error>;
async fn current_parent_binding_for_child(
&mut self,
child_inode_id: InodeId,
) -> Result<Option<DirentryBindRecord>, Self::Error>
where
Self: Sized,
{
current_parent_binding_for_child(self, child_inode_id).await
}
async fn active_child_binding(
&mut self,
parent_inode_id: InodeId,
name_key: &NameKey,
) -> Result<Option<DirentryBindRecord>, Self::Error>
where
Self: Sized,
{
active_child_binding(self, parent_inode_id, name_key).await
}
async fn covering_subtree_tombstone(
&mut self,
inode_id: InodeId,
) -> Result<Option<SubtreeTombstoneRecord>, Self::Error>
where
Self: Sized,
{
covering_subtree_tombstone(self, inode_id).await
}
async fn visible_inode(&mut self, inode_id: InodeId) -> Result<Option<InodeRecord>, Self::Error>
where
Self: Sized,
{
visible_inode(self, inode_id).await
}
async fn visible_child(
&mut self,
parent_inode_id: InodeId,
name_key: &NameKey,
) -> Result<Option<DirentryBindRecord>, Self::Error>
where
Self: Sized,
{
visible_child(self, parent_inode_id, name_key).await
}
}
pub(crate) async fn current_parent_binding_for_child<R: MetadataVisibilityReads>(
reads: &mut R,
child_inode_id: InodeId,
) -> Result<Option<DirentryBindRecord>, R::Error> {
let Some(direntry) = reads
.find_latest_parent_binding_for_child(child_inode_id)
.await?
else {
return Ok(None);
};
if reads.is_binding_unbound(&direntry).await? {
return Ok(None);
}
Ok(Some(direntry))
}
pub(crate) async fn active_child_binding<R: MetadataVisibilityReads>(
reads: &mut R,
parent_inode_id: InodeId,
name_key: &NameKey,
) -> Result<Option<DirentryBindRecord>, R::Error> {
let Some(direntry) = reads
.find_latest_bound_child(parent_inode_id, name_key)
.await?
else {
return Ok(None);
};
if reads.is_binding_unbound(&direntry).await? {
return Ok(None);
}
let Some(latest_binding) = reads
.current_parent_binding_for_child(direntry.child_inode_id)
.await?
else {
return Ok(None);
};
if !latest_binding.same_binding(&direntry) {
return Ok(None);
}
Ok(Some(direntry))
}
pub(crate) async fn covering_subtree_tombstone<R: MetadataVisibilityReads>(
reads: &mut R,
inode_id: InodeId,
) -> Result<Option<SubtreeTombstoneRecord>, R::Error> {
let mut current = Some(inode_id);
let mut visited = BTreeSet::new();
while let Some(candidate_inode_id) = current {
if !visited.insert(candidate_inode_id.0) {
break;
}
if let Some(tombstone) = reads
.find_active_subtree_tombstone(candidate_inode_id)
.await?
{
return Ok(Some(tombstone));
}
current = reads
.current_parent_binding_for_child(candidate_inode_id)
.await?
.map(|direntry| direntry.parent_inode_id);
}
Ok(None)
}
pub(crate) async fn would_create_directory_cycle<R: MetadataVisibilityReads>(
reads: &mut R,
inode_id: InodeId,
new_parent_inode_id: InodeId,
) -> Result<bool, R::Error> {
let mut current = Some(new_parent_inode_id);
let mut visited = BTreeSet::new();
while let Some(candidate_inode_id) = current {
if !visited.insert(candidate_inode_id.0) {
break;
}
if candidate_inode_id == inode_id {
return Ok(true);
}
current = reads
.current_parent_binding_for_child(candidate_inode_id)
.await?
.map(|direntry| direntry.parent_inode_id);
}
Ok(false)
}
pub(crate) async fn visible_inode<R: MetadataVisibilityReads>(
reads: &mut R,
inode_id: InodeId,
) -> Result<Option<InodeRecord>, R::Error> {
let Some(inode) = reads.find_inode(inode_id).await? else {
return Ok(None);
};
if reads.covering_subtree_tombstone(inode_id).await?.is_some() {
return Ok(None);
}
Ok(Some(inode))
}
pub(crate) async fn visible_child<R: MetadataVisibilityReads>(
reads: &mut R,
parent_inode_id: InodeId,
name_key: &NameKey,
) -> Result<Option<DirentryBindRecord>, R::Error> {
let Some(parent) = reads.visible_inode(parent_inode_id).await? else {
return Ok(None);
};
if parent.inode_kind != InodeKind::Directory {
return Ok(None);
}
let Some(direntry) = reads
.active_child_binding(parent_inode_id, name_key)
.await?
else {
return Ok(None);
};
if reads
.visible_inode(direntry.child_inode_id)
.await?
.is_none()
{
return Ok(None);
}
Ok(Some(direntry))
}
pub(crate) async fn resolve_visible_path<R>(
reads: &mut R,
absolute_path: &AbsolutePath,
) -> Result<ResolvedVisiblePath, R::Error>
where
R: MetadataVisibilityReads,
R::Error: From<VisiblePathError>,
{
let root_inode_id = ROOT_INODE_ID;
let root = reads
.visible_inode(root_inode_id)
.await?
.ok_or(VisiblePathError::RootMissing)?;
if absolute_path.is_root() {
return Ok(ResolvedVisiblePath {
absolute_path: "/".to_owned(),
inode_id: root_inode_id,
inode_kind: root.inode_kind,
parent_inode_id: None,
display_name: String::new(),
});
}
let mut current_inode_id = root_inode_id;
let mut current_absolute_path = "/".to_owned();
let mut current_parent_inode_id = None;
let mut current_display_name = String::new();
for component in absolute_path.components() {
let current_inode = reads
.visible_inode(current_inode_id)
.await?
.ok_or_else(|| VisiblePathError::PathNotFound {
absolute_path: current_absolute_path.clone(),
})?;
if current_inode.inode_kind != InodeKind::Directory {
return Err(VisiblePathError::PathComponentNotDirectory {
absolute_path: current_absolute_path,
inode_id: current_inode_id,
inode_kind: current_inode.inode_kind,
}
.into());
}
let requested_absolute_path = join_display_path(¤t_absolute_path, component.as_str());
let display_name = component.to_display_name();
let name_key = NameKey::for_display_name(&display_name);
let direntry = reads
.visible_child(current_inode_id, &name_key)
.await?
.ok_or(VisiblePathError::PathNotFound {
absolute_path: requested_absolute_path,
})?;
current_inode_id = direntry.child_inode_id;
current_parent_inode_id = Some(direntry.parent_inode_id);
current_absolute_path =
join_display_path(¤t_absolute_path, direntry.display_name.as_str());
current_display_name = direntry.display_name.to_string();
}
let inode = reads
.visible_inode(current_inode_id)
.await?
.ok_or_else(|| VisiblePathError::PathNotFound {
absolute_path: current_absolute_path.clone(),
})?;
Ok(ResolvedVisiblePath {
absolute_path: current_absolute_path,
inode_id: current_inode_id,
inode_kind: inode.inode_kind,
parent_inode_id: current_parent_inode_id,
display_name: current_display_name,
})
}
fn join_display_path(base: &str, component: &str) -> String {
if base == "/" {
format!("/{component}")
} else {
format!("{base}/{component}")
}
}
pub(crate) fn resolve_in_memory_read<T>(future: impl Future<Output = T>) -> T {
future
.now_or_never()
.expect("in-memory metadata visibility reads should never await")
}
pub(super) struct MetadataStateAtSeqReads<'a> {
state: &'a MetadataState,
base_seq: ChangeSeq,
}
pub(super) struct MetadataStateAtHeadReads<'a> {
state: &'a MetadataState,
}
impl MetadataState {
pub(super) fn reads_at_seq(&self, base_seq: ChangeSeq) -> MetadataStateAtSeqReads<'_> {
MetadataStateAtSeqReads {
state: self,
base_seq,
}
}
pub(super) fn reads_at_head(&self) -> MetadataStateAtHeadReads<'_> {
MetadataStateAtHeadReads { state: self }
}
}
impl MetadataVisibilityReads for MetadataStateAtSeqReads<'_> {
type Error = VisiblePathError;
async fn find_inode(&mut self, inode_id: InodeId) -> Result<Option<InodeRecord>, Self::Error> {
Ok(self.state.inode_at_seq(inode_id, self.base_seq))
}
async fn find_latest_bound_child(
&mut self,
parent_inode_id: InodeId,
name_key: &NameKey,
) -> Result<Option<DirentryBindRecord>, Self::Error> {
Ok(self
.state
.bound_child_at_seq(parent_inode_id, name_key, self.base_seq))
}
async fn find_latest_parent_binding_for_child(
&mut self,
child_inode_id: InodeId,
) -> Result<Option<DirentryBindRecord>, Self::Error> {
Ok(self
.state
.latest_parent_binding_for_child_at_seq(child_inode_id, self.base_seq))
}
async fn find_active_subtree_tombstone(
&mut self,
root_inode_id: InodeId,
) -> Result<Option<SubtreeTombstoneRecord>, Self::Error> {
Ok(self
.state
.active_subtree_tombstone(root_inode_id, self.base_seq))
}
async fn is_binding_unbound(
&mut self,
direntry: &DirentryBindRecord,
) -> Result<bool, Self::Error> {
Ok(self
.state
.is_direntry_unbound_at_seq(direntry, self.base_seq))
}
async fn current_parent_binding_for_child(
&mut self,
child_inode_id: InodeId,
) -> Result<Option<DirentryBindRecord>, Self::Error> {
Ok(self
.state
.current_parent_binding_for_child(child_inode_id, self.base_seq))
}
async fn covering_subtree_tombstone(
&mut self,
inode_id: InodeId,
) -> Result<Option<SubtreeTombstoneRecord>, Self::Error> {
Ok(self
.state
.covering_subtree_tombstone(inode_id, self.base_seq))
}
async fn visible_inode(
&mut self,
inode_id: InodeId,
) -> Result<Option<InodeRecord>, Self::Error> {
Ok(self.state.visible_inode(inode_id, self.base_seq))
}
async fn visible_child(
&mut self,
parent_inode_id: InodeId,
name_key: &NameKey,
) -> Result<Option<DirentryBindRecord>, Self::Error> {
Ok(self
.state
.visible_child(parent_inode_id, name_key, self.base_seq))
}
}
impl MetadataVisibilityReads for MetadataStateAtHeadReads<'_> {
type Error = VisiblePathError;
async fn find_inode(&mut self, inode_id: InodeId) -> Result<Option<InodeRecord>, Self::Error> {
Ok(self.state.inode_at_head(inode_id))
}
async fn find_latest_bound_child(
&mut self,
parent_inode_id: InodeId,
name_key: &NameKey,
) -> Result<Option<DirentryBindRecord>, Self::Error> {
Ok(self.state.indexes.latest_bind(parent_inode_id, name_key))
}
async fn find_latest_parent_binding_for_child(
&mut self,
child_inode_id: InodeId,
) -> Result<Option<DirentryBindRecord>, Self::Error> {
Ok(self
.state
.latest_parent_binding_for_child_at_seq(child_inode_id, self.state.indexed_seq()))
}
async fn find_active_subtree_tombstone(
&mut self,
root_inode_id: InodeId,
) -> Result<Option<SubtreeTombstoneRecord>, Self::Error> {
Ok(self.state.active_subtree_tombstone_at_head(root_inode_id))
}
async fn is_binding_unbound(
&mut self,
direntry: &DirentryBindRecord,
) -> Result<bool, Self::Error> {
Ok(self.state.is_direntry_unbound_at_head(direntry))
}
async fn current_parent_binding_for_child(
&mut self,
child_inode_id: InodeId,
) -> Result<Option<DirentryBindRecord>, Self::Error> {
Ok(self
.state
.current_parent_binding_for_child_at_head(child_inode_id))
}
async fn active_child_binding(
&mut self,
parent_inode_id: InodeId,
name_key: &NameKey,
) -> Result<Option<DirentryBindRecord>, Self::Error> {
Ok(self.state.indexes.active_child(parent_inode_id, name_key))
}
async fn visible_inode(
&mut self,
inode_id: InodeId,
) -> Result<Option<InodeRecord>, Self::Error> {
Ok(self.state.visible_inode_at_head(inode_id))
}
async fn visible_child(
&mut self,
parent_inode_id: InodeId,
name_key: &NameKey,
) -> Result<Option<DirentryBindRecord>, Self::Error> {
Ok(self.state.visible_child_at_head(parent_inode_id, name_key))
}
}