diff --git a/internal/nvsandboxutils/cgo_helpers.h b/internal/nvsandboxutils/cgo_helpers.h index 23b3c256b..c460d0f5b 100644 --- a/internal/nvsandboxutils/cgo_helpers.h +++ b/internal/nvsandboxutils/cgo_helpers.h @@ -18,6 +18,7 @@ // Code generated by https://git.io/c-for-go. DO NOT EDIT. #include "nvsandboxutils.h" +#include "dlsym_static.h" #include #pragma once diff --git a/internal/nvsandboxutils/const.go b/internal/nvsandboxutils/const.go index 9e8cdf3f6..20b540f28 100644 --- a/internal/nvsandboxutils/const.go +++ b/internal/nvsandboxutils/const.go @@ -20,9 +20,10 @@ package nvsandboxutils /* -#cgo linux LDFLAGS: -Wl,--export-dynamic -Wl,--unresolved-symbols=ignore-in-object-files +#cgo linux LDFLAGS: -Wl,--export-dynamic #cgo darwin LDFLAGS: -Wl,-undefined,dynamic_lookup #include "nvsandboxutils.h" +#include "dlsym_static.h" #include #include "cgo_helpers.h" */ diff --git a/internal/nvsandboxutils/dlsym_static.c b/internal/nvsandboxutils/dlsym_static.c new file mode 100644 index 000000000..e0481b593 --- /dev/null +++ b/internal/nvsandboxutils/dlsym_static.c @@ -0,0 +1,70 @@ +/** +# Copyright 2026 NVIDIA CORPORATION +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +**/ + +#include +#include + +#include "dlsym_static.h" + +nvSandboxUtilsRet_t nvSandboxUtilsInit_dl(nvSandboxUtilsInitInput_t *input) +{ + nvSandboxUtilsRet_t (*fn)(nvSandboxUtilsInitInput_t *) = dlsym(RTLD_DEFAULT, "nvSandboxUtilsInit"); + if (fn == NULL) { + return NVSANDBOXUTILS_ERROR_LIBRARY_LOAD; + } + + return fn(input); +} + +nvSandboxUtilsRet_t nvSandboxUtilsShutdown_dl(void) +{ + nvSandboxUtilsRet_t (*fn)(void) = dlsym(RTLD_DEFAULT, "nvSandboxUtilsShutdown"); + if (fn == NULL) { + return NVSANDBOXUTILS_ERROR_LIBRARY_LOAD; + } + + return fn(); +} + +nvSandboxUtilsRet_t nvSandboxUtilsGetDriverVersion_dl(char *version, unsigned int length) +{ + nvSandboxUtilsRet_t (*fn)(char *, unsigned int) = dlsym(RTLD_DEFAULT, "nvSandboxUtilsGetDriverVersion"); + if (fn == NULL) { + return NVSANDBOXUTILS_ERROR_LIBRARY_LOAD; + } + + return fn(version, length); +} + +nvSandboxUtilsRet_t nvSandboxUtilsGetGpuResource_dl(nvSandboxUtilsGpuRes_t *request) +{ + nvSandboxUtilsRet_t (*fn)(nvSandboxUtilsGpuRes_t *) = dlsym(RTLD_DEFAULT, "nvSandboxUtilsGetGpuResource"); + if (fn == NULL) { + return NVSANDBOXUTILS_ERROR_LIBRARY_LOAD; + } + + return fn(request); +} + +nvSandboxUtilsRet_t nvSandboxUtilsGetFileContent_dl(char *filePath, char *content, unsigned int *contentSize) +{ + nvSandboxUtilsRet_t (*fn)(char *, char *, unsigned int *) = dlsym(RTLD_DEFAULT, "nvSandboxUtilsGetFileContent"); + if (fn == NULL) { + return NVSANDBOXUTILS_ERROR_LIBRARY_LOAD; + } + + return fn(filePath, content, contentSize); +} diff --git a/internal/nvsandboxutils/dlsym_static.h b/internal/nvsandboxutils/dlsym_static.h new file mode 100644 index 000000000..3278f14af --- /dev/null +++ b/internal/nvsandboxutils/dlsym_static.h @@ -0,0 +1,28 @@ +/** +# Copyright 2026 NVIDIA CORPORATION +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +**/ + +#ifndef __NVSANDBOXUTILS_DLSYM_H__ +#define __NVSANDBOXUTILS_DLSYM_H__ + +#include "nvsandboxutils.h" + +nvSandboxUtilsRet_t nvSandboxUtilsInit_dl(nvSandboxUtilsInitInput_t *input); +nvSandboxUtilsRet_t nvSandboxUtilsShutdown_dl(void); +nvSandboxUtilsRet_t nvSandboxUtilsGetDriverVersion_dl(char *version, unsigned int length); +nvSandboxUtilsRet_t nvSandboxUtilsGetGpuResource_dl(nvSandboxUtilsGpuRes_t *request); +nvSandboxUtilsRet_t nvSandboxUtilsGetFileContent_dl(char *filePath, char *content, unsigned int *contentSize); + +#endif // __NVSANDBOXUTILS_DLSYM_H__ diff --git a/internal/nvsandboxutils/gen/nvsandboxutils/nvsandboxutils.yml b/internal/nvsandboxutils/gen/nvsandboxutils/nvsandboxutils.yml index 851db7436..3146032e5 100644 --- a/internal/nvsandboxutils/gen/nvsandboxutils/nvsandboxutils.yml +++ b/internal/nvsandboxutils/gen/nvsandboxutils/nvsandboxutils.yml @@ -30,12 +30,12 @@ GENERATOR: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. - Includes: ["nvsandboxutils.h"] + Includes: ["nvsandboxutils.h", "dlsym_static.h"] FlagGroups: - - {name: "LDFLAGS", traits: ["linux"], flags: ["-Wl,--export-dynamic","-Wl,--unresolved-symbols=ignore-in-object-files"]} + - {name: "LDFLAGS", traits: ["linux"], flags: ["-Wl,--export-dynamic"]} - {name: "LDFLAGS", traits: ["darwin"], flags: ["-Wl,-undefined,dynamic_lookup"]} PARSER: - SourcesPaths: ["nvsandboxutils.h"] + SourcesPaths: ["nvsandboxutils.h", "dlsym_static.h"] TRANSLATOR: ConstRules: defines: eval @@ -62,5 +62,6 @@ TRANSLATOR: - {action: replace, from: "_t$"} - {transform: export} function: - - {action: accept, from: "^nvSandboxUtils"} + - {action: accept, from: "^nvSandboxUtils[[:alpha:]]+_dl$"} + - {action: replace, from: "_dl$"} - {transform: unexport} diff --git a/internal/nvsandboxutils/nvsandboxutils.go b/internal/nvsandboxutils/nvsandboxutils.go index 29544bc93..fb0750508 100644 --- a/internal/nvsandboxutils/nvsandboxutils.go +++ b/internal/nvsandboxutils/nvsandboxutils.go @@ -20,9 +20,10 @@ package nvsandboxutils /* -#cgo linux LDFLAGS: -Wl,--export-dynamic -Wl,--unresolved-symbols=ignore-in-object-files +#cgo linux LDFLAGS: -Wl,--export-dynamic #cgo darwin LDFLAGS: -Wl,-undefined,dynamic_lookup #include "nvsandboxutils.h" +#include "dlsym_static.h" #include #include "cgo_helpers.h" */ @@ -32,14 +33,14 @@ import "unsafe" // nvSandboxUtilsInit function as declared in nvsandboxutils/nvsandboxutils.h func nvSandboxUtilsInit(Input *InitInput) Ret { cInput, _ := (*C.nvSandboxUtilsInitInput_t)(unsafe.Pointer(Input)), cgoAllocsUnknown - __ret := C.nvSandboxUtilsInit(cInput) + __ret := C.nvSandboxUtilsInit_dl(cInput) __v := (Ret)(__ret) return __v } // nvSandboxUtilsShutdown function as declared in nvsandboxutils/nvsandboxutils.h func nvSandboxUtilsShutdown() Ret { - __ret := C.nvSandboxUtilsShutdown() + __ret := C.nvSandboxUtilsShutdown_dl() __v := (Ret)(__ret) return __v } @@ -48,7 +49,7 @@ func nvSandboxUtilsShutdown() Ret { func nvSandboxUtilsGetDriverVersion(Version *byte, Length uint32) Ret { cVersion, _ := (*C.char)(unsafe.Pointer(Version)), cgoAllocsUnknown cLength, _ := (C.uint)(Length), cgoAllocsUnknown - __ret := C.nvSandboxUtilsGetDriverVersion(cVersion, cLength) + __ret := C.nvSandboxUtilsGetDriverVersion_dl(cVersion, cLength) __v := (Ret)(__ret) return __v } @@ -56,7 +57,7 @@ func nvSandboxUtilsGetDriverVersion(Version *byte, Length uint32) Ret { // nvSandboxUtilsGetGpuResource function as declared in nvsandboxutils/nvsandboxutils.h func nvSandboxUtilsGetGpuResource(Request *GpuRes) Ret { cRequest, _ := (*C.nvSandboxUtilsGpuRes_t)(unsafe.Pointer(Request)), cgoAllocsUnknown - __ret := C.nvSandboxUtilsGetGpuResource(cRequest) + __ret := C.nvSandboxUtilsGetGpuResource_dl(cRequest) __v := (Ret)(__ret) return __v } @@ -66,7 +67,7 @@ func nvSandboxUtilsGetFileContent(FilePath *byte, Content *byte, ContentSize *ui cFilePath, _ := (*C.char)(unsafe.Pointer(FilePath)), cgoAllocsUnknown cContent, _ := (*C.char)(unsafe.Pointer(Content)), cgoAllocsUnknown cContentSize, _ := (*C.uint)(unsafe.Pointer(ContentSize)), cgoAllocsUnknown - __ret := C.nvSandboxUtilsGetFileContent(cFilePath, cContent, cContentSize) + __ret := C.nvSandboxUtilsGetFileContent_dl(cFilePath, cContent, cContentSize) __v := (Ret)(__ret) return __v } diff --git a/internal/nvsandboxutils/nvsandboxutils_test.go b/internal/nvsandboxutils/nvsandboxutils_test.go new file mode 100644 index 000000000..ba71fa5a0 --- /dev/null +++ b/internal/nvsandboxutils/nvsandboxutils_test.go @@ -0,0 +1,191 @@ +/** +# Copyright 2026 NVIDIA CORPORATION +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +**/ + +package nvsandboxutils + +import ( + "os" + "os/exec" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +const ( + mockRootfs = "/mock/rootfs" + mockGpuUUID = "GPU-6fd91e39-6ba7-4d6b-b1d6-1dffb0bc7a2f" + mockMigUUID = "MIG-6fd91e39-6ba7-4d6b-b1d6-1dffb0bc7a2f" + mockDriverVersion = "999.88.77" + + mockSymlinkPath = "/dev/dri/by-path/pci-0000:41:00.0-card" + mockSymlinkTarget = "../card1" +) + +func buildMockLibrary(t *testing.T) string { + t.Helper() + + cc := os.Getenv("CC") + if cc == "" { + cc = "cc" + } + if _, err := exec.LookPath(cc); err != nil { + t.Skipf("no C compiler available: %v", err) + } + + path := filepath.Join(t.TempDir(), "mock_nvsandbox.so") + cmd := exec.Command(cc, "-shared", "-fPIC", "-I", ".", "-o", path, filepath.Join("testdata", "mock_nvsandbox.c")) + output, err := cmd.CombinedOutput() + require.NoErrorf(t, err, "failed to build mock library: %s", output) + + return path +} + +func newMockLibrary(t *testing.T) *library { + t.Helper() + + l := newLibrary(WithLibraryPath(buildMockLibrary(t))) + t.Cleanup(func() { _ = l.close() }) + + return l +} + +func newInitializedMockLibrary(t *testing.T) *library { + t.Helper() + + l := newMockLibrary(t) + require.Equal(t, SUCCESS, l.Init(mockRootfs)) + + return l +} + +func TestEntryPointBeforeLoadReturnsLibraryLoadError(t *testing.T) { + require.Equal(t, ERROR_LIBRARY_LOAD, nvSandboxUtilsShutdown()) +} + +func TestInitRejectsAnUnexpectedRootfs(t *testing.T) { + l := newMockLibrary(t) + + require.Equal(t, ERROR_INVALID_ARG, l.Init("/not/the/mock/rootfs")) +} + +func TestGetDriverVersion(t *testing.T) { + l := newInitializedMockLibrary(t) + + version, ret := l.GetDriverVersion() + require.Equal(t, SUCCESS, ret) + require.Equal(t, mockDriverVersion, version) +} + +func TestGetFileContent(t *testing.T) { + testCases := []struct { + description string + path string + expectedContent string + expectedRet Ret + }{ + { + description: "known path", + path: mockSymlinkPath, + expectedContent: mockSymlinkTarget, + expectedRet: SUCCESS, + }, + { + description: "unknown path", + path: "/not/a/known/path", + expectedRet: ERROR_FILEPATH_NOT_FOUND, + }, + } + + l := newInitializedMockLibrary(t) + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + content, ret := l.GetFileContent(tc.path) + require.Equal(t, tc.expectedRet, ret) + if tc.expectedRet == SUCCESS { + require.Equal(t, tc.expectedContent, content) + } + }) + } +} + +func TestGetGpuResource(t *testing.T) { + testCases := []struct { + description string + uuid string + expected []GpuFileInfo + expectedRet Ret + }{ + { + description: "GPU UUID returns the full list", + uuid: mockGpuUUID, + expected: []GpuFileInfo{ + { + Path: "/dev/nvidia0", + Type: NV_DEV, + SubType: NV_DEV_NVIDIA, + Module: NV_GPU, + Flags: NV_FILE_FLAG_HINT, + }, + { + Path: "/dev/nvidiactl", + Type: NV_DEV, + SubType: NV_DEV_NVIDIA_CTL, + Module: NV_DRIVER_NVIDIA, + Flags: NV_FILE_FLAG_HINT, + }, + }, + expectedRet: SUCCESS, + }, + { + description: "MIG UUID selects the MIG input type", + uuid: mockMigUUID, + expected: []GpuFileInfo{ + { + Path: "/dev/nvidiactl", + Type: NV_DEV, + SubType: NV_DEV_NVIDIA_CTL, + Module: NV_DRIVER_NVIDIA, + Flags: NV_FILE_FLAG_HINT, + }, + }, + expectedRet: SUCCESS, + }, + { + description: "unknown UUID", + uuid: "GPU-unknown", + expectedRet: ERROR_DEVICE_NOT_FOUND, + }, + } + + l := newInitializedMockLibrary(t) + + for _, tc := range testCases { + t.Run(tc.description, func(t *testing.T) { + files, ret := l.GetGpuResource(tc.uuid) + require.Equal(t, tc.expectedRet, ret) + require.Equal(t, tc.expected, files) + }) + } +} + +func TestShutdownUnloadsTheLibrary(t *testing.T) { + l := newInitializedMockLibrary(t) + + require.Equal(t, SUCCESS, l.Shutdown()) + require.Equal(t, refcount(0), l.refcount) +} diff --git a/internal/nvsandboxutils/testdata/mock_nvsandbox.c b/internal/nvsandboxutils/testdata/mock_nvsandbox.c new file mode 100644 index 000000000..e2bae65b1 --- /dev/null +++ b/internal/nvsandboxutils/testdata/mock_nvsandbox.c @@ -0,0 +1,137 @@ +/** +# Copyright 2026 NVIDIA CORPORATION +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +**/ + +#include + +#include "nvsandboxutils.h" + +#define MOCK_ROOTFS "/mock/rootfs" +#define MOCK_GPU_UUID "GPU-6fd91e39-6ba7-4d6b-b1d6-1dffb0bc7a2f" +#define MOCK_MIG_UUID "MIG-6fd91e39-6ba7-4d6b-b1d6-1dffb0bc7a2f" +#define MOCK_DRIVER_VERSION "999.88.77" + +#define MOCK_SYMLINK_PATH "/dev/dri/by-path/pci-0000:41:00.0-card" +#define MOCK_SYMLINK_TARGET "../card1" + +static int initialized = 0; + +nvSandboxUtilsRet_t nvSandboxUtilsInit(nvSandboxUtilsInitInput_t *input) +{ + if (input == NULL) { + return NVSANDBOXUTILS_ERROR_INVALID_ARG; + } + if (input->version != 1) { + return NVSANDBOXUTILS_ERROR_VERSION_NOT_SUPPORTED; + } + if (input->type != NV_ROOTFS_PATH || strcmp(input->value, MOCK_ROOTFS) != 0) { + return NVSANDBOXUTILS_ERROR_INVALID_ARG; + } + + initialized = 1; + return NVSANDBOXUTILS_SUCCESS; +} + +nvSandboxUtilsRet_t nvSandboxUtilsShutdown(void) +{ + if (!initialized) { + return NVSANDBOXUTILS_ERROR_UNINITIALIZED; + } + + initialized = 0; + return NVSANDBOXUTILS_SUCCESS; +} + +nvSandboxUtilsRet_t nvSandboxUtilsGetDriverVersion(char *version, unsigned int length) +{ + if (!initialized) { + return NVSANDBOXUTILS_ERROR_UNINITIALIZED; + } + if (version == NULL) { + return NVSANDBOXUTILS_ERROR_INVALID_ARG; + } + if (length < sizeof(MOCK_DRIVER_VERSION)) { + return NVSANDBOXUTILS_ERROR_INSUFFICIENT_SIZE; + } + + strcpy(version, MOCK_DRIVER_VERSION); + return NVSANDBOXUTILS_SUCCESS; +} + +static char devNvidia0[] = "/dev/nvidia0"; +static char devNvidiactl[] = "/dev/nvidiactl"; + +static nvSandboxUtilsGpuFileInfo_v1_t gpuFiles[] = { + { + .next = &gpuFiles[1], + .fileType = NV_DEV, + .fileSubType = NV_DEV_NVIDIA, + .module = NV_GPU, + .flags = NV_FILE_FLAG_HINT, + .filePath = devNvidia0, + }, + { + .next = NULL, + .fileType = NV_DEV, + .fileSubType = NV_DEV_NVIDIA_CTL, + .module = NV_DRIVER_NVIDIA, + .flags = NV_FILE_FLAG_HINT, + .filePath = devNvidiactl, + }, +}; + +nvSandboxUtilsRet_t nvSandboxUtilsGetGpuResource(nvSandboxUtilsGpuRes_t *request) +{ + if (!initialized) { + return NVSANDBOXUTILS_ERROR_UNINITIALIZED; + } + if (request == NULL) { + return NVSANDBOXUTILS_ERROR_INVALID_ARG; + } + if (request->version != 1) { + return NVSANDBOXUTILS_ERROR_VERSION_NOT_SUPPORTED; + } + + if (request->inputType == NV_GPU_INPUT_GPU_UUID && strcmp(request->input, MOCK_GPU_UUID) == 0) { + request->files = &gpuFiles[0]; + return NVSANDBOXUTILS_SUCCESS; + } + if (request->inputType == NV_GPU_INPUT_MIG_UUID && strcmp(request->input, MOCK_MIG_UUID) == 0) { + request->files = &gpuFiles[1]; + return NVSANDBOXUTILS_SUCCESS; + } + + return NVSANDBOXUTILS_ERROR_DEVICE_NOT_FOUND; +} + +nvSandboxUtilsRet_t nvSandboxUtilsGetFileContent(char *filePath, char *content, unsigned int *contentSize) +{ + if (!initialized) { + return NVSANDBOXUTILS_ERROR_UNINITIALIZED; + } + if (filePath == NULL || content == NULL || contentSize == NULL) { + return NVSANDBOXUTILS_ERROR_INVALID_ARG; + } + if (strcmp(filePath, MOCK_SYMLINK_PATH) != 0) { + return NVSANDBOXUTILS_ERROR_FILEPATH_NOT_FOUND; + } + if (*contentSize < sizeof(MOCK_SYMLINK_TARGET)) { + return NVSANDBOXUTILS_ERROR_INSUFFICIENT_SIZE; + } + + strcpy(content, MOCK_SYMLINK_TARGET); + *contentSize = sizeof(MOCK_SYMLINK_TARGET) - 1; + return NVSANDBOXUTILS_SUCCESS; +}