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