From 48eeeb4479e31c41f2cfcb00136c6e78d9d90150 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Sun, 2 Aug 2026 11:46:22 +0200 Subject: [PATCH 1/7] confd: fix dangling Finit symlinks for optional services Minimal images log the following on every boot and initctl reload, e.g., when the regression test framework reconfigures the system: finit[1]: Skipping /etc/finit.d/enabled/netbrowse.conf, dangling symlink: No such file or directory finit[1]: service_register():/etc/finit.d/enabled/ttyd.conf: skipping ttyd: No such file or directory finit[1]: Skipping /etc/finit.d/enabled/webui.conf, dangling symlink: No such file or directory The web services are enabled in the default configuration, so confd's finit_enable() creates enabled/ symlinks also on images where the service was never installed. svc_enable() already guards its nginx symlinks with the corresponding check. The ttyd case differs: its conf ships unconditionally in the common rootfs skeleton while the daemon itself is an optional package. Skip enable, with a log message at INFO, when the service conf is not available in the image. At build time, drop the ttyd confs when ttyd is not selected, and prune any dangling enabled/*.conf symlinks. Signed-off-by: Joachim Wiberg --- board/common/post-build.sh | 5 +++++ 1 file changed, 5 insertions(+) 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 From 509f978bc9fae2523ddc7cf5d4a480a65e1d1dcc Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 5 Aug 2026 10:28:06 +0200 Subject: [PATCH 2/7] confd: fix bashisms in /bin/sh scripts Preparation for switching /bin/sh from bash to busybox ash, issue #961. All of these only work today because /bin/sh is bash: - wait-interface used '&>', which POSIX sh parses as backgrounding the command, making the interface-exists check always true and the boot-time wait a no-op - the generated ethtool-flow-control.sh and ethtool-aneg.sh scripts used '[[', not available in POSIX sh: the not-supported guard can never fire, and the failing ethtool command that follows aborts the whole dagger generation - dagger used 'echo -ne', which plain sh echo does not implement Found with checkbashisms(1). Signed-off-by: Joachim Wiberg --- src/confd/bin/dagger | 2 +- src/confd/bin/wait-interface | 2 +- src/confd/src/ieee802-ethernet-interface.c | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) 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"); From daaf68f0eddad93e7030300a8349894862873401 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 5 Aug 2026 10:30:14 +0200 Subject: [PATCH 3/7] Change /bin/sh from bash to BusyBox ash Every system(3) call, dagger action script, and Finit run-parts spawns /bin/sh: bash pays start-up and memory costs that BusyBox ash does not. Same rationale as Debian's dash-as-/bin/sh. Busybox' default shell is the buildroot choice default, and the BusyBox config already sets CONFIG_SH_IS_ASH. Bash remains, now explicitly selected, for interactive shells and scripts using #!/bin/bash. Fixes #961 Signed-off-by: Joachim Wiberg --- configs/aarch64_defconfig | 2 +- configs/aarch64_minimal_defconfig | 2 +- configs/arm_defconfig | 2 +- configs/arm_minimal_defconfig | 2 +- configs/riscv64_defconfig | 2 +- configs/x86_64_defconfig | 2 +- configs/x86_64_minimal_defconfig | 2 +- doc/ChangeLog.md | 4 ++++ 8 files changed, 11 insertions(+), 7 deletions(-) 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 ------------------------- From cc2d53d74b02544b9928838a7a9dcdae4c8257a3 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 5 Aug 2026 13:42:55 +0200 Subject: [PATCH 4/7] Convert has-quirk to POSIX sh, called per interface by confd The interface quirk check spawns a shell per interface on every interface configuration change, and at boot, one of the few remaining bash spawns on the configuration hot path after issue #961. The only bash dependency was the process substitution feeding grep both the ethtool -i output and the expected key=val lines. Replace with a single awk over the ethtool output, checking that all pairs of the pattern match; splitting on the first '=' like before. Verified against the bash version with a stubbed ethtool: interface name match, ethtool pattern match/mismatch, unknown quirk, and missing quirks file all agree, under both dash and busybox ash. Signed-off-by: Joachim Wiberg --- .../common/rootfs/usr/libexec/infix/has-quirk | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) 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" } From 65c70d74280a3b5e6f743669222edeca4254ba4a Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Thu, 6 Aug 2026 09:19:05 +0200 Subject: [PATCH 5/7] test: fix bashisms in DUT-side shell fragments infamy's runsh() feeds the script to /bin/sh on the DUT, and with the switch to busybox ash bashisms no longer work: - brace expansion degrades to a literal argument, so the syslog tests' log file cleanup removed nothing, leaving stale files that skew message counts in later runs - the watchdog test's '&>' backgrounded the lockup with stdout and stderr still attached to the SSH session, hanging the test until TCP timeout, long after the DUT had rebooted as intended Shell fragments in the infamy netns helpers are unaffected, they run on the test host where /bin/sh was already a POSIX shell. Signed-off-by: Joachim Wiberg --- test/case/hardware/watchdog/test.py | 6 +++++- test/case/syslog/advanced_compare/test.py | 2 +- test/case/syslog/basic/test.py | 2 +- test/case/syslog/hostname_filter/test.py | 2 +- test/case/syslog/pattern_match/test.py | 2 +- test/case/syslog/property_filter/test.py | 2 +- 6 files changed, 10 insertions(+), 6 deletions(-) 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({ From 1de2c3ad74985ecc5a09d401a50355b18a804f90 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 26 Aug 2026 14:31:27 +0200 Subject: [PATCH 6/7] confd: answer interface quirks from the parsed quirks file Every quirk lookup spawns three processes -- the shell from system(3), its command substitution, and has-quirk itself -- to read a file confd already holds in memory. Four lookups per port, plus the one in 25-mqprio, is ~420 processes per boot on a 28-port switch, and the same again on every interface configuration change. confd loads /etc/product/interface-quirks.json into confd.ifquirks at startup, but nothing has ever read it. Match the interface name there instead. @ethtool: patterns need driver info, so those still go out to has-quirk; boards with no quirks file, or with only interface name patterns, no longer spawn anything. 25-mqprio hoists the file check out of its per-interface loop for the same reason. Verified against has-quirk across all product quirks files, four interface names and six quirk names, with a stubbed ethtool: 192 cases, no differences. Signed-off-by: Joachim Wiberg --- .../rootfs/usr/libexec/infix/init.d/25-mqprio | 5 +++- src/confd/src/interfaces.c | 28 +++++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) 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/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) From c03bd97ad7d9fcf72e21527a4f5696df89ab4786 Mon Sep 17 00:00:00 2001 From: Joachim Wiberg Date: Wed, 26 Aug 2026 14:31:28 +0200 Subject: [PATCH 7/7] board: drop stray RPi CM4 interface quirks file Quirks are read from /etc/product/interface-quirks.json, so the copy at the product directory root is never found. The one below etc/product/ already carries the same two quirks. Signed-off-by: Joachim Wiberg --- .../raspberrypi,4-compute-module/interface-quirks.json | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 board/aarch64/raspberrypi-rpi64/rootfs/usr/share/product/raspberrypi,4-compute-module/interface-quirks.json 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 - } -}