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, RenameContext,
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, FileSystemEntriesLock, FileSystemPermanentLock, FsRename,
20 FsRenameRecursive, FuseFsRenameLevel, LockDepMutex,
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: LockDepMutex<LinkedHashMap<ArcKey<DirEntry>, ()>, FileSystemEntriesLock>,
136}
137
138enum DirEntryCache {
139 Permanent(LockDepMutex<HashSet<ArcKey<DirEntry>>, FileSystemPermanentLock>),
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(
164 kernel: &Kernel,
165 cache_mode: CacheMode,
166 ops: impl FileSystemOps,
167 mut options: FileSystemOptions,
168 ) -> Result<FileSystemHandle, Errno> {
169 let uses_external_node_ids = ops.uses_external_node_ids();
170 let node_cache = Arc::new(FsNodeCache::new(uses_external_node_ids));
171 assert_eq!(ops.uses_external_node_ids(), node_cache.uses_external_node_ids());
172
173 let mount_options = security::sb_eat_lsm_opts(&kernel, &mut options.params)?;
174 let security_state = security::file_system_init_security(&mount_options, &ops)?;
175
176 let fs_lockdep_type = ops.fs_lockdep_type();
177
178 let file_system = Arc::new(FileSystem {
179 kernel: kernel.weak_self.clone(),
180 root: OnceLock::new(),
181 ops: Box::new(ops),
182 options,
183 dev_id: kernel.device_registry.next_anonymous_dev_id(),
184 rename_mutex: match fs_lockdep_type {
185 FsLockDepType::Normal => {
186 DynamicLockDepMutex::new::<FsRename>(FileSystemRenameToken::default())
187 }
188 FsLockDepType::Recursive => {
189 DynamicLockDepMutex::new::<FsRenameRecursive>(FileSystemRenameToken::default())
190 }
191 FsLockDepType::Fuse => {
192 DynamicLockDepMutex::new::<FuseFsRenameLevel>(FileSystemRenameToken::default())
193 }
194 },
195 node_cache,
196 dcache: match cache_mode {
197 CacheMode::Permanent => DirEntryCache::Permanent(Default::default()),
198 CacheMode::Cached(CacheConfig { capacity }) => {
199 DirEntryCache::Lru(LruCache { capacity, entries: Default::default() })
200 }
201 CacheMode::Uncached => DirEntryCache::Uncached,
202 },
203 security_state,
204 });
205
206 // TODO: https://fxbug.dev/366405587 - Workaround to allow SELinux to note that this
207 // `FileSystem` needs labeling, once a policy has been loaded.
208 security::file_system_post_init_security(kernel, &file_system);
209
210 Ok(file_system)
211 }
212
213 fn set_root(self: &FileSystemHandle, root: FsNodeHandle) {
214 // No need to cache the root directory, it is owned by the filesystem.
215 let root_dir = DirEntry::new_uncached(root, None, FsString::default());
216 assert!(
217 self.root.set(root_dir).is_ok(),
218 "FileSystem::set_root can't be called more than once"
219 );
220 }
221
222 pub fn has_permanent_entries(&self) -> bool {
223 matches!(self.dcache, DirEntryCache::Permanent(_))
224 }
225
226 /// Returns the `FsLockDepType` of this filesystem, delegated from `FileSystemOps`.
227 pub fn fs_lockdep_type(&self) -> FsLockDepType {
228 self.ops.fs_lockdep_type()
229 }
230
231 /// The root directory entry of this file system.
232 ///
233 /// Panics if this file system does not have a root directory.
234 pub fn root(&self) -> &DirEntryHandle {
235 self.root.get().unwrap_or_else(|| panic!("FileSystem {} has no root", self.name()))
236 }
237
238 /// The root directory entry of this `FileSystem`, if it has one.
239 pub fn maybe_root(&self) -> Option<&DirEntryHandle> {
240 self.root.get()
241 }
242
243 pub fn get_or_create_node<F>(
244 &self,
245 node_key: ino_t,
246 create_fn: F,
247 ) -> Result<FsNodeHandle, Errno>
248 where
249 F: FnOnce() -> Result<FsNodeHandle, Errno>,
250 {
251 self.get_and_validate_or_create_node(node_key, |_| true, create_fn)
252 }
253
254 /// Get a node that is validated with the callback, or create an FsNode for
255 /// this file system.
256 ///
257 /// If node_id is Some, then this function checks the node cache to
258 /// determine whether this node is already open. If so, the function
259 /// returns the existing FsNode if it passes the validation check. If no
260 /// node exists, or a node does but fails the validation check, the function
261 /// calls the given create_fn function to create the FsNode.
262 ///
263 /// If node_id is None, then this function assigns a new identifier number
264 /// and calls the given create_fn function to create the FsNode with the
265 /// assigned number.
266 ///
267 /// Returns Err only if create_fn returns Err.
268 pub fn get_and_validate_or_create_node<V, C>(
269 &self,
270 node_key: ino_t,
271 validate_fn: V,
272 create_fn: C,
273 ) -> Result<FsNodeHandle, Errno>
274 where
275 V: Fn(&FsNodeHandle) -> bool,
276 C: FnOnce() -> Result<FsNodeHandle, Errno>,
277 {
278 self.node_cache.get_and_validate_or_create_node(node_key, validate_fn, create_fn)
279 }
280
281 /// File systems that produce their own IDs for nodes should invoke this
282 /// function. The ones who leave to this object to assign the IDs should
283 /// call |create_node_and_allocate_node_id|.
284 pub fn create_node_with_flags(
285 self: &Arc<Self>,
286 ino: Option<ino_t>,
287 ops: impl Into<Box<dyn FsNodeOps>>,
288 info: FsNodeInfo,
289 flags: FsNodeFlags,
290 ) -> FsNodeHandle {
291 let ino = ino.unwrap_or_else(|| self.allocate_ino());
292 let node = FsNode::new_uncached(ino, ops, self, info, flags);
293 self.node_cache.insert_node(&node);
294 node
295 }
296
297 pub fn create_node(
298 self: &Arc<Self>,
299 ino: ino_t,
300 ops: impl Into<Box<dyn FsNodeOps>>,
301 info: FsNodeInfo,
302 ) -> FsNodeHandle {
303 self.create_node_with_flags(Some(ino), ops, info, FsNodeFlags::empty())
304 }
305
306 pub fn create_node_and_allocate_node_id(
307 self: &Arc<Self>,
308 ops: impl Into<Box<dyn FsNodeOps>>,
309 info: FsNodeInfo,
310 ) -> FsNodeHandle {
311 self.create_node_with_flags(None, ops, info, FsNodeFlags::empty())
312 }
313
314 /// Create a node for a directory that has no parent.
315 pub fn create_detached_node(
316 self: &Arc<Self>,
317 ino: ino_t,
318 ops: impl Into<Box<dyn FsNodeOps>>,
319 info: FsNodeInfo,
320 ) -> FsNodeHandle {
321 assert!(info.mode.is_dir());
322 let node = FsNode::new_uncached(ino, ops, self, info, FsNodeFlags::empty());
323 self.node_cache.insert_node(&node);
324 node
325 }
326
327 /// Create a root node for the filesystem.
328 ///
329 /// This is a convenience function that creates a root node with the default
330 /// directory mode and root credentials.
331 pub fn create_root(self: &Arc<Self>, ino: ino_t, ops: impl Into<Box<dyn FsNodeOps>>) {
332 let info = FsNodeInfo::new(mode!(IFDIR, 0o777), FsCred::root());
333 self.create_root_with_info(ino, ops, info);
334 }
335
336 pub fn create_root_with_info(
337 self: &Arc<Self>,
338 ino: ino_t,
339 ops: impl Into<Box<dyn FsNodeOps>>,
340 info: FsNodeInfo,
341 ) {
342 let node = self.create_detached_node(ino, ops, info);
343 self.set_root(node);
344 }
345
346 /// Remove the given FsNode from the node cache.
347 ///
348 /// Called from the Release trait of FsNode.
349 pub fn remove_node(&self, node: &FsNode) {
350 self.node_cache.remove_node(node);
351 }
352
353 pub fn allocate_ino(&self) -> ino_t {
354 self.node_cache
355 .allocate_ino()
356 .expect("allocate_ino called on a filesystem that uses external node IDs")
357 }
358
359 /// Allocate a contiguous block of node ids.
360 pub fn allocate_ino_range(&self, size: usize) -> Range<ino_t> {
361 self.node_cache
362 .allocate_ino_range(size)
363 .expect("allocate_ino_range called on a filesystem that uses external node IDs")
364 }
365
366 /// Move |renamed| that is at |old_name| in |old_parent| to |new_name| in |new_parent|
367 /// replacing |replaced|.
368 /// If |replaced| exists and is a directory, this function must check that |renamed| is n
369 /// directory and that |replaced| is empty.
370 pub fn rename(
371 &self,
372 current_task: &CurrentTask,
373 context: &mut RenameContext<'_>,
374 old_name: &FsStr,
375 new_name: &FsStr,
376 ) -> Result<(), Errno> {
377 self.ops.rename(self, current_task, context, old_name, new_name)
378 }
379
380 /// Exchanges the two nodes identified by `name1` and `name2` in the context.
381 /// The parent directories and other metadata are contained within the `context`.
382 pub fn exchange(
383 &self,
384 current_task: &CurrentTask,
385 context: &mut RenameContext<'_>,
386 name1: &FsStr,
387 name2: &FsStr,
388 ) -> Result<(), Errno> {
389 self.ops.exchange(self, current_task, context, name1, name2)
390 }
391
392 /// Forces a FileSystem unmount.
393 // TODO(https://fxbug.dev/394694891): kernel shutdown should ideally unmount FileSystems via
394 // their drop impl, which should be triggered by Mount.unmount().
395 pub fn force_unmount_ops(&self) {
396 self.ops.unmount();
397 }
398
399 /// Returns the `statfs` for this filesystem.
400 ///
401 /// Each `FileSystemOps` impl is expected to override this to return the specific statfs for
402 /// the filesystem.
403 ///
404 /// Returns `ENOSYS` if the `FileSystemOps` don't implement `stat`.
405 pub fn statfs(&self, current_task: &CurrentTask) -> Result<statfs, Errno> {
406 security::sb_statfs(current_task, &self)?;
407 let mut stat = self.ops.statfs(self, current_task)?;
408 if stat.f_frsize == 0 {
409 stat.f_frsize = stat.f_bsize as i64;
410 }
411 Ok(stat)
412 }
413
414 pub fn sync(&self, current_task: &CurrentTask) -> Result<(), Errno> {
415 self.ops.sync(self, current_task)
416 }
417
418 pub fn did_create_dir_entry(&self, entry: &DirEntryHandle) {
419 match &self.dcache {
420 DirEntryCache::Permanent(p) => {
421 p.lock().insert(ArcKey(entry.clone()));
422 }
423 DirEntryCache::Lru(LruCache { entries, .. }) => {
424 entries.lock().insert(ArcKey(entry.clone()), ());
425 }
426 DirEntryCache::Uncached => {}
427 }
428 }
429
430 pub fn will_destroy_dir_entry(&self, entry: &DirEntryHandle) {
431 match &self.dcache {
432 DirEntryCache::Permanent(p) => {
433 p.lock().remove(ArcKey::ref_cast(entry));
434 }
435 DirEntryCache::Lru(LruCache { entries, .. }) => {
436 entries.lock().remove(ArcKey::ref_cast(entry));
437 }
438 DirEntryCache::Uncached => {}
439 };
440 }
441
442 /// Informs the cache that the entry was used.
443 pub fn did_access_dir_entry(&self, entry: &DirEntryHandle) {
444 if let DirEntryCache::Lru(LruCache { entries, .. }) = &self.dcache {
445 entries.lock().get_refresh(ArcKey::ref_cast(entry));
446 }
447 }
448
449 /// Purges old entries from the cache. This is done as a separate step to avoid potential
450 /// deadlocks that could occur if done at admission time (where locks might be held that are
451 /// required when dropping old entries). This should be called after any new entries are
452 /// admitted with no locks held that might be required for dropping entries.
453 pub fn purge_old_entries(&self) {
454 if let DirEntryCache::Lru(l) = &self.dcache {
455 let mut purged = SmallVec::<[DirEntryHandle; 4]>::new();
456 {
457 let mut entries = l.entries.lock();
458 while entries.len() > l.capacity {
459 purged.push(entries.pop_front().unwrap().0.0);
460 }
461 }
462 // Entries will get dropped here whilst we're not holding a lock.
463 std::mem::drop(purged);
464 }
465 }
466
467 /// Returns the `FileSystem`'s `FileSystemOps` as a `&T`, or `None` if the downcast fails.
468 pub fn downcast_ops<T: 'static>(&self) -> Option<&T> {
469 self.ops.as_ref().as_any().downcast_ref()
470 }
471
472 pub fn name(&self) -> &'static FsStr {
473 self.ops.name()
474 }
475
476 pub fn manages_timestamps(&self) -> bool {
477 self.ops.manages_timestamps()
478 }
479
480 /// Returns the crypt service associated with this filesystem, if any. The crypt service
481 /// implements the fuchsia.fxfs.Crypt protocol and maintains an internal structure that maps
482 /// each encryption key id to the actual key.
483 pub fn crypt_service(&self) -> Option<Arc<CryptService>> {
484 self.ops.crypt_service()
485 }
486
487 /// Reconfigures the MountFlags associated with the filesystem with the specified `flags`.
488 /// Filesystems may customize `FsNodeOps::update_flags()` to take action (e.g. flushing dirty
489 /// files when transitioning from read-write to read-only), or to reject reconfiguration.
490 pub fn update_flags(
491 &self,
492 current_task: &CurrentTask,
493 flags: FileSystemFlags,
494 ) -> Result<(), Errno> {
495 self.ops.update_flags(self, current_task, flags)
496 }
497}
498
499/// The filesystem-implementation-specific data for FileSystem.
500pub trait FileSystemOps: AsAny + Send + Sync + 'static {
501 /// Returns the `FsLockDepType` of this filesystem.
502 ///
503 /// Defaults to `FsLockDepType::Normal`. Filesystems that can be stacked (like OverlayFS)
504 /// should override this to return `FsLockDepType::Recursive`.
505 fn fs_lockdep_type(&self) -> FsLockDepType {
506 FsLockDepType::Normal
507 }
508
509 /// Return information about this filesystem.
510 ///
511 /// A typical implementation looks like this:
512 /// ```
513 /// Ok(statfs::default(FILE_SYSTEM_MAGIC))
514 /// ```
515 /// or, if the filesystem wants to customize fields:
516 /// ```
517 /// Ok(statfs {
518 /// f_blocks: self.blocks,
519 /// ..statfs::default(FILE_SYSTEM_MAGIC)
520 /// })
521 /// ```
522 fn statfs(&self, _fs: &FileSystem, _current_task: &CurrentTask) -> Result<statfs, Errno>;
523
524 /// Reconfigure the filesystem with the given flags.
525 ///
526 /// This is called during a remount operation (MS_REMOUNT), to allow the filesystem to update
527 /// internal resources as necessary to support the new flags.
528 fn update_flags(
529 &self,
530 fs: &FileSystem,
531 _current_task: &CurrentTask,
532 new_flags: FileSystemFlags,
533 ) -> Result<(), Errno> {
534 fs.options.flags.store(new_flags, Ordering::Relaxed);
535 Ok(())
536 }
537
538 fn name(&self) -> &'static FsStr;
539
540 /// Whether this file system uses external node IDs.
541 ///
542 /// If this is true, then the file system is responsible for assigning node IDs to its nodes.
543 /// Otherwise, the VFS will assign node IDs to the nodes.
544 fn uses_external_node_ids(&self) -> bool {
545 false
546 }
547
548 /// Rename the given node.
549 ///
550 /// The node to be renamed is passed as "renamed". It currently has
551 /// old_name in old_parent. After the rename operation, it should have
552 /// new_name in new_parent.
553 ///
554 /// If new_parent already has a child named new_name, that node is passed as
555 /// "replaced". In that case, both "renamed" and "replaced" will be
556 /// directories and the rename operation should succeed only if "replaced"
557 /// is empty. The VFS will check that there are no children of "replaced" in
558 /// the DirEntry cache, but the implementation of this function is
559 /// responsible for checking that there are no children of replaced that are
560 /// known only to the file system implementation (e.g., present on-disk but
561 /// not in the DirEntry cache).
562 fn rename(
563 &self,
564 _fs: &FileSystem,
565 _current_task: &CurrentTask,
566 _context: &mut RenameContext<'_>,
567 _old_name: &FsStr,
568 _new_name: &FsStr,
569 ) -> Result<(), Errno> {
570 error!(EROFS)
571 }
572
573 /// Exchanges the two nodes identified by `name1` and `name2` in the context.
574 ///
575 /// Semantically, this is an atomic exchange of two paths (similar to two
576 /// renames, one in each direction). It uses `RenameContext` because the
577 /// locking requirements and metadata needed (parent directories, node info)
578 /// are identical to a rename operation involving two paths.
579 fn exchange(
580 &self,
581 _fs: &FileSystem,
582 _current_task: &CurrentTask,
583 _context: &mut RenameContext<'_>,
584 _name1: &FsStr,
585 _name2: &FsStr,
586 ) -> Result<(), Errno> {
587 error!(EINVAL)
588 }
589
590 /// Called when the filesystem is unmounted.
591 fn unmount(&self) {}
592
593 /// Indicates if the filesystem can manage the timestamps (i.e. ctime and mtime).
594 ///
595 /// Starnix updates the timestamps in FsNode's `info` directly. However, if the filesystem can
596 /// manage the timestamps, then Starnix does not need to do so. `info` will be refreshed with
597 /// the timestamps from the filesystem by calling `fetch_and_refresh_info(..)` on the FsNode.
598 fn manages_timestamps(&self) -> bool {
599 false
600 }
601
602 /// Returns the crypt service associated with this filesystem, if any.
603 fn crypt_service(&self) -> Option<Arc<CryptService>> {
604 None
605 }
606
607 fn sync(&self, _fs: &FileSystem, _current_task: &CurrentTask) -> Result<(), Errno> {
608 Ok(())
609 }
610}
611
612impl Drop for FileSystem {
613 fn drop(&mut self) {
614 self.ops.unmount();
615 }
616}
617
618pub type FileSystemHandle = Arc<FileSystem>;