diff --git a/board/aarch64/raspberrypi-rpi64/rootfs/usr/share/product/raspberrypi,4-compute-module/interface-quirks.json b/board/aarch64/raspberrypi-rpi64/rootfs/usr/share/product/raspberrypi,4-compute-module/interface-quirks.json deleted file mode 100644 index fa1746169..000000000 --- a/board/aarch64/raspberrypi-rpi64/rootfs/usr/share/product/raspberrypi,4-compute-module/interface-quirks.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "eth0": { - "phy-detached-when-down": true - }, - "eth1": { - "phy-detached-when-down": true - } -} diff --git a/board/common/post-build.sh b/board/common/post-build.sh index eef3e9492..0135bc099 100755 --- a/board/common/post-build.sh +++ b/board/common/post-build.sh @@ -146,3 +146,8 @@ mkuserguide() if [ "$BR2_PACKAGE_WEBUI" = "y" ]; then mkuserguide fi + +# Drop dangling Finit enabled/*.conf symlinks, e.g., optional services +# not part of this image, they cause noise at every initctl reload. +# NOTE: must be the last step before creating the image! +find "$TARGET_DIR/etc/finit.d/enabled" -xtype l -delete 2>/dev/null diff --git a/board/common/rootfs/usr/libexec/infix/has-quirk b/board/common/rootfs/usr/libexec/infix/has-quirk index 7dc24d91d..d5225534e 100755 --- a/board/common/rootfs/usr/libexec/infix/has-quirk +++ b/board/common/rootfs/usr/libexec/infix/has-quirk @@ -1,4 +1,4 @@ -#!/bin/bash +#!/bin/sh IFQUIRKSFILE=${IFQUIRKSFILE:-/etc/product/interface-quirks.json} @@ -26,10 +26,18 @@ ethtoolmatch() { local pattern="${1#@ethtool:}" - grep -qFxvf \ - <(ethtool -i "$ifname") \ - <(echo -n "$pattern" | awk -v FS="=" -v RS=";" '{ printf("%s: %s\n", $1, $2); }') \ - && return + # All key=val pairs of the pattern must match a "key: val" line + ethtool -i "$ifname" | awk -v pat="$pattern" ' + BEGIN { + n = split(pat, kv, ";"); + for (i = 1; i <= n; i++) { + eq = index(kv[i], "="); + want[substr(kv[i], 1, eq - 1) ": " substr(kv[i], eq + 1)] = 1; + } + } + ($0 in want) { delete want[$0]; found++; } + END { exit(found < n); } + ' || return match "@ethtool:$pattern" } diff --git a/board/common/rootfs/usr/libexec/infix/init.d/25-mqprio b/board/common/rootfs/usr/libexec/infix/init.d/25-mqprio index d53cc54d8..875dcc0b9 100755 --- a/board/common/rootfs/usr/libexec/infix/init.d/25-mqprio +++ b/board/common/rootfs/usr/libexec/infix/init.d/25-mqprio @@ -37,13 +37,16 @@ queues() echo "$out" } +quirks= +[ -f /etc/product/interface-quirks.json ] && quirks=yes + set $(ip -j -d link show | jq -r '.[] | .ifname, .num_tx_queues') while [ "$1" ]; do iface="$1" txqs="$2" shift 2 - [ $(/usr/libexec/infix/has-quirk "broken-mqprio" "$iface") = "true" ] && echo "Skipping $iface, does not support mqprio" && continue + [ "$quirks" ] && [ $(/usr/libexec/infix/has-quirk "broken-mqprio" "$iface") = "true" ] && echo "Skipping $iface, does not support mqprio" && continue [ $txqs -lt 2 ] && continue [ $txqs -gt 8 ] && txqs=8 diff --git a/configs/aarch64_defconfig b/configs/aarch64_defconfig index 5d290ff92..0c11611bf 100644 --- a/configs/aarch64_defconfig +++ b/configs/aarch64_defconfig @@ -16,7 +16,6 @@ BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_EUDEV=y BR2_ROOTFS_DEVICE_TABLE="system/device_table.txt ${BR2_EXTERNAL_INFIX_PATH}/board/common/xattrs" BR2_ROOTFS_MERGED_USR=y # BR2_TARGET_ENABLE_ROOT_LOGIN is not set -BR2_SYSTEM_BIN_SH_BASH=y BR2_TARGET_GENERIC_GETTY_PORT="@console" BR2_TARGET_GENERIC_GETTY_TERM="xterm" BR2_SYSTEM_DHCP="eth0" @@ -98,6 +97,7 @@ BR2_PACKAGE_TRACEROUTE=y BR2_PACKAGE_ULOGD=y BR2_PACKAGE_WHOIS=y BR2_PACKAGE_WIREGUARD_TOOLS=y +BR2_PACKAGE_BASH=y BR2_PACKAGE_BASH_COMPLETION=y BR2_PACKAGE_NEOFETCH=y BR2_PACKAGE_SUDO=y diff --git a/configs/aarch64_minimal_defconfig b/configs/aarch64_minimal_defconfig index 0d56b361c..7dca9604d 100644 --- a/configs/aarch64_minimal_defconfig +++ b/configs/aarch64_minimal_defconfig @@ -16,7 +16,6 @@ BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_EUDEV=y BR2_ROOTFS_DEVICE_TABLE="system/device_table.txt ${BR2_EXTERNAL_INFIX_PATH}/board/common/xattrs" BR2_ROOTFS_MERGED_USR=y # BR2_TARGET_ENABLE_ROOT_LOGIN is not set -BR2_SYSTEM_BIN_SH_BASH=y BR2_TARGET_GENERIC_GETTY_PORT="@console" BR2_TARGET_GENERIC_GETTY_TERM="xterm" BR2_SYSTEM_DHCP="eth0" @@ -82,6 +81,7 @@ BR2_PACKAGE_SOCAT=y BR2_PACKAGE_TCPDUMP=y BR2_PACKAGE_WHOIS=y BR2_PACKAGE_WIREGUARD_TOOLS=y +BR2_PACKAGE_BASH=y BR2_PACKAGE_BASH_COMPLETION=y BR2_PACKAGE_SUDO=y BR2_PACKAGE_GETENT=y diff --git a/configs/arm_defconfig b/configs/arm_defconfig index 0c8f61daf..bdd7be9fe 100644 --- a/configs/arm_defconfig +++ b/configs/arm_defconfig @@ -17,7 +17,6 @@ BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_EUDEV=y BR2_ROOTFS_DEVICE_TABLE="system/device_table.txt ${BR2_EXTERNAL_INFIX_PATH}/board/common/xattrs" BR2_ROOTFS_MERGED_USR=y # BR2_TARGET_ENABLE_ROOT_LOGIN is not set -BR2_SYSTEM_BIN_SH_BASH=y BR2_TARGET_GENERIC_GETTY_PORT="@console" BR2_TARGET_GENERIC_GETTY_TERM="xterm" BR2_SYSTEM_DHCP="eth0" @@ -97,6 +96,7 @@ BR2_PACKAGE_TRACEROUTE=y BR2_PACKAGE_ULOGD=y BR2_PACKAGE_WHOIS=y BR2_PACKAGE_WIREGUARD_TOOLS=y +BR2_PACKAGE_BASH=y BR2_PACKAGE_BASH_COMPLETION=y BR2_PACKAGE_NEOFETCH=y BR2_PACKAGE_SUDO=y diff --git a/configs/arm_minimal_defconfig b/configs/arm_minimal_defconfig index 3ef9a86cd..a5fc7decf 100644 --- a/configs/arm_minimal_defconfig +++ b/configs/arm_minimal_defconfig @@ -17,7 +17,6 @@ BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_EUDEV=y BR2_ROOTFS_DEVICE_TABLE="system/device_table.txt ${BR2_EXTERNAL_INFIX_PATH}/board/common/xattrs" BR2_ROOTFS_MERGED_USR=y # BR2_TARGET_ENABLE_ROOT_LOGIN is not set -BR2_SYSTEM_BIN_SH_BASH=y BR2_TARGET_GENERIC_GETTY_PORT="@console" BR2_TARGET_GENERIC_GETTY_TERM="xterm" BR2_SYSTEM_DHCP="eth0" @@ -84,6 +83,7 @@ BR2_PACKAGE_SOCAT=y BR2_PACKAGE_TCPDUMP=y BR2_PACKAGE_WHOIS=y BR2_PACKAGE_WIREGUARD_TOOLS=y +BR2_PACKAGE_BASH=y BR2_PACKAGE_BASH_COMPLETION=y BR2_PACKAGE_SUDO=y BR2_PACKAGE_GETENT=y diff --git a/configs/riscv64_defconfig b/configs/riscv64_defconfig index 5c0ad566f..6164d6646 100644 --- a/configs/riscv64_defconfig +++ b/configs/riscv64_defconfig @@ -14,7 +14,6 @@ BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_EUDEV=y BR2_ROOTFS_DEVICE_TABLE="system/device_table.txt ${BR2_EXTERNAL_INFIX_PATH}/board/common/xattrs" BR2_ROOTFS_MERGED_USR=y # BR2_TARGET_ENABLE_ROOT_LOGIN is not set -BR2_SYSTEM_BIN_SH_BASH=y BR2_TARGET_GENERIC_GETTY_PORT="@console" BR2_TARGET_GENERIC_GETTY_TERM="xterm" BR2_SYSTEM_DHCP="eth0" @@ -108,6 +107,7 @@ BR2_PACKAGE_TRACEROUTE=y BR2_PACKAGE_ULOGD=y BR2_PACKAGE_WHOIS=y BR2_PACKAGE_WIREGUARD_TOOLS=y +BR2_PACKAGE_BASH=y BR2_PACKAGE_BASH_COMPLETION=y BR2_PACKAGE_NEOFETCH=y BR2_PACKAGE_SUDO=y diff --git a/configs/x86_64_defconfig b/configs/x86_64_defconfig index 73d44bcf3..5db6691c5 100644 --- a/configs/x86_64_defconfig +++ b/configs/x86_64_defconfig @@ -15,7 +15,6 @@ BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_EUDEV=y BR2_ROOTFS_DEVICE_TABLE="system/device_table.txt ${BR2_EXTERNAL_INFIX_PATH}/board/common/xattrs" BR2_ROOTFS_MERGED_USR=y # BR2_TARGET_ENABLE_ROOT_LOGIN is not set -BR2_SYSTEM_BIN_SH_BASH=y BR2_TARGET_GENERIC_GETTY_PORT="@console" BR2_TARGET_GENERIC_GETTY_TERM="xterm" BR2_SYSTEM_DHCP="eth0" @@ -97,6 +96,7 @@ BR2_PACKAGE_TRACEROUTE=y BR2_PACKAGE_ULOGD=y BR2_PACKAGE_WHOIS=y BR2_PACKAGE_WIREGUARD_TOOLS=y +BR2_PACKAGE_BASH=y BR2_PACKAGE_BASH_COMPLETION=y BR2_PACKAGE_NEOFETCH=y BR2_PACKAGE_SUDO=y diff --git a/configs/x86_64_minimal_defconfig b/configs/x86_64_minimal_defconfig index ba7fb3170..058bab508 100644 --- a/configs/x86_64_minimal_defconfig +++ b/configs/x86_64_minimal_defconfig @@ -15,7 +15,6 @@ BR2_ROOTFS_DEVICE_CREATION_DYNAMIC_EUDEV=y BR2_ROOTFS_DEVICE_TABLE="system/device_table.txt ${BR2_EXTERNAL_INFIX_PATH}/board/common/xattrs" BR2_ROOTFS_MERGED_USR=y # BR2_TARGET_ENABLE_ROOT_LOGIN is not set -BR2_SYSTEM_BIN_SH_BASH=y BR2_TARGET_GENERIC_GETTY_PORT="@console" BR2_TARGET_GENERIC_GETTY_TERM="xterm" BR2_SYSTEM_DHCP="eth0" @@ -81,6 +80,7 @@ BR2_PACKAGE_SOCAT=y BR2_PACKAGE_TCPDUMP=y BR2_PACKAGE_WHOIS=y BR2_PACKAGE_WIREGUARD_TOOLS=y +BR2_PACKAGE_BASH=y BR2_PACKAGE_BASH_COMPLETION=y BR2_PACKAGE_SUDO=y BR2_PACKAGE_GETENT=y diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index 823d7b68a..00f253a51 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -10,6 +10,10 @@ All notable changes to the project are documented in this file. - Upgrade Linux kernel to 6.18.49 (LTS) - Upgrade FRR to 10.5.5 +- `/bin/sh` is now provided by Busybox ash instead of Bash, speeding up + boot and configuration changes, issue #961. Same rationale as Debian's + dash-as-/bin/sh. Bash remains available for interactive use and for + scripts using `#!/bin/bash` [v26.08.0][] - 2026-09-01 ------------------------- diff --git a/src/confd/bin/dagger b/src/confd/bin/dagger index 8949ca716..eedaf57c4 100755 --- a/src/confd/bin/dagger +++ b/src/confd/bin/dagger @@ -67,7 +67,7 @@ action_exec() EOF $TIME -f "$TIMEFMT" -o $meta.time $work >>"$work.log" 2>&1 || code=$? - echo -ne "\t" >>$meta + printf '\t' >>$meta # busybox's time(1) will happily write "Command exited # with non-zero status" and similar messages to the # output, even when -f is used. Work around that by only diff --git a/src/confd/bin/wait-interface b/src/confd/bin/wait-interface index 8900fdbc2..a3f53963c 100644 --- a/src/confd/bin/wait-interface +++ b/src/confd/bin/wait-interface @@ -7,7 +7,7 @@ fi ifname=$1 timeout=$2 while true; do - if ip link show $ifname &>/dev/null; then + if ip link show $ifname >/dev/null 2>&1; then break fi diff --git a/src/confd/src/ieee802-ethernet-interface.c b/src/confd/src/ieee802-ethernet-interface.c index 87eed1c76..996d4665a 100644 --- a/src/confd/src/ieee802-ethernet-interface.c +++ b/src/confd/src/ieee802-ethernet-interface.c @@ -131,7 +131,7 @@ static int netdag_gen_ethtool_flow_control(struct dagger *net, struct lyd_node * return -EIO; /* Check if the NIC supports pause frames at all */ - fprintf(fp, "[[ -n $(ethtool --json %s | jq '.[] | select(.\"supported-pause-frame-use\" == \"No\")') ]] && exit 0\n", ifname); + fprintf(fp, "[ -n \"$(ethtool --json %s | jq '.[] | select(.\"supported-pause-frame-use\" == \"No\")')\" ] && exit 0\n", ifname); /* Disable flow control */ fprintf(fp, "ethtool --pause %s autoneg %s rx off tx off\n", @@ -189,7 +189,7 @@ static int netdag_gen_ethtool_autoneg(struct dagger *net, struct lyd_node *cif) if (!fp) return -EIO; - fprintf(fp, "[[ -n $(ethtool --json %s | jq '.[] | select(.\"supports-auto-negotiation\" == false)') ]] && exit 0\n", ifname); + fprintf(fp, "[ -n \"$(ethtool --json %s | jq '.[] | select(.\"supports-auto-negotiation\" == false)')\" ] && exit 0\n", ifname); duplex = lydx_get_cattr(eth, "duplex"); diff --git a/src/confd/src/interfaces.c b/src/confd/src/interfaces.c index db01484c7..be1bc222b 100644 --- a/src/confd/src/interfaces.c +++ b/src/confd/src/interfaces.c @@ -15,10 +15,34 @@ #define IFACE_PROBE_TIMEOUT 40 +/* Absent, null, and false are the only values jq -e treats as unset */ +static bool quirk_is_set(json_t *quirks, const char *quirkname) +{ + json_t *val = json_object_get(quirks, quirkname); + + return val && !json_is_null(val) && !json_is_false(val); +} + bool iface_has_quirk(const char *ifname, const char *quirkname) { - return systemf("[ $(/usr/libexec/infix/has-quirk %s %s) = true ]", - quirkname, ifname) == 0; + const char *pattern; + json_t *quirks; + + if (!confd.ifquirks) + return false; + + quirks = json_object_get(confd.ifquirks, ifname); + if (quirks && quirk_is_set(quirks, quirkname)) + return true; + + /* @ethtool: patterns match on driver info, left to has-quirk */ + json_object_foreach(confd.ifquirks, pattern, quirks) { + if (!strncmp(pattern, "@ethtool:", 9)) + return systemf("[ $(/usr/libexec/infix/has-quirk %s %s) = true ]", + quirkname, ifname) == 0; + } + + return false; } static bool iface_is_phys(const char *ifname) diff --git a/test/case/hardware/watchdog/test.py b/test/case/hardware/watchdog/test.py index a3d3e0be8..70afa3122 100755 --- a/test/case/hardware/watchdog/test.py +++ b/test/case/hardware/watchdog/test.py @@ -46,6 +46,10 @@ def status(): tgtssh.runsh(f""" lockup() {{ + # Detach: a backgrounded function in POSIX sh holds a copy + # of the session's stdout, keeping the SSH channel open + exec /dev/null 2>&1 + # Give the SSH session some time to properly shut down sleep 3 @@ -55,7 +59,7 @@ def status(): time_secs={dog['timeout'] * 2} }} - lockup /dev/null & + lockup & """) with test.step("Wait for the watchdog to trip"): diff --git a/test/case/syslog/advanced_compare/test.py b/test/case/syslog/advanced_compare/test.py index 41ee70fd9..c83bbe657 100755 --- a/test/case/syslog/advanced_compare/test.py +++ b/test/case/syslog/advanced_compare/test.py @@ -28,7 +28,7 @@ lambda: env.attach("target", "mgmt", "ssh")) with test.step("Clean up old log files from previous test runs"): - tgtssh.runsh("sudo rm -f /var/log/{exact-errors,no-debug,baseline}") + tgtssh.runsh("sudo rm -f /var/log/exact-errors /var/log/no-debug /var/log/baseline") with test.step("Configure syslog with advanced-compare"): target.put_config_dicts({ diff --git a/test/case/syslog/basic/test.py b/test/case/syslog/basic/test.py index 972804696..7e0054a47 100755 --- a/test/case/syslog/basic/test.py +++ b/test/case/syslog/basic/test.py @@ -56,7 +56,7 @@ }) with test.step("Verify log files have been created"): - user = tgtssh.runsh("ls /var/log/{foo,bar.log}").stdout + user = tgtssh.runsh("ls /var/log/foo /var/log/bar.log").stdout if "/var/log/foo" not in user: test.fail() if "/var/log/bar.log" not in user: diff --git a/test/case/syslog/hostname_filter/test.py b/test/case/syslog/hostname_filter/test.py index 6bbbb684c..b34bc4144 100755 --- a/test/case/syslog/hostname_filter/test.py +++ b/test/case/syslog/hostname_filter/test.py @@ -47,7 +47,7 @@ def check_log(): lambda: env.attach("server", "mgmt", "ssh")) with test.step("Clean up old log files on server"): - serverssh.runsh("sudo rm -f /var/log/{router1,router2,all-hosts}") + serverssh.runsh("sudo rm -f /var/log/router1 /var/log/router2 /var/log/all-hosts") with test.step("Configure server as syslog sink with hostname filtering"): _, server_link = env.ltop.xlate("server", "link") diff --git a/test/case/syslog/pattern_match/test.py b/test/case/syslog/pattern_match/test.py index 2171bc288..daf046a8a 100755 --- a/test/case/syslog/pattern_match/test.py +++ b/test/case/syslog/pattern_match/test.py @@ -27,7 +27,7 @@ lambda: env.attach("target", "mgmt", "ssh")) with test.step("Clean up old log files from previous test runs"): - tgtssh.runsh("sudo rm -f /var/log/{errors,routers,all-messages}") + tgtssh.runsh("sudo rm -f /var/log/errors /var/log/routers /var/log/all-messages") with test.step("Configure syslog with pattern-match filters"): target.put_config_dicts({ diff --git a/test/case/syslog/property_filter/test.py b/test/case/syslog/property_filter/test.py index 9ca9d81b8..7f7d7fd9d 100755 --- a/test/case/syslog/property_filter/test.py +++ b/test/case/syslog/property_filter/test.py @@ -26,7 +26,7 @@ lambda: env.attach("target", "mgmt", "ssh")) with test.step("Clean up old log files"): - tgtssh.runsh("sudo rm -f /var/log/{myapp,not-error,case-test,baseline}") + tgtssh.runsh("sudo rm -f /var/log/myapp /var/log/not-error /var/log/case-test /var/log/baseline") with test.step("Configure syslog with property filters"): target.put_config_dicts({