1mod file_attr;
2mod inode_tracker;
3mod inodes;
4pub mod root_nodes;
5
6#[cfg(feature = "fuse")]
7pub mod fuse;
8
9#[cfg(feature = "virtiofs")]
10pub mod virtiofs;
11
12pub use self::root_nodes::RootNodes;
13use self::{
14 file_attr::ROOT_FILE_ATTR,
15 inode_tracker::InodeTracker,
16 inodes::{DirectoryInodeData, InodeData},
17};
18use crate::{
19 B3Digest, Node,
20 blobservice::{BlobReader, BlobService},
21 directoryservice::DirectoryService,
22 path::PathComponent,
23};
24use bstr::ByteVec;
25use fuse_backend_rs::api::filesystem::{
26 Context, FileSystem, FsOptions, GetxattrReply, ListxattrReply, ROOT_ID,
27};
28use fuse_backend_rs::{
29 abi::fuse_abi::{Attr, OpenOptions, stat64},
30 api::filesystem::Entry,
31};
32use futures::{StreamExt, stream::BoxStream};
33use parking_lot::RwLock;
34use std::sync::Mutex;
35use std::{
36 collections::HashMap,
37 io,
38 sync::atomic::AtomicU64,
39 sync::{Arc, atomic::Ordering},
40 time::Duration,
41};
42use std::{ffi::CStr, io::Cursor};
43use tokio::io::{AsyncReadExt, AsyncSeekExt};
44use tracing::{Span, debug, error, instrument, warn};
45
46pub struct SnixStoreFs<BS, DS, RN: RootNodes> {
79 blob_service: BS,
80 directory_service: DS,
81 root_nodes_provider: Arc<RN>,
82 settings: FSSettings,
83
84 root_nodes: RwLock<HashMap<PathComponent, u64>>,
86
87 inode_tracker: RwLock<InodeTracker>,
89
90 #[allow(clippy::type_complexity)]
97 dir_handles: RwLock<
98 HashMap<
99 u64,
100 (
101 Span,
102 Arc<Mutex<BoxStream<'static, (usize, Result<(PathComponent, Node), RN::Error>)>>>,
103 ),
104 >,
105 >,
106
107 next_dir_handle: AtomicU64,
108
109 #[allow(clippy::type_complexity)]
111 file_handles: RwLock<HashMap<u64, (Span, Arc<Mutex<Box<dyn BlobReader>>>)>>,
112
113 next_file_handle: AtomicU64,
114
115 tokio_handle: tokio::runtime::Handle,
116}
117
118#[derive(Debug, Default)]
120pub struct FSSettings {
121 pub list_root: bool,
123
124 pub uid_gid_override: Option<(u32, u32)>,
126
127 pub show_xattr: bool,
129}
130
131impl<BS, DS, RN> SnixStoreFs<BS, DS, RN>
132where
133 BS: BlobService,
134 DS: DirectoryService,
135 RN: RootNodes,
136{
137 pub fn new(
138 blob_service: BS,
139 directory_service: DS,
140 root_nodes_provider: RN,
141
142 settings: FSSettings,
143 tokio_handle: tokio::runtime::Handle,
144 ) -> Self {
145 Self {
146 blob_service,
147 directory_service,
148 root_nodes_provider: Arc::new(root_nodes_provider),
149
150 settings,
151
152 root_nodes: RwLock::new(HashMap::default()),
153 inode_tracker: RwLock::new(Default::default()),
154
155 dir_handles: RwLock::new(Default::default()),
156 next_dir_handle: AtomicU64::new(1),
157
158 file_handles: RwLock::new(Default::default()),
159 next_file_handle: AtomicU64::new(1),
160 tokio_handle,
161 }
162 }
163
164 fn get_inode_for_root_name(&self, name: &PathComponent) -> Option<u64> {
167 self.root_nodes.read().get(name).cloned()
168 }
169
170 #[allow(clippy::type_complexity)]
178 #[instrument(skip(self), err)]
179 fn get_directory_children(
180 &self,
181 ino: u64,
182 ) -> io::Result<(B3Digest, Vec<(u64, PathComponent, Node)>)> {
183 let data = self.inode_tracker.read().get(ino).unwrap();
184 match *data {
185 InodeData::Directory(DirectoryInodeData::Populated(parent_digest, ref children)) => {
187 Ok((parent_digest, children.clone()))
188 }
189 InodeData::Directory(DirectoryInodeData::Sparse(parent_digest, _)) => {
192 let directory = self
193 .tokio_handle
194 .block_on(async { self.directory_service.get(&parent_digest).await })
195 .map_err(|err| {
196 warn!(%err, "error from directory service");
197 io::Error::other(err)
198 })?
199 .ok_or_else(|| {
200 warn!(directory.digest=%parent_digest, "directory not found");
201 io::Error::from_raw_os_error(libc::EIO)
203 })?;
204
205 let children = {
209 let mut inode_tracker = self.inode_tracker.write();
210
211 let children: Vec<(u64, PathComponent, Node)> = directory
212 .into_nodes()
213 .map(|(child_name, child_node)| {
214 let inode_data = InodeData::from_node(&child_node);
215
216 let child_ino = inode_tracker.put(inode_data);
217 (child_ino, child_name, child_node)
218 })
219 .collect();
220
221 inode_tracker.replace(
223 ino,
224 Arc::new(InodeData::Directory(DirectoryInodeData::Populated(
225 parent_digest,
226 children.clone(),
227 ))),
228 );
229
230 children
231 };
232
233 Ok((parent_digest, children))
234 }
235 InodeData::Regular(..) | InodeData::Symlink(_) => {
237 Err(io::Error::from_raw_os_error(libc::ENOTDIR))
238 }
239 }
240 }
241
242 fn name_in_root_to_ino_and_data(
250 &self,
251 name: &PathComponent,
252 ) -> io::Result<(u64, Arc<InodeData>)> {
253 if let Some(inode) = self.get_inode_for_root_name(name) {
257 return Ok((
258 inode,
259 self.inode_tracker
260 .read()
261 .get(inode)
262 .expect("must exist")
263 .to_owned(),
264 ));
265 }
266
267 match self
269 .tokio_handle
270 .block_on(async { self.root_nodes_provider.get_by_basename(name).await })
271 {
272 Err(_e) => Err(io::Error::from_raw_os_error(libc::EIO)),
274 Ok(None) => Err(io::Error::from_raw_os_error(libc::ENOENT)),
276 Ok(Some(root_node)) => {
278 if let Some(ino) = self.root_nodes.read().get(name) {
281 return Ok((
282 *ino,
283 self.inode_tracker.read().get(*ino).expect("must exist"),
284 ));
285 }
286
287 let mut root_nodes = self.root_nodes.write();
290 let mut inode_tracker = self.inode_tracker.write();
291
292 let inode_data = InodeData::from_node(&root_node);
295 let ino = inode_tracker.put(inode_data.clone());
296 root_nodes.insert(name.to_owned(), ino);
297
298 Ok((ino, Arc::new(inode_data)))
299 }
300 }
301 }
302
303 fn inode_data_to_attr(&self, inode_data: &InodeData, ino: u64) -> Attr {
306 let mode = match inode_data {
307 InodeData::Regular(_, _, false) => libc::S_IFREG | 0o444,
308 InodeData::Regular(_, _, true) => libc::S_IFREG | 0o555,
310 InodeData::Symlink(_) => libc::S_IFLNK | 0o444,
311 InodeData::Directory(_) => libc::S_IFDIR | 0o555,
312 };
313 #[cfg(target_os = "macos")]
315 let mode = mode as u32;
316 let mut attr = Attr {
317 ino,
318 blocks: 1024,
320 size: match inode_data {
321 InodeData::Regular(_, size, _) => *size,
322 InodeData::Symlink(target) => target.len() as u64,
323 InodeData::Directory(DirectoryInodeData::Sparse(_, size)) => *size,
324 InodeData::Directory(DirectoryInodeData::Populated(_, children)) => {
325 children.len() as u64
326 }
327 },
328 mode,
329 mtime: 1, ..Default::default()
331 };
332
333 if let Some((uid, gid)) = self.settings.uid_gid_override {
334 attr.uid = uid;
335 attr.gid = gid;
336 }
337
338 attr
339 }
340}
341fn attr_to_fuse_entry(attr: Attr) -> Entry {
342 Entry {
343 inode: attr.ino,
344 attr: attr.into(),
345 attr_timeout: Duration::MAX,
346 entry_timeout: Duration::MAX,
347 ..Default::default()
348 }
349}
350
351fn node_to_fuse_type(node: &Node) -> u32 {
353 #[allow(clippy::let_and_return)]
354 let ty = match node {
355 Node::Directory { .. } => libc::S_IFDIR,
356 Node::File { .. } => libc::S_IFREG,
357 Node::Symlink { .. } => libc::S_IFLNK,
358 };
359 #[cfg(target_os = "macos")]
361 let ty = ty as u32;
362
363 ty
364}
365
366const XATTR_NAME_DIRECTORY_DIGEST: &[u8] = b"user.snix.castore.directory.digest";
367const XATTR_NAME_BLOB_DIGEST: &[u8] = b"user.snix.castore.blob.digest";
368
369#[cfg(all(feature = "virtiofs", target_os = "linux"))]
370impl<BS, DS, RN> fuse_backend_rs::api::filesystem::Layer for SnixStoreFs<BS, DS, RN>
371where
372 BS: BlobService,
373 DS: DirectoryService,
374 RN: RootNodes,
375{
376 fn root_inode(&self) -> Self::Inode {
377 ROOT_ID
378 }
379}
380
381impl<BS, DS, RN> FileSystem for SnixStoreFs<BS, DS, RN>
382where
383 BS: BlobService,
384 DS: DirectoryService,
385 RN: RootNodes,
386{
387 type Handle = u64;
388 type Inode = u64;
389
390 fn init(&self, _capable: FsOptions) -> io::Result<FsOptions> {
391 let mut opts = FsOptions::empty();
392
393 opts |= FsOptions::ASYNC_READ;
395
396 #[cfg(target_os = "linux")]
397 {
398 opts |= FsOptions::DO_READDIRPLUS;
400 opts |= FsOptions::READDIRPLUS_AUTO;
402 opts |= FsOptions::PARALLEL_DIROPS;
404 opts |= FsOptions::CACHE_SYMLINKS;
406 }
407 Ok(opts)
410 }
411
412 #[tracing::instrument(skip_all, fields(rq.inode = inode))]
413 fn getattr(
414 &self,
415 _ctx: &Context,
416 inode: Self::Inode,
417 _handle: Option<Self::Handle>,
418 ) -> io::Result<(stat64, Duration)> {
419 let attr = if inode == ROOT_ID {
420 ROOT_FILE_ATTR
421 } else {
422 self.inode_data_to_attr(
423 self.inode_tracker
424 .read()
425 .get(inode)
426 .ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))?
427 .as_ref(),
428 inode,
429 )
430 };
431
432 Ok((attr.into(), Duration::MAX))
433 }
434
435 #[tracing::instrument(skip_all, fields(rq.parent_inode = parent, rq.name = ?name))]
436 fn lookup(
437 &self,
438 _ctx: &Context,
439 parent: Self::Inode,
440 name: &std::ffi::CStr,
441 ) -> io::Result<Entry> {
442 debug!("lookup");
443
444 let name: PathComponent = name.try_into().map_err(|_| std::io::ErrorKind::NotFound)?;
447
448 let (ino, inode_data) = if parent == ROOT_ID {
450 self.name_in_root_to_ino_and_data(&name)?
452 } else {
453 let (parent_digest, children) = self.get_directory_children(parent)?;
456
457 Span::current().record("directory.digest", parent_digest.to_string());
458 let idx = children
461 .binary_search_by_key(&&name, |(_, n, _)| n)
462 .map_err(|_| {
463 io::Error::from_raw_os_error(libc::ENOENT)
465 })?;
466
467 let (child_ino, _, child_node) = &children[idx];
468
469 (*child_ino, Arc::new(InodeData::from_node(child_node)))
471 };
472
473 debug!(inode_data=?&inode_data, ino=ino, "Some");
474
475 let attr = self.inode_data_to_attr(
476 self.inode_tracker
477 .read()
478 .get(ino)
479 .ok_or_else(|| io::Error::from_raw_os_error(libc::ENOENT))?
480 .as_ref(),
481 ino,
482 );
483 Ok(attr_to_fuse_entry(attr))
484 }
485
486 #[tracing::instrument(skip_all, fields(rq.inode = inode))]
487 fn opendir(
488 &self,
489 _ctx: &Context,
490 inode: Self::Inode,
491 _flags: u32,
492 ) -> io::Result<(Option<Self::Handle>, OpenOptions)> {
493 if inode == ROOT_ID {
496 if !self.settings.list_root {
497 return Err(io::Error::from_raw_os_error(libc::EPERM)); }
499
500 let root_nodes_provider = self.root_nodes_provider.clone();
501 let stream = self
502 .tokio_handle
503 .block_on(async move { root_nodes_provider.list().enumerate().boxed() });
504
505 let dh = self.next_dir_handle.fetch_add(1, Ordering::SeqCst);
511
512 self.dir_handles
513 .write()
514 .insert(dh, (Span::current(), Arc::new(Mutex::new(stream))));
515
516 return Ok((Some(dh), OpenOptions::NONSEEKABLE));
517 }
518
519 let mut opts = OpenOptions::empty();
520
521 opts |= OpenOptions::KEEP_CACHE;
522 #[cfg(target_os = "linux")]
523 {
524 opts |= OpenOptions::CACHE_DIR;
525 }
526 Ok((None, opts))
528 }
529
530 #[tracing::instrument(skip_all, fields(rq.inode = inode, rq.handle = handle, rq.offset = offset), parent = self.dir_handles.read().get(&handle).and_then(|x| x.0.id()))]
531 fn readdir(
532 &self,
533 _ctx: &Context,
534 inode: Self::Inode,
535 handle: Self::Handle,
536 _size: u32,
537 offset: u64,
538 add_entry: &mut dyn FnMut(fuse_backend_rs::api::filesystem::DirEntry) -> io::Result<usize>,
539 ) -> io::Result<()> {
540 debug!("readdir");
541
542 if inode == ROOT_ID {
543 if !self.settings.list_root {
544 return Err(io::Error::from_raw_os_error(libc::EPERM)); }
546
547 let dir_handles = self.dir_handles.read();
549 let (_span, stream) = dir_handles.get(&handle).ok_or_else(|| {
550 warn!("dir handle {} unknown", handle);
551 io::Error::from_raw_os_error(libc::EIO)
552 })?;
553
554 let mut stream = stream
555 .lock()
556 .map_err(|_| io::Error::other("mutex poisoned"))?;
557
558 while let Some((i, n)) = self.tokio_handle.block_on(async { stream.next().await }) {
559 let (name, node) = n.map_err(|e| {
560 warn!("failed to retrieve root node: {}", e);
561 io::Error::from_raw_os_error(libc::EIO)
562 })?;
563
564 let ino = self.get_inode_for_root_name(&name).unwrap_or_else(|| {
566 let ino = self.inode_tracker.write().put(InodeData::from_node(&node));
569 self.root_nodes.write().insert(name.clone(), ino);
570 ino
571 });
572
573 let written = add_entry(fuse_backend_rs::api::filesystem::DirEntry {
574 ino,
575 offset: offset + (i as u64) + 1,
576 type_: node_to_fuse_type(&node),
577 name: name.as_ref(),
578 })?;
579 if written == 0 {
581 break;
582 }
583 }
584 return Ok(());
585 }
586
587 let (parent_digest, children) = self.get_directory_children(inode)?;
589 Span::current().record("directory.digest", parent_digest.to_string());
590
591 for (i, (ino, child_name, child_node)) in
592 children.into_iter().skip(offset as usize).enumerate()
593 {
594 let written = add_entry(fuse_backend_rs::api::filesystem::DirEntry {
596 ino,
597 offset: offset + (i as u64) + 1,
598 type_: node_to_fuse_type(&child_node),
599 name: child_name.as_ref(),
600 })?;
601 if written == 0 {
603 break;
604 }
605 }
606
607 Ok(())
608 }
609
610 #[tracing::instrument(skip_all, fields(rq.inode = inode, rq.handle = handle), parent = self.dir_handles.read().get(&handle).and_then(|x| x.0.id()))]
611 fn readdirplus(
612 &self,
613 _ctx: &Context,
614 inode: Self::Inode,
615 handle: Self::Handle,
616 _size: u32,
617 offset: u64,
618 add_entry: &mut dyn FnMut(
619 fuse_backend_rs::api::filesystem::DirEntry,
620 Entry,
621 ) -> io::Result<usize>,
622 ) -> io::Result<()> {
623 debug!("readdirplus");
624
625 if inode == ROOT_ID {
626 if !self.settings.list_root {
627 return Err(io::Error::from_raw_os_error(libc::EPERM)); }
629
630 let dir_handles = self.dir_handles.read();
632 let (_span, stream) = dir_handles.get(&handle).ok_or_else(|| {
633 warn!("dir handle {} unknown", handle);
634 io::Error::from_raw_os_error(libc::EIO)
635 })?;
636
637 let mut stream = stream
638 .lock()
639 .map_err(|_| io::Error::other("mutex poisoned"))?;
640
641 while let Some((i, n)) = self.tokio_handle.block_on(async { stream.next().await }) {
642 let (name, node) = n.map_err(|e| {
643 warn!("failed to retrieve root node: {}", e);
644 io::Error::from_raw_os_error(libc::EPERM)
645 })?;
646
647 let inode_data = InodeData::from_node(&node);
648
649 let ino = self.get_inode_for_root_name(&name).unwrap_or_else(|| {
651 let ino = self.inode_tracker.write().put(inode_data.clone());
654 self.root_nodes.write().insert(name.clone(), ino);
655 ino
656 });
657
658 let written = add_entry(
659 fuse_backend_rs::api::filesystem::DirEntry {
660 ino,
661 offset: offset + (i as u64) + 1,
662 type_: node_to_fuse_type(&node),
663 name: name.as_ref(),
664 },
665 attr_to_fuse_entry(self.inode_data_to_attr(&inode_data, ino)),
666 )?;
667 if written == 0 {
669 break;
670 }
671 }
672 return Ok(());
673 }
674
675 let (parent_digest, children) = self.get_directory_children(inode)?;
677 Span::current().record("directory.digest", parent_digest.to_string());
678
679 for (i, (ino, name, child_node)) in children.into_iter().skip(offset as usize).enumerate() {
680 let inode_data = InodeData::from_node(&child_node);
681
682 let written = add_entry(
684 fuse_backend_rs::api::filesystem::DirEntry {
685 ino,
686 offset: offset + (i as u64) + 1,
687 type_: node_to_fuse_type(&child_node),
688 name: name.as_ref(),
689 },
690 attr_to_fuse_entry(self.inode_data_to_attr(&inode_data, ino)),
691 )?;
692 if written == 0 {
694 break;
695 }
696 }
697
698 Ok(())
699 }
700
701 #[tracing::instrument(skip_all, fields(rq.inode = inode, rq.handle = handle), parent = self.dir_handles.read().get(&handle).and_then(|x| x.0.id()))]
702 fn releasedir(
703 &self,
704 _ctx: &Context,
705 inode: Self::Inode,
706 _flags: u32,
707 handle: Self::Handle,
708 ) -> io::Result<()> {
709 if inode == ROOT_ID {
710 if let Some(stream) = self.dir_handles.write().remove(&handle) {
712 drop(stream)
714 } else {
715 warn!("dir handle not found");
716 }
717 }
718
719 Ok(())
720 }
721
722 #[tracing::instrument(skip_all, fields(rq.inode = inode))]
723 fn open(
724 &self,
725 _ctx: &Context,
726 inode: Self::Inode,
727 _flags: u32,
728 _fuse_flags: u32,
729 ) -> io::Result<(Option<Self::Handle>, OpenOptions, Option<u32>)> {
730 if inode == ROOT_ID {
731 return Err(io::Error::from_raw_os_error(libc::ENOSYS));
732 }
733
734 match *self.inode_tracker.read().get(inode).unwrap() {
736 InodeData::Directory(..) | InodeData::Symlink(_) => {
738 warn!("is directory");
739 Err(io::Error::from_raw_os_error(libc::EISDIR))
740 }
741 InodeData::Regular(ref blob_digest, _blob_size, _) => {
742 Span::current().record("blob.digest", blob_digest.to_string());
743
744 match self
745 .tokio_handle
746 .block_on(async { self.blob_service.open_read(blob_digest).await })
747 {
748 Ok(None) => {
749 warn!("blob not found");
750 Err(io::Error::from_raw_os_error(libc::EIO))
751 }
752 Err(e) => {
753 warn!(e=?e, "error opening blob");
754 Err(io::Error::from_raw_os_error(libc::EIO))
755 }
756 Ok(Some(blob_reader)) => {
757 let fh = self.next_file_handle.fetch_add(1, Ordering::SeqCst);
763
764 self.file_handles
765 .write()
766 .insert(fh, (Span::current(), Arc::new(Mutex::new(blob_reader))));
767
768 Ok((
769 Some(fh),
770 OpenOptions::KEEP_CACHE,
772 None,
773 ))
774 }
775 }
776 }
777 }
778 }
779
780 #[tracing::instrument(skip_all, fields(rq.inode = inode, rq.handle = handle), parent = self.file_handles.read().get(&handle).and_then(|x| x.0.id()))]
781 fn release(
782 &self,
783 _ctx: &Context,
784 inode: Self::Inode,
785 _flags: u32,
786 handle: Self::Handle,
787 _flush: bool,
788 _flock_release: bool,
789 _lock_owner: Option<u64>,
790 ) -> io::Result<()> {
791 match self.file_handles.write().remove(&handle) {
792 Some(blob_reader) => drop(blob_reader),
794 None => {
795 warn!("file handle not found");
797 }
798 }
799
800 Ok(())
801 }
802
803 #[tracing::instrument(skip_all, fields(rq.inode = inode, rq.handle = handle, rq.offset = offset, rq.size = size), parent = self.file_handles.read().get(&handle).and_then(|x| x.0.id()))]
804 fn read(
805 &self,
806 _ctx: &Context,
807 inode: Self::Inode,
808 handle: Self::Handle,
809 w: &mut dyn fuse_backend_rs::api::filesystem::ZeroCopyWriter,
810 size: u32,
811 offset: u64,
812 _lock_owner: Option<u64>,
813 _flags: u32,
814 ) -> io::Result<usize> {
815 debug!("read");
816
817 let (_span, blob_reader) = self
821 .file_handles
822 .read()
823 .get(&handle)
824 .ok_or_else(|| {
825 warn!("file handle {} unknown", handle);
826 io::Error::from_raw_os_error(libc::EIO)
827 })
828 .cloned()?;
829
830 let mut blob_reader = blob_reader
831 .lock()
832 .map_err(|_| io::Error::other("mutex poisoned"))?;
833
834 let buf = self.tokio_handle.block_on(async move {
835 let pos = blob_reader
837 .seek(io::SeekFrom::Start(offset))
838 .await
839 .map_err(|e| {
840 warn!("failed to seek to offset {}: {}", offset, e);
841 io::Error::from_raw_os_error(libc::EIO)
842 })?;
843
844 debug_assert_eq!(offset, pos);
845
846 let mut buf: Vec<u8> = Vec::with_capacity(size as usize);
850
851 tokio::io::copy(&mut blob_reader.as_mut().take(size as u64), &mut buf).await?;
853
854 Ok::<_, std::io::Error>(buf)
855 })?;
856
857 let buf_len = buf.len();
860 let bytes_written = io::copy(&mut Cursor::new(buf), w)?;
861 if bytes_written != buf_len as u64 {
862 error!(bytes_written=%bytes_written, "unable to write all of buf to kernel");
863 return Err(io::Error::from_raw_os_error(libc::EIO));
864 }
865
866 Ok(bytes_written as usize)
867 }
868
869 #[tracing::instrument(skip_all, fields(rq.inode = inode))]
870 fn readlink(&self, _ctx: &Context, inode: Self::Inode) -> io::Result<Vec<u8>> {
871 if inode == ROOT_ID {
872 return Err(io::Error::from_raw_os_error(libc::ENOSYS));
873 }
874
875 match *self.inode_tracker.read().get(inode).unwrap() {
877 InodeData::Directory(..) | InodeData::Regular(..) => {
878 Err(io::Error::from_raw_os_error(libc::EINVAL))
879 }
880 InodeData::Symlink(ref target) => Ok(target.to_vec()),
881 }
882 }
883
884 #[tracing::instrument(skip_all, fields(rq.inode = inode, name=?name))]
885 fn getxattr(
886 &self,
887 _ctx: &Context,
888 inode: Self::Inode,
889 name: &CStr,
890 size: u32,
891 ) -> io::Result<GetxattrReply> {
892 if !self.settings.show_xattr {
893 return Err(io::Error::from_raw_os_error(libc::ENOSYS));
894 }
895
896 let digest_str = match *self
898 .inode_tracker
899 .read()
900 .get(inode)
901 .ok_or_else(|| io::Error::from_raw_os_error(libc::ENODATA))?
902 {
903 InodeData::Directory(DirectoryInodeData::Sparse(ref digest, _))
904 | InodeData::Directory(DirectoryInodeData::Populated(ref digest, _))
905 if name.to_bytes() == XATTR_NAME_DIRECTORY_DIGEST =>
906 {
907 digest.to_string()
908 }
909 InodeData::Regular(ref digest, _, _) if name.to_bytes() == XATTR_NAME_BLOB_DIGEST => {
910 digest.to_string()
911 }
912 _ => {
913 return Err(io::Error::from_raw_os_error(libc::ENODATA));
914 }
915 };
916
917 if size == 0 {
918 Ok(GetxattrReply::Count(digest_str.len() as u32))
919 } else if size < digest_str.len() as u32 {
920 Err(io::Error::from_raw_os_error(libc::ERANGE))
921 } else {
922 Ok(GetxattrReply::Value(digest_str.into_bytes()))
923 }
924 }
925
926 #[tracing::instrument(skip_all, fields(rq.inode = inode))]
927 fn listxattr(
928 &self,
929 _ctx: &Context,
930 inode: Self::Inode,
931 size: u32,
932 ) -> io::Result<ListxattrReply> {
933 if !self.settings.show_xattr {
934 return Err(io::Error::from_raw_os_error(libc::ENOSYS));
935 }
936
937 let xattrs_names = {
939 let mut out = Vec::new();
940 if let Some(inode_data) = self.inode_tracker.read().get(inode) {
941 match *inode_data {
942 InodeData::Directory(_) => {
943 out.extend_from_slice(XATTR_NAME_DIRECTORY_DIGEST);
944 out.push_byte(b'\x00');
945 }
946 InodeData::Regular(..) => {
947 out.extend_from_slice(XATTR_NAME_BLOB_DIGEST);
948 out.push_byte(b'\x00');
949 }
950 _ => {}
951 }
952 }
953 out
954 };
955
956 if size == 0 {
957 Ok(ListxattrReply::Count(xattrs_names.len() as u32))
958 } else if size < xattrs_names.len() as u32 {
959 Err(io::Error::from_raw_os_error(libc::ERANGE))
960 } else {
961 Ok(ListxattrReply::Names(xattrs_names.to_vec()))
962 }
963 }
964}