diff --git a/yazi-fs/src/provider/local/calculator.rs b/yazi-fs/src/provider/local/calculator.rs index 5ad9afbb..ef91ac63 100644 --- a/yazi-fs/src/provider/local/calculator.rs +++ b/yazi-fs/src/provider/local/calculator.rs @@ -3,13 +3,19 @@ use std::{collections::VecDeque, future::poll_fn, io, mem, path::{Path, PathBuf} use either::Either; use tokio::task::JoinHandle; +#[cfg(target_os = "linux")] +use crate::mounts::Partition; use crate::cha::Cha; type Task = Either; +#[cfg(target_os = "linux")] +type SystemicMounts = std::collections::HashSet; +#[cfg(not(target_os = "linux"))] +type SystemicMounts = (); pub enum SizeCalculator { - Idle((VecDeque, Option), Cha), - Pending(JoinHandle<(VecDeque, Option)>, Cha), + Idle((VecDeque, Option, SystemicMounts), Cha), + Pending(JoinHandle<(VecDeque, Option, SystemicMounts)>, Cha), } impl SizeCalculator { @@ -18,12 +24,17 @@ impl SizeCalculator { tokio::task::spawn_blocking(move || { let cha = Cha::new(p.file_name().unwrap_or_default(), std::fs::symlink_metadata(&p)?); if !cha.is_dir() { - return Ok(Self::Idle((VecDeque::new(), Some(cha.len)), cha)); + return Ok(Self::Idle((VecDeque::new(), Some(cha.len), systemic_mounts(&p)), cha)); + } + + let systemic = systemic_mounts(&p); + if is_systemic_mount(&p, &systemic) { + return Ok(Self::Idle((VecDeque::new(), Some(0), systemic), cha)); } let mut buf = VecDeque::from([Either::Right(std::fs::read_dir(&p)?)]); - let size = Self::next_chunk(&mut buf); - Ok(Self::Idle((buf, size), cha)) + let size = Self::next_chunk(&mut buf, &systemic); + Ok(Self::Idle((buf, size, systemic), cha)) }) .await? } @@ -47,7 +58,7 @@ impl SizeCalculator { poll_fn(|cx| { loop { match self { - Self::Idle((buf, size), cha) => { + Self::Idle((buf, size, systemic), cha) => { if let Some(s) = size.take() { return Poll::Ready(Ok(Some(s))); } else if buf.is_empty() { @@ -55,10 +66,11 @@ impl SizeCalculator { } let mut buf = mem::take(buf); + let systemic = mem::take(systemic); *self = Self::Pending( tokio::task::spawn_blocking(move || { - let size = Self::next_chunk(&mut buf); - (buf, size) + let size = Self::next_chunk(&mut buf, &systemic); + (buf, size, systemic) }), *cha, ); @@ -72,7 +84,10 @@ impl SizeCalculator { .await } - fn next_chunk(buf: &mut VecDeque>) -> Option { + fn next_chunk( + buf: &mut VecDeque>, + systemic: &SystemicMounts, + ) -> Option { let (mut i, mut size, now) = (0, 0, Instant::now()); macro_rules! pop_and_continue { () => {{ @@ -102,7 +117,10 @@ impl SizeCalculator { let Ok(ent) = next else { continue }; let Ok(ft) = ent.file_type() else { continue }; if ft.is_dir() { - buf.push_back(Either::Left(ent.path())); + let path = ent.path(); + if !is_systemic_mount(&path, systemic) { + buf.push_back(Either::Left(path)); + } } else if let Ok(meta) = ent.metadata() { size += meta.len(); } @@ -110,3 +128,86 @@ impl SizeCalculator { Some(size) } } + +#[cfg(target_os = "linux")] +fn systemic_mounts(root: &Path) -> SystemicMounts { + systemic_mounts_from(root, &std::fs::read_to_string("/proc/mounts").unwrap_or_default()) +} + +#[cfg(not(target_os = "linux"))] +fn systemic_mounts(_: &Path) -> SystemicMounts {} + +#[cfg(target_os = "linux")] +fn is_systemic_mount(path: &Path, systemic: &SystemicMounts) -> bool { systemic.contains(path) } + +#[cfg(not(target_os = "linux"))] +fn is_systemic_mount(_: &Path, _: &SystemicMounts) -> bool { false } + +#[cfg(target_os = "linux")] +fn systemic_mounts_from(root: &Path, mounts: &str) -> SystemicMounts { + mounts + .lines() + .filter_map(|line| { + let mut it = line.split_whitespace(); + let _src = it.next()?; + let dist = unmangle_octal(it.next()?); + let fstype = unmangle_octal(it.next()?); + let dist = PathBuf::from(dist.as_ref()); + (Partition { fstype: Some(fstype.into_owned().into()), ..Default::default() }.systemic() + && (dist == root || dist.starts_with(root))) + .then_some(dist) + }) + .collect() +} + +#[cfg(target_os = "linux")] +fn unmangle_octal(s: &str) -> std::borrow::Cow<'_, str> { + use yazi_shared::replace_cow; + + let mut s = std::borrow::Cow::Borrowed(s); + for (a, b) in + [(r"\011", "\t"), (r"\012", "\n"), (r"\040", " "), (r"\043", "#"), (r"\134", r"\")] + { + s = replace_cow(s, a, b); + } + s +} + +#[cfg(test)] +mod tests { + use std::{path::Path, sync::OnceLock}; + + use super::SizeCalculator; + + #[cfg(target_os = "linux")] + use super::systemic_mounts_from; + + fn init() { + static INIT: OnceLock<()> = OnceLock::new(); + INIT.get_or_init(crate::init); + } + + #[cfg(target_os = "linux")] + #[test] + fn systemic_mounts_only_include_systemic_descendants() { + let mounts = systemic_mounts_from( + Path::new("/"), + "rootfs / ext4 rw 0 0\nproc /proc proc rw 0 0\nsysfs /sys sysfs rw 0 0\ntmpfs /tmp tmpfs rw 0 0\n/dev/sda1 /home ext4 rw 0 0\n", + ); + + assert!(mounts.contains(Path::new("/proc"))); + assert!(mounts.contains(Path::new("/sys"))); + assert!(mounts.contains(Path::new("/tmp"))); + assert!(!mounts.contains(Path::new("/home"))); + } + + #[cfg(target_os = "linux")] + #[tokio::test] + async fn proc_size_ignores_systemic_pseudo_files() { + init(); + + let mut it = SizeCalculator::new(Path::new("/proc")).await.unwrap(); + assert_eq!(it.next().await.unwrap(), Some(0)); + assert_eq!(it.next().await.unwrap(), None); + } +}