Nothing to see here, move along
1use crate::block_io::BlockIo;
2use crate::cache::BlockCache;
3use crate::cap::{FsCap, FsRights};
4use crate::dir;
5use crate::error::FsError;
6use crate::file;
7use crate::freemap::FreemapAllocator;
8use crate::pool::NodePool;
9use lancer_core::fs::{INODE_INLINE_MAX, Inode, InodeFlags, InodeType, MAX_SYMLINK_DEPTH};
10
11const MAX_PATH_COMPONENTS: usize = 32;
12const MAX_NAME_LEN: usize = 680;
13
14#[allow(clippy::too_many_arguments)]
15pub fn file_create(
16 pool: &mut NodePool,
17 cache: &mut BlockCache,
18 bio: &mut BlockIo,
19 freemap: &mut FreemapAllocator,
20 parent_inode: &Inode,
21 name: &[u8],
22 inode_type: InodeType,
23 txn: u64,
24 next_object_id: u64,
25) -> Result<(Inode, Inode, u64), FsError> {
26 if name.is_empty() || name.len() > MAX_NAME_LEN {
27 return Err(FsError::NameTooLong);
28 }
29
30 let inode_block = freemap.alloc_blocks(cache, bio, 1)?;
31
32 let new_inode = match inode_type {
33 InodeType::File => {
34 let mut inode = Inode::new_file(next_object_id, 1, txn, 0, 0);
35 inode.flags |= InodeFlags::INLINE;
36 inode
37 }
38 InodeType::Directory => Inode::new_directory(next_object_id, 1, txn, 0, 0),
39 InodeType::Symlink => Inode::new_symlink(next_object_id, 1, txn, 0, 0, &[]),
40 };
41
42 let inode_with_name = match name.len() > 34 {
43 true => {
44 let mut inode = new_inode;
45 inode.extended_attrs[..name.len()].copy_from_slice(name);
46 inode
47 }
48 false => new_inode,
49 };
50
51 let inode_phys = crate::block_num_to_phys(inode_block);
52 file::write_inode_to_cache(cache, bio, inode_block, &inode_with_name)?;
53
54 let updated_parent = dir::dir_insert(pool, cache, bio, parent_inode, name, inode_phys, txn)?;
55
56 Ok((updated_parent, inode_with_name, inode_block))
57}
58
59pub fn file_delete(
60 pool: &mut NodePool,
61 cache: &mut BlockCache,
62 bio: &mut BlockIo,
63 parent_inode: &Inode,
64 name: &[u8],
65 txn: u64,
66) -> Result<Inode, FsError> {
67 dir::dir_remove(pool, cache, bio, parent_inode, name, txn)
68}
69
70#[allow(clippy::too_many_arguments)]
71pub fn mkdir(
72 pool: &mut NodePool,
73 cache: &mut BlockCache,
74 bio: &mut BlockIo,
75 freemap: &mut FreemapAllocator,
76 parent_inode: &Inode,
77 name: &[u8],
78 txn: u64,
79 next_object_id: u64,
80) -> Result<(Inode, Inode, u64), FsError> {
81 file_create(
82 pool,
83 cache,
84 bio,
85 freemap,
86 parent_inode,
87 name,
88 InodeType::Directory,
89 txn,
90 next_object_id,
91 )
92}
93
94pub fn rmdir(
95 pool: &mut NodePool,
96 cache: &mut BlockCache,
97 bio: &mut BlockIo,
98 parent_inode: &Inode,
99 name: &[u8],
100 txn: u64,
101) -> Result<Inode, FsError> {
102 let entry =
103 dir::dir_lookup(pool, cache, bio, parent_inode, name, None)?.ok_or(FsError::NotFound)?;
104
105 let child_block = crate::blockref_block_num(&entry);
106 let child_inode = file::read_inode(cache, bio, child_block)?;
107
108 match child_inode.inode_type_enum() {
109 Some(InodeType::Directory) => {}
110 _ => return Err(FsError::NotADirectory),
111 }
112
113 match dir::dir_is_empty(pool, cache, bio, &child_inode)? {
114 true => {}
115 false => return Err(FsError::DirectoryNotEmpty),
116 }
117
118 file_delete(pool, cache, bio, parent_inode, name, txn)
119}
120
121#[allow(clippy::too_many_arguments)]
122pub fn create_symlink(
123 pool: &mut NodePool,
124 cache: &mut BlockCache,
125 bio: &mut BlockIo,
126 freemap: &mut FreemapAllocator,
127 parent_inode: &Inode,
128 name: &[u8],
129 target: &[u8],
130 txn: u64,
131 next_object_id: u64,
132) -> Result<(Inode, Inode, u64), FsError> {
133 if target.is_empty() || target.len() > INODE_INLINE_MAX {
134 return Err(FsError::InvalidName);
135 }
136
137 if name.is_empty() || name.len() > MAX_NAME_LEN {
138 return Err(FsError::NameTooLong);
139 }
140
141 let inode_block = freemap.alloc_blocks(cache, bio, 1)?;
142
143 let symlink_inode = Inode::new_symlink(next_object_id, 1, txn, 0, 0, target);
144
145 let inode_with_name = match name.len() > 34 {
146 true => {
147 let mut inode = symlink_inode;
148 inode.extended_attrs[..name.len()].copy_from_slice(name);
149 inode
150 }
151 false => symlink_inode,
152 };
153
154 let inode_phys = crate::block_num_to_phys(inode_block);
155 file::write_inode_to_cache(cache, bio, inode_block, &inode_with_name)?;
156
157 let updated_parent = dir::dir_insert(pool, cache, bio, parent_inode, name, inode_phys, txn)?;
158
159 Ok((updated_parent, inode_with_name, inode_block))
160}
161
162pub fn readlink(inode: &Inode) -> Option<&[u8]> {
163 match inode.inode_type_enum() {
164 Some(InodeType::Symlink) => inode.inline_data(),
165 _ => None,
166 }
167}
168
169#[allow(clippy::too_many_arguments)]
170pub fn resolve_path(
171 pool: &mut NodePool,
172 cache: &mut BlockCache,
173 bio: &mut BlockIo,
174 root_inode: &Inode,
175 root_block_num: u64,
176 start_inode: &Inode,
177 start_block_num: u64,
178 path: &[u8],
179 txn_filter: Option<u64>,
180 symlink_depth: u8,
181) -> Result<(Inode, u64), FsError> {
182 if symlink_depth >= MAX_SYMLINK_DEPTH {
183 return Err(FsError::SymlinkDepthExceeded);
184 }
185
186 let (base_inode, base_block) = match path.first() {
187 Some(b'/') => (root_inode.clone(), root_block_num),
188 _ => (start_inode.clone(), start_block_num),
189 };
190
191 let components = PathComponents::new(path);
192
193 components.fold_bounded(
194 (base_inode, base_block),
195 MAX_PATH_COMPONENTS,
196 |state, component| {
197 walk_component(
198 pool,
199 cache,
200 bio,
201 root_inode,
202 root_block_num,
203 state,
204 component,
205 txn_filter,
206 symlink_depth,
207 )
208 },
209 )
210}
211
212#[allow(clippy::too_many_arguments)]
213fn walk_component(
214 pool: &mut NodePool,
215 cache: &mut BlockCache,
216 bio: &mut BlockIo,
217 root_inode: &Inode,
218 root_block_num: u64,
219 (current_inode, current_block): (Inode, u64),
220 component: &[u8],
221 txn_filter: Option<u64>,
222 symlink_depth: u8,
223) -> Result<(Inode, u64), FsError> {
224 match component {
225 b".." => Err(FsError::InvalidName),
226 b"." => Ok((current_inode, current_block)),
227 name => {
228 match current_inode.inode_type_enum() {
229 Some(InodeType::Directory) => {}
230 _ => return Err(FsError::NotADirectory),
231 }
232
233 let entry = dir::dir_lookup(pool, cache, bio, ¤t_inode, name, txn_filter)?
234 .ok_or(FsError::NotFound)?;
235 let child_block = crate::blockref_block_num(&entry);
236 let child_inode = file::read_inode(cache, bio, child_block)?;
237
238 match child_inode.inode_type_enum() {
239 Some(InodeType::Symlink) => {
240 let target = child_inode.inline_data().ok_or(FsError::InvalidBlock)?;
241 resolve_path(
242 pool,
243 cache,
244 bio,
245 root_inode,
246 root_block_num,
247 ¤t_inode,
248 current_block,
249 target,
250 txn_filter,
251 symlink_depth + 1,
252 )
253 }
254 _ => Ok((child_inode, child_block)),
255 }
256 }
257 }
258}
259
260struct PathComponents<'a> {
261 remaining: &'a [u8],
262}
263
264impl<'a> PathComponents<'a> {
265 fn new(path: &'a [u8]) -> Self {
266 Self { remaining: path }
267 }
268
269 fn fold_bounded<S, F>(self, init: S, max_components: usize, mut f: F) -> Result<S, FsError>
270 where
271 F: FnMut(S, &[u8]) -> Result<S, FsError>,
272 {
273 self.enumerate()
274 .try_fold(init, |state, (i, component)| match i >= max_components {
275 true => Err(FsError::PathTooDeep),
276 false => f(state, component),
277 })
278 }
279}
280
281impl<'a> Iterator for PathComponents<'a> {
282 type Item = &'a [u8];
283
284 fn next(&mut self) -> Option<Self::Item> {
285 skip_slashes(&mut self.remaining);
286 match self.remaining.is_empty() {
287 true => None,
288 false => {
289 let end = self
290 .remaining
291 .iter()
292 .position(|&b| b == b'/')
293 .unwrap_or(self.remaining.len());
294 let component = &self.remaining[..end];
295 self.remaining = &self.remaining[end..];
296 Some(component)
297 }
298 }
299 }
300}
301
302fn skip_slashes(remaining: &mut &[u8]) {
303 let skip = remaining
304 .iter()
305 .position(|&b| b != b'/')
306 .unwrap_or(remaining.len());
307 *remaining = &remaining[skip..];
308}
309
310#[allow(clippy::too_many_arguments)]
311pub fn resolve_path_cap(
312 pool: &mut NodePool,
313 cache: &mut BlockCache,
314 bio: &mut BlockIo,
315 dir_cap: &FsCap,
316 dir_inode: &Inode,
317 dir_block: u64,
318 path: &[u8],
319 txn_filter: Option<u64>,
320) -> Result<(FsCap, Inode, u64), FsError> {
321 dir_cap.check_rights(FsRights::TRAVERSE)?;
322
323 let components = PathComponents::new(path);
324
325 let (cap, inode, block, _depth) = components.fold_bounded(
326 (*dir_cap, dir_inode.clone(), dir_block, 0u8),
327 MAX_PATH_COMPONENTS,
328 |(cap, inode, block, depth), component| {
329 walk_component_cap(
330 pool,
331 cache,
332 bio,
333 dir_cap,
334 dir_inode,
335 dir_block,
336 (cap, inode, block),
337 component,
338 txn_filter,
339 depth,
340 )
341 },
342 )?;
343 Ok((cap, inode, block))
344}
345
346#[allow(clippy::too_many_arguments)]
347fn walk_component_cap(
348 pool: &mut NodePool,
349 cache: &mut BlockCache,
350 bio: &mut BlockIo,
351 root_cap: &FsCap,
352 root_inode: &Inode,
353 root_block: u64,
354 (current_cap, current_inode, current_block): (FsCap, Inode, u64),
355 component: &[u8],
356 txn_filter: Option<u64>,
357 symlink_depth: u8,
358) -> Result<(FsCap, Inode, u64, u8), FsError> {
359 match component {
360 b".." => Err(FsError::InsufficientRights),
361 b"." => Ok((current_cap, current_inode, current_block, symlink_depth)),
362 name => {
363 current_cap.check_rights(FsRights::TRAVERSE)?;
364
365 match current_inode.inode_type_enum() {
366 Some(InodeType::Directory) => {}
367 _ => return Err(FsError::NotADirectory),
368 }
369
370 let entry = dir::dir_lookup(pool, cache, bio, ¤t_inode, name, txn_filter)?
371 .ok_or(FsError::NotFound)?;
372 let child_block = crate::blockref_block_num(&entry);
373 let child_inode = file::read_inode(cache, bio, child_block)?;
374
375 match child_inode.inode_type_enum() {
376 Some(InodeType::Symlink) => match symlink_depth >= MAX_SYMLINK_DEPTH {
377 true => Err(FsError::SymlinkDepthExceeded),
378 false => {
379 let target = child_inode.inline_data().ok_or(FsError::InvalidBlock)?;
380 let (cap, inode, block) = resolve_path_cap_recursive(
381 pool,
382 cache,
383 bio,
384 root_cap,
385 root_inode,
386 root_block,
387 ¤t_cap,
388 ¤t_inode,
389 current_block,
390 target,
391 txn_filter,
392 symlink_depth + 1,
393 )?;
394 Ok((cap, inode, block, symlink_depth + 1))
395 }
396 },
397 _ => {
398 let child_cap =
399 current_cap.derive(child_inode.object_id, child_inode.generation);
400 Ok((child_cap, child_inode, child_block, symlink_depth))
401 }
402 }
403 }
404 }
405}
406
407#[allow(clippy::too_many_arguments)]
408fn resolve_path_cap_recursive(
409 pool: &mut NodePool,
410 cache: &mut BlockCache,
411 bio: &mut BlockIo,
412 root_cap: &FsCap,
413 root_inode: &Inode,
414 root_block: u64,
415 current_cap: &FsCap,
416 current_inode: &Inode,
417 current_block: u64,
418 path: &[u8],
419 txn_filter: Option<u64>,
420 symlink_depth: u8,
421) -> Result<(FsCap, Inode, u64), FsError> {
422 let (base_cap, base_inode, base_block) = match path.first() {
423 Some(b'/') => (*root_cap, root_inode.clone(), root_block),
424 _ => (*current_cap, current_inode.clone(), current_block),
425 };
426
427 let components = PathComponents::new(path);
428
429 components
430 .fold_bounded(
431 (base_cap, base_inode, base_block, symlink_depth),
432 MAX_PATH_COMPONENTS,
433 |(cap, inode, block, depth), component| {
434 walk_component_cap(
435 pool,
436 cache,
437 bio,
438 root_cap,
439 root_inode,
440 root_block,
441 (cap, inode, block),
442 component,
443 txn_filter,
444 depth,
445 )
446 },
447 )
448 .map(|(cap, inode, block, _depth)| (cap, inode, block))
449}