Skip to content

Commit

Permalink
Merge pull request #1018 from cyphar/console-rewrite
Browse files Browse the repository at this point in the history
Consoles, consoles, consoles.
  • Loading branch information
Mrunal Patel authored Dec 7, 2016
2 parents 47ea5c7 + b0fc85e commit 34f23cb
Show file tree
Hide file tree
Showing 51 changed files with 1,306 additions and 350 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
vendor/pkg
/runc
contrib/cmd/recvtty/recvtty
Godeps/_workspace/src/github.com/opencontainers/runc
man/man8
release
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ RUN mkdir -p /go/src/github.com/mvdan \
# setup a playground for us to spawn containers in
ENV ROOTFS /busybox
RUN mkdir -p ${ROOTFS} \
&& curl -o- -sSL 'https://github.com/jpetazzo/docker-busybox/raw/buildroot-2014.11/rootfs.tar' | tar -C ${ROOTFS} -xf -
&& curl -o- -sSL 'https://github.com/docker-library/busybox/raw/a0558a9006ce0dd6f6ec5d56cfd3f32ebeeb815f/glibc/busybox.tar.xz' | tar xfJC - ${ROOTFS}


COPY script/tmpmount /
WORKDIR /go/src/github.com/opencontainers/runc
Expand Down
25 changes: 20 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
.PHONY: dbuild man \
.PHONY: all shell dbuild man \
localtest localunittest localintegration \
test unittest integration

SOURCES := $(shell find . 2>&1 | grep -E '.*\.(c|h|go)$$')
PREFIX := $(DESTDIR)/usr/local
BINDIR := $(PREFIX)/sbin
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null)
Expand All @@ -25,13 +26,23 @@ VERSION := ${shell cat ./VERSION}

SHELL := $(shell command -v bash 2>/dev/null)

all: $(RUNC_LINK)
.DEFAULT: runc

runc: $(SOURCES) | $(RUNC_LINK)
go build -i -ldflags "-X main.gitCommit=${COMMIT} -X main.version=${VERSION}" -tags "$(BUILDTAGS)" -o runc .

static: $(RUNC_LINK)
all: runc recvtty

recvtty: contrib/cmd/recvtty/recvtty

contrib/cmd/recvtty/recvtty: $(SOURCES) | $(RUNC_LINK)
go build -i -ldflags "-X main.gitCommit=${COMMIT} -X main.version=${VERSION}" -tags "$(BUILDTAGS)" -o contrib/cmd/recvtty/recvtty ./contrib/cmd/recvtty

static: $(SOURCES) | $(RUNC_LINK)
CGO_ENABLED=1 go build -i -tags "$(BUILDTAGS) cgo static_build" -ldflags "-w -extldflags -static -X main.gitCommit=${COMMIT} -X main.version=${VERSION}" -o runc .
CGO_ENABLED=1 go build -i -tags "$(BUILDTAGS) cgo static_build" -ldflags "-w -extldflags -static -X main.gitCommit=${COMMIT} -X main.version=${VERSION}" -o contrib/cmd/recvtty/recvtty ./contrib/cmd/recvtty

release: $(RUNC_LINK)
release: $(RUNC_LINK) | $(RUNC_LINK)
@flag_list=(seccomp selinux apparmor static ambient); \
unset expression; \
for flag in "$${flag_list[@]}"; do \
Expand Down Expand Up @@ -61,7 +72,7 @@ $(RUNC_LINK):
ln -sfn $(CURDIR) $(RUNC_LINK)

dbuild: runcimage
docker run --rm -v $(CURDIR):/go/src/$(PROJECT) --privileged $(RUNC_IMAGE) make
docker run --rm -v $(CURDIR):/go/src/$(PROJECT) --privileged $(RUNC_IMAGE) make clean all

lint:
go vet ./...
Expand Down Expand Up @@ -91,6 +102,9 @@ integration: runcimage
localintegration: all
bats -t tests/integration${TESTFLAGS}

shell: all
docker run -e TESTFLAGS -ti --privileged --rm -v $(CURDIR):/go/src/$(PROJECT) $(RUNC_IMAGE) bash

install:
install -D -m0755 runc $(BINDIR)/runc

Expand All @@ -112,6 +126,7 @@ uninstall-man:

clean:
rm -f runc
rm -f contrib/cmd/recvtty/recvtty
rm -f $(RUNC_LINK)
rm -rf $(GOPATH)/pkg
rm -rf $(RELEASE_DIR)
Expand Down
247 changes: 247 additions & 0 deletions contrib/cmd/recvtty/recvtty.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
/*
* Copyright 2016 SUSE LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package main

import (
"fmt"
"io"
"io/ioutil"
"net"
"os"
"strings"

"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/utils"
"github.com/urfave/cli"
)

// version will be populated by the Makefile, read from
// VERSION file of the source code.
var version = ""

// gitCommit will be the hash that the binary was built from
// and will be populated by the Makefile
var gitCommit = ""

const (
usage = `Open Container Initiative contrib/cmd/recvtty
recvtty is a reference implementation of a consumer of runC's --console-socket
API. It has two main modes of operation:
* single: Only permit one terminal to be sent to the socket, which is
then hooked up to the stdio of the recvtty process. This is useful
for rudimentary shell management of a container.
* null: Permit as many terminals to be sent to the socket, but they
are read to /dev/null. This is used for testing, and imitates the
old runC API's --console=/dev/pts/ptmx hack which would allow for a
similar trick. This is probably not what you want to use, unless
you're doing something like our bats integration tests.
To use recvtty, just specify a socket path at which you want to receive
terminals:
$ recvtty [--mode <single|null>] socket.sock
`
)

func bail(err error) {
fmt.Fprintf(os.Stderr, "[recvtty] fatal error: %v\n", err)
os.Exit(1)
}

func handleSingle(path string) error {
// Open a socket.
ln, err := net.Listen("unix", path)
if err != nil {
return err
}
defer ln.Close()

// We only accept a single connection, since we can only really have
// one reader for os.Stdin. Plus this is all a PoC.
conn, err := ln.Accept()
if err != nil {
return err
}
defer conn.Close()

// Close ln, to allow for other instances to take over.
ln.Close()

// Get the fd of the connection.
unixconn, ok := conn.(*net.UnixConn)
if !ok {
return fmt.Errorf("failed to cast to unixconn")
}

socket, err := unixconn.File()
if err != nil {
return err
}
defer socket.Close()

// Get the master file descriptor from runC.
master, err := utils.RecvFd(socket)
if err != nil {
return err
}

// Print the file descriptor tag.
ti, err := libcontainer.GetTerminalInfo(master.Name())
if err != nil {
return err
}
fmt.Printf("[recvtty] received masterfd: container '%s'\n", ti.ContainerID)

// Copy from our stdio to the master fd.
quitChan := make(chan struct{})
go func() {
io.Copy(os.Stdout, master)
quitChan <- struct{}{}
}()
go func() {
io.Copy(master, os.Stdin)
quitChan <- struct{}{}
}()

// Only close the master fd once we've stopped copying.
<-quitChan
master.Close()
return nil
}

func handleNull(path string) error {
// Open a socket.
ln, err := net.Listen("unix", path)
if err != nil {
return err
}
defer ln.Close()

// As opposed to handleSingle we accept as many connections as we get, but
// we don't interact with Stdin at all (and we copy stdout to /dev/null).
for {
conn, err := ln.Accept()
if err != nil {
return err
}
go func(conn net.Conn) {
// Don't leave references lying around.
defer conn.Close()

// Get the fd of the connection.
unixconn, ok := conn.(*net.UnixConn)
if !ok {
return
}

socket, err := unixconn.File()
if err != nil {
return
}
defer socket.Close()

// Get the master file descriptor from runC.
master, err := utils.RecvFd(socket)
if err != nil {
return
}

// Print the file descriptor tag.
ti, err := libcontainer.GetTerminalInfo(master.Name())
if err != nil {
bail(err)
}
fmt.Printf("[recvtty] received masterfd: container '%s'\n", ti.ContainerID)

// Just do a dumb copy to /dev/null.
devnull, err := os.OpenFile("/dev/null", os.O_RDWR, 0)
if err != nil {
// TODO: Handle this nicely.
return
}

io.Copy(devnull, master)
devnull.Close()
}(conn)
}
}

func main() {
app := cli.NewApp()
app.Name = "recvtty"
app.Usage = usage

// Set version to be the same as runC.
var v []string
if version != "" {
v = append(v, version)
}
if gitCommit != "" {
v = append(v, fmt.Sprintf("commit: %s", gitCommit))
}
app.Version = strings.Join(v, "\n")

// Set the flags.
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "mode, m",
Value: "single",
Usage: "Mode of operation (single or null)",
},
cli.StringFlag{
Name: "pid-file",
Value: "",
Usage: "Path to write daemon process ID to",
},
}

app.Action = func(ctx *cli.Context) error {
args := ctx.Args()
if len(args) != 1 {
return fmt.Errorf("need to specify a single socket path")
}
path := ctx.Args()[0]

pidPath := ctx.String("pid-file")
if pidPath != "" {
pid := fmt.Sprintf("%d\n", os.Getpid())
if err := ioutil.WriteFile(pidPath, []byte(pid), 0644); err != nil {
return err
}
}

switch ctx.String("mode") {
case "single":
if err := handleSingle(path); err != nil {
return err
}
case "null":
if err := handleNull(path); err != nil {
return err
}
default:
return fmt.Errorf("need to select a valid mode: %s", ctx.String("mode"))
}
return nil
}
if err := app.Run(os.Args); err != nil {
bail(err)
}
}
4 changes: 2 additions & 2 deletions create.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ command(s) that get executed on start, edit the args parameter of the spec. See
Usage: `path to the root of the bundle directory, defaults to the current directory`,
},
cli.StringFlag{
Name: "console",
Name: "console-socket",
Value: "",
Usage: "specify the pty slave path for use with the container",
Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal",
},
cli.StringFlag{
Name: "pid-file",
Expand Down
8 changes: 4 additions & 4 deletions exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ Where "<container-id>" is the name for the instance of the container and
EXAMPLE:
For example, if the container is configured to run the linux ps command the
following will output a list of processes running in the container:
# runc exec <container-id> ps`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "console",
Usage: "specify the pty slave path for use with the container",
Name: "console-socket",
Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal",
},
cli.StringFlag{
Name: "cwd",
Expand Down Expand Up @@ -131,7 +131,7 @@ func execProcess(context *cli.Context) (int, error) {
enableSubreaper: false,
shouldDestroy: false,
container: container,
console: context.String("console"),
consoleSocket: context.String("console-socket"),
detach: detach,
pidFile: context.String("pid-file"),
}
Expand Down
Loading

0 comments on commit 34f23cb

Please sign in to comment.