mirror of
https://github.com/podman-container-tools/podman.git
synced 2026-08-05 00:15:44 +00:00
exec: honor ConsoleSize so the terminal is sized at creation
The exec API accepts a ConsoleSize but it is dropped: the exec pseudo-terminal is created at its default size and only corrected afterwards by an asynchronous resize. A short-lived exec that reads its window size at startup (e.g. `stty size`) can therefore observe the wrong size, because the resize may arrive after the process has already read it. docker applies the size at creation. Carry the requested ConsoleSize through ExecConfig and into the exec OCI process spec (process.consoleSize) so the runtime sizes the terminal before the process starts, removing the race. The local and remote CLIs capture the caller's terminal size when -t is given and pass it through ExecOptions, matching the behavior of `podman run`. Re-enable the previously flaky `podman exec` case in the interactive system test, which this change makes deterministic. Signed-off-by: Shuai Yuan <shuaiyuanzju@gmail.com>
This commit is contained in:
parent
d84fb8be61
commit
f5efef5043
9 changed files with 63 additions and 7 deletions
|
|
@ -11,6 +11,7 @@ import (
|
|||
|
||||
"github.com/spf13/cobra"
|
||||
"go.podman.io/common/pkg/completion"
|
||||
"go.podman.io/common/pkg/resize"
|
||||
"go.podman.io/podman/v6/cmd/podman/common"
|
||||
"go.podman.io/podman/v6/cmd/podman/registry"
|
||||
"go.podman.io/podman/v6/cmd/podman/validate"
|
||||
|
|
@ -18,6 +19,7 @@ import (
|
|||
"go.podman.io/podman/v6/pkg/domain/entities"
|
||||
envLib "go.podman.io/podman/v6/pkg/env"
|
||||
"go.podman.io/podman/v6/pkg/rootless"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -189,6 +191,16 @@ func exec(cmd *cobra.Command, args []string) error {
|
|||
streams.AttachOutput = true
|
||||
streams.AttachError = true
|
||||
|
||||
// When allocating a TTY, capture the current terminal size up front so the
|
||||
// exec pseudo-terminal is created at the right size, rather than relying on
|
||||
// the asynchronous resize that follows attach. This matters for short-lived
|
||||
// commands that read their window size at startup (e.g. `stty size`).
|
||||
if execOpts.Tty {
|
||||
if w, h, err := term.GetSize(int(os.Stdin.Fd())); err == nil {
|
||||
execOpts.ConsoleSize = &resize.TerminalSize{Width: uint16(w), Height: uint16(h)}
|
||||
}
|
||||
}
|
||||
|
||||
if execNoSession {
|
||||
exitCode, err := registry.ContainerEngine().ContainerExecNoSession(registry.Context(), nameOrID, execOpts, streams)
|
||||
registry.SetExitCode(exitCode)
|
||||
|
|
|
|||
|
|
@ -29,6 +29,12 @@ type ExecConfig struct {
|
|||
Command []string `json:"command"`
|
||||
// Terminal is whether the exec session will allocate a pseudoterminal.
|
||||
Terminal bool `json:"terminal,omitempty"`
|
||||
// ConsoleSize is an optional initial size for the exec session's
|
||||
// pseudoterminal, applied at creation when Terminal is true. This lets a
|
||||
// one-shot exec read the correct window size immediately instead of
|
||||
// relying on an asynchronous resize that may arrive after the process has
|
||||
// already started.
|
||||
ConsoleSize *resize.TerminalSize `json:"consoleSize,omitempty"`
|
||||
// AttachStdin is whether the STDIN stream will be forwarded to the exec
|
||||
// session's first process when attaching. Only available if Terminal is
|
||||
// false.
|
||||
|
|
@ -1180,6 +1186,7 @@ func prepareForExec(c *Container, session *ExecSession) (*ExecOptions, error) {
|
|||
opts.ExitCommand = session.Config.ExitCommand
|
||||
opts.ExitCommandDelay = session.Config.ExitCommandDelay
|
||||
opts.Privileged = session.Config.Privileged
|
||||
opts.ConsoleSize = session.Config.ConsoleSize
|
||||
|
||||
return opts, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -201,6 +201,9 @@ type ExecOptions struct {
|
|||
Env map[string]string
|
||||
// Terminal is whether to create a new TTY for the exec session.
|
||||
Terminal bool
|
||||
// ConsoleSize is an optional initial size for the exec session's TTY,
|
||||
// applied at creation when Terminal is true.
|
||||
ConsoleSize *resize.TerminalSize
|
||||
// Cwd is the working directory for the executed command. If unset, the
|
||||
// working directory of the container will be used.
|
||||
Cwd string
|
||||
|
|
|
|||
|
|
@ -704,6 +704,16 @@ func (c *Container) prepareProcessExec(options *ExecOptions, env []string, sessi
|
|||
pspec.Terminal = false
|
||||
if options.Terminal {
|
||||
pspec.Terminal = true
|
||||
// Size the exec PTY at creation so the process reads the correct
|
||||
// window size immediately. Without this the PTY starts at 0x0 and is
|
||||
// only corrected by an asynchronous resize, which can arrive after a
|
||||
// one-shot process has already read its size.
|
||||
if options.ConsoleSize != nil {
|
||||
pspec.ConsoleSize = &spec.Box{
|
||||
Height: uint(options.ConsoleSize.Height),
|
||||
Width: uint(options.ConsoleSize.Width),
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(env) > 0 {
|
||||
pspec.Env = append(pspec.Env, env...)
|
||||
|
|
|
|||
|
|
@ -42,6 +42,14 @@ func ExecCreateHandler(w http.ResponseWriter, r *http.Request) {
|
|||
libpodConfig := new(libpod.ExecConfig)
|
||||
libpodConfig.Command = input.Cmd
|
||||
libpodConfig.Terminal = input.Tty
|
||||
// Preserve the requested initial terminal size so the exec PTY can be sized
|
||||
// at creation (docker sends ConsoleSize as [height, width]).
|
||||
if input.ConsoleSize != nil {
|
||||
libpodConfig.ConsoleSize = &resize.TerminalSize{
|
||||
Height: uint16(input.ConsoleSize[0]),
|
||||
Width: uint16(input.ConsoleSize[1]),
|
||||
}
|
||||
}
|
||||
libpodConfig.AttachStdin = input.AttachStdin
|
||||
libpodConfig.AttachStderr = input.AttachStderr
|
||||
libpodConfig.AttachStdout = input.AttachStdout
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"time"
|
||||
|
||||
nettypes "go.podman.io/common/libnetwork/types"
|
||||
"go.podman.io/common/pkg/resize"
|
||||
imageTypes "go.podman.io/image/v5/types"
|
||||
"go.podman.io/podman/v6/libpod/define"
|
||||
"go.podman.io/podman/v6/pkg/domain/entities/types"
|
||||
|
|
@ -283,6 +284,7 @@ type ContainerLogsOptions struct {
|
|||
// a container
|
||||
type ExecOptions struct {
|
||||
Cmd []string
|
||||
ConsoleSize *resize.TerminalSize
|
||||
DetachKeys string
|
||||
Envs map[string]string
|
||||
Interactive bool
|
||||
|
|
|
|||
|
|
@ -890,6 +890,13 @@ func makeExecConfig(options entities.ExecOptions, rt *libpod.Runtime, noSession
|
|||
execConfig.PreserveFD = options.PreserveFD
|
||||
execConfig.AttachStdin = options.Interactive
|
||||
|
||||
// Size the exec terminal at creation so a process that reads its window size
|
||||
// at startup (e.g. `stty size`) observes the right value, rather than relying
|
||||
// on the asynchronous resize that follows attach.
|
||||
if options.Tty {
|
||||
execConfig.ConsoleSize = options.ConsoleSize
|
||||
}
|
||||
|
||||
// Only set up exit command for regular exec sessions, not no-session mode
|
||||
if !noSession {
|
||||
// Make an exit command
|
||||
|
|
|
|||
|
|
@ -618,6 +618,12 @@ func makeExecConfig(options entities.ExecOptions) *handlers.ExecCreateConfig {
|
|||
createConfig.Env = env
|
||||
createConfig.WorkingDir = options.WorkDir
|
||||
createConfig.Cmd = options.Cmd
|
||||
// Size the exec terminal at creation, mirroring the local path, so the
|
||||
// service does not have to rely on the asynchronous resize that follows
|
||||
// attach. ConsoleSize is ordered [height, width].
|
||||
if options.Tty && options.ConsoleSize != nil {
|
||||
createConfig.ConsoleSize = &[2]uint{uint(options.ConsoleSize.Height), uint(options.ConsoleSize.Width)}
|
||||
}
|
||||
|
||||
return createConfig
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,14 +70,15 @@ function teardown() {
|
|||
|
||||
run_podman rm -t 0 -f mystty
|
||||
|
||||
# FIXME: the checks below are flaking a lot (see #10710).
|
||||
# The same must hold for podman exec. The exec pseudo-terminal is now sized
|
||||
# at creation (honoring the requested ConsoleSize), so stty reads the right
|
||||
# dimensions immediately instead of racing the asynchronous resize that
|
||||
# previously followed attach (see #10710).
|
||||
run_podman run -d --name mystty $IMAGE top
|
||||
run_podman exec -it mystty stty size <$PODMAN_TEST_PTY
|
||||
is "$output" "$rows $cols$CR" "stty under podman exec reads the correct dimensions"
|
||||
|
||||
# check that the same works for podman exec
|
||||
# run_podman run -d --name mystty $IMAGE top
|
||||
# run_podman exec -it mystty stty size <$PODMAN_TEST_PTY
|
||||
# is "$output" "$rows $cols" "stty under podman exec reads the correct dimensions"
|
||||
#
|
||||
# run_podman rm -t 0 -f mystty
|
||||
run_podman rm -t 0 -f mystty
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue