mirror of
https://github.com/opencontainers/runc.git
synced 2025-09-27 03:46:19 +08:00

This rather naively fixes an error observed where a processes stdio streams are not written to when there is an error upon starting up the process, such as when the executable doesn't exist within the container's rootfs. Before the "fix", when an error occurred on start, `terminate` is called immediately, which calls `cmd.Process.Kill()`, then calling `Wait()` on the process. In some cases when this `Kill` is called the stdio stream have not yet been written to, causing non-deterministic output. The error itself is properly preserved but users attached to the process will not see this error. With the fix it is just calling `Wait()` when an error occurs rather than trying to `Kill()` the process first. This seems to preserve stdio. Signed-off-by: Brian Goff <cpuguy83@gmail.com>
389 lines
8.4 KiB
Go
389 lines
8.4 KiB
Go
package integration
|
|
|
|
import (
|
|
"bytes"
|
|
"io"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/opencontainers/runc/libcontainer"
|
|
)
|
|
|
|
func TestExecIn(t *testing.T) {
|
|
if testing.Short() {
|
|
return
|
|
}
|
|
rootfs, err := newRootfs()
|
|
ok(t, err)
|
|
defer remove(rootfs)
|
|
config := newTemplateConfig(rootfs)
|
|
container, err := newContainer(config)
|
|
ok(t, err)
|
|
defer container.Destroy()
|
|
|
|
// Execute a first process in the container
|
|
stdinR, stdinW, err := os.Pipe()
|
|
ok(t, err)
|
|
process := &libcontainer.Process{
|
|
Args: []string{"cat"},
|
|
Env: standardEnvironment,
|
|
Stdin: stdinR,
|
|
}
|
|
err = container.Start(process)
|
|
stdinR.Close()
|
|
defer stdinW.Close()
|
|
ok(t, err)
|
|
|
|
buffers := newStdBuffers()
|
|
ps := &libcontainer.Process{
|
|
Args: []string{"ps"},
|
|
Env: standardEnvironment,
|
|
Stdin: buffers.Stdin,
|
|
Stdout: buffers.Stdout,
|
|
Stderr: buffers.Stderr,
|
|
}
|
|
|
|
err = container.Start(ps)
|
|
ok(t, err)
|
|
waitProcess(ps, t)
|
|
stdinW.Close()
|
|
waitProcess(process, t)
|
|
|
|
out := buffers.Stdout.String()
|
|
if !strings.Contains(out, "cat") || !strings.Contains(out, "ps") {
|
|
t.Fatalf("unexpected running process, output %q", out)
|
|
}
|
|
}
|
|
|
|
func TestExecInRlimit(t *testing.T) {
|
|
if testing.Short() {
|
|
return
|
|
}
|
|
rootfs, err := newRootfs()
|
|
ok(t, err)
|
|
defer remove(rootfs)
|
|
config := newTemplateConfig(rootfs)
|
|
container, err := newContainer(config)
|
|
ok(t, err)
|
|
defer container.Destroy()
|
|
|
|
stdinR, stdinW, err := os.Pipe()
|
|
ok(t, err)
|
|
process := &libcontainer.Process{
|
|
Args: []string{"cat"},
|
|
Env: standardEnvironment,
|
|
Stdin: stdinR,
|
|
}
|
|
err = container.Start(process)
|
|
stdinR.Close()
|
|
defer stdinW.Close()
|
|
ok(t, err)
|
|
|
|
buffers := newStdBuffers()
|
|
ps := &libcontainer.Process{
|
|
Args: []string{"/bin/sh", "-c", "ulimit -n"},
|
|
Env: standardEnvironment,
|
|
Stdin: buffers.Stdin,
|
|
Stdout: buffers.Stdout,
|
|
Stderr: buffers.Stderr,
|
|
}
|
|
err = container.Start(ps)
|
|
ok(t, err)
|
|
waitProcess(ps, t)
|
|
|
|
stdinW.Close()
|
|
waitProcess(process, t)
|
|
|
|
out := buffers.Stdout.String()
|
|
if limit := strings.TrimSpace(out); limit != "1025" {
|
|
t.Fatalf("expected rlimit to be 1025, got %s", limit)
|
|
}
|
|
}
|
|
|
|
func TestExecInError(t *testing.T) {
|
|
if testing.Short() {
|
|
return
|
|
}
|
|
rootfs, err := newRootfs()
|
|
ok(t, err)
|
|
defer remove(rootfs)
|
|
config := newTemplateConfig(rootfs)
|
|
container, err := newContainer(config)
|
|
ok(t, err)
|
|
defer container.Destroy()
|
|
|
|
// Execute a first process in the container
|
|
stdinR, stdinW, err := os.Pipe()
|
|
ok(t, err)
|
|
process := &libcontainer.Process{
|
|
Args: []string{"cat"},
|
|
Env: standardEnvironment,
|
|
Stdin: stdinR,
|
|
}
|
|
err = container.Start(process)
|
|
stdinR.Close()
|
|
defer func() {
|
|
stdinW.Close()
|
|
if _, err := process.Wait(); err != nil {
|
|
t.Log(err)
|
|
}
|
|
}()
|
|
ok(t, err)
|
|
|
|
for i := 0; i < 42; i++ {
|
|
var out bytes.Buffer
|
|
unexistent := &libcontainer.Process{
|
|
Args: []string{"unexistent"},
|
|
Env: standardEnvironment,
|
|
Stdout: &out,
|
|
}
|
|
err = container.Start(unexistent)
|
|
if err == nil {
|
|
t.Fatal("Should be an error")
|
|
}
|
|
if !strings.Contains(err.Error(), "executable file not found") {
|
|
t.Fatalf("Should be error about not found executable, got %s", err)
|
|
}
|
|
if !bytes.Contains(out.Bytes(), []byte("executable file not found")) {
|
|
t.Fatalf("executable file not found error not delivered to stdio:\n%s", out.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestExecInTTY(t *testing.T) {
|
|
if testing.Short() {
|
|
return
|
|
}
|
|
rootfs, err := newRootfs()
|
|
ok(t, err)
|
|
defer remove(rootfs)
|
|
config := newTemplateConfig(rootfs)
|
|
container, err := newContainer(config)
|
|
ok(t, err)
|
|
defer container.Destroy()
|
|
|
|
// Execute a first process in the container
|
|
stdinR, stdinW, err := os.Pipe()
|
|
ok(t, err)
|
|
process := &libcontainer.Process{
|
|
Args: []string{"cat"},
|
|
Env: standardEnvironment,
|
|
Stdin: stdinR,
|
|
}
|
|
err = container.Start(process)
|
|
stdinR.Close()
|
|
defer stdinW.Close()
|
|
ok(t, err)
|
|
|
|
var stdout bytes.Buffer
|
|
ps := &libcontainer.Process{
|
|
Args: []string{"ps"},
|
|
Env: standardEnvironment,
|
|
}
|
|
console, err := ps.NewConsole(0)
|
|
copy := make(chan struct{})
|
|
go func() {
|
|
io.Copy(&stdout, console)
|
|
close(copy)
|
|
}()
|
|
ok(t, err)
|
|
err = container.Start(ps)
|
|
ok(t, err)
|
|
select {
|
|
case <-time.After(5 * time.Second):
|
|
t.Fatal("Waiting for copy timed out")
|
|
case <-copy:
|
|
}
|
|
waitProcess(ps, t)
|
|
|
|
stdinW.Close()
|
|
waitProcess(process, t)
|
|
|
|
out := stdout.String()
|
|
if !strings.Contains(out, "cat") || !strings.Contains(string(out), "ps") {
|
|
t.Fatalf("unexpected running process, output %q", out)
|
|
}
|
|
}
|
|
|
|
func TestExecInEnvironment(t *testing.T) {
|
|
if testing.Short() {
|
|
return
|
|
}
|
|
rootfs, err := newRootfs()
|
|
ok(t, err)
|
|
defer remove(rootfs)
|
|
config := newTemplateConfig(rootfs)
|
|
container, err := newContainer(config)
|
|
ok(t, err)
|
|
defer container.Destroy()
|
|
|
|
// Execute a first process in the container
|
|
stdinR, stdinW, err := os.Pipe()
|
|
ok(t, err)
|
|
process := &libcontainer.Process{
|
|
Args: []string{"cat"},
|
|
Env: standardEnvironment,
|
|
Stdin: stdinR,
|
|
}
|
|
err = container.Start(process)
|
|
stdinR.Close()
|
|
defer stdinW.Close()
|
|
ok(t, err)
|
|
|
|
buffers := newStdBuffers()
|
|
process2 := &libcontainer.Process{
|
|
Args: []string{"env"},
|
|
Env: []string{
|
|
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
|
|
"DEBUG=true",
|
|
"DEBUG=false",
|
|
"ENV=test",
|
|
},
|
|
Stdin: buffers.Stdin,
|
|
Stdout: buffers.Stdout,
|
|
Stderr: buffers.Stderr,
|
|
}
|
|
err = container.Start(process2)
|
|
ok(t, err)
|
|
waitProcess(process2, t)
|
|
|
|
stdinW.Close()
|
|
waitProcess(process, t)
|
|
|
|
out := buffers.Stdout.String()
|
|
// check execin's process environment
|
|
if !strings.Contains(out, "DEBUG=false") ||
|
|
!strings.Contains(out, "ENV=test") ||
|
|
!strings.Contains(out, "HOME=/root") ||
|
|
!strings.Contains(out, "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin") ||
|
|
strings.Contains(out, "DEBUG=true") {
|
|
t.Fatalf("unexpected running process, output %q", out)
|
|
}
|
|
}
|
|
|
|
func TestExecinPassExtraFiles(t *testing.T) {
|
|
if testing.Short() {
|
|
return
|
|
}
|
|
rootfs, err := newRootfs()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer remove(rootfs)
|
|
config := newTemplateConfig(rootfs)
|
|
container, err := newContainer(config)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer container.Destroy()
|
|
|
|
// Execute a first process in the container
|
|
stdinR, stdinW, err := os.Pipe()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
process := &libcontainer.Process{
|
|
Args: []string{"cat"},
|
|
Env: standardEnvironment,
|
|
Stdin: stdinR,
|
|
}
|
|
err = container.Start(process)
|
|
stdinR.Close()
|
|
defer stdinW.Close()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var stdout bytes.Buffer
|
|
pipeout1, pipein1, err := os.Pipe()
|
|
pipeout2, pipein2, err := os.Pipe()
|
|
inprocess := &libcontainer.Process{
|
|
Args: []string{"sh", "-c", "cd /proc/$$/fd; echo -n *; echo -n 1 >3; echo -n 2 >4"},
|
|
Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"},
|
|
ExtraFiles: []*os.File{pipein1, pipein2},
|
|
Stdin: nil,
|
|
Stdout: &stdout,
|
|
}
|
|
err = container.Start(inprocess)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
waitProcess(inprocess, t)
|
|
stdinW.Close()
|
|
waitProcess(process, t)
|
|
|
|
out := string(stdout.Bytes())
|
|
// fd 5 is the directory handle for /proc/$$/fd
|
|
if out != "0 1 2 3 4 5" {
|
|
t.Fatalf("expected to have the file descriptors '0 1 2 3 4 5' passed to exec, got '%s'", out)
|
|
}
|
|
var buf = []byte{0}
|
|
_, err = pipeout1.Read(buf)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
out1 := string(buf)
|
|
if out1 != "1" {
|
|
t.Fatalf("expected first pipe to receive '1', got '%s'", out1)
|
|
}
|
|
|
|
_, err = pipeout2.Read(buf)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
out2 := string(buf)
|
|
if out2 != "2" {
|
|
t.Fatalf("expected second pipe to receive '2', got '%s'", out2)
|
|
}
|
|
}
|
|
|
|
func TestExecInOomScoreAdj(t *testing.T) {
|
|
if testing.Short() {
|
|
return
|
|
}
|
|
rootfs, err := newRootfs()
|
|
ok(t, err)
|
|
defer remove(rootfs)
|
|
config := newTemplateConfig(rootfs)
|
|
config.OomScoreAdj = 200
|
|
container, err := newContainer(config)
|
|
ok(t, err)
|
|
defer container.Destroy()
|
|
|
|
stdinR, stdinW, err := os.Pipe()
|
|
ok(t, err)
|
|
process := &libcontainer.Process{
|
|
Args: []string{"cat"},
|
|
Env: standardEnvironment,
|
|
Stdin: stdinR,
|
|
}
|
|
err = container.Start(process)
|
|
stdinR.Close()
|
|
defer stdinW.Close()
|
|
ok(t, err)
|
|
|
|
buffers := newStdBuffers()
|
|
ps := &libcontainer.Process{
|
|
Args: []string{"/bin/sh", "-c", "cat /proc/self/oom_score_adj"},
|
|
Env: standardEnvironment,
|
|
Stdin: buffers.Stdin,
|
|
Stdout: buffers.Stdout,
|
|
Stderr: buffers.Stderr,
|
|
}
|
|
err = container.Start(ps)
|
|
ok(t, err)
|
|
waitProcess(ps, t)
|
|
|
|
stdinW.Close()
|
|
waitProcess(process, t)
|
|
|
|
out := buffers.Stdout.String()
|
|
if oomScoreAdj := strings.TrimSpace(out); oomScoreAdj != strconv.Itoa(config.OomScoreAdj) {
|
|
t.Fatalf("expected oomScoreAdj to be %d, got %s", config.OomScoreAdj, oomScoreAdj)
|
|
}
|
|
}
|