mirror of
https://github.com/nestriness/nestri.git
synced 2026-09-19 17:25:19 +03:00
fix(nesdoctor): report storage properly, and stop being blind on Windows
A submission from a team machine with four drives and 22 TiB reported
`disk=8880`, and the field was not wrong so much as meaningless: it was the
free space on the single largest mount, with no capacity anywhere and no total.
A content store is sized against capacity.
Storage now reports four things, because they answer different questions and
one number could not:
diskfree total free across every real filesystem
disksize total capacity
diskmax the largest single filesystem, which is the real ceiling for any
one store -- a dataset cannot be spread across drives
disks how many there are
The ambiguous `disk` key is gone rather than silently redefined, so old rows
stay readable as what they were. `Get-PSDrive` reports Free *and* Used and we
were reading only Free, hence no capacity on Windows at all.
Pseudo-filesystems are now excluded by *type* rather than by mount path. Path
filtering missed `/tmp` on a tmpfs, whose free space is RAM -- so 7 GiB of
memory was being added to a storage total, which is exactly the sort of number
a capacity plan gets built on.
## The real finding, which was not about disks
"We are working blind on Windows" is correct, and both Windows bugs this tool
has had prove it: a virtual display adapter reported as the GPU, and a URL
truncated at its first `&`. Both were in code that only runs on Windows, both
were found by a person reading the results channel, and neither could have been
found here -- the development machine is Linux and `xdg-open` never sees a
shell.
Two things about that, and the first is the one that generalises.
`OPENERS` is now a const with a test asserting the property that actually
matters: **never hand a URL to anything that will re-parse it.** No `cmd`, no
`sh`, no `powershell`, no `start` builtin, and no argument that looks like it
wants the URL interpolated into it. Unlike the bug, that is checkable on every
platform in a millisecond. Verified by reintroducing `cmd /C start "" <url>`
and confirming the test fails with the right message, then reverting.
And CI already runs a real Windows machine and a real macOS one -- we simply
were not looking at them. Each smoke-tested target now prints its full report
and JSON into a collapsed log group. Deliberately not `set -e`: this step is
for looking, and a probe that misbehaves on a runner must not fail a release.
It turns "working blind" into "looking at it once per release", which would
have shown the Parsec adapter problem the first time a Windows binary was ever
built.
Version to 0.2.2.
This commit is contained in:
@@ -63,6 +63,14 @@ pub struct Disk {
|
||||
/// *separate devices*) cannot be answered at all.
|
||||
pub source: Option<String>,
|
||||
pub free_gib: f64,
|
||||
/// Total capacity, not just what is free.
|
||||
///
|
||||
/// Added after a submission from a machine with four drives and 22 TiB
|
||||
/// reported `disk=8880` -- the free space on the single largest mount. A
|
||||
/// content store is sized against capacity, and reporting only the largest
|
||||
/// mount's free space understates a multi-drive machine by however many
|
||||
/// drives it has.
|
||||
pub size_gib: Option<f64>,
|
||||
}
|
||||
|
||||
pub fn probe() -> SysInfo {
|
||||
@@ -348,38 +356,75 @@ fn disks() -> Vec<Disk> {
|
||||
let Ok(avail_kb) = f[3].parse::<f64>() else {
|
||||
continue;
|
||||
};
|
||||
let size_kb = f[1].parse::<f64>().ok();
|
||||
let source = f[0].to_string();
|
||||
let mount = f[5..].join(" ");
|
||||
// Pseudo-filesystems are noise, and tmpfs free space is RAM.
|
||||
if ["/dev", "/sys", "/proc", "/run", "/boot", "/snap"]
|
||||
.iter()
|
||||
.any(|p| mount.starts_with(p))
|
||||
let fs = fs_type(&mount);
|
||||
|
||||
// Filter by filesystem type, not by mount path. Filtering paths
|
||||
// missed `/tmp` on a tmpfs, whose "free space" is RAM -- so a
|
||||
// 7 GiB tmpfs was being added to a storage total, which is exactly
|
||||
// the sort of number a capacity plan would then be built on.
|
||||
const PSEUDO: [&str; 9] = [
|
||||
"tmpfs",
|
||||
"ramfs",
|
||||
"devtmpfs",
|
||||
"devfs",
|
||||
"squashfs",
|
||||
"overlay",
|
||||
"efivarfs",
|
||||
"fuse.portal",
|
||||
"iso9660",
|
||||
];
|
||||
if fs.as_deref().is_some_and(|f| PSEUDO.contains(&f)) {
|
||||
continue;
|
||||
}
|
||||
// Paths still worth skipping regardless of what they are mounted as.
|
||||
if [
|
||||
"/dev",
|
||||
"/sys",
|
||||
"/proc",
|
||||
"/run",
|
||||
"/boot",
|
||||
"/snap",
|
||||
"/var/lib/docker",
|
||||
]
|
||||
.iter()
|
||||
.any(|p| mount.starts_with(p))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
out.push(Disk {
|
||||
fs: fs_type(&mount),
|
||||
fs,
|
||||
mount,
|
||||
source: Some(source),
|
||||
free_gib: avail_kb / 1048576.0,
|
||||
size_gib: size_kb.map(|k| k / 1048576.0),
|
||||
});
|
||||
}
|
||||
}
|
||||
#[cfg(windows)]
|
||||
if let Some(txt) =
|
||||
ps("Get-PSDrive -PSProvider FileSystem | ForEach-Object { \"$($_.Name)|$($_.Free)\" }")
|
||||
{
|
||||
// Free *and* Used, so capacity is Free + Used. `Get-PSDrive` reports both
|
||||
// and we were reading only Free.
|
||||
if let Some(txt) = ps(
|
||||
r#"Get-PSDrive -PSProvider FileSystem | ForEach-Object { "$($_.Name)|$($_.Free)|$($_.Used)" }"#,
|
||||
) {
|
||||
for line in txt.lines() {
|
||||
if let Some((name, free)) = line.split_once('|') {
|
||||
if let Ok(b) = free.trim().parse::<f64>() {
|
||||
out.push(Disk {
|
||||
mount: format!("{}:", name.trim()),
|
||||
fs: None,
|
||||
source: None,
|
||||
free_gib: b / 1073741824.0,
|
||||
});
|
||||
}
|
||||
let f: Vec<&str> = line.split('|').collect();
|
||||
if f.len() < 2 {
|
||||
continue;
|
||||
}
|
||||
let Ok(free) = f[1].trim().parse::<f64>() else {
|
||||
continue;
|
||||
};
|
||||
let used = f.get(2).and_then(|u| u.trim().parse::<f64>().ok());
|
||||
out.push(Disk {
|
||||
mount: format!("{}:", f[0].trim()),
|
||||
fs: None,
|
||||
source: None,
|
||||
free_gib: free / 1073741824.0,
|
||||
size_gib: used.map(|u| (free + u) / 1073741824.0),
|
||||
});
|
||||
}
|
||||
}
|
||||
out.sort_by(|a, b| b.free_gib.total_cmp(&a.free_gib));
|
||||
|
||||
Reference in New Issue
Block a user