From e4d10a09cd6a617459e1f657f63982e84289f2fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Wed, 19 Feb 2025 12:16:33 +0100 Subject: [PATCH] Make swap size configurable Allow configuration of the swap size via /etc/default/haos-swapfile file. By setting the SWAPSIZE variable in this file, swapfile get recreated on the next reboot to the defined size. Size can be either in bytes or with optional units (B/K/M/G, accepting some variations but always interpreted as power of 10). The size is then rounded to 4k block size. If no override is defined or the value can't be parsed, it falls back to previously used 33% of system RAM. Fixes #968 --- .../rootfs-overlay/etc/default/.empty | 0 .../usr/lib/systemd/system/etc-default.mount | 13 ++++ .../lib/systemd/system/haos-swapfile.service | 4 +- .../lib/systemd/system/mnt-data-swapfile.swap | 1 + .../rootfs-overlay/usr/libexec/haos-swapfile | 61 +++++++++++++++---- tests/smoke_test/test_basic.py | 25 ++++++++ 6 files changed, 89 insertions(+), 15 deletions(-) create mode 100644 buildroot-external/rootfs-overlay/etc/default/.empty create mode 100644 buildroot-external/rootfs-overlay/usr/lib/systemd/system/etc-default.mount diff --git a/buildroot-external/rootfs-overlay/etc/default/.empty b/buildroot-external/rootfs-overlay/etc/default/.empty new file mode 100644 index 00000000000..e69de29bb2d diff --git a/buildroot-external/rootfs-overlay/usr/lib/systemd/system/etc-default.mount b/buildroot-external/rootfs-overlay/usr/lib/systemd/system/etc-default.mount new file mode 100644 index 00000000000..8130c7f4792 --- /dev/null +++ b/buildroot-external/rootfs-overlay/usr/lib/systemd/system/etc-default.mount @@ -0,0 +1,13 @@ +[Unit] +Description=Persistent /etc/default directory +Requires=mnt-overlay.mount +After=mnt-overlay.mount + +[Mount] +What=/mnt/overlay/etc/default +Where=/etc/default +Type=None +Options=bind + +[Install] +WantedBy=hassos-bind.target diff --git a/buildroot-external/rootfs-overlay/usr/lib/systemd/system/haos-swapfile.service b/buildroot-external/rootfs-overlay/usr/lib/systemd/system/haos-swapfile.service index 872438487ef..263286329aa 100644 --- a/buildroot-external/rootfs-overlay/usr/lib/systemd/system/haos-swapfile.service +++ b/buildroot-external/rootfs-overlay/usr/lib/systemd/system/haos-swapfile.service @@ -1,8 +1,8 @@ [Unit] Description=HAOS swap DefaultDependencies=no -Requires=mnt-data.mount -After=mnt-data.mount systemd-growfs@mnt-data.service +Requires=etc-default.mount mnt-data.mount +After=etc-default.mount mnt-data.mount systemd-growfs@mnt-data.service Before=mnt-data-swapfile.swap [Service] diff --git a/buildroot-external/rootfs-overlay/usr/lib/systemd/system/mnt-data-swapfile.swap b/buildroot-external/rootfs-overlay/usr/lib/systemd/system/mnt-data-swapfile.swap index e7eca95dbb3..c6fb697baff 100644 --- a/buildroot-external/rootfs-overlay/usr/lib/systemd/system/mnt-data-swapfile.swap +++ b/buildroot-external/rootfs-overlay/usr/lib/systemd/system/mnt-data-swapfile.swap @@ -1,5 +1,6 @@ [Unit] Description=HAOS swap file +ConditionFileNotEmpty=/mnt/data/swapfile [Swap] What=/mnt/data/swapfile diff --git a/buildroot-external/rootfs-overlay/usr/libexec/haos-swapfile b/buildroot-external/rootfs-overlay/usr/libexec/haos-swapfile index 46080ff7674..e450392a84f 100755 --- a/buildroot-external/rootfs-overlay/usr/libexec/haos-swapfile +++ b/buildroot-external/rootfs-overlay/usr/libexec/haos-swapfile @@ -1,24 +1,59 @@ #!/bin/sh set -e -swapfile="/mnt/data/swapfile" +size2kilobytes() { + bytes="$(echo "$1" | awk \ + 'BEGIN{IGNORECASE = 1} + function tobytes(n,b,p) {printf "%u\n", n*b^p/1024} + /[0-9]B?$/{tobytes($1, 1, 0); next}; + /K(i?B)?$/{tobytes($1, 2, 10); next}; + /M(i?B)?$/{tobytes($1, 2, 20); next}; + /G(i?B)?$/{tobytes($1, 2, 30); next}; + {print -1}')" + echo "$bytes" +} + +if [ -f /etc/default/haos-swapfile ]; then + # shellcheck disable=SC1091 + . /etc/default/haos-swapfile +fi +SWAPFILE="/mnt/data/swapfile" + +# Swap size in kilobytes (as it's also what meminfo shows) +SWAPSIZE="$(size2kilobytes "${SWAPSIZE}")" + +if [ -z "${SWAPSIZE}" ] || [ "${SWAPSIZE}" = "-1" ]; then + # Default to 33% of total memory + SWAPSIZE="$(awk '/MemTotal/{ print int($2 * 0.33) }' /proc/meminfo)" + echo "[INFO] Using default swapsize of 33% RAM (${SWAPSIZE} kB)" +fi + # Swap space in 4k blocks -swapsize="$(awk '/MemTotal/{ print int($2 * 0.33 / 4) }' /proc/meminfo)" +SWAPSIZE_BLOCKS=$((SWAPSIZE / 4)) +if [ "${SWAPSIZE_BLOCKS}" -lt 10 ]; then + echo "[INFO] Requested swap size smaller than 40kB, disabling swap" -if [ ! -s "${swapfile}" ] || [ "$(stat "${swapfile}" -c '%s')" -lt $((swapsize * 4096)) ]; then - # Check free space (in 4k blocks) - if [ "$(stat -f /mnt/data -c '%f')" -lt "${swapsize}" ]; then - echo "[WARNING] Not enough space to allocate swapfile" - exit 1 - fi + if [ -f "${SWAPFILE}" ]; then + echo "[INFO] Removing existing swapfile" + rm -f "${SWAPFILE}" + fi - echo "[INFO] Creating swapfile of size $((swapsize *4))k" - umask 0077 - dd if=/dev/zero of="${swapfile}" bs=4k count="${swapsize}" + exit 0 fi -if ! swaplabel "${swapfile}" > /dev/null 2>&1; then - /usr/lib/systemd/systemd-makefs swap "${swapfile}" +if [ ! -s "${SWAPFILE}" ] || [ "$(stat "${SWAPFILE}" -c '%s')" -ne $((SWAPSIZE_BLOCKS * 4096)) ]; then + # Check free space (in 4k blocks) + if [ "$(stat -f /mnt/data -c '%f')" -lt "${SWAPSIZE_BLOCKS}" ]; then + echo "[ERROR] Not enough space to allocate swapfile" + exit 1 + fi + + echo "[INFO] Creating swapfile of size ${SWAPSIZE} kB (rounded to ${SWAPSIZE_BLOCKS} blocks)" + umask 0077 + dd if=/dev/zero of="${SWAPFILE}" bs=4k count="${SWAPSIZE_BLOCKS}" fi +if ! swaplabel "${SWAPFILE}" > /dev/null 2>&1; then + /usr/lib/systemd/systemd-makefs swap "${SWAPFILE}" +fi diff --git a/tests/smoke_test/test_basic.py b/tests/smoke_test/test_basic.py index eb6d42b4566..9ae86b10499 100644 --- a/tests/smoke_test/test_basic.py +++ b/tests/smoke_test/test_basic.py @@ -78,6 +78,31 @@ def test_systemctl_check_no_failed(shell): assert "0 loaded units listed." in output, f"Some units failed:\n{"\n".join(output)}" +@pytest.mark.dependency(depends=["test_init"]) +def test_custom_swap_size(shell, target): + output = shell.run_check("stat -c '%s' /mnt/data/swapfile") + # set new swap size to half of the previous size - round to 4k blocks + new_swap_size = (int(output[0]) // 2 // 4096) * 4096 + shell.run_check(f"echo 'SWAPSIZE={new_swap_size/1024/1024}M' > /etc/default/haos-swapfile; reboot") + # reactivate ShellDriver to handle login again + target.deactivate(shell) + target.activate(shell) + output = shell.run_check("stat -c '%s' /mnt/data/swapfile") + assert int(output[0]) == new_swap_size, f"Incorrect swap size {new_swap_size}B: {output}" + + +@pytest.mark.dependency(depends=["test_custom_swap_size"]) +def test_no_swap(shell, target): + output = shell.run_check("echo 'SWAPSIZE=0' > /etc/default/haos-swapfile; reboot") + # reactivate ShellDriver to handle login again + target.deactivate(shell) + target.activate(shell) + output = shell.run_check("systemctl --no-pager -l list-units --state=failed") + assert "0 loaded units listed." in output, f"Some units failed:\n{"\n".join(output)}" + swapon = shell.run_check("swapon --show") + assert swapon == [], f"Swapfile still exists: {swapon}" + + @pytest.mark.dependency(depends=["test_init"]) def test_kernel_not_tainted(shell): """Check if the kernel is not tainted - do it at the end of the