github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/shim/proc/init.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 "encoding/json" 21 "fmt" 22 "io" 23 "path/filepath" 24 "strings" 25 "sync" 26 "time" 27 28 "github.com/containerd/console" 29 30 "github.com/containerd/containerd/errdefs" 31 "github.com/containerd/containerd/log" 32 "github.com/containerd/containerd/mount" 33 "github.com/containerd/containerd/pkg/process" 34 "github.com/containerd/containerd/pkg/stdio" 35 36 "github.com/containerd/fifo" 37 runc "github.com/containerd/go-runc" 38 specs "github.com/opencontainers/runtime-spec/specs-go" 39 "golang.org/x/sys/unix" 40 "github.com/nicocha30/gvisor-ligolo/pkg/shim/runsc" 41 "github.com/nicocha30/gvisor-ligolo/pkg/shim/utils" 42 ) 43 44 const statusStopped = "stopped" 45 46 // Init represents an initial process for a container. 47 type Init struct { 48 wg sync.WaitGroup 49 initState initState 50 51 // mu is used to ensure that `Start()` and `Exited()` calls return in 52 // the right order when invoked in separate go routines. This is the 53 // case within the shim implementation as it makes use of the reaper 54 // interface. 55 mu sync.Mutex 56 57 waitBlock chan struct{} 58 59 WorkDir string 60 61 id string 62 Bundle string 63 console console.Console 64 Platform stdio.Platform 65 io runc.IO 66 runtime *runsc.Runsc 67 status int 68 exited time.Time 69 pid int 70 closers []io.Closer 71 stdin io.Closer 72 stdio stdio.Stdio 73 Rootfs string 74 IoUID int 75 IoGID int 76 Sandbox bool 77 UserLog string 78 Monitor ProcessMonitor 79 } 80 81 // NewRunsc returns a new runsc instance for a process. 82 func NewRunsc(root, path, namespace, runtime string, config map[string]string, spec *specs.Spec) *runsc.Runsc { 83 if root == "" { 84 root = RunscRoot 85 } 86 return &runsc.Runsc{ 87 Command: runtime, 88 PdeathSignal: unix.SIGKILL, 89 Log: filepath.Join(path, "log.json"), 90 LogFormat: runc.JSON, 91 PanicLog: utils.PanicLogPath(spec), 92 Root: filepath.Join(root, namespace), 93 Config: config, 94 } 95 } 96 97 // New returns a new init process. 98 func New(id string, runtime *runsc.Runsc, stdio stdio.Stdio) *Init { 99 p := &Init{ 100 id: id, 101 runtime: runtime, 102 stdio: stdio, 103 status: 0, 104 waitBlock: make(chan struct{}), 105 } 106 p.initState = &createdState{p: p} 107 return p 108 } 109 110 // Create the process with the provided config. 111 func (p *Init) Create(ctx context.Context, r *CreateConfig) (err error) { 112 var socket *runc.Socket 113 if r.Terminal { 114 if socket, err = runc.NewTempConsoleSocket(); err != nil { 115 return fmt.Errorf("failed to create OCI runtime console socket: %w", err) 116 } 117 defer socket.Close() 118 } else if hasNoIO(r) { 119 if p.io, err = runc.NewNullIO(); err != nil { 120 return fmt.Errorf("creating new NULL IO: %w", err) 121 } 122 } else { 123 if p.io, err = runc.NewPipeIO(p.IoUID, p.IoGID, withConditionalIO(p.stdio)); err != nil { 124 return fmt.Errorf("failed to create OCI runtime io pipes: %w", err) 125 } 126 } 127 // pidFile is the file that will contain the sandbox pid. 128 pidFile := filepath.Join(p.Bundle, "init.pid") 129 opts := &runsc.CreateOpts{ 130 PidFile: pidFile, 131 } 132 if socket != nil { 133 opts.ConsoleSocket = socket 134 } 135 if p.Sandbox { 136 opts.IO = p.io 137 // UserLog is only useful for sandbox. 138 opts.UserLog = p.UserLog 139 } 140 if err := p.runtime.Create(ctx, r.ID, r.Bundle, opts); err != nil { 141 return p.runtimeError(err, "OCI runtime create failed") 142 } 143 if r.Stdin != "" { 144 sc, err := fifo.OpenFifo(context.Background(), r.Stdin, unix.O_WRONLY|unix.O_NONBLOCK, 0) 145 if err != nil { 146 return fmt.Errorf("failed to open stdin fifo %s: %w", r.Stdin, err) 147 } 148 p.stdin = sc 149 p.closers = append(p.closers, sc) 150 } 151 ctx, cancel := context.WithTimeout(ctx, 30*time.Second) 152 defer cancel() 153 if socket != nil { 154 console, err := socket.ReceiveMaster() 155 if err != nil { 156 return fmt.Errorf("failed to retrieve console master: %w", err) 157 } 158 console, err = p.Platform.CopyConsole(ctx, console, r.Stdin, r.Stdout, r.Stderr, &p.wg) 159 if err != nil { 160 return fmt.Errorf("failed to start console copy: %w", err) 161 } 162 p.console = console 163 } else if !hasNoIO(r) { 164 if err := copyPipes(ctx, p.io, r.Stdin, r.Stdout, r.Stderr, &p.wg); err != nil { 165 return fmt.Errorf("failed to start io pipe copy: %w", err) 166 } 167 } 168 pid, err := runc.ReadPidFile(pidFile) 169 if err != nil { 170 return fmt.Errorf("failed to retrieve OCI runtime container pid: %w", err) 171 } 172 p.pid = pid 173 return nil 174 } 175 176 // Wait waits for the process to exit. 177 func (p *Init) Wait() { 178 <-p.waitBlock 179 } 180 181 // ID returns the ID of the process. 182 func (p *Init) ID() string { 183 return p.id 184 } 185 186 // Pid returns the PID of the process. 187 func (p *Init) Pid() int { 188 return p.pid 189 } 190 191 // ExitStatus returns the exit status of the process. 192 func (p *Init) ExitStatus() int { 193 p.mu.Lock() 194 defer p.mu.Unlock() 195 return p.status 196 } 197 198 // ExitedAt returns the time when the process exited. 199 func (p *Init) ExitedAt() time.Time { 200 p.mu.Lock() 201 defer p.mu.Unlock() 202 return p.exited 203 } 204 205 // Status returns the status of the process. 206 func (p *Init) Status(ctx context.Context) (string, error) { 207 p.mu.Lock() 208 defer p.mu.Unlock() 209 210 return p.initState.State(ctx) 211 } 212 213 func (p *Init) state(ctx context.Context) (string, error) { 214 c, err := p.runtime.State(ctx, p.id) 215 if err != nil { 216 if strings.Contains(err.Error(), "does not exist") { 217 return statusStopped, nil 218 } 219 return "", p.runtimeError(err, "OCI runtime state failed") 220 } 221 return p.convertStatus(c.Status), nil 222 } 223 224 // Start starts the init process. 225 func (p *Init) Start(ctx context.Context) error { 226 p.mu.Lock() 227 defer p.mu.Unlock() 228 229 return p.initState.Start(ctx) 230 } 231 232 func (p *Init) start(ctx context.Context) error { 233 var cio runc.IO 234 if !p.Sandbox { 235 cio = p.io 236 } 237 if err := p.runtime.Start(ctx, p.id, cio); err != nil { 238 return p.runtimeError(err, "OCI runtime start failed") 239 } 240 go func() { 241 status, err := p.runtime.Wait(context.Background(), p.id) 242 if err != nil { 243 log.G(ctx).WithError(err).Errorf("Failed to wait for container %q", p.id) 244 p.killAllLocked(ctx) 245 status = internalErrorCode 246 } 247 ExitCh <- Exit{ 248 Timestamp: time.Now(), 249 ID: p.id, 250 Status: status, 251 } 252 }() 253 return nil 254 } 255 256 // SetExited set the exit stauts of the init process. 257 func (p *Init) SetExited(status int) { 258 p.mu.Lock() 259 defer p.mu.Unlock() 260 261 p.initState.SetExited(status) 262 } 263 264 func (p *Init) setExited(status int) { 265 if !p.exited.IsZero() { 266 log.L.Debugf("Status already set to %d, ignoring status: %d", p.status, status) 267 return 268 } 269 270 log.L.Debugf("Setting status: %d", status) 271 p.exited = time.Now() 272 p.status = status 273 p.Platform.ShutdownConsole(context.Background(), p.console) 274 close(p.waitBlock) 275 } 276 277 // Delete deletes the init process. 278 func (p *Init) Delete(ctx context.Context) error { 279 p.mu.Lock() 280 defer p.mu.Unlock() 281 282 return p.initState.Delete(ctx) 283 } 284 285 func (p *Init) delete(ctx context.Context) error { 286 p.killAllLocked(ctx) 287 p.wg.Wait() 288 289 err := p.runtime.Delete(ctx, p.id, nil) 290 if err != nil { 291 // ignore errors if a runtime has already deleted the process 292 // but we still hold metadata and pipes 293 // 294 // this is common during a checkpoint, runc will delete the container state 295 // after a checkpoint and the container will no longer exist within runc 296 if strings.Contains(err.Error(), "does not exist") { 297 err = nil 298 } else { 299 err = p.runtimeError(err, "failed to delete task") 300 } 301 } 302 if p.io != nil { 303 for _, c := range p.closers { 304 c.Close() 305 } 306 p.io.Close() 307 } 308 if err2 := mount.UnmountAll(p.Rootfs, 0); err2 != nil { 309 log.G(ctx).WithError(err2).Warn("failed to cleanup rootfs mount") 310 if err == nil { 311 err = fmt.Errorf("failed rootfs umount: %w", err2) 312 } 313 } 314 return err 315 } 316 317 // Resize resizes the init processes console. 318 func (p *Init) Resize(ws console.WinSize) error { 319 p.mu.Lock() 320 defer p.mu.Unlock() 321 322 if p.console == nil { 323 return nil 324 } 325 return p.console.Resize(ws) 326 } 327 328 func (p *Init) resize(ws console.WinSize) error { 329 if p.console == nil { 330 return nil 331 } 332 return p.console.Resize(ws) 333 } 334 335 // Kill kills the init process. 336 func (p *Init) Kill(ctx context.Context, signal uint32, all bool) error { 337 p.mu.Lock() 338 defer p.mu.Unlock() 339 340 return p.initState.Kill(ctx, signal, all) 341 } 342 343 func (p *Init) kill(ctx context.Context, signal uint32, all bool) error { 344 var ( 345 killErr error 346 backoff = 100 * time.Millisecond 347 ) 348 const timeout = time.Second 349 for start := time.Now(); time.Since(start) < timeout; { 350 state, err := p.initState.State(ctx) 351 if err != nil { 352 return p.runtimeError(err, "OCI runtime state failed") 353 } 354 // For runsc, signal only works when container is running state. 355 // If the container is not in running state, directly return 356 // "no such process" 357 if state == statusStopped { 358 return fmt.Errorf("no such process: %w", errdefs.ErrNotFound) 359 } 360 killErr = p.runtime.Kill(ctx, p.id, int(signal), &runsc.KillOpts{All: all}) 361 if killErr == nil { 362 return nil 363 } 364 time.Sleep(backoff) 365 backoff *= 2 366 } 367 return p.runtimeError(killErr, "kill timeout") 368 } 369 370 // KillAll kills all processes belonging to the init process. If 371 // `runsc kill --all` returns error, assume the container has already stopped. 372 func (p *Init) KillAll(context context.Context) { 373 p.mu.Lock() 374 defer p.mu.Unlock() 375 p.killAllLocked(context) 376 } 377 378 func (p *Init) killAllLocked(context context.Context) { 379 if err := p.runtime.Kill(context, p.id, int(unix.SIGKILL), &runsc.KillOpts{All: true}); err != nil { 380 log.L.Warningf("Ignoring error killing container %q: %v", p.id, err) 381 } 382 } 383 384 // Stdin returns the stdin of the process. 385 func (p *Init) Stdin() io.Closer { 386 return p.stdin 387 } 388 389 // Runtime returns the OCI runtime configured for the init process. 390 func (p *Init) Runtime() *runsc.Runsc { 391 return p.runtime 392 } 393 394 // Exec returns a new child process. 395 func (p *Init) Exec(ctx context.Context, path string, r *ExecConfig) (process.Process, error) { 396 p.mu.Lock() 397 defer p.mu.Unlock() 398 399 return p.initState.Exec(ctx, path, r) 400 } 401 402 // exec returns a new exec'd process. 403 func (p *Init) exec(path string, r *ExecConfig) (process.Process, error) { 404 var spec specs.Process 405 if err := json.Unmarshal(r.Spec.Value, &spec); err != nil { 406 return nil, err 407 } 408 spec.Terminal = r.Terminal 409 410 e := &execProcess{ 411 id: r.ID, 412 path: path, 413 parent: p, 414 spec: spec, 415 stdio: stdio.Stdio{ 416 Stdin: r.Stdin, 417 Stdout: r.Stdout, 418 Stderr: r.Stderr, 419 Terminal: r.Terminal, 420 }, 421 waitBlock: make(chan struct{}), 422 } 423 e.execState = &execCreatedState{p: e} 424 return e, nil 425 } 426 427 func (p *Init) Stats(ctx context.Context, id string) (*runc.Stats, error) { 428 p.mu.Lock() 429 defer p.mu.Unlock() 430 431 return p.initState.Stats(ctx, id) 432 } 433 434 func (p *Init) stats(ctx context.Context, id string) (*runc.Stats, error) { 435 return p.Runtime().Stats(ctx, id) 436 } 437 438 // Stdio returns the stdio of the process. 439 func (p *Init) Stdio() stdio.Stdio { 440 return p.stdio 441 } 442 443 func (p *Init) runtimeError(rErr error, msg string) error { 444 if rErr == nil { 445 return nil 446 } 447 448 rMsg, err := getLastRuntimeError(p.runtime) 449 switch { 450 case err != nil: 451 return fmt.Errorf("%s: %w (unable to retrieve OCI runtime error: %v)", msg, rErr, err) 452 case rMsg == "": 453 return fmt.Errorf("%s: %w", msg, rErr) 454 default: 455 return fmt.Errorf("%s: %s", msg, rMsg) 456 } 457 } 458 459 func (p *Init) convertStatus(status string) string { 460 if status == "created" && !p.Sandbox && p.status == internalErrorCode { 461 // Treat start failure state for non-root container as stopped. 462 return statusStopped 463 } 464 return status 465 } 466 467 func withConditionalIO(c stdio.Stdio) runc.IOOpt { 468 return func(o *runc.IOOption) { 469 o.OpenStdin = c.Stdin != "" 470 o.OpenStdout = c.Stdout != "" 471 o.OpenStderr = c.Stderr != "" 472 } 473 }