Archilyzer · Source

archilyzer

Archilyzer
git clone https://archilyzer.pages.dev/source/archilyzer.git
Log | Files | Refs | README | LICENSE

commit 97e9d95d3f797e67447af025d08c39104081ce45
parent 9b614f0e25ec0d8f43389169ce3863762f7dc8af
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date:   Thu, 17 Sep 2026 14:04:32 -0400

storage: an unmeasurable root reports no free space, not Infinity

getFreeBytes fails open to Number.POSITIVE_INFINITY — right for the download
gate it was written for, wrong to hand onward as a number. Infinity does not
survive JSON, so it crosses the RSC boundary as `null` and every byte
formatter downstream gets a surprise. freeBytes is spread only when finite,
which is what the field being optional already means.

Forcing the fail-open needs a filesystem the unit test cannot conjure, so the
assertion is on the contract instead: a freeBytes that is present is finite.

mountByUuid takes the same ProbeOptions as probeLocation, so the by-uuid
directory is named in one place rather than two.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Diffstat:
Mcommon/lib/storageVolumes.test.ts | 6++++++
Mcommon/lib/storageVolumes.ts | 24++++++++++++++++--------
2 files changed, 22 insertions(+), 8 deletions(-)

diff --git a/common/lib/storageVolumes.test.ts b/common/lib/storageVolumes.test.ts @@ -120,6 +120,12 @@ test("available: the root is a directory, with identity and free bytes", async ( assert.equal(p.identity.known && p.identity.label, "PLATTER"); // root === join(mountpoint, relPath), the invariant a re-point relies on. assert.equal(p.identity.known && p.identity.relPath, "archilyzer-media"); + // NEVER Infinity. getFreeBytes fails open to it (statfs unsupported, a + // permissions error, ENOTDIR) and that value does not survive JSON — it + // reaches a client component as `null`. Forcing the fail-open needs a + // filesystem this test cannot conjure, so the assertion is on the contract: + // a freeBytes that is present is a finite number. + assert.ok(Number.isFinite(p.freeBytes)); assert.ok((p.freeBytes ?? 0) > 0); // In fstab and not an automount: nothing to warn about. assert.equal(p.warning, undefined); diff --git a/common/lib/storageVolumes.ts b/common/lib/storageVolumes.ts @@ -68,11 +68,12 @@ export const MOUNT_TIMEOUT_MS = 15_000; // difference between "unmounted" and "absent". export const BY_UUID_DIR = "/dev/disk/by-uuid"; -// Test seams ONLY. Production callers pass nothing and get the constants above; -// the unit tests shorten the findmnt timeout (so the "fake binary that sleeps" -// case does not cost the suite three seconds) and point `byUuidDir` at a tmp -// directory, which is the only way to exercise unmounted-vs-absent without -// plugging a disk in. +// Test seams ONLY, shared by probeLocation and mountByUuid so the by-uuid +// directory is named in one place. Production callers pass nothing and get the +// constants above; the unit tests shorten the findmnt timeout (so the "fake +// binary that sleeps" case does not cost the suite three seconds) and point +// `byUuidDir` at a tmp directory, which is the only way to exercise +// unmounted-vs-absent without plugging a disk in. export type ProbeOptions = { findmntTimeoutMs?: number; byUuidDir?: string; @@ -238,7 +239,13 @@ export async function probeLocation( if (isDir) { const identity = await identityOfPath(root, bins, timeoutMs); - const freeBytes = await getFreeBytes(root); + // getFreeBytes FAILS OPEN TO Infinity (statfs unsupported, a permissions + // error, ENOTDIR) — right for a download gate, wrong to carry as a number: + // Infinity does not survive JSON, so it reaches a client component as + // `null` and every byte formatter downstream gets a surprise. An + // unmeasurable root reports no free space at all, which is the honest + // answer and the one the field is already optional for. + const free = await getFreeBytes(root); // Two ways a mount will not be there after a reboot: udisks put it under // /run/media (or /media) because a human plugged it in, or there is no // fstab entry naming its UUID. The fstab call is skipped when the @@ -256,7 +263,7 @@ export async function probeLocation( status: "available", identity, ...(warning ? { warning } : {}), - freeBytes, + ...(Number.isFinite(free) ? { freeBytes: free } : {}), }; } @@ -332,6 +339,7 @@ export type MountResult = { export async function mountByUuid( uuid: string, bins: VolumeBins, + opts: ProbeOptions = {}, ): Promise<MountResult> { if (!(await udisksctlAvailable(bins))) { return { @@ -341,7 +349,7 @@ export async function mountByUuid( } const res = await run( bins.udisksctlBin, - ["mount", "-b", path.join(BY_UUID_DIR, uuid)], + ["mount", "-b", path.join(opts.byUuidDir ?? BY_UUID_DIR, uuid)], MOUNT_TIMEOUT_MS, ); if (!res.ok) {