2019-10-22 21:36:22 +00:00
package main
import (
"flag"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"runtime/debug"
"strconv"
"strings"
"syscall"
2022-05-10 12:53:07 +00:00
2023-08-10 08:49:46 +00:00
"github.com/NVIDIA/nvidia-container-toolkit/internal/config"
2022-05-09 13:42:59 +00:00
"github.com/NVIDIA/nvidia-container-toolkit/internal/info"
2023-06-06 19:42:56 +00:00
"github.com/NVIDIA/nvidia-container-toolkit/internal/logger"
2022-05-10 12:53:07 +00:00
"github.com/NVIDIA/nvidia-container-toolkit/internal/lookup"
2019-10-22 21:36:22 +00:00
)
var (
2022-05-12 12:02:06 +00:00
debugflag = flag . Bool ( "debug" , false , "enable debug output" )
versionflag = flag . Bool ( "version" , false , "enable version output" )
configflag = flag . String ( "config" , "" , "configuration file" )
2019-10-22 21:36:22 +00:00
)
func exit ( ) {
if err := recover ( ) ; err != nil {
if _ , ok := err . ( runtime . Error ) ; ok {
log . Println ( err )
}
if * debugflag {
log . Printf ( "%s" , debug . Stack ( ) )
}
os . Exit ( 1 )
}
os . Exit ( 0 )
}
2023-08-10 08:49:46 +00:00
func getCLIPath ( config config . ContainerCLIConfig ) string {
if config . Path != "" {
return config . Path
2019-10-22 21:36:22 +00:00
}
2023-08-10 08:49:46 +00:00
if err := os . Setenv ( "PATH" , lookup . GetPath ( config . Root ) ) ; err != nil {
2019-10-22 21:36:22 +00:00
log . Panicln ( "couldn't set PATH variable:" , err )
}
path , err := exec . LookPath ( "nvidia-container-cli" )
if err != nil {
log . Panicln ( "couldn't find binary nvidia-container-cli in" , os . Getenv ( "PATH" ) , ":" , err )
}
return path
}
// getRootfsPath returns an absolute path. We don't need to resolve symlinks for now.
func getRootfsPath ( config containerConfig ) string {
rootfs , err := filepath . Abs ( config . Rootfs )
if err != nil {
log . Panicln ( err )
}
return rootfs
}
func doPrestart ( ) {
var err error
defer exit ( )
log . SetFlags ( 0 )
2023-03-13 13:22:18 +00:00
hook , err := getHookConfig ( )
if err != nil || hook == nil {
log . Panicln ( "error getting hook config:" , err )
}
2023-08-10 08:49:46 +00:00
cli := hook . NVIDIAContainerCLIConfig
2019-10-22 21:36:22 +00:00
2023-03-13 13:22:18 +00:00
container := getContainerConfig ( * hook )
2019-10-22 21:36:22 +00:00
nvidia := container . Nvidia
if nvidia == nil {
// Not a GPU container, nothing to do.
return
}
2023-08-10 08:49:46 +00:00
if ! hook . NVIDIAContainerRuntimeHookConfig . SkipModeDetection && info . ResolveAutoMode ( & logInterceptor { } , hook . NVIDIAContainerRuntimeConfig . Mode , container . Image ) != "legacy" {
2023-06-05 14:03:03 +00:00
log . Panicln ( "invoking the NVIDIA Container Runtime Hook directly (e.g. specifying the docker --gpus flag) is not supported. Please use the NVIDIA Container Runtime (e.g. specify the --runtime=nvidia flag) instead." )
}
2019-10-22 21:36:22 +00:00
rootfs := getRootfsPath ( container )
args := [ ] string { getCLIPath ( cli ) }
2023-08-10 08:49:46 +00:00
if cli . Root != "" {
args = append ( args , fmt . Sprintf ( "--root=%s" , cli . Root ) )
2019-10-22 21:36:22 +00:00
}
if cli . LoadKmods {
args = append ( args , "--load-kmods" )
}
2024-10-14 11:49:47 +00:00
if hook . Features . DisableImexChannelCreation . IsEnabled ( ) {
args = append ( args , "--no-create-imex-channels" )
}
2020-02-01 00:43:41 +00:00
if cli . NoPivot {
args = append ( args , "--no-pivot" )
}
2019-10-22 21:36:22 +00:00
if * debugflag {
args = append ( args , "--debug=/dev/stderr" )
2023-08-10 08:49:46 +00:00
} else if cli . Debug != "" {
args = append ( args , fmt . Sprintf ( "--debug=%s" , cli . Debug ) )
2019-10-22 21:36:22 +00:00
}
2023-08-10 08:49:46 +00:00
if cli . Ldcache != "" {
args = append ( args , fmt . Sprintf ( "--ldcache=%s" , cli . Ldcache ) )
2019-10-22 21:36:22 +00:00
}
2023-08-10 08:49:46 +00:00
if cli . User != "" {
args = append ( args , fmt . Sprintf ( "--user=%s" , cli . User ) )
2019-10-22 21:36:22 +00:00
}
args = append ( args , "configure" )
2023-11-14 15:56:50 +00:00
if ldconfigPath := cli . NormalizeLDConfigPath ( ) ; ldconfigPath != "" {
args = append ( args , fmt . Sprintf ( "--ldconfig=%s" , ldconfigPath ) )
2019-10-22 21:36:22 +00:00
}
if cli . NoCgroups {
args = append ( args , "--no-cgroups" )
}
2024-10-14 12:53:36 +00:00
if devicesString := strings . Join ( nvidia . Devices , "," ) ; len ( devicesString ) > 0 {
args = append ( args , fmt . Sprintf ( "--device=%s" , devicesString ) )
2019-10-22 21:36:22 +00:00
}
2019-12-20 21:22:08 +00:00
if len ( nvidia . MigConfigDevices ) > 0 {
args = append ( args , fmt . Sprintf ( "--mig-config=%s" , nvidia . MigConfigDevices ) )
}
if len ( nvidia . MigMonitorDevices ) > 0 {
args = append ( args , fmt . Sprintf ( "--mig-monitor=%s" , nvidia . MigMonitorDevices ) )
}
2024-10-14 12:32:06 +00:00
if imexString := strings . Join ( nvidia . ImexChannels , "," ) ; len ( imexString ) > 0 {
args = append ( args , fmt . Sprintf ( "--imex-channel=%s" , imexString ) )
2024-01-17 22:38:10 +00:00
}
2019-10-22 21:36:22 +00:00
2019-12-20 16:07:43 +00:00
for _ , cap := range strings . Split ( nvidia . DriverCapabilities , "," ) {
2019-10-22 21:36:22 +00:00
if len ( cap ) == 0 {
break
}
args = append ( args , capabilityToCLI ( cap ) )
}
2023-07-07 10:34:43 +00:00
for _ , req := range nvidia . Requirements {
args = append ( args , fmt . Sprintf ( "--require=%s" , req ) )
2019-10-22 21:36:22 +00:00
}
args = append ( args , fmt . Sprintf ( "--pid=%s" , strconv . FormatUint ( uint64 ( container . Pid ) , 10 ) ) )
args = append ( args , rootfs )
env := append ( os . Environ ( ) , cli . Environment ... )
2023-08-28 09:07:04 +00:00
//nolint:gosec // TODO: Can we harden this so that there is less risk of command injection?
2019-10-22 21:36:22 +00:00
err = syscall . Exec ( args [ 0 ] , args , env )
log . Panicln ( "exec failed:" , err )
}
func usage ( ) {
fmt . Fprintf ( os . Stderr , "Usage of %s:\n" , os . Args [ 0 ] )
flag . PrintDefaults ( )
fmt . Fprintf ( os . Stderr , "\nCommands:\n" )
fmt . Fprintf ( os . Stderr , " prestart\n run the prestart hook\n" )
fmt . Fprintf ( os . Stderr , " poststart\n no-op\n" )
fmt . Fprintf ( os . Stderr , " poststop\n no-op\n" )
}
func main ( ) {
flag . Usage = usage
flag . Parse ( )
2022-05-12 12:02:06 +00:00
if * versionflag {
fmt . Printf ( "%v version %v\n" , "NVIDIA Container Runtime Hook" , info . GetVersionString ( ) )
return
}
2019-10-22 21:36:22 +00:00
args := flag . Args ( )
if len ( args ) == 0 {
flag . Usage ( )
os . Exit ( 2 )
}
switch args [ 0 ] {
case "prestart" :
doPrestart ( )
os . Exit ( 0 )
case "poststart" :
fallthrough
case "poststop" :
os . Exit ( 0 )
default :
flag . Usage ( )
os . Exit ( 2 )
}
}
2022-05-09 13:42:59 +00:00
2023-06-06 19:42:56 +00:00
// logInterceptor implements the logger.Interface to allow for logging from executable.
type logInterceptor struct {
logger . NullLogger
}
2022-05-09 13:42:59 +00:00
func ( l * logInterceptor ) Infof ( format string , args ... interface { } ) {
log . Printf ( format , args ... )
}