Merge pull request #525 from crosbymichael/exec

Load process.json for exec and add detach
This commit is contained in:
Alexander Morozov 2016-02-05 12:37:56 -08:00
commit 4f601205d4
5 changed files with 182 additions and 108 deletions

111
exec.go
View File

@ -3,14 +3,17 @@
package main
import (
"encoding/json"
"fmt"
"os"
"path"
"strconv"
"strings"
"syscall"
"github.com/Sirupsen/logrus"
"github.com/codegangsta/cli"
"github.com/opencontainers/specs"
)
var execCommand = cli.Command{
@ -37,15 +40,25 @@ var execCommand = cli.Command{
Name: "user, u",
Usage: "UID (format: <uid>[:<gid>])",
},
cli.StringFlag{
Name: "process,p",
Usage: "path to the process.json",
},
cli.BoolFlag{
Name: "detach,d",
Usage: "detach from the container's process",
},
cli.StringFlag{
Name: "pid-file",
Value: "",
Usage: "specify the file to write the process id to",
},
},
Action: func(context *cli.Context) {
if len(context.Args()) == 0 {
logrus.Fatal("pass the command")
}
if os.Geteuid() != 0 {
logrus.Fatal("runc should be run as root")
}
status, err := execProcess(context, context.Args())
status, err := execProcess(context)
if err != nil {
logrus.Fatalf("exec failed: %v", err)
}
@ -53,72 +66,96 @@ var execCommand = cli.Command{
},
}
func execProcess(context *cli.Context, args []string) (int, error) {
func execProcess(context *cli.Context) (int, error) {
container, err := getContainer(context)
if err != nil {
return -1, err
}
bundle := container.Config().Rootfs
if err := os.Chdir(path.Dir(bundle)); err != nil {
return -1, err
}
spec, _, err := loadSpec(specConfig, runtimeConfig)
var (
detach = context.Bool("detach")
rootfs = container.Config().Rootfs
)
rootuid, err := container.Config().HostUID()
if err != nil {
return -1, err
}
p, err := getProcess(context, path.Dir(rootfs))
if err != nil {
return -1, err
}
process := newProcess(*p)
tty, err := setupIO(process, rootuid, context.String("console"), p.Terminal, detach)
if err != nil {
return -1, err
}
if err := container.Start(process); 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()
return -1, err
}
}
if detach {
return 0, nil
}
handler := newSignalHandler(tty)
defer handler.Close()
return handler.forward(process)
}
func getProcess(context *cli.Context, bundle string) (*specs.Process, error) {
if path := context.String("process"); path != "" {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
var p specs.Process
if err := json.NewDecoder(f).Decode(&p); err != nil {
return nil, err
}
return &p, nil
}
// process via cli flags
if err := os.Chdir(bundle); err != nil {
return nil, err
}
spec, _, err := loadSpec(specConfig, runtimeConfig)
if err != nil {
return nil, err
}
p := spec.Process
p.Args = context.Args()
// override the cwd, if passed
if context.String("cwd") != "" {
p.Cwd = context.String("cwd")
}
// append the passed env variables
for _, e := range context.StringSlice("env") {
p.Env = append(p.Env, e)
}
// set the tty
if context.IsSet("tty") {
p.Terminal = context.Bool("tty")
}
// override the user, if passed
if context.String("user") != "" {
u := strings.SplitN(context.String("user"), ":", 2)
if len(u) > 1 {
gid, err := strconv.Atoi(u[1])
if err != nil {
return -1, fmt.Errorf("parsing %s as int for gid failed: %v", u[1], err)
return nil, fmt.Errorf("parsing %s as int for gid failed: %v", u[1], err)
}
p.User.GID = uint32(gid)
}
uid, err := strconv.Atoi(u[0])
if err != nil {
return -1, fmt.Errorf("parsing %s as int for uid failed: %v", u[0], err)
return nil, fmt.Errorf("parsing %s as int for uid failed: %v", u[0], err)
}
p.User.UID = uint32(uid)
}
process := newProcess(p)
process.Args = args
rootuid, err := container.Config().HostUID()
if err != nil {
return -1, err
}
tty, err := newTty(p.Terminal, process, rootuid, context.String("console"))
if err != nil {
return -1, err
}
handler := newSignalHandler(tty)
defer handler.Close()
if err := container.Start(process); err != nil {
return -1, err
}
return handler.forward(process)
return &p, nil
}

View File

@ -5,6 +5,7 @@ package main
import (
"fmt"
"os"
"syscall"
"github.com/Sirupsen/logrus"
"github.com/codegangsta/cli"
@ -53,6 +54,15 @@ var restoreCommand = cli.Command{
Value: "",
Usage: "path to the root of the bundle directory",
},
cli.BoolFlag{
Name: "detach,d",
Usage: "detach from the container's process",
},
cli.StringFlag{
Name: "pid-file",
Value: "",
Usage: "specify the file to write the process id to",
},
},
Action: func(context *cli.Context) {
imagePath := context.String("image-path")
@ -108,21 +118,30 @@ func restoreContainer(context *cli.Context, spec *specs.LinuxSpec, config *confi
// ensure that the container is always removed if we were the process
// that created it.
defer destroy(container)
process := &libcontainer.Process{
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
detach := context.Bool("detach")
if !detach {
defer destroy(container)
}
tty, err := newTty(spec.Process.Terminal, process, rootuid, "")
process := &libcontainer.Process{}
tty, err := setupIO(process, rootuid, "", false, detach)
if err != nil {
return -1, err
}
handler := newSignalHandler(tty)
defer handler.Close()
if err := container.Restore(process, options); 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()
return -1, err
}
}
if detach {
return 0, nil
}
handler := newSignalHandler(tty)
defer handler.Close()
return handler.forward(process)
}

View File

@ -132,38 +132,19 @@ func startContainer(context *cli.Context, spec *specs.LinuxSpec, rspec *specs.Li
process.ExtraFiles = append(process.ExtraFiles, os.NewFile(uintptr(i), ""))
}
}
var tty *tty
if spec.Process.Terminal {
if tty, err = createTty(process, rootuid, context.String("console")); err != nil {
return -1, err
}
} else if detach {
if err := dupStdio(process, rootuid); err != nil {
return -1, err
}
} else {
if tty, err = createStdioPipes(process, rootuid); err != nil {
return -1, err
}
tty, err := setupIO(process, rootuid, context.String("console"), spec.Process.Terminal, detach)
if err != nil {
return -1, err
}
if err := container.Start(process); err != nil {
return -1, err
}
if pidFile := context.String("pid-file"); pidFile != "" {
pid, err := process.Pid()
if err != nil {
if err := createPidFile(pidFile, process); err != nil {
process.Signal(syscall.SIGKILL)
process.Wait()
return -1, err
}
f, err := os.Create(pidFile)
if err != nil {
logrus.WithField("pid", pid).Error("create pid file")
} else {
_, err = fmt.Fprintf(f, "%d", pid)
f.Close()
if err != nil {
logrus.WithField("error", err).Error("write pid file")
}
}
}
if detach {
return 0, nil
@ -172,40 +153,3 @@ func startContainer(context *cli.Context, spec *specs.LinuxSpec, rspec *specs.Li
defer handler.Close()
return handler.forward(process)
}
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, rspec *specs.LinuxRuntimeSpec, notifySocket string) {
mountName := "sdNotify"
spec.Mounts = append(spec.Mounts, specs.MountPoint{Name: mountName, Path: notifySocket})
spec.Process.Env = append(spec.Process.Env, fmt.Sprintf("NOTIFY_SOCKET=%s", notifySocket))
rspec.Mounts[mountName] = specs.Mount{Type: "bind", Source: notifySocket, Options: []string{"bind"}}
}
// If systemd is supporting on-demand socket activation, this function will add support
// for on-demand socket activation for the containerized service.
func setupSocketActivation(spec *specs.LinuxSpec, listenFds string) {
spec.Process.Env = append(spec.Process.Env, fmt.Sprintf("LISTEN_FDS=%s", listenFds), "LISTEN_PID=1")
}
func destroy(container libcontainer.Container) {
if err := container.Destroy(); err != nil {
logrus.Error(err)
}
}

5
tty.go
View File

@ -35,7 +35,10 @@ func createStdioPipes(p *libcontainer.Process, rootuid int) (*tty, error) {
i.Stderr,
},
}
go io.Copy(i.Stdin, os.Stdin)
go func() {
io.Copy(i.Stdin, os.Stdin)
i.Stdin.Close()
}()
go io.Copy(os.Stdout, i.Stdout)
go io.Copy(os.Stderr, i.Stderr)
return t, nil

View File

@ -6,7 +6,9 @@ import (
"fmt"
"os"
"path/filepath"
"syscall"
"github.com/Sirupsen/logrus"
"github.com/codegangsta/cli"
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/configs"
@ -154,3 +156,72 @@ func newProcess(p specs.Process) *libcontainer.Process {
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, rspec *specs.LinuxRuntimeSpec, notifySocket string) {
mountName := "sdNotify"
spec.Mounts = append(spec.Mounts, specs.MountPoint{Name: mountName, Path: notifySocket})
spec.Process.Env = append(spec.Process.Env, fmt.Sprintf("NOTIFY_SOCKET=%s", notifySocket))
rspec.Mounts[mountName] = specs.Mount{Type: "bind", Source: notifySocket, Options: []string{"bind"}}
}
// If systemd is supporting on-demand socket activation, this function will add support
// for on-demand socket activation for the containerized service.
func setupSocketActivation(spec *specs.LinuxSpec, listenFds string) {
spec.Process.Env = append(spec.Process.Env, fmt.Sprintf("LISTEN_FDS=%s", listenFds), "LISTEN_PID=1")
}
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
}