Skip to main content

turbo_tasks_fs/
fs_map.rs

1use std::{
2    ops::Bound,
3    path::{Path, PathBuf},
4};
5
6use turbo_frozenmap::FrozenMap;
7use turbo_rcstr::RcStr;
8use turbo_tasks::{OperationVc, ResolvedVc, Vc};
9use turbo_unix_path::sys_to_unix;
10
11use crate::{DiskFileSystem, FileSystemPath, canonicalized_path_cache::CanonicalizedPathWalkCache};
12
13/// An ordered set of canonical system roots and their owning filesystems.
14///
15/// The roots must not overlap: no root may be an ancestor of another root. [`Self::lookup_fs_path`]
16/// relies on this invariant when selecting the nearest preceding root in path order.
17#[turbo_tasks::value(shared, eq = "manual")]
18pub struct DiskFileSystemMap {
19    roots: FrozenMap<PathBuf, ResolvedVc<DiskFileSystem>>,
20    #[turbo_tasks(debug_ignore, unsafe_ignore)]
21    #[bincode(skip)]
22    pub(crate) canonicalized_paths: CanonicalizedPathWalkCache,
23}
24
25impl PartialEq for DiskFileSystemMap {
26    fn eq(&self, other: &Self) -> bool {
27        self.roots == other.roots
28    }
29}
30
31impl Eq for DiskFileSystemMap {}
32
33impl FromIterator<(PathBuf, ResolvedVc<DiskFileSystem>)> for DiskFileSystemMap {
34    fn from_iter<T: IntoIterator<Item = (PathBuf, ResolvedVc<DiskFileSystem>)>>(iter: T) -> Self {
35        let filesystems = FrozenMap::from_iter(iter);
36        for pair in filesystems.as_slice().windows(2) {
37            let (previous_root, _) = &pair[0];
38            let (root, _) = &pair[1];
39            assert!(
40                !root.starts_with(previous_root),
41                "filesystem root {} overlaps another filesystem root",
42                root.display()
43            );
44        }
45        DiskFileSystemMap {
46            roots: filesystems,
47            canonicalized_paths: Default::default(),
48        }
49    }
50}
51
52impl DiskFileSystemMap {
53    pub(crate) fn contains(&self, root: &Path, current: ResolvedVc<DiskFileSystem>) -> bool {
54        self.roots.get(root).is_some_and(|fs| *fs == current)
55    }
56
57    pub fn len(&self) -> usize {
58        self.roots.len()
59    }
60
61    pub fn is_empty(&self) -> bool {
62        self.roots.is_empty()
63    }
64
65    /// Finds the containing root; the suffix is empty only when `path` is that root.
66    pub(crate) fn lookup_sys_path_suffix<'a>(
67        &self,
68        path: &'a Path,
69    ) -> Option<LookupSysPathSuffix<'a>> {
70        let (root, fs) = self
71            .roots
72            .range::<Path, _>((Bound::Unbounded, Bound::Included(path)))
73            .next_back()?;
74        Some(LookupSysPathSuffix {
75            fs: *fs,
76            remaining: path.strip_prefix(root).ok()?,
77        })
78    }
79
80    /// Converts an absolute system path into a path owned by one of the installed filesystems.
81    ///
82    /// Returns `None` if the file path does not exist inside any other root, or if the relative
83    /// path would not be valid unicode.
84    pub fn lookup_fs_path(&self, path: &Path) -> Option<FileSystemPath> {
85        let LookupSysPathSuffix {
86            fs,
87            remaining: relative,
88        } = self.lookup_sys_path_suffix(path)?;
89        let relative = relative.to_str()?;
90        Some(FileSystemPath::new_normalized_unchecked(
91            ResolvedVc::upcast(fs),
92            RcStr::from(sys_to_unix(relative)),
93        ))
94    }
95
96    /// Creates a new empty `DiskFileSystemMap`, used when constructing a [`DiskFileSystem`] that
97    /// cannot traverse to any other roots outside of itself.
98    pub fn empty() -> OperationVc<DiskFileSystemMap> {
99        #[turbo_tasks::function(operation)]
100        pub fn operation() -> Vc<DiskFileSystemMap> {
101            DiskFileSystemMap {
102                roots: FrozenMap::new(),
103                canonicalized_paths: Default::default(),
104            }
105            .cell()
106        }
107        operation()
108    }
109}
110
111pub struct LookupSysPathSuffix<'a> {
112    pub fs: ResolvedVc<DiskFileSystem>,
113    pub remaining: &'a Path,
114}
115
116#[cfg(test)]
117mod tests {
118    use turbo_rcstr::rcstr;
119    use turbo_tasks_backend::{BackendOptions, TurboTasksBackend, noop_backing_storage};
120
121    use super::*;
122
123    #[tokio::test]
124    async fn component_safe_lookup() {
125        #[turbo_tasks::function(operation, root)]
126        async fn assert_component_safe_lookup() -> anyhow::Result<()> {
127            let fs = DiskFileSystem::new(rcstr!("root"), Vc::cell(rcstr!("/tmp/root")))
128                .to_resolved()
129                .await?;
130            let map: DiskFileSystemMap = [(PathBuf::from("/tmp/root"), fs)].into_iter().collect();
131            assert_eq!(map.len(), 1);
132            assert!(map.contains(Path::new("/tmp/root"), fs));
133            assert_eq!(
134                map.lookup_fs_path(Path::new("/tmp/root/file"))
135                    .unwrap()
136                    .path,
137                "file"
138            );
139            assert!(
140                map.lookup_fs_path(Path::new("/tmp/root-other/file"))
141                    .is_none()
142            );
143            Ok(())
144        }
145
146        let tt = turbo_tasks::TurboTasks::new(TurboTasksBackend::new(
147            BackendOptions::default(),
148            noop_backing_storage(),
149        ));
150        tt.run_once(async {
151            assert_component_safe_lookup()
152                .read_strongly_consistent()
153                .await
154        })
155        .await
156        .unwrap();
157    }
158}