1use crate::engine::block::BlockStore;
2use crate::engine::error::{VfsError, VfsResult};
3use crate::engine::metadata::MetadataStore;
4use crate::engine::types::{
5 decode_unwritten_extents, encode_unwritten_extents, normalize_path, set_xattr_value,
6 unwritten_after_allocate, unwritten_after_collapse, unwritten_after_insert,
7 unwritten_after_truncate, unwritten_after_write, unwritten_after_zero, unwritten_byte_ranges,
8 validate_xattr_name, BlockKey, ChunkEdit, ChunkRange, CreateInodeAttrs, Dentry, InodeMeta,
9 InodePatch, InodeType, SnapshotId, Storage, Timespec, VirtualStat, DEFAULT_CHUNK_SIZE,
10 DEFAULT_INLINE_THRESHOLD, INTERNAL_XATTR_PREFIX,
11};
12use crate::engine::vfs::{Snapshottable, VirtualFileSystem};
13use async_trait::async_trait;
14use std::collections::BTreeMap;
15
16#[derive(Debug, Clone)]
17pub struct ChunkedFsOptions {
18 pub inline_threshold: usize,
19 pub chunk_size: u32,
20 pub uid: u32,
21 pub gid: u32,
22 pub file_mode: u32,
23 pub dir_mode: u32,
24}
25
26impl Default for ChunkedFsOptions {
27 fn default() -> Self {
28 Self {
29 inline_threshold: DEFAULT_INLINE_THRESHOLD,
30 chunk_size: DEFAULT_CHUNK_SIZE,
31 uid: 0,
32 gid: 0,
33 file_mode: 0o644,
34 dir_mode: 0o755,
35 }
36 }
37}
38
39#[derive(Debug, Clone)]
40pub struct ChunkedFs<M, B> {
41 metadata: M,
42 blocks: B,
43 options: ChunkedFsOptions,
44 adaptive_chunk_size: bool,
45}
46
47const MAX_ADAPTIVE_CHUNK_SIZE: u32 = 1024 * 1024;
48
49impl<M, B> ChunkedFs<M, B> {
50 pub fn new(metadata: M, blocks: B) -> Self {
51 Self::with_options(metadata, blocks, ChunkedFsOptions::default())
52 }
53
54 pub fn with_options(metadata: M, blocks: B, options: ChunkedFsOptions) -> Self {
55 Self {
56 metadata,
57 blocks,
58 options,
59 adaptive_chunk_size: false,
60 }
61 }
62
63 pub fn with_adaptive_chunk_size(metadata: M, blocks: B, options: ChunkedFsOptions) -> Self {
64 Self {
65 metadata,
66 blocks,
67 options,
68 adaptive_chunk_size: true,
69 }
70 }
71
72 pub fn metadata(&self) -> &M {
73 &self.metadata
74 }
75
76 pub fn blocks(&self) -> &B {
77 &self.blocks
78 }
79}
80
81impl<M: MetadataStore, B: BlockStore> ChunkedFs<M, B> {
82 fn initial_chunk_size(&self, write_len: usize) -> u32 {
83 if !self.adaptive_chunk_size {
84 return self.options.chunk_size;
85 }
86 let write_len = u32::try_from(write_len).unwrap_or(u32::MAX);
87 write_len
88 .max(self.options.chunk_size)
89 .min(MAX_ADAPTIVE_CHUNK_SIZE.max(self.options.chunk_size))
90 }
91
92 async fn write_existing_or_create(&self, path: &str, content: &[u8]) -> VfsResult<()> {
93 let (parent, name) = self.metadata.resolve_parent(path).await?;
94 let existing = self.metadata.lstat(path).await.ok();
95 let (ino, mut xattrs) = match existing {
96 Some(meta) => {
97 if meta.kind == InodeType::Directory {
98 return Err(VfsError::eisdir(path));
99 }
100 (meta.ino, meta.xattrs)
101 }
102 None => {
103 let chunk_size = self.initial_chunk_size(content.len());
104 let storage = if content.len() <= self.options.inline_threshold {
105 Storage::Inline(content.to_vec())
106 } else {
107 Storage::Chunked { chunk_size }
108 };
109 let meta = self
110 .metadata
111 .create(
112 parent.ino,
113 &name,
114 CreateInodeAttrs::file(
115 self.options.file_mode,
116 self.options.uid,
117 self.options.gid,
118 storage,
119 ),
120 )
121 .await?;
122 if content.len() <= self.options.inline_threshold {
123 return Ok(());
124 }
125 (meta.ino, meta.xattrs)
126 }
127 };
128 encode_unwritten_extents(&mut xattrs, &[]);
129
130 if content.len() <= self.options.inline_threshold {
131 let freed = self
132 .metadata
133 .set_attr(
134 ino,
135 InodePatch {
136 storage: Some(Storage::Inline(content.to_vec())),
137 size: Some(content.len() as u64),
138 xattrs: Some(xattrs),
139 ..InodePatch::default()
140 },
141 )
142 .await?;
143 self.blocks.delete_many(&freed).await?;
144 return Ok(());
145 }
146
147 let chunk_size = self.initial_chunk_size(content.len());
148 let mut edits = Vec::new();
149 for (index, chunk) in content.chunks(chunk_size as usize).enumerate() {
150 let key = BlockKey::from_content(chunk);
151 if !self.blocks.exists(&key).await? {
152 self.blocks.put(&key, chunk).await?;
153 }
154 edits.push(ChunkEdit {
155 index: index as u64,
156 key,
157 len: chunk.len() as u32,
158 });
159 }
160 self.metadata
161 .set_attr(
162 ino,
163 InodePatch {
164 storage: Some(Storage::Chunked { chunk_size }),
165 size: Some(content.len() as u64),
166 xattrs: Some(xattrs),
167 ..InodePatch::default()
168 },
169 )
170 .await?;
171 let freed = self
172 .metadata
173 .commit_write(
174 ino,
175 edits,
176 content.len() as u64,
177 dense_allocation(content.len() as u64),
178 )
179 .await?;
180 self.blocks.delete_many(&freed).await?;
181 Ok(())
182 }
183
184 fn file_chunk_size(&self, storage: &Storage) -> u32 {
185 match storage {
186 Storage::Chunked { chunk_size } => *chunk_size,
187 Storage::Inline(_) | Storage::None => self.options.chunk_size,
188 }
189 }
190
191 fn ensure_file<'a>(&self, path: &str, meta: &'a InodeMeta) -> VfsResult<&'a InodeMeta> {
192 match meta.kind {
193 InodeType::File => Ok(meta),
194 InodeType::Directory => Err(VfsError::eisdir(path)),
195 InodeType::Symlink => Err(VfsError::eopnotsupp("resolved symlink without target file")),
196 InodeType::CharacterDevice | InodeType::BlockDevice | InodeType::Fifo => {
197 Err(VfsError::new(
198 "ENXIO",
199 format!("device I/O requires kernel dispatch: {path}"),
200 ))
201 }
202 }
203 }
204
205 async fn read_file_range(
206 &self,
207 meta: &InodeMeta,
208 offset: u64,
209 length: usize,
210 ) -> VfsResult<Vec<u8>> {
211 if length == 0 || offset >= meta.size {
212 return Ok(Vec::new());
213 }
214 let available = meta.size.saturating_sub(offset).min(length as u64);
215 let output_len = usize::try_from(available)
216 .map_err(|_| VfsError::einval(format!("range length is too large: {available}")))?;
217
218 match &meta.storage {
219 Storage::Inline(data) => {
220 let start = usize::try_from(offset).map_err(|_| {
221 VfsError::einval(format!("range offset is too large: {offset}"))
222 })?;
223 if start >= data.len() {
224 return Ok(vec![0; output_len]);
225 }
226 let end = start.saturating_add(output_len).min(data.len());
227 let mut output = vec![0; output_len];
228 output[..end - start].copy_from_slice(&data[start..end]);
229 Ok(output)
230 }
231 Storage::None => Ok(vec![0; output_len]),
232 Storage::Chunked { chunk_size } => {
233 let chunk_size = u64::from(*chunk_size);
234 let end_offset = offset
235 .checked_add(available)
236 .ok_or_else(|| VfsError::einval("range end overflows"))?;
237 let start_index = offset / chunk_size;
238 let end_index = end_offset.div_ceil(chunk_size);
239 let chunks = self
240 .metadata
241 .get_chunks(
242 meta.ino,
243 ChunkRange {
244 start: start_index,
245 end: Some(end_index),
246 },
247 )
248 .await?;
249 let mut output = vec![0; output_len];
250 for chunk in chunks {
251 let chunk_start = chunk.index.saturating_mul(chunk_size);
252 let block = self.blocks.get(&chunk.key).await?;
253 let copy_start = offset.max(chunk_start);
254 let copy_end = end_offset.min(chunk_start.saturating_add(block.len() as u64));
255 if copy_start >= copy_end {
256 continue;
257 }
258 let output_start = usize::try_from(copy_start - offset)
259 .map_err(|_| VfsError::einval("range output offset is too large"))?;
260 let block_start = usize::try_from(copy_start - chunk_start)
261 .map_err(|_| VfsError::einval("range block offset is too large"))?;
262 let len = usize::try_from(copy_end - copy_start)
263 .map_err(|_| VfsError::einval("range copy length is too large"))?;
264 output[output_start..output_start + len]
265 .copy_from_slice(&block[block_start..block_start + len]);
266 }
267 Ok(output)
268 }
269 }
270 }
271
272 async fn put_chunk_edit(&self, index: u64, data: Vec<u8>) -> VfsResult<ChunkEdit> {
273 let len = u32::try_from(data.len())
274 .map_err(|_| VfsError::einval(format!("chunk is too large: {}", data.len())))?;
275 let key = BlockKey::from_content(&data);
276 if !self.blocks.exists(&key).await? {
277 self.blocks.put(&key, &data).await?;
278 }
279 Ok(ChunkEdit { index, key, len })
280 }
281
282 async fn set_unwritten_extents(
283 &self,
284 meta: &InodeMeta,
285 extents: &[(u64, u64)],
286 ) -> VfsResult<()> {
287 let mut xattrs = meta.xattrs.clone();
288 encode_unwritten_extents(&mut xattrs, extents);
289 let freed = self
290 .metadata
291 .set_attr(
292 meta.ino,
293 InodePatch {
294 xattrs: Some(xattrs),
295 ..InodePatch::default()
296 },
297 )
298 .await?;
299 self.blocks.delete_many(&freed).await
300 }
301
302 async fn write_chunked_range(
303 &self,
304 meta: &InodeMeta,
305 content: &[u8],
306 offset: u64,
307 ) -> VfsResult<u64> {
308 if content.is_empty() {
309 return Ok(meta.size);
310 }
311 let content_len = u64::try_from(content.len()).map_err(|_| {
312 VfsError::einval(format!("pwrite content is too large: {}", content.len()))
313 })?;
314 let end_offset = offset
315 .checked_add(content_len)
316 .ok_or_else(|| VfsError::einval("pwrite end offset overflows"))?;
317 let new_size = meta.size.max(end_offset);
318
319 if !matches!(meta.storage, Storage::Chunked { .. })
320 && usize::try_from(new_size)
321 .ok()
322 .is_some_and(|len| len <= self.options.inline_threshold)
323 {
324 let old_len = usize::try_from(meta.size)
325 .map_err(|_| VfsError::einval(format!("file is too large: {}", meta.size)))?;
326 let mut data = self.read_file_range(meta, 0, old_len).await?;
327 let start = usize::try_from(offset)
328 .map_err(|_| VfsError::einval(format!("pwrite offset is too large: {offset}")))?;
329 let end = start.saturating_add(content.len());
330 if start > data.len() {
331 data.resize(start, 0);
332 }
333 if end > data.len() {
334 data.resize(end, 0);
335 }
336 data[start..end].copy_from_slice(content);
337 let mut xattrs = meta.xattrs.clone();
338 encode_unwritten_extents(
339 &mut xattrs,
340 &unwritten_after_write(
341 &decode_unwritten_extents(&meta.xattrs)?,
342 offset,
343 content_len,
344 ),
345 );
346 let freed = self
347 .metadata
348 .set_attr(
349 meta.ino,
350 InodePatch {
351 storage: Some(Storage::Inline(data)),
352 size: Some(new_size),
353 allocated_extents: Some(allocation_after_write(
354 &meta.allocated_extents,
355 offset,
356 content_len,
357 )),
358 xattrs: Some(xattrs),
359 ..InodePatch::default()
360 },
361 )
362 .await?;
363 self.blocks.delete_many(&freed).await?;
364 return Ok(new_size);
365 }
366
367 let chunk_size = u64::from(match &meta.storage {
368 Storage::Chunked { chunk_size } => *chunk_size,
369 Storage::Inline(_) | Storage::None => self.initial_chunk_size(content.len()),
370 });
371 let start_index = offset / chunk_size;
372 let end_index = end_offset.div_ceil(chunk_size);
373 let existing_chunks = if matches!(meta.storage, Storage::Chunked { .. }) {
374 self.metadata
375 .get_chunks(
376 meta.ino,
377 ChunkRange {
378 start: start_index,
379 end: Some(end_index),
380 },
381 )
382 .await?
383 .into_iter()
384 .map(|chunk| (chunk.index, chunk.key))
385 .collect::<BTreeMap<_, _>>()
386 } else {
387 BTreeMap::new()
388 };
389
390 let mut edits = Vec::new();
391 if let Storage::Inline(data) = &meta.storage {
392 let inline_chunks = u64::try_from(data.len())
393 .map_err(|_| VfsError::einval("inline data is too large"))?
394 .div_ceil(chunk_size);
395 for index in 0..inline_chunks.min(start_index) {
396 let chunk_start = index.saturating_mul(chunk_size);
397 let chunk_len = chunk_size.min(new_size.saturating_sub(chunk_start));
398 let mut chunk_data = vec![
399 0;
400 usize::try_from(chunk_len).map_err(|_| {
401 VfsError::einval("inline prefix chunk is too large")
402 })?
403 ];
404 let copy_start = usize::try_from(chunk_start)
405 .map_err(|_| VfsError::einval("inline prefix offset is too large"))?;
406 let copy_end = data.len().min(copy_start.saturating_add(chunk_data.len()));
407 chunk_data[..copy_end - copy_start].copy_from_slice(&data[copy_start..copy_end]);
408 edits.push(self.put_chunk_edit(index, chunk_data).await?);
409 }
410 }
411 for index in start_index..end_index {
412 let chunk_start = index.saturating_mul(chunk_size);
413 let chunk_len = chunk_size.min(new_size.saturating_sub(chunk_start));
414 let mut chunk_data = vec![
415 0;
416 usize::try_from(chunk_len).map_err(|_| {
417 VfsError::einval("chunk length is too large")
418 })?
419 ];
420
421 match &meta.storage {
422 Storage::Inline(data) => {
423 let copy_start = chunk_start.min(data.len() as u64);
424 let copy_end = chunk_start.saturating_add(chunk_len).min(data.len() as u64);
425 if copy_start < copy_end {
426 let dst = usize::try_from(copy_start - chunk_start)
427 .map_err(|_| VfsError::einval("inline chunk offset is too large"))?;
428 let src = usize::try_from(copy_start)
429 .map_err(|_| VfsError::einval("inline source offset is too large"))?;
430 let len = usize::try_from(copy_end - copy_start)
431 .map_err(|_| VfsError::einval("inline copy length is too large"))?;
432 chunk_data[dst..dst + len].copy_from_slice(&data[src..src + len]);
433 }
434 }
435 Storage::Chunked { .. } => {
436 if let Some(key) = existing_chunks.get(&index) {
437 let old = self.blocks.get(key).await?;
438 let len = old.len().min(chunk_data.len());
439 chunk_data[..len].copy_from_slice(&old[..len]);
440 }
441 }
442 Storage::None => {}
443 }
444
445 let write_start = offset.max(chunk_start);
446 let write_end = end_offset.min(chunk_start.saturating_add(chunk_len));
447 if write_start < write_end {
448 let dst = usize::try_from(write_start - chunk_start)
449 .map_err(|_| VfsError::einval("chunk write offset is too large"))?;
450 let src = usize::try_from(write_start - offset)
451 .map_err(|_| VfsError::einval("content write offset is too large"))?;
452 let len = usize::try_from(write_end - write_start)
453 .map_err(|_| VfsError::einval("chunk write length is too large"))?;
454 chunk_data[dst..dst + len].copy_from_slice(&content[src..src + len]);
455 }
456
457 edits.push(self.put_chunk_edit(index, chunk_data).await?);
458 }
459
460 if !matches!(meta.storage, Storage::Chunked { .. }) {
461 self.metadata
462 .set_attr(
463 meta.ino,
464 InodePatch {
465 storage: Some(Storage::Chunked {
466 chunk_size: u32::try_from(chunk_size)
467 .map_err(|_| VfsError::einval("chunk size is too large"))?,
468 }),
469 size: Some(new_size),
470 ..InodePatch::default()
471 },
472 )
473 .await?;
474 }
475 let freed = self
476 .metadata
477 .commit_write(
478 meta.ino,
479 edits,
480 new_size,
481 allocation_after_write(&meta.allocated_extents, offset, content_len),
482 )
483 .await?;
484 self.blocks.delete_many(&freed).await?;
485 self.set_unwritten_extents(
486 meta,
487 &unwritten_after_write(
488 &decode_unwritten_extents(&meta.xattrs)?,
489 offset,
490 content_len,
491 ),
492 )
493 .await?;
494 Ok(new_size)
495 }
496}
497
498#[async_trait]
499impl<M: MetadataStore, B: BlockStore> VirtualFileSystem for ChunkedFs<M, B> {
500 async fn read_file(&self, path: &str) -> VfsResult<Vec<u8>> {
501 let meta = self.metadata.resolve(path).await?;
502 self.ensure_file(path, &meta)?;
503 let len = usize::try_from(meta.size)
504 .map_err(|_| VfsError::einval(format!("file is too large: {}", meta.size)))?;
505 self.read_file_range(&meta, 0, len).await
506 }
507
508 async fn read_dir(&self, path: &str) -> VfsResult<Vec<String>> {
509 let meta = self.metadata.resolve(path).await?;
510 Ok(self
511 .metadata
512 .list_dir(meta.ino)
513 .await?
514 .into_iter()
515 .map(|entry| entry.name)
516 .collect())
517 }
518
519 async fn read_dir_with_types(&self, path: &str) -> VfsResult<Vec<Dentry>> {
520 let meta = self.metadata.resolve(path).await?;
521 Ok(self
522 .metadata
523 .list_dir(meta.ino)
524 .await?
525 .into_iter()
526 .map(|entry| Dentry {
527 name: entry.name,
528 ino: entry.meta.ino,
529 kind: entry.meta.kind,
530 })
531 .collect())
532 }
533
534 async fn write_file(&self, path: &str, content: &[u8]) -> VfsResult<()> {
535 self.write_existing_or_create(path, content).await
536 }
537
538 async fn create_dir(&self, path: &str) -> VfsResult<()> {
539 let (parent, name) = self.metadata.resolve_parent(path).await?;
540 self.metadata
541 .create(
542 parent.ino,
543 &name,
544 CreateInodeAttrs::directory(
545 self.options.dir_mode,
546 self.options.uid,
547 self.options.gid,
548 ),
549 )
550 .await?;
551 Ok(())
552 }
553
554 async fn mkdir(&self, path: &str, recursive: bool) -> VfsResult<()> {
555 if !recursive {
556 return self.create_dir(path).await;
557 }
558 let normalized = normalize_path(path)?;
559 let mut current = String::new();
560 for part in normalized
561 .trim_start_matches('/')
562 .split('/')
563 .filter(|p| !p.is_empty())
564 {
565 current.push('/');
566 current.push_str(part);
567 if !self.exists(¤t).await {
568 self.create_dir(¤t).await?;
569 }
570 }
571 Ok(())
572 }
573
574 async fn mknod(&self, path: &str, mode: u32, rdev: u64) -> VfsResult<()> {
575 let (parent, name) = self.metadata.resolve_parent(path).await?;
576 self.metadata
577 .create(
578 parent.ino,
579 &name,
580 CreateInodeAttrs::special_node(mode, self.options.uid, self.options.gid, rdev)?,
581 )
582 .await?;
583 Ok(())
584 }
585
586 async fn exists(&self, path: &str) -> bool {
587 self.metadata.resolve(path).await.is_ok()
588 }
589
590 async fn stat(&self, path: &str) -> VfsResult<VirtualStat> {
591 Ok(self.metadata.resolve(path).await?.to_stat())
592 }
593
594 async fn lstat(&self, path: &str) -> VfsResult<VirtualStat> {
595 Ok(self.metadata.lstat(path).await?.to_stat())
596 }
597
598 async fn remove_file(&self, path: &str) -> VfsResult<()> {
599 let meta = self.metadata.lstat(path).await?;
600 if meta.kind == InodeType::Directory {
601 return Err(VfsError::eisdir(path));
602 }
603 let (parent, name) = self.metadata.resolve_parent(path).await?;
604 let freed = self.metadata.remove(parent.ino, &name).await?;
605 self.blocks.delete_many(&freed).await
606 }
607
608 async fn remove_dir(&self, path: &str) -> VfsResult<()> {
609 let meta = self.metadata.lstat(path).await?;
610 if meta.kind != InodeType::Directory {
611 return Err(VfsError::enotdir(path));
612 }
613 let (parent, name) = self.metadata.resolve_parent(path).await?;
614 let freed = self.metadata.remove(parent.ino, &name).await?;
615 self.blocks.delete_many(&freed).await
616 }
617
618 async fn rename(&self, old_path: &str, new_path: &str) -> VfsResult<()> {
619 let (src_parent, src) = self.metadata.resolve_parent(old_path).await?;
620 let (dst_parent, dst) = self.metadata.resolve_parent(new_path).await?;
621 let freed = self
622 .metadata
623 .rename(src_parent.ino, &src, dst_parent.ino, &dst)
624 .await?;
625 self.blocks.delete_many(&freed).await
626 }
627
628 async fn realpath(&self, path: &str) -> VfsResult<String> {
629 self.metadata.resolve(path).await?;
630 normalize_path(path)
631 }
632
633 async fn symlink(&self, target: &str, link_path: &str) -> VfsResult<()> {
634 let (parent, name) = self.metadata.resolve_parent(link_path).await?;
635 self.metadata
636 .create(
637 parent.ino,
638 &name,
639 CreateInodeAttrs::symlink(target.to_string(), self.options.uid, self.options.gid),
640 )
641 .await?;
642 Ok(())
643 }
644
645 async fn readlink(&self, path: &str) -> VfsResult<String> {
646 let meta = self.metadata.lstat(path).await?;
647 if meta.kind != InodeType::Symlink {
648 return Err(VfsError::einval(format!("not a symlink: {path}")));
649 }
650 Ok(meta.symlink_target.unwrap_or_default())
651 }
652
653 async fn link(&self, old_path: &str, new_path: &str) -> VfsResult<()> {
654 let target = self.metadata.resolve(old_path).await?;
655 let (parent, name) = self.metadata.resolve_parent(new_path).await?;
656 self.metadata.link(parent.ino, &name, target.ino).await
657 }
658
659 async fn chmod(&self, path: &str, mode: u32) -> VfsResult<()> {
660 let meta = self.metadata.resolve(path).await?;
661 self.metadata
662 .set_attr(
663 meta.ino,
664 InodePatch {
665 mode: Some(mode),
666 ..InodePatch::default()
667 },
668 )
669 .await?;
670 Ok(())
671 }
672
673 async fn chown(&self, path: &str, uid: u32, gid: u32) -> VfsResult<()> {
674 let meta = self.metadata.resolve(path).await?;
675 self.metadata
676 .set_attr(
677 meta.ino,
678 InodePatch {
679 uid: Some(uid),
680 gid: Some(gid),
681 ..InodePatch::default()
682 },
683 )
684 .await?;
685 Ok(())
686 }
687
688 async fn lchown(&self, path: &str, uid: u32, gid: u32) -> VfsResult<()> {
689 let meta = self.metadata.lstat(path).await?;
690 self.metadata
691 .set_attr(
692 meta.ino,
693 InodePatch {
694 uid: Some(uid),
695 gid: Some(gid),
696 ..InodePatch::default()
697 },
698 )
699 .await?;
700 Ok(())
701 }
702
703 async fn get_xattr(&self, path: &str, name: &str, follow_symlinks: bool) -> VfsResult<Vec<u8>> {
704 validate_xattr_name(name)?;
705 let meta = if follow_symlinks {
706 self.metadata.resolve(path).await?
707 } else {
708 self.metadata.lstat(path).await?
709 };
710 meta.xattrs.get(name).cloned().ok_or_else(|| {
711 VfsError::new(
712 "ENODATA",
713 format!("extended attribute does not exist: {name}"),
714 )
715 })
716 }
717
718 async fn list_xattrs(&self, path: &str, follow_symlinks: bool) -> VfsResult<Vec<String>> {
719 let meta = if follow_symlinks {
720 self.metadata.resolve(path).await?
721 } else {
722 self.metadata.lstat(path).await?
723 };
724 Ok(meta
725 .xattrs
726 .into_keys()
727 .filter(|name| !name.starts_with(INTERNAL_XATTR_PREFIX))
728 .collect())
729 }
730
731 async fn set_xattr(
732 &self,
733 path: &str,
734 name: &str,
735 value: &[u8],
736 flags: u32,
737 follow_symlinks: bool,
738 ) -> VfsResult<()> {
739 let meta = if follow_symlinks {
740 self.metadata.resolve(path).await?
741 } else {
742 self.metadata.lstat(path).await?
743 };
744 let mut xattrs = meta.xattrs;
745 set_xattr_value(&mut xattrs, name, value, flags)?;
746 self.metadata
747 .set_attr(
748 meta.ino,
749 InodePatch {
750 xattrs: Some(xattrs),
751 ..InodePatch::default()
752 },
753 )
754 .await?;
755 Ok(())
756 }
757
758 async fn remove_xattr(&self, path: &str, name: &str, follow_symlinks: bool) -> VfsResult<()> {
759 validate_xattr_name(name)?;
760 let meta = if follow_symlinks {
761 self.metadata.resolve(path).await?
762 } else {
763 self.metadata.lstat(path).await?
764 };
765 let mut xattrs = meta.xattrs;
766 if xattrs.remove(name).is_none() {
767 return Err(VfsError::new(
768 "ENODATA",
769 format!("extended attribute does not exist: {name}"),
770 ));
771 }
772 self.metadata
773 .set_attr(
774 meta.ino,
775 InodePatch {
776 xattrs: Some(xattrs),
777 ..InodePatch::default()
778 },
779 )
780 .await?;
781 Ok(())
782 }
783
784 async fn utimes(&self, path: &str, atime_ms: u64, mtime_ms: u64) -> VfsResult<()> {
785 let meta = self.metadata.resolve(path).await?;
786 self.metadata
787 .set_attr(
788 meta.ino,
789 InodePatch {
790 atime: Some(ms_to_timespec(atime_ms)),
791 mtime: Some(ms_to_timespec(mtime_ms)),
792 ..InodePatch::default()
793 },
794 )
795 .await?;
796 Ok(())
797 }
798
799 async fn set_atime(&self, path: &str, atime_ms: u64) -> VfsResult<()> {
800 let meta = self.metadata.resolve(path).await?;
801 self.metadata
802 .set_attr(
803 meta.ino,
804 InodePatch {
805 atime: Some(ms_to_timespec(atime_ms)),
806 ..InodePatch::default()
807 },
808 )
809 .await?;
810 Ok(())
811 }
812
813 async fn truncate(&self, path: &str, length: u64) -> VfsResult<()> {
814 let meta = self.metadata.resolve(path).await?;
815 self.ensure_file(path, &meta)?;
816 if length == meta.size {
817 return Ok(());
818 }
819
820 if usize::try_from(length)
821 .ok()
822 .is_some_and(|len| len <= self.options.inline_threshold)
823 {
824 let data = self
825 .read_file_range(&meta, 0, usize::try_from(length).unwrap_or(0))
826 .await?;
827 let mut xattrs = meta.xattrs.clone();
828 encode_unwritten_extents(
829 &mut xattrs,
830 &unwritten_after_truncate(&decode_unwritten_extents(&meta.xattrs)?, length),
831 );
832 let freed = self
833 .metadata
834 .set_attr(
835 meta.ino,
836 InodePatch {
837 storage: Some(Storage::Inline(data)),
838 size: Some(length),
839 allocated_extents: Some(allocation_after_truncate(
840 &meta.allocated_extents,
841 length,
842 )),
843 xattrs: Some(xattrs),
844 ..InodePatch::default()
845 },
846 )
847 .await?;
848 self.blocks.delete_many(&freed).await?;
849 return Ok(());
850 }
851
852 let chunk_size = u64::from(self.file_chunk_size(&meta.storage));
853 let mut edits = Vec::new();
854 if !matches!(meta.storage, Storage::Chunked { .. }) {
855 let existing_len = meta.size.min(length);
856 let mut offset = 0;
857 while offset < existing_len {
858 let len = (existing_len - offset).min(chunk_size);
859 let data = self
860 .read_file_range(
861 &meta,
862 offset,
863 usize::try_from(len)
864 .map_err(|_| VfsError::einval("truncate chunk is too large"))?,
865 )
866 .await?;
867 edits.push(self.put_chunk_edit(offset / chunk_size, data).await?);
868 offset = offset.saturating_add(len);
869 }
870 self.metadata
871 .set_attr(
872 meta.ino,
873 InodePatch {
874 storage: Some(Storage::Chunked {
875 chunk_size: self.options.chunk_size,
876 }),
877 size: Some(length),
878 ..InodePatch::default()
879 },
880 )
881 .await?;
882 } else if length < meta.size && !length.is_multiple_of(chunk_size) {
883 let final_index = length / chunk_size;
884 let final_start = final_index.saturating_mul(chunk_size);
885 let final_len = length - final_start;
886 let data = self
887 .read_file_range(
888 &meta,
889 final_start,
890 usize::try_from(final_len)
891 .map_err(|_| VfsError::einval("truncate final chunk is too large"))?,
892 )
893 .await?;
894 edits.push(self.put_chunk_edit(final_index, data).await?);
895 }
896
897 let freed = self
898 .metadata
899 .commit_write(
900 meta.ino,
901 edits,
902 length,
903 allocation_after_truncate(&meta.allocated_extents, length),
904 )
905 .await?;
906 self.blocks.delete_many(&freed).await?;
907 self.set_unwritten_extents(
908 &meta,
909 &unwritten_after_truncate(&decode_unwritten_extents(&meta.xattrs)?, length),
910 )
911 .await
912 }
913
914 async fn allocate(&self, path: &str, offset: u64, length: u64) -> VfsResult<()> {
915 let end = offset
916 .checked_add(length)
917 .ok_or_else(|| VfsError::einval("allocation range overflows"))?;
918 let meta = self.metadata.resolve(path).await?;
919 self.ensure_file(path, &meta)?;
920 let mut xattrs = meta.xattrs.clone();
921 encode_unwritten_extents(
922 &mut xattrs,
923 &unwritten_after_allocate(
924 &decode_unwritten_extents(&meta.xattrs)?,
925 &meta.allocated_extents,
926 offset,
927 length,
928 ),
929 );
930 let freed = self
931 .metadata
932 .set_attr(
933 meta.ino,
934 InodePatch {
935 size: Some(meta.size.max(end)),
936 allocated_extents: Some(allocation_after_write(
937 &meta.allocated_extents,
938 offset,
939 length,
940 )),
941 xattrs: Some(xattrs),
942 ..InodePatch::default()
943 },
944 )
945 .await?;
946 self.blocks.delete_many(&freed).await
947 }
948
949 async fn punch_hole(&self, path: &str, offset: u64, length: u64) -> VfsResult<()> {
950 const PUNCH_CHUNK_BYTES: u64 = 64 * 1024;
951
952 let requested_end = offset
953 .checked_add(length)
954 .ok_or_else(|| VfsError::einval("hole-punch range overflows"))?;
955 let meta = self.metadata.resolve(path).await?;
956 self.ensure_file(path, &meta)?;
957 let end = requested_end.min(meta.size);
958 let mut cursor = offset.min(meta.size);
959 while cursor < end {
960 let chunk_len = usize::try_from((end - cursor).min(PUNCH_CHUNK_BYTES))
961 .map_err(|_| VfsError::einval("hole-punch chunk is too large"))?;
962 self.write_chunked_range(&meta, &vec![0; chunk_len], cursor)
963 .await?;
964 cursor += chunk_len as u64;
965 }
966 let freed = self
967 .metadata
968 .set_attr(
969 meta.ino,
970 InodePatch {
971 allocated_extents: Some(allocation_after_punch(
972 &meta.allocated_extents,
973 offset,
974 length,
975 )),
976 xattrs: Some({
977 let mut xattrs = meta.xattrs.clone();
978 encode_unwritten_extents(
979 &mut xattrs,
980 &unwritten_after_write(
981 &decode_unwritten_extents(&meta.xattrs)?,
982 offset,
983 length,
984 ),
985 );
986 xattrs
987 }),
988 ..InodePatch::default()
989 },
990 )
991 .await?;
992 self.blocks.delete_many(&freed).await
993 }
994
995 async fn zero_range(
996 &self,
997 path: &str,
998 offset: u64,
999 length: u64,
1000 keep_size: bool,
1001 ) -> VfsResult<()> {
1002 const ZERO_CHUNK_BYTES: u64 = 64 * 1024;
1003
1004 let end = offset
1005 .checked_add(length)
1006 .ok_or_else(|| VfsError::einval("zero range overflows"))?;
1007 if length == 0 {
1008 return Err(VfsError::einval("zero range length must be nonzero"));
1009 }
1010 let original = self.metadata.resolve(path).await?;
1011 self.ensure_file(path, &original)?;
1012 let zero_end = if keep_size {
1013 end.min(original.size)
1014 } else {
1015 end
1016 };
1017 let mut cursor = offset.min(zero_end);
1018 while cursor < zero_end {
1019 let chunk_len = usize::try_from((zero_end - cursor).min(ZERO_CHUNK_BYTES))
1020 .map_err(|_| VfsError::einval("zero range chunk is too large"))?;
1021 self.pwrite(path, &vec![0; chunk_len], cursor).await?;
1022 cursor += chunk_len as u64;
1023 }
1024 let updated = self.metadata.resolve(path).await?;
1025 let freed = self
1026 .metadata
1027 .set_attr(
1028 updated.ino,
1029 InodePatch {
1030 size: keep_size.then_some(original.size),
1031 allocated_extents: Some(allocation_after_write(
1032 &updated.allocated_extents,
1033 offset,
1034 length,
1035 )),
1036 xattrs: Some({
1037 let mut xattrs = original.xattrs.clone();
1038 encode_unwritten_extents(
1039 &mut xattrs,
1040 &unwritten_after_zero(
1041 &decode_unwritten_extents(&original.xattrs)?,
1042 &original.allocated_extents,
1043 offset,
1044 length,
1045 ),
1046 );
1047 xattrs
1048 }),
1049 ..InodePatch::default()
1050 },
1051 )
1052 .await?;
1053 self.blocks.delete_many(&freed).await
1054 }
1055
1056 async fn insert_range(&self, path: &str, offset: u64, length: u64) -> VfsResult<()> {
1057 validate_shift_range(offset, length)?;
1058 let meta = self.metadata.resolve(path).await?;
1059 self.ensure_file(path, &meta)?;
1060 if offset >= meta.size {
1061 return Err(VfsError::einval("insert range offset must be before EOF"));
1062 }
1063 let mut data = self
1064 .read_file_range(
1065 &meta,
1066 0,
1067 usize::try_from(meta.size)
1068 .map_err(|_| VfsError::einval("insert source is too large"))?,
1069 )
1070 .await?;
1071 let start = usize::try_from(offset)
1072 .map_err(|_| VfsError::einval("insert range offset is too large"))?;
1073 let insert_len = usize::try_from(length)
1074 .map_err(|_| VfsError::einval("insert range length is too large"))?;
1075 data.splice(start..start, std::iter::repeat_n(0, insert_len));
1076 let extents = allocation_after_insert(&meta.allocated_extents, offset, length);
1077 let unwritten =
1078 unwritten_after_insert(&decode_unwritten_extents(&meta.xattrs)?, offset, length);
1079 self.write_existing_or_create(path, &data).await?;
1080 let updated = self.metadata.resolve(path).await?;
1081 let freed = self
1082 .metadata
1083 .set_attr(
1084 updated.ino,
1085 InodePatch {
1086 allocated_extents: Some(extents),
1087 xattrs: Some({
1088 let mut xattrs = meta.xattrs.clone();
1089 encode_unwritten_extents(&mut xattrs, &unwritten);
1090 xattrs
1091 }),
1092 ..InodePatch::default()
1093 },
1094 )
1095 .await?;
1096 self.blocks.delete_many(&freed).await
1097 }
1098
1099 async fn collapse_range(&self, path: &str, offset: u64, length: u64) -> VfsResult<()> {
1100 validate_shift_range(offset, length)?;
1101 let end = offset
1102 .checked_add(length)
1103 .ok_or_else(|| VfsError::einval("collapse range overflows"))?;
1104 let meta = self.metadata.resolve(path).await?;
1105 self.ensure_file(path, &meta)?;
1106 if end >= meta.size {
1107 return Err(VfsError::einval("collapse range must end before EOF"));
1108 }
1109 let mut data = self
1110 .read_file_range(
1111 &meta,
1112 0,
1113 usize::try_from(meta.size)
1114 .map_err(|_| VfsError::einval("collapse source is too large"))?,
1115 )
1116 .await?;
1117 let start = usize::try_from(offset)
1118 .map_err(|_| VfsError::einval("collapse range offset is too large"))?;
1119 let end = usize::try_from(end)
1120 .map_err(|_| VfsError::einval("collapse range end is too large"))?;
1121 data.drain(start..end);
1122 let extents = allocation_after_collapse(&meta.allocated_extents, offset, length);
1123 let unwritten =
1124 unwritten_after_collapse(&decode_unwritten_extents(&meta.xattrs)?, offset, length);
1125 self.write_existing_or_create(path, &data).await?;
1126 let updated = self.metadata.resolve(path).await?;
1127 let freed = self
1128 .metadata
1129 .set_attr(
1130 updated.ino,
1131 InodePatch {
1132 allocated_extents: Some(extents),
1133 xattrs: Some({
1134 let mut xattrs = meta.xattrs.clone();
1135 encode_unwritten_extents(&mut xattrs, &unwritten);
1136 xattrs
1137 }),
1138 ..InodePatch::default()
1139 },
1140 )
1141 .await?;
1142 self.blocks.delete_many(&freed).await
1143 }
1144
1145 async fn allocated_ranges(&self, path: &str) -> VfsResult<Vec<(u64, u64)>> {
1146 let meta = self.metadata.resolve(path).await?;
1147 self.ensure_file(path, &meta)?;
1148 Ok(allocation_byte_ranges(&meta.allocated_extents, meta.size))
1149 }
1150
1151 async fn unwritten_ranges(&self, path: &str) -> VfsResult<Vec<(u64, u64)>> {
1152 let meta = self.metadata.resolve(path).await?;
1153 self.ensure_file(path, &meta)?;
1154 unwritten_byte_ranges(&meta.xattrs, meta.size)
1155 }
1156
1157 async fn pread(&self, path: &str, offset: u64, length: usize) -> VfsResult<Vec<u8>> {
1158 let meta = self.metadata.resolve(path).await?;
1159 self.ensure_file(path, &meta)?;
1160 self.read_file_range(&meta, offset, length).await
1161 }
1162
1163 async fn pwrite(&self, path: &str, content: &[u8], offset: u64) -> VfsResult<()> {
1164 let meta = self.metadata.resolve(path).await?;
1165 self.ensure_file(path, &meta)?;
1166 self.write_chunked_range(&meta, content, offset).await?;
1167 Ok(())
1168 }
1169
1170 async fn append(&self, path: &str, content: &[u8]) -> VfsResult<u64> {
1171 let meta = self.metadata.resolve(path).await?;
1172 self.ensure_file(path, &meta)?;
1173 let len = meta
1174 .size
1175 .checked_add(u64::try_from(content.len()).map_err(|_| {
1176 VfsError::einval(format!("append content is too large: {}", content.len()))
1177 })?)
1178 .ok_or_else(|| VfsError::einval("append size overflows"))?;
1179 self.write_chunked_range(&meta, content, meta.size).await?;
1180 Ok(len)
1181 }
1182
1183 async fn sync(&self, _path: &str) -> VfsResult<()> {
1184 self.blocks.sync().await?;
1185 self.metadata.flush().await
1186 }
1187}
1188
1189#[async_trait]
1190impl<M: MetadataStore, B: BlockStore> Snapshottable for ChunkedFs<M, B> {
1191 async fn snapshot(&self, root: u64) -> VfsResult<SnapshotId> {
1192 self.metadata.snapshot(root).await
1193 }
1194
1195 async fn fork(&self, snap: SnapshotId) -> VfsResult<u64> {
1196 self.metadata.fork(snap).await
1197 }
1198}
1199
1200fn ms_to_timespec(ms: u64) -> Timespec {
1201 Timespec {
1202 sec: (ms / 1_000) as i64,
1203 nsec: ((ms % 1_000) * 1_000_000) as u32,
1204 }
1205}
1206
1207fn dense_allocation(size: u64) -> Vec<(u64, u64)> {
1208 if size == 0 {
1209 Vec::new()
1210 } else {
1211 vec![(0, size.div_ceil(512))]
1212 }
1213}
1214
1215fn allocation_after_write(existing: &[(u64, u64)], offset: u64, length: u64) -> Vec<(u64, u64)> {
1216 if length == 0 {
1217 return existing.to_vec();
1218 }
1219 let mut pending = (offset / 512, offset.saturating_add(length).div_ceil(512));
1220 let mut merged = Vec::with_capacity(existing.len() + 1);
1221 for &(start, end) in existing {
1222 if end < pending.0 {
1223 merged.push((start, end));
1224 } else if pending.1 < start {
1225 merged.push(pending);
1226 pending = (start, end);
1227 } else {
1228 pending.0 = pending.0.min(start);
1229 pending.1 = pending.1.max(end);
1230 }
1231 }
1232 merged.push(pending);
1233 merged
1234}
1235
1236fn allocation_after_truncate(existing: &[(u64, u64)], size: u64) -> Vec<(u64, u64)> {
1237 let end = size.div_ceil(512);
1238 existing
1239 .iter()
1240 .filter_map(|(start, extent_end)| {
1241 let extent_end = (*extent_end).min(end);
1242 (*start < extent_end).then_some((*start, extent_end))
1243 })
1244 .collect()
1245}
1246
1247fn allocation_after_punch(existing: &[(u64, u64)], offset: u64, length: u64) -> Vec<(u64, u64)> {
1248 let start = offset.div_ceil(512);
1249 let end = offset.saturating_add(length) / 512;
1250 if start >= end {
1251 return existing.to_vec();
1252 }
1253 existing
1254 .iter()
1255 .flat_map(|&(extent_start, extent_end)| {
1256 [
1257 (extent_start, extent_end.min(start)),
1258 (extent_start.max(end), extent_end),
1259 ]
1260 .into_iter()
1261 .filter(|(part_start, part_end)| part_start < part_end)
1262 })
1263 .collect()
1264}
1265
1266fn validate_shift_range(offset: u64, length: u64) -> VfsResult<()> {
1267 if length == 0 || !offset.is_multiple_of(512) || !length.is_multiple_of(512) {
1268 return Err(VfsError::einval(
1269 "insert/collapse range requires a nonzero 512-byte-aligned range",
1270 ));
1271 }
1272 Ok(())
1273}
1274
1275fn allocation_after_insert(existing: &[(u64, u64)], offset: u64, length: u64) -> Vec<(u64, u64)> {
1276 let start = offset / 512;
1277 let shift = length / 512;
1278 normalize_extents(existing.iter().flat_map(|&(extent_start, extent_end)| {
1279 if extent_end <= start {
1280 vec![(extent_start, extent_end)]
1281 } else if extent_start >= start {
1282 vec![(extent_start + shift, extent_end + shift)]
1283 } else {
1284 vec![(extent_start, start), (start + shift, extent_end + shift)]
1285 }
1286 }))
1287}
1288
1289fn allocation_after_collapse(existing: &[(u64, u64)], offset: u64, length: u64) -> Vec<(u64, u64)> {
1290 let start = offset / 512;
1291 let end = start + length / 512;
1292 normalize_extents(existing.iter().flat_map(|&(extent_start, extent_end)| {
1293 let mut parts = Vec::with_capacity(2);
1294 if extent_start < start {
1295 parts.push((extent_start, extent_end.min(start)));
1296 }
1297 if extent_end > end {
1298 parts.push((
1299 extent_start.max(end) - (end - start),
1300 extent_end - (end - start),
1301 ));
1302 }
1303 parts
1304 }))
1305}
1306
1307fn normalize_extents(extents: impl IntoIterator<Item = (u64, u64)>) -> Vec<(u64, u64)> {
1308 let mut merged = Vec::<(u64, u64)>::new();
1309 for (start, end) in extents.into_iter().filter(|(start, end)| start < end) {
1310 if let Some(last) = merged.last_mut().filter(|last| start <= last.1) {
1311 last.1 = last.1.max(end);
1312 } else {
1313 merged.push((start, end));
1314 }
1315 }
1316 merged
1317}
1318
1319fn allocation_byte_ranges(extents: &[(u64, u64)], size: u64) -> Vec<(u64, u64)> {
1320 extents
1321 .iter()
1322 .filter_map(|&(start, end)| {
1323 let start = start.saturating_mul(512).min(size);
1324 let end = end.saturating_mul(512).min(size);
1325 (start < end).then_some((start, end))
1326 })
1327 .collect()
1328}