Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 12 additions & 7 deletions pkg/lookup/ldcache.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
32 changes: 32 additions & 0 deletions pkg/lookup/ldcache_test.go
Original file line number Diff line number Diff line change
@@ -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"
)

Expand Down Expand Up @@ -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)
}
16 changes: 9 additions & 7 deletions pkg/lookup/library.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +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 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 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...)

Expand All @@ -50,9 +52,9 @@ func NewLibraryLocator(opts ...Option) Locator {
"/lib/aarch64-linux-gnu/nvidia/current",
}...),
)
l := First(
l := AsUnique(Merge(
NewSymlinkLocator(opts...),
f.newLdcacheLocator(),
)
))
return l
}
36 changes: 36 additions & 0 deletions pkg/lookup/merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
)

type first []Locator
type merged []Locator

type unique struct {
locator Locator
Expand All @@ -38,6 +39,21 @@ func First(locators ...Locator) Locator {
return f
}

// 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 {
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
Expand All @@ -58,6 +74,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,
Expand Down
58 changes: 58 additions & 0 deletions pkg/lookup/merge_test.go
Original file line number Diff line number Diff line change
@@ -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)
}