Skip to content

Commit

Permalink
Merge pull request openshift#64 from vishh/runin
Browse files Browse the repository at this point in the history
Adding RunIn to run a user specified command in an existing container.
  • Loading branch information
Michael Crosby committed Jul 23, 2014
2 parents 1f3d65f + b2337e4 commit 80c1ae9
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 10 deletions.
82 changes: 73 additions & 9 deletions namespaces/execin.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,96 @@ package namespaces

import (
"encoding/json"
"os"
"strconv"

"github.com/docker/libcontainer"
"github.com/docker/libcontainer/label"
"github.com/docker/libcontainer/system"
"io"
"os"
"os/exec"
"strconv"
"syscall"
)

// Runs the command under 'args' inside an existing container referred to by 'container'.
// Returns the exitcode of the command upon success and appropriate error on failure.
func RunIn(container *libcontainer.Config, state *libcontainer.State, args []string, nsinitPath string, stdin io.Reader, stdout, stderr io.Writer, console string, startCallback func(*exec.Cmd)) (int, error) {
initArgs, err := getNsEnterCommand(strconv.Itoa(state.InitPid), container, console, args)
if err != nil {
return -1, err
}

cmd := exec.Command(nsinitPath, initArgs...)
// Note: these are only used in non-tty mode
// if there is a tty for the container it will be opened within the namespace and the
// fds will be duped to stdin, stdiout, and stderr
cmd.Stdin = stdin
cmd.Stdout = stdout
cmd.Stderr = stderr

if err := cmd.Start(); err != nil {
return -1, err
}
if startCallback != nil {
startCallback(cmd)
}

if err := cmd.Wait(); err != nil {
if _, ok := err.(*exec.ExitError); !ok {
return -1, err
}
}

return cmd.ProcessState.Sys().(syscall.WaitStatus).ExitStatus(), nil
}

// ExecIn uses an existing pid and joins the pid's namespaces with the new command.
func ExecIn(container *libcontainer.Config, state *libcontainer.State, args []string) error {
// TODO(vmarmol): If this gets too long, send it over a pipe to the child.
// Marshall the container into JSON since it won't be available in the namespace.
containerJson, err := json.Marshal(container)
// Enter the namespace and then finish setup
args, err := getNsEnterCommand(strconv.Itoa(state.InitPid), container, "", args)
if err != nil {
return err
}

// Enter the namespace and then finish setup
finalArgs := []string{os.Args[0], "nsenter", "--nspid", strconv.Itoa(state.InitPid), "--containerjson", string(containerJson), "--"}
finalArgs = append(finalArgs, args...)
finalArgs := append([]string{os.Args[0]}, args...)

if err := system.Execv(finalArgs[0], finalArgs[0:], os.Environ()); err != nil {
return err
}

panic("unreachable")
}

func getContainerJson(container *libcontainer.Config) (string, error) {
// TODO(vmarmol): If this gets too long, send it over a pipe to the child.
// Marshall the container into JSON since it won't be available in the namespace.
containerJson, err := json.Marshal(container)
if err != nil {
return "", err
}
return string(containerJson), nil
}

func getNsEnterCommand(initPid string, container *libcontainer.Config, console string, args []string) ([]string, error) {
containerJson, err := getContainerJson(container)
if err != nil {
return nil, err
}

out := []string{
"nsenter",
"--nspid", initPid,
"--containerjson", containerJson,
}

if console != "" {
out = append(out, "--console", console)
}
out = append(out, "--")
out = append(out, args...)

return out, nil
}

// Run a command in a container after entering the namespace.
func NsEnter(container *libcontainer.Config, args []string) error {
// clear the current processes env and replace it with the environment
Expand Down
35 changes: 35 additions & 0 deletions namespaces/nsenter.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,15 @@ void nsenter() {
static const struct option longopts[] = {
{ "nspid", required_argument, NULL, 'n' },
{ "containerjson", required_argument, NULL, 'c' },
{ "console", required_argument, NULL, 't' },
{ NULL, 0, NULL, 0 }
};
int c;
pid_t init_pid = -1;
char *init_pid_str = NULL;
char *container_json = NULL;
char *console = NULL;
while ((c = getopt_long_only(argc, argv, "n:s:c:", longopts, NULL)) != -1) {
switch (c) {
case 'n':
Expand All @@ -103,6 +105,9 @@ void nsenter() {
case 'c':
container_json = optarg;
break;
case 't':
console = optarg;
break;
}
}
Expand All @@ -121,6 +126,21 @@ void nsenter() {
argc -= 3;
argv += 3;
if (setsid() == -1) {
fprintf(stderr, "setsid failed. Error: %s\n", strerror(errno));
exit(1);
}
// before we setns we need to dup the console
int consolefd = -1;
if (console != NULL) {
consolefd = open(console, O_RDWR);
if (consolefd < 0) {
fprintf(stderr, "nsenter: failed to open console %s\n", console, strerror(errno));
exit(1);
}
}
// Setns on all supported namespaces.
char ns_dir[PATH_MAX];
memset(ns_dir, 0, PATH_MAX);
Expand Down Expand Up @@ -159,6 +179,21 @@ void nsenter() {
// We must fork to actually enter the PID namespace.
int child = fork();
if (child == 0) {
if (consolefd != -1) {
if (dup2(consolefd, STDIN_FILENO) != 0) {
fprintf(stderr, "nsenter: failed to dup 0 %s\n", strerror(errno));
exit(1);
}
if (dup2(consolefd, STDOUT_FILENO) != STDOUT_FILENO) {
fprintf(stderr, "nsenter: failed to dup 1 %s\n", strerror(errno));
exit(1);
}
if (dup2(consolefd, STDERR_FILENO) != STDERR_FILENO) {
fprintf(stderr, "nsenter: failed to dup 2 %s\n", strerror(errno));
exit(1);
}
}
// Finish executing, let the Go runtime take over.
return;
} else {
Expand Down
55 changes: 54 additions & 1 deletion nsinit/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func execAction(context *cli.Context) {
}

if state != nil {
err = namespaces.ExecIn(container, state, []string(context.Args()))
exitCode, err = runIn(container, state, []string(context.Args()))
} else {
exitCode, err = startContainer(container, dataPath, []string(context.Args()))
}
Expand All @@ -48,6 +48,59 @@ func execAction(context *cli.Context) {
os.Exit(exitCode)
}

func runIn(container *libcontainer.Config, state *libcontainer.State, args []string) (int, error) {
var (
master *os.File
console string
err error

stdin = os.Stdin
stdout = os.Stdout
stderr = os.Stderr
sigc = make(chan os.Signal, 10)
)

signal.Notify(sigc)

if container.Tty {
stdin = nil
stdout = nil
stderr = nil

master, console, err = consolepkg.CreateMasterAndConsole()
if err != nil {
log.Fatal(err)
}

go io.Copy(master, os.Stdin)
go io.Copy(os.Stdout, master)

state, err := term.SetRawTerminal(os.Stdin.Fd())
if err != nil {
log.Fatal(err)
}

defer term.RestoreTerminal(os.Stdin.Fd(), state)
}

startCallback := func(cmd *exec.Cmd) {
go func() {
resizeTty(master)

for sig := range sigc {
switch sig {
case syscall.SIGWINCH:
resizeTty(master)
default:
cmd.Process.Signal(sig)
}
}
}()
}

return namespaces.RunIn(container, state, args, os.Args[0], stdin, stdout, stderr, console, startCallback)
}

// startContainer starts the container. Returns the exit status or -1 and an
// error.
//
Expand Down
1 change: 1 addition & 0 deletions nsinit/nsenter.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ var nsenterCommand = cli.Command{
Flags: []cli.Flag{
cli.IntFlag{Name: "nspid"},
cli.StringFlag{Name: "containerjson"},
cli.StringFlag{Name: "console"},
},
}

Expand Down

0 comments on commit 80c1ae9

Please sign in to comment.