gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/runsc/container/hook.go (about) 1 // Copyright 2018 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package container 16 17 import ( 18 "bytes" 19 "encoding/json" 20 "fmt" 21 "os/exec" 22 "path/filepath" 23 "strings" 24 "time" 25 26 specs "github.com/opencontainers/runtime-spec/specs-go" 27 "gvisor.dev/gvisor/pkg/log" 28 ) 29 30 // This file implements hooks as defined in OCI spec: 31 // https://github.com/opencontainers/runtime-spec/blob/master/config.md#toc22 32 // 33 // "hooks":{ 34 // "prestart":[{ 35 // "path":"/usr/bin/dockerd", 36 // "args":[ 37 // "libnetwork-setkey", "arg2", 38 // ] 39 // }] 40 // }, 41 42 // executeHooksBestEffort executes hooks and logs warning in case they fail. 43 // Runs all hooks, always. 44 func executeHooksBestEffort(hooks []specs.Hook, s specs.State) { 45 for _, h := range hooks { 46 if err := executeHook(h, s); err != nil { 47 log.Warningf("Failure to execute hook %+v, err: %v", h, err) 48 } 49 } 50 } 51 52 // executeHooks executes hooks until the first one fails or they all execute. 53 func executeHooks(hooks []specs.Hook, s specs.State) error { 54 for _, h := range hooks { 55 if err := executeHook(h, s); err != nil { 56 return err 57 } 58 } 59 return nil 60 } 61 62 func executeHook(h specs.Hook, s specs.State) error { 63 log.Debugf("Executing hook %+v, state: %+v", h, s) 64 65 if strings.TrimSpace(h.Path) == "" { 66 return fmt.Errorf("empty path for hook") 67 } 68 if !filepath.IsAbs(h.Path) { 69 return fmt.Errorf("path for hook is not absolute: %q", h.Path) 70 } 71 72 // Don't invoke nvidia-container-runtime-hook at prestart, which may be 73 // configured by e.g. Docker's --gpus flag, since 74 // nvidia-container-runtime-hook doesn't understand gVisor's bifurcation 75 // between sentry and application filesystems. 76 if strings.HasSuffix(h.Path, "/nvidia-container-runtime-hook") { 77 log.Infof("Skipping nvidia-container-runtime-hook") 78 return nil 79 } 80 81 b, err := json.Marshal(s) 82 if err != nil { 83 return err 84 } 85 var stdout, stderr bytes.Buffer 86 cmd := exec.Cmd{ 87 Path: h.Path, 88 Args: h.Args, 89 Env: h.Env, 90 Stdin: bytes.NewReader(b), 91 Stdout: &stdout, 92 Stderr: &stderr, 93 } 94 if err := cmd.Start(); err != nil { 95 return err 96 } 97 98 c := make(chan error, 1) 99 go func() { 100 c <- cmd.Wait() 101 }() 102 103 var timer <-chan time.Time 104 if h.Timeout != nil { 105 timer = time.After(time.Duration(*h.Timeout) * time.Second) 106 } 107 select { 108 case err := <-c: 109 if err != nil { 110 return fmt.Errorf("failure executing hook %q, err: %v\nstdout: %s\nstderr: %s", h.Path, err, stdout.String(), stderr.String()) 111 } 112 case <-timer: 113 _ = cmd.Process.Kill() 114 _ = cmd.Wait() 115 return fmt.Errorf("timeout executing hook %q\nstdout: %s\nstderr: %s", h.Path, stdout.String(), stderr.String()) 116 } 117 118 log.Debugf("Execute hook %q success!", h.Path) 119 return nil 120 }