Skip to content

Commit 929c64b

Browse files
committed
ldconfig: Create ld.so.conf file if missing
Some container images (namely distroless images) may not have the /etc/ld.so.conf file present. This patch modifies the ldconfig CDI hook to create a "standard" top-level ld.so.conf file if it is missing that includes "standard" /etc/ld.so.conf.d/*.conf drop-in files. Signed-off-by: Jean-Francois Roy <[email protected]>
1 parent 07f71f1 commit 929c64b

File tree

2 files changed

+155
-13
lines changed

2 files changed

+155
-13
lines changed

internal/ldconfig/ldconfig.go

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,15 @@ const (
3737
// higher precedence than other libraries on the system, but lower than
3838
// the 00-cuda-compat that is included in some containers.
3939
ldsoconfdFilenamePattern = "00-nvcr-*.conf"
40+
// defaultTopLevelLdsoconfFilePath is the standard location of the top-level ld.so.conf file.
41+
// Most container images based on a distro will have this file, but distroless container images
42+
// may not.
43+
defaultTopLevelLdsoconfFilePath = "/etc/ld.so.conf"
44+
// defaultLdsoconfdDir is the standard location for the ld.so.conf.d drop-in directory. Most
45+
// container images based on a distro will have this directory included by the top-level
46+
// ld.so.conf file, but some may not. And some container images may not have a top-level
47+
// ld.so.conf file at all.
48+
defaultLdsoconfdDir = "/etc/ld.so.conf.d"
4049
)
4150

4251
type Ldconfig struct {
@@ -115,20 +124,22 @@ func (l *Ldconfig) UpdateLDCache() error {
115124

116125
// Explicitly specify using /etc/ld.so.conf since the host's ldconfig may
117126
// be configured to use a different config file by default.
118-
const topLevelLdsoconfFilePath = "/etc/ld.so.conf"
119-
filteredDirectories, err := l.filterDirectories(topLevelLdsoconfFilePath, l.directories...)
127+
filteredDirectories, err := l.filterDirectories(defaultTopLevelLdsoconfFilePath, l.directories...)
120128
if err != nil {
121129
return err
122130
}
123131

124132
args := []string{
125133
filepath.Base(ldconfigPath),
126-
"-f", topLevelLdsoconfFilePath,
134+
"-f", defaultTopLevelLdsoconfFilePath,
127135
"-C", "/etc/ld.so.cache",
128136
}
129137

130-
if err := createLdsoconfdFile(ldsoconfdFilenamePattern, filteredDirectories...); err != nil {
131-
return fmt.Errorf("failed to update ld.so.conf.d: %w", err)
138+
if err := ensureLdsoconfFile(defaultTopLevelLdsoconfFilePath, defaultLdsoconfdDir); err != nil {
139+
return fmt.Errorf("failed to ensure ld.so.conf file: %w", err)
140+
}
141+
if err := createLdsoconfdFile(defaultLdsoconfdDir, ldsoconfdFilenamePattern, filteredDirectories...); err != nil {
142+
return fmt.Errorf("failed to create ld.so.conf.d drop-in file: %w", err)
132143
}
133144

134145
return SafeExec(ldconfigPath, args, nil)
@@ -179,19 +190,15 @@ func (l *Ldconfig) filterDirectories(configFilePath string, directories ...strin
179190
return filtered, nil
180191
}
181192

182-
// createLdsoconfdFile creates a file at /etc/ld.so.conf.d/.
183-
// The file is created at /etc/ld.so.conf.d/{{ .pattern }} using `CreateTemp` and
184-
// contains the specified directories on each line.
185-
func createLdsoconfdFile(pattern string, dirs ...string) error {
193+
// createLdsoconfdFile creates a ld.so.conf.d drop-in file with the specified directories on each
194+
// line. The file is created at `ldsoconfdDir`/{{ .pattern }} using `CreateTemp`.
195+
func createLdsoconfdFile(ldsoconfdDir, pattern string, dirs ...string) error {
186196
if len(dirs) == 0 {
187197
return nil
188198
}
189-
190-
ldsoconfdDir := "/etc/ld.so.conf.d"
191199
if err := os.MkdirAll(ldsoconfdDir, 0755); err != nil {
192200
return fmt.Errorf("failed to create ld.so.conf.d: %w", err)
193201
}
194-
195202
configFile, err := os.CreateTemp(ldsoconfdDir, pattern)
196203
if err != nil {
197204
return fmt.Errorf("failed to create config file: %w", err)
@@ -205,7 +212,7 @@ func createLdsoconfdFile(pattern string, dirs ...string) error {
205212
if added[dir] {
206213
continue
207214
}
208-
_, err = fmt.Fprintf(configFile, "%s\n", dir)
215+
_, err := fmt.Fprintf(configFile, "%s\n", dir)
209216
if err != nil {
210217
return fmt.Errorf("failed to update config file: %w", err)
211218
}
@@ -220,6 +227,19 @@ func createLdsoconfdFile(pattern string, dirs ...string) error {
220227
return nil
221228
}
222229

230+
// ensureLdsoconfFile creates a "standard" top-level ld.so.conf file if none exists.
231+
//
232+
// The created file will contain a single include statement for "`ldsoconfdDir`/*.conf".
233+
func ensureLdsoconfFile(topLevelLdsoconfFilePath, ldsoconfdDir string) error {
234+
configFile, err := os.OpenFile(topLevelLdsoconfFilePath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0644)
235+
if err != nil && !os.IsExist(err) {
236+
return fmt.Errorf("failed to create top-level ld.so.conf file: %w", err)
237+
}
238+
defer configFile.Close()
239+
configFile.WriteString("include " + ldsoconfdDir + "/*.conf\n")
240+
return nil
241+
}
242+
223243
// getLdsoconfDirectories returns a map of ldsoconf directories to the conf
224244
// files that refer to the directory.
225245
func (l *Ldconfig) getLdsoconfDirectories(configFilePath string) (map[string]struct{}, error) {

internal/ldconfig/ldconfig_test.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package ldconfig
1919

2020
import (
2121
"os"
22+
"path/filepath"
2223
"strings"
2324
"testing"
2425

@@ -124,3 +125,124 @@ include INCLUDED_PATTERN*
124125
})
125126
}
126127
}
128+
129+
func TestCreateLdsoconfdFile(t *testing.T) {
130+
testCases := []struct {
131+
description string
132+
pattern string
133+
dirs []string
134+
expectedContent []string
135+
}{
136+
{
137+
description: "empty directories",
138+
pattern: "test-*.conf",
139+
dirs: []string{},
140+
expectedContent: nil,
141+
},
142+
{
143+
description: "single directory",
144+
pattern: "test-*.conf",
145+
dirs: []string{"/usr/local/lib"},
146+
expectedContent: []string{
147+
"/usr/local/lib",
148+
},
149+
},
150+
{
151+
description: "multiple directories",
152+
pattern: "test-*.conf",
153+
dirs: []string{"/usr/local/lib", "/opt/lib", "/usr/lib64"},
154+
expectedContent: []string{
155+
"/usr/local/lib",
156+
"/opt/lib",
157+
"/usr/lib64",
158+
},
159+
},
160+
{
161+
description: "duplicate directories",
162+
pattern: "test-*.conf",
163+
dirs: []string{"/usr/local/lib", "/opt/lib", "/usr/local/lib"},
164+
expectedContent: []string{
165+
"/usr/local/lib",
166+
"/opt/lib",
167+
},
168+
},
169+
}
170+
171+
for _, tc := range testCases {
172+
t.Run(tc.description, func(t *testing.T) {
173+
tmpDir := t.TempDir()
174+
175+
err := createLdsoconfdFile(tmpDir, tc.pattern, tc.dirs...)
176+
require.NoError(t, err)
177+
178+
if len(tc.expectedContent) == 0 {
179+
entries, err := os.ReadDir(tmpDir)
180+
require.NoError(t, err)
181+
require.Empty(t, entries)
182+
return
183+
}
184+
185+
entries, err := os.ReadDir(tmpDir)
186+
require.NoError(t, err)
187+
require.Len(t, entries, 1)
188+
createdFile := filepath.Join(tmpDir, entries[0].Name())
189+
190+
info, err := os.Stat(createdFile)
191+
require.NoError(t, err)
192+
require.Equal(t, os.FileMode(0644), info.Mode().Perm())
193+
194+
content, err := os.ReadFile(createdFile)
195+
require.NoError(t, err)
196+
lines := strings.Split(strings.TrimSpace(string(content)), "\n")
197+
require.Equal(t, tc.expectedContent, lines)
198+
})
199+
}
200+
}
201+
202+
func TestEnsureLdsoconfFile(t *testing.T) {
203+
testCases := []struct {
204+
description string
205+
existingContent string
206+
ldsoconfdDir string
207+
expectCreation bool
208+
expectedContent string
209+
}{
210+
{
211+
description: "creates file when none exists",
212+
existingContent: "",
213+
ldsoconfdDir: "/custom/ld.so.conf.d",
214+
expectCreation: true,
215+
expectedContent: "include /custom/ld.so.conf.d/*.conf\n",
216+
},
217+
{
218+
description: "does not modify existing file",
219+
existingContent: "# custom config\n/usr/local/lib\n",
220+
ldsoconfdDir: "/etc/ld.so.conf.d",
221+
expectCreation: false,
222+
expectedContent: "# custom config\n/usr/local/lib\n",
223+
},
224+
}
225+
226+
for _, tc := range testCases {
227+
t.Run(tc.description, func(t *testing.T) {
228+
tmpDir := t.TempDir()
229+
confFilePath := filepath.Join(tmpDir, "ld.so.conf")
230+
231+
if tc.existingContent != "" {
232+
err := os.WriteFile(confFilePath, []byte(tc.existingContent), 0644)
233+
require.NoError(t, err)
234+
}
235+
236+
err := ensureLdsoconfFile(confFilePath, tc.ldsoconfdDir)
237+
require.NoError(t, err)
238+
239+
info, err := os.Stat(confFilePath)
240+
require.NoError(t, err)
241+
require.Equal(t, os.FileMode(0644), info.Mode().Perm())
242+
243+
content, err := os.ReadFile(confFilePath)
244+
require.NoError(t, err)
245+
require.Equal(t, tc.expectedContent, string(content))
246+
})
247+
}
248+
}

0 commit comments

Comments
 (0)