Skip to content

Commit 8e3e911

Browse files
committed
feat(updater): refuse an image whose boot partition cannot hold two slots
The factory installs one slot. The first update on the device puts a second one beside it, on a boot partition that never grows - it is sized once in create_image.sh, and no update can repartition the disk it is running from. So a boot partition that is more than half full when the image is written is an image that cannot be updated, and nothing said so until a cp failed on a device with error 112. check_boot_reserve runs at the end of an INSTALLIMAGE run, after everything the factory writes to /boot, and raises 115 above 40%. Not 50: the second slot's kernel and initrd are a future build and will not be smaller than today's. Measured on the x86_64 build of 2026-09-10: 68.4 MiB of 252.7 MiB, 27%. The arm boot partitions carry a firmware directory as well and are not measured - the "disc usage (boot, after install)" line of the next rpi build says whether 40 is the right number there. It reads df -k, not -B1: this one runs in the factory, where df is busybox's and has no GNU block-size options. Every other df reader in the tree runs on a device only, which is why -B1 has worked so far. Four cases in perform_update.test.sh, boundary included. Raising the threshold or switching to -B1 turns them red - checked. Not run in a real build. Signed-off-by: Simon Walz <contact@simonwalz.de>
1 parent d8ba49d commit 8e3e911

2 files changed

Lines changed: 66 additions & 12 deletions

File tree

‎updater/perform_update.sh‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,32 @@ check_free_space() {
100100
fi
101101
}
102102

103+
# The factory installs one slot, and the first update puts a second one beside
104+
# it on a boot partition that never grows: it is sized once, in
105+
# image-factory/create_image.sh, and no update can repartition the disk it
106+
# runs from. So the moment to find out that two slots do not fit is here, in
107+
# the factory, where the layout can still be changed - on a device it is a
108+
# kernel that cannot be installed any more.
109+
#
110+
# 40 and not 50 percent: the second slot's kernel and initrd are a future
111+
# build, and they will not be smaller than today's.
112+
#
113+
# df -k, not -B1: in the factory this is busybox's df, which has no GNU
114+
# block-size options. Fields from the end, for a wrapped device name.
115+
check_boot_reserve() {
116+
local size used percent
117+
118+
read -r size used <<<"$(df -k "${TARGET_BOOT}" \
119+
| awk 'END { print $(NF-4), $(NF-3) }')"
120+
percent="$(( used * 100 / size ))"
121+
122+
if (( percent > 40 )); then
123+
raise 115 "${TARGET_BOOT} is ${percent}% full with one slot installed, so
124+
the second slot will not fit. Give the boot partition more room in
125+
image-factory/create_image.sh."
126+
fi
127+
}
128+
103129
target_platform() {
104130
local arch="${1:-}"
105131
local platform=""
@@ -331,6 +357,8 @@ main() {
331357

332358
if [[ "${INSTALLIMAGE}" = "true" ]]; then
333359
copy_system_json
360+
361+
check_boot_reserve
334362
fi
335363

336364
exit 0

‎updater/perform_update.test.sh‎

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -66,18 +66,32 @@ btrfs() {
6666
}
6767

6868
df() {
69-
if [[ "${1}" = "-h" ]]; then
70-
echo "Filesystem Size Used Available Use% Mounted on"
71-
echo "/dev/mapper/loop0p3 1.3G 733.8M 522.3M 58% ${2}"
72-
return
73-
fi
74-
echo "Filesystem 1B-blocks Used Available Use% Mounted on"
75-
if [[ -n "${MOCK_DF_WRAPS-}" ]]; then
76-
echo "/dev/disk/by-uuid/1a2b3c4d-0000-1111-2222-334455667788"
77-
echo " 1446256640 769654784 ${MOCK_AVAIL_BYTES-5368709120} 53% ${2}"
78-
return
79-
fi
80-
echo "/dev/mapper/loop0p3 1446256640 769654784 ${MOCK_AVAIL_BYTES-5368709120} 53% ${2}"
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}"
73+
;;
74+
-k)
75+
# The default is the boot partition of the x86_64 build of 2026-09-10:
76+
# 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
84+
;;
85+
*)
86+
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
93+
;;
94+
esac
8195
}
8296

8397
# shellcheck source=/dev/null
@@ -178,6 +192,18 @@ expect "pull_image: a local build is not" "0" pulls_of
178192
export INSTALLIMAGE=""
179193
export IMAGE="ghcr.io/cuos-dev/cuos-system:development"
180194

195+
expect_rc "check_boot_reserve: one slot at 27%" 0 rc_of check_boot_reserve
196+
MOCK_SIZE_KB=100000
197+
MOCK_USED_KB=40000
198+
expect_rc "check_boot_reserve: 40% is still fine" 0 rc_of check_boot_reserve
199+
MOCK_USED_KB=41000
200+
expect_rc "check_boot_reserve: 41% leaves the second slot too little" 115 \
201+
rc_of check_boot_reserve
202+
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=""
206+
181207
expect_rc "check_environment: everything mounted" 0 rc_of check_environment
182208
MOCK_UNMOUNTED="${TARGET_ROOT}"
183209
expect_rc "check_environment: no root filesystem" 100 rc_of check_environment

0 commit comments

Comments
 (0)