Files
runc/utils.go
Michael Crosby 044e298507 Improve error handling in runc
The error handling on the runc cli is currenly pretty messy because
messages to the user are split between regular stderr format and logrus
message format.  This changes all the error reporting to the cli to only
output on stderr and exit(1) for consumers of the api.

By default logrus logs to /dev/null so that it is not seen by the user.
If the user wants extra and/or structured loggging/errors from runc they
can use the `--log` flag to provide a path to the file where they want
this information.  This allows a consistent behavior on the cli but
extra power and information when debugging with logs.

This also includes a change to enable the same logging information
inside the container's init by adding an init cli command that can share
the existing flags for all other runc commands.

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
2016-03-09 11:08:30 -08:00

373 lines
8.5 KiB
Go

// +build linux
package main
import (
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strconv"
"syscall"
"github.com/Sirupsen/logrus"
"github.com/codegangsta/cli"
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/specs"
)
const wildcard = -1
var errEmptyID = errors.New("container id cannot be empty")
var allowedDevices = []*configs.Device{
// allow mknod for any device
{
Type: 'c',
Major: wildcard,
Minor: wildcard,
Permissions: "m",
Allow: true,
},
{
Type: 'b',
Major: wildcard,
Minor: wildcard,
Permissions: "m",
Allow: true,
},
{
Type: 'c',
Path: "/dev/null",
Major: 1,
Minor: 3,
Permissions: "rwm",
Allow: true,
},
{
Type: 'c',
Path: "/dev/random",
Major: 1,
Minor: 8,
Permissions: "rwm",
Allow: true,
},
{
Type: 'c',
Path: "/dev/full",
Major: 1,
Minor: 7,
Permissions: "rwm",
Allow: true,
},
{
Type: 'c',
Path: "/dev/tty",
Major: 5,
Minor: 0,
Permissions: "rwm",
Allow: true,
},
{
Type: 'c',
Path: "/dev/zero",
Major: 1,
Minor: 5,
Permissions: "rwm",
Allow: true,
},
{
Type: 'c',
Path: "/dev/urandom",
Major: 1,
Minor: 9,
Permissions: "rwm",
Allow: true,
},
{
Path: "/dev/console",
Type: 'c',
Major: 5,
Minor: 1,
Permissions: "rwm",
Allow: true,
},
// /dev/pts/ - pts namespaces are "coming soon"
{
Path: "",
Type: 'c',
Major: 136,
Minor: wildcard,
Permissions: "rwm",
Allow: true,
},
{
Path: "",
Type: 'c',
Major: 5,
Minor: 2,
Permissions: "rwm",
Allow: true,
},
// tuntap
{
Path: "",
Type: 'c',
Major: 10,
Minor: 200,
Permissions: "rwm",
Allow: true,
},
}
var (
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",
}
)
var container libcontainer.Container
func containerPreload(context *cli.Context) error {
c, err := getContainer(context)
if err != nil {
return err
}
container = c
return nil
}
// loadFactory returns the configured factory instance for execing containers.
func loadFactory(context *cli.Context) (libcontainer.Factory, error) {
var (
debug = "false"
root = context.GlobalString("root")
)
if context.GlobalBool("debug") {
debug = "true"
}
abs, err := filepath.Abs(root)
if err != nil {
return nil, err
}
logAbs, err := filepath.Abs(context.GlobalString("log"))
if err != nil {
return nil, err
}
return libcontainer.New(abs, libcontainer.Cgroupfs, func(l *libcontainer.LinuxFactory) error {
l.CriuPath = context.GlobalString("criu")
return nil
},
libcontainer.InitArgs(os.Args[0],
"--log", logAbs,
"--log-format", context.GlobalString("log-format"),
fmt.Sprintf("--debug=%s", debug),
"init"),
)
}
// getContainer returns the specified container instance by loading it from state
// with the default factory.
func getContainer(context *cli.Context) (libcontainer.Container, error) {
id := context.Args().First()
if id == "" {
return nil, errEmptyID
}
factory, err := loadFactory(context)
if err != nil {
return nil, err
}
return factory.Load(id)
}
// 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) {
// make sure the error is written to the logger
logrus.Error(err)
// return proper unix error codes
if exerr, ok := err.(*exec.Error); ok {
switch exerr.Err {
case os.ErrPermission:
fmt.Fprintln(os.Stderr, err)
os.Exit(126)
case exec.ErrNotFound:
fmt.Fprintln(os.Stderr, err)
os.Exit(127)
default:
if os.IsNotExist(exerr.Err) {
fmt.Fprintf(os.Stderr, "exec: %s: %v\n", strconv.Quote(exerr.Name), os.ErrNotExist)
os.Exit(127)
}
}
}
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
func fatalf(t string, v ...interface{}) {
fatal(fmt.Errorf(t, v...))
}
func getDefaultImagePath(context *cli.Context) string {
cwd, err := os.Getwd()
if err != nil {
panic(err)
}
return filepath.Join(cwd, "checkpoint")
}
// newProcess returns a new libcontainer Process with the arguments from the
// spec and stdio from the current process.
func newProcess(p specs.Process) *libcontainer.Process {
return &libcontainer.Process{
Args: p.Args,
Env: p.Env,
// 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,
}
}
func dupStdio(process *libcontainer.Process, rootuid 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, rootuid); 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.LinuxSpec, 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 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")
}
if createTTY {
return createTty(process, rootuid, console)
}
if detach {
if err := dupStdio(process, rootuid); err != nil {
return nil, err
}
return &tty{}, nil
}
return createStdioPipes(process, rootuid)
}
func createPidFile(path string, process *libcontainer.Process) error {
pid, err := process.Pid()
if err != nil {
return err
}
f, err := os.Create(path)
if err != nil {
return err
}
defer f.Close()
_, err = fmt.Fprintf(f, "%d", pid)
return err
}
func createContainer(context *cli.Context, id string, spec *specs.LinuxSpec) (libcontainer.Container, error) {
config, err := createLibcontainerConfig(id, spec)
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
}
return factory.Create(id, config)
}
// runProcess will create a new process in the specified container
// by executing the process specified in the 'config'.
func runProcess(container libcontainer.Container, config *specs.Process, listenFDs []*os.File, console string, pidFile string, detach bool) (int, error) {
process := newProcess(*config)
// Add extra file descriptors if needed
if len(listenFDs) > 0 {
process.Env = append(process.Env, fmt.Sprintf("LISTEN_FDS=%d", len(listenFDs)), "LISTEN_PID=1")
process.ExtraFiles = append(process.ExtraFiles, listenFDs...)
}
rootuid, err := container.Config().HostUID()
if err != nil {
return -1, err
}
tty, err := setupIO(process, rootuid, console, config.Terminal, detach)
if err != nil {
return -1, err
}
defer tty.Close()
handler := newSignalHandler(tty)
defer handler.Close()
if err := container.Start(process); err != nil {
return -1, err
}
if err := tty.ClosePostStart(); err != nil {
return -1, err
}
if pidFile != "" {
if err := createPidFile(pidFile, process); err != nil {
process.Signal(syscall.SIGKILL)
process.Wait()
return -1, err
}
}
if detach {
return 0, nil
}
return handler.forward(process)
}