From 32d1d366d2a872066df84a898cb29048f026b285 Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Sat, 1 Aug 2026 21:43:43 -0400 Subject: [PATCH 1/2] fix(cdi): include 32-bit libraries in discovery Signed-off-by: Eli Bosley --- pkg/lookup/ldcache.go | 19 ++++++++----- pkg/lookup/ldcache_test.go | 32 +++++++++++++++++++++ pkg/lookup/library.go | 11 +++----- pkg/lookup/merge.go | 33 ++++++++++++++++++++++ pkg/lookup/merge_test.go | 58 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 139 insertions(+), 14 deletions(-) create mode 100644 pkg/lookup/merge_test.go diff --git a/pkg/lookup/ldcache.go b/pkg/lookup/ldcache.go index b39e4adda..4ae4c7337 100644 --- a/pkg/lookup/ldcache.go +++ b/pkg/lookup/ldcache.go @@ -43,16 +43,21 @@ func (f *Factory) newLdcacheLocator() Locator { f.logger.Warningf("Failed to load ldcache: %v", err) return notFound } + return f.newLdcacheLocatorFrom(cache) +} +func (f *Factory) newLdcacheLocatorFrom(cache ldcache.LDCache) Locator { var libraries []string - _, libs64 := cache.List() - for _, library := range libs64 { - chain, err := symlinks.ResolveChain(library) - if err != nil { - f.logger.Warningf("Failed to resolve symlink chain for library %q: %v", library, err) - continue + libs32, libs64 := cache.List() + for _, libs := range [][]string{libs64, libs32} { + for _, library := range libs { + chain, err := symlinks.ResolveChain(library) + if err != nil { + f.logger.Warningf("Failed to resolve symlink chain for library %q: %v", library, err) + continue + } + libraries = append(libraries, chain...) } - libraries = append(libraries, chain...) } l := &ldcacheLocator{ diff --git a/pkg/lookup/ldcache_test.go b/pkg/lookup/ldcache_test.go index a3a075958..07a48abed 100644 --- a/pkg/lookup/ldcache_test.go +++ b/pkg/lookup/ldcache_test.go @@ -1,12 +1,15 @@ package lookup import ( + "os" "path/filepath" + "strings" "testing" testlog "github.com/sirupsen/logrus/hooks/test" "github.com/stretchr/testify/require" + "github.com/NVIDIA/nvidia-container-toolkit/internal/ldcache" "github.com/NVIDIA/nvidia-container-toolkit/internal/test" ) @@ -75,3 +78,32 @@ func TestLDCacheLookup(t *testing.T) { } } } + +func TestLDCacheLookupIncludes32BitLibraries(t *testing.T) { + logger, _ := testlog.NewNullLogger() + root := t.TempDir() + + lib64 := filepath.Join(root, "usr/lib64/libcuda.so.999.88.77") + lib32 := filepath.Join(root, "usr/lib/libcuda.so.999.88.77") + require.NoError(t, os.MkdirAll(filepath.Dir(lib64), 0o755)) + require.NoError(t, os.MkdirAll(filepath.Dir(lib32), 0o755)) + require.NoError(t, os.WriteFile(lib64, nil, 0o600)) + require.NoError(t, os.WriteFile(lib32, nil, 0o600)) + + cache := &ldcache.LDCacheMock{ + ListFunc: func() ([]string, []string) { + return []string{lib32}, []string{lib64} + }, + } + l := NewFactory( + WithLogger(logger), + WithRoot(root), + ).newLdcacheLocatorFrom(cache) + + candidates, err := l.Locate("libcuda.so.*") + require.NoError(t, err) + for i := range candidates { + candidates[i] = strings.TrimPrefix(candidates[i], "/private") + } + require.Equal(t, []string{lib64, lib32}, candidates) +} diff --git a/pkg/lookup/library.go b/pkg/lookup/library.go index e57bd0e17..ca57bfbd9 100644 --- a/pkg/lookup/library.go +++ b/pkg/lookup/library.go @@ -19,11 +19,8 @@ package lookup // NewLibraryLocator creates a library locator using the specified options. // If search paths (WithSearchPaths(path1, path2, ...)) are explicitly specified // a library locator using these as absolute paths are used. Otherwise the -// library is constructed using the following ordering, returning the first -// successful result: -// - attempt to locate the library / pattern using dlopen -// - attempt to locate the library from a set of predefined search paths. -// - attempt to locate the library from the ldcache. +// library is constructed by combining results from a set of predefined search +// paths and the ldcache. func NewLibraryLocator(opts ...Option) Locator { f := NewFactory(opts...) @@ -50,9 +47,9 @@ func NewLibraryLocator(opts ...Option) Locator { "/lib/aarch64-linux-gnu/nvidia/current", }...), ) - l := First( + l := AsUnique(Merge( NewSymlinkLocator(opts...), f.newLdcacheLocator(), - ) + )) return l } diff --git a/pkg/lookup/merge.go b/pkg/lookup/merge.go index ade3dd5ae..53e09ce79 100644 --- a/pkg/lookup/merge.go +++ b/pkg/lookup/merge.go @@ -21,6 +21,7 @@ import ( ) type first []Locator +type merged []Locator type unique struct { locator Locator @@ -38,6 +39,18 @@ func First(locators ...Locator) Locator { return f } +// Merge returns a locator that combines the matches from all supplied locators. +func Merge(locators ...Locator) Locator { + var m merged + for _, l := range locators { + if l == nil { + continue + } + m = append(m, l) + } + return m +} + // Locate returns the results for the first locator that returns a non-empty non-error result. func (f first) Locate(pattern string) ([]string, error) { var allErrors []error @@ -58,6 +71,26 @@ func (f first) Locate(pattern string) ([]string, error) { return nil, errors.Join(allErrors...) } +// Locate returns the combined results from all locators that return matches. +func (m merged) Locate(pattern string) ([]string, error) { + var candidates []string + var allErrors []error + for _, l := range m { + matches, err := l.Locate(pattern) + if err != nil { + allErrors = append(allErrors, err) + continue + } + candidates = append(candidates, matches...) + } + + if len(candidates) > 0 { + return candidates, nil + } + + return nil, errors.Join(allErrors...) +} + func AsUnique(locator Locator) Locator { return &unique{ locator: locator, diff --git a/pkg/lookup/merge_test.go b/pkg/lookup/merge_test.go new file mode 100644 index 000000000..c81daf63d --- /dev/null +++ b/pkg/lookup/merge_test.go @@ -0,0 +1,58 @@ +/** +# 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 lookup + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestMerge(t *testing.T) { + first := &LocatorMock{ + LocateFunc: func(string) ([]string, error) { + return []string{"first"}, nil + }, + } + second := &LocatorMock{ + LocateFunc: func(string) ([]string, error) { + return []string{"second"}, nil + }, + } + + candidates, err := Merge(first, second).Locate("libcuda.so.*") + require.NoError(t, err) + require.Equal(t, []string{"first", "second"}, candidates) +} + +func TestMergeReturnsMatchesWhenAnotherLocatorFails(t *testing.T) { + failing := &LocatorMock{ + LocateFunc: func(string) ([]string, error) { + return nil, fmt.Errorf("lookup failed") + }, + } + matching := &LocatorMock{ + LocateFunc: func(string) ([]string, error) { + return []string{"match"}, nil + }, + } + + candidates, err := Merge(failing, matching).Locate("libcuda.so.*") + require.NoError(t, err) + require.Equal(t, []string{"match"}, candidates) +} From 82fadadd85bd0f966b1665eebf9519fd44fc914b Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Sat, 1 Aug 2026 21:55:59 -0400 Subject: [PATCH 2/2] docs(lookup): clarify locator precedence Signed-off-by: Eli Bosley --- pkg/lookup/library.go | 9 +++++++-- pkg/lookup/merge.go | 5 ++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/pkg/lookup/library.go b/pkg/lookup/library.go index ca57bfbd9..d6fbfb58d 100644 --- a/pkg/lookup/library.go +++ b/pkg/lookup/library.go @@ -19,8 +19,13 @@ package lookup // NewLibraryLocator creates a library locator using the specified options. // If search paths (WithSearchPaths(path1, path2, ...)) are explicitly specified // a library locator using these as absolute paths are used. Otherwise the -// library is constructed by combining results from a set of predefined search -// paths and the ldcache. +// library locator combines unique matches from the following sources, in +// precedence order: +// - predefined search paths +// - 64-bit entries from the ldcache +// - 32-bit entries from the ldcache +// +// If multiple sources return the same path, the first occurrence is kept. func NewLibraryLocator(opts ...Option) Locator { f := NewFactory(opts...) diff --git a/pkg/lookup/merge.go b/pkg/lookup/merge.go index 53e09ce79..a2cd8c2a8 100644 --- a/pkg/lookup/merge.go +++ b/pkg/lookup/merge.go @@ -39,7 +39,10 @@ func First(locators ...Locator) Locator { return f } -// Merge returns a locator that combines the matches from all supplied locators. +// Merge returns a locator that combines matches from all supplied locators in +// argument order. Nil locators are ignored. If at least one locator returns a +// match, errors from the other locators are ignored; otherwise, all locator +// errors are joined and returned. func Merge(locators ...Locator) Locator { var m merged for _, l := range locators {