Skip to content

Commit fa5a4ac

Browse files
committed
Read ldcache at construction instead of on each locate call
This change udpates the ldcache locator to read the ldcache at construction and use these contents to perform future lookups against. Each of the cache entries are resolved and lookups return the resolved target. Assuming a symlink chain: libcuda.so -> libcuda.so.1 -> libcuda.so.VERSION, this means that libcuda.so.VERION will be returned for any of the following inputs: libcuda.so, libcuda.so.1, libcudal.so.*. Signed-off-by: Evan Lezar <[email protected]>
1 parent 9f1bd62 commit fa5a4ac

File tree

7 files changed

+162
-260
lines changed

7 files changed

+162
-260
lines changed

internal/ldcache/ldcache.go

Lines changed: 5 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,12 @@ import (
2222
"bytes"
2323
"encoding/binary"
2424
"errors"
25-
"fmt"
2625
"os"
2726
"path/filepath"
28-
"strings"
2927
"syscall"
3028
"unsafe"
3129

3230
"github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
33-
"github.com/NVIDIA/nvidia-container-toolkit/internal/lookup/symlinks"
3431
)
3532

3633
const ldcachePath = "/etc/ld.so.cache"
@@ -82,10 +79,9 @@ type entry2 struct {
8279

8380
// LDCache represents the interface for performing lookups into the LDCache
8481
//
85-
//go:generate moq -out ldcache_mock.go . LDCache
82+
//go:generate moq -rm -out ldcache_mock.go . LDCache
8683
type LDCache interface {
8784
List() ([]string, []string)
88-
Lookup(...string) ([]string, []string)
8985
}
9086

9187
type ldcache struct {
@@ -105,14 +101,7 @@ func New(logger logger.Interface, root string) (LDCache, error) {
105101

106102
logger.Debugf("Opening ld.conf at %v", path)
107103
f, err := os.Open(path)
108-
if os.IsNotExist(err) {
109-
logger.Warningf("Could not find ld.so.cache at %v; creating empty cache", path)
110-
e := &empty{
111-
logger: logger,
112-
path: path,
113-
}
114-
return e, nil
115-
} else if err != nil {
104+
if err != nil {
116105
return nil, err
117106
}
118107
defer f.Close()
@@ -196,7 +185,7 @@ type entry struct {
196185
}
197186

198187
// getEntries returns the entires of the ldcache in a go-friendly struct.
199-
func (c *ldcache) getEntries(selected func(string) bool) []entry {
188+
func (c *ldcache) getEntries() []entry {
200189
var entries []entry
201190
for _, e := range c.entries {
202191
bits := 0
@@ -223,9 +212,6 @@ func (c *ldcache) getEntries(selected func(string) bool) []entry {
223212
c.logger.Debugf("Skipping invalid lib")
224213
continue
225214
}
226-
if !selected(lib) {
227-
continue
228-
}
229215
value := bytesToString(c.libs[e.Value:])
230216
if value == "" {
231217
c.logger.Debugf("Skipping invalid value for lib %v", lib)
@@ -236,51 +222,19 @@ func (c *ldcache) getEntries(selected func(string) bool) []entry {
236222
bits: bits,
237223
value: value,
238224
}
239-
240225
entries = append(entries, e)
241226
}
242-
243227
return entries
244228
}
245229

246230
// List creates a list of libraries in the ldcache.
247231
// The 32-bit and 64-bit libraries are returned separately.
248232
func (c *ldcache) List() ([]string, []string) {
249-
all := func(s string) bool { return true }
250-
251-
return c.resolveSelected(all)
252-
}
253-
254-
// Lookup searches the ldcache for the specified prefixes.
255-
// The 32-bit and 64-bit libraries matching the prefixes are returned.
256-
func (c *ldcache) Lookup(libPrefixes ...string) ([]string, []string) {
257-
c.logger.Debugf("Looking up %v in cache", libPrefixes)
258-
259-
// We define a functor to check whether a given library name matches any of the prefixes
260-
matchesAnyPrefix := func(s string) bool {
261-
for _, p := range libPrefixes {
262-
if strings.HasPrefix(s, p) {
263-
return true
264-
}
265-
}
266-
return false
267-
}
268-
269-
return c.resolveSelected(matchesAnyPrefix)
270-
}
271-
272-
// resolveSelected process the entries in the LDCach based on the supplied filter and returns the resolved paths.
273-
// The paths are separated by bittage.
274-
func (c *ldcache) resolveSelected(selected func(string) bool) ([]string, []string) {
275233
paths := make(map[int][]string)
276234
processed := make(map[string]bool)
277235

278-
for _, e := range c.getEntries(selected) {
279-
path, err := c.resolve(e.value)
280-
if err != nil {
281-
c.logger.Debugf("Could not resolve entry: %v", err)
282-
continue
283-
}
236+
for _, e := range c.getEntries() {
237+
path := filepath.Join(c.root, e.value)
284238
if processed[path] {
285239
continue
286240
}
@@ -291,29 +245,6 @@ func (c *ldcache) resolveSelected(selected func(string) bool) ([]string, []strin
291245
return paths[32], paths[64]
292246
}
293247

294-
// resolve resolves the specified ldcache entry based on the value being processed.
295-
// The input is the name of the entry in the cache.
296-
func (c *ldcache) resolve(target string) (string, error) {
297-
name := filepath.Join(c.root, target)
298-
299-
c.logger.Debugf("checking %v", name)
300-
301-
link, err := symlinks.Resolve(name)
302-
if err != nil {
303-
return "", fmt.Errorf("failed to resolve symlink: %v", err)
304-
}
305-
if link == name {
306-
return name, nil
307-
}
308-
309-
// We return absolute paths for all targets
310-
if !filepath.IsAbs(link) || strings.HasPrefix(link, ".") {
311-
link = filepath.Join(filepath.Dir(target), link)
312-
}
313-
314-
return c.resolve(link)
315-
}
316-
317248
// bytesToString converts a byte slice to a string.
318249
// This assumes that the byte slice is null-terminated
319250
func bytesToString(value []byte) string {

internal/ldcache/ldcache_mock.go

Lines changed: 1 addition & 45 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/lookup/ldcache.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/**
2+
# Copyright 2024 NVIDIA CORPORATION
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
**/
16+
17+
package lookup
18+
19+
import (
20+
"fmt"
21+
"path/filepath"
22+
"slices"
23+
24+
"github.com/NVIDIA/nvidia-container-toolkit/internal/ldcache"
25+
)
26+
27+
type ldcacheLocator struct {
28+
*builder
29+
resolvesTo map[string]string
30+
}
31+
32+
var _ Locator = (*ldcacheLocator)(nil)
33+
34+
func NewLdcacheLocator(opts ...Option) Locator {
35+
b := newBuilder(opts...)
36+
37+
cache, err := ldcache.New(b.logger, b.root)
38+
if err != nil {
39+
b.logger.Warningf("Failed to load ldcache: %v", err)
40+
if b.isOptional {
41+
return &null{}
42+
}
43+
return &notFound{}
44+
}
45+
46+
chain := NewSymlinkChainLocator(WithOptional(true))
47+
48+
resolvesTo := make(map[string]string)
49+
_, libs64 := cache.List()
50+
for _, library := range libs64 {
51+
if _, processed := resolvesTo[library]; processed {
52+
continue
53+
}
54+
candidates, err := chain.Locate(library)
55+
if err != nil {
56+
b.logger.Errorf("error processing library %s from ldcache: %v", library, err)
57+
continue
58+
}
59+
60+
if len(candidates) == 0 {
61+
resolvesTo[library] = library
62+
continue
63+
}
64+
65+
// candidates represents a symlink chain.
66+
// The first element represents the start of the chain and the last
67+
// element the final target.
68+
target := candidates[len(candidates)-1]
69+
for _, candidate := range candidates {
70+
resolvesTo[candidate] = target
71+
}
72+
}
73+
74+
return &ldcacheLocator{
75+
builder: b,
76+
resolvesTo: resolvesTo,
77+
}
78+
}
79+
80+
// Locate finds the specified libraryname.
81+
// If the input is a library name, the ldcache is searched otherwise the
82+
// provided path is resolved as a symlink.
83+
func (l ldcacheLocator) Locate(libname string) ([]string, error) {
84+
var matcher func(string, string) bool
85+
86+
if filepath.IsAbs(libname) {
87+
matcher = func(p string, c string) bool {
88+
m, _ := filepath.Match(filepath.Join(l.root, p), c)
89+
return m
90+
}
91+
} else {
92+
matcher = func(p string, c string) bool {
93+
m, _ := filepath.Match(p, filepath.Base(c))
94+
return m
95+
}
96+
}
97+
98+
var matches []string
99+
seen := make(map[string]bool)
100+
for name, target := range l.resolvesTo {
101+
if !matcher(libname, name) {
102+
continue
103+
}
104+
if seen[target] {
105+
continue
106+
}
107+
seen[target] = true
108+
matches = append(matches, target)
109+
}
110+
111+
slices.Sort(matches)
112+
113+
if len(matches) == 0 && !l.isOptional {
114+
return nil, fmt.Errorf("%s: %w", libname, ErrNotFound)
115+
}
116+
117+
return matches, nil
118+
}

internal/lookup/ldcache_test.go

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,40 @@ func TestLDCacheLookup(t *testing.T) {
2828
expectedError: ErrNotFound,
2929
},
3030
{
31-
rootFs: "rootfs-1",
32-
inputs: []string{"libcuda.so.1", "libcuda.so.*", "libcuda.so.*.*", "libcuda.so.999.88.77"},
31+
rootFs: "rootfs-1",
32+
inputs: []string{
33+
"libcuda.so.1",
34+
"libcuda.so.*",
35+
"libcuda.so.*.*",
36+
"libcuda.so.999.88.77",
37+
"/lib/x86_64-linux-gnu/libcuda.so.1",
38+
"/lib/x86_64-linux-gnu/libcuda.so.*",
39+
"/lib/x86_64-linux-gnu/libcuda.so.*.*",
40+
"/lib/x86_64-linux-gnu/libcuda.so.999.88.77",
41+
},
3342
expected: "/lib/x86_64-linux-gnu/libcuda.so.999.88.77",
3443
},
3544
{
36-
rootFs: "rootfs-2",
37-
inputs: []string{"libcuda.so.1", "libcuda.so.*", "libcuda.so.*.*", "libcuda.so.999.88.77"},
45+
rootFs: "rootfs-2",
46+
inputs: []string{
47+
"libcuda.so.1",
48+
"libcuda.so.*",
49+
"libcuda.so.*.*",
50+
"libcuda.so.999.88.77",
51+
"/var/lib/nvidia/lib64/libcuda.so.1",
52+
"/var/lib/nvidia/lib64/libcuda.so.*",
53+
"/var/lib/nvidia/lib64/libcuda.so.*.*",
54+
"/var/lib/nvidia/lib64/libcuda.so.999.88.77",
55+
},
3856
expected: "/var/lib/nvidia/lib64/libcuda.so.999.88.77",
3957
},
4058
}
4159

4260
for _, tc := range testCases {
4361
for _, input := range tc.inputs {
44-
t.Run(tc.rootFs+input, func(t *testing.T) {
62+
t.Run(tc.rootFs+" "+input, func(t *testing.T) {
4563
rootfs := filepath.Join(moduleRoot, "testdata", "lookup", tc.rootFs)
46-
l := newLdcacheLocator(
64+
l := NewLdcacheLocator(
4765
WithLogger(logger),
4866
WithRoot(rootfs),
4967
)

0 commit comments

Comments
 (0)