Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 39 additions & 13 deletions internal/ldconfig/ldconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ const (
// higher precedence than other libraries on the system, but lower than
// the 00-cuda-compat that is included in some containers.
ldsoconfdFilenamePattern = "00-nvcr-*.conf"
// defaultTopLevelLdsoconfFilePath is the standard location of the top-level ld.so.conf file.
// Most container images based on a distro will have this file, but distroless container images
// may not.
defaultTopLevelLdsoconfFilePath = "/etc/ld.so.conf"
// defaultLdsoconfdDir is the standard location for the ld.so.conf.d drop-in directory. Most
// container images based on a distro will have this directory included by the top-level
// ld.so.conf file, but some may not. And some container images may not have a top-level
// ld.so.conf file at all.
defaultLdsoconfdDir = "/etc/ld.so.conf.d"
)

type Ldconfig struct {
Expand Down Expand Up @@ -115,20 +124,22 @@ func (l *Ldconfig) UpdateLDCache() error {

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

args := []string{
filepath.Base(ldconfigPath),
"-f", topLevelLdsoconfFilePath,
"-f", defaultTopLevelLdsoconfFilePath,
"-C", "/etc/ld.so.cache",
}

if err := createLdsoconfdFile(ldsoconfdFilenamePattern, filteredDirectories...); err != nil {
return fmt.Errorf("failed to update ld.so.conf.d: %w", err)
if err := ensureLdsoconfFile(defaultTopLevelLdsoconfFilePath, defaultLdsoconfdDir); err != nil {
return fmt.Errorf("failed to ensure ld.so.conf file: %w", err)
}
if err := createLdsoconfdFile(defaultLdsoconfdDir, ldsoconfdFilenamePattern, filteredDirectories...); err != nil {
return fmt.Errorf("failed to create ld.so.conf.d drop-in file: %w", err)
}

return SafeExec(ldconfigPath, args, nil)
Expand Down Expand Up @@ -179,19 +190,15 @@ func (l *Ldconfig) filterDirectories(configFilePath string, directories ...strin
return filtered, nil
}

// createLdsoconfdFile creates a file at /etc/ld.so.conf.d/.
// The file is created at /etc/ld.so.conf.d/{{ .pattern }} using `CreateTemp` and
// contains the specified directories on each line.
func createLdsoconfdFile(pattern string, dirs ...string) error {
// createLdsoconfdFile creates a ld.so.conf.d drop-in file with the specified directories on each
// line. The file is created at `ldsoconfdDir`/{{ .pattern }} using `CreateTemp`.
func createLdsoconfdFile(ldsoconfdDir, pattern string, dirs ...string) error {
if len(dirs) == 0 {
return nil
}

ldsoconfdDir := "/etc/ld.so.conf.d"
if err := os.MkdirAll(ldsoconfdDir, 0755); err != nil {
return fmt.Errorf("failed to create ld.so.conf.d: %w", err)
}

configFile, err := os.CreateTemp(ldsoconfdDir, pattern)
if err != nil {
return fmt.Errorf("failed to create config file: %w", err)
Expand All @@ -205,7 +212,7 @@ func createLdsoconfdFile(pattern string, dirs ...string) error {
if added[dir] {
continue
}
_, err = fmt.Fprintf(configFile, "%s\n", dir)
_, err := fmt.Fprintf(configFile, "%s\n", dir)
if err != nil {
return fmt.Errorf("failed to update config file: %w", err)
}
Expand All @@ -220,6 +227,25 @@ func createLdsoconfdFile(pattern string, dirs ...string) error {
return nil
}

// ensureLdsoconfFile creates a "standard" top-level ld.so.conf file if none exists.
//
// The created file will contain a single include statement for "`ldsoconfdDir`/*.conf".
func ensureLdsoconfFile(topLevelLdsoconfFilePath, ldsoconfdDir string) error {
configFile, err := os.OpenFile(topLevelLdsoconfFilePath, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0644)
if err != nil {
if os.IsExist(err) {
return nil
}
return fmt.Errorf("failed to create top-level ld.so.conf file: %w", err)
}
defer configFile.Close()
_, err = configFile.WriteString("include " + ldsoconfdDir + "/*.conf\n")
if err != nil {
return fmt.Errorf("failed to write to top-level ld.so.conf file: %w", err)
}
return nil
}

// getLdsoconfDirectories returns a map of ldsoconf directories to the conf
// files that refer to the directory.
func (l *Ldconfig) getLdsoconfDirectories(configFilePath string) (map[string]struct{}, error) {
Expand Down
122 changes: 122 additions & 0 deletions internal/ldconfig/ldconfig_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package ldconfig

import (
"os"
"path/filepath"
"strings"
"testing"

Expand Down Expand Up @@ -124,3 +125,124 @@ include INCLUDED_PATTERN*
})
}
}

func TestCreateLdsoconfdFile(t *testing.T) {
testCases := []struct {
description string
pattern string
dirs []string
expectedContent []string
}{
{
description: "empty directories",
pattern: "test-*.conf",
dirs: []string{},
expectedContent: nil,
},
{
description: "single directory",
pattern: "test-*.conf",
dirs: []string{"/usr/local/lib"},
expectedContent: []string{
"/usr/local/lib",
},
},
{
description: "multiple directories",
pattern: "test-*.conf",
dirs: []string{"/usr/local/lib", "/opt/lib", "/usr/lib64"},
expectedContent: []string{
"/usr/local/lib",
"/opt/lib",
"/usr/lib64",
},
},
{
description: "duplicate directories",
pattern: "test-*.conf",
dirs: []string{"/usr/local/lib", "/opt/lib", "/usr/local/lib"},
expectedContent: []string{
"/usr/local/lib",
"/opt/lib",
},
},
}

for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
tmpDir := t.TempDir()

err := createLdsoconfdFile(tmpDir, tc.pattern, tc.dirs...)
require.NoError(t, err)

if len(tc.expectedContent) == 0 {
entries, err := os.ReadDir(tmpDir)
require.NoError(t, err)
require.Empty(t, entries)
return
}

entries, err := os.ReadDir(tmpDir)
require.NoError(t, err)
require.Len(t, entries, 1)
createdFile := filepath.Join(tmpDir, entries[0].Name())

info, err := os.Stat(createdFile)
require.NoError(t, err)
require.Equal(t, os.FileMode(0644), info.Mode().Perm())

content, err := os.ReadFile(createdFile)
require.NoError(t, err)
lines := strings.Split(strings.TrimSpace(string(content)), "\n")
require.Equal(t, tc.expectedContent, lines)
})
}
}

func TestEnsureLdsoconfFile(t *testing.T) {
testCases := []struct {
description string
existingContent string
ldsoconfdDir string
expectCreation bool
expectedContent string
}{
{
description: "creates file when none exists",
existingContent: "",
ldsoconfdDir: "/custom/ld.so.conf.d",
expectCreation: true,
expectedContent: "include /custom/ld.so.conf.d/*.conf\n",
},
{
description: "does not modify existing file",
existingContent: "# custom config\n/usr/local/lib\n",
ldsoconfdDir: "/etc/ld.so.conf.d",
expectCreation: false,
expectedContent: "# custom config\n/usr/local/lib\n",
},
}

for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
tmpDir := t.TempDir()
confFilePath := filepath.Join(tmpDir, "ld.so.conf")

if tc.existingContent != "" {
err := os.WriteFile(confFilePath, []byte(tc.existingContent), 0644) //nolint:gosec
require.NoError(t, err)
}

err := ensureLdsoconfFile(confFilePath, tc.ldsoconfdDir)
require.NoError(t, err)

info, err := os.Stat(confFilePath)
require.NoError(t, err)
require.Equal(t, os.FileMode(0644), info.Mode().Perm())

content, err := os.ReadFile(confFilePath)
require.NoError(t, err)
require.Equal(t, tc.expectedContent, string(content))
})
}
}