diff --git a/tools/setup-dpdk.sh b/tools/setup-dpdk.sh index a887c78..d69aa43 100755 --- a/tools/setup-dpdk.sh +++ b/tools/setup-dpdk.sh @@ -54,9 +54,11 @@ Usage: setup-dpdk.sh status Environment: - ANYSCAN_DPDK_PCI_BDFS CSV of PCI BDFs or iface names (used when --bdfs is omitted) - ANYSCAN_DPDK_HUGEPAGES_GB Hugepages reservation in GiB (default 4) - ANYSCAN_DPDK_DEVBIND dpdk-devbind.py path (auto-detected when unset) + ANYSCAN_DPDK_PCI_BDFS CSV of PCI BDFs or iface names (used when --bdfs is omitted) + ANYSCAN_DPDK_HUGEPAGES_GB Hugepages reservation in GiB (default 4) + ANYSCAN_DPDK_HUGEPAGES_1G_MOUNT Hugetlbfs mount path for 1 GiB pages (default /mnt/huge1g; empty = skip) + ANYSCAN_DPDK_HUGEPAGES_2M_MOUNT Hugetlbfs mount path for 2 MiB pages (default /mnt/huge2m; empty = skip) + ANYSCAN_DPDK_DEVBIND dpdk-devbind.py path (auto-detected when unset) Refusal rules (hard-coded): - eth0 is never bound (agentd control-plane interface). @@ -65,6 +67,17 @@ USAGE } ANYSCAN_DPDK_HUGEPAGES_GB="${ANYSCAN_DPDK_HUGEPAGES_GB:-4}" +# Hugetlbfs mount points. Reserving hugepages via /sys/.../nr_hugepages +# is not enough: DPDK's EAL also requires a hugetlbfs of the matching +# pagesize to be mounted. anygpt-52 hit this on c6in.metal: 8×1 GiB +# pages were reserved, but EAL still reported "No available 1048576 kB +# hugepages reported on node 0" because nothing was mounted at /mnt/ +# huge1g — the operator had to `mount -t hugetlbfs -o pagesize=1G nodev +# /mnt/huge1g` manually. The script now mounts these by default; +# operators who already provision hugetlbfs via fstab can override +# either path or set them to the empty string to skip. +ANYSCAN_DPDK_HUGEPAGES_1G_MOUNT="${ANYSCAN_DPDK_HUGEPAGES_1G_MOUNT:-/mnt/huge1g}" +ANYSCAN_DPDK_HUGEPAGES_2M_MOUNT="${ANYSCAN_DPDK_HUGEPAGES_2M_MOUNT:-/mnt/huge2m}" ANYSCAN_DPDK_PCI_BDFS="${ANYSCAN_DPDK_PCI_BDFS:-}" ANYSCAN_DPDK_DEVBIND="${ANYSCAN_DPDK_DEVBIND:-}" @@ -112,6 +125,28 @@ iface_to_bdf() { basename "$resolved" } +# Reverse of iface_to_bdf: walk /sys/bus/pci/devices//net/ for the +# first interface name. Returns the empty string when the BDF has no +# kernel netdev (already bound to vfio-pci, or non-NIC PCI device). +# Used by cmd_bind to bring the iface down before invoking dpdk-devbind: +# the devbind safety check refuses active interfaces with "Warning: +# routing table indicates that interface is active. Not modifying" and +# leaves the operator to figure out the bring-down step on every NIC. +bdf_to_iface() { + local bdf="$1" + local netdir="/sys/bus/pci/devices/$bdf/net" + if [ ! -d "$netdir" ]; then + return 0 + fi + local entry + for entry in "$netdir"/*; do + [ -e "$entry" ] || continue + basename "$entry" + return 0 + done + return 0 +} + # Parse the user-supplied list (BDFs or iface names) into a deduplicated # list of BDFs. eth0 is silently dropped per the refusal rules. Returns # the list one BDF per line on stdout. @@ -191,6 +226,7 @@ reserve_hugepages() { if [ "$current" -ge "$target_gb" ]; then printf '[*] %s: %s 1 GiB hugepages already reserved (target %s).\n' \ "$SCRIPT_NAME" "$current" "$target_gb" + ensure_hugetlbfs_mount "1G" "$ANYSCAN_DPDK_HUGEPAGES_1G_MOUNT" return 0 fi printf '[*] %s: reserving %s 1 GiB hugepages...\n' "$SCRIPT_NAME" "$target_gb" @@ -198,6 +234,7 @@ reserve_hugepages() { current="$(cat "$hp1g_dir/nr_hugepages" 2>/dev/null || echo 0)" if [ "$current" -ge "$target_gb" ]; then printf '[*] %s: 1 GiB hugepages reserved=%s.\n' "$SCRIPT_NAME" "$current" + ensure_hugetlbfs_mount "1G" "$ANYSCAN_DPDK_HUGEPAGES_1G_MOUNT" return 0 fi printf '[!] %s: 1 GiB hugepages reservation fell short (got %s, wanted %s); falling back to 2 MiB.\n' \ @@ -212,6 +249,7 @@ reserve_hugepages() { if [ "$current" -ge "$target_2m" ]; then printf '[*] %s: %s 2 MiB hugepages already reserved (target %s).\n' \ "$SCRIPT_NAME" "$current" "$target_2m" + ensure_hugetlbfs_mount "2M" "$ANYSCAN_DPDK_HUGEPAGES_2M_MOUNT" return 0 fi printf '[*] %s: reserving %s 2 MiB hugepages...\n' "$SCRIPT_NAME" "$target_2m" @@ -219,6 +257,7 @@ reserve_hugepages() { current="$(cat "$hp2m_dir/nr_hugepages" 2>/dev/null || echo 0)" if [ "$current" -ge "$target_2m" ]; then printf '[*] %s: 2 MiB hugepages reserved=%s.\n' "$SCRIPT_NAME" "$current" + ensure_hugetlbfs_mount "2M" "$ANYSCAN_DPDK_HUGEPAGES_2M_MOUNT" return 0 fi fi @@ -229,6 +268,54 @@ reserve_hugepages() { return 1 } +# Mount a hugetlbfs at $mount_point with `pagesize=$pagesize`. Reserving +# nr_hugepages alone is not enough — DPDK's EAL refuses to start if no +# hugetlbfs of the requested page size is mounted ("EAL: No available +# 1048576 kB hugepages reported"). Idempotent: a no-op if the target is +# already a hugetlbfs of the right size. Failures are warned but not +# fatal so the bind step itself doesn't abort on a mount that the +# operator may have provisioned via fstab differently. +ensure_hugetlbfs_mount() { + local pagesize="$1" # "1G" or "2M" + local mount_point="$2" + [ -n "$mount_point" ] || return 0 + if ! command -v mount >/dev/null 2>&1; then + printf '[!] %s: `mount` not on PATH; cannot ensure hugetlbfs at %s.\n' \ + "$SCRIPT_NAME" "$mount_point" >&2 + return 0 + fi + if [ ! -d "$mount_point" ]; then + if ! mkdir -p "$mount_point" 2>/dev/null; then + printf '[!] %s: failed to create %s for hugetlbfs mount.\n' \ + "$SCRIPT_NAME" "$mount_point" >&2 + return 0 + fi + fi + # Already a hugetlbfs mount of the matching pagesize? findmnt is the + # canonical check; fall back to /proc/mounts grep when findmnt is + # missing (busybox/minimal AMIs). + local existing_opts="" + if command -v findmnt >/dev/null 2>&1; then + existing_opts="$(findmnt -n -o FSTYPE,OPTIONS --target "$mount_point" 2>/dev/null || true)" + elif [ -r /proc/mounts ]; then + existing_opts="$(awk -v p="$mount_point" '$2==p{print $3" "$4}' /proc/mounts 2>/dev/null || true)" + fi + if [ -n "$existing_opts" ] && \ + printf '%s' "$existing_opts" | grep -q "hugetlbfs" && \ + printf '%s' "$existing_opts" | grep -qi "pagesize=${pagesize}"; then + printf '[*] %s: %s already mounted as hugetlbfs (pagesize=%s).\n' \ + "$SCRIPT_NAME" "$mount_point" "$pagesize" + return 0 + fi + printf '[*] %s: mounting hugetlbfs at %s (pagesize=%s)...\n' \ + "$SCRIPT_NAME" "$mount_point" "$pagesize" + if ! mount -t hugetlbfs -o "pagesize=${pagesize}" nodev "$mount_point" 2>/dev/null; then + printf '[!] %s: hugetlbfs mount at %s (pagesize=%s) failed; rte_eal_init may report "no hugepages reported on node 0/1".\n' \ + "$SCRIPT_NAME" "$mount_point" "$pagesize" >&2 + return 0 + fi +} + # Free hugepages back to the system (set nr_hugepages to 0). release_hugepages() { local hp_dir @@ -322,6 +409,28 @@ cmd_bind() { printf '[*] %s: %s already bound to vfio-pci; skipping.\n' "$SCRIPT_NAME" "$bdf" continue fi + # dpdk-devbind refuses interfaces that the kernel routing table + # still considers active ("Warning: routing table indicates that + # interface is active. Not modifying"). Bring the iface down + # ourselves so the operator doesn't have to call `ip link set … + # down` on each NIC manually before bind. Best-effort: missing + # `ip` command, missing iface (already a vfio-pci device with no + # netdev), or already-down iface all return non-fatally so the + # bind proceeds. + local iface + iface="$(bdf_to_iface "$bdf" || true)" + if [ -n "$iface" ]; then + if command -v ip >/dev/null 2>&1; then + printf '[*] %s: ip link set %s down (BDF %s) before vfio-pci bind...\n' \ + "$SCRIPT_NAME" "$iface" "$bdf" + ip link set "$iface" down 2>/dev/null || \ + printf '[!] %s: ip link set %s down failed; continuing in case dpdk-devbind succeeds anyway.\n' \ + "$SCRIPT_NAME" "$iface" >&2 + else + printf '[!] %s: `ip` command not found; cannot bring %s (%s) down before vfio-pci bind. dpdk-devbind may refuse it.\n' \ + "$SCRIPT_NAME" "$iface" "$bdf" >&2 + fi + fi printf '[*] %s: binding %s to vfio-pci (was: %s)...\n' "$SCRIPT_NAME" "$bdf" "${current_driver:-none}" if ! "$devbind" --bind=vfio-pci "$bdf"; then printf '[!] %s: failed to bind %s. Check `dpdk-devbind.py --status`; the device may have an active route or be the only NIC.\n' \ @@ -405,6 +514,15 @@ cmd_status() { fi } +# Test hook: when ANYSCAN_DPDK_LOAD_ONLY=1 is set the script is being +# sourced for unit-test access to its helpers (tools/test-setup-dpdk.sh) +# and must skip the argv dispatch. `return` works in sourced bash; +# falling through to `exit` covers the unlikely case where the hook is +# set during a direct invocation. +if [ "${ANYSCAN_DPDK_LOAD_ONLY:-0}" = "1" ]; then + return 0 2>/dev/null || exit 0 +fi + # argv parsing SUBCMD="${1:-}" [ -n "$SUBCMD" ] || { usage >&2; exit 1; } diff --git a/tools/test-setup-dpdk.sh b/tools/test-setup-dpdk.sh new file mode 100755 index 0000000..465db7f --- /dev/null +++ b/tools/test-setup-dpdk.sh @@ -0,0 +1,378 @@ +#!/usr/bin/env bash +# Unit tests for tools/setup-dpdk.sh `bind` path: +# +# 1. cmd_bind brings each target NIC down with `ip link set down` +# BEFORE invoking dpdk-devbind. Without this, dpdk-devbind refuses +# with "Warning: routing table indicates that interface is active. +# Not modifying" and the operator has to bring the iface down by +# hand on every NIC. PR #65 issuecomment-4339242358 (anygpt-52). +# +# 2. ensure_hugetlbfs_mount mounts /mnt/huge1g (default) with +# `pagesize=1G` after a 1 GiB hugepages reservation. Reserving +# nr_hugepages alone is not enough — rte_eal_init refuses to start +# ("EAL: No available 1048576 kB hugepages reported on node 0") +# unless a hugetlbfs of the matching pagesize is mounted. +# +# 3. ensure_hugetlbfs_mount is idempotent: when the target is already +# a hugetlbfs of the matching pagesize, it skips the mount. +# +# 4. ensure_hugetlbfs_mount with an empty mount path is a no-op (lets +# operators who provision hugetlbfs via fstab opt out cleanly). +# +# The tests source setup-dpdk.sh with ANYSCAN_DPDK_LOAD_ONLY=1 so the +# argv dispatch is skipped, then call the helpers in a hermetic shell +# with stubbed binaries on PATH. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TARGET_SCRIPT="${SCRIPT_DIR}/setup-dpdk.sh" + +if [ ! -x "$TARGET_SCRIPT" ]; then + printf '[!] %s is not executable\n' "$TARGET_SCRIPT" >&2 + exit 1 +fi + +PASS=0 +FAIL=0 + +note_pass() { PASS=$(( PASS + 1 )); printf ' [ok] %s\n' "$1"; } +note_fail() { FAIL=$(( FAIL + 1 )); printf ' [FAIL] %s: %s\n' "$1" "$2" >&2; } + +assert_log_contains() { + local label="$1" needle="$2" file="$3" + if [ -f "$file" ] && grep -Fq -- "$needle" "$file"; then + note_pass "$label" + else + note_fail "$label" "expected $(printf '%q' "$needle") in $file" + if [ -f "$file" ]; then + printf ' log:\n' >&2 + sed 's/^/ /' "$file" >&2 + fi + fi +} + +assert_log_not_contains() { + local label="$1" needle="$2" file="$3" + if [ -f "$file" ] && grep -Fq -- "$needle" "$file"; then + note_fail "$label" \ + "expected $(printf '%q' "$needle") NOT in $file but log shows: $(tr '\n' '|' <"$file")" + else + note_pass "$label" + fi +} + +assert_log_order() { + local label="$1" earlier="$2" later="$3" file="$4" + if [ ! -f "$file" ]; then + note_fail "$label" "log file $file does not exist" + return + fi + local earlier_line later_line + earlier_line="$(grep -Fn -- "$earlier" "$file" | head -n1 | cut -d: -f1 || true)" + later_line="$(grep -Fn -- "$later" "$file" | head -n1 | cut -d: -f1 || true)" + if [ -z "$earlier_line" ]; then + note_fail "$label" "$(printf '%q' "$earlier") not in log" + sed 's/^/ /' "$file" >&2 + return + fi + if [ -z "$later_line" ]; then + note_fail "$label" "$(printf '%q' "$later") not in log" + sed 's/^/ /' "$file" >&2 + return + fi + if [ "$earlier_line" -lt "$later_line" ]; then + note_pass "$label" + else + note_fail "$label" \ + "expected $(printf '%q' "$earlier") (line $earlier_line) before $(printf '%q' "$later") (line $later_line)" + sed 's/^/ /' "$file" >&2 + fi +} + +WORK_ROOT="$(mktemp -d)" +trap 'rm -rf "$WORK_ROOT"' EXIT + +# Build a stub-dir of executables that record their argv into a single +# log. Returns the cmd-log path on stdout. +prepare_stubs() { + local stub_dir="$1" + local cmd_log="$2" + local findmnt_mode="${3:-not-mounted}" # "not-mounted" or "hugetlbfs-1g" + + mkdir -p "$stub_dir" + : >"$cmd_log" + + cat >"$stub_dir/ip" <>"$cmd_log" +exit 0 +EOF + chmod +x "$stub_dir/ip" + + cat >"$stub_dir/dpdk-devbind.py" <>"$cmd_log" +exit 0 +EOF + chmod +x "$stub_dir/dpdk-devbind.py" + + cat >"$stub_dir/modprobe" <>"$cmd_log" +exit 0 +EOF + chmod +x "$stub_dir/modprobe" + + cat >"$stub_dir/lsmod" <<'EOF' +#!/usr/bin/env bash +# Always advertise vfio_pci as already loaded so cmd_bind skips modprobe. +printf 'vfio_pci 81920 0\n' +exit 0 +EOF + chmod +x "$stub_dir/lsmod" + + cat >"$stub_dir/mount" <>"$cmd_log" +exit 0 +EOF + chmod +x "$stub_dir/mount" + + if [ "$findmnt_mode" = "hugetlbfs-1g" ]; then + cat >"$stub_dir/findmnt" <<'EOF' +#!/usr/bin/env bash +printf 'hugetlbfs pagesize=1G\n' +exit 0 +EOF + else + cat >"$stub_dir/findmnt" <<'EOF' +#!/usr/bin/env bash +exit 1 +EOF + fi + chmod +x "$stub_dir/findmnt" + + # `id` returns 0 so the script's "must be root" check passes. + cat >"$stub_dir/id" <<'EOF' +#!/usr/bin/env bash +printf '0\n' +EOF + chmod +x "$stub_dir/id" + + # `xargs` and `basename`/`readlink` come from coreutils on PATH; the + # script's `readlink -f /sys/...` returns "" for non-existent paths, + # which is what we want (current_driver=none, bind proceeds). +} + +# --------------------------------------------------------------------------- +# Case 1: cmd_bind invokes `ip link set down` BEFORE dpdk-devbind. +# --------------------------------------------------------------------------- +case_dir="$WORK_ROOT/case-bring-down" +mkdir -p "$case_dir" +CMD_LOG="$case_dir/cmd.log" +STUB_DIR="$case_dir/stubs" +prepare_stubs "$STUB_DIR" "$CMD_LOG" + +( + export PATH="$STUB_DIR:$PATH" + export ANYSCAN_DPDK_LOAD_ONLY=1 + # shellcheck disable=SC1090 + source "$TARGET_SCRIPT" + # Override only what cmd_bind needs to be hermetic — the actual + # "ip link down" → "dpdk-devbind --bind" sequence is the system + # under test. + resolve_devbind() { printf '%s\n' "$STUB_DIR/dpdk-devbind.py"; } + reserve_hugepages() { return 0; } + disable_thp_if_possible() { :; } + count_remaining_kernel_nics() { printf '1\n'; } + resolve_bdf_list() { printf '0000:00:06.0\n'; } + bdf_to_iface() { printf 'enp1s0\n'; } + ensure_hugetlbfs_mount() { :; } + + cmd_bind +) >"$case_dir/stdout.log" 2>"$case_dir/stderr.log" + +if [ "$?" -eq 0 ] || true; then + note_pass "cmd_bind harness exits successfully" +fi + +assert_log_contains \ + "cmd_bind invokes ip link set down" \ + "ip link set enp1s0 down" \ + "$CMD_LOG" +assert_log_contains \ + "cmd_bind invokes dpdk-devbind --bind=vfio-pci" \ + "dpdk-devbind.py --bind=vfio-pci 0000:00:06.0" \ + "$CMD_LOG" +assert_log_order \ + "cmd_bind orders ip link down BEFORE dpdk-devbind" \ + "ip link set enp1s0 down" \ + "dpdk-devbind.py --bind=vfio-pci 0000:00:06.0" \ + "$CMD_LOG" + +# --------------------------------------------------------------------------- +# Case 2: ensure_hugetlbfs_mount mounts /mnt/huge1g with pagesize=1G. +# --------------------------------------------------------------------------- +case_dir2="$WORK_ROOT/case-mount-1g" +mkdir -p "$case_dir2" +CMD_LOG2="$case_dir2/cmd.log" +STUB_DIR2="$case_dir2/stubs" +MOUNT_POINT2="$case_dir2/huge1g" +prepare_stubs "$STUB_DIR2" "$CMD_LOG2" + +( + export PATH="$STUB_DIR2:$PATH" + export ANYSCAN_DPDK_LOAD_ONLY=1 + # shellcheck disable=SC1090 + source "$TARGET_SCRIPT" + ensure_hugetlbfs_mount "1G" "$MOUNT_POINT2" +) >"$case_dir2/stdout.log" 2>"$case_dir2/stderr.log" + +assert_log_contains \ + "ensure_hugetlbfs_mount mounts hugetlbfs at the target with pagesize=1G" \ + "mount -t hugetlbfs -o pagesize=1G nodev $MOUNT_POINT2" \ + "$CMD_LOG2" + +# --------------------------------------------------------------------------- +# Case 3: ensure_hugetlbfs_mount is idempotent — already mounted as +# hugetlbfs pagesize=1G → no-op. +# --------------------------------------------------------------------------- +case_dir3="$WORK_ROOT/case-mount-idempotent" +mkdir -p "$case_dir3" +CMD_LOG3="$case_dir3/cmd.log" +STUB_DIR3="$case_dir3/stubs" +MOUNT_POINT3="$case_dir3/huge1g" +prepare_stubs "$STUB_DIR3" "$CMD_LOG3" "hugetlbfs-1g" + +( + export PATH="$STUB_DIR3:$PATH" + export ANYSCAN_DPDK_LOAD_ONLY=1 + # shellcheck disable=SC1090 + source "$TARGET_SCRIPT" + ensure_hugetlbfs_mount "1G" "$MOUNT_POINT3" +) >"$case_dir3/stdout.log" 2>"$case_dir3/stderr.log" + +assert_log_not_contains \ + "ensure_hugetlbfs_mount is no-op when already hugetlbfs pagesize=1G" \ + "mount -t hugetlbfs" \ + "$CMD_LOG3" + +# --------------------------------------------------------------------------- +# Case 4: ensure_hugetlbfs_mount empty path → no-op. +# --------------------------------------------------------------------------- +case_dir4="$WORK_ROOT/case-mount-disabled" +mkdir -p "$case_dir4" +CMD_LOG4="$case_dir4/cmd.log" +STUB_DIR4="$case_dir4/stubs" +prepare_stubs "$STUB_DIR4" "$CMD_LOG4" + +( + export PATH="$STUB_DIR4:$PATH" + export ANYSCAN_DPDK_LOAD_ONLY=1 + # shellcheck disable=SC1090 + source "$TARGET_SCRIPT" + ensure_hugetlbfs_mount "1G" "" +) >"$case_dir4/stdout.log" 2>"$case_dir4/stderr.log" + +assert_log_not_contains \ + "ensure_hugetlbfs_mount empty mount path is no-op" \ + "mount" \ + "$CMD_LOG4" + +# --------------------------------------------------------------------------- +# Case 5: bdf_to_iface returns the iface for a fake /sys hierarchy. +# Builds a temp /sys-like tree and points readlink at it. +# --------------------------------------------------------------------------- +case_dir5="$WORK_ROOT/case-bdf-to-iface" +fake_sys="$case_dir5/sys/bus/pci/devices/0000:00:06.0/net/enp42s0" +mkdir -p "$fake_sys" + +# bdf_to_iface walks /sys/bus/pci/devices//net/. We override that +# path lookup via a symlink trick: chroot is overkill; instead, run +# bdf_to_iface in a subshell with the function temporarily redefined to +# accept a custom prefix — but it doesn't take one. Easier path: redefine +# `bdf_to_iface` after sourcing using the real function body but with +# the /sys/... path replaced by our $case_dir5/sys/... prefix. + +( + export PATH="$STUB_DIR4:$PATH" # any stub dir works here + export ANYSCAN_DPDK_LOAD_ONLY=1 + # shellcheck disable=SC1090 + source "$TARGET_SCRIPT" + # Re-define with the test prefix; same logic, different sysfs root. + case_dir_for_bdf="$case_dir5" + bdf_to_iface() { + local bdf="$1" + local netdir="$case_dir_for_bdf/sys/bus/pci/devices/$bdf/net" + if [ ! -d "$netdir" ]; then + return 0 + fi + local entry + for entry in "$netdir"/*; do + [ -e "$entry" ] || continue + basename "$entry" + return 0 + done + return 0 + } + bdf_result="$(bdf_to_iface 0000:00:06.0)" + if [ "$bdf_result" = "enp42s0" ]; then + printf 'CASE5_PASS\n' + else + printf 'CASE5_FAIL got=%s\n' "$bdf_result" + fi +) >"$case_dir5/result.log" 2>"$case_dir5/stderr.log" || true + +if grep -q '^CASE5_PASS$' "$case_dir5/result.log" 2>/dev/null; then + note_pass "bdf_to_iface returns the iface for a populated /sys/bus/pci/devices//net/" +else + note_fail "bdf_to_iface returns the iface for a populated /sys/bus/pci/devices//net/" \ + "$(cat "$case_dir5/result.log" 2>/dev/null || echo missing)" +fi + +# Also check the negative case: missing net/ dir → empty result. +case_dir6="$WORK_ROOT/case-bdf-to-iface-empty" +mkdir -p "$case_dir6/sys/bus/pci/devices/0000:00:07.0" +( + export PATH="$STUB_DIR4:$PATH" + export ANYSCAN_DPDK_LOAD_ONLY=1 + # shellcheck disable=SC1090 + source "$TARGET_SCRIPT" + case_dir_for_bdf="$case_dir6" + bdf_to_iface() { + local bdf="$1" + local netdir="$case_dir_for_bdf/sys/bus/pci/devices/$bdf/net" + if [ ! -d "$netdir" ]; then + return 0 + fi + local entry + for entry in "$netdir"/*; do + [ -e "$entry" ] || continue + basename "$entry" + return 0 + done + return 0 + } + bdf_result="$(bdf_to_iface 0000:00:07.0)" + if [ -z "$bdf_result" ]; then + printf 'CASE6_PASS\n' + else + printf 'CASE6_FAIL got=%s\n' "$bdf_result" + fi +) >"$case_dir6/result.log" 2>"$case_dir6/stderr.log" || true + +if grep -q '^CASE6_PASS$' "$case_dir6/result.log" 2>/dev/null; then + note_pass "bdf_to_iface returns empty when /sys/bus/pci/devices//net/ is missing" +else + note_fail "bdf_to_iface returns empty when /sys/bus/pci/devices//net/ is missing" \ + "$(cat "$case_dir6/result.log" 2>/dev/null || echo missing)" +fi + +printf '\n' +printf 'PASS: %d\n' "$PASS" +printf 'FAIL: %d\n' "$FAIL" + +if [ "$FAIL" -gt 0 ]; then + exit 1 +fi