Nothing to see here, move along meow
1use crate::block_io::BlockIo;
2use crate::btree;
3use crate::cache::BlockCache;
4use crate::cap::FsRights;
5use crate::commit::SuperblockPair;
6use crate::cow::FreemapReserved;
7use crate::ditto::DittoRegion;
8use crate::error::FsError;
9use crate::file;
10use crate::freemap::FreemapAllocator;
11use crate::handle::HandleTableSet;
12use crate::ipc_proto::{FsOpcode, FsRequest, FsResponse};
13use crate::ops;
14use crate::pool::NodePool;
15use crate::scrub::ScrubState;
16use crate::snapshot;
17use crate::transaction;
18use lancer_core::fs::{
19 BlockRef, Compression, DEDUP_SHARDS, INODE_DIRECT_REFS, InodeFlags, InodeType,
20};
21use lancer_vfs_proto::{READDIR_ENTRY_SIZE, READDIR_MAX_NAME, ReadDirEntry};
22
23const MAX_NAME_LEN: usize = 680;
24const LZ4_HASH_SIZE: usize = 1 << 12;
25
26pub struct FsState {
27 pub sb_pair: SuperblockPair,
28 pub freemap: FreemapAllocator,
29 pub reserved: FreemapReserved,
30 pub ditto: DittoRegion,
31 pub scrub: ScrubState,
32 pub handles: HandleTableSet,
33 pub next_object_id: u64,
34 pub dedup_roots: [BlockRef; DEDUP_SHARDS],
35 pub root_inode_block: u64,
36 pub root_object_id: u64,
37 pub root_generation: u64,
38}
39
40impl FsState {
41 pub fn from_superblock_pair(
42 sb_pair: SuperblockPair,
43 root_inode_block: u64,
44 root_object_id: u64,
45 root_generation: u64,
46 ) -> Self {
47 let freemap = FreemapAllocator::init(sb_pair.active());
48 let reserved = FreemapReserved::init(sb_pair.active().total_blocks);
49 let ditto = DittoRegion::init(freemap.data_region_start(), freemap.total_data_blocks());
50 let saved_cursor = sb_pair.active().scrub_cursor;
51 let mut scrub = ScrubState::new();
52 scrub.start(saved_cursor);
53 let dedup_roots = sb_pair.active().dedup_roots;
54 let next_object_id = sb_pair.active().next_object_id.max(2);
55
56 Self {
57 sb_pair,
58 freemap,
59 reserved,
60 ditto,
61 scrub,
62 handles: HandleTableSet::new(),
63 next_object_id,
64 dedup_roots,
65 root_inode_block,
66 root_object_id,
67 root_generation,
68 }
69 }
70}
71
72#[allow(clippy::too_many_arguments)]
73pub fn dispatch_request(
74 req: &FsRequest,
75 pool: &mut NodePool,
76 cache: &mut BlockCache,
77 bio: &mut BlockIo,
78 state: &mut FsState,
79 data_area: *mut u8,
80 data_area_size: usize,
81 client_pid: u16,
82 scratch: &mut [u8],
83 hash_table: &mut [u16; LZ4_HASH_SIZE],
84) -> FsResponse {
85 let opcode_raw = req.opcode;
86 let tag = req.tag;
87
88 let response = match FsOpcode::from_u8(opcode_raw) {
89 None => FsResponse::error(opcode_raw, tag, FsError::InvalidBlock),
90 Some(opcode) => match opcode {
91 FsOpcode::Open => handle_open(
92 req,
93 pool,
94 cache,
95 bio,
96 state,
97 data_area,
98 data_area_size,
99 client_pid,
100 ),
101 FsOpcode::Read => handle_read(
102 req,
103 pool,
104 cache,
105 bio,
106 state,
107 data_area,
108 data_area_size,
109 client_pid,
110 ),
111 FsOpcode::Write => handle_write(
112 req,
113 pool,
114 cache,
115 bio,
116 state,
117 data_area,
118 data_area_size,
119 client_pid,
120 scratch,
121 hash_table,
122 ),
123 FsOpcode::Close => handle_close(req, state, client_pid),
124 FsOpcode::Stat => handle_stat(req, cache, bio, state, client_pid),
125 FsOpcode::Mkdir => handle_mkdir(
126 req,
127 pool,
128 cache,
129 bio,
130 state,
131 data_area,
132 data_area_size,
133 client_pid,
134 ),
135 FsOpcode::Unlink => handle_unlink(
136 req,
137 pool,
138 cache,
139 bio,
140 state,
141 data_area,
142 data_area_size,
143 client_pid,
144 ),
145 FsOpcode::ReadDir => handle_readdir(
146 req,
147 pool,
148 cache,
149 bio,
150 state,
151 data_area,
152 data_area_size,
153 client_pid,
154 ),
155 FsOpcode::CreateSnapshot => handle_create_snapshot(
156 req,
157 pool,
158 cache,
159 bio,
160 state,
161 data_area,
162 data_area_size,
163 client_pid,
164 ),
165 FsOpcode::DeleteSnapshot => handle_delete_snapshot(
166 req,
167 pool,
168 cache,
169 bio,
170 state,
171 data_area,
172 data_area_size,
173 client_pid,
174 ),
175 FsOpcode::Sync => handle_sync(req, pool, cache, bio, state, scratch, hash_table),
176 FsOpcode::StatFs => handle_statfs(req, cache, bio, state),
177 FsOpcode::Truncate => handle_truncate(req, pool, cache, bio, state, client_pid),
178 FsOpcode::Rename => handle_rename(
179 req,
180 pool,
181 cache,
182 bio,
183 state,
184 data_area,
185 data_area_size,
186 client_pid,
187 ),
188 },
189 };
190
191 let needs_commit = matches!(
192 FsOpcode::from_u8(opcode_raw),
193 Some(
194 FsOpcode::Open
195 | FsOpcode::Write
196 | FsOpcode::Mkdir
197 | FsOpcode::Unlink
198 | FsOpcode::Truncate
199 | FsOpcode::Rename
200 )
201 );
202
203 match needs_commit && response.status == 0 {
204 true => {
205 let tree_root = state.sb_pair.active().tree_root;
206 let dedup_roots = state.dedup_roots;
207 let snapshot_root = state.sb_pair.active().snapshot_root;
208 let scrub_cursor = state.scrub.cursor();
209
210 match transaction::transaction_commit(
211 pool,
212 cache,
213 bio,
214 &mut state.freemap,
215 &mut state.reserved,
216 &mut state.sb_pair,
217 &tree_root,
218 &dedup_roots,
219 &snapshot_root,
220 Some(&state.ditto),
221 scrub_cursor,
222 state.next_object_id,
223 ) {
224 Ok(()) => {
225 state.dedup_roots = state.sb_pair.active().dedup_roots;
226 response
227 }
228 Err(e) => FsResponse::error(opcode_raw, tag, e),
229 }
230 }
231 false => response,
232 }
233}
234
235unsafe fn read_name_from_data<'a>(
236 data_area: *const u8,
237 data_area_size: usize,
238 offset: u64,
239 len: u64,
240) -> Option<&'a [u8]> {
241 let off = offset as usize;
242 let length = len as usize;
243 match off.checked_add(length) {
244 Some(end) if end <= data_area_size && length <= MAX_NAME_LEN && length > 0 => {
245 Some(unsafe { core::slice::from_raw_parts(data_area.add(off), length) })
246 }
247 _ => None,
248 }
249}
250
251#[allow(clippy::too_many_arguments)]
252fn handle_open(
253 req: &FsRequest,
254 pool: &mut NodePool,
255 cache: &mut BlockCache,
256 bio: &mut BlockIo,
257 state: &mut FsState,
258 data_area: *mut u8,
259 data_area_size: usize,
260 client_pid: u16,
261) -> FsResponse {
262 let tag = req.tag;
263 let dir_handle = req.handle;
264 let mode = FsRights::from_raw(req.flags as u16);
265 let name_offset = req.arg0;
266 let name_len = req.arg1;
267
268 let result = (|| -> Result<(u8, u64, u8), FsError> {
269 let (dir_cap, dir_inode_block) = {
270 let client = state
271 .handles
272 .client(client_pid)
273 .ok_or(FsError::InvalidHandle)?;
274 let dir_entry = client.validate(dir_handle, FsRights::TRAVERSE)?;
275 (dir_entry.to_cap(), dir_entry.inode_block)
276 };
277 let dir_inode = file::read_inode(cache, bio, dir_inode_block)?;
278
279 let name = unsafe { read_name_from_data(data_area, data_area_size, name_offset, name_len) }
280 .ok_or(FsError::InvalidName)?;
281
282 let resolve_result = ops::resolve_path_cap(
283 pool,
284 cache,
285 bio,
286 &dir_cap,
287 &dir_inode,
288 dir_inode_block,
289 name,
290 None,
291 );
292
293 let (child_inode, child_block, open_rights) = match resolve_result {
294 Ok((child_cap, child_inode, child_block)) => {
295 (child_inode, child_block, child_cap.rights.restrict(mode))
296 }
297 Err(FsError::NotFound) if mode.contains(FsRights::CREATE) => {
298 dir_cap
299 .check_rights(FsRights::CREATE)
300 .map_err(FsError::from)?;
301
302 let txn = state.sb_pair.active().transaction_id.wrapping_add(1);
303 let obj_id = state.next_object_id;
304 state.next_object_id += 1;
305
306 let (updated_parent, child_inode, child_block) = ops::file_create(
307 pool,
308 cache,
309 bio,
310 &mut state.freemap,
311 &dir_inode,
312 name,
313 InodeType::File,
314 txn,
315 obj_id,
316 )?;
317
318 file::write_inode_to_cache(cache, bio, dir_inode_block, &updated_parent)?;
319
320 let inheritable = dir_cap.rights.restrict(FsRights::TRAVERSE_INHERITABLE);
321 (child_inode, child_block, inheritable.restrict(mode))
322 }
323 Err(e) => return Err(e),
324 };
325
326 let client_mut = state
327 .handles
328 .client_mut(client_pid)
329 .ok_or(FsError::InvalidHandle)?;
330 let handle = client_mut.allocate(
331 child_inode.object_id,
332 child_inode.generation,
333 open_rights,
334 child_block,
335 )?;
336
337 Ok((handle, child_inode.size, child_inode.inode_type))
338 })();
339
340 match result {
341 Ok((handle, size, itype)) => FsResponse::success(
342 FsOpcode::Open as u8,
343 tag,
344 handle as u64,
345 size | ((itype as u64) << 56),
346 ),
347 Err(e) => FsResponse::error(FsOpcode::Open as u8, tag, e),
348 }
349}
350
351#[allow(clippy::too_many_arguments)]
352fn handle_read(
353 req: &FsRequest,
354 pool: &mut NodePool,
355 cache: &mut BlockCache,
356 bio: &mut BlockIo,
357 state: &mut FsState,
358 data_area: *mut u8,
359 data_area_size: usize,
360 client_pid: u16,
361) -> FsResponse {
362 let tag = req.tag;
363 let handle = req.handle;
364 let offset = req.arg0;
365 let len = req.arg1 as u32;
366 let buf_offset = req.arg2 as u32;
367
368 let result = (|| -> Result<usize, FsError> {
369 let (inode_block, generation) = {
370 let client = state
371 .handles
372 .client(client_pid)
373 .ok_or(FsError::InvalidHandle)?;
374 let entry = client.validate(handle, FsRights::READ)?;
375 (entry.inode_block, entry.generation)
376 };
377 let inode = file::read_inode(cache, bio, inode_block)?;
378
379 match inode.generation == generation {
380 true => {}
381 false => return Err(FsError::StaleGeneration),
382 }
383
384 let write_end = (buf_offset as usize)
385 .checked_add(len as usize)
386 .ok_or(FsError::InvalidBlock)?;
387 match write_end <= data_area_size {
388 true => {}
389 false => return Err(FsError::InvalidBlock),
390 }
391
392 let buf = unsafe {
393 core::slice::from_raw_parts_mut(data_area.add(buf_offset as usize), len as usize)
394 };
395 file::file_read(pool, cache, bio, &inode, offset, buf)
396 })();
397
398 match result {
399 Ok(bytes_read) => FsResponse::success(FsOpcode::Read as u8, tag, bytes_read as u64, 0),
400 Err(e) => FsResponse::error(FsOpcode::Read as u8, tag, e),
401 }
402}
403
404#[allow(clippy::too_many_arguments)]
405fn handle_write(
406 req: &FsRequest,
407 pool: &mut NodePool,
408 cache: &mut BlockCache,
409 bio: &mut BlockIo,
410 state: &mut FsState,
411 data_area: *mut u8,
412 data_area_size: usize,
413 client_pid: u16,
414 scratch: &mut [u8],
415 hash_table: &mut [u16; LZ4_HASH_SIZE],
416) -> FsResponse {
417 let tag = req.tag;
418 let handle = req.handle;
419 let offset = req.arg0;
420 let len = req.arg1 as u32;
421 let buf_offset = req.arg2 as u32;
422
423 let result = (|| -> Result<usize, FsError> {
424 let (inode_block, inode) = {
425 let client = state
426 .handles
427 .client(client_pid)
428 .ok_or(FsError::InvalidHandle)?;
429 let entry = client.validate(handle, FsRights::WRITE)?;
430 let inode = file::read_inode(cache, bio, entry.inode_block)?;
431 match inode.generation == entry.generation {
432 true => (entry.inode_block, inode),
433 false => return Err(FsError::StaleGeneration),
434 }
435 };
436
437 let read_end = (buf_offset as usize)
438 .checked_add(len as usize)
439 .ok_or(FsError::InvalidBlock)?;
440 match read_end <= data_area_size {
441 true => {}
442 false => return Err(FsError::InvalidBlock),
443 }
444
445 let data = unsafe {
446 core::slice::from_raw_parts(
447 data_area.add(buf_offset as usize) as *const u8,
448 len as usize,
449 )
450 };
451
452 let txn = state.sb_pair.active().transaction_id.wrapping_add(1);
453 let dedup_roots = state.dedup_roots;
454
455 let (updated_inode, written, new_dedups) = file::file_write(
456 pool,
457 cache,
458 bio,
459 &mut state.freemap,
460 &dedup_roots,
461 &inode,
462 offset,
463 data,
464 txn,
465 Compression::None,
466 scratch,
467 hash_table,
468 )?;
469
470 state.dedup_roots = new_dedups;
471
472 file::write_inode_to_cache(cache, bio, inode_block, &updated_inode)?;
473
474 Ok(written)
475 })();
476
477 match result {
478 Ok(bytes_written) => {
479 FsResponse::success(FsOpcode::Write as u8, tag, bytes_written as u64, 0)
480 }
481 Err(e) => FsResponse::error(FsOpcode::Write as u8, tag, e),
482 }
483}
484
485fn handle_close(req: &FsRequest, state: &mut FsState, client_pid: u16) -> FsResponse {
486 let tag = req.tag;
487 let handle = req.handle;
488
489 let result = state
490 .handles
491 .client_mut(client_pid)
492 .ok_or(FsError::InvalidHandle)
493 .and_then(|client| client.release(handle).map_err(FsError::from));
494
495 match result {
496 Ok(()) => FsResponse::success(FsOpcode::Close as u8, tag, 0, 0),
497 Err(e) => FsResponse::error(FsOpcode::Close as u8, tag, e),
498 }
499}
500
501fn handle_stat(
502 req: &FsRequest,
503 cache: &mut BlockCache,
504 bio: &mut BlockIo,
505 state: &FsState,
506 client_pid: u16,
507) -> FsResponse {
508 let tag = req.tag;
509 let handle = req.handle;
510
511 let result = (|| -> Result<(u64, u64), FsError> {
512 let client = state
513 .handles
514 .client(client_pid)
515 .ok_or(FsError::InvalidHandle)?;
516 let entry = client.validate(handle, FsRights::READ)?;
517 let inode = file::read_inode(cache, bio, entry.inode_block)?;
518
519 let type_and_blocks =
520 (inode.inode_type as u64) << 48 | (inode.block_count & 0xFFFF_FFFF_FFFF);
521
522 Ok((inode.size, type_and_blocks))
523 })();
524
525 match result {
526 Ok((size, type_blocks)) => {
527 FsResponse::success(FsOpcode::Stat as u8, tag, size, type_blocks)
528 }
529 Err(e) => FsResponse::error(FsOpcode::Stat as u8, tag, e),
530 }
531}
532
533#[allow(clippy::too_many_arguments)]
534fn handle_mkdir(
535 req: &FsRequest,
536 pool: &mut NodePool,
537 cache: &mut BlockCache,
538 bio: &mut BlockIo,
539 state: &mut FsState,
540 data_area: *mut u8,
541 data_area_size: usize,
542 client_pid: u16,
543) -> FsResponse {
544 let tag = req.tag;
545 let dir_handle = req.handle;
546 let name_offset = req.arg0;
547 let name_len = req.arg1;
548
549 let result = (|| -> Result<u8, FsError> {
550 let (inode_block, parent_rights) = {
551 let client = state
552 .handles
553 .client(client_pid)
554 .ok_or(FsError::InvalidHandle)?;
555 let entry = client.validate(dir_handle, FsRights::CREATE)?;
556 (entry.inode_block, entry.rights)
557 };
558 let dir_inode = file::read_inode(cache, bio, inode_block)?;
559
560 let name = unsafe { read_name_from_data(data_area, data_area_size, name_offset, name_len) }
561 .ok_or(FsError::InvalidName)?;
562
563 let txn = state.sb_pair.active().transaction_id.wrapping_add(1);
564 let obj_id = state.next_object_id;
565 state.next_object_id += 1;
566
567 let (updated_parent, child_inode, child_block) = ops::mkdir(
568 pool,
569 cache,
570 bio,
571 &mut state.freemap,
572 &dir_inode,
573 name,
574 txn,
575 obj_id,
576 )?;
577
578 file::write_inode_to_cache(cache, bio, inode_block, &updated_parent)?;
579
580 let child_rights = parent_rights.restrict(FsRights::TRAVERSE_INHERITABLE);
581 let client_mut = state
582 .handles
583 .client_mut(client_pid)
584 .ok_or(FsError::InvalidHandle)?;
585 let new_handle = client_mut.allocate(
586 child_inode.object_id,
587 child_inode.generation,
588 child_rights,
589 child_block,
590 )?;
591
592 Ok(new_handle)
593 })();
594
595 match result {
596 Ok(handle) => FsResponse::success(FsOpcode::Mkdir as u8, tag, handle as u64, 0),
597 Err(e) => FsResponse::error(FsOpcode::Mkdir as u8, tag, e),
598 }
599}
600
601#[allow(clippy::too_many_arguments)]
602fn handle_unlink(
603 req: &FsRequest,
604 pool: &mut NodePool,
605 cache: &mut BlockCache,
606 bio: &mut BlockIo,
607 state: &mut FsState,
608 data_area: *mut u8,
609 data_area_size: usize,
610 client_pid: u16,
611) -> FsResponse {
612 let tag = req.tag;
613 let dir_handle = req.handle;
614 let name_offset = req.arg0;
615 let name_len = req.arg1;
616
617 let result = (|| -> Result<(), FsError> {
618 let inode_block = {
619 let client = state
620 .handles
621 .client(client_pid)
622 .ok_or(FsError::InvalidHandle)?;
623 let entry = client.validate(dir_handle, FsRights::DELETE)?;
624 entry.inode_block
625 };
626 let dir_inode = file::read_inode(cache, bio, inode_block)?;
627
628 let name = unsafe { read_name_from_data(data_area, data_area_size, name_offset, name_len) }
629 .ok_or(FsError::InvalidName)?;
630
631 let txn = state.sb_pair.active().transaction_id.wrapping_add(1);
632 let updated_parent = ops::file_delete(pool, cache, bio, &dir_inode, name, txn)?;
633
634 file::write_inode_to_cache(cache, bio, inode_block, &updated_parent)?;
635
636 Ok(())
637 })();
638
639 match result {
640 Ok(()) => FsResponse::success(FsOpcode::Unlink as u8, tag, 0, 0),
641 Err(e) => FsResponse::error(FsOpcode::Unlink as u8, tag, e),
642 }
643}
644
645#[allow(clippy::too_many_arguments)]
646fn handle_readdir(
647 req: &FsRequest,
648 pool: &mut NodePool,
649 cache: &mut BlockCache,
650 bio: &mut BlockIo,
651 state: &mut FsState,
652 data_area: *mut u8,
653 data_area_size: usize,
654 client_pid: u16,
655) -> FsResponse {
656 let tag = req.tag;
657 let dir_handle = req.handle;
658 let cursor = req.arg0;
659 let buf_offset = req.arg1 as usize;
660 let buf_len = req.arg2 as usize;
661
662 let result = (|| -> Result<(u64, u64), FsError> {
663 let inode_block = {
664 let client = state
665 .handles
666 .client(client_pid)
667 .ok_or(FsError::InvalidHandle)?;
668 let entry = client.validate(dir_handle, FsRights::LIST)?;
669 entry.inode_block
670 };
671 let dir_inode = file::read_inode(cache, bio, inode_block)?;
672
673 let write_end = buf_offset
674 .checked_add(buf_len)
675 .ok_or(FsError::InvalidBlock)?;
676 match write_end <= data_area_size {
677 true => {}
678 false => return Err(FsError::InvalidBlock),
679 }
680
681 let max_entries = buf_len / READDIR_ENTRY_SIZE;
682 let mut iter = btree::btree_range(&dir_inode.direct[0], cursor, u64::MAX, None);
683 let mut entries_written: u64 = 0;
684 let mut next_cursor: u64 = cursor;
685
686 let mut done = false;
687 (0..max_entries).try_for_each(|_| -> Result<(), FsError> {
688 match done {
689 true => Ok(()),
690 false => match iter.next(pool, cache, bio)? {
691 None => {
692 done = true;
693 Ok(())
694 }
695 Some(entry) => {
696 let name_len = entry.logical_size as u16;
697 let child_block = crate::blockref_block_num(&entry);
698 let child_inode = file::read_inode(cache, bio, child_block)?;
699
700 let mut name_buf = [0u8; READDIR_MAX_NAME];
701 let inline_len = (name_len as usize).min(READDIR_MAX_NAME);
702 if entry.flags & 0x01 != 0 {
703 let mut full_name = [0u8; 34];
704 crate::dir::unpack_inline_name(&entry, &mut full_name);
705 name_buf[..inline_len].copy_from_slice(&full_name[..inline_len]);
706 } else if name_len > 0 {
707 let inode_data = child_inode.extended_attrs;
708 let copy_len = inline_len.min(inode_data.len());
709 name_buf[..copy_len].copy_from_slice(&inode_data[..copy_len]);
710 }
711
712 let dir_entry = ReadDirEntry {
713 object_id: child_inode.object_id,
714 size: child_inode.size,
715 name_len,
716 inode_type: child_inode.inode_type,
717 _pad: 0,
718 name: name_buf,
719 _pad2: [0; 4],
720 };
721
722 let write_pos = buf_offset + entries_written as usize * READDIR_ENTRY_SIZE;
723 let entry_bytes = dir_entry.as_bytes();
724 unsafe {
725 core::ptr::copy_nonoverlapping(
726 entry_bytes.as_ptr(),
727 data_area.add(write_pos),
728 READDIR_ENTRY_SIZE,
729 );
730 }
731
732 entries_written += 1;
733 next_cursor = entry.key.wrapping_add(1);
734 Ok(())
735 }
736 },
737 }
738 })?;
739
740 Ok((entries_written, next_cursor))
741 })();
742
743 match result {
744 Ok((count, next)) => FsResponse::success(FsOpcode::ReadDir as u8, tag, count, next),
745 Err(e) => FsResponse::error(FsOpcode::ReadDir as u8, tag, e),
746 }
747}
748
749#[allow(clippy::too_many_arguments)]
750fn handle_create_snapshot(
751 req: &FsRequest,
752 pool: &mut NodePool,
753 cache: &mut BlockCache,
754 bio: &mut BlockIo,
755 state: &mut FsState,
756 data_area: *mut u8,
757 data_area_size: usize,
758 client_pid: u16,
759) -> FsResponse {
760 let tag = req.tag;
761 let name_offset = req.arg0;
762 let name_len = req.arg1;
763
764 let result = (|| -> Result<(), FsError> {
765 let client = state
766 .handles
767 .client(client_pid)
768 .ok_or(FsError::InvalidHandle)?;
769 client.validate(req.handle, FsRights::SNAPSHOT)?;
770
771 let name = unsafe { read_name_from_data(data_area, data_area_size, name_offset, name_len) }
772 .ok_or(FsError::InvalidName)?;
773
774 let tree_root = state.sb_pair.active().tree_root;
775 let dedup_roots = state.dedup_roots;
776 let snapshot_root = state.sb_pair.active().snapshot_root;
777 let scrub_cursor = state.scrub.cursor();
778
779 snapshot::create_snapshot(
780 pool,
781 cache,
782 bio,
783 &mut state.freemap,
784 &mut state.reserved,
785 &mut state.sb_pair,
786 &tree_root,
787 &dedup_roots,
788 &snapshot_root,
789 name,
790 Some(&state.ditto),
791 scrub_cursor,
792 state.next_object_id,
793 )?;
794
795 state.dedup_roots = state.sb_pair.active().dedup_roots;
796 Ok(())
797 })();
798
799 match result {
800 Ok(()) => FsResponse::success(FsOpcode::CreateSnapshot as u8, tag, 0, 0),
801 Err(e) => FsResponse::error(FsOpcode::CreateSnapshot as u8, tag, e),
802 }
803}
804
805#[allow(clippy::too_many_arguments)]
806fn handle_delete_snapshot(
807 req: &FsRequest,
808 pool: &mut NodePool,
809 cache: &mut BlockCache,
810 bio: &mut BlockIo,
811 state: &mut FsState,
812 data_area: *mut u8,
813 data_area_size: usize,
814 client_pid: u16,
815) -> FsResponse {
816 let tag = req.tag;
817 let name_offset = req.arg0;
818 let name_len = req.arg1;
819
820 let result = (|| -> Result<(), FsError> {
821 let client = state
822 .handles
823 .client(client_pid)
824 .ok_or(FsError::InvalidHandle)?;
825 client.validate(req.handle, FsRights::SNAPSHOT)?;
826
827 let name = unsafe { read_name_from_data(data_area, data_area_size, name_offset, name_len) }
828 .ok_or(FsError::InvalidName)?;
829
830 let snapshot_root = state.sb_pair.active().snapshot_root;
831 let scrub_cursor = state.scrub.cursor();
832
833 snapshot::delete_snapshot(
834 pool,
835 cache,
836 bio,
837 &mut state.freemap,
838 &mut state.reserved,
839 &mut state.sb_pair,
840 &snapshot_root,
841 name,
842 Some(&state.ditto),
843 scrub_cursor,
844 state.next_object_id,
845 )?;
846
847 state.dedup_roots = state.sb_pair.active().dedup_roots;
848 Ok(())
849 })();
850
851 match result {
852 Ok(()) => FsResponse::success(FsOpcode::DeleteSnapshot as u8, tag, 0, 0),
853 Err(e) => FsResponse::error(FsOpcode::DeleteSnapshot as u8, tag, e),
854 }
855}
856
857#[allow(clippy::too_many_arguments)]
858fn handle_sync(
859 req: &FsRequest,
860 pool: &mut NodePool,
861 cache: &mut BlockCache,
862 bio: &mut BlockIo,
863 state: &mut FsState,
864 _scratch: &mut [u8],
865 _hash_table: &mut [u16; LZ4_HASH_SIZE],
866) -> FsResponse {
867 let tag = req.tag;
868
869 let result = (|| -> Result<(), FsError> {
870 let tree_root = state.sb_pair.active().tree_root;
871 let dedup_roots = state.dedup_roots;
872 let snapshot_root = state.sb_pair.active().snapshot_root;
873 let scrub_cursor = state.scrub.cursor();
874
875 transaction::transaction_commit(
876 pool,
877 cache,
878 bio,
879 &mut state.freemap,
880 &mut state.reserved,
881 &mut state.sb_pair,
882 &tree_root,
883 &dedup_roots,
884 &snapshot_root,
885 Some(&state.ditto),
886 scrub_cursor,
887 state.next_object_id,
888 )?;
889
890 state.dedup_roots = state.sb_pair.active().dedup_roots;
891 Ok(())
892 })();
893
894 match result {
895 Ok(()) => FsResponse::success(FsOpcode::Sync as u8, tag, 0, 0),
896 Err(e) => FsResponse::error(FsOpcode::Sync as u8, tag, e),
897 }
898}
899
900fn handle_statfs(
901 req: &FsRequest,
902 cache: &mut BlockCache,
903 bio: &mut BlockIo,
904 state: &mut FsState,
905) -> FsResponse {
906 let tag = req.tag;
907 let total = state.freemap.total_data_blocks();
908 let free = match state.freemap.count_allocated_blocks(cache, bio) {
909 Ok(allocated) => total.saturating_sub(allocated),
910 Err(_) => 0,
911 };
912 FsResponse::success(FsOpcode::StatFs as u8, tag, total, free)
913}
914
915fn handle_truncate(
916 req: &FsRequest,
917 _pool: &mut NodePool,
918 cache: &mut BlockCache,
919 bio: &mut BlockIo,
920 state: &mut FsState,
921 client_pid: u16,
922) -> FsResponse {
923 let tag = req.tag;
924 let handle = req.handle;
925 let new_size = req.arg0;
926
927 let result = (|| -> Result<(), FsError> {
928 let (inode_block, inode) = {
929 let client = state
930 .handles
931 .client(client_pid)
932 .ok_or(FsError::InvalidHandle)?;
933 let entry = client.validate(handle, FsRights::WRITE)?;
934 let inode = file::read_inode(cache, bio, entry.inode_block)?;
935 match inode.generation == entry.generation {
936 true => (entry.inode_block, inode),
937 false => return Err(FsError::StaleGeneration),
938 }
939 };
940
941 match inode.inode_type {
942 0 => {}
943 1 => return Err(FsError::IsADirectory),
944 _ => return Err(FsError::InvalidHandle),
945 }
946
947 match new_size == 0 {
948 true => {
949 let mut updated = inode.clone();
950 updated.size = 0;
951 updated.block_count = 0;
952 updated.flags |= InodeFlags::INLINE;
953 updated.direct = [BlockRef::ZERO; INODE_DIRECT_REFS];
954 let txn = state.sb_pair.active().transaction_id.wrapping_add(1);
955 updated.transaction_id = txn;
956 file::write_inode_to_cache(cache, bio, inode_block, &updated)?;
957 Ok(())
958 }
959 false => match new_size >= inode.size {
960 true => Ok(()),
961 false => Err(FsError::InvalidBlock),
962 },
963 }
964 })();
965
966 match result {
967 Ok(()) => FsResponse::success(FsOpcode::Truncate as u8, tag, new_size, 0),
968 Err(e) => FsResponse::error(FsOpcode::Truncate as u8, tag, e),
969 }
970}
971
972#[allow(clippy::too_many_arguments)]
973fn handle_rename(
974 req: &FsRequest,
975 pool: &mut NodePool,
976 cache: &mut BlockCache,
977 bio: &mut BlockIo,
978 state: &mut FsState,
979 data_area: *mut u8,
980 data_area_size: usize,
981 client_pid: u16,
982) -> FsResponse {
983 let tag = req.tag;
984 let src_dir_handle = req.handle;
985 let dst_dir_handle = req.flags;
986 let src_name_offset = req.arg0 as u32 as u64;
987 let src_name_len = (req.arg0 >> 32) as u32 as u64;
988 let dst_name_offset = req.arg1 as u32 as u64;
989 let dst_name_len = (req.arg1 >> 32) as u32 as u64;
990
991 let result = (|| -> Result<(), FsError> {
992 let (src_dir_block, dst_dir_block) = {
993 let client = state
994 .handles
995 .client(client_pid)
996 .ok_or(FsError::InvalidHandle)?;
997 let src_entry = client.validate(src_dir_handle, FsRights::DELETE)?;
998 let dst_entry = client.validate(dst_dir_handle, FsRights::CREATE)?;
999 (src_entry.inode_block, dst_entry.inode_block)
1000 };
1001
1002 let src_name = unsafe {
1003 read_name_from_data(data_area, data_area_size, src_name_offset, src_name_len)
1004 }
1005 .ok_or(FsError::InvalidName)?;
1006 let dst_name = unsafe {
1007 read_name_from_data(data_area, data_area_size, dst_name_offset, dst_name_len)
1008 }
1009 .ok_or(FsError::InvalidName)?;
1010
1011 let mut src_name_buf = [0u8; MAX_NAME_LEN];
1012 src_name_buf[..src_name.len()].copy_from_slice(src_name);
1013 let src_len = src_name.len();
1014
1015 let mut dst_name_buf = [0u8; MAX_NAME_LEN];
1016 dst_name_buf[..dst_name.len()].copy_from_slice(dst_name);
1017 let dst_len = dst_name.len();
1018
1019 let src_dir_inode = file::read_inode(cache, bio, src_dir_block)?;
1020 let child_block = {
1021 let child_cap = ops::resolve_path_cap(
1022 pool,
1023 cache,
1024 bio,
1025 &state
1026 .handles
1027 .client(client_pid)
1028 .ok_or(FsError::InvalidHandle)?
1029 .validate(src_dir_handle, FsRights::TRAVERSE)?
1030 .to_cap(),
1031 &src_dir_inode,
1032 src_dir_block,
1033 &src_name_buf[..src_len],
1034 None,
1035 )?;
1036 child_cap.2
1037 };
1038 let child_phys = crate::block_num_to_phys(child_block);
1039
1040 let txn = state.sb_pair.active().transaction_id.wrapping_add(1);
1041
1042 let updated_src = ops::file_delete(
1043 pool,
1044 cache,
1045 bio,
1046 &src_dir_inode,
1047 &src_name_buf[..src_len],
1048 txn,
1049 )?;
1050 file::write_inode_to_cache(cache, bio, src_dir_block, &updated_src)?;
1051
1052 let dst_dir_inode = match src_dir_block == dst_dir_block {
1053 true => updated_src,
1054 false => file::read_inode(cache, bio, dst_dir_block)?,
1055 };
1056
1057 let updated_dst = crate::dir::dir_insert(
1058 pool,
1059 cache,
1060 bio,
1061 &dst_dir_inode,
1062 &dst_name_buf[..dst_len],
1063 child_phys,
1064 txn,
1065 )?;
1066 file::write_inode_to_cache(cache, bio, dst_dir_block, &updated_dst)?;
1067
1068 Ok(())
1069 })();
1070
1071 match result {
1072 Ok(()) => FsResponse::success(FsOpcode::Rename as u8, tag, 0, 0),
1073 Err(e) => FsResponse::error(FsOpcode::Rename as u8, tag, e),
1074 }
1075}