Skip to content

Commit e1075cb

Browse files
authored
Merge pull request #1459 from jfroy/create-ldsoconf-if-missing
ldconfig: Create ld.so.conf file if missing
2 parents 25f7bfa + f2ac792 commit e1075cb

File tree

2 files changed

+161
-13
lines changed

2 files changed

+161
-13
lines changed

internal/ldconfig/ldconfig.go

Lines changed: 39 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 {
@@ -117,20 +126,22 @@ func (l *Ldconfig) UpdateLDCache() error {
117126

118127
// Explicitly specify using /etc/ld.so.conf since the host's ldconfig may
119128
// be configured to use a different config file by default.
120-
const topLevelLdsoconfFilePath = "/etc/ld.so.conf"
121-
filteredDirectories, err := l.filterDirectories(topLevelLdsoconfFilePath, l.directories...)
129+
filteredDirectories, err := l.filterDirectories(defaultTopLevelLdsoconfFilePath, l.directories...)
122130
if err != nil {
123131
return err
124132
}
125133

126134
args := []string{
127135
filepath.Base(ldconfigPath),
128-
"-f", topLevelLdsoconfFilePath,
136+
"-f", defaultTopLevelLdsoconfFilePath,
129137
"-C", "/etc/ld.so.cache",
130138
}
131139

132-
if err := createLdsoconfdFile(ldsoconfdFilenamePattern, filteredDirectories...); err != nil {
133-
return fmt.Errorf("failed to update ld.so.conf.d: %w", err)
140+
if err := ensureLdsoconfFile(defaultTopLevelLdsoconfFilePath, defaultLdsoconfdDir); err != nil {
141+
return fmt.Errorf("failed to ensure ld.so.conf file: %w", err)
142+
}
143+
if err := createLdsoconfdFile(defaultLdsoconfdDir, ldsoconfdFilenamePattern, filteredDirectories...); err != nil {
144+
return fmt.Errorf("failed to create ld.so.conf.d drop-in file: %w", err)
134145
}
135146

136147
return SafeExec(ldconfigPath, args, nil)
@@ -181,19 +192,15 @@ func (l *Ldconfig) filterDirectories(configFilePath string, directories ...strin
181192
return filtered, nil
182193
}
183194

184-
// createLdsoconfdFile creates a file at /etc/ld.so.conf.d/.
185-
// The file is created at /etc/ld.so.conf.d/{{ .pattern }} using `CreateTemp` and
186-
// contains the specified directories on each line.
187-
func createLdsoconfdFile(pattern string, dirs ...string) error {
195+
// createLdsoconfdFile creates a ld.so.conf.d drop-in file with the specified directories on each
196+
// line. The file is created at `ldsoconfdDir`/{{ .pattern }} using `CreateTemp`.
197+
func createLdsoconfdFile(ldsoconfdDir, pattern string, dirs ...string) error {
188198
if len(dirs) == 0 {
189199
return nil
190200
}
191-
192-
ldsoconfdDir := "/etc/ld.so.conf.d"
193201
if err := os.MkdirAll(ldsoconfdDir, 0755); err != nil {
194202
return fmt.Errorf("failed to create ld.so.conf.d: %w", err)
195203
}
196-
197204
configFile, err := os.CreateTemp(ldsoconfdDir, pattern)
198205
if err != nil {
199206
return fmt.Errorf("failed to create config file: %w", err)
@@ -207,7 +214,7 @@ func createLdsoconfdFile(pattern string, dirs ...string) error {
207214
if added[dir] {
208215
continue
209216
}
210-
_, err = fmt.Fprintf(configFile, "%s\n", dir)
217+
_, err := fmt.Fprintf(configFile, "%s\n", dir)
211218
if err != nil {
212219
return fmt.Errorf("failed to update config file: %w", err)
213220
}
@@ -222,6 +229,25 @@ func createLdsoconfdFile(pattern string, dirs ...string) error {
222229
return nil
223230
}
224231

232+
// ensureLdsoconfFile creates a "standard" top-level ld.so.conf file if none exists.
233+
//
234+
// The created file will contain a single include statement for "`ldsoconfdDir`/*.conf".
235+
func ensureLdsoconfFile(topLevelLdsoconfFilePath, ldsoconfdDir string) error {
236+
configFile, err := os.OpenFile(topLevelLdsoconfFilePath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0644)
237+
if err != nil {
238+
if os.IsExist(err) {
239+
return nil
240+
}
241+
return fmt.Errorf("failed to create top-level ld.so.conf file: %w", err)
242+
}
243+
defer configFile.Close()
244+
_, err = configFile.WriteString("include " + ldsoconfdDir + "/*.conf\n")
245+
if err != nil {
246+
return fmt.Errorf("failed to write to top-level ld.so.conf file: %w", err)
247+
}
248+
return nil
249+
}
250+
225251
// getLdsoconfDirectories returns a map of ldsoconf directories to the conf
226252
// files that refer to the directory.
227253
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) //nolint:gosec
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)