From 6ee5be782763ec4704f45b1333a181bfb1340c03 Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Sat, 1 Aug 2026 22:10:30 -0400 Subject: [PATCH] fix(cdi): expose compat32 NVIDIA libraries Signed-off-by: Eli Bosley --- nvidia-driver.plg | 173 ++++++++++++++- packages/nvidia-driver-2026.08.01.txz | Bin 0 -> 40116 bytes packages/nvidia-driver-2026.08.01.txz.md5 | 1 + tests/generate-cdi-test.sh | 248 ++++++++++++++++++++++ tests/probe-compat32-vulkan.sh | 46 ++++ 5 files changed, 462 insertions(+), 6 deletions(-) create mode 100644 packages/nvidia-driver-2026.08.01.txz create mode 100644 packages/nvidia-driver-2026.08.01.txz.md5 create mode 100755 tests/generate-cdi-test.sh create mode 100755 tests/probe-compat32-vulkan.sh diff --git a/nvidia-driver.plg b/nvidia-driver.plg index 4961258..a453a4d 100644 --- a/nvidia-driver.plg +++ b/nvidia-driver.plg @@ -2,7 +2,7 @@ - + @@ -16,6 +16,9 @@ +###2026.08.01 +- Temporarily add the host's 32-bit NVIDIA libraries and aliases when nvidia-ctk omits them + ###2026.06.07 - Fix for missing link @@ -329,10 +332,168 @@ fi } generate_cdi() { - nvidia-ctk cdi generate --output=/etc/cdi/nvidia.yaml >/dev/null 2>&1 - if ! grep -q '/usr/lib/x86_64-linux-gnu/gbm/nvidia-drm_gbm.so' /etc/cdi/nvidia.yaml ; then - sed -i -E 's#^([[:space:]]*)- \.\./libnvidia-allocator\.so\.1::/usr/lib64/gbm/nvidia-drm_gbm\.so[[:space:]]*$#\1- ../libnvidia-allocator.so.1::/usr/lib64/gbm/nvidia-drm_gbm.so\n\1- --link\n\1- /usr/lib64/libnvidia-allocator.so.1::/usr/lib/x86_64-linux-gnu/gbm/nvidia-drm_gbm.so#' /etc/cdi/nvidia.yaml + local cdi_path="${NVIDIA_CDI_PATH:-/etc/cdi/nvidia.yaml}" + local staging generated transformed compat_output compat_json folders_json + local library canonical candidate header folder scanned target + local -a compat_libraries=("") + local -a compat_folders=("") + local -a compat_targets=("") + local -a scanned_folders=("") + + if ! mkdir -p "$(dirname "${cdi_path}")" ; then + echo "Failed to create NVIDIA CDI directory" >&2 + return 1 + fi + + staging="$(mktemp -d "${cdi_path}.stage.XXXXXX")" || return 1 + generated="${staging}/generated.json" + transformed="${staging}/transformed.json" + + if ! nvidia-ctk cdi generate --format=json --output="${generated}" >/dev/null 2>&1 ; then + echo "Failed to generate NVIDIA CDI specification" >&2 + rm -f "${generated}" "${transformed}" + rmdir "${staging}" 2>/dev/null || true + return 1 + fi + + # nvidia-ctk currently omits compat32 libraries from CDI generation. Keep the + # additions idempotent so this becomes a no-op when the upstream fix lands: + # https://github.com/NVIDIA/nvidia-container-toolkit/pull/1968 + if ! compat_output="$(nvidia-container-cli list --libraries --compat32 2>/dev/null)" ; then + echo "Failed to discover NVIDIA compat32 libraries" >&2 + rm -f "${generated}" "${transformed}" + rmdir "${staging}" 2>/dev/null || true + return 1 fi + + while IFS= read -r library ; do + [ -n "${library}" ] || continue + + if [[ "${library}" != /* || ! -r "${library}" ]]; then + echo "Invalid NVIDIA compat32 library: ${library}" >&2 + rm -f "${generated}" "${transformed}" + rmdir "${staging}" 2>/dev/null || true + return 1 + fi + + header="$(od -An -t x1 -N5 "${library}" 2>/dev/null | tr -d ' \n')" + if [ "${header}" == "7f454c4602" ]; then + continue + fi + if [ "${header}" != "7f454c4601" ]; then + echo "Invalid ELF library reported by NVIDIA compat32 discovery: ${library}" >&2 + rm -f "${generated}" "${transformed}" + rmdir "${staging}" 2>/dev/null || true + return 1 + fi + + if ! canonical="$(readlink -f "${library}" 2>/dev/null)" || [ -z "${canonical}" ]; then + echo "Unable to resolve NVIDIA compat32 library: ${library}" >&2 + rm -f "${generated}" "${transformed}" + rmdir "${staging}" 2>/dev/null || true + return 1 + fi + + compat_libraries+=("${library}") + compat_folders+=("$(dirname "${library}")") + compat_targets+=("${canonical}") + done <<< "${compat_output}" + + for folder in "${compat_folders[@]}" ; do + [ -n "${folder}" ] || continue + scanned="false" + for target in "${scanned_folders[@]}" ; do + if [ "${folder}" == "${target}" ]; then + scanned="true" + break + fi + done + [ "${scanned}" == "false" ] || continue + scanned_folders+=("${folder}") + + for candidate in "${folder}"/* ; do + [ -L "${candidate}" ] || continue + if ! canonical="$(readlink -f "${candidate}" 2>/dev/null)" || [ -z "${canonical}" ]; then + continue + fi + + for target in "${compat_targets[@]}" ; do + [ -n "${target}" ] || continue + if [ "${canonical}" == "${target}" ]; then + compat_libraries+=("${candidate}") + break + fi + done + done + done + + compat_json="$(printf '%s\n' "${compat_libraries[@]}" | jq -Rsc 'split("\n") | map(select(length > 0)) | unique | sort')" + folders_json="$(printf '%s\n' "${compat_folders[@]}" | jq -Rsc 'split("\n") | map(select(length > 0)) | unique | sort')" + + if ! jq -e --argjson compat "${compat_json}" --argjson folders "${folders_json}" ' + def valid_spec: + .kind == "nvidia.com/gpu" and + (.devices | type == "array") and + (.containerEdits | type == "object") and + ((.containerEdits.mounts // []) | type == "array") and + ((.containerEdits.hooks // []) | type == "array") and + all(.containerEdits.hooks[]?; type == "object" and (.args | type == "array")); + def is_ldcache: + .args[0:2] == ["nvidia-cdi-hook", "update-ldcache"]; + def has_folder($args; $folder): + [range(0; $args | length) as $index | + select($args[$index] == "--folder" and $args[$index + 1] == $folder)] | + length > 0; + def add_folders($folders): + reduce $folders[] as $folder (.; + if has_folder(.args; $folder) then . + else .args += ["--folder", $folder] + end); + def gbm_source: + "../libnvidia-allocator.so.1::/usr/lib64/gbm/nvidia-drm_gbm.so"; + def gbm_target: + "/usr/lib64/libnvidia-allocator.so.1::/usr/lib/x86_64-linux-gnu/gbm/nvidia-drm_gbm.so"; + + if (valid_spec | not) then error("invalid NVIDIA CDI specification") else . end | + .containerEdits.mounts = (.containerEdits.mounts // []) | + .containerEdits.hooks = (.containerEdits.hooks // []) | + reduce $compat[] as $path (.; + if any(.containerEdits.mounts[]?; + .hostPath == $path and .containerPath == $path) then . + else .containerEdits.mounts += [{ + hostPath: $path, + containerPath: $path, + options: ["ro", "nosuid", "nodev", "rbind", "rprivate"] + }] + end) | + if ($folders | length) == 0 then . + elif any(.containerEdits.hooks[]; is_ldcache) then + .containerEdits.hooks |= map(if is_ldcache then add_folders($folders) else . end) + else error("NVIDIA CDI update-ldcache hook is missing") + end | + if any(.containerEdits.hooks[].args[]; . == gbm_target) then . + else + .containerEdits.hooks |= map( + if (.args | index(gbm_source)) != null then + .args += ["--link", gbm_target] + else . end) + end + ' "${generated}" >"${transformed}" ; then + echo "Failed to augment NVIDIA CDI specification" >&2 + rm -f "${generated}" "${transformed}" + rmdir "${staging}" 2>/dev/null || true + return 1 + fi + + if ! mv -f "${transformed}" "${cdi_path}" ; then + echo "Failed to publish NVIDIA CDI specification" >&2 + rm -f "${generated}" "${transformed}" + rmdir "${staging}" 2>/dev/null || true + return 1 + fi + + rm -f "${generated}" + rmdir "${staging}" 2>/dev/null || true } #Create settings file if not found @@ -390,10 +551,10 @@ if ! modinfo nvidia -0 >/dev/null 2>&1 ; then echo "-----------------Installing Nvidia ${OS}Driver Package v$(echo $LAT_PACKAGE | cut -d '-' -f2)-------------------" install > /dev/null activate - generate_cdi + generate_cdi || exit 1 else activate - generate_cdi + generate_cdi || exit 1 fi #Enable update check diff --git a/packages/nvidia-driver-2026.08.01.txz b/packages/nvidia-driver-2026.08.01.txz new file mode 100644 index 0000000000000000000000000000000000000000..d82c9d42ac98a04927259faae75b9babbd0a8507 GIT binary patch literal 40116 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

O73a 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 literal 0 HcmV?d00001 diff --git a/packages/nvidia-driver-2026.08.01.txz.md5 b/packages/nvidia-driver-2026.08.01.txz.md5 new file mode 100644 index 0000000..9ab50ad --- /dev/null +++ b/packages/nvidia-driver-2026.08.01.txz.md5 @@ -0,0 +1 @@ +b78519b667ec93207a04a8ffbf38496c diff --git a/tests/generate-cdi-test.sh b/tests/generate-cdi-test.sh new file mode 100755 index 0000000..398f1bc --- /dev/null +++ b/tests/generate-cdi-test.sh @@ -0,0 +1,248 @@ +#!/bin/bash + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +TEST_ROOT="$(mktemp -d)" +INSTALL_SCRIPT="${TEST_ROOT}/install.sh" +FUNCTION_SCRIPT="${TEST_ROOT}/generate-cdi.sh" + +cleanup() { + rm -rf "${TEST_ROOT}" +} + +trap cleanup EXIT + +python3 - "${REPO_ROOT}/nvidia-driver.plg" "${INSTALL_SCRIPT}" <<'PY' +import sys +import xml.etree.ElementTree as ET + +root = ET.parse(sys.argv[1]).getroot() +for file_element in root.findall("FILE"): + inline = file_element.find("INLINE") + if inline is not None and inline.text and "generate_cdi()" in inline.text: + with open(sys.argv[2], "w", encoding="utf-8") as output: + output.write(inline.text) + break +else: + raise SystemExit("generate_cdi function not found") +PY + +sed -n '/^generate_cdi() {/,/^}$/p' "${INSTALL_SCRIPT}" >"${FUNCTION_SCRIPT}" +# shellcheck source=/dev/null +source "${FUNCTION_SCRIPT}" + +write_fixture() { + local output="$1" + + jq -n \ + --arg compat_path "${MOCK_EXISTING_COMPAT_PATH:-}" \ + --arg compat_alias "${MOCK_EXISTING_COMPAT_ALIAS:-}" \ + --arg compat_folder "${MOCK_EXISTING_COMPAT_FOLDER:-}" ' + def mount($path): { + hostPath: $path, + containerPath: $path, + options: ["ro"] + }; + { + cdiVersion: "0.5.0", + kind: "nvidia.com/gpu", + devices: [{name: "GPU-test", containerEdits: {}}], + containerEdits: { + mounts: [mount("/usr/lib64/existing.so")] + + ([$compat_path, $compat_alias] | map(select(length > 0) | mount(.))), + hooks: [ + { + hookName: "createContainer", + path: "/usr/bin/nvidia-cdi-hook", + args: [ + "nvidia-cdi-hook", + "create-symlinks", + "--link", + "../libnvidia-allocator.so.1::/usr/lib64/gbm/nvidia-drm_gbm.so" + ] + }, + { + hookName: "createContainer", + path: "/usr/bin/nvidia-cdi-hook", + args: ([ + "nvidia-cdi-hook", + "update-ldcache", + "--folder", + "/usr/lib64" + ] + if $compat_folder == "" then [] else ["--folder", $compat_folder] end) + } + ] + } + }' >"${output}" +} + +nvidia-ctk() { + local argument output="" + + if [ "${MOCK_GENERATION_STATUS:-0}" -ne 0 ]; then + return "${MOCK_GENERATION_STATUS}" + fi + + for argument in "$@" ; do + case "${argument}" in + --output=*) output="${argument#--output=}" ;; + esac + done + + if [ "${MOCK_GENERATED_SPEC:-valid}" == "valid" ]; then + write_fixture "${output}" + else + printf '%s\n' "${MOCK_GENERATED_SPEC}" >"${output}" + fi +} + +nvidia-container-cli() { + if [ "${MOCK_DISCOVERY_STATUS:-0}" -ne 0 ]; then + return "${MOCK_DISCOVERY_STATUS}" + fi + + printf '%s' "${MOCK_LIBRARY_OUTPUT:-}" +} + +assert_eq() { + local expected="$1" actual="$2" message="$3" + + if [ "${expected}" != "${actual}" ]; then + echo "FAIL: ${message}: expected '${expected}', got '${actual}'" >&2 + exit 1 + fi +} + +run_positive_case() { + local case_root="${TEST_ROOT}/positive" + local lib32_dir="${case_root}/usr/lib" + local lib64_dir="${case_root}/usr/lib64" + local lib32="${lib32_dir}/libGLX_nvidia.so.595.84" + local alias="${lib32_dir}/libGLX_nvidia.so.0" + local broken_alias="${lib32_dir}/libnvidia-broken.so.0" + local lib64="${lib64_dir}/libGLX_nvidia.so.595.84" + local spec="${case_root}/nvidia.yaml" + + mkdir -p "${lib32_dir}" "${lib64_dir}" + printf '\177ELF\001fixture' >"${lib32}" + printf '\177ELF\002fixture' >"${lib64}" + ln -s "$(basename "${lib32}")" "${alias}" + ln -s "libnvidia-missing.so" "${broken_alias}" + + export NVIDIA_CDI_PATH="${spec}" + MOCK_LIBRARY_OUTPUT="${lib64}"$'\n'"${lib32}"$'\n' + MOCK_DISCOVERY_STATUS=0 + MOCK_GENERATED_SPEC=valid + generate_cdi + + assert_eq "nvidia.com/gpu" "$(jq -r '.kind' "${spec}")" "vendor kind" + assert_eq "true" "$(jq -r --arg path "${lib32}" 'any(.containerEdits.mounts[]; .hostPath == $path and .containerPath == $path)' "${spec}")" "ELF32 mount" + assert_eq "true" "$(jq -r --arg path "${alias}" 'any(.containerEdits.mounts[]; .hostPath == $path and .containerPath == $path)' "${spec}")" "ELF32 alias mount" + assert_eq "false" "$(jq -r --arg path "${broken_alias}" 'any(.containerEdits.mounts[]; .hostPath == $path)' "${spec}")" "broken alias exclusion" + assert_eq "false" "$(jq -r --arg path "${lib64}" 'any(.containerEdits.mounts[]; .hostPath == $path)' "${spec}")" "ELF64 exclusion" + assert_eq "true" "$(jq -r --arg folder "${lib32_dir}" 'any(.containerEdits.hooks[].args[]; . == $folder)' "${spec}")" "compat32 ldconfig folder" + assert_eq "true" "$(jq -r 'any(.containerEdits.hooks[].args[]; . == "/usr/lib64/libnvidia-allocator.so.1::/usr/lib/x86_64-linux-gnu/gbm/nvidia-drm_gbm.so")' "${spec}")" "GBM compatibility link" +} + +run_no_compat_case() { + local case_root="${TEST_ROOT}/no-compat" + local spec="${case_root}/nvidia.yaml" + + mkdir -p "${case_root}" + export NVIDIA_CDI_PATH="${spec}" + MOCK_LIBRARY_OUTPUT="" + MOCK_DISCOVERY_STATUS=0 + MOCK_GENERATED_SPEC=valid + generate_cdi + + assert_eq "1" "$(jq -r '.containerEdits.mounts | length' "${spec}")" "native mount preservation" + assert_eq "true" "$(jq -r 'any(.containerEdits.hooks[].args[]; . == "/usr/lib64/libnvidia-allocator.so.1::/usr/lib/x86_64-linux-gnu/gbm/nvidia-drm_gbm.so")' "${spec}")" "no-compat GBM compatibility link" +} + +run_upstream_complete_case() { + local case_root="${TEST_ROOT}/upstream-complete" + local lib32_dir="${case_root}/usr/lib" + local lib32="${lib32_dir}/libGLX_nvidia.so.595.84" + local alias="${lib32_dir}/libGLX_nvidia.so.0" + local spec="${case_root}/nvidia.yaml" + + mkdir -p "${lib32_dir}" + printf '\177ELF\001fixture' >"${lib32}" + ln -s "$(basename "${lib32}")" "${alias}" + + export NVIDIA_CDI_PATH="${spec}" + MOCK_LIBRARY_OUTPUT="${lib32}"$'\n' + MOCK_DISCOVERY_STATUS=0 + MOCK_GENERATION_STATUS=0 + MOCK_GENERATED_SPEC=valid + MOCK_EXISTING_COMPAT_PATH="${lib32}" + MOCK_EXISTING_COMPAT_ALIAS="${alias}" + MOCK_EXISTING_COMPAT_FOLDER="${lib32_dir}" + generate_cdi + MOCK_EXISTING_COMPAT_PATH="" + MOCK_EXISTING_COMPAT_ALIAS="" + MOCK_EXISTING_COMPAT_FOLDER="" + + assert_eq "1" "$(jq -r --arg path "${lib32}" '[.containerEdits.mounts[] | select(.hostPath == $path)] | length' "${spec}")" "upstream ELF32 mount is not duplicated" + assert_eq "1" "$(jq -r --arg path "${alias}" '[.containerEdits.mounts[] | select(.hostPath == $path)] | length' "${spec}")" "upstream ELF32 alias is not duplicated" + assert_eq "1" "$(jq -r --arg folder "${lib32_dir}" '[.containerEdits.hooks[] | select(.args[0:2] == ["nvidia-cdi-hook", "update-ldcache"]) | .args[] | select(. == $folder)] | length' "${spec}")" "upstream compat32 folder is not duplicated" +} + +run_preservation_case() { + local name="$1" generated_spec="$2" discovery_status="$3" + local generation_status="${4:-0}" library_output="${5:-}" + local case_root="${TEST_ROOT}/${name}" + local spec="${case_root}/nvidia.yaml" + + mkdir -p "${case_root}" + printf '%s\n' 'stable-spec' >"${spec}" + + export NVIDIA_CDI_PATH="${spec}" + MOCK_LIBRARY_OUTPUT="${library_output}" + MOCK_DISCOVERY_STATUS="${discovery_status}" + MOCK_GENERATION_STATUS="${generation_status}" + MOCK_GENERATED_SPEC="${generated_spec}" + + if generate_cdi 2>/dev/null ; then + echo "FAIL: ${name} unexpectedly succeeded" >&2 + exit 1 + fi + + assert_eq "stable-spec" "$(cat "${spec}")" "${name} preserves stable spec" +} + +run_invalid_elf_case() { + local case_root="${TEST_ROOT}/invalid-elf" + local spec="${case_root}/nvidia.yaml" + local library="${case_root}/libnvidia.so" + + mkdir -p "${case_root}" + printf '%s\n' 'stable-spec' >"${spec}" + printf '%s\n' 'not-an-elf-library' >"${library}" + + export NVIDIA_CDI_PATH="${spec}" + MOCK_LIBRARY_OUTPUT="${library}" + MOCK_DISCOVERY_STATUS=0 + MOCK_GENERATION_STATUS=0 + MOCK_GENERATED_SPEC=valid + + if generate_cdi 2>/dev/null ; then + echo "FAIL: invalid-elf unexpectedly succeeded" >&2 + exit 1 + fi + + assert_eq "stable-spec" "$(cat "${spec}")" "invalid-elf preserves stable spec" +} + +run_positive_case +run_no_compat_case +run_upstream_complete_case +run_preservation_case "discovery-failure" valid 17 +run_preservation_case "generation-failure" valid 0 23 +run_preservation_case "malformed-generation" '{not-json' 0 +run_preservation_case "structural-generation" '[]' 0 +run_preservation_case "missing-library" valid 0 0 "${TEST_ROOT}/missing/libnvidia.so" +run_invalid_elf_case + +echo "generate_cdi tests passed" diff --git a/tests/probe-compat32-vulkan.sh b/tests/probe-compat32-vulkan.sh new file mode 100755 index 0000000..80ebcdd --- /dev/null +++ b/tests/probe-compat32-vulkan.sh @@ -0,0 +1,46 @@ +#!/bin/bash + +set -euo pipefail + +container="${1:-}" +probe_user="${PROBE_USER:-retro}" +display="${DISPLAY:-:0}" +probe="${VULKAN_PROBE:-/home/retro/.steam/steam/steamapps/common/SteamLinuxRuntime/steam-runtime/i386/usr/bin/vkcube}" + +if [ -z "${container}" ]; then + container="$(docker ps --format '{{.Names}}' | grep -m1 '^WolfSteam_' || true)" +fi + +if [ -z "${container}" ]; then + echo "No running WolfSteam container found" >&2 + exit 2 +fi + +probe_type="$(docker exec "${container}" file "${probe}")" +if [[ "${probe_type}" != *"ELF 32-bit"* ]]; then + echo "Vulkan probe is not ELF32: ${probe_type}" >&2 + exit 2 +fi + +set +e +output="$( + timeout 20 docker exec -u "${probe_user}" -e DISPLAY="${display}" "${container}" \ + "${probe}" --c 1 --suppress_popups 2>&1 +)" +status=$? +set -e + +printf '%s\n' "${output}" + +selected="$(printf '%s\n' "${output}" | sed -n 's/^Selected GPU [^:]*: \([^,]*\).*/\1/p' | head -1)" +if [ -z "${selected}" ]; then + echo "The ELF32 Vulkan probe did not report a selected GPU (exit ${status})" >&2 + exit 1 +fi + +if [[ "${selected,,}" != *"nvidia"* ]]; then + echo "Expected the ELF32 Vulkan probe to select NVIDIA, got: ${selected}" >&2 + exit 1 +fi + +echo "ELF32 Vulkan selected NVIDIA"