Permalink
Browse files

contrib: add recvtty proof-of-concept

This is a proof-of-concept for the --console-socket API. It just acts as
a dumb input-output copy process (nowhere near as good as the internal
runC one since it doesn't handle console resizes or signals). It also
provides a test-friendly mode that will be used in the bats integration
tests.

This patch is part of the console rewrite patchset.

Signed-off-by: Aleksa Sarai <asarai@suse.de>
  • Loading branch information...
1 parent 7df64f8 commit 1543444adad0ee7a0f648115fa3436c6eb05097a @cyphar cyphar committed Sep 5, 2016
Showing with 265 additions and 5 deletions.
  1. +1 −0 .gitignore
  2. +17 −5 Makefile
  3. +247 −0 contrib/cmd/recvtty/recvtty.go
View
@@ -1,5 +1,6 @@
vendor/pkg
/runc
+contrib/cmd/recvtty/recvtty
Godeps/_workspace/src/github.com/opencontainers/runc
man/man8
release
View
@@ -1,7 +1,8 @@
-.PHONY: dbuild man \
+.PHONY: all 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)
@@ -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
-release: $(RUNC_LINK)
+release: $(RUNC_LINK) | $(RUNC_LINK)
@flag_list=(seccomp selinux apparmor static ambient); \
unset expression; \
for flag in "$${flag_list[@]}"; do \
@@ -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 ./...
@@ -113,6 +124,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)
@@ -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)
+ }
+}

0 comments on commit 1543444

Please sign in to comment.