Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

5 changes: 5 additions & 0 deletions board/common/post-build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
18 changes: 13 additions & 5 deletions board/common/rootfs/usr/libexec/infix/has-quirk
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/bash
#!/bin/sh

IFQUIRKSFILE=${IFQUIRKSFILE:-/etc/product/interface-quirks.json}

Expand Down Expand Up @@ -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"
}
Expand Down
5 changes: 4 additions & 1 deletion board/common/rootfs/usr/libexec/infix/init.d/25-mqprio
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion configs/aarch64_defconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion configs/aarch64_minimal_defconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion configs/arm_defconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion configs/arm_minimal_defconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion configs/riscv64_defconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion configs/x86_64_defconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion configs/x86_64_minimal_defconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions doc/ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
-------------------------
Expand Down
2 changes: 1 addition & 1 deletion src/confd/bin/dagger
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/confd/bin/wait-interface
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions src/confd/src/ieee802-ethernet-interface.c
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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");

Expand Down
28 changes: 26 additions & 2 deletions src/confd/src/interfaces.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 5 additions & 1 deletion test/case/hardware/watchdog/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 >/dev/null 2>&1

# Give the SSH session some time to properly shut down
sleep 3

Expand All @@ -55,7 +59,7 @@ def status():
time_secs={dog['timeout'] * 2}
}}

lockup </dev/null &>/dev/null &
lockup &
""")

with test.step("Wait for the watchdog to trip"):
Expand Down
2 changes: 1 addition & 1 deletion test/case/syslog/advanced_compare/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
2 changes: 1 addition & 1 deletion test/case/syslog/basic/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion test/case/syslog/hostname_filter/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion test/case/syslog/pattern_match/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
2 changes: 1 addition & 1 deletion test/case/syslog/property_filter/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down