| // +build linux |
| |
| package main |
| |
| import ( |
| "errors" |
| "fmt" |
| "os" |
| "path/filepath" |
| "strconv" |
| "syscall" |
| |
| "github.com/Sirupsen/logrus" |
| "github.com/coreos/go-systemd/activation" |
| "github.com/opencontainers/runc/libcontainer" |
| "github.com/opencontainers/runc/libcontainer/cgroups/systemd" |
| "github.com/opencontainers/runc/libcontainer/specconv" |
| "github.com/opencontainers/runtime-spec/specs-go" |
| "github.com/urfave/cli" |
| ) |
| |
| var errEmptyID = errors.New("container id cannot be empty") |
| |
| var container libcontainer.Container |
| |
| // 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 |
| } |
| cgroupManager := libcontainer.Cgroupfs |
| if context.GlobalBool("systemd-cgroup") { |
| if systemd.UseSystemd() { |
| cgroupManager = libcontainer.SystemdCgroups |
| } else { |
| return nil, fmt.Errorf("systemd cgroup flag passed, but systemd support for managing cgroups is not available") |
| } |
| } |
| return libcontainer.New(abs, cgroupManager, libcontainer.CriuPath(context.GlobalString("criu"))) |
| } |
| |
| // 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) |
| } |
| |
| 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 _, gid := range p.User.AdditionalGids { |
| lp.AdditionalGroups = append(lp.AdditionalGroups, strconv.FormatUint(uint64(gid), 10)) |
| } |
| 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, 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") |
| } |
| if createTTY { |
| return createTty(process, rootuid, rootgid, console) |
| } |
| if detach { |
| if err := dupStdio(process, rootuid, rootgid); err != nil { |
| return nil, err |
| } |
| return &tty{}, nil |
| } |
| return createStdioPipes(process, rootuid, rootgid) |
| } |
| |
| // createPidFile creates a file with the processes pid inside it atomically |
| // it creates a temp file with the paths filename + '.' infront of it |
| // then renames the file |
| func createPidFile(path string, process *libcontainer.Process) error { |
| pid, err := process.Pid() |
| if err != nil { |
| return err |
| } |
| var ( |
| tmpDir = filepath.Dir(path) |
| tmpName = filepath.Join(tmpDir, fmt.Sprintf(".%s", filepath.Base(path))) |
| ) |
| f, err := os.OpenFile(tmpName, os.O_RDWR|os.O_CREATE|os.O_EXCL|os.O_SYNC, 0666) |
| if err != nil { |
| return err |
| } |
| _, err = fmt.Fprintf(f, "%d", pid) |
| f.Close() |
| if err != nil { |
| return err |
| } |
| return os.Rename(tmpName, path) |
| } |
| |
| func createContainer(context *cli.Context, id string, spec *specs.Spec) (libcontainer.Container, error) { |
| config, err := specconv.CreateLibcontainerConfig(&specconv.CreateOpts{ |
| CgroupName: id, |
| UseSystemdCgroup: context.GlobalBool("systemd-cgroup"), |
| NoPivotRoot: context.Bool("no-pivot"), |
| NoNewKeyring: context.Bool("no-new-keyring"), |
| Spec: spec, |
| }) |
| if err != nil { |
| return nil, err |
| } |
| |
| factory, err := loadFactory(context) |
| if err != nil { |
| return nil, err |
| } |
| return factory.Create(id, config) |
| } |
| |
| type runner struct { |
| enableSubreaper bool |
| shouldDestroy bool |
| detach bool |
| listenFDs []*os.File |
| pidFile string |
| console string |
| container libcontainer.Container |
| create bool |
| } |
| |
| func (r *runner) run(config *specs.Process) (int, error) { |
| process, err := newProcess(*config) |
| if err != nil { |
| r.destroy() |
| return -1, err |
| } |
| if len(r.listenFDs) > 0 { |
| 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() |
| if err != nil { |
| r.destroy() |
| return -1, err |
| } |
| rootgid, err := r.container.Config().HostGID() |
| 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 |
| if !r.create { |
| startFn = r.container.Run |
| } |
| defer tty.Close() |
| if err := startFn(process); err != nil { |
| r.destroy() |
| return -1, err |
| } |
| if err := tty.ClosePostStart(); err != nil { |
| r.terminate(process) |
| r.destroy() |
| return -1, err |
| } |
| if r.pidFile != "" { |
| if err := createPidFile(r.pidFile, process); err != nil { |
| r.terminate(process) |
| r.destroy() |
| return -1, err |
| } |
| } |
| if r.detach || r.create { |
| return 0, nil |
| } |
| status, err := handler.forward(process) |
| if err != nil { |
| r.terminate(process) |
| } |
| r.destroy() |
| return status, err |
| } |
| |
| func (r *runner) destroy() { |
| if r.shouldDestroy { |
| destroy(r.container) |
| } |
| } |
| |
| func (r *runner) terminate(p *libcontainer.Process) { |
| p.Signal(syscall.SIGKILL) |
| p.Wait() |
| } |
| |
| func validateProcessSpec(spec *specs.Process) error { |
| if spec.Cwd == "" { |
| return fmt.Errorf("Cwd property must not be empty") |
| } |
| if !filepath.IsAbs(spec.Cwd) { |
| return fmt.Errorf("Cwd must be an absolute path") |
| } |
| if len(spec.Args) == 0 { |
| return fmt.Errorf("args must not be empty") |
| } |
| return nil |
| } |
| |
| func startContainer(context *cli.Context, spec *specs.Spec, create bool) (int, error) { |
| id := context.Args().First() |
| if id == "" { |
| return -1, errEmptyID |
| } |
| container, err := createContainer(context, id, spec) |
| if err != nil { |
| return -1, err |
| } |
| // Support on-demand socket activation by passing file descriptors into the container init process. |
| listenFDs := []*os.File{} |
| if os.Getenv("LISTEN_FDS") != "" { |
| listenFDs = activation.Files(false) |
| } |
| r := &runner{ |
| enableSubreaper: !context.Bool("no-subreaper"), |
| shouldDestroy: true, |
| container: container, |
| listenFDs: listenFDs, |
| console: context.String("console"), |
| detach: context.Bool("detach"), |
| pidFile: context.String("pid-file"), |
| create: create, |
| } |
| return r.run(&spec.Process) |
| } |