Skip to content

Commit 5b1d918

Browse files
committed
Filter already tracked directories from ldcache update
Signed-off-by: Evan Lezar <[email protected]>
1 parent e6eaa43 commit 5b1d918

File tree

2 files changed

+188
-4
lines changed

2 files changed

+188
-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+
}

0 commit comments

Comments
 (0)