| // +build linux |
| |
| package main |
| |
| import ( |
| "errors" |
| "fmt" |
| "os" |
| "path/filepath" |
| "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, |
| }, |
| { |
| Path: "/dev/tty0", |
| Type: 'c', |
| Major: 4, |
| Minor: 0, |
| Permissions: "rwm", |
| Allow: true, |
| }, |
| { |
| Path: "/dev/tty1", |
| Type: 'c', |
| Major: 4, |
| 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 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) { |
| root := context.GlobalString("root") |
| abs, err := filepath.Abs(root) |
| if err != nil { |
| return nil, err |
| } |
| return libcontainer.New(abs, libcontainer.Cgroupfs, func(l *libcontainer.LinuxFactory) error { |
| l.CriuPath = context.GlobalString("criu") |
| return nil |
| }) |
| } |
| |
| // 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) { |
| if lerr, ok := err.(libcontainer.Error); ok { |
| lerr.Detail(os.Stderr) |
| os.Exit(1) |
| } |
| fmt.Fprintln(os.Stderr, err) |
| os.Exit(1) |
| } |
| |
| 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, |
| } |
| } |
| |
| 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) |
| } |
| } |
| |
| 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 nil, 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 |
| } |
| |
| if err := container.Start(process); err != nil { |
| tty.Close() |
| return -1, err |
| } |
| |
| if pidFile != "" { |
| if err := createPidFile(pidFile, process); err != nil { |
| process.Signal(syscall.SIGKILL) |
| process.Wait() |
| tty.Close() |
| return -1, err |
| } |
| } |
| if detach { |
| return 0, nil |
| } |
| handler := newSignalHandler(tty) |
| defer handler.Close() |
| |
| return handler.forward(process) |
| } |