mirror of
				https://github.com/NVIDIA/nvidia-container-toolkit
				synced 2025-06-26 18:18:24 +00:00 
			
		
		
		
	Merge branch 'chmod-hook' into 'main'
Add nvidia-ctk hook chmod command to set permissions and ensure permissions of `/dev/nvidia-caps` is set See merge request nvidia/container-toolkit/container-toolkit!232
This commit is contained in:
		
						commit
						61ff3fbd7b
					
				
							
								
								
									
										140
									
								
								cmd/nvidia-ctk/hook/chmod/chmod.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								cmd/nvidia-ctk/hook/chmod/chmod.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,140 @@ | ||||
| /** | ||||
| # Copyright (c) 2022, NVIDIA CORPORATION.  All rights reserved. | ||||
| # | ||||
| # Licensed under the Apache License, Version 2.0 (the "License"); | ||||
| # you may not use this file except in compliance with the License. | ||||
| # You may obtain a copy of the License at | ||||
| # | ||||
| #     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| # | ||||
| # Unless required by applicable law or agreed to in writing, software | ||||
| # distributed under the License is distributed on an "AS IS" BASIS, | ||||
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
| # See the License for the specific language governing permissions and | ||||
| # limitations under the License. | ||||
| **/ | ||||
| 
 | ||||
| package chmod | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 	"syscall" | ||||
| 
 | ||||
| 	"github.com/NVIDIA/nvidia-container-toolkit/internal/lookup" | ||||
| 	"github.com/NVIDIA/nvidia-container-toolkit/internal/oci" | ||||
| 	"github.com/sirupsen/logrus" | ||||
| 	"github.com/urfave/cli/v2" | ||||
| ) | ||||
| 
 | ||||
| type command struct { | ||||
| 	logger *logrus.Logger | ||||
| } | ||||
| 
 | ||||
| type config struct { | ||||
| 	paths         cli.StringSlice | ||||
| 	mode          string | ||||
| 	containerSpec string | ||||
| } | ||||
| 
 | ||||
| // NewCommand constructs a chmod command with the specified logger
 | ||||
| func NewCommand(logger *logrus.Logger) *cli.Command { | ||||
| 	c := command{ | ||||
| 		logger: logger, | ||||
| 	} | ||||
| 	return c.build() | ||||
| } | ||||
| 
 | ||||
| // build the chmod command
 | ||||
| func (m command) build() *cli.Command { | ||||
| 	cfg := config{} | ||||
| 
 | ||||
| 	// Create the 'chmod' command
 | ||||
| 	c := cli.Command{ | ||||
| 		Name:  "chmod", | ||||
| 		Usage: "Set the permissions of folders in the container by running chmod. The container root is prefixed to the specified paths.", | ||||
| 		Before: func(c *cli.Context) error { | ||||
| 			return validateFlags(c, &cfg) | ||||
| 		}, | ||||
| 		Action: func(c *cli.Context) error { | ||||
| 			return m.run(c, &cfg) | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	c.Flags = []cli.Flag{ | ||||
| 		&cli.StringSliceFlag{ | ||||
| 			Name:        "path", | ||||
| 			Usage:       "Specifiy a path to apply the specified mode to", | ||||
| 			Destination: &cfg.paths, | ||||
| 		}, | ||||
| 		&cli.StringFlag{ | ||||
| 			Name:        "mode", | ||||
| 			Usage:       "Specify the file mode", | ||||
| 			Destination: &cfg.mode, | ||||
| 		}, | ||||
| 		&cli.StringFlag{ | ||||
| 			Name:        "container-spec", | ||||
| 			Usage:       "Specify the path to the OCI container spec. If empty or '-' the spec will be read from STDIN", | ||||
| 			Destination: &cfg.containerSpec, | ||||
| 		}, | ||||
| 	} | ||||
| 
 | ||||
| 	return &c | ||||
| } | ||||
| 
 | ||||
| func validateFlags(c *cli.Context, cfg *config) error { | ||||
| 	if strings.TrimSpace(cfg.mode) == "" { | ||||
| 		return fmt.Errorf("a non-empty mode must be specified") | ||||
| 	} | ||||
| 
 | ||||
| 	for _, p := range cfg.paths.Value() { | ||||
| 		if strings.TrimSpace(p) == "" { | ||||
| 			return fmt.Errorf("paths must not be empty") | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func (m command) run(c *cli.Context, cfg *config) error { | ||||
| 	s, err := oci.LoadContainerState(cfg.containerSpec) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to load container state: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	containerRoot, err := s.GetContainerRoot() | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to determined container root: %v", err) | ||||
| 	} | ||||
| 	if containerRoot == "" { | ||||
| 		return fmt.Errorf("empty container root detected") | ||||
| 	} | ||||
| 
 | ||||
| 	paths := m.getPaths(containerRoot, cfg.paths.Value()) | ||||
| 	if len(paths) == 0 { | ||||
| 		m.logger.Debugf("No paths specified; exiting") | ||||
| 		return nil | ||||
| 	} | ||||
| 
 | ||||
| 	locator := lookup.NewExecutableLocator(m.logger, "") | ||||
| 	targets, err := locator.Locate("chmod") | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("failed to locate chmod: %v", err) | ||||
| 	} | ||||
| 	chmodPath := targets[0] | ||||
| 
 | ||||
| 	args := append([]string{filepath.Base(chmodPath), cfg.mode}, paths...) | ||||
| 
 | ||||
| 	return syscall.Exec(chmodPath, args, nil) | ||||
| } | ||||
| 
 | ||||
| // getPaths updates the specified paths relative to the root.
 | ||||
| func (m command) getPaths(root string, paths []string) []string { | ||||
| 	var pathsInRoot []string | ||||
| 	for _, f := range paths { | ||||
| 		pathsInRoot = append(pathsInRoot, filepath.Join(root, f)) | ||||
| 	} | ||||
| 
 | ||||
| 	return pathsInRoot | ||||
| } | ||||
| @ -17,6 +17,7 @@ | ||||
| package hook | ||||
| 
 | ||||
| import ( | ||||
| 	chmod "github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-ctk/hook/chmod" | ||||
| 	symlinks "github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-ctk/hook/create-symlinks" | ||||
| 	ldcache "github.com/NVIDIA/nvidia-container-toolkit/cmd/nvidia-ctk/hook/update-ldcache" | ||||
| 	"github.com/sirupsen/logrus" | ||||
| @ -46,6 +47,7 @@ func (m hookCommand) build() *cli.Command { | ||||
| 	hook.Subcommands = []*cli.Command{ | ||||
| 		ldcache.NewCommand(m.logger), | ||||
| 		symlinks.NewCommand(m.logger), | ||||
| 		chmod.NewCommand(m.logger), | ||||
| 	} | ||||
| 
 | ||||
| 	return &hook | ||||
|  | ||||
| @ -26,6 +26,7 @@ import ( | ||||
| 	"github.com/NVIDIA/nvidia-container-toolkit/internal/discover" | ||||
| 	"github.com/NVIDIA/nvidia-container-toolkit/internal/ldcache" | ||||
| 	"github.com/NVIDIA/nvidia-container-toolkit/internal/lookup" | ||||
| 	"github.com/container-orchestrated-devices/container-device-interface/pkg/cdi" | ||||
| 	specs "github.com/container-orchestrated-devices/container-device-interface/specs-go" | ||||
| 	"github.com/sirupsen/logrus" | ||||
| 	"github.com/urfave/cli/v2" | ||||
| @ -212,7 +213,12 @@ func (m command) generateSpec() (*specs.Spec, error) { | ||||
| 
 | ||||
| 	ldcacheUpdateHook := m.generateUpdateLdCacheHook(libraries) | ||||
| 
 | ||||
| 	spec.ContainerEdits.Hooks = []*specs.Hook{ldcacheUpdateHook} | ||||
| 	deviceFolderPermissionHooks, err := m.generateDeviceFolderPermissionHooks(ldcacheUpdateHook.Path, allDeviceNodes) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to generated permission hooks for device nodes: %v", err) | ||||
| 	} | ||||
| 
 | ||||
| 	spec.ContainerEdits.Hooks = append([]*specs.Hook{ldcacheUpdateHook}, deviceFolderPermissionHooks...) | ||||
| 
 | ||||
| 	return &spec, nil | ||||
| } | ||||
| @ -384,3 +390,49 @@ func (m command) generateUpdateLdCacheHook(libraries []string) *specs.Hook { | ||||
| 		Args:     hook.Args, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (m command) generateDeviceFolderPermissionHooks(nvidiaCTKPath string, deviceNodes []*specs.DeviceNode) ([]*specs.Hook, error) { | ||||
| 	var deviceFolders []string | ||||
| 	seen := make(map[string]bool) | ||||
| 
 | ||||
| 	for _, dn := range deviceNodes { | ||||
| 		if !strings.HasPrefix(dn.Path, "/dev") { | ||||
| 			m.logger.Warningf("Skipping unexpected device folder path for device %v", dn.Path) | ||||
| 			continue | ||||
| 		} | ||||
| 		for df := filepath.Dir(dn.Path); df != "/dev"; df = filepath.Dir(df) { | ||||
| 			if seen[df] { | ||||
| 				continue | ||||
| 			} | ||||
| 			deviceFolders = append(deviceFolders, df) | ||||
| 			seen[df] = true | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	foldersByMode := make(map[string][]string) | ||||
| 	for _, p := range deviceFolders { | ||||
| 		info, err := os.Stat(p) | ||||
| 		if err != nil { | ||||
| 			return nil, fmt.Errorf("failed to get info for path %v: %v", p, err) | ||||
| 		} | ||||
| 		mode := fmt.Sprintf("%o", info.Mode().Perm()) | ||||
| 		foldersByMode[mode] = append(foldersByMode[mode], p) | ||||
| 	} | ||||
| 
 | ||||
| 	var hooks []*specs.Hook | ||||
| 	for mode, folders := range foldersByMode { | ||||
| 		args := []string{filepath.Base(nvidiaCTKPath), "hook", "chmod", "--mode", mode} | ||||
| 		for _, folder := range folders { | ||||
| 			args = append(args, "--path", folder) | ||||
| 		} | ||||
| 		hook := specs.Hook{ | ||||
| 			HookName: cdi.CreateContainerHook, | ||||
| 			Path:     nvidiaCTKPath, | ||||
| 			Args:     args, | ||||
| 		} | ||||
| 
 | ||||
| 		hooks = append(hooks, &hook) | ||||
| 	} | ||||
| 
 | ||||
| 	return hooks, nil | ||||
| } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user