| // +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/specs-go" |
| ) |
| |
| 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, error) { |
| lp := &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, |
| } |
| for _, rlimit := range p.Rlimits { |
| rl, err := createLibContainerRlimit(rlimit) |
| if err != nil { |
| return nil, err |
| } |
| lp.Rlimits = append(lp.Rlimits, rl) |
| } |
| return lp, nil |
| } |
| |
| 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.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 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.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666) |
| 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.Spec) (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, err := newProcess(*config) |
| if err != nil { |
| return -1, err |
| } |
| |
| // 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) |
| } |