Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consoles, consoles, consoles. #1018

Merged
merged 10 commits into from
Dec 7, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -26,13 +27,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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its ok to output recvtty in current directory instead of contrib here i think.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought so too, but @hqhq mentioned that it isn't really an "official" binary so I'm on the fence about it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think as long as we dont install it explicitly its ok, but im not sure either

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was concerned that it might cause confusion that runc depends on this recvtty if we build it under root.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just going to leave it as-is, since it feels marginally safer tucked away so nobody ships it as a serious component of runC. :P


release: $(RUNC_LINK)
release: $(RUNC_LINK) | $(RUNC_LINK)
@flag_list=(seccomp selinux apparmor static ambient); \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this right? $(RUNC_LINK) | $(RUNC_LINK) or $(SOURCES) | $(RUNC_LINK) ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it should be $(SOURCES). Thanks.

unset expression; \
for flag in "$${flag_list[@]}"; do \
Expand Down Expand Up @@ -62,7 +73,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 @@ -92,6 +103,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 @@ -113,6 +127,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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the code below it looks like single is the default. Should probably say that here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

recvtty -h will already show the default.

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",
Copy link
Member

@mikebrow mikebrow Sep 14, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given null is closer to the prior use cases for runc, is making "single" the best default choice here, or just a limitation given the above mentioned stdin limitation for this mockup client?

Copy link
Member Author

@cyphar cyphar Sep 14, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just because we used a --console /dev/pts/ptmx to hack around detaching inside the tests doesn't mean that was the intended usecase for --console. 😉 The original purpose of --console was to allow a higher level manager of runC to directly access the console file descriptor without runC getting in the way.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok hack removed got it...

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