Skip to main content

starnix_core/vfs/
file_system.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::security;
6use crate::task::{CurrentTask, Kernel};
7use crate::vfs::fs_args::MountParams;
8use crate::vfs::fs_node_cache::FsNodeCache;
9use crate::vfs::{
10    DirEntry, DirEntryHandle, FsNode, FsNodeFlags, FsNodeHandle, FsNodeInfo, FsNodeOps, FsStr,
11    FsString,
12};
13use flyweights::FlyByteStr;
14use linked_hash_map::LinkedHashMap;
15use ref_cast::RefCast;
16use smallvec::SmallVec;
17use starnix_crypt::CryptService;
18use starnix_sync::{
19    DynamicLockDepMutex, FileOpsCore, FsRename, FsRenameRecursive, FuseFsRenameLevel,
20    LockEqualOrBefore, Locked, Mutex,
21};
22use starnix_uapi::arc_key::ArcKey;
23use starnix_uapi::as_any::AsAny;
24use starnix_uapi::auth::FsCred;
25use starnix_uapi::device_id::DeviceId;
26use starnix_uapi::errors::Errno;
27use starnix_uapi::file_mode::mode;
28use starnix_uapi::mount_flags::{AtomicFileSystemFlags, FileSystemFlags};
29use starnix_uapi::{error, ino_t, statfs};
30use std::collections::HashSet;
31use std::ops::Range;
32use std::sync::atomic::Ordering;
33use std::sync::{Arc, OnceLock, Weak};
34
35#[derive(Debug, Default)]
36pub struct FileSystemRenameToken {}
37
38/// The type of the filesystem for LockDep purposes.
39///
40/// `Normal` filesystems use standard lock levels.
41/// `Recursive` filesystems (like OverlayFS) use lock levels that precede normal ones,
42/// allowing them to lock the underlying filesystem without violating the hierarchy.
43/// `Fuse` filesystems do blocking calls while holding locks and require specific lock
44/// ordering because of this.
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum FsLockDepType {
47    Normal,
48    Recursive,
49    Fuse,
50}
51
52/// A file system that can be mounted in a namespace.
53pub struct FileSystem {
54    pub kernel: Weak<Kernel>,
55    root: OnceLock<DirEntryHandle>,
56    ops: Box<dyn FileSystemOps>,
57
58    /// The options specified when mounting the filesystem. Saved here for display in
59    /// /proc/[pid]/mountinfo.
60    pub options: FileSystemOptions,
61
62    /// The device ID of this filesystem. Returned in the st_dev field when stating an inode in
63    /// this filesystem.
64    pub dev_id: DeviceId,
65
66    /// A file-system global mutex to serialize rename operations.
67    ///
68    /// This mutex is useful because the invariants enforced during a rename
69    /// operation involve many DirEntry objects. In the future, we might be
70    /// able to remove this mutex, but we will need to think carefully about
71    /// how rename operations can interleave.
72    ///
73    /// See DirEntry::rename.
74    pub rename_mutex: DynamicLockDepMutex<FileSystemRenameToken>,
75
76    /// The FsNode cache for this file system.
77    ///
78    /// When two directory entries are hard links to the same underlying inode,
79    /// this cache lets us re-use the same FsNode object for both directory
80    /// entries.
81    ///
82    /// Rather than calling FsNode::new directly, file systems should call
83    /// FileSystem::get_or_create_node to see if the FsNode already exists in
84    /// the cache.
85    node_cache: Arc<FsNodeCache>,
86
87    /// DirEntryHandle cache for the filesystem. Holds strong references to DirEntry objects. For
88    /// filesystems with permanent entries, this will hold a strong reference to every node to make
89    /// sure it doesn't get freed without being explicitly unlinked. Otherwise, entries are
90    /// maintained in an LRU cache.
91    dcache: DirEntryCache,
92
93    /// Holds security state for this file system, which is created and used by the Linux Security
94    /// Modules subsystem hooks.
95    pub security_state: security::FileSystemState,
96}
97
98impl std::fmt::Debug for FileSystem {
99    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100        write!(f, "FileSystem")
101    }
102}
103
104#[derive(Debug, Default)]
105pub struct FileSystemOptions {
106    /// The source string passed as the first argument to mount(), e.g. a block device.
107    pub source: FlyByteStr,
108    /// Flags kept per-superblock.
109    pub flags: AtomicFileSystemFlags,
110    /// Filesystem options passed as the last argument to mount().
111    pub params: MountParams,
112}
113
114impl Clone for FileSystemOptions {
115    fn clone(&self) -> Self {
116        Self {
117            source: self.source.clone(),
118            flags: self.flags.load(Ordering::Relaxed).into(),
119            params: self.params.clone(),
120        }
121    }
122}
123
124impl FileSystemOptions {
125    pub fn source_for_display(&self) -> &FsStr {
126        if self.source.is_empty() {
127            return "none".into();
128        }
129        self.source.as_ref()
130    }
131}
132
133struct LruCache {
134    capacity: usize,
135    entries: Mutex<LinkedHashMap<ArcKey<DirEntry>, ()>>,
136}
137
138enum DirEntryCache {
139    Permanent(Mutex<HashSet<ArcKey<DirEntry>>>),
140    Lru(LruCache),
141    Uncached,
142}
143
144/// Configuration for CacheMode::Cached.
145pub struct CacheConfig {
146    pub capacity: usize,
147}
148
149pub enum CacheMode {
150    /// Entries are pemanent, instead of a cache of the backing storage. An example is tmpfs: the
151    /// DirEntry tree *is* the backing storage, as opposed to ext4, which uses the DirEntry tree as
152    /// a cache and removes unused nodes from it.
153    Permanent,
154    /// Entries are cached.
155    Cached(CacheConfig),
156    /// Entries are uncached. This can be appropriate in cases where it is difficult for the
157    /// filesystem to keep the cache coherent: e.g. the /proc/<pid>/task directory.
158    Uncached,
159}
160
161impl FileSystem {
162    /// Create a new filesystem.
163    pub fn new<L>(
164        locked: &mut Locked<L>,
165        kernel: &Kernel,
166        cache_mode: CacheMode,
167        ops: impl FileSystemOps,
168        mut options: FileSystemOptions,
169    ) -> Result<FileSystemHandle, Errno>
170    where
171        L: LockEqualOrBefore<FileOpsCore>,
172    {
173        let uses_external_node_ids = ops.uses_external_node_ids();
174        let node_cache = Arc::new(FsNodeCache::new(uses_external_node_ids));
175        assert_eq!(ops.uses_external_node_ids(), node_cache.uses_external_node_ids());
176
177        let mount_options = security::sb_eat_lsm_opts(&kernel, &mut options.params)?;
178        let security_state = security::file_system_init_security(&mount_options, &ops)?;
179
180        let fs_lockdep_type = ops.fs_lockdep_type();
181
182        let file_system = Arc::new(FileSystem {
183            kernel: kernel.weak_self.clone(),
184            root: OnceLock::new(),
185            ops: Box::new(ops),
186            options,
187            dev_id: kernel.device_registry.next_anonymous_dev_id(locked),
188            rename_mutex: match fs_lockdep_type {
189                FsLockDepType::Normal => {
190                    DynamicLockDepMutex::new::<FsRename>(FileSystemRenameToken::default())
191                }
192                FsLockDepType::Recursive => {
193                    DynamicLockDepMutex::new::<FsRenameRecursive>(FileSystemRenameToken::default())
194                }
195                FsLockDepType::Fuse => {
196                    DynamicLockDepMutex::new::<FuseFsRenameLevel>(FileSystemRenameToken::default())
197                }
198            },
199            node_cache,
200            dcache: match cache_mode {
201                CacheMode::Permanent => DirEntryCache::Permanent(Mutex::new(HashSet::new())),
202                CacheMode::Cached(CacheConfig { capacity }) => DirEntryCache::Lru(LruCache {
203                    capacity,
204                    entries: Mutex::new(LinkedHashMap::new()),
205                }),
206                CacheMode::Uncached => DirEntryCache::Uncached,
207            },
208            security_state,
209        });
210
211        // TODO: https://fxbug.dev/366405587 - Workaround to allow SELinux to note that this
212        // `FileSystem` needs labeling, once a policy has been loaded.
213        security::file_system_post_init_security(kernel, &file_system);
214
215        Ok(file_system)
216    }
217
218    fn set_root(self: &FileSystemHandle, root: FsNodeHandle) {
219        // No need to cache the root directory, it is owned by the filesystem.
220        let root_dir = DirEntry::new_uncached(root, None, FsString::default());
221        assert!(
222            self.root.set(root_dir).is_ok(),
223            "FileSystem::set_root can't be called more than once"
224        );
225    }
226
227    pub fn has_permanent_entries(&self) -> bool {
228        matches!(self.dcache, DirEntryCache::Permanent(_))
229    }
230
231    /// Returns the `FsLockDepType` of this filesystem, delegated from `FileSystemOps`.
232    pub fn fs_lockdep_type(&self) -> FsLockDepType {
233        self.ops.fs_lockdep_type()
234    }
235
236    /// The root directory entry of this file system.
237    ///
238    /// Panics if this file system does not have a root directory.
239    pub fn root(&self) -> &DirEntryHandle {
240        self.root.get().unwrap_or_else(|| panic!("FileSystem {} has no root", self.name()))
241    }
242
243    /// The root directory entry of this `FileSystem`, if it has one.
244    pub fn maybe_root(&self) -> Option<&DirEntryHandle> {
245        self.root.get()
246    }
247
248    pub fn get_or_create_node<F>(
249        &self,
250        node_key: ino_t,
251        create_fn: F,
252    ) -> Result<FsNodeHandle, Errno>
253    where
254        F: FnOnce() -> Result<FsNodeHandle, Errno>,
255    {
256        self.get_and_validate_or_create_node(node_key, |_| true, create_fn)
257    }
258
259    /// Get a node that is validated with the callback, or create an FsNode for
260    /// this file system.
261    ///
262    /// If node_id is Some, then this function checks the node cache to
263    /// determine whether this node is already open. If so, the function
264    /// returns the existing FsNode if it passes the validation check. If no
265    /// node exists, or a node does but fails the validation check, the function
266    /// calls the given create_fn function to create the FsNode.
267    ///
268    /// If node_id is None, then this function assigns a new identifier number
269    /// and calls the given create_fn function to create the FsNode with the
270    /// assigned number.
271    ///
272    /// Returns Err only if create_fn returns Err.
273    pub fn get_and_validate_or_create_node<V, C>(
274        &self,
275        node_key: ino_t,
276        validate_fn: V,
277        create_fn: C,
278    ) -> Result<FsNodeHandle, Errno>
279    where
280        V: Fn(&FsNodeHandle) -> bool,
281        C: FnOnce() -> Result<FsNodeHandle, Errno>,
282    {
283        self.node_cache.get_and_validate_or_create_node(node_key, validate_fn, create_fn)
284    }
285
286    /// File systems that produce their own IDs for nodes should invoke this
287    /// function. The ones who leave to this object to assign the IDs should
288    /// call |create_node_and_allocate_node_id|.
289    pub fn create_node_with_flags(
290        self: &Arc<Self>,
291        ino: Option<ino_t>,
292        ops: impl Into<Box<dyn FsNodeOps>>,
293        info: FsNodeInfo,
294        flags: FsNodeFlags,
295    ) -> FsNodeHandle {
296        let ino = ino.unwrap_or_else(|| self.allocate_ino());
297        let node = FsNode::new_uncached(ino, ops, self, info, flags);
298        self.node_cache.insert_node(&node);
299        node
300    }
301
302    pub fn create_node(
303        self: &Arc<Self>,
304        ino: ino_t,
305        ops: impl Into<Box<dyn FsNodeOps>>,
306        info: FsNodeInfo,
307    ) -> FsNodeHandle {
308        self.create_node_with_flags(Some(ino), ops, info, FsNodeFlags::empty())
309    }
310
311    pub fn create_node_and_allocate_node_id(
312        self: &Arc<Self>,
313        ops: impl Into<Box<dyn FsNodeOps>>,
314        info: FsNodeInfo,
315    ) -> FsNodeHandle {
316        self.create_node_with_flags(None, ops, info, FsNodeFlags::empty())
317    }
318
319    /// Create a node for a directory that has no parent.
320    pub fn create_detached_node(
321        self: &Arc<Self>,
322        ino: ino_t,
323        ops: impl Into<Box<dyn FsNodeOps>>,
324        info: FsNodeInfo,
325    ) -> FsNodeHandle {
326        assert!(info.mode.is_dir());
327        let node = FsNode::new_uncached(ino, ops, self, info, FsNodeFlags::empty());
328        self.node_cache.insert_node(&node);
329        node
330    }
331
332    /// Create a root node for the filesystem.
333    ///
334    /// This is a convenience function that creates a root node with the default
335    /// directory mode and root credentials.
336    pub fn create_root(self: &Arc<Self>, ino: ino_t, ops: impl Into<Box<dyn FsNodeOps>>) {
337        let info = FsNodeInfo::new(mode!(IFDIR, 0o777), FsCred::root());
338        self.create_root_with_info(ino, ops, info);
339    }
340
341    pub fn create_root_with_info(
342        self: &Arc<Self>,
343        ino: ino_t,
344        ops: impl Into<Box<dyn FsNodeOps>>,
345        info: FsNodeInfo,
346    ) {
347        let node = self.create_detached_node(ino, ops, info);
348        self.set_root(node);
349    }
350
351    /// Remove the given FsNode from the node cache.
352    ///
353    /// Called from the Release trait of FsNode.
354    pub fn remove_node(&self, node: &FsNode) {
355        self.node_cache.remove_node(node);
356    }
357
358    pub fn allocate_ino(&self) -> ino_t {
359        self.node_cache
360            .allocate_ino()
361            .expect("allocate_ino called on a filesystem that uses external node IDs")
362    }
363
364    /// Allocate a contiguous block of node ids.
365    pub fn allocate_ino_range(&self, size: usize) -> Range<ino_t> {
366        self.node_cache
367            .allocate_ino_range(size)
368            .expect("allocate_ino_range called on a filesystem that uses external node IDs")
369    }
370
371    /// Move |renamed| that is at |old_name| in |old_parent| to |new_name| in |new_parent|
372    /// replacing |replaced|.
373    /// If |replaced| exists and is a directory, this function must check that |renamed| is n
374    /// directory and that |replaced| is empty.
375    pub fn rename<L>(
376        &self,
377        locked: &mut Locked<L>,
378        current_task: &CurrentTask,
379        old_parent: &FsNodeHandle,
380        old_name: &FsStr,
381        new_parent: &FsNodeHandle,
382        new_name: &FsStr,
383        renamed: &FsNodeHandle,
384        replaced: Option<&FsNodeHandle>,
385    ) -> Result<(), Errno>
386    where
387        L: LockEqualOrBefore<FileOpsCore>,
388    {
389        let locked = locked.cast_locked::<FileOpsCore>();
390        self.ops.rename(
391            locked,
392            self,
393            current_task,
394            old_parent,
395            old_name,
396            new_parent,
397            new_name,
398            renamed,
399            replaced,
400        )
401    }
402
403    /// Exchanges `node1` and `node2`. Parent directory node and the corresponding names
404    /// for the two exchanged nodes are passed as `parent1`, `name1`, `parent2`, `name2`.
405    pub fn exchange(
406        &self,
407        current_task: &CurrentTask,
408        node1: &FsNodeHandle,
409        parent1: &FsNodeHandle,
410        name1: &FsStr,
411        node2: &FsNodeHandle,
412        parent2: &FsNodeHandle,
413        name2: &FsStr,
414    ) -> Result<(), Errno> {
415        self.ops.exchange(self, current_task, node1, parent1, name1, node2, parent2, name2)
416    }
417
418    /// Forces a FileSystem unmount.
419    // TODO(https://fxbug.dev/394694891): kernel shutdown should ideally unmount FileSystems via
420    // their drop impl, which should be triggered by Mount.unmount().
421    pub fn force_unmount_ops(&self) {
422        self.ops.unmount();
423    }
424
425    /// Returns the `statfs` for this filesystem.
426    ///
427    /// Each `FileSystemOps` impl is expected to override this to return the specific statfs for
428    /// the filesystem.
429    ///
430    /// Returns `ENOSYS` if the `FileSystemOps` don't implement `stat`.
431    pub fn statfs<L>(
432        &self,
433        locked: &mut Locked<L>,
434        current_task: &CurrentTask,
435    ) -> Result<statfs, Errno>
436    where
437        L: LockEqualOrBefore<FileOpsCore>,
438    {
439        security::sb_statfs(current_task, &self)?;
440        let locked = locked.cast_locked::<FileOpsCore>();
441        let mut stat = self.ops.statfs(locked, self, current_task)?;
442        if stat.f_frsize == 0 {
443            stat.f_frsize = stat.f_bsize as i64;
444        }
445        Ok(stat)
446    }
447
448    pub fn sync<L>(&self, locked: &mut Locked<L>, current_task: &CurrentTask) -> Result<(), Errno>
449    where
450        L: LockEqualOrBefore<FileOpsCore>,
451    {
452        self.ops.sync(locked.cast_locked::<FileOpsCore>(), self, current_task)
453    }
454
455    pub fn did_create_dir_entry(&self, entry: &DirEntryHandle) {
456        match &self.dcache {
457            DirEntryCache::Permanent(p) => {
458                p.lock().insert(ArcKey(entry.clone()));
459            }
460            DirEntryCache::Lru(LruCache { entries, .. }) => {
461                entries.lock().insert(ArcKey(entry.clone()), ());
462            }
463            DirEntryCache::Uncached => {}
464        }
465    }
466
467    pub fn will_destroy_dir_entry(&self, entry: &DirEntryHandle) {
468        match &self.dcache {
469            DirEntryCache::Permanent(p) => {
470                p.lock().remove(ArcKey::ref_cast(entry));
471            }
472            DirEntryCache::Lru(LruCache { entries, .. }) => {
473                entries.lock().remove(ArcKey::ref_cast(entry));
474            }
475            DirEntryCache::Uncached => {}
476        };
477    }
478
479    /// Informs the cache that the entry was used.
480    pub fn did_access_dir_entry(&self, entry: &DirEntryHandle) {
481        if let DirEntryCache::Lru(LruCache { entries, .. }) = &self.dcache {
482            entries.lock().get_refresh(ArcKey::ref_cast(entry));
483        }
484    }
485
486    /// Purges old entries from the cache. This is done as a separate step to avoid potential
487    /// deadlocks that could occur if done at admission time (where locks might be held that are
488    /// required when dropping old entries). This should be called after any new entries are
489    /// admitted with no locks held that might be required for dropping entries.
490    pub fn purge_old_entries(&self) {
491        if let DirEntryCache::Lru(l) = &self.dcache {
492            let mut purged = SmallVec::<[DirEntryHandle; 4]>::new();
493            {
494                let mut entries = l.entries.lock();
495                while entries.len() > l.capacity {
496                    purged.push(entries.pop_front().unwrap().0.0);
497                }
498            }
499            // Entries will get dropped here whilst we're not holding a lock.
500            std::mem::drop(purged);
501        }
502    }
503
504    /// Returns the `FileSystem`'s `FileSystemOps` as a `&T`, or `None` if the downcast fails.
505    pub fn downcast_ops<T: 'static>(&self) -> Option<&T> {
506        self.ops.as_ref().as_any().downcast_ref()
507    }
508
509    pub fn name(&self) -> &'static FsStr {
510        self.ops.name()
511    }
512
513    pub fn manages_timestamps(&self) -> bool {
514        self.ops.manages_timestamps()
515    }
516
517    /// Returns the crypt service associated with this filesystem, if any. The crypt service
518    /// implements the fuchsia.fxfs.Crypt protocol and maintains an internal structure that maps
519    /// each encryption key id to the actual key.
520    pub fn crypt_service(&self) -> Option<Arc<CryptService>> {
521        self.ops.crypt_service()
522    }
523
524    /// Reconfigures the MountFlags associated with the filesystem with the specified `flags`.
525    /// Filesystems may customize `FsNodeOps::update_flags()` to take action (e.g. flushing dirty
526    /// files when transitioning from read-write to read-only), or to reject reconfiguration.
527    pub fn update_flags(
528        &self,
529        current_task: &CurrentTask,
530        flags: FileSystemFlags,
531    ) -> Result<(), Errno> {
532        self.ops.update_flags(self, current_task, flags)
533    }
534}
535
536/// The filesystem-implementation-specific data for FileSystem.
537pub trait FileSystemOps: AsAny + Send + Sync + 'static {
538    /// Returns the `FsLockDepType` of this filesystem.
539    ///
540    /// Defaults to `FsLockDepType::Normal`. Filesystems that can be stacked (like OverlayFS)
541    /// should override this to return `FsLockDepType::Recursive`.
542    fn fs_lockdep_type(&self) -> FsLockDepType {
543        FsLockDepType::Normal
544    }
545
546    /// Return information about this filesystem.
547    ///
548    /// A typical implementation looks like this:
549    /// ```
550    /// Ok(statfs::default(FILE_SYSTEM_MAGIC))
551    /// ```
552    /// or, if the filesystem wants to customize fields:
553    /// ```
554    /// Ok(statfs {
555    ///     f_blocks: self.blocks,
556    ///     ..statfs::default(FILE_SYSTEM_MAGIC)
557    /// })
558    /// ```
559    fn statfs(
560        &self,
561        _locked: &mut Locked<FileOpsCore>,
562        _fs: &FileSystem,
563        _current_task: &CurrentTask,
564    ) -> Result<statfs, Errno>;
565
566    /// Reconfigure the filesystem with the given flags.
567    ///
568    /// This is called during a remount operation (MS_REMOUNT), to allow the filesystem to update
569    /// internal resources as necessary to support the new flags.
570    fn update_flags(
571        &self,
572        fs: &FileSystem,
573        _current_task: &CurrentTask,
574        new_flags: FileSystemFlags,
575    ) -> Result<(), Errno> {
576        fs.options.flags.store(new_flags, Ordering::Relaxed);
577        Ok(())
578    }
579
580    fn name(&self) -> &'static FsStr;
581
582    /// Whether this file system uses external node IDs.
583    ///
584    /// If this is true, then the file system is responsible for assigning node IDs to its nodes.
585    /// Otherwise, the VFS will assign node IDs to the nodes.
586    fn uses_external_node_ids(&self) -> bool {
587        false
588    }
589
590    /// Rename the given node.
591    ///
592    /// The node to be renamed is passed as "renamed". It currently has
593    /// old_name in old_parent. After the rename operation, it should have
594    /// new_name in new_parent.
595    ///
596    /// If new_parent already has a child named new_name, that node is passed as
597    /// "replaced". In that case, both "renamed" and "replaced" will be
598    /// directories and the rename operation should succeed only if "replaced"
599    /// is empty. The VFS will check that there are no children of "replaced" in
600    /// the DirEntry cache, but the implementation of this function is
601    /// responsible for checking that there are no children of replaced that are
602    /// known only to the file system implementation (e.g., present on-disk but
603    /// not in the DirEntry cache).
604    fn rename(
605        &self,
606        _locked: &mut Locked<FileOpsCore>,
607        _fs: &FileSystem,
608        _current_task: &CurrentTask,
609        _old_parent: &FsNodeHandle,
610        _old_name: &FsStr,
611        _new_parent: &FsNodeHandle,
612        _new_name: &FsStr,
613        _renamed: &FsNodeHandle,
614        _replaced: Option<&FsNodeHandle>,
615    ) -> Result<(), Errno> {
616        error!(EROFS)
617    }
618
619    fn exchange(
620        &self,
621        _fs: &FileSystem,
622        _current_task: &CurrentTask,
623        _node1: &FsNodeHandle,
624        _parent1: &FsNodeHandle,
625        _name1: &FsStr,
626        _node2: &FsNodeHandle,
627        _parent2: &FsNodeHandle,
628        _name2: &FsStr,
629    ) -> Result<(), Errno> {
630        error!(EINVAL)
631    }
632
633    /// Called when the filesystem is unmounted.
634    fn unmount(&self) {}
635
636    /// Indicates if the filesystem can manage the timestamps (i.e. ctime and mtime).
637    ///
638    /// Starnix updates the timestamps in FsNode's `info` directly. However, if the filesystem can
639    /// manage the timestamps, then Starnix does not need to do so. `info` will be refreshed with
640    /// the timestamps from the filesystem by calling `fetch_and_refresh_info(..)` on the FsNode.
641    fn manages_timestamps(&self) -> bool {
642        false
643    }
644
645    /// Returns the crypt service associated with this filesystem, if any.
646    fn crypt_service(&self) -> Option<Arc<CryptService>> {
647        None
648    }
649
650    fn sync(
651        &self,
652        _locked: &mut Locked<FileOpsCore>,
653        _fs: &FileSystem,
654        _current_task: &CurrentTask,
655    ) -> Result<(), Errno> {
656        Ok(())
657    }
658}
659
660impl Drop for FileSystem {
661    fn drop(&mut self) {
662        self.ops.unmount();
663    }
664}
665
666pub type FileSystemHandle = Arc<FileSystem>;