Model symlinks better in FakeFs, add read_link Fs method

This commit is contained in:
Max Brunsfeld 2023-06-13 17:16:11 -07:00
parent 4c03231863
commit 55f1a6647f
2 changed files with 65 additions and 24 deletions

View file

@ -32,5 +32,8 @@ serde_json.workspace = true
log.workspace = true log.workspace = true
libc = "0.2" libc = "0.2"
[dev-dependencies]
gpui = { path = "../gpui", features = ["test-support"] }
[features] [features]
test-support = [] test-support = []

View file

@ -108,6 +108,7 @@ pub trait Fs: Send + Sync {
async fn canonicalize(&self, path: &Path) -> Result<PathBuf>; async fn canonicalize(&self, path: &Path) -> Result<PathBuf>;
async fn is_file(&self, path: &Path) -> bool; async fn is_file(&self, path: &Path) -> bool;
async fn metadata(&self, path: &Path) -> Result<Option<Metadata>>; async fn metadata(&self, path: &Path) -> Result<Option<Metadata>>;
async fn read_link(&self, path: &Path) -> Result<PathBuf>;
async fn read_dir( async fn read_dir(
&self, &self,
path: &Path, path: &Path,
@ -323,6 +324,11 @@ impl Fs for RealFs {
})) }))
} }
async fn read_link(&self, path: &Path) -> Result<PathBuf> {
let path = smol::fs::read_link(path).await?;
Ok(path)
}
async fn read_dir( async fn read_dir(
&self, &self,
path: &Path, path: &Path,
@ -407,46 +413,51 @@ enum FakeFsEntry {
impl FakeFsState { impl FakeFsState {
fn read_path<'a>(&'a self, target: &Path) -> Result<Arc<Mutex<FakeFsEntry>>> { fn read_path<'a>(&'a self, target: &Path) -> Result<Arc<Mutex<FakeFsEntry>>> {
Ok(self Ok(self
.try_read_path(target) .try_read_path(target, true)
.ok_or_else(|| anyhow!("path does not exist: {}", target.display()))? .ok_or_else(|| anyhow!("path does not exist: {}", target.display()))?
.0) .0)
} }
fn try_read_path<'a>(&'a self, target: &Path) -> Option<(Arc<Mutex<FakeFsEntry>>, PathBuf)> { fn try_read_path<'a>(
&'a self,
target: &Path,
follow_symlink: bool,
) -> Option<(Arc<Mutex<FakeFsEntry>>, PathBuf)> {
let mut path = target.to_path_buf(); let mut path = target.to_path_buf();
let mut real_path = PathBuf::new(); let mut canonical_path = PathBuf::new();
let mut entry_stack = Vec::new(); let mut entry_stack = Vec::new();
'outer: loop { 'outer: loop {
let mut path_components = path.components().collect::<collections::VecDeque<_>>(); let mut path_components = path.components().peekable();
while let Some(component) = path_components.pop_front() { while let Some(component) = path_components.next() {
match component { match component {
Component::Prefix(_) => panic!("prefix paths aren't supported"), Component::Prefix(_) => panic!("prefix paths aren't supported"),
Component::RootDir => { Component::RootDir => {
entry_stack.clear(); entry_stack.clear();
entry_stack.push(self.root.clone()); entry_stack.push(self.root.clone());
real_path.clear(); canonical_path.clear();
real_path.push("/"); canonical_path.push("/");
} }
Component::CurDir => {} Component::CurDir => {}
Component::ParentDir => { Component::ParentDir => {
entry_stack.pop()?; entry_stack.pop()?;
real_path.pop(); canonical_path.pop();
} }
Component::Normal(name) => { Component::Normal(name) => {
let current_entry = entry_stack.last().cloned()?; let current_entry = entry_stack.last().cloned()?;
let current_entry = current_entry.lock(); let current_entry = current_entry.lock();
if let FakeFsEntry::Dir { entries, .. } = &*current_entry { if let FakeFsEntry::Dir { entries, .. } = &*current_entry {
let entry = entries.get(name.to_str().unwrap()).cloned()?; let entry = entries.get(name.to_str().unwrap()).cloned()?;
let _entry = entry.lock(); if path_components.peek().is_some() || follow_symlink {
if let FakeFsEntry::Symlink { target, .. } = &*_entry { let entry = entry.lock();
let mut target = target.clone(); if let FakeFsEntry::Symlink { target, .. } = &*entry {
target.extend(path_components); let mut target = target.clone();
path = target; target.extend(path_components);
continue 'outer; path = target;
} else { continue 'outer;
entry_stack.push(entry.clone()); }
real_path.push(name);
} }
entry_stack.push(entry.clone());
canonical_path.push(name);
} else { } else {
return None; return None;
} }
@ -455,7 +466,7 @@ impl FakeFsState {
} }
break; break;
} }
entry_stack.pop().map(|entry| (entry, real_path)) Some((entry_stack.pop()?, canonical_path))
} }
fn write_path<Fn, T>(&self, path: &Path, callback: Fn) -> Result<T> fn write_path<Fn, T>(&self, path: &Path, callback: Fn) -> Result<T>
@ -776,6 +787,10 @@ impl FakeFsEntry {
matches!(self, Self::File { .. }) matches!(self, Self::File { .. })
} }
fn is_symlink(&self) -> bool {
matches!(self, Self::Symlink { .. })
}
fn file_content(&self, path: &Path) -> Result<&String> { fn file_content(&self, path: &Path) -> Result<&String> {
if let Self::File { content, .. } = self { if let Self::File { content, .. } = self {
Ok(content) Ok(content)
@ -1056,8 +1071,8 @@ impl Fs for FakeFs {
let path = normalize_path(path); let path = normalize_path(path);
self.simulate_random_delay().await; self.simulate_random_delay().await;
let state = self.state.lock(); let state = self.state.lock();
if let Some((_, real_path)) = state.try_read_path(&path) { if let Some((_, canonical_path)) = state.try_read_path(&path, true) {
Ok(real_path) Ok(canonical_path)
} else { } else {
Err(anyhow!("path does not exist: {}", path.display())) Err(anyhow!("path does not exist: {}", path.display()))
} }
@ -1067,7 +1082,7 @@ impl Fs for FakeFs {
let path = normalize_path(path); let path = normalize_path(path);
self.simulate_random_delay().await; self.simulate_random_delay().await;
let state = self.state.lock(); let state = self.state.lock();
if let Some((entry, _)) = state.try_read_path(&path) { if let Some((entry, _)) = state.try_read_path(&path, true) {
entry.lock().is_file() entry.lock().is_file()
} else { } else {
false false
@ -1078,10 +1093,17 @@ impl Fs for FakeFs {
self.simulate_random_delay().await; self.simulate_random_delay().await;
let path = normalize_path(path); let path = normalize_path(path);
let state = self.state.lock(); let state = self.state.lock();
if let Some((entry, real_path)) = state.try_read_path(&path) { if let Some((mut entry, _)) = state.try_read_path(&path, false) {
let entry = entry.lock(); let is_symlink = entry.lock().is_symlink();
let is_symlink = real_path != path; if is_symlink {
if let Some(e) = state.try_read_path(&path, true).map(|e| e.0) {
entry = e;
} else {
return Ok(None);
}
}
let entry = entry.lock();
Ok(Some(match &*entry { Ok(Some(match &*entry {
FakeFsEntry::File { inode, mtime, .. } => Metadata { FakeFsEntry::File { inode, mtime, .. } => Metadata {
inode: *inode, inode: *inode,
@ -1102,6 +1124,22 @@ impl Fs for FakeFs {
} }
} }
async fn read_link(&self, path: &Path) -> Result<PathBuf> {
self.simulate_random_delay().await;
let path = normalize_path(path);
let state = self.state.lock();
if let Some((entry, _)) = state.try_read_path(&path, false) {
let entry = entry.lock();
if let FakeFsEntry::Symlink { target } = &*entry {
Ok(target.clone())
} else {
Err(anyhow!("not a symlink: {}", path.display()))
}
} else {
Err(anyhow!("path does not exist: {}", path.display()))
}
}
async fn read_dir( async fn read_dir(
&self, &self,
path: &Path, path: &Path,