Skip to content

Commit 25fc385

Browse files
committed
Filter already tracked directories from ldcache update
This change fixes the behavior where the order of precedence of existing folders are changed because they are added to the .conf file for ldconfig. Signed-off-by: Evan Lezar <elezar@nvidia.com>
1 parent c171e65 commit 25fc385

File tree

3 files changed

+215
-4
lines changed

3 files changed

+215
-4
lines changed

internal/ldconfig/ldconfig.go

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
package ldconfig
1919

2020
import (
21+
"bufio"
2122
"fmt"
2223
"os"
2324
"os/exec"
@@ -75,21 +76,81 @@ func (l *Ldconfig) UpdateLDCache(directories ...string) error {
7576
return err
7677
}
7778

79+
// Explicitly specify using /etc/ld.so.conf since the host's ldconfig may
80+
// be configured to use a different config file by default.
81+
configFilePath := "/etc/ld.so.conf"
82+
filteredDirectories, err := filterDirectories(configFilePath, directories...)
83+
if err != nil {
84+
return err
85+
}
86+
7887
args := []string{
7988
filepath.Base(ldconfigPath),
80-
// Explicitly specify using /etc/ld.so.conf since the host's ldconfig may
81-
// be configured to use a different config file by default.
82-
"-f", "/etc/ld.so.conf",
89+
"-f", configFilePath,
8390
"-C", "/etc/ld.so.cache",
8491
}
8592

86-
if err := createLdsoconfdFile(ldsoconfdFilenamePattern, directories...); err != nil {
93+
if err := createLdsoconfdFile(ldsoconfdFilenamePattern, filteredDirectories...); err != nil {
8794
return fmt.Errorf("failed to update ld.so.conf.d: %w", err)
8895
}
8996

9097
return SafeExec(ldconfigPath, args, nil)
9198
}
9299

100+
func filterDirectories(configFilePath string, directories ...string) ([]string, error) {
101+
processedConfFiles := make(map[string]bool)
102+
ldconfigFilenames := []string{configFilePath}
103+
104+
ldconfigDirs := make(map[string]string)
105+
for len(ldconfigFilenames) > 0 {
106+
ldconfigFilename := ldconfigFilenames[0]
107+
ldconfigFilenames = ldconfigFilenames[1:]
108+
if processedConfFiles[ldconfigFilename] {
109+
continue
110+
}
111+
processedConfFiles[ldconfigFilename] = true
112+
113+
if len(ldconfigFilename) == 0 {
114+
continue
115+
}
116+
117+
ldsoconf, err := os.Open(ldconfigFilename)
118+
if os.IsNotExist(err) {
119+
continue
120+
}
121+
if err != nil {
122+
return nil, err
123+
}
124+
defer ldsoconf.Close()
125+
126+
scanner := bufio.NewScanner(ldsoconf)
127+
for scanner.Scan() {
128+
line := strings.TrimSpace(scanner.Text())
129+
switch {
130+
case strings.HasPrefix(line, "#") || len(line) == 0:
131+
continue
132+
case strings.HasPrefix(line, "include "):
133+
includes, err := filepath.Glob(strings.TrimPrefix(line, "include "))
134+
if err != nil {
135+
return nil, err
136+
}
137+
ldconfigFilenames = append(ldconfigFilenames, includes...)
138+
default:
139+
ldconfigDirs[line] = ldconfigFilename
140+
}
141+
}
142+
}
143+
144+
var filtered []string
145+
for _, d := range directories {
146+
if _, ok := ldconfigDirs[d]; ok {
147+
continue
148+
}
149+
filtered = append(filtered, d)
150+
}
151+
return filtered, nil
152+
}
153+
93154
func (l *Ldconfig) prepareRoot() (string, error) {
94155
// To prevent leaking the parent proc filesystem, we create a new proc mount
95156
// in the specified root.

internal/ldconfig/ldconfig_test.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/**
2+
# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3+
# SPDX-License-Identifier: Apache-2.0
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
**/
17+
18+
package ldconfig
19+
20+
import (
21+
"os"
22+
"strings"
23+
"testing"
24+
25+
"github.com/stretchr/testify/require"
26+
)
27+
28+
func TestFilterDirectories(t *testing.T) {
29+
const topLevelConf = "TOPLEVEL.conf"
30+
31+
testCases := []struct {
32+
description string
33+
confs map[string]string // map[filename]content, must have topLevelConf key
34+
input []string
35+
expected []string
36+
}{
37+
{
38+
description: "all filtered",
39+
confs: map[string]string{
40+
topLevelConf: `
41+
# some comment
42+
/tmp/libdir1
43+
/tmp/libdir2
44+
`,
45+
},
46+
input: []string{"/tmp/libdir1", "/tmp/libdir2"},
47+
expected: nil,
48+
},
49+
{
50+
description: "partially filtered",
51+
confs: map[string]string{
52+
topLevelConf: `
53+
/tmp/libdir1
54+
`,
55+
},
56+
input: []string{"/tmp/libdir1", "/tmp/libdir2"},
57+
expected: []string{"/tmp/libdir2"},
58+
},
59+
{
60+
description: "none filtered",
61+
confs: map[string]string{
62+
topLevelConf: `
63+
# empty config
64+
`,
65+
},
66+
input: []string{"/tmp/libdir1", "/tmp/libdir2"},
67+
expected: []string{"/tmp/libdir1", "/tmp/libdir2"},
68+
},
69+
{
70+
description: "filter with include and comments",
71+
confs: map[string]string{
72+
topLevelConf: `
73+
# comment
74+
/tmp/libdir1
75+
include /nonexistent/pattern*
76+
`,
77+
},
78+
input: []string{"/tmp/libdir1", "/tmp/libdir2"},
79+
expected: []string{"/tmp/libdir2"},
80+
},
81+
{
82+
description: "include directive picks up more dirs to filter",
83+
confs: map[string]string{
84+
topLevelConf: `
85+
# top-level
86+
include INCLUDED_PATTERN*
87+
/tmp/libdir3
88+
`,
89+
"INCLUDED_PATTERN0.conf": `
90+
/tmp/libdir2
91+
# another comment
92+
/tmp/libdir4
93+
`,
94+
"INCLUDED_PATTERN1.conf": `
95+
/tmp/libdir1
96+
`,
97+
},
98+
input: []string{"/tmp/libdir1", "/tmp/libdir2", "/tmp/libdir3", "/tmp/libdir4", "/tmp/libdir5"},
99+
expected: []string{"/tmp/libdir5"},
100+
},
101+
}
102+
103+
for _, tc := range testCases {
104+
t.Run(tc.description, func(t *testing.T) {
105+
tmpDir := t.TempDir()
106+
107+
// Prepare file contents, adjusting include globs to be absolute and unique within tmpDir
108+
for name, content := range tc.confs {
109+
if name == topLevelConf && len(tc.confs) > 1 {
110+
content = strings.ReplaceAll(content, "include INCLUDED_PATTERN*", "include "+tmpDir+"/INCLUDED_PATTERN*")
111+
}
112+
err := os.WriteFile(tmpDir+"/"+name, []byte(content), 0600)
113+
require.NoError(t, err)
114+
}
115+
116+
topLevelConfPath := tmpDir + "/" + topLevelConf
117+
filtered, err := filterDirectories(topLevelConfPath, tc.input...)
118+
119+
require.NoError(t, err)
120+
require.Equal(t, tc.expected, filtered)
121+
})
122+
}
123+
}

tests/e2e/nvidia-container-toolkit_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,4 +533,31 @@ EOF`)
533533
Expect(err).ToNot(HaveOccurred())
534534
})
535535
})
536+
537+
When("running a container an ubuntu container with specific ld.so.conf ordering", Ordered, func() {
538+
var (
539+
expectedOutput string
540+
)
541+
BeforeAll(func(ctx context.Context) {
542+
_, _, err := runner.Run(`docker build -t libordering \
543+
- <<EOF
544+
FROM ubuntu
545+
ENV NVIDIA_VISIBLE_DEVICES=all
546+
RUN mkdir -p /extra/lib
547+
RUN cp /usr/lib/$(uname -m)-linux-gnu/libc.so.? /extra/lib/
548+
RUN echo "/extra/lib" > /etc/ld.so.conf.d/00-xxx.conf
549+
RUN ldconfig
550+
EOF`)
551+
Expect(err).ToNot(HaveOccurred())
552+
553+
expectedOutput, _, err = runner.Run(`docker run --rm --runtime=runc libordering bash -c "ldconfig -p | grep libc.so."`)
554+
Expect(err).ToNot(HaveOccurred())
555+
})
556+
557+
It("should not change the ordering of libraries", func(ctx context.Context) {
558+
output, _, err := runner.Run(`docker run --rm --runtime=nvidia libordering bash -c "ldconfig -p | grep libc.so."`)
559+
Expect(err).ToNot(HaveOccurred())
560+
Expect(output).To(Equal(expectedOutput))
561+
})
562+
})
536563
})

0 commit comments

Comments
 (0)