1use super::visibility::{self, resolve_in_memory_read, unbind_matches_binding};
11use super::{DirentryBindRecord, InodeRecord, MetadataState, SubtreeTombstoneRecord};
12use loonfs_api::{AbsolutePath, ChangeSeq, InodeId, InodeKind, NameKey};
13use serde::{Deserialize, Serialize};
14use std::future::Future;
15use thiserror::Error;
16
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
18pub struct ResolvedVisiblePath {
19 pub absolute_path: String,
20 pub inode_id: InodeId,
21 pub inode_kind: InodeKind,
22 pub parent_inode_id: Option<InodeId>,
23 pub display_name: String,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Error)]
27pub enum VisiblePathError {
28 #[error("canonical root inode is missing")]
29 RootMissing,
30 #[error("visible path not found: `{absolute_path}`")]
31 PathNotFound { absolute_path: String },
32 #[error(
33 "path component traversal expected directory at `{absolute_path}` but found inode `{inode_id}` kind `{inode_kind}`"
34 )]
35 PathComponentNotDirectory {
36 absolute_path: String,
37 inode_id: InodeId,
38 inode_kind: InodeKind,
39 },
40}
41
42impl MetadataState {
43 pub fn inode_at_seq(&self, inode_id: InodeId, base_seq: ChangeSeq) -> Option<InodeRecord> {
44 if base_seq >= self.indexed_seq() {
45 return self.inode_at_head(inode_id);
46 }
47 self.inode_at_seq_scan(inode_id, base_seq)
48 }
49
50 pub(crate) fn inode_at_head(&self, inode_id: InodeId) -> Option<InodeRecord> {
51 self.indexes.inode(inode_id)
52 }
53
54 fn inode_at_seq_scan(&self, inode_id: InodeId, base_seq: ChangeSeq) -> Option<InodeRecord> {
55 self.inodes
56 .iter()
57 .find(|inode| inode.inode_id == inode_id && inode.created_seq <= base_seq)
58 .cloned()
59 }
60
61 pub(crate) fn bound_child_at_seq(
64 &self,
65 parent_inode_id: InodeId,
66 name_key: &NameKey,
67 base_seq: ChangeSeq,
68 ) -> Option<DirentryBindRecord> {
69 if base_seq >= self.indexed_seq() {
70 return self.indexes.latest_bind(parent_inode_id, name_key);
71 }
72 self.bound_child_at_seq_scan(parent_inode_id, name_key, base_seq)
73 }
74
75 fn bound_child_at_seq_scan(
76 &self,
77 parent_inode_id: InodeId,
78 name_key: &NameKey,
79 base_seq: ChangeSeq,
80 ) -> Option<DirentryBindRecord> {
81 self.direntry_binds
82 .iter()
83 .filter(|direntry| {
84 direntry.parent_inode_id == parent_inode_id
85 && direntry.name_key == *name_key
86 && direntry.bind_seq <= base_seq
87 })
88 .max_by_key(|direntry| (direntry.bind_seq, direntry.bind_delta_index))
89 .cloned()
90 }
91
92 pub fn current_parent_binding_for_child(
93 &self,
94 child_inode_id: InodeId,
95 base_seq: ChangeSeq,
96 ) -> Option<DirentryBindRecord> {
97 if base_seq >= self.indexed_seq() {
98 return self.current_parent_binding_for_child_at_head(child_inode_id);
99 }
100 read_now(visibility::current_parent_binding_for_child(
101 &mut self.reads_at_seq(base_seq),
102 child_inode_id,
103 ))
104 }
105
106 pub(crate) fn current_parent_binding_for_child_at_head(
107 &self,
108 child_inode_id: InodeId,
109 ) -> Option<DirentryBindRecord> {
110 self.indexes.active_parent_for_child(child_inode_id)
111 }
112
113 pub fn active_subtree_tombstone(
114 &self,
115 root_inode_id: InodeId,
116 base_seq: ChangeSeq,
117 ) -> Option<SubtreeTombstoneRecord> {
118 if base_seq >= self.indexed_seq() {
119 return self.active_subtree_tombstone_at_head(root_inode_id);
120 }
121 self.active_subtree_tombstone_scan(root_inode_id, base_seq)
122 }
123
124 pub(crate) fn active_subtree_tombstone_at_head(
125 &self,
126 root_inode_id: InodeId,
127 ) -> Option<SubtreeTombstoneRecord> {
128 self.indexes.active_tombstone(root_inode_id)
129 }
130
131 fn active_subtree_tombstone_scan(
132 &self,
133 root_inode_id: InodeId,
134 base_seq: ChangeSeq,
135 ) -> Option<SubtreeTombstoneRecord> {
136 super::rows::active_tombstone_from_records(
137 self.subtree_tombstones
138 .iter()
139 .filter(|tombstone| tombstone.root_inode_id == root_inode_id)
140 .cloned(),
141 base_seq,
142 )
143 }
144
145 pub fn covering_subtree_tombstone(
146 &self,
147 inode_id: InodeId,
148 base_seq: ChangeSeq,
149 ) -> Option<SubtreeTombstoneRecord> {
150 if base_seq >= self.indexed_seq() {
151 return self.covering_subtree_tombstone_at_head(inode_id);
152 }
153 read_now(visibility::covering_subtree_tombstone(
154 &mut self.reads_at_seq(base_seq),
155 inode_id,
156 ))
157 }
158
159 pub(crate) fn covering_subtree_tombstone_at_head(
160 &self,
161 inode_id: InodeId,
162 ) -> Option<SubtreeTombstoneRecord> {
163 read_now(visibility::covering_subtree_tombstone(
164 &mut self.reads_at_head(),
165 inode_id,
166 ))
167 }
168
169 pub fn visible_inode(&self, inode_id: InodeId, base_seq: ChangeSeq) -> Option<InodeRecord> {
170 if base_seq >= self.indexed_seq() {
171 return self.visible_inode_at_head(inode_id);
172 }
173 read_now(visibility::visible_inode(
174 &mut self.reads_at_seq(base_seq),
175 inode_id,
176 ))
177 }
178
179 pub(crate) fn visible_inode_at_head(&self, inode_id: InodeId) -> Option<InodeRecord> {
180 read_now(visibility::visible_inode(
181 &mut self.reads_at_head(),
182 inode_id,
183 ))
184 }
185
186 pub fn visible_child(
187 &self,
188 parent_inode_id: InodeId,
189 name_key: &NameKey,
190 base_seq: ChangeSeq,
191 ) -> Option<DirentryBindRecord> {
192 if base_seq >= self.indexed_seq() {
193 return self.visible_child_at_head(parent_inode_id, name_key);
194 }
195 read_now(visibility::visible_child(
196 &mut self.reads_at_seq(base_seq),
197 parent_inode_id,
198 name_key,
199 ))
200 }
201
202 pub(crate) fn visible_child_at_head(
203 &self,
204 parent_inode_id: InodeId,
205 name_key: &NameKey,
206 ) -> Option<DirentryBindRecord> {
207 read_now(visibility::visible_child(
208 &mut self.reads_at_head(),
209 parent_inode_id,
210 name_key,
211 ))
212 }
213
214 pub fn resolve_visible_path(
215 &self,
216 absolute_path: &AbsolutePath,
217 base_seq: ChangeSeq,
218 ) -> Result<ResolvedVisiblePath, VisiblePathError> {
219 resolve_in_memory_read(visibility::resolve_visible_path(
220 &mut self.reads_at_seq(base_seq),
221 absolute_path,
222 ))
223 }
224
225 pub(super) fn latest_parent_binding_for_child_at_seq(
226 &self,
227 child_inode_id: InodeId,
228 base_seq: ChangeSeq,
229 ) -> Option<DirentryBindRecord> {
230 self.direntry_binds
231 .iter()
232 .filter(|direntry| {
233 direntry.child_inode_id == child_inode_id && direntry.bind_seq <= base_seq
234 })
235 .max_by_key(|direntry| (direntry.bind_seq, direntry.bind_delta_index))
236 .cloned()
237 }
238
239 pub(crate) fn is_direntry_unbound_at_seq(
240 &self,
241 direntry: &DirentryBindRecord,
242 base_seq: ChangeSeq,
243 ) -> bool {
244 if base_seq >= self.indexed_seq() {
245 return self.is_direntry_unbound_at_head(direntry);
246 }
247 self.is_direntry_unbound_at_seq_scan(direntry, base_seq)
248 }
249
250 pub(crate) fn is_direntry_unbound_at_head(&self, direntry: &DirentryBindRecord) -> bool {
251 self.indexes.is_unbound(direntry)
252 }
253
254 fn is_direntry_unbound_at_seq_scan(
255 &self,
256 direntry: &DirentryBindRecord,
257 base_seq: ChangeSeq,
258 ) -> bool {
259 self.direntry_unbinds
260 .iter()
261 .any(|unbind| unbind.unbind_seq <= base_seq && unbind_matches_binding(unbind, direntry))
262 }
263
264 pub fn would_create_directory_cycle(
265 &self,
266 inode_id: InodeId,
267 new_parent_inode_id: InodeId,
268 base_seq: ChangeSeq,
269 ) -> bool {
270 if base_seq >= self.indexed_seq() {
271 return self.would_create_directory_cycle_at_head(inode_id, new_parent_inode_id);
272 }
273 read_now(visibility::would_create_directory_cycle(
274 &mut self.reads_at_seq(base_seq),
275 inode_id,
276 new_parent_inode_id,
277 ))
278 }
279
280 pub(crate) fn would_create_directory_cycle_at_head(
281 &self,
282 inode_id: InodeId,
283 new_parent_inode_id: InodeId,
284 ) -> bool {
285 read_now(visibility::would_create_directory_cycle(
286 &mut self.reads_at_head(),
287 inode_id,
288 new_parent_inode_id,
289 ))
290 }
291}
292
293fn read_now<T>(future: impl Future<Output = Result<T, VisiblePathError>>) -> T {
297 resolve_in_memory_read(future).expect("seq-scoped metadata state reads should be infallible")
298}