mirror of
https://github.com/opencontainers/runc.git
synced 2026-07-11 06:03:57 +08:00
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>
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
vendor/pkg
|
vendor/pkg
|
||||||
/runc
|
/runc
|
||||||
|
contrib/cmd/recvtty/recvtty
|
||||||
Godeps/_workspace/src/github.com/opencontainers/runc
|
Godeps/_workspace/src/github.com/opencontainers/runc
|
||||||
man/man8
|
man/man8
|
||||||
release
|
release
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
.PHONY: dbuild man \
|
.PHONY: all dbuild man \
|
||||||
localtest localunittest localintegration \
|
localtest localunittest localintegration \
|
||||||
test unittest integration
|
test unittest integration
|
||||||
|
|
||||||
|
SOURCES := $(shell find . 2>&1 | grep -E '.*\.(c|h|go)$$')
|
||||||
PREFIX := $(DESTDIR)/usr/local
|
PREFIX := $(DESTDIR)/usr/local
|
||||||
BINDIR := $(PREFIX)/sbin
|
BINDIR := $(PREFIX)/sbin
|
||||||
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null)
|
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)
|
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 .
|
go build -i -ldflags "-X main.gitCommit=${COMMIT} -X main.version=${VERSION}" -tags "$(BUILDTAGS)" -o runc .
|
||||||
|
|
||||||
static: $(RUNC_LINK)
|
all: runc recvtty
|
||||||
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 .
|
|
||||||
|
|
||||||
release: $(RUNC_LINK)
|
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) | $(RUNC_LINK)
|
||||||
@flag_list=(seccomp selinux apparmor static ambient); \
|
@flag_list=(seccomp selinux apparmor static ambient); \
|
||||||
unset expression; \
|
unset expression; \
|
||||||
for flag in "$${flag_list[@]}"; do \
|
for flag in "$${flag_list[@]}"; do \
|
||||||
@@ -62,7 +73,7 @@ $(RUNC_LINK):
|
|||||||
ln -sfn $(CURDIR) $(RUNC_LINK)
|
ln -sfn $(CURDIR) $(RUNC_LINK)
|
||||||
|
|
||||||
dbuild: runcimage
|
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:
|
lint:
|
||||||
go vet ./...
|
go vet ./...
|
||||||
@@ -113,6 +124,7 @@ uninstall-man:
|
|||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f runc
|
rm -f runc
|
||||||
|
rm -f contrib/cmd/recvtty/recvtty
|
||||||
rm -f $(RUNC_LINK)
|
rm -f $(RUNC_LINK)
|
||||||
rm -rf $(GOPATH)/pkg
|
rm -rf $(GOPATH)/pkg
|
||||||
rm -rf $(RELEASE_DIR)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user