Merge v1.2.1-master

Signed-off-by: Emile Vauge <emile@vauge.com>
This commit is contained in:
Emile Vauge 2017-04-11 17:10:46 +02:00
parent a590155b0b
commit aeb17182b4
No known key found for this signature in database
GPG key ID: D808B4C167352E59
396 changed files with 27271 additions and 9969 deletions

View file

@ -24,20 +24,37 @@ checkpointed.`,
Flags: []cli.Flag{
cli.StringFlag{Name: "image-path", Value: "", Usage: "path for saving criu image files"},
cli.StringFlag{Name: "work-path", Value: "", Usage: "path for saving work files and logs"},
cli.StringFlag{Name: "parent-path", Value: "", Usage: "path for previous criu image files in pre-dump"},
cli.BoolFlag{Name: "leave-running", Usage: "leave the process running after checkpointing"},
cli.BoolFlag{Name: "tcp-established", Usage: "allow open tcp connections"},
cli.BoolFlag{Name: "ext-unix-sk", Usage: "allow external unix sockets"},
cli.BoolFlag{Name: "shell-job", Usage: "allow shell jobs"},
cli.StringFlag{Name: "page-server", Value: "", Usage: "ADDRESS:PORT of the page server"},
cli.BoolFlag{Name: "file-locks", Usage: "handle file locks, for safety"},
cli.BoolFlag{Name: "pre-dump", Usage: "dump container's memory information only, leave the container running after this"},
cli.StringFlag{Name: "manage-cgroups-mode", Value: "", Usage: "cgroups mode: 'soft' (default), 'full' and 'strict'"},
cli.StringSliceFlag{Name: "empty-ns", Usage: "create a namespace, but don't restore its properies"},
},
Action: func(context *cli.Context) error {
if err := checkArgs(context, 1, exactArgs); err != nil {
return err
}
// XXX: Currently this is untested with rootless containers.
if isRootless() {
return fmt.Errorf("runc checkpoint requires root")
}
container, err := getContainer(context)
if err != nil {
return err
}
status, err := container.Status()
if err != nil {
return err
}
if status == libcontainer.Created {
fatalf("Container cannot be checkpointed in created state")
}
defer destroy(container)
options := criuOptions(context)
// these are the mandatory criu options for a container
@ -95,7 +112,7 @@ func setManageCgroupsMode(context *cli.Context, options *libcontainer.CriuOpts)
}
}
var namespaceMapping = map[specs.NamespaceType]int{
var namespaceMapping = map[specs.LinuxNamespaceType]int{
specs.NetworkNamespace: syscall.CLONE_NEWNET,
}
@ -103,7 +120,7 @@ func setEmptyNsMask(context *cli.Context, options *libcontainer.CriuOpts) error
var nsmask int
for _, ns := range context.StringSlice("empty-ns") {
f, exists := namespaceMapping[specs.NamespaceType(ns)]
f, exists := namespaceMapping[specs.LinuxNamespaceType(ns)]
if !exists {
return fmt.Errorf("namespace %q is not supported", ns)
}

View file

@ -29,9 +29,9 @@ command(s) that get executed on start, edit the args parameter of the spec. See
Usage: `path to the root of the bundle directory, defaults to the current directory`,
},
cli.StringFlag{
Name: "console",
Name: "console-socket",
Value: "",
Usage: "specify the pty slave path for use with the container",
Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal",
},
cli.StringFlag{
Name: "pid-file",
@ -46,8 +46,18 @@ command(s) that get executed on start, edit the args parameter of the spec. See
Name: "no-new-keyring",
Usage: "do not create a new session keyring for the container. This will cause the container to inherit the calling processes session key",
},
cli.IntFlag{
Name: "preserve-fds",
Usage: "Pass N additional file descriptors to the container (stdio + $LISTEN_FDS + N in total)",
},
},
Action: func(context *cli.Context) error {
if err := checkArgs(context, 1, exactArgs); err != nil {
return err
}
if err := revisePidFile(context); err != nil {
return err
}
spec, err := setupSpec(context)
if err != nil {
return err

View file

@ -14,10 +14,10 @@ import (
)
func killContainer(container libcontainer.Container) error {
container.Signal(syscall.SIGKILL)
_ = container.Signal(syscall.SIGKILL, false)
for i := 0; i < 100; i++ {
time.Sleep(100 * time.Millisecond)
if err := container.Signal(syscall.Signal(0)); err != nil {
if err := container.Signal(syscall.Signal(0), false); err != nil {
destroy(container)
return nil
}
@ -27,7 +27,7 @@ func killContainer(container libcontainer.Container) error {
var deleteCommand = cli.Command{
Name: "delete",
Usage: "delete any resources held by the container often used with detached containers",
Usage: "delete any resources held by the container often used with detached container",
ArgsUsage: `<container-id>
Where "<container-id>" is the name for the instance of the container.
@ -41,21 +41,26 @@ status of "ubuntu01" as "stopped" the following will delete resources held for
Flags: []cli.Flag{
cli.BoolFlag{
Name: "force, f",
Usage: "Forcibly kills the container if it is still running",
Usage: "Forcibly deletes the container if it is still running (uses SIGKILL)",
},
},
Action: func(context *cli.Context) error {
if err := checkArgs(context, 1, exactArgs); err != nil {
return err
}
id := context.Args().First()
container, err := getContainer(context)
if err != nil {
if lerr, ok := err.(libcontainer.Error); ok && lerr.Code() == libcontainer.ContainerNotExists {
// if there was an aborted start or something of the sort then the container's directory could exist but
// libcontainer does not see it because the state.json file inside that directory was never created.
path := filepath.Join(context.GlobalString("root"), context.Args().First())
if err := os.RemoveAll(path); err != nil {
return err
path := filepath.Join(context.GlobalString("root"), id)
if e := os.RemoveAll(path); e != nil {
fmt.Fprintf(os.Stderr, "remove %s: %v\n", path, e)
}
}
return nil
return err
}
s, err := container.Status()
if err != nil {
@ -69,9 +74,11 @@ status of "ubuntu01" as "stopped" the following will delete resources held for
default:
if context.Bool("force") {
return killContainer(container)
} else {
return fmt.Errorf("cannot delete container %s that is not stopped: %s\n", id, s)
}
return fmt.Errorf("cannot delete container that is not stopped: %s", s)
}
return nil
},
}

View file

@ -24,7 +24,7 @@ type event struct {
// stats is the runc specific stats structure for stability when encoding and decoding stats.
type stats struct {
Cpu cpu `json:"cpu"`
CPU cpu `json:"cpu"`
Memory memory `json:"memory"`
Pids pids `json:"pids"`
Blkio blkio `json:"blkio"`
@ -108,6 +108,9 @@ information is displayed once every 5 seconds.`,
cli.BoolFlag{Name: "stats", Usage: "display the container's stats then exit"},
},
Action: func(context *cli.Context) error {
if err := checkArgs(context, 1, exactArgs); err != nil {
return err
}
container, err := getContainer(context)
if err != nil {
return err
@ -121,7 +124,6 @@ information is displayed once every 5 seconds.`,
return err
}
if status == libcontainer.Stopped {
fatalf("container with id %s is not running", container.ID())
return fmt.Errorf("container with id %s is not running", container.ID())
}
var (
@ -196,13 +198,13 @@ func convertLibcontainerStats(ls *libcontainer.Stats) *stats {
s.Pids.Current = cg.PidsStats.Current
s.Pids.Limit = cg.PidsStats.Limit
s.Cpu.Usage.Kernel = cg.CpuStats.CpuUsage.UsageInKernelmode
s.Cpu.Usage.User = cg.CpuStats.CpuUsage.UsageInUsermode
s.Cpu.Usage.Total = cg.CpuStats.CpuUsage.TotalUsage
s.Cpu.Usage.Percpu = cg.CpuStats.CpuUsage.PercpuUsage
s.Cpu.Throttling.Periods = cg.CpuStats.ThrottlingData.Periods
s.Cpu.Throttling.ThrottledPeriods = cg.CpuStats.ThrottlingData.ThrottledPeriods
s.Cpu.Throttling.ThrottledTime = cg.CpuStats.ThrottlingData.ThrottledTime
s.CPU.Usage.Kernel = cg.CpuStats.CpuUsage.UsageInKernelmode
s.CPU.Usage.User = cg.CpuStats.CpuUsage.UsageInUsermode
s.CPU.Usage.Total = cg.CpuStats.CpuUsage.TotalUsage
s.CPU.Usage.Percpu = cg.CpuStats.CpuUsage.PercpuUsage
s.CPU.Throttling.Periods = cg.CpuStats.ThrottlingData.Periods
s.CPU.Throttling.ThrottledPeriods = cg.CpuStats.ThrottlingData.ThrottledPeriods
s.CPU.Throttling.ThrottledTime = cg.CpuStats.ThrottlingData.ThrottledTime
s.Memory.Cache = cg.MemoryStats.Cache
s.Memory.Kernel = convertMemoryEntry(cg.MemoryStats.KernelUsage)

View file

@ -9,6 +9,7 @@ import (
"strconv"
"strings"
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/utils"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/urfave/cli"
@ -17,20 +18,21 @@ import (
var execCommand = cli.Command{
Name: "exec",
Usage: "execute new process inside the container",
ArgsUsage: `<container-id> <container command>
ArgsUsage: `<container-id> <command> [command options] || -p process.json <container-id>
Where "<container-id>" is the name for the instance of the container and
"<container command>" is the command to be executed in the container.
"<command>" is the command to be executed in the container.
"<command>" can't be empty unless a "-p" flag provided.
EXAMPLE:
For example, if the container is configured to run the linux ps command the
following will output a list of processes running in the container:
# runc exec <container-id> ps`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "console",
Usage: "specify the pty slave path for use with the container",
Name: "console-socket",
Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal",
},
cli.StringFlag{
Name: "cwd",
@ -79,13 +81,17 @@ following will output a list of processes running in the container:
Usage: "add a capability to the bounding set for the process",
},
cli.BoolFlag{
Name: "no-subreaper",
Usage: "disable the use of the subreaper used to reap reparented processes",
Name: "no-subreaper",
Usage: "disable the use of the subreaper used to reap reparented processes",
Hidden: true,
},
},
Action: func(context *cli.Context) error {
if os.Geteuid() != 0 {
return fmt.Errorf("runc should be run as root")
if err := checkArgs(context, 1, minArgs); err != nil {
return err
}
if err := revisePidFile(context); err != nil {
return err
}
status, err := execProcess(context)
if err == nil {
@ -93,6 +99,7 @@ following will output a list of processes running in the container:
}
return fmt.Errorf("exec failed: %v", err)
},
SkipArgReorder: true,
}
func execProcess(context *cli.Context) (int, error) {
@ -100,6 +107,13 @@ func execProcess(context *cli.Context) (int, error) {
if err != nil {
return -1, err
}
status, err := container.Status()
if err != nil {
return -1, err
}
if status == libcontainer.Stopped {
return -1, fmt.Errorf("cannot exec a container that has stopped")
}
path := context.String("process")
if path == "" && len(context.Args()) == 1 {
return -1, fmt.Errorf("process args cannot be empty")
@ -115,10 +129,10 @@ func execProcess(context *cli.Context) (int, error) {
return -1, err
}
r := &runner{
enableSubreaper: !context.Bool("no-subreaper"),
enableSubreaper: false,
shouldDestroy: false,
container: container,
console: context.String("console"),
consoleSocket: context.String("console-socket"),
detach: detach,
pidFile: context.String("pid-file"),
}
@ -159,12 +173,17 @@ func getProcess(context *cli.Context, bundle string) (*specs.Process, error) {
p.SelinuxLabel = l
}
if caps := context.StringSlice("cap"); len(caps) > 0 {
p.Capabilities = caps
for _, c := range caps {
p.Capabilities.Bounding = append(p.Capabilities.Bounding, c)
p.Capabilities.Inheritable = append(p.Capabilities.Inheritable, c)
p.Capabilities.Effective = append(p.Capabilities.Effective, c)
p.Capabilities.Permitted = append(p.Capabilities.Permitted, c)
p.Capabilities.Ambient = append(p.Capabilities.Ambient, c)
}
}
// append the passed env variables
for _, e := range context.StringSlice("env") {
p.Env = append(p.Env, e)
}
p.Env = append(p.Env, context.StringSlice("env")...)
// set the tty
if context.IsSet("tty") {
p.Terminal = context.Bool("tty")

View file

@ -52,17 +52,29 @@ var signalMap = map[string]syscall.Signal{
var killCommand = cli.Command{
Name: "kill",
Usage: "kill sends the specified signal (default: SIGTERM) to the container's init process",
ArgsUsage: `<container-id> <signal>
ArgsUsage: `<container-id> [signal]
Where "<container-id>" is the name for the instance of the container and
"<signal>" is the signal to be sent to the init process.
"[signal]" is the signal to be sent to the init process.
EXAMPLE:
For example, if the container id is "ubuntu01" the following will send a "KILL"
signal to the init process of the "ubuntu01" container:
# runc kill ubuntu01 KILL`,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "all, a",
Usage: "send the specified signal to all processes inside the container",
},
},
Action: func(context *cli.Context) error {
if err := checkArgs(context, 1, minArgs); err != nil {
return err
}
if err := checkArgs(context, 2, maxArgs); err != nil {
return err
}
container, err := getContainer(context)
if err != nil {
return err
@ -77,8 +89,7 @@ signal to the init process of the "ubuntu01" container:
if err != nil {
return err
}
if err := container.Signal(signal); err != nil {
if err := container.Signal(signal, context.Bool("all")); err != nil {
return err
}
return nil
@ -88,7 +99,13 @@ signal to the init process of the "ubuntu01" container:
func parseSignal(rawSignal string) (syscall.Signal, error) {
s, err := strconv.Atoi(rawSignal)
if err == nil {
return syscall.Signal(s), nil
sig := syscall.Signal(s)
for _, msig := range signalMap {
if sig == msig {
return sig, nil
}
}
return -1, fmt.Errorf("unknown signal %q", rawSignal)
}
signal, ok := signalMap[strings.TrimPrefix(strings.ToUpper(rawSignal), "SIG")]
if !ok {

View file

@ -199,18 +199,16 @@ type ExecUser struct {
// files cannot be opened for any reason, the error is ignored and a nil
// io.Reader is passed instead.
func GetExecUserPath(userSpec string, defaults *ExecUser, passwdPath, groupPath string) (*ExecUser, error) {
passwd, err := os.Open(passwdPath)
if err != nil {
passwd = nil
} else {
defer passwd.Close()
var passwd, group io.Reader
if passwdFile, err := os.Open(passwdPath); err == nil {
passwd = passwdFile
defer passwdFile.Close()
}
group, err := os.Open(groupPath)
if err != nil {
group = nil
} else {
defer group.Close()
if groupFile, err := os.Open(groupPath); err == nil {
group = groupFile
defer groupFile.Close()
}
return GetExecUser(userSpec, defaults, passwd, group)
@ -343,7 +341,7 @@ func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (
if len(groups) > 0 {
// First match wins, even if there's more than one matching entry.
user.Gid = groups[0].Gid
} else if groupArg != "" {
} else {
// If we can't find a group with the given name, the only other valid
// option is if it's a numeric group name with no associated entry in group.
@ -433,9 +431,11 @@ func GetAdditionalGroups(additionalGroups []string, group io.Reader) ([]int, err
// that opens the groupPath given and gives it as an argument to
// GetAdditionalGroups.
func GetAdditionalGroupsPath(additionalGroups []string, groupPath string) ([]int, error) {
group, err := os.Open(groupPath)
if err == nil {
defer group.Close()
var group io.Reader
if groupFile, err := os.Open(groupPath); err == nil {
group = groupFile
defer groupFile.Close()
}
return GetAdditionalGroups(additionalGroups, group)
}

View file

@ -7,11 +7,14 @@ import (
"io/ioutil"
"os"
"path/filepath"
"syscall"
"text/tabwriter"
"time"
"encoding/json"
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/user"
"github.com/opencontainers/runc/libcontainer/utils"
"github.com/urfave/cli"
)
@ -21,6 +24,8 @@ const formatOptions = `table or json`
// containerState represents the platform agnostic pieces relating to a
// running container's status and state
type containerState struct {
// Version is the OCI version for the container
Version string `json:"ociVersion"`
// ID is the container ID
ID string `json:"id"`
// InitProcessPid is the init process id in the parent namespace
@ -29,10 +34,14 @@ type containerState struct {
Status string `json:"status"`
// Bundle is the path on the filesystem to the bundle
Bundle string `json:"bundle"`
// Rootfs is a path to a directory containing the container's root filesystem.
Rootfs string `json:"rootfs"`
// Created is the unix timestamp for the creation time of the container in UTC
Created time.Time `json:"created"`
// Annotations is the user defined annotations added to the config.
Annotations map[string]string `json:"annotations,omitempty"`
// The owner of the state directory (the owner of the container).
Owner string `json:"owner"`
}
var listCommand = cli.Command{
@ -62,6 +71,9 @@ To list containers created using a non-default value for "--root":
},
},
Action: func(context *cli.Context) error {
if err := checkArgs(context, 0, exactArgs); err != nil {
return err
}
s, err := getContainers(context)
if err != nil {
return err
@ -77,14 +89,15 @@ To list containers created using a non-default value for "--root":
switch context.String("format") {
case "table":
w := tabwriter.NewWriter(os.Stdout, 12, 1, 3, ' ', 0)
fmt.Fprint(w, "ID\tPID\tSTATUS\tBUNDLE\tCREATED\n")
fmt.Fprint(w, "ID\tPID\tSTATUS\tBUNDLE\tCREATED\tOWNER\n")
for _, item := range s {
fmt.Fprintf(w, "%s\t%d\t%s\t%s\t%s\n",
fmt.Fprintf(w, "%s\t%d\t%s\t%s\t%s\t%s\n",
item.ID,
item.InitProcessPid,
item.Status,
item.Bundle,
item.Created.Format(time.RFC3339Nano))
item.Created.Format(time.RFC3339Nano),
item.Owner)
}
if err := w.Flush(); err != nil {
return err
@ -118,26 +131,43 @@ func getContainers(context *cli.Context) ([]containerState, error) {
var s []containerState
for _, item := range list {
if item.IsDir() {
// This cast is safe on Linux.
stat := item.Sys().(*syscall.Stat_t)
owner, err := user.LookupUid(int(stat.Uid))
if err != nil {
owner.Name = string(stat.Uid)
}
container, err := factory.Load(item.Name())
if err != nil {
return nil, err
fmt.Fprintf(os.Stderr, "load container %s: %v\n", item.Name(), err)
continue
}
containerStatus, err := container.Status()
if err != nil {
return nil, err
fmt.Fprintf(os.Stderr, "status for %s: %v\n", item.Name(), err)
continue
}
state, err := container.State()
if err != nil {
return nil, err
fmt.Fprintf(os.Stderr, "state for %s: %v\n", item.Name(), err)
continue
}
pid := state.BaseState.InitProcessPid
if containerStatus == libcontainer.Stopped {
pid = 0
}
bundle, annotations := utils.Annotations(state.Config.Labels)
s = append(s, containerState{
Version: state.BaseState.Config.Version,
ID: state.BaseState.ID,
InitProcessPid: state.BaseState.InitProcessPid,
InitProcessPid: pid,
Status: containerStatus.String(),
Bundle: bundle,
Rootfs: state.BaseState.Config.Rootfs,
Created: state.BaseState.Created,
Annotations: annotations,
Owner: owner.Name,
})
}
}

View file

@ -2,6 +2,7 @@ package main
import (
"fmt"
"io"
"os"
"strings"
@ -129,7 +130,20 @@ func main() {
}
return nil
}
// If the command returns an error, cli takes upon itself to print
// the error on cli.ErrWriter and exit.
// Use our own writer here to ensure the log gets sent to the right location.
cli.ErrWriter = &FatalWriter{cli.ErrWriter}
if err := app.Run(os.Args); err != nil {
fatal(err)
}
}
type FatalWriter struct {
cliErrWriter io.Writer
}
func (f *FatalWriter) Write(p []byte) (n int, err error) {
logrus.Error(string(p))
return f.cliErrWriter.Write(p)
}

108
vendor/github.com/opencontainers/runc/notify_socket.go generated vendored Normal file
View file

@ -0,0 +1,108 @@
// +build linux
package main
import (
"bytes"
"fmt"
"net"
"path/filepath"
"github.com/Sirupsen/logrus"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/urfave/cli"
)
type notifySocket struct {
socket *net.UnixConn
host string
socketPath string
}
func newNotifySocket(context *cli.Context, notifySocketHost string, id string) *notifySocket {
if notifySocketHost == "" {
return nil
}
root := filepath.Join(context.GlobalString("root"), id)
path := filepath.Join(root, "notify.sock")
notifySocket := &notifySocket{
socket: nil,
host: notifySocketHost,
socketPath: path,
}
return notifySocket
}
func (ns *notifySocket) Close() error {
return ns.socket.Close()
}
// If systemd is supporting sd_notify protocol, this function will add support
// for sd_notify protocol from within the container.
func (s *notifySocket) setupSpec(context *cli.Context, spec *specs.Spec) {
mount := specs.Mount{Destination: s.host, Type: "bind", Source: s.socketPath, Options: []string{"bind"}}
spec.Mounts = append(spec.Mounts, mount)
spec.Process.Env = append(spec.Process.Env, fmt.Sprintf("NOTIFY_SOCKET=%s", s.host))
}
func (s *notifySocket) setupSocket() error {
addr := net.UnixAddr{
Name: s.socketPath,
Net: "unixgram",
}
socket, err := net.ListenUnixgram("unixgram", &addr)
if err != nil {
return err
}
s.socket = socket
return nil
}
// pid1 must be set only with -d, as it is used to set the new process as the main process
// for the service in systemd
func (notifySocket *notifySocket) run(pid1 int) {
buf := make([]byte, 512)
notifySocketHostAddr := net.UnixAddr{Name: notifySocket.host, Net: "unixgram"}
client, err := net.DialUnix("unixgram", nil, &notifySocketHostAddr)
if err != nil {
logrus.Error(err)
return
}
for {
r, err := notifySocket.socket.Read(buf)
if err != nil {
break
}
var out bytes.Buffer
for _, line := range bytes.Split(buf[0:r], []byte{'\n'}) {
if bytes.HasPrefix(line, []byte("READY=")) {
_, err = out.Write(line)
if err != nil {
return
}
_, err = out.Write([]byte{'\n'})
if err != nil {
return
}
_, err = client.Write(out.Bytes())
if err != nil {
return
}
// now we can inform systemd to use pid1 as the pid to monitor
if pid1 > 0 {
newPid := fmt.Sprintf("MAINPID=%d\n", pid1)
client.Write([]byte(newPid))
}
return
}
}
}
}

View file

@ -15,6 +15,9 @@ paused. `,
Use runc list to identiy instances of containers and their current status.`,
Action: func(context *cli.Context) error {
if err := checkArgs(context, 1, exactArgs); err != nil {
return err
}
container, err := getContainer(context)
if err != nil {
return err
@ -22,6 +25,7 @@ Use runc list to identiy instances of containers and their current status.`,
if err := container.Pause(); err != nil {
return err
}
return nil
},
}
@ -37,6 +41,9 @@ resumed.`,
Use runc list to identiy instances of containers and their current status.`,
Action: func(context *cli.Context) error {
if err := checkArgs(context, 1, exactArgs); err != nil {
return err
}
container, err := getContainer(context)
if err != nil {
return err
@ -44,6 +51,7 @@ Use runc list to identiy instances of containers and their current status.`,
if err := container.Resume(); err != nil {
return err
}
return nil
},
}

View file

@ -20,11 +20,19 @@ var psCommand = cli.Command{
Flags: []cli.Flag{
cli.StringFlag{
Name: "format, f",
Value: "",
Value: "table",
Usage: `select one of: ` + formatOptions,
},
},
Action: func(context *cli.Context) error {
if err := checkArgs(context, 1, minArgs); err != nil {
return err
}
// XXX: Currently not supported with rootless containers.
if isRootless() {
return fmt.Errorf("runc ps requires root")
}
container, err := getContainer(context)
if err != nil {
return err
@ -35,21 +43,27 @@ var psCommand = cli.Command{
return err
}
if context.String("format") == "json" {
if err := json.NewEncoder(os.Stdout).Encode(pids); err != nil {
return err
}
return nil
switch context.String("format") {
case "table":
case "json":
return json.NewEncoder(os.Stdout).Encode(pids)
default:
return fmt.Errorf("invalid format option")
}
psArgs := context.Args().Get(1)
if psArgs == "" {
psArgs = "-ef"
// [1:] is to remove command name, ex:
// context.Args(): [containet_id ps_arg1 ps_arg2 ...]
// psArgs: [ps_arg1 ps_arg2 ...]
//
psArgs := context.Args()[1:]
if len(psArgs) == 0 {
psArgs = []string{"-ef"}
}
output, err := exec.Command("ps", strings.Split(psArgs, " ")...).Output()
cmd := exec.Command("ps", psArgs...)
output, err := cmd.CombinedOutput()
if err != nil {
return err
return fmt.Errorf("%s: %s", err, output)
}
lines := strings.Split(string(output), "\n")
@ -78,6 +92,7 @@ var psCommand = cli.Command{
}
return nil
},
SkipArgReorder: true,
}
func getPidIndex(title string) (int, error) {

View file

@ -3,6 +3,7 @@
package main
import (
"fmt"
"os"
"syscall"
@ -83,6 +84,14 @@ using the runc checkpoint command.`,
},
},
Action: func(context *cli.Context) error {
if err := checkArgs(context, 1, exactArgs); err != nil {
return err
}
// XXX: Currently this is untested with rootless containers.
if isRootless() {
return fmt.Errorf("runc restore requires root")
}
imagePath := context.String("image-path")
id := context.Args().First()
if id == "" {
@ -147,7 +156,7 @@ func restoreContainer(context *cli.Context, spec *specs.Spec, config *configs.Co
setManageCgroupsMode(context, options)
if err := setEmptyNsMask(context, options); err != nil {
if err = setEmptyNsMask(context, options); err != nil {
return -1, err
}
@ -158,29 +167,34 @@ func restoreContainer(context *cli.Context, spec *specs.Spec, config *configs.Co
defer destroy(container)
}
process := &libcontainer.Process{}
tty, err := setupIO(process, rootuid, rootgid, "", false, detach)
tty, err := setupIO(process, rootuid, rootgid, false, detach, "")
if err != nil {
return -1, err
}
defer tty.Close()
handler := newSignalHandler(tty, !context.Bool("no-subreaper"))
notifySocket := newNotifySocket(context, os.Getenv("NOTIFY_SOCKET"), id)
if notifySocket != nil {
notifySocket.setupSpec(context, spec)
notifySocket.setupSocket()
}
handler := newSignalHandler(!context.Bool("no-subreaper"), notifySocket)
if err := container.Restore(process, options); err != nil {
return -1, err
}
// We don't need to do a tty.recvtty because config.Terminal is always false.
defer tty.Close()
if err := tty.ClosePostStart(); err != nil {
return -1, err
}
if pidFile := context.String("pid-file"); pidFile != "" {
if err := createPidFile(pidFile, process); err != nil {
process.Signal(syscall.SIGKILL)
process.Wait()
_ = process.Signal(syscall.SIGKILL)
_, _ = process.Wait()
return -1, err
}
}
if detach {
return 0, nil
}
return handler.forward(process)
return handler.forward(process, tty, detach)
}
func criuOptions(context *cli.Context) *libcontainer.CriuOpts {
@ -191,10 +205,12 @@ func criuOptions(context *cli.Context) *libcontainer.CriuOpts {
return &libcontainer.CriuOpts{
ImagesDirectory: imagePath,
WorkDirectory: context.String("work-path"),
ParentImage: context.String("parent-path"),
LeaveRunning: context.Bool("leave-running"),
TcpEstablished: context.Bool("tcp-established"),
ExternalUnixConnections: context.Bool("ext-unix-sk"),
ShellJob: context.Bool("shell-job"),
FileLocks: context.Bool("file-locks"),
PreDump: context.Bool("pre-dump"),
}
}

View file

@ -43,7 +43,7 @@ var rlimitMap = map[string]int{
func strToRlimit(key string) (int, error) {
rl, ok := rlimitMap[key]
if !ok {
return 0, fmt.Errorf("Wrong rlimit value: %s", key)
return 0, fmt.Errorf("wrong rlimit value: %s", key)
}
return rl, nil
}

View file

@ -32,9 +32,9 @@ command(s) that get executed on start, edit the args parameter of the spec. See
Usage: `path to the root of the bundle directory, defaults to the current directory`,
},
cli.StringFlag{
Name: "console",
Name: "console-socket",
Value: "",
Usage: "specify the pty slave path for use with the container",
Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal",
},
cli.BoolFlag{
Name: "detach, d",
@ -57,8 +57,18 @@ command(s) that get executed on start, edit the args parameter of the spec. See
Name: "no-new-keyring",
Usage: "do not create a new session keyring for the container. This will cause the container to inherit the calling processes session key",
},
cli.IntFlag{
Name: "preserve-fds",
Usage: "Pass N additional file descriptors to the container (stdio + $LISTEN_FDS + N in total)",
},
},
Action: func(context *cli.Context) error {
if err := checkArgs(context, 1, exactArgs); err != nil {
return err
}
if err := revisePidFile(context); err != nil {
return err
}
spec, err := setupSpec(context)
if err != nil {
return err

View file

@ -17,7 +17,9 @@ const signalBufferSize = 2048
// newSignalHandler returns a signal handler for processing SIGCHLD and SIGWINCH signals
// while still forwarding all other signals to the process.
func newSignalHandler(tty *tty, enableSubreaper bool) *signalHandler {
// If notifySocket is present, use it to read systemd notifications from the container and
// forward them to notifySocketHost.
func newSignalHandler(enableSubreaper bool, notifySocket *notifySocket) *signalHandler {
if enableSubreaper {
// set us as the subreaper before registering the signal handler for the container
if err := system.SetSubreaper(1); err != nil {
@ -30,8 +32,8 @@ func newSignalHandler(tty *tty, enableSubreaper bool) *signalHandler {
// handle all signals for the process.
signal.Notify(s)
return &signalHandler{
tty: tty,
signals: s,
signals: s,
notifySocket: notifySocket,
}
}
@ -43,25 +45,39 @@ type exit struct {
}
type signalHandler struct {
signals chan os.Signal
tty *tty
signals chan os.Signal
notifySocket *notifySocket
}
// forward handles the main signal event loop forwarding, resizing, or reaping depending
// on the signal received.
func (h *signalHandler) forward(process *libcontainer.Process) (int, error) {
func (h *signalHandler) forward(process *libcontainer.Process, tty *tty, detach bool) (int, error) {
// make sure we know the pid of our main process so that we can return
// after it dies.
if detach && h.notifySocket == nil {
return 0, nil
}
pid1, err := process.Pid()
if err != nil {
return -1, err
}
if h.notifySocket != nil {
if detach {
h.notifySocket.run(pid1)
return 0, nil
} else {
go h.notifySocket.run(0)
}
}
// perform the initial tty resize.
h.tty.resize()
tty.resize()
for s := range h.signals {
switch s {
case syscall.SIGWINCH:
h.tty.resize()
tty.resize()
case syscall.SIGCHLD:
exits, err := h.reap()
if err != nil {
@ -77,6 +93,9 @@ func (h *signalHandler) forward(process *libcontainer.Process) (int, error) {
// status because we must ensure that any of the go specific process
// fun such as flushing pipes are complete before we return.
process.Wait()
if h.notifySocket != nil {
h.notifySocket.Close()
}
return e.status, nil
}
}

View file

@ -10,6 +10,7 @@ import (
"runtime"
"github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/runc/libcontainer/specconv"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/urfave/cli"
)
@ -48,9 +49,9 @@ In the run command above, "container1" is the name for the instance of the
container that you are starting. The name you provide for the container instance
must be unique on your host.
An alternative for generating a customized spec config is to use "ocitools", the
sub-command "ocitools generate" has lots of options that can be used to do any
customizations as you want, see [ocitools](https://github.com/opencontainers/ocitools)
An alternative for generating a customized spec config is to use "oci-runtime-tool", the
sub-command "oci-runtime-tool generate" has lots of options that can be used to do any
customizations as you want, see [runtime-tools](https://github.com/opencontainers/runtime-tools)
to get more information.
When starting a container through runc, runc needs root privilege. If not
@ -63,129 +64,20 @@ container on your host.`,
Value: "",
Usage: "path to the root of the bundle directory",
},
cli.BoolFlag{
Name: "rootless",
Usage: "generate a configuration for a rootless container",
},
},
Action: func(context *cli.Context) error {
spec := specs.Spec{
Version: specs.Version,
Platform: specs.Platform{
OS: runtime.GOOS,
Arch: runtime.GOARCH,
},
Root: specs.Root{
Path: "rootfs",
Readonly: true,
},
Process: specs.Process{
Terminal: true,
User: specs.User{},
Args: []string{
"sh",
},
Env: []string{
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"TERM=xterm",
},
Cwd: "/",
NoNewPrivileges: true,
Capabilities: []string{
"CAP_AUDIT_WRITE",
"CAP_KILL",
"CAP_NET_BIND_SERVICE",
},
Rlimits: []specs.Rlimit{
{
Type: "RLIMIT_NOFILE",
Hard: uint64(1024),
Soft: uint64(1024),
},
},
},
Hostname: "runc",
Mounts: []specs.Mount{
{
Destination: "/proc",
Type: "proc",
Source: "proc",
Options: nil,
},
{
Destination: "/dev",
Type: "tmpfs",
Source: "tmpfs",
Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
},
{
Destination: "/dev/pts",
Type: "devpts",
Source: "devpts",
Options: []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5"},
},
{
Destination: "/dev/shm",
Type: "tmpfs",
Source: "shm",
Options: []string{"nosuid", "noexec", "nodev", "mode=1777", "size=65536k"},
},
{
Destination: "/dev/mqueue",
Type: "mqueue",
Source: "mqueue",
Options: []string{"nosuid", "noexec", "nodev"},
},
{
Destination: "/sys",
Type: "sysfs",
Source: "sysfs",
Options: []string{"nosuid", "noexec", "nodev", "ro"},
},
{
Destination: "/sys/fs/cgroup",
Type: "cgroup",
Source: "cgroup",
Options: []string{"nosuid", "noexec", "nodev", "relatime", "ro"},
},
},
Linux: specs.Linux{
MaskedPaths: []string{
"/proc/kcore",
"/proc/latency_stats",
"/proc/timer_stats",
"/proc/sched_debug",
},
ReadonlyPaths: []string{
"/proc/asound",
"/proc/bus",
"/proc/fs",
"/proc/irq",
"/proc/sys",
"/proc/sysrq-trigger",
},
Resources: &specs.Resources{
Devices: []specs.DeviceCgroup{
{
Allow: false,
Access: sPtr("rwm"),
},
},
},
Namespaces: []specs.Namespace{
{
Type: "pid",
},
{
Type: "network",
},
{
Type: "ipc",
},
{
Type: "uts",
},
{
Type: "mount",
},
},
},
if err := checkArgs(context, 0, exactArgs); err != nil {
return err
}
spec := specconv.Example()
rootless := context.Bool("rootless")
if rootless {
specconv.ToRootless(spec)
}
checkNoFile := func(name string) error {
@ -207,7 +99,7 @@ container on your host.`,
if err := checkNoFile(specConfig); err != nil {
return err
}
data, err := json.MarshalIndent(&spec, "", "\t")
data, err := json.MarshalIndent(spec, "", "\t")
if err != nil {
return err
}
@ -218,11 +110,7 @@ container on your host.`,
},
}
func sPtr(s string) *string { return &s }
func rPtr(r rune) *rune { return &r }
func iPtr(i int64) *int64 { return &i }
func u32Ptr(i int64) *uint32 { u := uint32(i); return &u }
func fmPtr(i int64) *os.FileMode { fm := os.FileMode(i); return &fm }
func sPtr(s string) *string { return &s }
// loadSpec loads the specification from the provided path.
func loadSpec(cPath string) (spec *specs.Spec, err error) {
@ -238,17 +126,30 @@ func loadSpec(cPath string) (spec *specs.Spec, err error) {
if err = json.NewDecoder(cf).Decode(&spec); err != nil {
return nil, err
}
if err = validatePlatform(&spec.Platform); err != nil {
return nil, err
}
return spec, validateProcessSpec(&spec.Process)
}
func createLibContainerRlimit(rlimit specs.Rlimit) (configs.Rlimit, error) {
func createLibContainerRlimit(rlimit specs.LinuxRlimit) (configs.Rlimit, error) {
rl, err := strToRlimit(rlimit.Type)
if err != nil {
return configs.Rlimit{}, err
}
return configs.Rlimit{
Type: rl,
Hard: uint64(rlimit.Hard),
Soft: uint64(rlimit.Soft),
Hard: rlimit.Hard,
Soft: rlimit.Soft,
}, nil
}
func validatePlatform(platform *specs.Platform) error {
if platform.OS != runtime.GOOS {
return fmt.Errorf("target os %s mismatch with current os %s", platform.OS, runtime.GOOS)
}
if platform.Arch != runtime.GOARCH {
return fmt.Errorf("target arch %s mismatch with current arch %s", platform.Arch, runtime.GOARCH)
}
return nil
}

View file

@ -1,6 +1,7 @@
package main
import (
"errors"
"fmt"
"github.com/opencontainers/runc/libcontainer"
@ -9,14 +10,17 @@ import (
var startCommand = cli.Command{
Name: "start",
Usage: "start signals a created container to execute the user defined process",
Usage: "executes the user defined process in a created container",
ArgsUsage: `<container-id>
Where "<container-id>" is your name for the instance of the container that you
are starting. The name you provide for the container instance must be unique on
your host.`,
Description: `The start command signals the container to start the user's defined process.`,
Description: `The start command executes the user defined process in a created container.`,
Action: func(context *cli.Context) error {
if err := checkArgs(context, 1, exactArgs); err != nil {
return err
}
container, err := getContainer(context)
if err != nil {
return err
@ -29,11 +33,11 @@ your host.`,
case libcontainer.Created:
return container.Exec()
case libcontainer.Stopped:
return fmt.Errorf("cannot start a container that has run and stopped")
return errors.New("cannot start a container that has stopped")
case libcontainer.Running:
return fmt.Errorf("cannot start an already running container")
return errors.New("cannot start an already running container")
default:
return fmt.Errorf("cannot start a container in the %s state", status)
return fmt.Errorf("cannot start a container in the %s state\n", status)
}
},
}

View file

@ -5,35 +5,12 @@ package main
import (
"encoding/json"
"os"
"time"
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/utils"
"github.com/urfave/cli"
)
// cState represents the platform agnostic pieces relating to a running
// container's status and state. Note: The fields in this structure adhere to
// the opencontainers/runtime-spec/specs-go requirement for json fields that must be returned
// in a state command.
type cState struct {
// Version is the OCI version for the container
Version string `json:"ociVersion"`
// ID is the container ID
ID string `json:"id"`
// InitProcessPid is the init process id in the parent namespace
InitProcessPid int `json:"pid"`
// Bundle is the path on the filesystem to the bundle
Bundle string `json:"bundlePath"`
// Rootfs is a path to a directory containing the container's root filesystem.
Rootfs string `json:"rootfsPath"`
// Status is the current status of the container, running, paused, ...
Status string `json:"status"`
// Created is the unix timestamp for the creation time of the container in UTC
Created time.Time `json:"created"`
// Annotations is the user defined annotations added to the config.
Annotations map[string]string `json:"annotations,omitempty"`
}
var stateCommand = cli.Command{
Name: "state",
Usage: "output the state of a container",
@ -43,6 +20,9 @@ Where "<container-id>" is your name for the instance of the container.`,
Description: `The state command outputs current state information for the
instance of a container.`,
Action: func(context *cli.Context) error {
if err := checkArgs(context, 1, exactArgs); err != nil {
return err
}
container, err := getContainer(context)
if err != nil {
return err
@ -55,11 +35,15 @@ instance of a container.`,
if err != nil {
return err
}
pid := state.BaseState.InitProcessPid
if containerStatus == libcontainer.Stopped {
pid = 0
}
bundle, annotations := utils.Annotations(state.Config.Labels)
cs := cState{
cs := containerState{
Version: state.BaseState.Config.Version,
ID: state.BaseState.ID,
InitProcessPid: state.BaseState.InitProcessPid,
InitProcessPid: pid,
Status: containerStatus.String(),
Bundle: bundle,
Rootfs: state.BaseState.Config.Rootfs,

View file

@ -10,11 +10,27 @@ import (
"github.com/docker/docker/pkg/term"
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/utils"
)
// setup standard pipes so that the TTY of the calling runc process
// is not inherited by the container.
func createStdioPipes(p *libcontainer.Process, rootuid, rootgid int) (*tty, error) {
type tty struct {
console libcontainer.Console
state *term.State
closers []io.Closer
postStart []io.Closer
wg sync.WaitGroup
consoleC chan error
}
func (t *tty) copyIO(w io.Writer, r io.ReadCloser) {
defer t.wg.Done()
io.Copy(w, r)
r.Close()
}
// setup pipes for the process so that advanced features like c/r are able to easily checkpoint
// and restore the process's IO without depending on a host specific path or device
func setupProcessPipes(p *libcontainer.Process, rootuid, rootgid int) (*tty, error) {
i, err := p.InitializeIO(rootuid, rootgid)
if err != nil {
return nil, err
@ -46,45 +62,37 @@ func createStdioPipes(p *libcontainer.Process, rootuid, rootgid int) (*tty, erro
return t, nil
}
func (t *tty) copyIO(w io.Writer, r io.ReadCloser) {
defer t.wg.Done()
io.Copy(w, r)
r.Close()
func inheritStdio(process *libcontainer.Process) error {
process.Stdin = os.Stdin
process.Stdout = os.Stdout
process.Stderr = os.Stderr
return nil
}
func createTty(p *libcontainer.Process, rootuid, rootgid int, consolePath string) (*tty, error) {
if consolePath != "" {
if err := p.ConsoleFromPath(consolePath); err != nil {
return nil, err
}
return &tty{}, nil
}
console, err := p.NewConsole(rootuid, rootgid)
func (t *tty) recvtty(process *libcontainer.Process, socket *os.File) error {
f, err := utils.RecvFd(socket)
if err != nil {
return nil, err
return err
}
console := libcontainer.ConsoleFromFile(f)
go io.Copy(console, os.Stdin)
go io.Copy(os.Stdout, console)
t.wg.Add(1)
go t.copyIO(os.Stdout, console)
state, err := term.SetRawTerminal(os.Stdin.Fd())
if err != nil {
return nil, fmt.Errorf("failed to set the terminal from the stdin: %v", err)
return fmt.Errorf("failed to set the terminal from the stdin: %v", err)
}
return &tty{
console: console,
state: state,
closers: []io.Closer{
console,
},
}, nil
t.state = state
t.console = console
t.closers = []io.Closer{console}
return nil
}
type tty struct {
console libcontainer.Console
state *term.State
closers []io.Closer
postStart []io.Closer
wg sync.WaitGroup
func (t *tty) waitConsole() error {
if t.consoleC != nil {
return <-t.consoleC
}
return nil
}
// ClosePostStart closes any fds that are provided to the container and dup2'd
@ -122,5 +130,5 @@ func (t *tty) resize() error {
if err != nil {
return err
}
return term.SetWinsize(t.console.Fd(), ws)
return term.SetWinsize(t.console.File().Fd(), ws)
}

View file

@ -13,6 +13,7 @@ import (
"github.com/urfave/cli"
)
func i64Ptr(i int64) *int64 { return &i }
func u64Ptr(i uint64) *uint64 { return &i }
func u16Ptr(i uint16) *uint16 { return &i }
@ -40,12 +41,14 @@ The accepted format is as follow (unchanged values can be omitted):
"shares": 0,
"quota": 0,
"period": 0,
"realtimeRuntime": 0,
"realtimePeriod": 0,
"cpus": "",
"mems": ""
},
"blockIO": {
"blkioWeight": 0
},
}
}
Note: if data is to be read from a file or the standard input, all
@ -59,16 +62,24 @@ other options are ignored.
},
cli.StringFlag{
Name: "cpu-period",
Usage: "CPU period to be used for hardcapping (in usecs). 0 to use system default",
Usage: "CPU CFS period to be used for hardcapping (in usecs). 0 to use system default",
},
cli.StringFlag{
Name: "cpu-quota",
Usage: "CPU hardcap limit (in usecs). Allowed cpu time in a given period",
Usage: "CPU CFS hardcap limit (in usecs). Allowed cpu time in a given period",
},
cli.StringFlag{
Name: "cpu-share",
Usage: "CPU shares (relative weight vs. other containers)",
},
cli.StringFlag{
Name: "cpu-rt-period",
Usage: "CPU realtime period to be used for hardcapping (in usecs). 0 to use system default",
},
cli.StringFlag{
Name: "cpu-rt-runtime",
Usage: "CPU realtime hardcap limit (in usecs). Allowed cpu time in a given period",
},
cli.StringFlag{
Name: "cpuset-cpus",
Usage: "CPU(s) to use",
@ -99,27 +110,32 @@ other options are ignored.
},
},
Action: func(context *cli.Context) error {
if err := checkArgs(context, 1, exactArgs); err != nil {
return err
}
container, err := getContainer(context)
if err != nil {
return err
}
r := specs.Resources{
Memory: &specs.Memory{
r := specs.LinuxResources{
Memory: &specs.LinuxMemory{
Limit: u64Ptr(0),
Reservation: u64Ptr(0),
Swap: u64Ptr(0),
Kernel: u64Ptr(0),
KernelTCP: u64Ptr(0),
},
CPU: &specs.CPU{
Shares: u64Ptr(0),
Quota: u64Ptr(0),
Period: u64Ptr(0),
Cpus: sPtr(""),
Mems: sPtr(""),
CPU: &specs.LinuxCPU{
Shares: u64Ptr(0),
Quota: i64Ptr(0),
Period: u64Ptr(0),
RealtimeRuntime: i64Ptr(0),
RealtimePeriod: u64Ptr(0),
Cpus: "",
Mems: "",
},
BlockIO: &specs.BlockIO{
BlockIO: &specs.LinuxBlockIO{
Weight: u16Ptr(0),
},
}
@ -149,59 +165,86 @@ other options are ignored.
r.BlockIO.Weight = u16Ptr(uint16(val))
}
if val := context.String("cpuset-cpus"); val != "" {
r.CPU.Cpus = &val
r.CPU.Cpus = val
}
if val := context.String("cpuset-mems"); val != "" {
r.CPU.Mems = &val
r.CPU.Mems = val
}
for opt, dest := range map[string]*uint64{
"cpu-period": r.CPU.Period,
"cpu-quota": r.CPU.Quota,
"cpu-share": r.CPU.Shares,
for _, pair := range []struct {
opt string
dest *uint64
}{
{"cpu-period", r.CPU.Period},
{"cpu-rt-period", r.CPU.RealtimePeriod},
{"cpu-share", r.CPU.Shares},
} {
if val := context.String(opt); val != "" {
if val := context.String(pair.opt); val != "" {
var err error
*dest, err = strconv.ParseUint(val, 10, 64)
*pair.dest, err = strconv.ParseUint(val, 10, 64)
if err != nil {
return fmt.Errorf("invalid value for %s: %s", opt, err)
return fmt.Errorf("invalid value for %s: %s", pair.opt, err)
}
}
}
for _, pair := range []struct {
opt string
dest *int64
}{
for opt, dest := range map[string]*uint64{
"kernel-memory": r.Memory.Kernel,
"kernel-memory-tcp": r.Memory.KernelTCP,
"memory": r.Memory.Limit,
"memory-reservation": r.Memory.Reservation,
"memory-swap": r.Memory.Swap,
{"cpu-quota", r.CPU.Quota},
{"cpu-rt-runtime", r.CPU.RealtimeRuntime},
} {
if val := context.String(opt); val != "" {
v, err := units.RAMInBytes(val)
if val := context.String(pair.opt); val != "" {
var err error
*pair.dest, err = strconv.ParseInt(val, 10, 64)
if err != nil {
return fmt.Errorf("invalid value for %s: %s", opt, err)
return fmt.Errorf("invalid value for %s: %s", pair.opt, err)
}
*dest = uint64(v)
}
}
for _, pair := range []struct {
opt string
dest *uint64
}{
{"memory", r.Memory.Limit},
{"memory-swap", r.Memory.Swap},
{"kernel-memory", r.Memory.Kernel},
{"kernel-memory-tcp", r.Memory.KernelTCP},
{"memory-reservation", r.Memory.Reservation},
} {
if val := context.String(pair.opt); val != "" {
var v int64
if val != "-1" {
v, err = units.RAMInBytes(val)
if err != nil {
return fmt.Errorf("invalid value for %s: %s", pair.opt, err)
}
} else {
v = -1
}
*pair.dest = uint64(v)
}
}
}
// Update the value
config.Cgroups.Resources.BlkioWeight = *r.BlockIO.Weight
config.Cgroups.Resources.CpuPeriod = int64(*r.CPU.Period)
config.Cgroups.Resources.CpuQuota = int64(*r.CPU.Quota)
config.Cgroups.Resources.CpuShares = int64(*r.CPU.Shares)
config.Cgroups.Resources.CpusetCpus = *r.CPU.Cpus
config.Cgroups.Resources.CpusetMems = *r.CPU.Mems
config.Cgroups.Resources.KernelMemory = int64(*r.Memory.Kernel)
config.Cgroups.Resources.KernelMemoryTCP = int64(*r.Memory.KernelTCP)
config.Cgroups.Resources.Memory = int64(*r.Memory.Limit)
config.Cgroups.Resources.MemoryReservation = int64(*r.Memory.Reservation)
config.Cgroups.Resources.MemorySwap = int64(*r.Memory.Swap)
config.Cgroups.Resources.CpuPeriod = *r.CPU.Period
config.Cgroups.Resources.CpuQuota = *r.CPU.Quota
config.Cgroups.Resources.CpuShares = *r.CPU.Shares
config.Cgroups.Resources.CpuRtPeriod = *r.CPU.RealtimePeriod
config.Cgroups.Resources.CpuRtRuntime = *r.CPU.RealtimeRuntime
config.Cgroups.Resources.CpusetCpus = r.CPU.Cpus
config.Cgroups.Resources.CpusetMems = r.CPU.Mems
config.Cgroups.Resources.KernelMemory = *r.Memory.Kernel
config.Cgroups.Resources.KernelMemoryTCP = *r.Memory.KernelTCP
config.Cgroups.Resources.Memory = *r.Memory.Limit
config.Cgroups.Resources.MemoryReservation = *r.Memory.Reservation
config.Cgroups.Resources.MemorySwap = *r.Memory.Swap
if err := container.Set(config); err != nil {
return err
}
return nil
return container.Set(config)
},
}

View file

@ -3,12 +3,45 @@ package main
import (
"fmt"
"os"
"path/filepath"
"github.com/Sirupsen/logrus"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/urfave/cli"
)
const (
exactArgs = iota
minArgs
maxArgs
)
func checkArgs(context *cli.Context, expected, checkType int) error {
var err error
cmdName := context.Command.Name
switch checkType {
case exactArgs:
if context.NArg() != expected {
err = fmt.Errorf("%s: %q requires exactly %d argument(s)", os.Args[0], cmdName, expected)
}
case minArgs:
if context.NArg() < expected {
err = fmt.Errorf("%s: %q requires a minimum of %d argument(s)", os.Args[0], cmdName, expected)
}
case maxArgs:
if context.NArg() > expected {
err = fmt.Errorf("%s: %q requires a maximum of %d argument(s)", os.Args[0], cmdName, expected)
}
}
if err != nil {
fmt.Printf("Incorrect Usage.\n\n")
cli.ShowCommandHelp(context, cmdName)
return err
}
return nil
}
// fatal prints the error's details if it is a libcontainer specific error type
// then exits the program with an exit status of 1.
func fatal(err error) {
@ -18,7 +51,7 @@ func fatal(err error) {
os.Exit(1)
}
// setupSpec performs inital setup based on the cli.Context for the container
// setupSpec performs initial setup based on the cli.Context for the container
func setupSpec(context *cli.Context) (*specs.Spec, error) {
bundle := context.String("bundle")
if bundle != "" {
@ -30,12 +63,20 @@ func setupSpec(context *cli.Context) (*specs.Spec, error) {
if err != nil {
return nil, err
}
notifySocket := os.Getenv("NOTIFY_SOCKET")
if notifySocket != "" {
setupSdNotify(spec, notifySocket)
}
if os.Geteuid() != 0 {
return nil, fmt.Errorf("runc should be run as root")
}
return spec, nil
}
func revisePidFile(context *cli.Context) error {
pidFile := context.String("pid-file")
if pidFile == "" {
return nil
}
// convert pid-file to an absolute path so we can write to the right
// file after chdir to bundle
pidFile, err := filepath.Abs(pidFile)
if err != nil {
return err
}
return context.Set("pid-file", pidFile)
}

View file

@ -5,6 +5,7 @@ package main
import (
"errors"
"fmt"
"net"
"os"
"path/filepath"
"strconv"
@ -14,7 +15,9 @@ import (
"github.com/coreos/go-systemd/activation"
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/cgroups/systemd"
"github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/runc/libcontainer/specconv"
"github.com/opencontainers/runc/libcontainer/utils"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/urfave/cli"
)
@ -76,11 +79,18 @@ func newProcess(p specs.Process) (*libcontainer.Process, error) {
// TODO: fix libcontainer's API to better support uid/gid in a typesafe way.
User: fmt.Sprintf("%d:%d", p.User.UID, p.User.GID),
Cwd: p.Cwd,
Capabilities: p.Capabilities,
Label: p.SelinuxLabel,
NoNewPrivileges: &p.NoNewPrivileges,
AppArmorProfile: p.ApparmorProfile,
}
if p.Capabilities != nil {
lp.Capabilities = &configs.Capabilities{}
lp.Capabilities.Bounding = p.Capabilities.Bounding
lp.Capabilities.Effective = p.Capabilities.Effective
lp.Capabilities.Inheritable = p.Capabilities.Inheritable
lp.Capabilities.Permitted = p.Capabilities.Permitted
lp.Capabilities.Ambient = p.Capabilities.Ambient
}
for _, gid := range p.User.AdditionalGids {
lp.AdditionalGroups = append(lp.AdditionalGroups, strconv.FormatUint(uint64(gid), 10))
}
@ -94,53 +104,62 @@ func newProcess(p specs.Process) (*libcontainer.Process, error) {
return lp, nil
}
func dupStdio(process *libcontainer.Process, rootuid, rootgid int) error {
process.Stdin = os.Stdin
process.Stdout = os.Stdout
process.Stderr = os.Stderr
for _, fd := range []uintptr{
os.Stdin.Fd(),
os.Stdout.Fd(),
os.Stderr.Fd(),
} {
if err := syscall.Fchown(int(fd), rootuid, rootgid); err != nil {
return err
}
}
return nil
}
// If systemd is supporting sd_notify protocol, this function will add support
// for sd_notify protocol from within the container.
func setupSdNotify(spec *specs.Spec, notifySocket string) {
spec.Mounts = append(spec.Mounts, specs.Mount{Destination: notifySocket, Type: "bind", Source: notifySocket, Options: []string{"bind"}})
spec.Process.Env = append(spec.Process.Env, fmt.Sprintf("NOTIFY_SOCKET=%s", notifySocket))
}
func destroy(container libcontainer.Container) {
if err := container.Destroy(); err != nil {
logrus.Error(err)
}
}
// setupIO sets the proper IO on the process depending on the configuration
// If there is a nil error then there must be a non nil tty returned
func setupIO(process *libcontainer.Process, rootuid, rootgid int, console string, createTTY, detach bool) (*tty, error) {
// detach and createTty will not work unless a console path is passed
// so error out here before changing any terminal settings
if createTTY && detach && console == "" {
return nil, fmt.Errorf("cannot allocate tty if runc will detach")
}
// setupIO modifies the given process config according to the options.
func setupIO(process *libcontainer.Process, rootuid, rootgid int, createTTY, detach bool, sockpath string) (*tty, error) {
if createTTY {
return createTty(process, rootuid, rootgid, console)
process.Stdin = nil
process.Stdout = nil
process.Stderr = nil
t := &tty{}
if !detach {
parent, child, err := utils.NewSockPair("console")
if err != nil {
return nil, err
}
process.ConsoleSocket = child
t.postStart = append(t.postStart, parent, child)
t.consoleC = make(chan error, 1)
go func() {
if err := t.recvtty(process, parent); err != nil {
t.consoleC <- err
}
t.consoleC <- nil
}()
} else {
// the caller of runc will handle receiving the console master
conn, err := net.Dial("unix", sockpath)
if err != nil {
return nil, err
}
uc, ok := conn.(*net.UnixConn)
if !ok {
return nil, fmt.Errorf("casting to UnixConn failed")
}
t.postStart = append(t.postStart, uc)
socket, err := uc.File()
if err != nil {
return nil, err
}
t.postStart = append(t.postStart, socket)
process.ConsoleSocket = socket
}
return t, nil
}
// when runc will detach the caller provides the stdio to runc via runc's 0,1,2
// and the container's process inherits runc's stdio.
if detach {
if err := dupStdio(process, rootuid, rootgid); err != nil {
if err := inheritStdio(process); err != nil {
return nil, err
}
return &tty{}, nil
}
return createStdioPipes(process, rootuid, rootgid)
return setupProcessPipes(process, rootuid, rootgid)
}
// createPidFile creates a file with the processes pid inside it atomically
@ -167,6 +186,11 @@ func createPidFile(path string, process *libcontainer.Process) error {
return os.Rename(tmpName, path)
}
// XXX: Currently we autodetect rootless mode.
func isRootless() bool {
return os.Geteuid() != 0
}
func createContainer(context *cli.Context, id string, spec *specs.Spec) (libcontainer.Container, error) {
config, err := specconv.CreateLibcontainerConfig(&specconv.CreateOpts{
CgroupName: id,
@ -174,18 +198,12 @@ func createContainer(context *cli.Context, id string, spec *specs.Spec) (libcont
NoPivotRoot: context.Bool("no-pivot"),
NoNewKeyring: context.Bool("no-new-keyring"),
Spec: spec,
Rootless: isRootless(),
})
if err != nil {
return nil, err
}
if _, err := os.Stat(config.Rootfs); err != nil {
if os.IsNotExist(err) {
return nil, fmt.Errorf("rootfs (%q) does not exist", config.Rootfs)
}
return nil, err
}
factory, err := loadFactory(context)
if err != nil {
return nil, err
@ -198,13 +216,19 @@ type runner struct {
shouldDestroy bool
detach bool
listenFDs []*os.File
preserveFDs int
pidFile string
console string
consoleSocket string
container libcontainer.Container
create bool
notifySocket *notifySocket
}
func (r *runner) run(config *specs.Process) (int, error) {
if err := r.checkTerminal(config); err != nil {
r.destroy()
return -1, err
}
process, err := newProcess(*config)
if err != nil {
r.destroy()
@ -214,55 +238,66 @@ func (r *runner) run(config *specs.Process) (int, error) {
process.Env = append(process.Env, fmt.Sprintf("LISTEN_FDS=%d", len(r.listenFDs)), "LISTEN_PID=1")
process.ExtraFiles = append(process.ExtraFiles, r.listenFDs...)
}
rootuid, err := r.container.Config().HostUID()
baseFd := 3 + len(process.ExtraFiles)
for i := baseFd; i < baseFd+r.preserveFDs; i++ {
process.ExtraFiles = append(process.ExtraFiles, os.NewFile(uintptr(i), "PreserveFD:"+strconv.Itoa(i)))
}
rootuid, err := r.container.Config().HostRootUID()
if err != nil {
r.destroy()
return -1, err
}
rootgid, err := r.container.Config().HostGID()
rootgid, err := r.container.Config().HostRootGID()
if err != nil {
r.destroy()
return -1, err
}
tty, err := setupIO(process, rootuid, rootgid, r.console, config.Terminal, r.detach || r.create)
if err != nil {
r.destroy()
return -1, err
}
handler := newSignalHandler(tty, r.enableSubreaper)
startFn := r.container.Start
var (
detach = r.detach || r.create
startFn = r.container.Start
)
if !r.create {
startFn = r.container.Run
}
if err := startFn(process); err != nil {
// Setting up IO is a two stage process. We need to modify process to deal
// with detaching containers, and then we get a tty after the container has
// started.
handler := newSignalHandler(r.enableSubreaper, r.notifySocket)
tty, err := setupIO(process, rootuid, rootgid, config.Terminal, detach, r.consoleSocket)
if err != nil {
r.destroy()
tty.Close()
return -1, err
}
if err := tty.ClosePostStart(); err != nil {
defer tty.Close()
if err = startFn(process); err != nil {
r.destroy()
return -1, err
}
if err := tty.waitConsole(); err != nil {
r.terminate(process)
r.destroy()
return -1, err
}
if err = tty.ClosePostStart(); err != nil {
r.terminate(process)
r.destroy()
tty.Close()
return -1, err
}
if r.pidFile != "" {
if err := createPidFile(r.pidFile, process); err != nil {
if err = createPidFile(r.pidFile, process); err != nil {
r.terminate(process)
r.destroy()
tty.Close()
return -1, err
}
}
if r.detach || r.create {
tty.Close()
return 0, nil
}
status, err := handler.forward(process)
status, err := handler.forward(process, tty, detach)
if err != nil {
r.terminate(process)
}
if detach {
return 0, nil
}
r.destroy()
tty.Close()
return status, err
}
@ -273,8 +308,20 @@ func (r *runner) destroy() {
}
func (r *runner) terminate(p *libcontainer.Process) {
p.Signal(syscall.SIGKILL)
p.Wait()
_ = p.Signal(syscall.SIGKILL)
_, _ = p.Wait()
}
func (r *runner) checkTerminal(config *specs.Process) error {
detach := r.detach || r.create
// Check command-line for sanity.
if detach && config.Terminal && r.consoleSocket == "" {
return fmt.Errorf("cannot allocate tty if runc will detach without setting console socket")
}
if (!detach || !config.Terminal) && r.consoleSocket != "" {
return fmt.Errorf("cannot use console socket if runc will not detach or allocate tty")
}
return nil
}
func validateProcessSpec(spec *specs.Process) error {
@ -295,11 +342,21 @@ func startContainer(context *cli.Context, spec *specs.Spec, create bool) (int, e
if id == "" {
return -1, errEmptyID
}
notifySocket := newNotifySocket(context, os.Getenv("NOTIFY_SOCKET"), id)
if notifySocket != nil {
notifySocket.setupSpec(context, spec)
}
container, err := createContainer(context, id, spec)
if err != nil {
return -1, err
}
detach := context.Bool("detach")
if notifySocket != nil {
notifySocket.setupSocket()
}
// Support on-demand socket activation by passing file descriptors into the container init process.
listenFDs := []*os.File{}
if os.Getenv("LISTEN_FDS") != "" {
@ -310,9 +367,11 @@ func startContainer(context *cli.Context, spec *specs.Spec, create bool) (int, e
shouldDestroy: true,
container: container,
listenFDs: listenFDs,
console: context.String("console"),
detach: detach,
notifySocket: notifySocket,
consoleSocket: context.String("console-socket"),
detach: context.Bool("detach"),
pidFile: context.String("pid-file"),
preserveFDs: context.Int("preserve-fds"),
create: create,
}
return r.run(&spec.Process)