Model symlinks better in FakeFs, add read_link Fs method
This commit is contained in:
parent
4c03231863
commit
55f1a6647f
2 changed files with 65 additions and 24 deletions
|
@ -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 = []
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue