Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 65 additions & 56 deletions internal/oci/runtime_oci.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,54 +262,8 @@ func (r *runtimeOCI) ExecContainer(ctx context.Context, c *Container, cmd []stri
return err
}
defer os.RemoveAll(processFile)

execCmd := r.constructExecCommand(ctx, c, processFile, "")
var cmdErr, copyError error
if tty {
cmdErr = ttyCmd(execCmd, stdin, stdout, resize)
} else {
var r, w *os.File
if stdin != nil {
// Use an os.Pipe here as it returns true *os.File objects.
// This way, if you run 'kubectl exec <pod> -i bash' (no tty) and type 'exit',
// the call below to execCmd.Run() can unblock because its Stdin is the read half
// of the pipe.
r, w, err = os.Pipe()
if err != nil {
return err
}
execCmd.Stdin = r
go func() {
_, copyError = pools.Copy(w, stdin)
w.Close()
}()
}

if stdout != nil {
execCmd.Stdout = stdout
}

if stderr != nil {
execCmd.Stderr = stderr
}

if err := execCmd.Start(); err != nil {
return err
}

// The read side of the pipe should be closed after the container process has been started.
if r != nil {
if err := r.Close(); err != nil {
return err
}
}

cmdErr = execCmd.Wait()
}

if copyError != nil {
return copyError
}
cmdErr := r.executeExec(execCmd, stdin, stdout, stderr, tty, resize)
if exitErr, ok := cmdErr.(*exec.ExitError); ok {
return &utilexec.ExitErrorWrapper{ExitError: exitErr}
}
Expand Down Expand Up @@ -339,32 +293,28 @@ func (r *runtimeOCI) ExecSyncContainer(ctx context.Context, c *Container, comman
cmd := r.constructExecCommand(ctx, c, processFile, pidFile)
cmd.SysProcAttr = sysProcAttrPlatform()

var stdoutBuf, stderrBuf bytes.Buffer
cmd.Stdout = &stdoutBuf
cmd.Stderr = &stderrBuf
stdoutBuf := nopWriteCloser{&bytes.Buffer{}}
stderrBuf := nopWriteCloser{&bytes.Buffer{}}
resize := make(chan remotecommand.TerminalSize)

pidFileCreatedDone := make(chan struct{}, 1)
pidFileCreatedCh, err := WatchForFile(pidFile, pidFileCreatedDone, notify.InModify, notify.InMovedTo)
if err != nil {
return nil, errors.Wrapf(err, "failed to watch %s", pidFile)
}

doneErr := cmd.Start()
if doneErr != nil {
return nil, err
}

// wait till the command is done
done := make(chan error, 1)
go func() {
done <- cmd.Wait()
done <- r.executeExec(cmd, nil, stdoutBuf, stderrBuf, c.terminal, resize)
close(done)
}()

// First, wait for the pid file to be created.
// When it is, the timer begins for the exec process.
// If the command fails before that happens, however,
// that needs to be caught.
var doneErr error
select {
case <-pidFileCreatedCh:
case doneErr = <-done:
Expand Down Expand Up @@ -416,6 +366,65 @@ func (r *runtimeOCI) ExecSyncContainer(ctx context.Context, c *Container, comman
}, nil
}

func (r *runtimeOCI) executeExec(execCmd *exec.Cmd, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
var cmdErr, copyError error
if tty {
return ttyCmd(execCmd, stdin, stdout, resize)
}
var rd, w *os.File
var err error
if stdin != nil {
// Use an os.Pipe here as it returns true *os.File objects.
// This way, if you run 'kubectl exec <pod> -i bash' (no tty) and type 'exit',
// the call below to execCmd.Run() can unblock because its Stdin is the read half
// of the pipe.
rd, w, err = os.Pipe()
if err != nil {
return err
}
execCmd.Stdin = rd
go func() {
_, copyError = pools.Copy(w, stdin)
w.Close()
}()
}

if stdout != nil {
execCmd.Stdout = stdout
}

if stderr != nil {
execCmd.Stderr = stderr
}

if err := execCmd.Start(); err != nil {
return err
}

// The read side of the pipe should be closed after the container process has been started.
if rd != nil {
if err := rd.Close(); err != nil {
return err
}
}

cmdErr = execCmd.Wait()

if copyError != nil {
return copyError
}
return cmdErr
}

// Needed because https://github.com/golang/go/issues/22823 was denied
type nopWriteCloser struct {
*bytes.Buffer
}

func (nopWriteCloser) Close() error {
return nil
}

func (r *runtimeOCI) constructExecCommand(ctx context.Context, c *Container, processFile, pidFile string) *exec.Cmd {
args := []string{rootFlag, r.root, "exec"}
if pidFile != "" {
Expand Down
9 changes: 9 additions & 0 deletions test/ctr.bats
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,15 @@ function wait_until_exit() {
crictl rm -f "$ctr_id"
}

@test "ctr execsync should succeed if container has a terminal" {
start_crio

jq ' .tty = true' "$TESTDATA"/container_sleep.json > "$newconfig"

ctr_id=$(crictl run "$newconfig" "$TESTDATA"/sandbox_config.json)
crictl exec --sync "$ctr_id" /bin/sh -c "[[ -t 1 ]]"
}

@test "ctr device add" {
# In an user namespace we can only bind mount devices from the host, not mknod
# https://github.com/opencontainers/runc/blob/master/libcontainer/rootfs_linux.go#L480-L481
Expand Down