Skip to content

Commit d270940

Browse files
authored
Merge pull request #1356 from crosbymichael/console-socket
Add separate console socket
2 parents c266f14 + 957ef9c commit d270940

File tree

16 files changed

+178
-290
lines changed

16 files changed

+178
-290
lines changed

contrib/cmd/recvtty/recvtty.go

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import (
2424
"os"
2525
"strings"
2626

27-
"github.com/opencontainers/runc/libcontainer"
2827
"github.com/opencontainers/runc/libcontainer/utils"
2928
"github.com/urfave/cli"
3029
)
@@ -102,13 +101,6 @@ func handleSingle(path string) error {
102101
return err
103102
}
104103

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-
112104
// Copy from our stdio to the master fd.
113105
quitChan := make(chan struct{})
114106
go func() {
@@ -163,13 +155,6 @@ func handleNull(path string) error {
163155
return
164156
}
165157

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-
173158
// Just do a dumb copy to /dev/null.
174159
devnull, err := os.OpenFile("/dev/null", os.O_RDWR, 0)
175160
if err != nil {

libcontainer/console.go

Lines changed: 0 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package libcontainer
22

33
import (
4-
"encoding/json"
5-
"fmt"
64
"io"
75
"os"
86
)
@@ -17,57 +15,3 @@ type Console interface {
1715
// Fd returns the fd for the master of the pty.
1816
File() *os.File
1917
}
20-
21-
const (
22-
TerminalInfoVersion uint32 = 201610041
23-
TerminalInfoType uint8 = 'T'
24-
)
25-
26-
// TerminalInfo is the structure which is passed as the non-ancillary data
27-
// in the sendmsg(2) call when runc is run with --console-socket. It
28-
// contains some information about the container which the console master fd
29-
// relates to (to allow for consumers to use a single unix socket to handle
30-
// multiple containers). This structure will probably move to runtime-spec
31-
// at some point. But for now it lies in libcontainer.
32-
type TerminalInfo struct {
33-
// Version of the API.
34-
Version uint32 `json:"version"`
35-
36-
// Type of message (future proofing).
37-
Type uint8 `json:"type"`
38-
39-
// Container contains the ID of the container.
40-
ContainerID string `json:"container_id"`
41-
}
42-
43-
func (ti *TerminalInfo) String() string {
44-
encoded, err := json.Marshal(*ti)
45-
if err != nil {
46-
panic(err)
47-
}
48-
return string(encoded)
49-
}
50-
51-
func NewTerminalInfo(containerId string) *TerminalInfo {
52-
return &TerminalInfo{
53-
Version: TerminalInfoVersion,
54-
Type: TerminalInfoType,
55-
ContainerID: containerId,
56-
}
57-
}
58-
59-
func GetTerminalInfo(encoded string) (*TerminalInfo, error) {
60-
ti := new(TerminalInfo)
61-
if err := json.Unmarshal([]byte(encoded), ti); err != nil {
62-
return nil, err
63-
}
64-
65-
if ti.Type != TerminalInfoType {
66-
return nil, fmt.Errorf("terminal info: incorrect type in payload (%q): %q", TerminalInfoType, ti.Type)
67-
}
68-
if ti.Version != TerminalInfoVersion {
69-
return nil, fmt.Errorf("terminal info: incorrect version in payload (%q): %q", TerminalInfoVersion, ti.Version)
70-
}
71-
72-
return ti, nil
73-
}

libcontainer/console_linux.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ import (
77
"unsafe"
88
)
99

10+
func ConsoleFromFile(f *os.File) Console {
11+
return &linuxConsole{
12+
master: f,
13+
}
14+
}
15+
1016
// newConsole returns an initialized console that can be used within a container by copying bytes
1117
// from the master side to the slave that is attached as the tty for the container's init process.
1218
func newConsole() (Console, error) {

libcontainer/container_linux.go

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ func (c *linuxContainer) deleteExecFifo() {
335335
}
336336

337337
func (c *linuxContainer) newParentProcess(p *Process, doInit bool) (parentProcess, error) {
338-
parentPipe, childPipe, err := newPipe()
338+
parentPipe, childPipe, err := utils.NewSockPair("init")
339339
if err != nil {
340340
return nil, newSystemErrorWithCause(err, "creating new init pipe")
341341
}
@@ -370,9 +370,17 @@ func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec.
370370
if cmd.SysProcAttr == nil {
371371
cmd.SysProcAttr = &syscall.SysProcAttr{}
372372
}
373-
cmd.ExtraFiles = append(p.ExtraFiles, childPipe)
373+
cmd.ExtraFiles = append(cmd.ExtraFiles, p.ExtraFiles...)
374+
if p.ConsoleSocket != nil {
375+
cmd.ExtraFiles = append(cmd.ExtraFiles, p.ConsoleSocket)
376+
cmd.Env = append(cmd.Env,
377+
fmt.Sprintf("_LIBCONTAINER_CONSOLE=%d", stdioFdCount+len(cmd.ExtraFiles)-1),
378+
)
379+
}
380+
cmd.ExtraFiles = append(cmd.ExtraFiles, childPipe)
374381
cmd.Env = append(cmd.Env,
375-
fmt.Sprintf("_LIBCONTAINER_INITPIPE=%d", stdioFdCount+len(cmd.ExtraFiles)-1))
382+
fmt.Sprintf("_LIBCONTAINER_INITPIPE=%d", stdioFdCount+len(cmd.ExtraFiles)-1),
383+
)
376384
// NOTE: when running a container with no PID namespace and the parent process spawning the container is
377385
// PID1 the pdeathsig is being delivered to the container's init process by the kernel for some reason
378386
// even with the parent still running.
@@ -395,7 +403,6 @@ func (c *linuxContainer) newInitProcess(p *Process, cmd *exec.Cmd, parentPipe, c
395403
if err != nil {
396404
return nil, err
397405
}
398-
p.consoleChan = make(chan *os.File, 1)
399406
return &initProcess{
400407
cmd: cmd,
401408
childPipe: childPipe,
@@ -422,8 +429,6 @@ func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe,
422429
if err != nil {
423430
return nil, err
424431
}
425-
// TODO: set on container for process management
426-
p.consoleChan = make(chan *os.File, 1)
427432
return &setnsProcess{
428433
cmd: cmd,
429434
cgroupPaths: c.cgroupManager.GetPaths(),
@@ -463,28 +468,10 @@ func (c *linuxContainer) newInitConfig(process *Process) *initConfig {
463468
if len(process.Rlimits) > 0 {
464469
cfg.Rlimits = process.Rlimits
465470
}
466-
/*
467-
* TODO: This should not be automatically computed. We should implement
468-
* this as a field in libcontainer.Process, and then we only dup the
469-
* new console over the file descriptors which were not explicitly
470-
* set with process.Std{in,out,err}. The reason I've left this as-is
471-
* is because the GetConsole() interface is new, there's no need to
472-
* polish this interface right now.
473-
*/
474-
if process.Stdin == nil && process.Stdout == nil && process.Stderr == nil {
475-
cfg.CreateConsole = true
476-
}
471+
cfg.CreateConsole = process.ConsoleSocket != nil
477472
return cfg
478473
}
479474

480-
func newPipe() (parent *os.File, child *os.File, err error) {
481-
fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0)
482-
if err != nil {
483-
return nil, nil, err
484-
}
485-
return os.NewFile(uintptr(fds[1]), "parent"), os.NewFile(uintptr(fds[0]), "child"), nil
486-
}
487-
488475
func (c *linuxContainer) Destroy() error {
489476
c.m.Lock()
490477
defer c.m.Unlock()

libcontainer/factory_linux.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -222,8 +222,10 @@ func (l *LinuxFactory) Type() string {
222222
func (l *LinuxFactory) StartInitialization() (err error) {
223223
var (
224224
pipefd, rootfd int
225+
consoleSocket *os.File
225226
envInitPipe = os.Getenv("_LIBCONTAINER_INITPIPE")
226227
envStateDir = os.Getenv("_LIBCONTAINER_STATEDIR")
228+
envConsole = os.Getenv("_LIBCONTAINER_CONSOLE")
227229
)
228230

229231
// Get the INITPIPE.
@@ -241,12 +243,20 @@ func (l *LinuxFactory) StartInitialization() (err error) {
241243
// Only init processes have STATEDIR.
242244
rootfd = -1
243245
if it == initStandard {
244-
rootfd, err = strconv.Atoi(envStateDir)
245-
if err != nil {
246+
if rootfd, err = strconv.Atoi(envStateDir); err != nil {
246247
return fmt.Errorf("unable to convert _LIBCONTAINER_STATEDIR=%s to int: %s", envStateDir, err)
247248
}
248249
}
249250

251+
if envConsole != "" {
252+
console, err := strconv.Atoi(envConsole)
253+
if err != nil {
254+
return fmt.Errorf("unable to convert _LIBCONTAINER_CONSOLE=%s to int: %s", envConsole, err)
255+
}
256+
consoleSocket = os.NewFile(uintptr(console), "console-socket")
257+
defer consoleSocket.Close()
258+
}
259+
250260
// clear the current process's environment to clean any libcontainer
251261
// specific env vars.
252262
os.Clearenv()
@@ -269,7 +279,7 @@ func (l *LinuxFactory) StartInitialization() (err error) {
269279
}
270280
}()
271281

272-
i, err := newContainerInit(it, pipe, rootfd)
282+
i, err := newContainerInit(it, pipe, consoleSocket, rootfd)
273283
if err != nil {
274284
return err
275285
}

libcontainer/init_linux.go

Lines changed: 12 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ type initer interface {
6666
Init() error
6767
}
6868

69-
func newContainerInit(t initType, pipe *os.File, stateDirFD int) (initer, error) {
69+
func newContainerInit(t initType, pipe *os.File, consoleSocket *os.File, stateDirFD int) (initer, error) {
7070
var config *initConfig
7171
if err := json.NewDecoder(pipe).Decode(&config); err != nil {
7272
return nil, err
@@ -77,15 +77,17 @@ func newContainerInit(t initType, pipe *os.File, stateDirFD int) (initer, error)
7777
switch t {
7878
case initSetns:
7979
return &linuxSetnsInit{
80-
pipe: pipe,
81-
config: config,
80+
pipe: pipe,
81+
consoleSocket: consoleSocket,
82+
config: config,
8283
}, nil
8384
case initStandard:
8485
return &linuxStandardInit{
85-
pipe: pipe,
86-
parentPid: syscall.Getppid(),
87-
config: config,
88-
stateDirFD: stateDirFD,
86+
pipe: pipe,
87+
consoleSocket: consoleSocket,
88+
parentPid: syscall.Getppid(),
89+
config: config,
90+
stateDirFD: stateDirFD,
8991
}, nil
9092
}
9193
return nil, fmt.Errorf("unknown init type %q", t)
@@ -155,7 +157,8 @@ func finalizeNamespace(config *initConfig) error {
155157
// consoles are scoped to a container properly (see runc#814 and the many
156158
// issues related to that). This has to be run *after* we've pivoted to the new
157159
// rootfs (and the users' configuration is entirely set up).
158-
func setupConsole(pipe *os.File, config *initConfig, mount bool) error {
160+
func setupConsole(socket *os.File, config *initConfig, mount bool) error {
161+
defer socket.Close()
159162
// At this point, /dev/ptmx points to something that we would expect. We
160163
// used to change the owner of the slave path, but since the /dev/pts mount
161164
// can have gid=X set (at the users' option). So touching the owner of the
@@ -174,37 +177,16 @@ func setupConsole(pipe *os.File, config *initConfig, mount bool) error {
174177
if !ok {
175178
return fmt.Errorf("failed to cast console to *linuxConsole")
176179
}
177-
178180
// Mount the console inside our rootfs.
179181
if mount {
180182
if err := linuxConsole.mount(); err != nil {
181183
return err
182184
}
183185
}
184-
185-
if err := writeSync(pipe, procConsole); err != nil {
186-
return err
187-
}
188-
189-
// We need to have a two-way synchronisation here. Though it might seem
190-
// pointless, it's important to make sure that the sendmsg(2) payload
191-
// doesn't get swallowed by an out-of-place read(2) [which happens if the
192-
// syscalls get reordered so that sendmsg(2) is before the other side's
193-
// read(2) of procConsole].
194-
if err := readSync(pipe, procConsoleReq); err != nil {
195-
return err
196-
}
197-
198186
// While we can access console.master, using the API is a good idea.
199-
if err := utils.SendFd(pipe, linuxConsole.File()); err != nil {
200-
return err
201-
}
202-
203-
// Make sure the other side received the fd.
204-
if err := readSync(pipe, procConsoleAck); err != nil {
187+
if err := utils.SendFd(socket, linuxConsole.File()); err != nil {
205188
return err
206189
}
207-
208190
// Now, dup over all the things.
209191
return linuxConsole.dupStdio()
210192
}

libcontainer/integration/execin_test.go

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
"github.com/opencontainers/runc/libcontainer"
1515
"github.com/opencontainers/runc/libcontainer/configs"
16+
"github.com/opencontainers/runc/libcontainer/utils"
1617
)
1718

1819
func TestExecIn(t *testing.T) {
@@ -279,9 +280,36 @@ func TestExecInTTY(t *testing.T) {
279280
Args: []string{"ps"},
280281
Env: standardEnvironment,
281282
}
283+
parent, child, err := utils.NewSockPair("console")
284+
if err != nil {
285+
ok(t, err)
286+
}
287+
defer parent.Close()
288+
defer child.Close()
289+
ps.ConsoleSocket = child
290+
type cdata struct {
291+
c libcontainer.Console
292+
err error
293+
}
294+
dc := make(chan *cdata, 1)
295+
go func() {
296+
f, err := utils.RecvFd(parent)
297+
if err != nil {
298+
dc <- &cdata{
299+
err: err,
300+
}
301+
}
302+
dc <- &cdata{
303+
c: libcontainer.ConsoleFromFile(f),
304+
}
305+
}()
282306
err = container.Run(ps)
283307
ok(t, err)
284-
console, err := ps.GetConsole()
308+
data := <-dc
309+
if data.err != nil {
310+
ok(t, data.err)
311+
}
312+
console := data.c
285313
copy := make(chan struct{})
286314
go func() {
287315
io.Copy(&stdout, console)

libcontainer/process.go

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,6 @@ type Process struct {
4747
// ExtraFiles specifies additional open files to be inherited by the container
4848
ExtraFiles []*os.File
4949

50-
// consoleChan provides the masterfd console.
51-
// TODO: Make this persistent in Process.
52-
consoleChan chan *os.File
53-
5450
// Capabilities specify the capabilities to keep when executing the process inside the container
5551
// All capabilities not specified will be dropped from the processes capability mask
5652
Capabilities *configs.Capabilities
@@ -69,6 +65,9 @@ type Process struct {
6965
// If Rlimits are not set, the container will inherit rlimits from the parent process
7066
Rlimits []configs.Rlimit
7167

68+
// ConsoleSocket provides the masterfd console.
69+
ConsoleSocket *os.File
70+
7271
ops processOperations
7372
}
7473

@@ -105,15 +104,3 @@ type IO struct {
105104
Stdout io.ReadCloser
106105
Stderr io.ReadCloser
107106
}
108-
109-
func (p *Process) GetConsole() (Console, error) {
110-
consoleFd, ok := <-p.consoleChan
111-
if !ok {
112-
return nil, fmt.Errorf("failed to get console from process")
113-
}
114-
115-
// TODO: Fix this so that it used the console API.
116-
return &linuxConsole{
117-
master: consoleFd,
118-
}, nil
119-
}

0 commit comments

Comments
 (0)