Skip to content

Commit bd133ee

Browse files
committed
fix: VFS should not walk circular symlinks
As of #6246, rust-analyzer follows symlinks. This can introduce an infinite loop if symlinks point to parent directories. Considering that #6246 was added in 2020 without many bug reports, this is clearly a rare occurrence. However, I am observing rust-analyzer hang on projects that have symlinks of the form: ``` test/a_symlink -> ../../ ``` Ignore symlinks that only point to the parent directories, as this is more robust but still allows typical symlink usage patterns.
1 parent 46702ff commit bd133ee

1 file changed

Lines changed: 27 additions & 1 deletion

File tree

‎crates/vfs-notify/src/lib.rs‎

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@
99
1010
#![warn(rust_2018_idioms, unused_lifetimes)]
1111

12-
use std::fs;
12+
use std::{
13+
fs,
14+
path::{Component,Path},
15+
};
1316

1417
use crossbeam_channel::{never, select, unbounded,Receiver,Sender};
1518
use notify::{Config,RecommendedWatcher,RecursiveMode,Watcher};
@@ -206,6 +209,11 @@ impl NotifyActor {
206209
returntrue;
207210
}
208211
let path = entry.path();
212+
213+
ifpath_is_parent_symlink(path){
214+
returnfalse;
215+
}
216+
209217
root == path
210218
|| dirs.exclude.iter().chain(&dirs.include).all(|it| it != path)
211219
});
@@ -258,3 +266,21 @@ fn read(path: &AbsPath) -> Option<Vec<u8>> {
258266
fnlog_notify_error<T>(res: notify::Result<T>) -> Option<T>{
259267
res.map_err(|err| tracing::warn!("notify error: {}", err)).ok()
260268
}
269+
270+
/// Is `path` a symlink to a parent directory?
271+
///
272+
/// Including this path is guaranteed to cause an infinite loop. This
273+
/// heuristic is not sufficient to catch all symlink cycles (it's
274+
/// possible to construct cycle using two or more symlinks), but it
275+
/// catches common cases.
276+
fnpath_is_parent_symlink(path:&Path) -> bool{
277+
letOk(destination) = std::fs::read_link(path)else{
278+
returnfalse;
279+
};
280+
281+
// If the symlink is of the form "../..", it's a parent symlink.
282+
let is_relative_parent =
283+
destination.components().all(|c| matches!(c,Component::CurDir | Component::ParentDir));
284+
285+
is_relative_parent || path.starts_with(destination)
286+
}

0 commit comments

Comments
 (0)