Skip to content

Commit 34f23cb

Browse files
author
Mrunal Patel
authored
Merge pull request #1018 from cyphar/console-rewrite
Consoles, consoles, consoles.
2 parents 47ea5c7 + b0fc85e commit 34f23cb

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1306
-350
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
vendor/pkg
22
/runc
3+
contrib/cmd/recvtty/recvtty
34
Godeps/_workspace/src/github.com/opencontainers/runc
45
man/man8
56
release

Dockerfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ RUN mkdir -p /go/src/github.com/mvdan \
4848
# setup a playground for us to spawn containers in
4949
ENV ROOTFS /busybox
5050
RUN mkdir -p ${ROOTFS} \
51-
&& curl -o- -sSL 'https://github.com/jpetazzo/docker-busybox/raw/buildroot-2014.11/rootfs.tar' | tar -C ${ROOTFS} -xf -
51+
&& curl -o- -sSL 'https://github.com/docker-library/busybox/raw/a0558a9006ce0dd6f6ec5d56cfd3f32ebeeb815f/glibc/busybox.tar.xz' | tar xfJC - ${ROOTFS}
52+
5253

5354
COPY script/tmpmount /
5455
WORKDIR /go/src/github.com/opencontainers/runc

Makefile

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
.PHONY: dbuild man \
1+
.PHONY: all shell dbuild man \
22
localtest localunittest localintegration \
33
test unittest integration
44

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

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

28-
all: $(RUNC_LINK)
29+
.DEFAULT: runc
30+
31+
runc: $(SOURCES) | $(RUNC_LINK)
2932
go build -i -ldflags "-X main.gitCommit=${COMMIT} -X main.version=${VERSION}" -tags "$(BUILDTAGS)" -o runc .
3033

31-
static: $(RUNC_LINK)
34+
all: runc recvtty
35+
36+
recvtty: contrib/cmd/recvtty/recvtty
37+
38+
contrib/cmd/recvtty/recvtty: $(SOURCES) | $(RUNC_LINK)
39+
go build -i -ldflags "-X main.gitCommit=${COMMIT} -X main.version=${VERSION}" -tags "$(BUILDTAGS)" -o contrib/cmd/recvtty/recvtty ./contrib/cmd/recvtty
40+
41+
static: $(SOURCES) | $(RUNC_LINK)
3242
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 .
43+
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
3344

34-
release: $(RUNC_LINK)
45+
release: $(RUNC_LINK) | $(RUNC_LINK)
3546
@flag_list=(seccomp selinux apparmor static ambient); \
3647
unset expression; \
3748
for flag in "$${flag_list[@]}"; do \
@@ -61,7 +72,7 @@ $(RUNC_LINK):
6172
ln -sfn $(CURDIR) $(RUNC_LINK)
6273

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

6677
lint:
6778
go vet ./...
@@ -91,6 +102,9 @@ integration: runcimage
91102
localintegration: all
92103
bats -t tests/integration${TESTFLAGS}
93104

105+
shell: all
106+
docker run -e TESTFLAGS -ti --privileged --rm -v $(CURDIR):/go/src/$(PROJECT) $(RUNC_IMAGE) bash
107+
94108
install:
95109
install -D -m0755 runc $(BINDIR)/runc
96110

@@ -112,6 +126,7 @@ uninstall-man:
112126

113127
clean:
114128
rm -f runc
129+
rm -f contrib/cmd/recvtty/recvtty
115130
rm -f $(RUNC_LINK)
116131
rm -rf $(GOPATH)/pkg
117132
rm -rf $(RELEASE_DIR)

contrib/cmd/recvtty/recvtty.go

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
/*
2+
* Copyright 2016 SUSE LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"fmt"
21+
"io"
22+
"io/ioutil"
23+
"net"
24+
"os"
25+
"strings"
26+
27+
"github.com/opencontainers/runc/libcontainer"
28+
"github.com/opencontainers/runc/libcontainer/utils"
29+
"github.com/urfave/cli"
30+
)
31+
32+
// version will be populated by the Makefile, read from
33+
// VERSION file of the source code.
34+
var version = ""
35+
36+
// gitCommit will be the hash that the binary was built from
37+
// and will be populated by the Makefile
38+
var gitCommit = ""
39+
40+
const (
41+
usage = `Open Container Initiative contrib/cmd/recvtty
42+
43+
recvtty is a reference implementation of a consumer of runC's --console-socket
44+
API. It has two main modes of operation:
45+
46+
* single: Only permit one terminal to be sent to the socket, which is
47+
then hooked up to the stdio of the recvtty process. This is useful
48+
for rudimentary shell management of a container.
49+
50+
* null: Permit as many terminals to be sent to the socket, but they
51+
are read to /dev/null. This is used for testing, and imitates the
52+
old runC API's --console=/dev/pts/ptmx hack which would allow for a
53+
similar trick. This is probably not what you want to use, unless
54+
you're doing something like our bats integration tests.
55+
56+
To use recvtty, just specify a socket path at which you want to receive
57+
terminals:
58+
59+
$ recvtty [--mode <single|null>] socket.sock
60+
`
61+
)
62+
63+
func bail(err error) {
64+
fmt.Fprintf(os.Stderr, "[recvtty] fatal error: %v\n", err)
65+
os.Exit(1)
66+
}
67+
68+
func handleSingle(path string) error {
69+
// Open a socket.
70+
ln, err := net.Listen("unix", path)
71+
if err != nil {
72+
return err
73+
}
74+
defer ln.Close()
75+
76+
// We only accept a single connection, since we can only really have
77+
// one reader for os.Stdin. Plus this is all a PoC.
78+
conn, err := ln.Accept()
79+
if err != nil {
80+
return err
81+
}
82+
defer conn.Close()
83+
84+
// Close ln, to allow for other instances to take over.
85+
ln.Close()
86+
87+
// Get the fd of the connection.
88+
unixconn, ok := conn.(*net.UnixConn)
89+
if !ok {
90+
return fmt.Errorf("failed to cast to unixconn")
91+
}
92+
93+
socket, err := unixconn.File()
94+
if err != nil {
95+
return err
96+
}
97+
defer socket.Close()
98+
99+
// Get the master file descriptor from runC.
100+
master, err := utils.RecvFd(socket)
101+
if err != nil {
102+
return err
103+
}
104+
105+
// Print the file descriptor tag.
106+
ti, err := libcontainer.GetTerminalInfo(master.Name())
107+
if err != nil {
108+
return err
109+
}
110+
fmt.Printf("[recvtty] received masterfd: container '%s'\n", ti.ContainerID)
111+
112+
// Copy from our stdio to the master fd.
113+
quitChan := make(chan struct{})
114+
go func() {
115+
io.Copy(os.Stdout, master)
116+
quitChan <- struct{}{}
117+
}()
118+
go func() {
119+
io.Copy(master, os.Stdin)
120+
quitChan <- struct{}{}
121+
}()
122+
123+
// Only close the master fd once we've stopped copying.
124+
<-quitChan
125+
master.Close()
126+
return nil
127+
}
128+
129+
func handleNull(path string) error {
130+
// Open a socket.
131+
ln, err := net.Listen("unix", path)
132+
if err != nil {
133+
return err
134+
}
135+
defer ln.Close()
136+
137+
// As opposed to handleSingle we accept as many connections as we get, but
138+
// we don't interact with Stdin at all (and we copy stdout to /dev/null).
139+
for {
140+
conn, err := ln.Accept()
141+
if err != nil {
142+
return err
143+
}
144+
go func(conn net.Conn) {
145+
// Don't leave references lying around.
146+
defer conn.Close()
147+
148+
// Get the fd of the connection.
149+
unixconn, ok := conn.(*net.UnixConn)
150+
if !ok {
151+
return
152+
}
153+
154+
socket, err := unixconn.File()
155+
if err != nil {
156+
return
157+
}
158+
defer socket.Close()
159+
160+
// Get the master file descriptor from runC.
161+
master, err := utils.RecvFd(socket)
162+
if err != nil {
163+
return
164+
}
165+
166+
// Print the file descriptor tag.
167+
ti, err := libcontainer.GetTerminalInfo(master.Name())
168+
if err != nil {
169+
bail(err)
170+
}
171+
fmt.Printf("[recvtty] received masterfd: container '%s'\n", ti.ContainerID)
172+
173+
// Just do a dumb copy to /dev/null.
174+
devnull, err := os.OpenFile("/dev/null", os.O_RDWR, 0)
175+
if err != nil {
176+
// TODO: Handle this nicely.
177+
return
178+
}
179+
180+
io.Copy(devnull, master)
181+
devnull.Close()
182+
}(conn)
183+
}
184+
}
185+
186+
func main() {
187+
app := cli.NewApp()
188+
app.Name = "recvtty"
189+
app.Usage = usage
190+
191+
// Set version to be the same as runC.
192+
var v []string
193+
if version != "" {
194+
v = append(v, version)
195+
}
196+
if gitCommit != "" {
197+
v = append(v, fmt.Sprintf("commit: %s", gitCommit))
198+
}
199+
app.Version = strings.Join(v, "\n")
200+
201+
// Set the flags.
202+
app.Flags = []cli.Flag{
203+
cli.StringFlag{
204+
Name: "mode, m",
205+
Value: "single",
206+
Usage: "Mode of operation (single or null)",
207+
},
208+
cli.StringFlag{
209+
Name: "pid-file",
210+
Value: "",
211+
Usage: "Path to write daemon process ID to",
212+
},
213+
}
214+
215+
app.Action = func(ctx *cli.Context) error {
216+
args := ctx.Args()
217+
if len(args) != 1 {
218+
return fmt.Errorf("need to specify a single socket path")
219+
}
220+
path := ctx.Args()[0]
221+
222+
pidPath := ctx.String("pid-file")
223+
if pidPath != "" {
224+
pid := fmt.Sprintf("%d\n", os.Getpid())
225+
if err := ioutil.WriteFile(pidPath, []byte(pid), 0644); err != nil {
226+
return err
227+
}
228+
}
229+
230+
switch ctx.String("mode") {
231+
case "single":
232+
if err := handleSingle(path); err != nil {
233+
return err
234+
}
235+
case "null":
236+
if err := handleNull(path); err != nil {
237+
return err
238+
}
239+
default:
240+
return fmt.Errorf("need to select a valid mode: %s", ctx.String("mode"))
241+
}
242+
return nil
243+
}
244+
if err := app.Run(os.Args); err != nil {
245+
bail(err)
246+
}
247+
}

create.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ command(s) that get executed on start, edit the args parameter of the spec. See
3030
Usage: `path to the root of the bundle directory, defaults to the current directory`,
3131
},
3232
cli.StringFlag{
33-
Name: "console",
33+
Name: "console-socket",
3434
Value: "",
35-
Usage: "specify the pty slave path for use with the container",
35+
Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal",
3636
},
3737
cli.StringFlag{
3838
Name: "pid-file",

exec.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@ Where "<container-id>" is the name for the instance of the container and
2626
EXAMPLE:
2727
For example, if the container is configured to run the linux ps command the
2828
following will output a list of processes running in the container:
29-
29+
3030
# runc exec <container-id> ps`,
3131
Flags: []cli.Flag{
3232
cli.StringFlag{
33-
Name: "console",
34-
Usage: "specify the pty slave path for use with the container",
33+
Name: "console-socket",
34+
Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal",
3535
},
3636
cli.StringFlag{
3737
Name: "cwd",
@@ -131,7 +131,7 @@ func execProcess(context *cli.Context) (int, error) {
131131
enableSubreaper: false,
132132
shouldDestroy: false,
133133
container: container,
134-
console: context.String("console"),
134+
consoleSocket: context.String("console-socket"),
135135
detach: detach,
136136
pidFile: context.String("pid-file"),
137137
}

0 commit comments

Comments
 (0)