github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/shim/runsc/runsc.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 runsc provides an API to interact with runsc command line. 17 package runsc 18 19 import ( 20 "bytes" 21 "context" 22 "encoding/json" 23 "fmt" 24 "io" 25 "io/ioutil" 26 "os" 27 "os/exec" 28 "path/filepath" 29 "strconv" 30 "time" 31 32 "github.com/containerd/containerd/log" 33 runc "github.com/containerd/go-runc" 34 specs "github.com/opencontainers/runtime-spec/specs-go" 35 "golang.org/x/sys/unix" 36 ) 37 38 // DefaultCommand is the default command for Runsc. 39 const DefaultCommand = "runsc" 40 41 // Monitor is the default process monitor to be used by runsc. 42 var Monitor runc.ProcessMonitor = &LogMonitor{Next: runc.Monitor} 43 44 // LogMonitor implements the runc.ProcessMonitor interface, logging the command 45 // that is getting executed, and then forwarding the call to another 46 // implementation. 47 type LogMonitor struct { 48 Next runc.ProcessMonitor 49 } 50 51 // Start implements runc.ProcessMonitor. 52 func (l *LogMonitor) Start(cmd *exec.Cmd) (chan runc.Exit, error) { 53 log.L.Debugf("Executing: %s", cmd.Args) 54 return l.Next.Start(cmd) 55 } 56 57 // Wait implements runc.ProcessMonitor. 58 func (l *LogMonitor) Wait(cmd *exec.Cmd, ch chan runc.Exit) (int, error) { 59 status, err := l.Next.Wait(cmd, ch) 60 log.L.Debugf("Command exit code: %d, err: %v", status, err) 61 return status, err 62 } 63 64 // Runsc is the client to the runsc cli. 65 type Runsc struct { 66 Command string 67 PdeathSignal unix.Signal 68 Setpgid bool 69 Root string 70 Log string 71 LogFormat runc.Format 72 Config map[string]string 73 } 74 75 // List returns all containers created inside the provided runsc root directory. 76 func (r *Runsc) List(context context.Context) ([]*runc.Container, error) { 77 data, stderr, err := cmdOutput(r.command(context, "list", "--format=json"), false) 78 if err != nil { 79 return nil, fmt.Errorf("%w: %s", err, stderr) 80 } 81 var out []*runc.Container 82 if err := json.Unmarshal(data, &out); err != nil { 83 return nil, err 84 } 85 return out, nil 86 } 87 88 // State returns the state for the container provided by id. 89 func (r *Runsc) State(context context.Context, id string) (*runc.Container, error) { 90 data, stderr, err := cmdOutput(r.command(context, "state", id), false) 91 if err != nil { 92 return nil, fmt.Errorf("%w: %s", err, stderr) 93 } 94 var c runc.Container 95 if err := json.Unmarshal(data, &c); err != nil { 96 return nil, err 97 } 98 return &c, nil 99 } 100 101 // CreateOpts is a set of options to Runsc.Create(). 102 type CreateOpts struct { 103 runc.IO 104 ConsoleSocket runc.ConsoleSocket 105 106 // PidFile is a path to where a pid file should be created. 107 PidFile string 108 109 // UserLog is a path to where runsc user log should be generated. 110 UserLog string 111 } 112 113 func (o *CreateOpts) args() (out []string, err error) { 114 if o.PidFile != "" { 115 abs, err := filepath.Abs(o.PidFile) 116 if err != nil { 117 return nil, err 118 } 119 out = append(out, "--pid-file", abs) 120 } 121 if o.ConsoleSocket != nil { 122 out = append(out, "--console-socket", o.ConsoleSocket.Path()) 123 } 124 if o.UserLog != "" { 125 out = append(out, "--user-log", o.UserLog) 126 } 127 return out, nil 128 } 129 130 // Create creates a new container and returns its pid if it was created successfully. 131 func (r *Runsc) Create(context context.Context, id, bundle string, opts *CreateOpts) error { 132 args := []string{"create", "--bundle", bundle} 133 if opts != nil { 134 oargs, err := opts.args() 135 if err != nil { 136 return err 137 } 138 args = append(args, oargs...) 139 } 140 cmd := r.command(context, append(args, id)...) 141 if opts != nil && opts.IO != nil { 142 opts.Set(cmd) 143 } 144 145 if cmd.Stdout == nil && cmd.Stderr == nil { 146 out, _, err := cmdOutput(cmd, true) 147 if err != nil { 148 return fmt.Errorf("%w: %s", err, out) 149 } 150 return nil 151 } 152 ec, err := Monitor.Start(cmd) 153 if err != nil { 154 return err 155 } 156 if opts != nil && opts.IO != nil { 157 if c, ok := opts.IO.(runc.StartCloser); ok { 158 if err := c.CloseAfterStart(); err != nil { 159 return err 160 } 161 } 162 } 163 status, err := Monitor.Wait(cmd, ec) 164 if err == nil && status != 0 { 165 err = fmt.Errorf("%s did not terminate sucessfully", cmd.Args[0]) 166 } 167 168 return err 169 } 170 171 func (r *Runsc) Pause(context context.Context, id string) error { 172 if out, _, err := cmdOutput(r.command(context, "pause", id), true); err != nil { 173 return fmt.Errorf("unable to pause: %w: %s", err, out) 174 } 175 return nil 176 } 177 178 func (r *Runsc) Resume(context context.Context, id string) error { 179 if out, _, err := cmdOutput(r.command(context, "resume", id), true); err != nil { 180 return fmt.Errorf("unable to resume: %w: %s", err, out) 181 } 182 return nil 183 } 184 185 // Start will start an already created container. 186 func (r *Runsc) Start(context context.Context, id string, cio runc.IO) error { 187 cmd := r.command(context, "start", id) 188 if cio != nil { 189 cio.Set(cmd) 190 } 191 192 if cmd.Stdout == nil && cmd.Stderr == nil { 193 out, _, err := cmdOutput(cmd, true) 194 if err != nil { 195 return fmt.Errorf("%w: %s", err, out) 196 } 197 return nil 198 } 199 200 ec, err := Monitor.Start(cmd) 201 if err != nil { 202 return err 203 } 204 if cio != nil { 205 if c, ok := cio.(runc.StartCloser); ok { 206 if err := c.CloseAfterStart(); err != nil { 207 return err 208 } 209 } 210 } 211 status, err := Monitor.Wait(cmd, ec) 212 if err == nil && status != 0 { 213 err = fmt.Errorf("%s did not terminate sucessfully", cmd.Args[0]) 214 } 215 216 return err 217 } 218 219 type waitResult struct { 220 ID string `json:"id"` 221 ExitStatus int `json:"exitStatus"` 222 } 223 224 // Wait will wait for a running container, and return its exit status. 225 func (r *Runsc) Wait(context context.Context, id string) (int, error) { 226 data, stderr, err := cmdOutput(r.command(context, "wait", id), false) 227 if err != nil { 228 return 0, fmt.Errorf("%w: %s", err, stderr) 229 } 230 var res waitResult 231 if err := json.Unmarshal(data, &res); err != nil { 232 return 0, err 233 } 234 return res.ExitStatus, nil 235 } 236 237 // ExecOpts is a set of options to runsc.Exec(). 238 type ExecOpts struct { 239 runc.IO 240 PidFile string 241 InternalPidFile string 242 ConsoleSocket runc.ConsoleSocket 243 Detach bool 244 } 245 246 func (o *ExecOpts) args() (out []string, err error) { 247 if o.ConsoleSocket != nil { 248 out = append(out, "--console-socket", o.ConsoleSocket.Path()) 249 } 250 if o.Detach { 251 out = append(out, "--detach") 252 } 253 if o.PidFile != "" { 254 abs, err := filepath.Abs(o.PidFile) 255 if err != nil { 256 return nil, err 257 } 258 out = append(out, "--pid-file", abs) 259 } 260 if o.InternalPidFile != "" { 261 abs, err := filepath.Abs(o.InternalPidFile) 262 if err != nil { 263 return nil, err 264 } 265 out = append(out, "--internal-pid-file", abs) 266 } 267 return out, nil 268 } 269 270 // Exec executes an additional process inside the container based on a full OCI 271 // Process specification. 272 func (r *Runsc) Exec(context context.Context, id string, spec specs.Process, opts *ExecOpts) error { 273 f, err := ioutil.TempFile(os.Getenv("XDG_RUNTIME_DIR"), "runsc-process") 274 if err != nil { 275 return err 276 } 277 defer os.Remove(f.Name()) 278 err = json.NewEncoder(f).Encode(spec) 279 f.Close() 280 if err != nil { 281 return err 282 } 283 args := []string{"exec", "--process", f.Name()} 284 if opts != nil { 285 oargs, err := opts.args() 286 if err != nil { 287 return err 288 } 289 args = append(args, oargs...) 290 } 291 cmd := r.command(context, append(args, id)...) 292 if opts != nil && opts.IO != nil { 293 opts.Set(cmd) 294 } 295 if cmd.Stdout == nil && cmd.Stderr == nil { 296 out, _, err := cmdOutput(cmd, true) 297 if err != nil { 298 return fmt.Errorf("%w: %s", err, out) 299 } 300 return nil 301 } 302 ec, err := Monitor.Start(cmd) 303 if err != nil { 304 return err 305 } 306 if opts != nil && opts.IO != nil { 307 if c, ok := opts.IO.(runc.StartCloser); ok { 308 if err := c.CloseAfterStart(); err != nil { 309 return err 310 } 311 } 312 } 313 status, err := Monitor.Wait(cmd, ec) 314 if err == nil && status != 0 { 315 err = fmt.Errorf("%s did not terminate sucessfully", cmd.Args[0]) 316 } 317 return err 318 } 319 320 // Run runs the create, start, delete lifecycle of the container and returns 321 // its exit status after it has exited. 322 func (r *Runsc) Run(context context.Context, id, bundle string, opts *CreateOpts) (int, error) { 323 args := []string{"run", "--bundle", bundle} 324 if opts != nil { 325 oargs, err := opts.args() 326 if err != nil { 327 return -1, err 328 } 329 args = append(args, oargs...) 330 } 331 cmd := r.command(context, append(args, id)...) 332 if opts != nil && opts.IO != nil { 333 opts.Set(cmd) 334 } 335 ec, err := Monitor.Start(cmd) 336 if err != nil { 337 return -1, err 338 } 339 return Monitor.Wait(cmd, ec) 340 } 341 342 // DeleteOpts is a set of options to runsc.Delete(). 343 type DeleteOpts struct { 344 Force bool 345 } 346 347 func (o *DeleteOpts) args() (out []string) { 348 if o.Force { 349 out = append(out, "--force") 350 } 351 return out 352 } 353 354 // Delete deletes the container. 355 func (r *Runsc) Delete(context context.Context, id string, opts *DeleteOpts) error { 356 args := []string{"delete"} 357 if opts != nil { 358 args = append(args, opts.args()...) 359 } 360 return r.runOrError(r.command(context, append(args, id)...)) 361 } 362 363 // KillOpts specifies options for killing a container and its processes. 364 type KillOpts struct { 365 All bool 366 Pid int 367 } 368 369 func (o *KillOpts) args() (out []string) { 370 if o.All { 371 out = append(out, "--all") 372 } 373 if o.Pid != 0 { 374 out = append(out, "--pid", strconv.Itoa(o.Pid)) 375 } 376 return out 377 } 378 379 // Kill sends the specified signal to the container. 380 func (r *Runsc) Kill(context context.Context, id string, sig int, opts *KillOpts) error { 381 args := []string{ 382 "kill", 383 } 384 if opts != nil { 385 args = append(args, opts.args()...) 386 } 387 return r.runOrError(r.command(context, append(args, id, strconv.Itoa(sig))...)) 388 } 389 390 // Stats return the stats for a container like cpu, memory, and I/O. 391 func (r *Runsc) Stats(context context.Context, id string) (*runc.Stats, error) { 392 cmd := r.command(context, "events", "--stats", id) 393 data, stderr, err := cmdOutput(cmd, false) 394 if err != nil { 395 return nil, fmt.Errorf("%w: %s", err, stderr) 396 } 397 var e runc.Event 398 if err := json.Unmarshal(data, &e); err != nil { 399 log.L.Debugf("Parsing events error: %v", err) 400 return nil, err 401 } 402 log.L.Debugf("Stats returned, type: %s, stats: %+v", e.Type, e.Stats) 403 if e.Type != "stats" { 404 return nil, fmt.Errorf(`unexpected event type %q, wanted "stats"`, e.Type) 405 } 406 if e.Stats == nil { 407 return nil, fmt.Errorf(`"runsc events -stat" succeeded but no stat was provided`) 408 } 409 return e.Stats, nil 410 } 411 412 // Events returns an event stream from runsc for a container with stats and OOM notifications. 413 func (r *Runsc) Events(context context.Context, id string, interval time.Duration) (chan *runc.Event, error) { 414 cmd := r.command(context, "events", fmt.Sprintf("--interval=%ds", int(interval.Seconds())), id) 415 rd, err := cmd.StdoutPipe() 416 if err != nil { 417 return nil, err 418 } 419 ec, err := Monitor.Start(cmd) 420 if err != nil { 421 rd.Close() 422 return nil, err 423 } 424 var ( 425 dec = json.NewDecoder(rd) 426 c = make(chan *runc.Event, 128) 427 ) 428 go func() { 429 defer func() { 430 close(c) 431 rd.Close() 432 Monitor.Wait(cmd, ec) 433 }() 434 for { 435 var e runc.Event 436 if err := dec.Decode(&e); err != nil { 437 if err == io.EOF { 438 return 439 } 440 e = runc.Event{ 441 Type: "error", 442 Err: err, 443 } 444 } 445 c <- &e 446 } 447 }() 448 return c, nil 449 } 450 451 // Ps lists all the processes inside the container returning their pids. 452 func (r *Runsc) Ps(context context.Context, id string) ([]int, error) { 453 data, stderr, err := cmdOutput(r.command(context, "ps", "--format", "json", id), false) 454 if err != nil { 455 return nil, fmt.Errorf("%w: %s", err, stderr) 456 } 457 var pids []int 458 if err := json.Unmarshal(data, &pids); err != nil { 459 return nil, err 460 } 461 return pids, nil 462 } 463 464 // Top lists all the processes inside the container returning the full ps data. 465 func (r *Runsc) Top(context context.Context, id string) (*runc.TopResults, error) { 466 data, stderr, err := cmdOutput(r.command(context, "ps", "--format", "table", id), false) 467 if err != nil { 468 return nil, fmt.Errorf("%w: %s", err, stderr) 469 } 470 471 topResults, err := runc.ParsePSOutput(data) 472 if err != nil { 473 return nil, fmt.Errorf("%s: ", err) 474 } 475 return topResults, nil 476 } 477 478 func (r *Runsc) args() []string { 479 var args []string 480 if r.Root != "" { 481 args = append(args, fmt.Sprintf("--root=%s", r.Root)) 482 } 483 if r.Log != "" { 484 args = append(args, fmt.Sprintf("--log=%s", r.Log)) 485 } 486 if r.LogFormat != "" { 487 args = append(args, fmt.Sprintf("--log-format=%s", r.LogFormat)) 488 } 489 for k, v := range r.Config { 490 args = append(args, fmt.Sprintf("--%s=%s", k, v)) 491 } 492 return args 493 } 494 495 // runOrError will run the provided command. 496 // 497 // If an error is encountered and neither Stdout or Stderr was set the error 498 // will be returned in the format of <error>: <stderr>. 499 func (r *Runsc) runOrError(cmd *exec.Cmd) error { 500 if cmd.Stdout != nil || cmd.Stderr != nil { 501 ec, err := Monitor.Start(cmd) 502 if err != nil { 503 return err 504 } 505 status, err := Monitor.Wait(cmd, ec) 506 if err == nil && status != 0 { 507 err = fmt.Errorf("%s did not terminate sucessfully", cmd.Args[0]) 508 } 509 return err 510 } 511 out, _, err := cmdOutput(cmd, true) 512 if err != nil { 513 return fmt.Errorf("%w: %s", err, out) 514 } 515 return nil 516 } 517 518 func (r *Runsc) command(context context.Context, args ...string) *exec.Cmd { 519 command := r.Command 520 if command == "" { 521 command = DefaultCommand 522 } 523 cmd := exec.CommandContext(context, command, append(r.args(), args...)...) 524 cmd.SysProcAttr = &unix.SysProcAttr{ 525 Setpgid: r.Setpgid, 526 } 527 if r.PdeathSignal != 0 { 528 cmd.SysProcAttr.Pdeathsig = r.PdeathSignal 529 } 530 531 return cmd 532 } 533 534 func cmdOutput(cmd *exec.Cmd, combined bool) ([]byte, []byte, error) { 535 stdout := getBuf() 536 defer putBuf(stdout) 537 cmd.Stdout = stdout 538 cmd.Stderr = stdout 539 540 var stderr *bytes.Buffer 541 if !combined { 542 stderr = getBuf() 543 defer putBuf(stderr) 544 cmd.Stderr = stderr 545 } 546 ec, err := Monitor.Start(cmd) 547 if err != nil { 548 return nil, nil, err 549 } 550 551 status, err := Monitor.Wait(cmd, ec) 552 if err == nil && status != 0 { 553 err = fmt.Errorf("%q did not terminate sucessfully", cmd.Args[0]) 554 } 555 if stderr == nil { 556 return stdout.Bytes(), nil, err 557 } 558 return stdout.Bytes(), stderr.Bytes(), err 559 }