From e2e658cc0382532b884c4696842c7cabce2ddb97 Mon Sep 17 00:00:00 2001 From: Michal Opala Date: Sun, 24 May 2026 18:53:49 +0200 Subject: [PATCH 1/4] F #213: Add 'set_switchdev' option to manage eswitch mode - Add 'set_switchdev' option - Manage eswitch mode in UDEV - Make VF;PF;FN detection safer (fix) - Update README.md Signed-off-by: Michal Opala --- roles/helper/pci/README.md | 34 ++++++++++++++++-------------- roles/helper/pci/tasks/devices.yml | 8 +++---- roles/helper/pci/tasks/query.yml | 3 +++ roles/helper/pci/tasks/udev.yml | 23 ++++++++++++++++++-- 4 files changed, 46 insertions(+), 22 deletions(-) diff --git a/roles/helper/pci/README.md b/roles/helper/pci/README.md index 93b4aa5..36cd4a7 100644 --- a/roles/helper/pci/README.md +++ b/roles/helper/pci/README.md @@ -11,21 +11,22 @@ N/A Role Variables -------------- -| Name | Type | Default | Description | -|-----------------------------|--------|-----------|-------------------------------------------------------------------------------------| -| `pci_devices` | `list` | `[]` | PCI devices configuration. | -| `pci_devices[].excluded` | `bool` | `false` | Do not process matching PCI devices. | -| `pci_devices[].unguarded` | `bool` | `false` | Do not protect matching PCI devices (this may cause primary NIC connectivity loss). | -| `pci_devices[].unlisted` | `bool` | `true` | Do not pass matching PCI devices to OpenNebula. | -| `pci_devices[].virtual` | `bool` | `false` | Do not fail query on missing virtual devices (SR-IOV). | -| `pci_devices[].address` | `str` | undefined | Glob PCI devices by PCI or MAC address. | -| `pci_devices[].vendor` | `str` | `*` | Glob PCI devices by PCI Vendor (if address is undefined). | -| `pci_devices[].device` | `str` | `*` | Glob PCI devices by PCI Device (if address is undefined). | -| `pci_devices[].class` | `str` | `*` | Glob PCI devices by PCI Class (if address is undefined). | -| `pci_devices[].set_counter` | `str` | undefined | Reset the "set_counter" internal counter that can be used with set_name ("{3}"). | -| `pci_devices[].set_driver` | `str` | `omit` | Use driverctl to override driver (unless "omit"). | -| `pci_devices[].set_name` | `str` | `omit` | Rename device in udev (unless "omit"). | -| `pci_devices[].set_numvfs` | `str` | `0` | Enable Virtual Functions for SR-IOV capable devices (integer >= 0 or "max"). | +| Name | Type | Default | Description | +|-------------------------------|--------|-----------|-------------------------------------------------------------------------------------| +| `pci_devices` | `list` | `[]` | PCI devices configuration. | +| `pci_devices[].excluded` | `bool` | `false` | Do not process matching PCI devices. | +| `pci_devices[].unguarded` | `bool` | `false` | Do not protect matching PCI devices (this may cause primary NIC connectivity loss). | +| `pci_devices[].unlisted` | `bool` | `true` | Do not pass matching PCI devices to OpenNebula. | +| `pci_devices[].virtual` | `bool` | `false` | Do not fail query on missing virtual devices (SR-IOV). | +| `pci_devices[].address` | `str` | undefined | Glob PCI devices by PCI or MAC address. | +| `pci_devices[].vendor` | `str` | `*` | Glob PCI devices by PCI Vendor (if address is undefined). | +| `pci_devices[].device` | `str` | `*` | Glob PCI devices by PCI Device (if address is undefined). | +| `pci_devices[].class` | `str` | `*` | Glob PCI devices by PCI Class (if address is undefined). | +| `pci_devices[].set_counter` | `str` | undefined | Reset the "set_counter" internal counter that can be used with set_name ("{3}"). | +| `pci_devices[].set_driver` | `str` | `omit` | Use driverctl to override driver (unless "omit"). | +| `pci_devices[].set_name` | `str` | `omit` | Rename device in udev (unless "omit"). | +| `pci_devices[].set_numvfs` | `str` | `0` | Enable Virtual Functions for SR-IOV capable devices (integer >= 0 or "max"). | +| `pci_devices[].set_switchdev` | `bool` | `false` | Toggle legacy/switchdev modes for SR-IOV capable devices. | Dependencies ------------ @@ -138,11 +139,12 @@ Example Playbook - hosts: node vars: pci_devices: - # Enable all available VFs for all existing Mellanox PFs. + # Enable all available VFs for all existing Mellanox PFs, then enable switchdev mode. - vendor: "15b3" device: "1015" class: "0200" set_numvfs: max + set_switchdev: true # Rename all existing Mellanox VFs using custom counter (starting from 1), then pass them to OpenNebula. - vendor: "15b3" diff --git a/roles/helper/pci/tasks/devices.yml b/roles/helper/pci/tasks/devices.yml index fe133fb..4717166 100644 --- a/roles/helper/pci/tasks/devices.yml +++ b/roles/helper/pci/tasks/devices.yml @@ -5,9 +5,9 @@ vars: _common: [bash, coreutils, driverctl, findutils, grep, pciutils] _specific: - Debian: [] - RedHat: [] - Suse: [] + Debian: [iproute2] + RedHat: [iproute] + Suse: [iproute2] register: package until: package is success retries: 12 @@ -141,7 +141,7 @@ file: "{{ role_path }}/tasks/query.yml" when: shell_revert_drivers is changed - - name: (Re)Enable VFs + - name: Enable VFs ansible.builtin.shell: cmd: | set -x -o errexit -o pipefail diff --git a/roles/helper/pci/tasks/query.yml b/roles/helper/pci/tasks/query.yml index 07266fe..dbb48fc 100644 --- a/roles/helper/pci/tasks/query.yml +++ b/roles/helper/pci/tasks/query.yml @@ -22,6 +22,7 @@ echo -e 'Set_driver:\t{{ v.set_driver | d('omit') }}' echo -e 'Set_name:\t{{ v.set_name | d('omit') }}' echo -e 'Set_numvfs:\t{{ v.set_numvfs | d(0) }}' + echo -e 'Set_switchdev:\t{{ v.set_switchdev | d(false) | bool | ternary('yes', 'no') }}' echo -e 'Virtual:\t{{ v.virtual | d(false) | bool | ternary('yes', 'no') }}' echo -e 'Unlisted:\t{{ v.unlisted | d(true) | bool | ternary('yes', 'no') }}' echo -e 'Unguarded:\t{{ v.unguarded | d(false) | bool | ternary('yes', 'no') }}' @@ -52,6 +53,7 @@ echo -e 'Set_driver:\t{{ v.set_driver | d('omit') }}' echo -e 'Set_name:\t{{ v.set_name | d('omit') }}' echo -e 'Set_numvfs:\t{{ v.set_numvfs | d(0) }}' + echo -e 'Set_switchdev:\t{{ v.set_switchdev | d(false) | bool | ternary('yes', 'no') }}' echo -e 'Virtual:\t{{ v.virtual | d(false) | bool | ternary('yes', 'no') }}' echo -e 'Unlisted:\t{{ v.unlisted | d(true) | bool | ternary('yes', 'no') }}' echo -e 'Unguarded:\t{{ v.unguarded | d(false) | bool | ternary('yes', 'no') }}' @@ -89,6 +91,7 @@ echo -e 'Set_driver:\t{{ v.set_driver | d('omit') }}' echo -e 'Set_name:\t{{ v.set_name | d('omit') }}' echo -e 'Set_numvfs:\t{{ v.set_numvfs | d(0) }}' + echo -e 'Set_switchdev:\t{{ v.set_switchdev | d(false) | bool | ternary('yes', 'no') }}' echo -e 'Virtual:\t{{ v.virtual | d(false) | bool | ternary('yes', 'no') }}' echo -e 'Unlisted:\t{{ v.unlisted | d(true) | bool | ternary('yes', 'no') }}' echo -e 'Unguarded:\t{{ v.unguarded | d(false) | bool | ternary('yes', 'no') }}' diff --git a/roles/helper/pci/tasks/udev.yml b/roles/helper/pci/tasks/udev.yml index 9aef27e..390cff8 100644 --- a/roles/helper/pci/tasks/udev.yml +++ b/roles/helper/pci/tasks/udev.yml @@ -16,6 +16,9 @@ _sriov_devices: >- {{ _lspci_devices | selectattr('Ecap_sriov', '==', 'yes') }} + _switchdev_devices: >- + {{ _sriov_devices | selectattr('Set_switchdev', '==', 'yes') }} + _all: >- {{ _lspci_devices | selectattr('Set_name', 'defined') | rejectattr('Set_name', '==', 'omit') @@ -39,14 +42,22 @@ {{ shell_vf_to_pf_fn.stdout_lines | d([]) | map('split', ';') | items2dict(key_name=0, value_name=2) }} + + _pf_to_first_vf: >- + {{ shell_vf_to_pf_fn.stdout_lines | d([]) + | map('split', ';') + | selectattr(2, '==', '0') + | items2dict(key_name=1, value_name=0) }} block: - name: Scan /sys/bus/pci/devices/*/virtfn* (SR-IOV) ansible.builtin.shell: cmd: | set -o errexit -o pipefail {% for v in _sriov_devices %} - find -P "/sys/bus/pci/devices/{{ v.Slot }}/" -maxdepth 1 -type l -name 'virtfn*' -printf '%l/%P\n' | while IFS='/' read -r _ VF FN; do - echo "$VF;{{ v.Slot }};${FN#virtfn}" + find -P '/sys/bus/pci/devices/{{ v.Slot }}/' -maxdepth 1 -type l -name 'virtfn*' -printf '%l|%P\n' | while IFS='|' read -r LINK NAME; do + VF="$(basename $LINK)" + FN="${NAME#virtfn}" + echo "$VF;{{ v.Slot }};$FN" done {% endfor %} executable: /bin/bash @@ -63,6 +74,14 @@ mode: u=rw,go=r loop_control: { label: "{{ item.dest }}" } loop: + - dest: /etc/udev/rules.d/98-eswitch.rules + content: | + # managed by one-deploy + # --- PCI + {% for v in _switchdev_devices | selectattr('Slot', 'in', _pf_to_first_vf) %} + SUBSYSTEM=="pci", ACTION=="add", ENV{ID_PATH}=="pci-{{ _pf_to_first_vf.get(v.Slot) }}", \ + RUN+="/usr/sbin/devlink dev eswitch set 'pci/{{ v.Slot }}' mode switchdev" + {% endfor %} - dest: /etc/udev/rules.d/99-rename.rules content: | # managed by one-deploy From 84bd83c141bd786beadd5df9aa879b34109d0ad8 Mon Sep 17 00:00:00 2001 From: Michal Opala Date: Mon, 25 May 2026 17:25:46 +0200 Subject: [PATCH 2/4] F #213: Move switchdev management to sriov-enable@.service - Migrate bash/systemd code to 'template' tasks - Extend sriov-manage.sh script to accept switchdev option - Revert UDEV attempt Signed-off-by: Michal Opala --- roles/helper/pci/tasks/devices.yml | 42 +++--------- roles/helper/pci/tasks/udev.yml | 17 ----- .../pci/templates/sriov-enable@.service.jinja | 13 ++++ .../pci/templates/sriov-manage.sh.jinja | 64 +++++++++++++++++++ 4 files changed, 87 insertions(+), 49 deletions(-) create mode 100644 roles/helper/pci/templates/sriov-enable@.service.jinja create mode 100644 roles/helper/pci/templates/sriov-manage.sh.jinja diff --git a/roles/helper/pci/tasks/devices.yml b/roles/helper/pci/tasks/devices.yml index 4717166..2264df6 100644 --- a/roles/helper/pci/tasks/devices.yml +++ b/roles/helper/pci/tasks/devices.yml @@ -57,51 +57,25 @@ - when: lspci_devices | count > 0 block: - name: Render sriov-enable service unit - ansible.builtin.copy: + ansible.builtin.template: dest: "{{ item.dest }}" + src: "{{ item.src }}" owner: 0 group: 0 mode: "{{ item.mode }}" - content: "{{ item.cmd }}" loop: - dest: /usr/local/sbin/sriov-manage.sh + src: sriov-manage.sh.jinja mode: u=rwx,go=rx - cmd: | - #!/usr/bin/env bash - set -eu - - # Split the input (e.g., 0000:27:00.0-4) into address and count - IFS='-' read -r PCI_ADDR VF_COUNT <<< "$1" - - # Verify the device exists before writing - if [[ -d "/sys/bus/pci/devices/$PCI_ADDR" ]]; then - echo "Setting $VF_COUNT VFs on $PCI_ADDR" - echo "$VF_COUNT" > "/sys/bus/pci/devices/$PCI_ADDR/sriov_numvfs" - else - echo "Error: Device $PCI_ADDR not found" >&2 - exit 1 - fi - dest: /etc/systemd/system/sriov-enable@.service + src: sriov-enable@.service.jinja mode: u=rw,go=r - cmd: | - [Unit] - Description=Enable SR-IOV VFs on %I - After=network-pre.target - - [Service] - Type=oneshot - RemainAfterExit=yes - # %I is replaced by the string after the @ in the command - ExecStart=/usr/local/sbin/sriov-manage.sh %i - - [Install] - WantedBy=multi-user.target - register: copy_sriov_enable_service + register: template_sriov_enable_service - name: Reload systemd ansible.builtin.systemd_service: daemon_reload: true - when: copy_sriov_enable_service is changed + when: template_sriov_enable_service is changed - name: Override drivers (revert when needed) ansible.builtin.shell: @@ -156,7 +130,11 @@ {% endif %} if [[ -n "$SRIOV_NUMVFS" ]]; then ALL="$(systemctl show --all -P Id 'sriov-enable@{{ v.Slot }}-*.service' | grep -E -v '^\s*$')" ||: + {% if v.Set_switchdev == 'yes' %} + TO_ENABLE="sriov-enable@{{ v.Slot }}-$SRIOV_NUMVFS-switchdev.service" + {% else %} TO_ENABLE="sriov-enable@{{ v.Slot }}-$SRIOV_NUMVFS.service" + {% endif %} if [[ "$(head -n1 '/sys/bus/pci/devices/{{ v.Slot }}/sriov_numvfs')" == 0 ]]; then # This handles the invalid case where VFs are no longer enabled but service is still active TO_DISABLE="$ALL" diff --git a/roles/helper/pci/tasks/udev.yml b/roles/helper/pci/tasks/udev.yml index 390cff8..5eb8ee7 100644 --- a/roles/helper/pci/tasks/udev.yml +++ b/roles/helper/pci/tasks/udev.yml @@ -16,9 +16,6 @@ _sriov_devices: >- {{ _lspci_devices | selectattr('Ecap_sriov', '==', 'yes') }} - _switchdev_devices: >- - {{ _sriov_devices | selectattr('Set_switchdev', '==', 'yes') }} - _all: >- {{ _lspci_devices | selectattr('Set_name', 'defined') | rejectattr('Set_name', '==', 'omit') @@ -42,12 +39,6 @@ {{ shell_vf_to_pf_fn.stdout_lines | d([]) | map('split', ';') | items2dict(key_name=0, value_name=2) }} - - _pf_to_first_vf: >- - {{ shell_vf_to_pf_fn.stdout_lines | d([]) - | map('split', ';') - | selectattr(2, '==', '0') - | items2dict(key_name=1, value_name=0) }} block: - name: Scan /sys/bus/pci/devices/*/virtfn* (SR-IOV) ansible.builtin.shell: @@ -74,14 +65,6 @@ mode: u=rw,go=r loop_control: { label: "{{ item.dest }}" } loop: - - dest: /etc/udev/rules.d/98-eswitch.rules - content: | - # managed by one-deploy - # --- PCI - {% for v in _switchdev_devices | selectattr('Slot', 'in', _pf_to_first_vf) %} - SUBSYSTEM=="pci", ACTION=="add", ENV{ID_PATH}=="pci-{{ _pf_to_first_vf.get(v.Slot) }}", \ - RUN+="/usr/sbin/devlink dev eswitch set 'pci/{{ v.Slot }}' mode switchdev" - {% endfor %} - dest: /etc/udev/rules.d/99-rename.rules content: | # managed by one-deploy diff --git a/roles/helper/pci/templates/sriov-enable@.service.jinja b/roles/helper/pci/templates/sriov-enable@.service.jinja new file mode 100644 index 0000000..53a2d84 --- /dev/null +++ b/roles/helper/pci/templates/sriov-enable@.service.jinja @@ -0,0 +1,13 @@ +# managed by one-deploy; vim:syn=systemd: +[Unit] +Description=Enable SR-IOV VFs on %I +After=network-pre.target + +[Service] +Type=oneshot +RemainAfterExit=yes +# %I is replaced by the string after the @ in the command +ExecStart=/usr/local/sbin/sriov-manage.sh %i + +[Install] +WantedBy=multi-user.target diff --git a/roles/helper/pci/templates/sriov-manage.sh.jinja b/roles/helper/pci/templates/sriov-manage.sh.jinja new file mode 100644 index 0000000..73eedbb --- /dev/null +++ b/roles/helper/pci/templates/sriov-manage.sh.jinja @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# managed by one-deploy; vim:syn=bash: + +set -o errexit -o nounset -o pipefail; shopt -s nullglob + +type -p basename devlink realpath &>/dev/null + +# Split the input (e.g., 0000:27:00.0-4-switchdev) into address, count and options +IFS='-' read -r PCI_ADDR VF_COUNT OPTIONS <<< "$1" +IFS='-' read -ra OPTIONS <<< "$OPTIONS" + +# Verify the device exists before writing +if ! [[ -d "/sys/bus/pci/devices/$PCI_ADDR/" ]]; then + echo "ERROR: No such device: $PCI_ADDR" >&2 + exit 1 +fi + +# Handle extra options before writing +for OPT in "${OPTIONS[@]}"; do + case "$OPT" in + switchdev) + if ! ESWITCH_SHOW="$(devlink dev eswitch show "pci/$PCI_ADDR")"; then + echo "ERROR: Not an eswitch device: $PCI_ADDR" >&2 + exit 1 + fi + + if [[ "$ESWITCH_SHOW" =~ (^|[[:space:]])mode[[:space:]]+switchdev([[:space:]]|$) ]]; then + echo "WARNING: Nothing to do for: $OPT" >&2 + continue + fi + + # Collect info about drivers in use + declare -A VF_DRIVER_MAP + for VF_PATH in /sys/bus/pci/devices/$PCI_ADDR/virtfn*; do + if ! VF_DRIVER="$(realpath -e $VF_PATH/driver)"; then + echo "WARNING: No driver found: $VF_PATH/driver" >&2 + continue + fi + VF_PCI_ADDR="$(basename $(realpath $VF_PATH))" + VF_DRIVER_MAP["$VF_PCI_ADDR"]="$VF_DRIVER" + done + + for VF_PCI_ADDR in "${!VF_DRIVER_MAP[@]}"; do + echo "Unbinding $VF_PCI_ADDR from ${VF_DRIVER_MAP["$VF_PCI_ADDR"]}" >&2 + echo "$VF_PCI_ADDR" >"${VF_DRIVER_MAP["$VF_PCI_ADDR"]}/unbind" + done + + echo "Enabling switchdev mode on $PCI_ADDR" >&2 + devlink dev eswitch set "pci/$PCI_ADDR" mode switchdev + + for VF_PCI_ADDR in "${!VF_DRIVER_MAP[@]}"; do + echo "Binding $VF_PCI_ADDR to ${VF_DRIVER_MAP["$VF_PCI_ADDR"]}" >&2 + echo "$VF_PCI_ADDR" >"${VF_DRIVER_MAP["$VF_PCI_ADDR"]}/bind" + done + ;; + *) + echo "ERROR: Unrecognized option: $OPT" >&2 + exit 1 + ;; + esac +done + +echo "Setting $VF_COUNT VFs on $PCI_ADDR" +echo "$VF_COUNT" >"/sys/bus/pci/devices/$PCI_ADDR/sriov_numvfs" From 8b4c57d2b2418eaa965833e9fcb55ef941384906 Mon Sep 17 00:00:00 2001 From: Michal Opala Date: Tue, 26 May 2026 12:48:42 +0200 Subject: [PATCH 3/4] F #213: Do not rely on findutils Signed-off-by: Michal Opala --- roles/helper/pci/tasks/devices.yml | 2 +- roles/helper/pci/tasks/udev.yml | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/roles/helper/pci/tasks/devices.yml b/roles/helper/pci/tasks/devices.yml index 2264df6..966cd17 100644 --- a/roles/helper/pci/tasks/devices.yml +++ b/roles/helper/pci/tasks/devices.yml @@ -3,7 +3,7 @@ ansible.builtin.package: name: "{{ _common + _specific[ansible_os_family] }}" vars: - _common: [bash, coreutils, driverctl, findutils, grep, pciutils] + _common: [bash, coreutils, driverctl, grep, pciutils] _specific: Debian: [iproute2] RedHat: [iproute] diff --git a/roles/helper/pci/tasks/udev.yml b/roles/helper/pci/tasks/udev.yml index 5eb8ee7..bbc4afc 100644 --- a/roles/helper/pci/tasks/udev.yml +++ b/roles/helper/pci/tasks/udev.yml @@ -31,28 +31,28 @@ {{ _all | selectattr('IOMMUGroup', 'defined') }} _vf_to_pf: >- - {{ shell_vf_to_pf_fn.stdout_lines | d([]) - | map('split', ';') - | items2dict(key_name=0, value_name=1) }} + {{ shell_pf_vf_fn.stdout_lines | d([]) + | map('split', ';') + | items2dict(key_name=1, value_name=0) }} _vf_to_fn: >- - {{ shell_vf_to_pf_fn.stdout_lines | d([]) - | map('split', ';') - | items2dict(key_name=0, value_name=2) }} + {{ shell_pf_vf_fn.stdout_lines | d([]) + | map('split', ';') + | items2dict(key_name=1, value_name=2) }} block: - name: Scan /sys/bus/pci/devices/*/virtfn* (SR-IOV) ansible.builtin.shell: cmd: | - set -o errexit -o pipefail + set -o errexit -o pipefail; shopt -s nullglob {% for v in _sriov_devices %} - find -P '/sys/bus/pci/devices/{{ v.Slot }}/' -maxdepth 1 -type l -name 'virtfn*' -printf '%l|%P\n' | while IFS='|' read -r LINK NAME; do - VF="$(basename $LINK)" - FN="${NAME#virtfn}" - echo "$VF;{{ v.Slot }};$FN" + for VF_PATH in '/sys/bus/pci/devices/{{ v.Slot }}/virtfn'*; do + VF="$(basename $(realpath $VF_PATH))" + FN="$(basename $VF_PATH)" + echo "{{ v.Slot }};$VF;${FN#virtfn}" done {% endfor %} executable: /bin/bash - register: shell_vf_to_pf_fn + register: shell_pf_vf_fn changed_when: false when: _sriov_devices | count > 0 From f5ac99b600d5de6b0d345fe9eb0174d33d8809a7 Mon Sep 17 00:00:00 2001 From: Michal Opala Date: Tue, 26 May 2026 13:03:01 +0200 Subject: [PATCH 4/4] F #213: Apply suggestions from shellcheck Signed-off-by: Michal Opala --- roles/helper/pci/tasks/udev.yml | 4 ++-- roles/helper/pci/templates/sriov-manage.sh.jinja | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/roles/helper/pci/tasks/udev.yml b/roles/helper/pci/tasks/udev.yml index bbc4afc..b4773f0 100644 --- a/roles/helper/pci/tasks/udev.yml +++ b/roles/helper/pci/tasks/udev.yml @@ -46,8 +46,8 @@ set -o errexit -o pipefail; shopt -s nullglob {% for v in _sriov_devices %} for VF_PATH in '/sys/bus/pci/devices/{{ v.Slot }}/virtfn'*; do - VF="$(basename $(realpath $VF_PATH))" - FN="$(basename $VF_PATH)" + VF="$(basename "$(realpath "$VF_PATH")")" + FN="$(basename "$VF_PATH")" echo "{{ v.Slot }};$VF;${FN#virtfn}" done {% endfor %} diff --git a/roles/helper/pci/templates/sriov-manage.sh.jinja b/roles/helper/pci/templates/sriov-manage.sh.jinja index 73eedbb..eb3ee34 100644 --- a/roles/helper/pci/templates/sriov-manage.sh.jinja +++ b/roles/helper/pci/templates/sriov-manage.sh.jinja @@ -31,12 +31,12 @@ for OPT in "${OPTIONS[@]}"; do # Collect info about drivers in use declare -A VF_DRIVER_MAP - for VF_PATH in /sys/bus/pci/devices/$PCI_ADDR/virtfn*; do - if ! VF_DRIVER="$(realpath -e $VF_PATH/driver)"; then + for VF_PATH in "/sys/bus/pci/devices/$PCI_ADDR/virtfn"*; do + if ! VF_DRIVER="$(realpath -e "$VF_PATH/driver")"; then echo "WARNING: No driver found: $VF_PATH/driver" >&2 continue fi - VF_PCI_ADDR="$(basename $(realpath $VF_PATH))" + VF_PCI_ADDR="$(basename "$(realpath "$VF_PATH")")" VF_DRIVER_MAP["$VF_PCI_ADDR"]="$VF_DRIVER" done