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  }