|
9 | 9 |
|
10 | 10 | #![warn(rust_2018_idioms, unused_lifetimes)]
|
11 | 11 |
|
12 |
| -use std::fs; |
| 12 | +use std::{ |
| 13 | + fs, |
| 14 | + path::{Component, Path}, |
| 15 | +}; |
13 | 16 |
|
14 | 17 | use crossbeam_channel::{never, select, unbounded, Receiver, Sender};
|
15 | 18 | use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher};
|
@@ -206,6 +209,11 @@ impl NotifyActor {
|
206 | 209 | return true;
|
207 | 210 | }
|
208 | 211 | let path = entry.path();
|
| 212 | + |
| 213 | + if path_is_parent_symlink(path) { |
| 214 | + return false; |
| 215 | + } |
| 216 | + |
209 | 217 | root == path
|
210 | 218 | || dirs.exclude.iter().chain(&dirs.include).all(|it| it != path)
|
211 | 219 | });
|
@@ -258,3 +266,21 @@ fn read(path: &AbsPath) -> Option<Vec<u8>> {
|
258 | 266 | fn log_notify_error<T>(res: notify::Result<T>) -> Option<T> {
|
259 | 267 | res.map_err(|err| tracing::warn!("notify error: {}", err)).ok()
|
260 | 268 | }
|
| 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 | +fn path_is_parent_symlink(path: &Path) -> bool { |
| 277 | + let Ok(destination) = std::fs::read_link(path) else { |
| 278 | + return false; |
| 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