#!/usr/bin/env bash # Storage has to be honest about three things, and each of them is a way the # page could quietly lie: # # 1. btrfs subvolumes mounted at / and /home are ONE filesystem sharing one # pool of free space. df reports them as two, and a page that copies df # shows the user twice the free space they have. # 2. zram is a block device and is not storage. Counting it as a drive # overstates how much disk this machine has by 8 GB. # 3. unmount and eject must refuse anything that is not removable. The UI is # the thing asking, and a UI can be wrong. # # The expensive folder scan is NOT exercised here: measuring a terabyte-scale # library is not something a test should do on the machine someone is using. set -uo pipefail repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" helper="$repo_dir/config/dot/quickshell/scripts/panama-disks" service="$repo_dir/config/dot/quickshell/services/Disks.qml" page="$repo_dir/config/dot/quickshell/modules/settings/StoragePage.qml" fail() { printf 'disks contract: %s\n' "$1" >&2 exit 1 } [[ -x "$helper" ]] || fail 'panama-disks is missing or not executable' [[ -r "$service" ]] || fail 'the Disks service is missing' [[ -r "$page" ]] || fail 'StoragePage is missing' # ── The service reports failure rather than inventing data ─────────────────── grep -q 'pragma Singleton' "$service" || fail 'Disks is not a singleton' grep -q 'property string lastError' "$service" || fail 'Disks cannot report a failure' grep -q 'function refresh(): void' "$service" || fail 'Disks has no refresh' grep -q 'function scan(): void' "$service" || fail 'Disks has no folder scan' grep -qE 'command\s*:\s*"' "$service" && fail 'Process command must be an argument array' # The expensive read must not run on open; that is the entire reason it is a # separate command. grep -q 'Component.onCompleted: Disks.refresh()' "$page" \ || fail 'the page does not read the cheap snapshot when it opens' grep -q 'Component.onCompleted: Disks.scan()' "$page" \ && fail 'the page starts the expensive folder walk on open' # Partitioning stays out, deliberately and visibly. grep -qiE 'mkfs|sfdisk|parted|wipefs|dd if=' "$helper" \ && fail 'the helper can partition or format, which this page deliberately does not do' # ── Snapshot shape ─────────────────────────────────────────────────────────── snapshot="$("$helper" snapshot)" || fail 'snapshot failed' command -v jq >/dev/null 2>&1 || { printf 'disks contract: SKIP (no jq)\n'; exit 0; } jq -e '(.drives | type == "array") and (.filesystems | type == "array") and (.swap | type == "array")' \ <<<"$snapshot" >/dev/null || fail 'snapshot is missing one of drives, filesystems, or swap' # zram is swap, never a drive. jq -e '[.drives[].name | startswith("zram")] | any | not' <<<"$snapshot" >/dev/null \ || fail 'zram is reported as a drive, which overstates how much disk this machine has' jq -e '[.swap[].kind] | index("zram") != null or (. | length == 0)' <<<"$snapshot" >/dev/null \ || fail 'zram is not reported as swap' # One entry per DEVICE, not per mount point. Two entries for one device would # be the doubled-free-space bug. duplicates="$(jq -r '[.filesystems[].device] | group_by(.) | map(select(length > 1)) | flatten | join(", ")' <<<"$snapshot")" [[ -z "$duplicates" ]] \ || fail "these devices appear more than once, so their free space is counted twice: $duplicates" # Every filesystem has to carry the numbers the page draws. jq -e '[.filesystems[] | (.sizeBytes > 0) and (.mountpoints | length > 0)] | all' <<<"$snapshot" >/dev/null \ || fail 'a filesystem is missing its size or its mount points' # A shared filesystem lists "/" first, because that is what it is called. jq -e '[.filesystems[] | select(.mountpoints | index("/")) | .mountpoints[0] == "/"] | all' \ <<<"$snapshot" >/dev/null || fail 'the root filesystem does not lead with "/"' # ── Refusals, against a recorded tree ──────────────────────────────────────── # Never against real hardware. An earlier version of this test ran unmount on # whatever device happened to hold the root filesystem and passed only because # the kernel refused -- which tests the kernel, not the helper, and would have # unmounted /home the moment nothing held it open. work="$(mktemp -d /tmp/panama-disks.XXXXXX)" trap 'rm -rf "$work"' EXIT cat >"$work/tree.json" <<'FIXTURE' {"blockdevices":[ {"name":"nvme0n1","path":"/dev/nvme0n1","size":2000398934016,"type":"disk","fstype":null, "mountpoints":[],"model":"Fixed Disk","serial":"FIXED1","rm":false,"hotplug":false,"rota":false, "fssize":null,"fsused":null,"fsavail":null, "children":[{"name":"nvme0n1p1","path":"/dev/nvme0n1p1","size":2000398934016,"type":"part", "fstype":"btrfs","mountpoints":["/"],"model":null,"serial":null,"rm":false,"hotplug":false, "rota":false,"fssize":2000398934016,"fsused":1000000000000,"fsavail":1000398934016}]}, {"name":"sdb","path":"/dev/sdb","size":32000000000,"type":"disk","fstype":null, "mountpoints":[],"model":"USB Stick","serial":"USB1","rm":true,"hotplug":true,"rota":false, "fssize":null,"fsused":null,"fsavail":null, "children":[{"name":"sdb1","path":"/dev/sdb1","size":32000000000,"type":"part", "fstype":"vfat","mountpoints":["/run/media/user/USB"],"model":null,"serial":null, "rm":true,"hotplug":true,"rota":false,"fssize":32000000000,"fsused":1000000000, "fsavail":31000000000}]} ]} FIXTURE # A fake udisksctl, so a refusal that should never reach the system is visible # as an empty log rather than inferred from an exit code. mkdir -p "$work/bin" cat >"$work/bin/udisksctl" <<'STUB' #!/usr/bin/env bash printf '%s\n' "$*" >>"$PANAMA_DISKS_CALL_LOG" STUB chmod +x "$work/bin/udisksctl" export PANAMA_DISKS_LSBLK="$work/tree.json" export PANAMA_DISKS_CALL_LOG="$work/calls" export PATH="$work/bin:$PATH" : >"$PANAMA_DISKS_CALL_LOG" # The root filesystem is not removable, and no argument may make it so. "$helper" unmount /dev/nvme0n1p1 >/dev/null 2>&1 \ && fail 'unmount accepted the root filesystem' "$helper" eject /dev/nvme0n1 >/dev/null 2>&1 \ && fail 'eject accepted a fixed drive' [[ ! -s "$PANAMA_DISKS_CALL_LOG" ]] \ || fail "a refused device still reached udisksctl: $(cat "$PANAMA_DISKS_CALL_LOG")" "$helper" unmount 'not-a-device' >/dev/null 2>&1 \ && fail 'unmount accepted something that is not a device path' "$helper" unmount '/dev/../etc/passwd' >/dev/null 2>&1 \ && fail 'unmount accepted a path escaping /dev' "$helper" unmount '/dev/sdb99' >/dev/null 2>&1 \ && fail 'unmount accepted a device that does not exist' [[ ! -s "$PANAMA_DISKS_CALL_LOG" ]] \ || fail "a rejected path still reached udisksctl: $(cat "$PANAMA_DISKS_CALL_LOG")" # The removable one is allowed, and reaches udisksctl with separate arguments. "$helper" unmount /dev/sdb1 >/dev/null 2>&1 \ || fail 'unmount refused a removable partition' grep -Fxq 'unmount -b /dev/sdb1' "$PANAMA_DISKS_CALL_LOG" \ || fail "unmount did not reach udisksctl correctly: $(cat "$PANAMA_DISKS_CALL_LOG")" "$helper" bogus-command >/dev/null 2>&1 \ && fail 'an unknown command was accepted' unset PANAMA_DISKS_LSBLK PANAMA_DISKS_CALL_LOG printf 'disks contract: PASS (%d drives, %d filesystems)\n' \ "$(jq '.drives | length' <<<"$snapshot")" \ "$(jq '.filesystems | length' <<<"$snapshot")"