Skip to content

Commit a7a8df2

Browse files
authored
Merge pull request #1403 from elezar/selective-ldcache-config
Filter already tracked directories from ldcache update
2 parents 83235a7 + 1bd5242 commit a7a8df2

File tree

4 files changed

+350
-33
lines changed

4 files changed

+350
-33
lines changed

cmd/nvidia-cdi-hook/update-ldcache/update-ldcache.go

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -140,25 +140,11 @@ func updateLdCacheHandler() {
140140
// It is invoked from a reexec'd handler and provides namespace isolation for
141141
// the operations performed by this hook. At the point where this is invoked,
142142
// we are in a new mount namespace that is cloned from the parent.
143-
//
144-
// args[0] is the reexec initializer function name
145-
// args[1] is the path of the ldconfig binary on the host
146-
// args[2] is the container root directory
147-
// The remaining args are folders where soname symlinks need to be created.
148143
func updateLdCache(args []string) error {
149-
if len(args) < 3 {
150-
return fmt.Errorf("incorrect arguments: %v", args)
151-
}
152-
hostLdconfigPath := args[1]
153-
containerRootDirPath := args[2]
154-
155-
ldconfig, err := ldconfig.New(
156-
hostLdconfigPath,
157-
containerRootDirPath,
158-
)
144+
ldconfig, err := ldconfig.NewFromArgs(args...)
159145
if err != nil {
160146
return fmt.Errorf("failed to construct ldconfig runner: %w", err)
161147
}
162148

163-
return ldconfig.UpdateLDCache(args[3:]...)
149+
return ldconfig.UpdateLDCache()
164150
}

internal/ldconfig/ldconfig.go

Lines changed: 195 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,13 @@
1818
package ldconfig
1919

2020
import (
21+
"bufio"
22+
"flag"
2123
"fmt"
2224
"os"
2325
"os/exec"
2426
"path/filepath"
27+
"runtime"
2528
"strings"
2629

2730
"github.com/NVIDIA/nvidia-container-toolkit/internal/config"
@@ -37,53 +40,94 @@ const (
3740
)
3841

3942
type Ldconfig struct {
40-
ldconfigPath string
41-
inRoot string
43+
ldconfigPath string
44+
inRoot string
45+
isDebianLikeHost bool
46+
isDebianLikeContainer bool
47+
directories []string
4248
}
4349

4450
// NewRunner creates an exec.Cmd that can be used to run ldconfig.
4551
func NewRunner(id string, ldconfigPath string, containerRoot string, additionalargs ...string) (*exec.Cmd, error) {
4652
args := []string{
4753
id,
48-
strings.TrimPrefix(config.NormalizeLDConfigPath("@"+ldconfigPath), "@"),
49-
containerRoot,
54+
"--ldconfig-path", strings.TrimPrefix(config.NormalizeLDConfigPath("@"+ldconfigPath), "@"),
55+
"--container-root", containerRoot,
56+
}
57+
if isDebian() {
58+
args = append(args, "--is-debian-like-host")
5059
}
5160
args = append(args, additionalargs...)
5261

5362
return createReexecCommand(args)
5463
}
5564

56-
// New creates an Ldconfig struct that is used to perform operations on the
57-
// ldcache and libraries in a particular root (e.g. a container).
58-
func New(ldconfigPath string, inRoot string) (*Ldconfig, error) {
59-
l := &Ldconfig{
60-
ldconfigPath: ldconfigPath,
61-
inRoot: inRoot,
65+
// NewFromArgs creates an Ldconfig struct from the args passed to the Cmd
66+
// above.
67+
// This struct is used to perform operations on the ldcache and libraries in a
68+
// particular root (e.g. a container).
69+
//
70+
// args[0] is the reexec initializer function name
71+
// The following flags are required:
72+
//
73+
// --ldconfig-path=LDCONFIG_PATH the path to ldconfig on the host
74+
// --container-root=CONTAINER_ROOT the path in which ldconfig must be run
75+
//
76+
// The following flags are optional:
77+
//
78+
// --is-debian-like-host Indicates that the host system is debian-based.
79+
//
80+
// The remaining args are folders where soname symlinks need to be created.
81+
func NewFromArgs(args ...string) (*Ldconfig, error) {
82+
if len(args) < 1 {
83+
return nil, fmt.Errorf("incorrect arguments: %v", args)
6284
}
63-
if ldconfigPath == "" {
85+
fs := flag.NewFlagSet(args[1], flag.ExitOnError)
86+
ldconfigPath := fs.String("ldconfig-path", "", "the path to ldconfig on the host")
87+
containerRoot := fs.String("container-root", "", "the path in which ldconfig must be run")
88+
isDebianLikeHost := fs.Bool("is-debian-like-host", false, "the hook is running from a Debian-like host")
89+
if err := fs.Parse(args[1:]); err != nil {
90+
return nil, err
91+
}
92+
93+
if *ldconfigPath == "" {
6494
return nil, fmt.Errorf("an ldconfig path must be specified")
6595
}
66-
if inRoot == "" || inRoot == "/" {
96+
if *containerRoot == "" || *containerRoot == "/" {
6797
return nil, fmt.Errorf("ldconfig must be run in the non-system root")
6898
}
99+
100+
l := &Ldconfig{
101+
ldconfigPath: *ldconfigPath,
102+
inRoot: *containerRoot,
103+
isDebianLikeHost: *isDebianLikeHost,
104+
isDebianLikeContainer: isDebian(),
105+
directories: fs.Args(),
106+
}
69107
return l, nil
70108
}
71109

72-
func (l *Ldconfig) UpdateLDCache(directories ...string) error {
110+
func (l *Ldconfig) UpdateLDCache() error {
73111
ldconfigPath, err := l.prepareRoot()
74112
if err != nil {
75113
return err
76114
}
77115

116+
// Explicitly specify using /etc/ld.so.conf since the host's ldconfig may
117+
// 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...)
120+
if err != nil {
121+
return err
122+
}
123+
78124
args := []string{
79125
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",
126+
"-f", topLevelLdsoconfFilePath,
83127
"-C", "/etc/ld.so.cache",
84128
}
85129

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

@@ -113,6 +157,22 @@ func (l *Ldconfig) prepareRoot() (string, error) {
113157
return ldconfigPath, nil
114158
}
115159

160+
func (l *Ldconfig) filterDirectories(configFilePath string, directories ...string) ([]string, error) {
161+
ldconfigDirs, err := l.getLdsoconfDirectories(configFilePath)
162+
if err != nil {
163+
return nil, err
164+
}
165+
166+
var filtered []string
167+
for _, d := range directories {
168+
if _, ok := ldconfigDirs[d]; ok {
169+
continue
170+
}
171+
filtered = append(filtered, d)
172+
}
173+
return filtered, nil
174+
}
175+
116176
// createLdsoconfdFile creates a file at /etc/ld.so.conf.d/.
117177
// The file is created at /etc/ld.so.conf.d/{{ .pattern }} using `CreateTemp` and
118178
// contains the specified directories on each line.
@@ -153,3 +213,121 @@ func createLdsoconfdFile(pattern string, dirs ...string) error {
153213

154214
return nil
155215
}
216+
217+
// getLdsoconfDirectories returns a map of ldsoconf directories to the conf
218+
// files that refer to the directory.
219+
func (l *Ldconfig) getLdsoconfDirectories(configFilePath string) (map[string]struct{}, error) {
220+
ldconfigDirs := make(map[string]struct{})
221+
for _, d := range l.getSystemSerachPaths() {
222+
ldconfigDirs[d] = struct{}{}
223+
}
224+
225+
processedConfFiles := make(map[string]bool)
226+
ldsoconfFilenames := []string{configFilePath}
227+
for len(ldsoconfFilenames) > 0 {
228+
ldsoconfFilename := ldsoconfFilenames[0]
229+
ldsoconfFilenames = ldsoconfFilenames[1:]
230+
if processedConfFiles[ldsoconfFilename] {
231+
continue
232+
}
233+
processedConfFiles[ldsoconfFilename] = true
234+
235+
if len(ldsoconfFilename) == 0 {
236+
continue
237+
}
238+
directories, includedFilenames, err := processLdsoconfFile(ldsoconfFilename)
239+
if err != nil {
240+
return nil, err
241+
}
242+
ldsoconfFilenames = append(ldsoconfFilenames, includedFilenames...)
243+
for _, d := range directories {
244+
ldconfigDirs[d] = struct{}{}
245+
}
246+
}
247+
return ldconfigDirs, nil
248+
}
249+
250+
func (l *Ldconfig) getSystemSerachPaths() []string {
251+
if l.isDebianLikeContainer {
252+
debianSystemSearchPaths()
253+
}
254+
return nonDebianSystemSearchPaths()
255+
}
256+
257+
// processLdsoconfFile extracts the list of directories and included configs
258+
// from the specified file.
259+
func processLdsoconfFile(ldsoconfFilename string) ([]string, []string, error) {
260+
ldsoconf, err := os.Open(ldsoconfFilename)
261+
if os.IsNotExist(err) {
262+
return nil, nil, nil
263+
}
264+
if err != nil {
265+
return nil, nil, err
266+
}
267+
defer ldsoconf.Close()
268+
269+
var directories []string
270+
var includedFilenames []string
271+
scanner := bufio.NewScanner(ldsoconf)
272+
for scanner.Scan() {
273+
line := strings.TrimSpace(scanner.Text())
274+
switch {
275+
case strings.HasPrefix(line, "#") || len(line) == 0:
276+
continue
277+
case strings.HasPrefix(line, "include "):
278+
include, err := filepath.Glob(strings.TrimPrefix(line, "include "))
279+
if err != nil {
280+
// We ignore invalid includes.
281+
// TODO: How does ldconfig handle this?
282+
continue
283+
}
284+
includedFilenames = append(includedFilenames, include...)
285+
default:
286+
directories = append(directories, line)
287+
}
288+
}
289+
return directories, includedFilenames, nil
290+
}
291+
292+
func isDebian() bool {
293+
info, err := os.Stat("/etc/debian_version")
294+
if err != nil {
295+
return false
296+
}
297+
return !info.IsDir()
298+
}
299+
300+
// nonDebianSystemSearchPaths returns the system search paths for non-Debian
301+
// systems.
302+
//
303+
// This list was taken from the output of:
304+
//
305+
// docker run --rm -ti redhat/ubi9 /usr/lib/ld-linux-aarch64.so.1 --help | grep -A6 "Shared library search path"
306+
func nonDebianSystemSearchPaths() []string {
307+
return []string{"/lib64", "/usr/lib64"}
308+
}
309+
310+
// debianSystemSearchPaths returns the system search paths for Debian-like
311+
// systems.
312+
//
313+
// This list was taken from the output of:
314+
//
315+
// docker run --rm -ti ubuntu /usr/lib/aarch64-linux-gnu/ld-linux-aarch64.so.1 --help | grep -A6 "Shared library search path"
316+
func debianSystemSearchPaths() []string {
317+
var paths []string
318+
switch runtime.GOARCH {
319+
case "amd64":
320+
paths = append(paths,
321+
"/lib/x86_64-linux-gnu",
322+
"/usr/lib/x86_64-linux-gnu",
323+
)
324+
case "arm64":
325+
paths = append(paths,
326+
"/lib/aarch64-linux-gnu",
327+
"/usr/lib/aarch64-linux-gnu",
328+
)
329+
}
330+
paths = append(paths, "/lib", "/usr/lib")
331+
332+
return paths
333+
}

0 commit comments

Comments
 (0)