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