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 content;
19mod disk;
20pub mod embed;
21mod error;
22mod fs_map;
23pub mod glob;
24mod globset;
25pub mod invalidation;
26mod invalidator_map;
27pub mod json;
28mod mutex_map;
29mod null_fs;
30mod path;
31mod path_map;
32mod read_glob;
33mod retry;
34pub mod rope;
35pub mod source_context;
36pub mod util;
37pub(crate) mod virtual_fs;
38mod watcher;
39#[cfg(windows)]
40pub mod windows;
41
42use std::{fmt::Debug, fs::FileType, path::PathBuf};
43
44use anyhow::Result;
45use auto_hash_map::AutoMap;
46use bincode::{Decode, Encode};
47use turbo_rcstr::RcStr;
48use turbo_tasks::{NonLocalValue, ResolvedVc, ValueToString, Vc, turbobail, turbofmt};
49
50pub(crate) use crate::{
51    content::FileComparison,
52    disk::{DiskFileSystemInner, format_absolute_fs_path},
53    error::AnyhowWrapper,
54};
55pub use crate::{
56    content::{
57        File, FileContent, FileJsonContent, FileLine, FileLinesContent, FileMeta, LinkContent,
58        LinkTarget, Permissions, PersistedFileContent, WriteLinkContent, WriteLinkTargetType,
59    },
60    disk::{DiskFileSystem, canonicalize_to_rcstr, validate_path_length},
61    fs_map::DiskFileSystemMap,
62    null_fs::NullFileSystem,
63    path::{
64        FileSystemPath, FileSystemPathOption, RealPathError, RealPathErrorType,
65        RealPathWithLinksResult, rebase,
66    },
67    read_glob::ReadGlobResult,
68    virtual_fs::VirtualFileSystem,
69    watcher::{DiskWatcherConfig, DiskWatcherPathMatcher, DiskWatcherRecursiveMode},
70};
71
72#[turbo_tasks::value_trait]
73pub trait FileSystem: ValueToString {
74    /// Returns the path to the root of the file system.
75    #[turbo_tasks::function]
76    fn root(self: ResolvedVc<Self>) -> Vc<FileSystemPath> {
77        FileSystemPath::new_normalized_unchecked(self, RcStr::default()).cell()
78    }
79    #[turbo_tasks::function]
80    fn read(self: Vc<Self>, fs_path: FileSystemPath) -> Vc<FileContent>;
81    /// Reads the target of a symbolic link (or junction point on Windows). See [`LinkContent`].
82    #[turbo_tasks::function]
83    fn read_link(self: Vc<Self>, fs_path: FileSystemPath) -> Vc<LinkContent>;
84    /// Returns whether a symbolic link is a junction point on Windows. Always `false` on all other
85    /// platforms.
86    #[turbo_tasks::function]
87    fn is_junction_point(self: Vc<Self>, fs_path: FileSystemPath) -> Vc<bool>;
88    #[turbo_tasks::function]
89    fn raw_read_dir(self: Vc<Self>, fs_path: FileSystemPath) -> Vc<RawDirectoryContent>;
90    #[turbo_tasks::function]
91    fn write(self: Vc<Self>, fs_path: FileSystemPath, content: Vc<FileContent>) -> Vc<()>;
92    /// Creates a symbolic link. See [`WriteLinkContent`] and [`FileSystemPath::write_link`].
93    #[turbo_tasks::function]
94    fn write_link(self: Vc<Self>, fs_path: FileSystemPath, target: Vc<WriteLinkContent>) -> Vc<()>;
95    #[turbo_tasks::function]
96    fn metadata(self: Vc<Self>, fs_path: FileSystemPath) -> Vc<FileMeta>;
97}
98
99#[derive(Hash, Clone, Debug, PartialEq, Eq, NonLocalValue, Encode, Decode)]
100pub enum RawDirectoryEntry {
101    File,
102    Directory,
103    Symlink,
104    // Other just means 'not a file, directory, or symlink'
105    Other,
106}
107
108#[derive(Hash, Clone, Debug, PartialEq, Eq, NonLocalValue, Encode, Decode)]
109pub enum DirectoryEntry {
110    File(FileSystemPath),
111    Directory(FileSystemPath),
112    Symlink(FileSystemPath),
113    Other(FileSystemPath),
114    Error(RcStr),
115}
116
117impl DirectoryEntry {
118    /// Handles the `DirectoryEntry::Symlink` variant by checking the symlink target
119    /// type and replacing it with `DirectoryEntry::File` or
120    /// `DirectoryEntry::Directory`.
121    pub async fn resolve_symlink(self) -> Result<Self> {
122        if let DirectoryEntry::Symlink(symlink) = &self {
123            let result = &*symlink.realpath_with_links().await?;
124            let real_path = match &result.path_result {
125                Ok(path) => path,
126                Err(error) => {
127                    return Ok(DirectoryEntry::Error(RcStr::from(error.to_string())));
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}