704 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			704 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
   Copyright The containerd Authors.
 | 
						|
 | 
						|
   Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
   you may not use this file except in compliance with the License.
 | 
						|
   You may obtain a copy of the License at
 | 
						|
 | 
						|
       http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 | 
						|
   Unless required by applicable law or agreed to in writing, software
 | 
						|
   distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
   See the License for the specific language governing permissions and
 | 
						|
   limitations under the License.
 | 
						|
*/
 | 
						|
 | 
						|
package runc
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"encoding/json"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"io"
 | 
						|
	"io/ioutil"
 | 
						|
	"os"
 | 
						|
	"os/exec"
 | 
						|
	"path/filepath"
 | 
						|
	"strconv"
 | 
						|
	"strings"
 | 
						|
	"syscall"
 | 
						|
	"time"
 | 
						|
 | 
						|
	specs "github.com/opencontainers/runtime-spec/specs-go"
 | 
						|
)
 | 
						|
 | 
						|
// Format is the type of log formatting options avaliable
 | 
						|
type Format string
 | 
						|
 | 
						|
// TopBody represents the structured data of the full ps output
 | 
						|
type TopResults struct {
 | 
						|
	// Processes running in the container, where each is process is an array of values corresponding to the headers
 | 
						|
	Processes [][]string `json:"Processes"`
 | 
						|
 | 
						|
	// Headers are the names of the columns
 | 
						|
	Headers []string `json:"Headers"`
 | 
						|
}
 | 
						|
 | 
						|
const (
 | 
						|
	none Format = ""
 | 
						|
	JSON Format = "json"
 | 
						|
	Text Format = "text"
 | 
						|
	// DefaultCommand is the default command for Runc
 | 
						|
	DefaultCommand = "runc"
 | 
						|
)
 | 
						|
 | 
						|
// Runc is the client to the runc cli
 | 
						|
type Runc struct {
 | 
						|
	//If command is empty, DefaultCommand is used
 | 
						|
	Command       string
 | 
						|
	Root          string
 | 
						|
	Debug         bool
 | 
						|
	Log           string
 | 
						|
	LogFormat     Format
 | 
						|
	PdeathSignal  syscall.Signal
 | 
						|
	Setpgid       bool
 | 
						|
	Criu          string
 | 
						|
	SystemdCgroup bool
 | 
						|
}
 | 
						|
 | 
						|
// List returns all containers created inside the provided runc root directory
 | 
						|
func (r *Runc) List(context context.Context) ([]*Container, error) {
 | 
						|
	data, err := cmdOutput(r.command(context, "list", "--format=json"), false)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	var out []*Container
 | 
						|
	if err := json.Unmarshal(data, &out); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return out, nil
 | 
						|
}
 | 
						|
 | 
						|
// State returns the state for the container provided by id
 | 
						|
func (r *Runc) State(context context.Context, id string) (*Container, error) {
 | 
						|
	data, err := cmdOutput(r.command(context, "state", id), true)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("%s: %s", err, data)
 | 
						|
	}
 | 
						|
	var c Container
 | 
						|
	if err := json.Unmarshal(data, &c); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return &c, nil
 | 
						|
}
 | 
						|
 | 
						|
type ConsoleSocket interface {
 | 
						|
	Path() string
 | 
						|
}
 | 
						|
 | 
						|
type CreateOpts struct {
 | 
						|
	IO
 | 
						|
	// PidFile is a path to where a pid file should be created
 | 
						|
	PidFile       string
 | 
						|
	ConsoleSocket ConsoleSocket
 | 
						|
	Detach        bool
 | 
						|
	NoPivot       bool
 | 
						|
	NoNewKeyring  bool
 | 
						|
	ExtraFiles    []*os.File
 | 
						|
}
 | 
						|
 | 
						|
func (o *CreateOpts) args() (out []string, err error) {
 | 
						|
	if o.PidFile != "" {
 | 
						|
		abs, err := filepath.Abs(o.PidFile)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		out = append(out, "--pid-file", abs)
 | 
						|
	}
 | 
						|
	if o.ConsoleSocket != nil {
 | 
						|
		out = append(out, "--console-socket", o.ConsoleSocket.Path())
 | 
						|
	}
 | 
						|
	if o.NoPivot {
 | 
						|
		out = append(out, "--no-pivot")
 | 
						|
	}
 | 
						|
	if o.NoNewKeyring {
 | 
						|
		out = append(out, "--no-new-keyring")
 | 
						|
	}
 | 
						|
	if o.Detach {
 | 
						|
		out = append(out, "--detach")
 | 
						|
	}
 | 
						|
	if o.ExtraFiles != nil {
 | 
						|
		out = append(out, "--preserve-fds", strconv.Itoa(len(o.ExtraFiles)))
 | 
						|
	}
 | 
						|
	return out, nil
 | 
						|
}
 | 
						|
 | 
						|
// Create creates a new container and returns its pid if it was created successfully
 | 
						|
func (r *Runc) Create(context context.Context, id, bundle string, opts *CreateOpts) error {
 | 
						|
	args := []string{"create", "--bundle", bundle}
 | 
						|
	if opts != nil {
 | 
						|
		oargs, err := opts.args()
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		args = append(args, oargs...)
 | 
						|
	}
 | 
						|
	cmd := r.command(context, append(args, id)...)
 | 
						|
	if opts != nil && opts.IO != nil {
 | 
						|
		opts.Set(cmd)
 | 
						|
	}
 | 
						|
	cmd.ExtraFiles = opts.ExtraFiles
 | 
						|
 | 
						|
	if cmd.Stdout == nil && cmd.Stderr == nil {
 | 
						|
		data, err := cmdOutput(cmd, true)
 | 
						|
		if err != nil {
 | 
						|
			return fmt.Errorf("%s: %s", err, data)
 | 
						|
		}
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	ec, err := Monitor.Start(cmd)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if opts != nil && opts.IO != nil {
 | 
						|
		if c, ok := opts.IO.(StartCloser); ok {
 | 
						|
			if err := c.CloseAfterStart(); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	status, err := Monitor.Wait(cmd, ec)
 | 
						|
	if err == nil && status != 0 {
 | 
						|
		err = fmt.Errorf("%s did not terminate sucessfully", cmd.Args[0])
 | 
						|
	}
 | 
						|
	return err
 | 
						|
}
 | 
						|
 | 
						|
// Start will start an already created container
 | 
						|
func (r *Runc) Start(context context.Context, id string) error {
 | 
						|
	return r.runOrError(r.command(context, "start", id))
 | 
						|
}
 | 
						|
 | 
						|
type ExecOpts struct {
 | 
						|
	IO
 | 
						|
	PidFile       string
 | 
						|
	ConsoleSocket ConsoleSocket
 | 
						|
	Detach        bool
 | 
						|
}
 | 
						|
 | 
						|
func (o *ExecOpts) args() (out []string, err error) {
 | 
						|
	if o.ConsoleSocket != nil {
 | 
						|
		out = append(out, "--console-socket", o.ConsoleSocket.Path())
 | 
						|
	}
 | 
						|
	if o.Detach {
 | 
						|
		out = append(out, "--detach")
 | 
						|
	}
 | 
						|
	if o.PidFile != "" {
 | 
						|
		abs, err := filepath.Abs(o.PidFile)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		out = append(out, "--pid-file", abs)
 | 
						|
	}
 | 
						|
	return out, nil
 | 
						|
}
 | 
						|
 | 
						|
// Exec executres and additional process inside the container based on a full
 | 
						|
// OCI Process specification
 | 
						|
func (r *Runc) Exec(context context.Context, id string, spec specs.Process, opts *ExecOpts) error {
 | 
						|
	f, err := ioutil.TempFile("", "runc-process")
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	defer os.Remove(f.Name())
 | 
						|
	err = json.NewEncoder(f).Encode(spec)
 | 
						|
	f.Close()
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	args := []string{"exec", "--process", f.Name()}
 | 
						|
	if opts != nil {
 | 
						|
		oargs, err := opts.args()
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		args = append(args, oargs...)
 | 
						|
	}
 | 
						|
	cmd := r.command(context, append(args, id)...)
 | 
						|
	if opts != nil && opts.IO != nil {
 | 
						|
		opts.Set(cmd)
 | 
						|
	}
 | 
						|
	if cmd.Stdout == nil && cmd.Stderr == nil {
 | 
						|
		data, err := cmdOutput(cmd, true)
 | 
						|
		if err != nil {
 | 
						|
			return fmt.Errorf("%s: %s", err, data)
 | 
						|
		}
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	ec, err := Monitor.Start(cmd)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if opts != nil && opts.IO != nil {
 | 
						|
		if c, ok := opts.IO.(StartCloser); ok {
 | 
						|
			if err := c.CloseAfterStart(); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	status, err := Monitor.Wait(cmd, ec)
 | 
						|
	if err == nil && status != 0 {
 | 
						|
		err = fmt.Errorf("%s did not terminate sucessfully", cmd.Args[0])
 | 
						|
	}
 | 
						|
	return err
 | 
						|
}
 | 
						|
 | 
						|
// Run runs the create, start, delete lifecycle of the container
 | 
						|
// and returns its exit status after it has exited
 | 
						|
func (r *Runc) Run(context context.Context, id, bundle string, opts *CreateOpts) (int, error) {
 | 
						|
	args := []string{"run", "--bundle", bundle}
 | 
						|
	if opts != nil {
 | 
						|
		oargs, err := opts.args()
 | 
						|
		if err != nil {
 | 
						|
			return -1, err
 | 
						|
		}
 | 
						|
		args = append(args, oargs...)
 | 
						|
	}
 | 
						|
	cmd := r.command(context, append(args, id)...)
 | 
						|
	if opts != nil && opts.IO != nil {
 | 
						|
		opts.Set(cmd)
 | 
						|
	}
 | 
						|
	ec, err := Monitor.Start(cmd)
 | 
						|
	if err != nil {
 | 
						|
		return -1, err
 | 
						|
	}
 | 
						|
	return Monitor.Wait(cmd, ec)
 | 
						|
}
 | 
						|
 | 
						|
type DeleteOpts struct {
 | 
						|
	Force bool
 | 
						|
}
 | 
						|
 | 
						|
func (o *DeleteOpts) args() (out []string) {
 | 
						|
	if o.Force {
 | 
						|
		out = append(out, "--force")
 | 
						|
	}
 | 
						|
	return out
 | 
						|
}
 | 
						|
 | 
						|
// Delete deletes the container
 | 
						|
func (r *Runc) Delete(context context.Context, id string, opts *DeleteOpts) error {
 | 
						|
	args := []string{"delete"}
 | 
						|
	if opts != nil {
 | 
						|
		args = append(args, opts.args()...)
 | 
						|
	}
 | 
						|
	return r.runOrError(r.command(context, append(args, id)...))
 | 
						|
}
 | 
						|
 | 
						|
// KillOpts specifies options for killing a container and its processes
 | 
						|
type KillOpts struct {
 | 
						|
	All bool
 | 
						|
}
 | 
						|
 | 
						|
func (o *KillOpts) args() (out []string) {
 | 
						|
	if o.All {
 | 
						|
		out = append(out, "--all")
 | 
						|
	}
 | 
						|
	return out
 | 
						|
}
 | 
						|
 | 
						|
// Kill sends the specified signal to the container
 | 
						|
func (r *Runc) Kill(context context.Context, id string, sig int, opts *KillOpts) error {
 | 
						|
	args := []string{
 | 
						|
		"kill",
 | 
						|
	}
 | 
						|
	if opts != nil {
 | 
						|
		args = append(args, opts.args()...)
 | 
						|
	}
 | 
						|
	return r.runOrError(r.command(context, append(args, id, strconv.Itoa(sig))...))
 | 
						|
}
 | 
						|
 | 
						|
// Stats return the stats for a container like cpu, memory, and io
 | 
						|
func (r *Runc) Stats(context context.Context, id string) (*Stats, error) {
 | 
						|
	cmd := r.command(context, "events", "--stats", id)
 | 
						|
	rd, err := cmd.StdoutPipe()
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	ec, err := Monitor.Start(cmd)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	defer func() {
 | 
						|
		rd.Close()
 | 
						|
		Monitor.Wait(cmd, ec)
 | 
						|
	}()
 | 
						|
	var e Event
 | 
						|
	if err := json.NewDecoder(rd).Decode(&e); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return e.Stats, nil
 | 
						|
}
 | 
						|
 | 
						|
// Events returns an event stream from runc for a container with stats and OOM notifications
 | 
						|
func (r *Runc) Events(context context.Context, id string, interval time.Duration) (chan *Event, error) {
 | 
						|
	cmd := r.command(context, "events", fmt.Sprintf("--interval=%ds", int(interval.Seconds())), id)
 | 
						|
	rd, err := cmd.StdoutPipe()
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	ec, err := Monitor.Start(cmd)
 | 
						|
	if err != nil {
 | 
						|
		rd.Close()
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	var (
 | 
						|
		dec = json.NewDecoder(rd)
 | 
						|
		c   = make(chan *Event, 128)
 | 
						|
	)
 | 
						|
	go func() {
 | 
						|
		defer func() {
 | 
						|
			close(c)
 | 
						|
			rd.Close()
 | 
						|
			Monitor.Wait(cmd, ec)
 | 
						|
		}()
 | 
						|
		for {
 | 
						|
			var e Event
 | 
						|
			if err := dec.Decode(&e); err != nil {
 | 
						|
				if err == io.EOF {
 | 
						|
					return
 | 
						|
				}
 | 
						|
				e = Event{
 | 
						|
					Type: "error",
 | 
						|
					Err:  err,
 | 
						|
				}
 | 
						|
			}
 | 
						|
			c <- &e
 | 
						|
		}
 | 
						|
	}()
 | 
						|
	return c, nil
 | 
						|
}
 | 
						|
 | 
						|
// Pause the container with the provided id
 | 
						|
func (r *Runc) Pause(context context.Context, id string) error {
 | 
						|
	return r.runOrError(r.command(context, "pause", id))
 | 
						|
}
 | 
						|
 | 
						|
// Resume the container with the provided id
 | 
						|
func (r *Runc) Resume(context context.Context, id string) error {
 | 
						|
	return r.runOrError(r.command(context, "resume", id))
 | 
						|
}
 | 
						|
 | 
						|
// Ps lists all the processes inside the container returning their pids
 | 
						|
func (r *Runc) Ps(context context.Context, id string) ([]int, error) {
 | 
						|
	data, err := cmdOutput(r.command(context, "ps", "--format", "json", id), true)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("%s: %s", err, data)
 | 
						|
	}
 | 
						|
	var pids []int
 | 
						|
	if err := json.Unmarshal(data, &pids); err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return pids, nil
 | 
						|
}
 | 
						|
 | 
						|
// Top lists all the processes inside the container returning the full ps data
 | 
						|
func (r *Runc) Top(context context.Context, id string, psOptions string) (*TopResults, error) {
 | 
						|
	data, err := cmdOutput(r.command(context, "ps", "--format", "table", id, psOptions), true)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("%s: %s", err, data)
 | 
						|
	}
 | 
						|
 | 
						|
	topResults, err := parsePSOutput(data)
 | 
						|
	if err != nil {
 | 
						|
		return nil, fmt.Errorf("%s: ", err)
 | 
						|
	}
 | 
						|
	return topResults, nil
 | 
						|
}
 | 
						|
 | 
						|
type CheckpointOpts struct {
 | 
						|
	// ImagePath is the path for saving the criu image file
 | 
						|
	ImagePath string
 | 
						|
	// WorkDir is the working directory for criu
 | 
						|
	WorkDir string
 | 
						|
	// ParentPath is the path for previous image files from a pre-dump
 | 
						|
	ParentPath string
 | 
						|
	// AllowOpenTCP allows open tcp connections to be checkpointed
 | 
						|
	AllowOpenTCP bool
 | 
						|
	// AllowExternalUnixSockets allows external unix sockets to be checkpointed
 | 
						|
	AllowExternalUnixSockets bool
 | 
						|
	// AllowTerminal allows the terminal(pty) to be checkpointed with a container
 | 
						|
	AllowTerminal bool
 | 
						|
	// CriuPageServer is the address:port for the criu page server
 | 
						|
	CriuPageServer string
 | 
						|
	// FileLocks handle file locks held by the container
 | 
						|
	FileLocks bool
 | 
						|
	// Cgroups is the cgroup mode for how to handle the checkpoint of a container's cgroups
 | 
						|
	Cgroups CgroupMode
 | 
						|
	// EmptyNamespaces creates a namespace for the container but does not save its properties
 | 
						|
	// Provide the namespaces you wish to be checkpointed without their settings on restore
 | 
						|
	EmptyNamespaces []string
 | 
						|
}
 | 
						|
 | 
						|
type CgroupMode string
 | 
						|
 | 
						|
const (
 | 
						|
	Soft   CgroupMode = "soft"
 | 
						|
	Full   CgroupMode = "full"
 | 
						|
	Strict CgroupMode = "strict"
 | 
						|
)
 | 
						|
 | 
						|
func (o *CheckpointOpts) args() (out []string) {
 | 
						|
	if o.ImagePath != "" {
 | 
						|
		out = append(out, "--image-path", o.ImagePath)
 | 
						|
	}
 | 
						|
	if o.WorkDir != "" {
 | 
						|
		out = append(out, "--work-path", o.WorkDir)
 | 
						|
	}
 | 
						|
	if o.ParentPath != "" {
 | 
						|
		out = append(out, "--parent-path", o.ParentPath)
 | 
						|
	}
 | 
						|
	if o.AllowOpenTCP {
 | 
						|
		out = append(out, "--tcp-established")
 | 
						|
	}
 | 
						|
	if o.AllowExternalUnixSockets {
 | 
						|
		out = append(out, "--ext-unix-sk")
 | 
						|
	}
 | 
						|
	if o.AllowTerminal {
 | 
						|
		out = append(out, "--shell-job")
 | 
						|
	}
 | 
						|
	if o.CriuPageServer != "" {
 | 
						|
		out = append(out, "--page-server", o.CriuPageServer)
 | 
						|
	}
 | 
						|
	if o.FileLocks {
 | 
						|
		out = append(out, "--file-locks")
 | 
						|
	}
 | 
						|
	if string(o.Cgroups) != "" {
 | 
						|
		out = append(out, "--manage-cgroups-mode", string(o.Cgroups))
 | 
						|
	}
 | 
						|
	for _, ns := range o.EmptyNamespaces {
 | 
						|
		out = append(out, "--empty-ns", ns)
 | 
						|
	}
 | 
						|
	return out
 | 
						|
}
 | 
						|
 | 
						|
type CheckpointAction func([]string) []string
 | 
						|
 | 
						|
// LeaveRunning keeps the container running after the checkpoint has been completed
 | 
						|
func LeaveRunning(args []string) []string {
 | 
						|
	return append(args, "--leave-running")
 | 
						|
}
 | 
						|
 | 
						|
// PreDump allows a pre-dump of the checkpoint to be made and completed later
 | 
						|
func PreDump(args []string) []string {
 | 
						|
	return append(args, "--pre-dump")
 | 
						|
}
 | 
						|
 | 
						|
// Checkpoint allows you to checkpoint a container using criu
 | 
						|
func (r *Runc) Checkpoint(context context.Context, id string, opts *CheckpointOpts, actions ...CheckpointAction) error {
 | 
						|
	args := []string{"checkpoint"}
 | 
						|
	if opts != nil {
 | 
						|
		args = append(args, opts.args()...)
 | 
						|
	}
 | 
						|
	for _, a := range actions {
 | 
						|
		args = a(args)
 | 
						|
	}
 | 
						|
	return r.runOrError(r.command(context, append(args, id)...))
 | 
						|
}
 | 
						|
 | 
						|
type RestoreOpts struct {
 | 
						|
	CheckpointOpts
 | 
						|
	IO
 | 
						|
 | 
						|
	Detach        bool
 | 
						|
	PidFile       string
 | 
						|
	NoSubreaper   bool
 | 
						|
	NoPivot       bool
 | 
						|
	ConsoleSocket ConsoleSocket
 | 
						|
}
 | 
						|
 | 
						|
func (o *RestoreOpts) args() ([]string, error) {
 | 
						|
	out := o.CheckpointOpts.args()
 | 
						|
	if o.Detach {
 | 
						|
		out = append(out, "--detach")
 | 
						|
	}
 | 
						|
	if o.PidFile != "" {
 | 
						|
		abs, err := filepath.Abs(o.PidFile)
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
		out = append(out, "--pid-file", abs)
 | 
						|
	}
 | 
						|
	if o.ConsoleSocket != nil {
 | 
						|
		out = append(out, "--console-socket", o.ConsoleSocket.Path())
 | 
						|
	}
 | 
						|
	if o.NoPivot {
 | 
						|
		out = append(out, "--no-pivot")
 | 
						|
	}
 | 
						|
	if o.NoSubreaper {
 | 
						|
		out = append(out, "-no-subreaper")
 | 
						|
	}
 | 
						|
	return out, nil
 | 
						|
}
 | 
						|
 | 
						|
// Restore restores a container with the provide id from an existing checkpoint
 | 
						|
func (r *Runc) Restore(context context.Context, id, bundle string, opts *RestoreOpts) (int, error) {
 | 
						|
	args := []string{"restore"}
 | 
						|
	if opts != nil {
 | 
						|
		oargs, err := opts.args()
 | 
						|
		if err != nil {
 | 
						|
			return -1, err
 | 
						|
		}
 | 
						|
		args = append(args, oargs...)
 | 
						|
	}
 | 
						|
	args = append(args, "--bundle", bundle)
 | 
						|
	cmd := r.command(context, append(args, id)...)
 | 
						|
	if opts != nil && opts.IO != nil {
 | 
						|
		opts.Set(cmd)
 | 
						|
	}
 | 
						|
	ec, err := Monitor.Start(cmd)
 | 
						|
	if err != nil {
 | 
						|
		return -1, err
 | 
						|
	}
 | 
						|
	if opts != nil && opts.IO != nil {
 | 
						|
		if c, ok := opts.IO.(StartCloser); ok {
 | 
						|
			if err := c.CloseAfterStart(); err != nil {
 | 
						|
				return -1, err
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return Monitor.Wait(cmd, ec)
 | 
						|
}
 | 
						|
 | 
						|
// Update updates the current container with the provided resource spec
 | 
						|
func (r *Runc) Update(context context.Context, id string, resources *specs.LinuxResources) error {
 | 
						|
	buf := getBuf()
 | 
						|
	defer putBuf(buf)
 | 
						|
 | 
						|
	if err := json.NewEncoder(buf).Encode(resources); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	args := []string{"update", "--resources", "-", id}
 | 
						|
	cmd := r.command(context, args...)
 | 
						|
	cmd.Stdin = buf
 | 
						|
	return r.runOrError(cmd)
 | 
						|
}
 | 
						|
 | 
						|
var ErrParseRuncVersion = errors.New("unable to parse runc version")
 | 
						|
 | 
						|
type Version struct {
 | 
						|
	Runc   string
 | 
						|
	Commit string
 | 
						|
	Spec   string
 | 
						|
}
 | 
						|
 | 
						|
// Version returns the runc and runtime-spec versions
 | 
						|
func (r *Runc) Version(context context.Context) (Version, error) {
 | 
						|
	data, err := cmdOutput(r.command(context, "--version"), false)
 | 
						|
	if err != nil {
 | 
						|
		return Version{}, err
 | 
						|
	}
 | 
						|
	return parseVersion(data)
 | 
						|
}
 | 
						|
 | 
						|
func parseVersion(data []byte) (Version, error) {
 | 
						|
	var v Version
 | 
						|
	parts := strings.Split(strings.TrimSpace(string(data)), "\n")
 | 
						|
	if len(parts) != 3 {
 | 
						|
		return v, ErrParseRuncVersion
 | 
						|
	}
 | 
						|
 | 
						|
	for i, p := range []struct {
 | 
						|
		dest  *string
 | 
						|
		split string
 | 
						|
	}{
 | 
						|
		{
 | 
						|
			dest:  &v.Runc,
 | 
						|
			split: "version ",
 | 
						|
		},
 | 
						|
		{
 | 
						|
			dest:  &v.Commit,
 | 
						|
			split: ": ",
 | 
						|
		},
 | 
						|
		{
 | 
						|
			dest:  &v.Spec,
 | 
						|
			split: ": ",
 | 
						|
		},
 | 
						|
	} {
 | 
						|
		p2 := strings.Split(parts[i], p.split)
 | 
						|
		if len(p2) != 2 {
 | 
						|
			return v, fmt.Errorf("unable to parse version line %q", parts[i])
 | 
						|
		}
 | 
						|
		*p.dest = p2[1]
 | 
						|
	}
 | 
						|
	return v, nil
 | 
						|
}
 | 
						|
 | 
						|
func (r *Runc) args() (out []string) {
 | 
						|
	if r.Root != "" {
 | 
						|
		out = append(out, "--root", r.Root)
 | 
						|
	}
 | 
						|
	if r.Debug {
 | 
						|
		out = append(out, "--debug")
 | 
						|
	}
 | 
						|
	if r.Log != "" {
 | 
						|
		out = append(out, "--log", r.Log)
 | 
						|
	}
 | 
						|
	if r.LogFormat != none {
 | 
						|
		out = append(out, "--log-format", string(r.LogFormat))
 | 
						|
	}
 | 
						|
	if r.Criu != "" {
 | 
						|
		out = append(out, "--criu", r.Criu)
 | 
						|
	}
 | 
						|
	if r.SystemdCgroup {
 | 
						|
		out = append(out, "--systemd-cgroup")
 | 
						|
	}
 | 
						|
	return out
 | 
						|
}
 | 
						|
 | 
						|
// runOrError will run the provided command.  If an error is
 | 
						|
// encountered and neither Stdout or Stderr was set the error and the
 | 
						|
// stderr of the command will be returned in the format of <error>:
 | 
						|
// <stderr>
 | 
						|
func (r *Runc) runOrError(cmd *exec.Cmd) error {
 | 
						|
	if cmd.Stdout != nil || cmd.Stderr != nil {
 | 
						|
		ec, err := Monitor.Start(cmd)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		status, err := Monitor.Wait(cmd, ec)
 | 
						|
		if err == nil && status != 0 {
 | 
						|
			err = fmt.Errorf("%s did not terminate sucessfully", cmd.Args[0])
 | 
						|
		}
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	data, err := cmdOutput(cmd, true)
 | 
						|
	if err != nil {
 | 
						|
		return fmt.Errorf("%s: %s", err, data)
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func cmdOutput(cmd *exec.Cmd, combined bool) ([]byte, error) {
 | 
						|
	b := getBuf()
 | 
						|
	defer putBuf(b)
 | 
						|
 | 
						|
	cmd.Stdout = b
 | 
						|
	if combined {
 | 
						|
		cmd.Stderr = b
 | 
						|
	}
 | 
						|
	ec, err := Monitor.Start(cmd)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	status, err := Monitor.Wait(cmd, ec)
 | 
						|
	if err == nil && status != 0 {
 | 
						|
		err = fmt.Errorf("%s did not terminate sucessfully", cmd.Args[0])
 | 
						|
	}
 | 
						|
 | 
						|
	return b.Bytes(), err
 | 
						|
}
 |