Skip to content

Commit 0484e07

Browse files
committed
refact(updater): read df in POSIX format instead of counting columns backwards
df -P prints one line per filesystem whatever the device name is called, which is what the counting from the end worked around. $4 says what $(NF-2) meant. Four readers: disk_usage (-Ph), check_free_space (-P -B1), check_boot_reserve (-Pk) and do-update-lxc.sh (-P -B1). init.sh and resources.sh keep --output=, which asks for a single column and cannot wrap either. The two that read a number now check that they got one. Without it a df that does not know -P would print its usage to stderr and leave the parse empty, and an empty string reads as zero free space in check_free_space, as a division by zero in check_boot_reserve and as a skipped guard in do-update-lxc.sh. They raise 100 naming the flag instead. That is the open question about this change: -P is POSIX and GNU has it, but the updater and the image factory are Alpine images with no coreutils, so their df is busybox's, and busybox's support for -P is not verified here. The installer factory already reads df -Pk (create_iso.sh:67) and is Debian-based, so it proves nothing for the other two. One command in the factory settles it: ./tool.sh shell -> df -Pk / The suite is at 57. Taking -P out of any of the four turns six cases red, and dropping either value check turns one more - the mock answers a df without -P the way a real one does, with the device name on a line of its own. Signed-off-by: Simon Walz <contact@simonwalz.de>
1 parent 9a6b2e0 commit 0484e07

3 files changed

Lines changed: 51 additions & 38 deletions

File tree

‎system/cuos/do-update-lxc.sh‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,12 @@ if [[ "${IMAGE_VERSION_STRING}" == "$(cat /etc/image)" ]]; then
3030
raise_info 22 "No new image version available. Exiting."
3131
fi
3232

33-
# Fields counted from the end: df puts a device name that does not fit on a
34-
# line of its own, and then the columns are one line further down.
35-
AVAIL_BYTES=$(df -B1 "/" | awk 'END { print $(NF-2) }')
33+
# -P: POSIX output, one line per filesystem. Without it df puts a device name
34+
# that does not fit on a line of its own and the columns move.
35+
AVAIL_BYTES=$(df -P -B1 "/" | awk 'NR == 2 { print $4 }')
36+
if [[ ! "${AVAIL_BYTES}" =~ ^[0-9]+$ ]]; then
37+
raise 21 "Could not read the free space of /: df -P -B1 gave \"${AVAIL_BYTES}\""
38+
fi
3639
REQUIRED_BYTES=$((2 * 1024 * 1024 * 1024))
3740
if [ "$AVAIL_BYTES" -le "$REQUIRED_BYTES" ]; then
3841
report_info "cuos:update:no_space" "Not enough free disk space available"

‎updater/perform_update.sh‎

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,11 @@ step() {
4242
echo "==> $*"
4343
}
4444

45-
# Fields counted from the end: df puts a device name that does not fit on a
46-
# line of its own.
45+
# -P: POSIX output, one line per filesystem. Without it df puts a device name
46+
# that does not fit on a line of its own and the columns move.
4747
disk_usage() {
48-
df -h "${1:-/}" \
49-
| awk 'END { printf "%s of %s used, %s free\n", $(NF-3), $(NF-4), $(NF-2) }'
48+
df -Ph "${1:-/}" \
49+
| awk 'NR == 2 { printf "%s of %s used, %s free\n", $3, $2, $4 }'
5050
}
5151

5252
report_disk_usage() {
@@ -101,7 +101,9 @@ check_environment() {
101101
check_free_space() {
102102
local avail_bytes required_bytes
103103

104-
avail_bytes="$(df -B1 "${TARGET_ROOT}" | awk 'END { print $(NF-2) }')"
104+
avail_bytes="$(df -P -B1 "${TARGET_ROOT}" | awk 'NR == 2 { print $4 }')"
105+
[[ "${avail_bytes}" =~ ^[0-9]+$ ]] \
106+
|| raise 100 "Could not read the free space of ${TARGET_ROOT}: df -P -B1 gave \"${avail_bytes}\""
105107
required_bytes="$((2 * 1024 * 1024 * 1024))"
106108

107109
if [[ "${avail_bytes}" -le "${required_bytes}" ]]; then
@@ -119,13 +121,14 @@ check_free_space() {
119121
# 40 and not 50 percent: the second slot's kernel and initrd are a future
120122
# build, and they will not be smaller than today's.
121123
#
122-
# df -k, not -B1: in the factory this is busybox's df, which has no GNU
123-
# block-size options. Fields from the end, for a wrapped device name.
124+
# df -Pk, not -B1: in the factory this is busybox's df, which has no GNU
125+
# block-size options. -P keeps the columns on one line.
124126
check_boot_reserve() {
125127
local size used percent
126128

127-
read -r size used <<<"$(df -k "${TARGET_BOOT}" \
128-
| awk 'END { print $(NF-4), $(NF-3) }')"
129+
read -r size used <<<"$(df -Pk "${TARGET_BOOT}" | awk 'NR == 2 { print $2, $3 }')"
130+
[[ "${size}" =~ ^[0-9]+$ && "${used}" =~ ^[0-9]+$ && "${size}" -gt 0 ]] \
131+
|| raise 100 "Could not read the usage of ${TARGET_BOOT}: df -Pk gave \"${size} ${used}\""
129132
percent="$(( used * 100 / size ))"
130133

131134
if (( percent > 40 )); then

‎updater/perform_update.test.sh‎

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -65,31 +65,37 @@ btrfs() {
6565
esac
6666
}
6767

68+
# POSIX output (df -P): one line per filesystem, however long the device name.
69+
# MOCK_DF_UNSUPPORTED stands for a df that does not know -P: usage to stderr,
70+
# nothing on stdout.
6871
df() {
69-
case "${1}" in
70-
-h)
71-
echo "Filesystem Size Used Available Use% Mounted on"
72-
echo "/dev/mapper/loop0p3 1.3G 733.8M 522.3M 58% ${2}"
72+
local path="${*: -1}"
73+
74+
if [[ -n "${MOCK_DF_UNSUPPORTED-}" ]]; then
75+
echo "df: unrecognized option: P" >&2
76+
return 1
77+
fi
78+
79+
case "$*" in
80+
*-Ph*)
81+
echo "Filesystem Size Used Avail Capacity Mounted on"
82+
echo "/dev/disk/by-uuid/1a2b3c4d-0000-1111-2222-334455667788 1.3G 733.8M 522.3M 58% ${path}"
7383
;;
74-
-k)
84+
*-Pk*)
7585
# The default is the boot partition of the x86_64 build of 2026-09-10:
7686
# 68.4 MiB of 252.7 MiB with one slot installed.
77-
echo "Filesystem 1K-blocks Used Available Use% Mounted on"
78-
if [[ -n "${MOCK_DF_WRAPS-}" ]]; then
79-
echo "/dev/disk/by-uuid/1a2b3c4d-0000-1111-2222-334455667788"
80-
echo " ${MOCK_SIZE_KB-258812} ${MOCK_USED_KB-70041} 188771 27% ${2}"
81-
else
82-
echo "/dev/mapper/loop0p2 ${MOCK_SIZE_KB-258812} ${MOCK_USED_KB-70041} 188771 27% ${2}"
83-
fi
87+
echo "Filesystem 1024-blocks Used Available Capacity Mounted on"
88+
echo "/dev/disk/by-uuid/1a2b3c4d-0000-1111-2222-334455667788 ${MOCK_SIZE_KB-258812} ${MOCK_USED_KB-70041} 188771 27% ${path}"
89+
;;
90+
*-P*)
91+
echo "Filesystem 1B-blocks Used Available Capacity Mounted on"
92+
echo "/dev/disk/by-uuid/1a2b3c4d-0000-1111-2222-334455667788 1446256640 769654784 ${MOCK_AVAIL_BYTES-5368709120} 53% ${path}"
8493
;;
8594
*)
95+
# Without -P the device name gets a line of its own and the columns move.
8696
echo "Filesystem 1B-blocks Used Available Use% Mounted on"
87-
if [[ -n "${MOCK_DF_WRAPS-}" ]]; then
88-
echo "/dev/disk/by-uuid/1a2b3c4d-0000-1111-2222-334455667788"
89-
echo " 1446256640 769654784 ${MOCK_AVAIL_BYTES-5368709120} 53% ${2}"
90-
else
91-
echo "/dev/mapper/loop0p3 1446256640 769654784 ${MOCK_AVAIL_BYTES-5368709120} 53% ${2}"
92-
fi
97+
echo "/dev/disk/by-uuid/1a2b3c4d-0000-1111-2222-334455667788"
98+
echo " 1446256640 769654784 ${MOCK_AVAIL_BYTES-5368709120} 53% ${path}"
9399
;;
94100
esac
95101
}
@@ -132,15 +138,15 @@ expect "product_name: neither" "CuOS" product_name
132138
export CONFIG_PATH="${WORK_DIR}/absent.json"
133139
expect "product_name: no config at all" "CuOS" product_name
134140

141+
# The mock's device name is longer than the column, which is what -P is for.
135142
expect_rc "check_free_space: 5 GB is enough" 0 rc_of check_free_space
136143
MOCK_AVAIL_BYTES="1073741824"
137144
expect_rc "check_free_space: 1 GB is not" 101 rc_of check_free_space
138145
MOCK_AVAIL_BYTES="5368709120"
139-
# df puts a device name too long for the column on a line of its own, which
140-
# moves the columns of the line that carries them.
141-
MOCK_DF_WRAPS=1
142-
expect_rc "check_free_space: wrapped df line" 0 rc_of check_free_space
143-
MOCK_DF_WRAPS=""
146+
MOCK_DF_UNSUPPORTED=1
147+
expect_rc "check_free_space: a df without -P is not guessed at" 100 \
148+
rc_of check_free_space
149+
MOCK_DF_UNSUPPORTED=""
144150

145151
export IMAGE="ghcr.io/cuos-dev/cuos-system:development"
146152
export T_FILE_IMAGE="${WORK_DIR}/image"
@@ -200,9 +206,10 @@ MOCK_USED_KB=41000
200206
expect_rc "check_boot_reserve: 41% leaves the second slot too little" 115 \
201207
rc_of check_boot_reserve
202208
unset MOCK_SIZE_KB MOCK_USED_KB
203-
MOCK_DF_WRAPS=1
204-
expect_rc "check_boot_reserve: wrapped df line" 0 rc_of check_boot_reserve
205-
MOCK_DF_WRAPS=""
209+
MOCK_DF_UNSUPPORTED=1
210+
expect_rc "check_boot_reserve: a df without -P is not guessed at" 100 \
211+
rc_of check_boot_reserve
212+
MOCK_DF_UNSUPPORTED=""
206213

207214
expect_rc "check_environment: everything mounted" 0 rc_of check_environment
208215
MOCK_UNMOUNTED="${TARGET_ROOT}"

0 commit comments

Comments
 (0)