github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/shim/proc/exec.go (about) 1 // Copyright 2018 The containerd Authors. 2 // Copyright 2018 The gVisor Authors. 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // https://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 package proc 17 18 import ( 19 "context" 20 "fmt" 21 "io" 22 "os" 23 "path/filepath" 24 "sync" 25 "time" 26 27 "github.com/containerd/console" 28 "github.com/containerd/containerd/errdefs" 29 "github.com/containerd/containerd/log" 30 "github.com/containerd/containerd/pkg/stdio" 31 "github.com/containerd/fifo" 32 runc "github.com/containerd/go-runc" 33 specs "github.com/opencontainers/runtime-spec/specs-go" 34 "golang.org/x/sys/unix" 35 "github.com/nicocha30/gvisor-ligolo/pkg/cleanup" 36 37 "github.com/nicocha30/gvisor-ligolo/pkg/shim/runsc" 38 ) 39 40 type execProcess struct { 41 wg sync.WaitGroup 42 43 execState execState 44 45 mu sync.Mutex 46 id string 47 console console.Console 48 io runc.IO 49 status int 50 exited time.Time 51 pid int 52 internalPid int 53 closers []io.Closer 54 stdin io.Closer 55 stdio stdio.Stdio 56 path string 57 spec specs.Process 58 59 parent *Init 60 waitBlock chan struct{} 61 } 62 63 func (e *execProcess) Wait() { 64 <-e.waitBlock 65 } 66 67 func (e *execProcess) ID() string { 68 return e.id 69 } 70 71 func (e *execProcess) Pid() int { 72 e.mu.Lock() 73 defer e.mu.Unlock() 74 return e.pid 75 } 76 77 func (e *execProcess) ExitStatus() int { 78 e.mu.Lock() 79 defer e.mu.Unlock() 80 return e.status 81 } 82 83 func (e *execProcess) ExitedAt() time.Time { 84 e.mu.Lock() 85 defer e.mu.Unlock() 86 return e.exited 87 } 88 89 func (e *execProcess) SetExited(status int) { 90 e.mu.Lock() 91 defer e.mu.Unlock() 92 93 e.execState.SetExited(status) 94 } 95 96 func (e *execProcess) setExited(status int) { 97 if !e.exited.IsZero() { 98 log.L.Debugf("Exec: status already set to %d, ignoring status: %d", e.status, status) 99 return 100 } 101 102 log.L.Debugf("Exec: setting status: %d", status) 103 e.status = status 104 e.exited = time.Now() 105 e.parent.Platform.ShutdownConsole(context.Background(), e.console) 106 close(e.waitBlock) 107 } 108 109 func (e *execProcess) Delete(ctx context.Context) error { 110 e.mu.Lock() 111 defer e.mu.Unlock() 112 113 return e.execState.Delete(ctx) 114 } 115 116 func (e *execProcess) delete() { 117 e.wg.Wait() 118 if e.io != nil { 119 for _, c := range e.closers { 120 c.Close() 121 } 122 e.io.Close() 123 } 124 } 125 126 func (e *execProcess) Resize(ws console.WinSize) error { 127 e.mu.Lock() 128 defer e.mu.Unlock() 129 130 return e.execState.Resize(ws) 131 } 132 133 func (e *execProcess) resize(ws console.WinSize) error { 134 if e.console == nil { 135 return nil 136 } 137 return e.console.Resize(ws) 138 } 139 140 func (e *execProcess) Kill(ctx context.Context, sig uint32, _ bool) error { 141 e.mu.Lock() 142 defer e.mu.Unlock() 143 144 return e.execState.Kill(ctx, sig, false) 145 } 146 147 func (e *execProcess) kill(ctx context.Context, sig uint32, _ bool) error { 148 internalPid := e.internalPid 149 if internalPid == 0 { 150 return nil 151 } 152 153 opts := runsc.KillOpts{Pid: internalPid} 154 if err := e.parent.runtime.Kill(ctx, e.parent.id, int(sig), &opts); err != nil { 155 return fmt.Errorf("%s: %w", err.Error(), errdefs.ErrNotFound) 156 } 157 return nil 158 } 159 160 func (e *execProcess) Stdin() io.Closer { 161 return e.stdin 162 } 163 164 func (e *execProcess) Stdio() stdio.Stdio { 165 return e.stdio 166 } 167 168 func (e *execProcess) Start(ctx context.Context) error { 169 e.mu.Lock() 170 defer e.mu.Unlock() 171 172 return e.execState.Start(ctx) 173 } 174 175 func (e *execProcess) start(ctx context.Context) error { 176 var socket *runc.Socket 177 178 switch { 179 case e.stdio.Terminal: 180 s, err := runc.NewTempConsoleSocket() 181 if err != nil { 182 return fmt.Errorf("failed to create runc console socket: %w", err) 183 } 184 defer s.Close() 185 socket = s 186 187 case e.stdio.IsNull(): 188 io, err := runc.NewNullIO() 189 if err != nil { 190 return fmt.Errorf("creating new NULL IO: %w", err) 191 } 192 e.io = io 193 194 default: 195 io, err := runc.NewPipeIO(e.parent.IoUID, e.parent.IoGID, withConditionalIO(e.stdio)) 196 if err != nil { 197 return fmt.Errorf("failed to create runc io pipes: %w", err) 198 } 199 e.io = io 200 } 201 202 opts := &runsc.ExecOpts{ 203 PidFile: filepath.Join(e.path, fmt.Sprintf("%s.pid", e.id)), 204 InternalPidFile: filepath.Join(e.path, fmt.Sprintf("%s-internal.pid", e.id)), 205 IO: e.io, 206 Detach: true, 207 } 208 defer func() { 209 _ = os.Remove(opts.PidFile) 210 _ = os.Remove(opts.InternalPidFile) 211 }() 212 if socket != nil { 213 opts.ConsoleSocket = socket 214 } 215 216 eventCh := e.parent.Monitor.Subscribe() 217 cu := cleanup.Make(func() { 218 e.parent.Monitor.Unsubscribe(eventCh) 219 }) 220 defer cu.Clean() 221 222 if err := e.parent.runtime.Exec(ctx, e.parent.id, e.spec, opts); err != nil { 223 close(e.waitBlock) 224 return e.parent.runtimeError(err, "OCI runtime exec failed") 225 } 226 if e.stdio.Stdin != "" { 227 sc, err := fifo.OpenFifo(context.Background(), e.stdio.Stdin, unix.O_WRONLY|unix.O_NONBLOCK, 0) 228 if err != nil { 229 return fmt.Errorf("failed to open stdin fifo %s: %w", e.stdio.Stdin, err) 230 } 231 e.closers = append(e.closers, sc) 232 e.stdin = sc 233 } 234 ctx, cancel := context.WithTimeout(ctx, 30*time.Second) 235 defer cancel() 236 if socket != nil { 237 console, err := socket.ReceiveMaster() 238 if err != nil { 239 return fmt.Errorf("failed to retrieve console master: %w", err) 240 } 241 if e.console, err = e.parent.Platform.CopyConsole(ctx, console, e.stdio.Stdin, e.stdio.Stdout, e.stdio.Stderr, &e.wg); err != nil { 242 return fmt.Errorf("failed to start console copy: %w", err) 243 } 244 } else if !e.stdio.IsNull() { 245 if err := copyPipes(ctx, e.io, e.stdio.Stdin, e.stdio.Stdout, e.stdio.Stderr, &e.wg); err != nil { 246 return fmt.Errorf("failed to start io pipe copy: %w", err) 247 } 248 } 249 250 pid, err := runc.ReadPidFile(opts.PidFile) 251 if err != nil { 252 return fmt.Errorf("failed to retrieve OCI runtime exec pid: %w", err) 253 } 254 e.pid = pid 255 internalPid, err := runc.ReadPidFile(opts.InternalPidFile) 256 if err != nil { 257 return fmt.Errorf("failed to retrieve OCI runtime exec internal pid: %w", err) 258 } 259 e.internalPid = internalPid 260 261 go func() { 262 defer e.parent.Monitor.Unsubscribe(eventCh) 263 for event := range eventCh { 264 if event.Pid == e.pid { 265 ExitCh <- Exit{ 266 Timestamp: event.Timestamp, 267 ID: e.id, 268 Status: event.Status, 269 } 270 break 271 } 272 } 273 }() 274 275 cu.Release() // cancel cleanup on success. 276 return nil 277 } 278 279 func (e *execProcess) Status(context.Context) (string, error) { 280 e.mu.Lock() 281 defer e.mu.Unlock() 282 // if we don't have a pid then the exec process has just been created 283 if e.pid == 0 { 284 return "created", nil 285 } 286 // This checks that `runsc exec` process is still running. This process has 287 // the same lifetime as the process executing inside the container. So instead 288 // of calling `runsc kill --pid`, just do a quick check that `runsc exec` is 289 // still running. 290 if err := unix.Kill(e.pid, 0); err != nil { 291 // Can't signal the process, it must have exited. 292 return "stopped", nil 293 } 294 return "running", nil 295 }