Skip to content
Merged
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/NVIDIA/nvidia-container-toolkit
go 1.25.0

require (
github.com/NVIDIA/go-nvlib v0.9.0
github.com/NVIDIA/go-nvlib v0.9.1-0.20251202135446-d0f42ba016dd
github.com/NVIDIA/go-nvml v0.13.0-1
github.com/google/uuid v1.6.0
github.com/moby/sys/mountinfo v0.7.2
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cyphar.com/go-pathrs v0.2.1 h1:9nx1vOgwVvX1mNBWDu93+vaceedpbsDqo+XuBGL40b8=
cyphar.com/go-pathrs v0.2.1/go.mod h1:y8f1EMG7r+hCuFf/rXsKqMJrJAUoADZGNh5/vZPKcGc=
github.com/NVIDIA/go-nvlib v0.9.0 h1:GKLIvLJ0uhCtTLLZp2Q8QIDRxOYH45MM4Y5OO3U5Rho=
github.com/NVIDIA/go-nvlib v0.9.0/go.mod h1:7mzx9FSdO9fXWP9NKuZmWkCwhkEcSWQFe2tmFwtLb9c=
github.com/NVIDIA/go-nvlib v0.9.1-0.20251202135446-d0f42ba016dd h1:MC1w/VYuo9Zt0se4SSx9BVid4a46ai+voN3knRvVWjE=
github.com/NVIDIA/go-nvlib v0.9.1-0.20251202135446-d0f42ba016dd/go.mod h1:7mzx9FSdO9fXWP9NKuZmWkCwhkEcSWQFe2tmFwtLb9c=
github.com/NVIDIA/go-nvml v0.13.0-1 h1:OLX8Jq3dONuPOQPC7rndB6+iDmDakw0XTYgzMxObkEw=
github.com/NVIDIA/go-nvml v0.13.0-1/go.mod h1:+KNA7c7gIBH7SKSJ1ntlwkfN80zdx8ovl4hrK3LmPt4=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
Expand Down
5 changes: 1 addition & 4 deletions internal/info/auto_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,13 +215,10 @@ func TestResolveAutoMode(t *testing.T) {
HasDXCoreFunc: func() (bool, string) {
return tc.info["dxcore"], "dxcore"
},
IsTegraSystemFunc: func() (bool, string) {
return tc.info["tegra"], "tegra"
},
HasTegraFilesFunc: func() (bool, string) {
return tc.info["tegra"], "tegra"
},
HasOnlyIntegratedGPUsFunc: func() (bool, string) {
HasAnIntegratedGPUFunc: func() (bool, string) {
return tc.info["nvgpu"], "nvgpu"
},
}
Expand Down
35 changes: 15 additions & 20 deletions internal/platform-support/tegra/csv.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,13 @@ import (
"github.com/NVIDIA/nvidia-container-toolkit/internal/platform-support/tegra/csv"
)

// newDiscovererFromCSVFiles creates a discoverer for the specified CSV files. A logger is also supplied.
// The constructed discoverer is comprised of a list, with each element in the list being associated with a
// single CSV files.
func (o tegraOptions) newDiscovererFromCSVFiles() (discover.Discover, error) {
if len(o.csvFiles) == 0 {
o.logger.Warningf("No CSV files specified")
// newDiscovererFromMountSpecs creates a discoverer for the specified mount specs.
func (o options) newDiscovererFromMountSpecs(targetsByType MountSpecPathsByType) (discover.Discover, error) {
if len(targetsByType) == 0 {
o.logger.Warningf("No mount specs specified")
return discover.None{}, nil
}

targetsByType := getTargetsFromCSVFiles(o.logger, o.csvFiles)

devices := discover.NewCharDeviceDiscoverer(
o.logger,
o.devRoot,
Expand Down Expand Up @@ -64,15 +60,13 @@ func (o tegraOptions) newDiscovererFromCSVFiles() (discover.Discover, error) {
)

// We process the explicitly requested symlinks.
symlinkTargets := o.ignorePatterns.Apply(targetsByType[csv.MountSpecSym]...)
o.logger.Debugf("Filtered symlink targets: %v", symlinkTargets)
symlinks := discover.NewMounts(
o.logger,
o.symlinkLocator,
o.driverRoot,
symlinkTargets,
targetsByType[csv.MountSpecSym],
)
createSymlinks := o.createCSVSymlinkHooks(symlinkTargets)
createSymlinks := o.createCSVSymlinkHooks(targetsByType[csv.MountSpecSym])

d := discover.Merge(
devices,
Expand All @@ -85,23 +79,24 @@ func (o tegraOptions) newDiscovererFromCSVFiles() (discover.Discover, error) {
return d, nil
}

// getTargetsFromCSVFiles returns the list of mount specs from the specified CSV files.
// These are aggregated by mount spec type.
// TODO: We use a function variable here to allow this to be overridden for testing.
// This should be properly mocked.
var getTargetsFromCSVFiles = func(logger logger.Interface, files []string) map[csv.MountSpecType][]string {
targetsByType := make(map[csv.MountSpecType][]string)
for _, filename := range files {
// MountSpecsFromCSVFiles returns a MountSpecPathsByTyper for the specified list
// of CSV files.
func MountSpecsFromCSVFiles(logger logger.Interface, csvFilePaths ...string) MountSpecPathsByType {
var mountSpecs mountSpecPathsByTypers

for _, filename := range csvFilePaths {
targets, err := loadCSVFile(logger, filename)
if err != nil {
logger.Warningf("Skipping CSV file %v: %v", filename, err)
continue
}
targetsByType := make(MountSpecPathsByType)
for _, t := range targets {
targetsByType[t.Type] = append(targetsByType[t.Type], t.Path)
}
mountSpecs = append(mountSpecs, targetsByType)
}
return targetsByType
return mountSpecs.MountSpecPathsByType()
}

// loadCSVFile loads the specified CSV file and returns the list of mount specs
Expand Down
35 changes: 11 additions & 24 deletions internal/platform-support/tegra/csv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,14 @@ import (
"github.com/stretchr/testify/require"

"github.com/NVIDIA/nvidia-container-toolkit/internal/discover"
"github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
"github.com/NVIDIA/nvidia-container-toolkit/internal/lookup"

"github.com/NVIDIA/nvidia-container-toolkit/internal/platform-support/tegra/csv"
)

func TestDiscovererFromCSVFiles(t *testing.T) {
logger, _ := testlog.NewNullLogger()
testCases := []struct {
description string
moutSpecs map[csv.MountSpecType][]string
moutSpecs MountSpecPathsByType
ignorePatterns []string
symlinkLocator lookup.Locator
symlinkChainLocator lookup.Locator
Expand All @@ -49,7 +46,7 @@ func TestDiscovererFromCSVFiles(t *testing.T) {
// TODO: This current resolves to two mounts that are the same.
// These are deduplicated at a later stage. We could consider deduplicating earlier in the pipeline.
description: "symlink is resolved to target; mounts and symlink are created",
moutSpecs: map[csv.MountSpecType][]string{
moutSpecs: MountSpecPathsByType{
"lib": {"/usr/lib/aarch64-linux-gnu/tegra/libv4l2_nvargus.so"},
"sym": {"/usr/lib/aarch64-linux-gnu/libv4l/plugins/nv/libv4l2_nvargus.so"},
},
Expand Down Expand Up @@ -105,7 +102,7 @@ func TestDiscovererFromCSVFiles(t *testing.T) {
// TODO: This current resolves to two mounts that are the same.
// These are deduplicated at a later stage. We could consider deduplicating earlier in the pipeline.
description: "single glob filter does not remove symlink mounts",
moutSpecs: map[csv.MountSpecType][]string{
moutSpecs: MountSpecPathsByType{
"lib": {"/usr/lib/aarch64-linux-gnu/tegra/libv4l2_nvargus.so"},
"sym": {"/usr/lib/aarch64-linux-gnu/libv4l/plugins/nv/libv4l2_nvargus.so"},
},
Expand Down Expand Up @@ -160,7 +157,7 @@ func TestDiscovererFromCSVFiles(t *testing.T) {
},
{
description: "** filter removes symlink mounts",
moutSpecs: map[csv.MountSpecType][]string{
moutSpecs: MountSpecPathsByType{
"lib": {"/usr/lib/aarch64-linux-gnu/tegra/libv4l2_nvargus.so"},
"sym": {"/usr/lib/aarch64-linux-gnu/libv4l/plugins/nv/libv4l2_nvargus.so"},
},
Expand All @@ -186,19 +183,20 @@ func TestDiscovererFromCSVFiles(t *testing.T) {
hookCreator := discover.NewHookCreator()
for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
defer setGetTargetsFromCSVFiles(tc.moutSpecs)()

o := tegraOptions{
o := options{
logger: logger,
hookCreator: hookCreator,
csvFiles: []string{"dummy"},
ignorePatterns: tc.ignorePatterns,
symlinkLocator: tc.symlinkLocator,
symlinkChainLocator: tc.symlinkChainLocator,
resolveSymlink: tc.symlinkResolver,

mountSpecs: Transform(
tc.moutSpecs,
IgnoreSymlinkMountSpecsByPattern(tc.ignorePatterns...),
),
}

d, err := o.newDiscovererFromCSVFiles()
d, err := o.newDiscovererFromMountSpecs(o.mountSpecs.MountSpecPathsByType())
require.ErrorIs(t, err, tc.expectedError)

hooks, err := d.Hooks()
Expand All @@ -212,14 +210,3 @@ func TestDiscovererFromCSVFiles(t *testing.T) {
})
}
}

func setGetTargetsFromCSVFiles(override map[csv.MountSpecType][]string) func() {
original := getTargetsFromCSVFiles
getTargetsFromCSVFiles = func(logger logger.Interface, files []string) map[csv.MountSpecType][]string {
return override
}

return func() {
getTargetsFromCSVFiles = original
}
}
106 changes: 102 additions & 4 deletions internal/platform-support/tegra/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@ package tegra
import (
"path/filepath"
"strings"

"github.com/NVIDIA/nvidia-container-toolkit/internal/platform-support/tegra/csv"
)

type ignoreMountSpecPatterns []string
type ignoreSymlinkMountSpecPatterns []string

func (d ignoreMountSpecPatterns) Match(name string) bool {
func (d ignoreSymlinkMountSpecPatterns) match(name string) bool {
for _, pattern := range d {
target := name
if strings.HasPrefix(pattern, "**/") {
Expand All @@ -37,13 +39,109 @@ func (d ignoreMountSpecPatterns) Match(name string) bool {
return false
}

func (d ignoreMountSpecPatterns) Apply(input ...string) []string {
func (d ignoreSymlinkMountSpecPatterns) filter(input ...string) []string {
var filtered []string
for _, name := range input {
if d.Match(name) {
if d.match(name) {
continue
}
filtered = append(filtered, name)
}
return filtered
}

func (d ignoreSymlinkMountSpecPatterns) Apply(input MountSpecPathsByTyper) MountSpecPathsByTyper {
ms := input.MountSpecPathsByType()

if symlinks, ok := ms[csv.MountSpecSym]; ok {
ms[csv.MountSpecSym] = d.filter(symlinks...)
}

return ms
}

// A filter removes elements from an input list and returns the remaining
// elements.
type filter interface {
apply(...string) []string
}

// A stringMatcher implements the MatchString function.
type stringMatcher interface {
MatchString(string) bool
}

// A matcherAsFilter is used to ensure that a string matcher can be used as a filter.
type matcherAsFilter struct {
stringMatcher
}

type filterByMountSpecType map[csv.MountSpecType]filter

type pathPatterns []string
type pathPattern string
type basenamePattern string

// MatchString for a set of path patterns returns true if any of the patterns
// matches against the input string.
func (d pathPatterns) MatchString(input string) bool {
for _, pattern := range d {
if match := pathPattern(pattern).MatchString(input); match {
return true
}
}
return false
}

// MatchString attempts to match a path pattern to the specified input string.
// If the pattern starts with `**/` the input is treated as a path and only
// the basenames are matched using regular glob rules.
func (d pathPattern) MatchString(input string) bool {
if strings.HasPrefix(string(d), "**/") {
return basenamePattern(d).MatchString(input)
}
match, _ := filepath.Match(string(d), input)
return match
}

// MatchString for a basename pattern applies the specified pattern against the
// basename of the input.
// If the pattern starts with **/, this is stripped before attempting to match.
func (d basenamePattern) MatchString(input string) bool {
pattern := strings.TrimPrefix(string(d), "**/")
match, _ := filepath.Match(pattern, filepath.Base(input))
return match
}

// Apply the specified per-type filters to the input mount specs.
func (p filterByMountSpecType) Apply(input MountSpecPathsByTyper) MountSpecPathsByTyper {
ms := input.MountSpecPathsByType()
for t, filter := range p {
if len(ms[t]) == 0 {
continue
}
ms[t] = filter.apply(ms[t]...)
}
return ms
}

// apply uses a matcher to filter an input string.
// Each element in the input that matches is skipped and the remaining elements
// are returned.
func (f *matcherAsFilter) apply(input ...string) []string {
var filtered []string
for _, path := range input {
if f.MatchString(path) {
continue
}
filtered = append(filtered, path)
}
return filtered
}

// removeAll is a filter that will not return any inputs.
type removeAll struct{}

func (a removeAll) apply(...string) []string {
return nil
}
4 changes: 2 additions & 2 deletions internal/platform-support/tegra/filter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import (
func TestIgnorePatterns(t *testing.T) {
testCases := []struct {
description string
blockedFilter []string
blockedFilter pathPatterns
input []string
expected []string
}{
Expand All @@ -50,7 +50,7 @@ func TestIgnorePatterns(t *testing.T) {

for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
filtered := ignoreMountSpecPatterns(tc.blockedFilter).Apply(tc.input...)
filtered := ignoreSymlinkMountSpecPatterns(tc.blockedFilter).filter(tc.input...)
require.ElementsMatch(t, tc.expected, filtered)
})
}
Expand Down
Loading