Skip to main content

turbo_tasks_fs/
lib.rs

1#![feature(arbitrary_self_types)]
2#![feature(arbitrary_self_types_pointers)]
3#![feature(btree_cursors)] // needed for the `InvalidatorMap` and watcher, reduces time complexity
4#![feature(io_error_more)]
5#![feature(min_specialization)]
6// if `normalize_lexically` isn't eventually stabilized, we can copy the implementation from the
7// stdlib into our source tree
8#![feature(normalize_lexically)]
9#![feature(trivial_bounds)]
10// Junction points are used on Windows. We could use a third-party crate for this if the junction
11// API isn't eventually stabilized.
12#![cfg_attr(windows, feature(junction_point))]
13#![allow(clippy::needless_return)] // tokio macro-generated code doesn't respect this
14#![allow(clippy::mutable_key_type)]
15
16mod content;
17mod disk;
18pub mod embed;
19mod error;
20pub mod glob;
21mod globset;
22pub mod invalidation;
23mod invalidator_map;
24pub mod json;
25mod mutex_map;
26mod null_fs;
27mod path;
28mod path_map;
29mod read_glob;
30mod retry;
31pub mod rope;
32pub mod source_context;
33pub mod util;
34pub(crate) mod virtual_fs;
35mod watcher;
36mod windows;
37
38use std::{fmt::Debug, fs::FileType, path::PathBuf};
39
40use anyhow::Result;
41use auto_hash_map::AutoMap;
42use bincode::{Decode, Encode};
43use turbo_rcstr::RcStr;
44use turbo_tasks::{
45    NonLocalValue, ResolvedVc, ValueToString, Vc, trace::TraceRawVcs, turbobail, turbofmt,
46};
47
48pub(crate) use crate::{
49    content::FileComparison,
50    disk::{DiskFileSystemInner, format_absolute_fs_path},
51    error::AnyhowWrapper,
52};
53pub use crate::{
54    content::{
55        File, FileContent, FileJsonContent, FileLine, FileLinesContent, FileMeta, LinkContent,
56        LinkType, Permissions, PersistedFileContent,
57    },
58    disk::{DiskFileSystem, canonicalize_to_rcstr, validate_path_length},
59    null_fs::NullFileSystem,
60    path::{FileSystemPath, FileSystemPathOption, RealPathResult, RealPathResultError, rebase},
61    read_glob::ReadGlobResult,
62    virtual_fs::VirtualFileSystem,
63    watcher::{DiskWatcherConfig, DiskWatcherRecursiveMode},
64    windows::to_verbatim_with_case_folded_disk,
65};
66
67#[turbo_tasks::value_trait]
68pub trait FileSystem: ValueToString {
69    /// Returns the path to the root of the file system.
70    #[turbo_tasks::function]
71    fn root(self: ResolvedVc<Self>) -> Vc<FileSystemPath> {
72        FileSystemPath::new_normalized_unchecked(self, RcStr::default()).cell()
73    }
74    #[turbo_tasks::function]
75    fn read(self: Vc<Self>, fs_path: FileSystemPath) -> Vc<FileContent>;
76    /// Reads the target of a symbolic link (or of a junction point on Windows).
77    ///
78    /// The base of the returned [`LinkContent::Link`] `target` depends on the link's
79    /// [`LinkType`]: root-relative and normalized for [`LinkType::ABSOLUTE`] links, or the raw
80    /// link-relative on-disk value otherwise.
81    ///
82    /// Returns [`LinkContent::Invalid`] if the target points outside of the filesystem root, and
83    /// [`LinkContent::NotFound`] if `fs_path` doesn't exist or isn't a link.
84    #[turbo_tasks::function]
85    fn read_link(self: Vc<Self>, fs_path: FileSystemPath) -> Vc<LinkContent>;
86    #[turbo_tasks::function]
87    fn raw_read_dir(self: Vc<Self>, fs_path: FileSystemPath) -> Vc<RawDirectoryContent>;
88    #[turbo_tasks::function]
89    fn write(self: Vc<Self>, fs_path: FileSystemPath, content: Vc<FileContent>) -> Vc<()>;
90    /// See [`FileSystemPath::write_symbolic_link_dir`].
91    #[turbo_tasks::function]
92    fn write_link(self: Vc<Self>, fs_path: FileSystemPath, target: Vc<LinkContent>) -> Vc<()>;
93    #[turbo_tasks::function]
94    fn metadata(self: Vc<Self>, fs_path: FileSystemPath) -> Vc<FileMeta>;
95}
96
97#[derive(Hash, Clone, Debug, PartialEq, Eq, TraceRawVcs, NonLocalValue, Encode, Decode)]
98pub enum RawDirectoryEntry {
99    File,
100    Directory,
101    Symlink,
102    // Other just means 'not a file, directory, or symlink'
103    Other,
104}
105
106#[derive(Hash, Clone, Debug, PartialEq, Eq, TraceRawVcs, NonLocalValue, Encode, Decode)]
107pub enum DirectoryEntry {
108    File(FileSystemPath),
109    Directory(FileSystemPath),
110    Symlink(FileSystemPath),
111    Other(FileSystemPath),
112    Error(RcStr),
113}
114
115impl DirectoryEntry {
116    /// Handles the `DirectoryEntry::Symlink` variant by checking the symlink target
117    /// type and replacing it with `DirectoryEntry::File` or
118    /// `DirectoryEntry::Directory`.
119    pub async fn resolve_symlink(self) -> Result<Self> {
120        if let DirectoryEntry::Symlink(symlink) = &self {
121            let result = &*symlink.realpath_with_links().await?;
122            let real_path = match &result.path_result {
123                Ok(path) => path,
124                Err(error) => {
125                    return Ok(DirectoryEntry::Error(
126                        error.as_error_message(symlink, result).await?,
127                    ));
128                }
129            };
130            Ok(match *real_path.get_type().await? {
131                FileSystemEntryType::Directory => DirectoryEntry::Directory(real_path.clone()),
132                FileSystemEntryType::File => DirectoryEntry::File(real_path.clone()),
133                // Happens if the link is to a non-existent file
134                FileSystemEntryType::NotFound => DirectoryEntry::Error(
135                    turbofmt!("Symlink {symlink} points at {real_path} which does not exist")
136                        .await?,
137                ),
138                // This is caused by eventual consistency
139                FileSystemEntryType::Symlink => turbobail!(
140                    "Symlink {symlink} points at a symlink but realpath_with_links returned a path"
141                ),
142                _ => self,
143            })
144        } else {
145            Ok(self)
146        }
147    }
148
149    pub fn path(self) -> Option<FileSystemPath> {
150        match self {
151            DirectoryEntry::File(path)
152            | DirectoryEntry::Directory(path)
153            | DirectoryEntry::Symlink(path)
154            | DirectoryEntry::Other(path) => Some(path),
155            DirectoryEntry::Error(_) => None,
156        }
157    }
158}
159
160#[turbo_tasks::value]
161#[derive(Hash, Clone, Copy, Debug)]
162pub enum FileSystemEntryType {
163    NotFound,
164    File,
165    Directory,
166    Symlink,
167    /// These would be things like named pipes, sockets, etc.
168    Other,
169    Error,
170}
171
172impl From<FileType> for FileSystemEntryType {
173    fn from(file_type: FileType) -> Self {
174        match file_type {
175            t if t.is_dir() => FileSystemEntryType::Directory,
176            t if t.is_file() => FileSystemEntryType::File,
177            t if t.is_symlink() => FileSystemEntryType::Symlink,
178            _ => FileSystemEntryType::Other,
179        }
180    }
181}
182
183impl From<DirectoryEntry> for FileSystemEntryType {
184    fn from(entry: DirectoryEntry) -> Self {
185        FileSystemEntryType::from(&entry)
186    }
187}
188
189impl From<&DirectoryEntry> for FileSystemEntryType {
190    fn from(entry: &DirectoryEntry) -> Self {
191        match entry {
192            DirectoryEntry::File(_) => FileSystemEntryType::File,
193            DirectoryEntry::Directory(_) => FileSystemEntryType::Directory,
194            DirectoryEntry::Symlink(_) => FileSystemEntryType::Symlink,
195            DirectoryEntry::Other(_) => FileSystemEntryType::Other,
196            DirectoryEntry::Error(_) => FileSystemEntryType::Error,
197        }
198    }
199}
200
201impl From<RawDirectoryEntry> for FileSystemEntryType {
202    fn from(entry: RawDirectoryEntry) -> Self {
203        FileSystemEntryType::from(&entry)
204    }
205}
206
207impl From<&RawDirectoryEntry> for FileSystemEntryType {
208    fn from(entry: &RawDirectoryEntry) -> Self {
209        match entry {
210            RawDirectoryEntry::File => FileSystemEntryType::File,
211            RawDirectoryEntry::Directory => FileSystemEntryType::Directory,
212            RawDirectoryEntry::Symlink => FileSystemEntryType::Symlink,
213            RawDirectoryEntry::Other => FileSystemEntryType::Other,
214        }
215    }
216}
217
218#[turbo_tasks::value]
219#[derive(Debug)]
220pub enum RawDirectoryContent {
221    // The entry keys are the directory relative file names
222    // e.g. for `/bar/foo`, it will be `foo`
223    Entries(AutoMap<RcStr, RawDirectoryEntry>),
224    NotFound,
225}
226
227impl RawDirectoryContent {
228    pub fn new(entries: AutoMap<RcStr, RawDirectoryEntry>) -> Vc<Self> {
229        Self::cell(RawDirectoryContent::Entries(entries))
230    }
231
232    pub fn not_found() -> Vc<Self> {
233        Self::cell(RawDirectoryContent::NotFound)
234    }
235}
236
237#[turbo_tasks::value]
238#[derive(Debug)]
239pub enum DirectoryContent {
240    Entries(AutoMap<RcStr, DirectoryEntry>),
241    NotFound,
242}
243
244impl DirectoryContent {
245    pub fn new(entries: AutoMap<RcStr, DirectoryEntry>) -> Vc<Self> {
246        Self::cell(DirectoryContent::Entries(entries))
247    }
248
249    pub fn not_found() -> Vc<Self> {
250        Self::cell(DirectoryContent::NotFound)
251    }
252}
253
254pub async fn to_sys_path(path: FileSystemPath) -> Result<Option<PathBuf>> {
255    if let Some(fs) = ResolvedVc::try_downcast_type::<DiskFileSystem>(path.fs) {
256        let sys_path = fs.await?.to_sys_path(&path);
257        return Ok(Some(sys_path));
258    }
259
260    Ok(None)
261}