Skip systemic mounts during local size calculation

Agent-Logs-Url: https://github.com/sxyazi/yazi/sessions/89d44d10-de98-42c8-bc7b-dc68c41459bb

Co-authored-by: sxyazi <17523360+sxyazi@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-03-31 13:56:19 +00:00 committed by GitHub
parent 62862d2fc4
commit 9711d6aa25
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -3,13 +3,19 @@ use std::{collections::VecDeque, future::poll_fn, io, mem, path::{Path, PathBuf}
use either::Either; use either::Either;
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
#[cfg(target_os = "linux")]
use crate::mounts::Partition;
use crate::cha::Cha; use crate::cha::Cha;
type Task = Either<PathBuf, std::fs::ReadDir>; type Task = Either<PathBuf, std::fs::ReadDir>;
#[cfg(target_os = "linux")]
type SystemicMounts = std::collections::HashSet<PathBuf>;
#[cfg(not(target_os = "linux"))]
type SystemicMounts = ();
pub enum SizeCalculator { pub enum SizeCalculator {
Idle((VecDeque<Task>, Option<u64>), Cha), Idle((VecDeque<Task>, Option<u64>, SystemicMounts), Cha),
Pending(JoinHandle<(VecDeque<Task>, Option<u64>)>, Cha), Pending(JoinHandle<(VecDeque<Task>, Option<u64>, SystemicMounts)>, Cha),
} }
impl SizeCalculator { impl SizeCalculator {
@ -18,12 +24,17 @@ impl SizeCalculator {
tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || {
let cha = Cha::new(p.file_name().unwrap_or_default(), std::fs::symlink_metadata(&p)?); let cha = Cha::new(p.file_name().unwrap_or_default(), std::fs::symlink_metadata(&p)?);
if !cha.is_dir() { 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 mut buf = VecDeque::from([Either::Right(std::fs::read_dir(&p)?)]);
let size = Self::next_chunk(&mut buf); let size = Self::next_chunk(&mut buf, &systemic);
Ok(Self::Idle((buf, size), cha)) Ok(Self::Idle((buf, size, systemic), cha))
}) })
.await? .await?
} }
@ -47,7 +58,7 @@ impl SizeCalculator {
poll_fn(|cx| { poll_fn(|cx| {
loop { loop {
match self { match self {
Self::Idle((buf, size), cha) => { Self::Idle((buf, size, systemic), cha) => {
if let Some(s) = size.take() { if let Some(s) = size.take() {
return Poll::Ready(Ok(Some(s))); return Poll::Ready(Ok(Some(s)));
} else if buf.is_empty() { } else if buf.is_empty() {
@ -55,10 +66,11 @@ impl SizeCalculator {
} }
let mut buf = mem::take(buf); let mut buf = mem::take(buf);
let systemic = mem::take(systemic);
*self = Self::Pending( *self = Self::Pending(
tokio::task::spawn_blocking(move || { tokio::task::spawn_blocking(move || {
let size = Self::next_chunk(&mut buf); let size = Self::next_chunk(&mut buf, &systemic);
(buf, size) (buf, size, systemic)
}), }),
*cha, *cha,
); );
@ -72,7 +84,10 @@ impl SizeCalculator {
.await .await
} }
fn next_chunk(buf: &mut VecDeque<Either<PathBuf, std::fs::ReadDir>>) -> Option<u64> { fn next_chunk(
buf: &mut VecDeque<Either<PathBuf, std::fs::ReadDir>>,
systemic: &SystemicMounts,
) -> Option<u64> {
let (mut i, mut size, now) = (0, 0, Instant::now()); let (mut i, mut size, now) = (0, 0, Instant::now());
macro_rules! pop_and_continue { macro_rules! pop_and_continue {
() => {{ () => {{
@ -102,7 +117,10 @@ impl SizeCalculator {
let Ok(ent) = next else { continue }; let Ok(ent) = next else { continue };
let Ok(ft) = ent.file_type() else { continue }; let Ok(ft) = ent.file_type() else { continue };
if ft.is_dir() { 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() { } else if let Ok(meta) = ent.metadata() {
size += meta.len(); size += meta.len();
} }
@ -110,3 +128,86 @@ impl SizeCalculator {
Some(size) 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);
}
}