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
7 changes: 4 additions & 3 deletions docs/roadmap-decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -344,9 +344,10 @@ is not completion.
preparation plus the fixed-path native install/config lifecycle are
implemented; ordinary runtime integration remains gated;
- Docker Desktop WSL integration proof, proof-bound bounded control requests,
and the separately constrained proof-bound attach transport are implemented
but not yet wired into an enabled frontend; multiplexed output, terminal,
resize, signal and exit-code semantics remain;
the separately constrained attach transport, strict raw-stream decoder,
exact container wait and TTY-resize operations are implemented but not yet
wired into an enabled frontend; terminal event collection, signal and
end-to-end exit-code propagation remain;
- namespace-prefixed/labeled WSL volume identity plus proof-bound exact
inspect/create/remove and namespace discovery are implemented; tool-time
creation and state/GC/backup/restore command integration remain;
Expand Down
9 changes: 5 additions & 4 deletions docs/roadmap-implementation-requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -607,10 +607,11 @@ registry-derived management/tool-shim reconciliation without enabling tool
execution. Canonical project storage classification with its proof-consuming
argument mapper, fail-closed Docker Desktop WSL integration proof and a
proof-bound bounded Engine API control-request primitive, constrained attach
transport and exact context-bound container-wait operation are implemented but
not yet wired into an enabled frontend. Multiplexed-output decoding,
terminal/resize, signal and end-to-end exit-code propagation remain. Runtime
wiring, argument/process behavior and real WSL qualification remain.
transport, strict multiplexed-output decoder, exact context-bound
container-wait operation and proof-bound container-TTY resize operation are
implemented but not yet wired into an enabled frontend. Terminal event
collection, signal and end-to-end exit-code propagation remain. Runtime wiring,
argument/process behavior and real WSL qualification remain.

Implementation must define native config/shim location, Docker endpoint,
project identity, named-volume behavior, file permissions, case sensitivity,
Expand Down
7 changes: 7 additions & 0 deletions docs/wsl-process-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ unknown stream identifiers, truncated frames, malformed reserved bytes, and
unsafe daemon-error payloads fail closed. This primitive does not enable the
frontend or change the existing Docker CLI path.

The same proof-bound path has an explicit container-TTY resize operation. It
accepts only an exact full container ID and positive unsigned 16-bit height and
width values, sends them to Docker's fixed resize endpoint, and accepts only an
HTTP 200 response. It does not inspect the caller's terminal or subscribe to
resize events; later frontend wiring must supply dimensions from a proven TTY
and invoke the operation for each accepted resize event.

`docker run` always receives `-i`. It additionally receives `-t` only when both
stdin and stdout report character-device mode; either redirection, either stat
failure, or a non-character stream keeps the invocation non-TTY. Stderr does
Expand Down
17 changes: 9 additions & 8 deletions docs/wsl.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,10 +237,12 @@ accepts only an exact full container ID and fixes the request to
`condition=not-running`. It repeats the socket/peer proof, uses the caller's
context as the long-poll lifetime, bounds the response to 64 KiB, rejects an
unsafe Engine error, and accepts only process exit codes from 0 through 255.
These primitives do not decode multiplexed output or implement container
creation/start, terminal behavior, resize, signals, or end-to-end exit-code
propagation. Nothing is wired into tool execution yet; real WSL2 + Docker
Desktop qualification remains mandatory before support.
The native package also has a strict decoder for non-TTY multiplexed output and
a proof-bound resize operation for one exact full container ID with positive
unsigned 16-bit terminal dimensions. These primitives do not implement
container creation/start, terminal event collection, signals, or end-to-end
exit-code propagation. Nothing is wired into tool execution yet; real WSL2 +
Docker Desktop qualification remains mandatory before support.

## Native WSL volume identity and control lifecycle

Expand Down Expand Up @@ -283,10 +285,9 @@ following:
mapper into native tool execution, then complete stdin/TTY and signal
semantics;
3. wire the implemented bounded Docker Desktop control-operation and attach
primitives, raw-stream decoder and wait primitive into container lifecycle,
then implement terminal/resize, signal and exit-code propagation without
accepting ambient
endpoint overrides;
primitives, raw-stream decoder, wait and resize operations into container
lifecycle, then implement terminal event collection, signal and exit-code
propagation without accepting ambient endpoint overrides;
4. Windows-filesystem and WSL-filesystem project tests plus mixed-invocation
rejection; and
5. real WSL2 + Docker Desktop end-to-end qualification before any support claim.
Expand Down
38 changes: 38 additions & 0 deletions internal/wsldocker/resize.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package wsldocker

import (
"context"
"errors"
"fmt"
"net/http"
"net/url"
"strconv"
)

func resizeContainer(ctx context.Context, containerID string, height, width uint16, deps operationDependencies) error {
if ctx == nil {
return errors.New("Docker Desktop WSL container resize requires a context")
}
if err := validateContainerID(containerID); err != nil {
return fmt.Errorf("Docker Desktop WSL container resize: %w", err)
}
if height == 0 {
return errors.New("Docker Desktop WSL container resize requires a positive height")
}
if width == 0 {
return errors.New("Docker Desktop WSL container resize requires a positive width")
}
if deps.check == nil || deps.statSocket == nil || deps.perform == nil {
return errors.New("Docker Desktop WSL container resize dependencies are incomplete")
}
_, err := execute(ctx, Request{
Method: http.MethodPost,
Path: "/containers/" + containerID + "/resize",
Query: url.Values{
"h": {strconv.FormatUint(uint64(height), 10)},
"w": {strconv.FormatUint(uint64(width), 10)},
},
SuccessStatuses: []int{http.StatusOK},
}, deps)
return err
}
17 changes: 17 additions & 0 deletions internal/wsldocker/resize_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//go:build linux

package wsldocker

import "context"

// ResizeContainer resizes the TTY for one exact container. Dimensions must be
// positive and fit the terminal's unsigned 16-bit row/column representation.
func ResizeContainer(ctx context.Context, containerID string, height, width uint16) error {
return resizeContainer(ctx, containerID, height, width, operationDependencies{
check: Check,
statSocket: statDockerSocket,
perform: func(ctx context.Context, socketPath string, request Request) (operationResult, error) {
return performDockerRequest(ctx, socketPath, request, operationTimeout, maxOperationOutput, 0)
},
})
}
14 changes: 14 additions & 0 deletions internal/wsldocker/resize_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//go:build !linux

package wsldocker

import (
"context"
"errors"
)

// ResizeContainer is unavailable outside Linux because the fixed Unix socket
// and peer credentials are part of the Docker Desktop WSL trust boundary.
func ResizeContainer(context.Context, string, uint16, uint16) error {
return errors.New("Docker Desktop WSL container resize requires Linux")
}
65 changes: 65 additions & 0 deletions internal/wsldocker/resize_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package wsldocker

import (
"context"
"net/http"
"strings"
"testing"
)

func TestResizeContainerBindsExactDimensionsToProvenSocket(t *testing.T) {
socket := validSocketInfo()
statCalls := 0
deps := validOperationDependencies(socket)
deps.statSocket = func(path string) (socketInfo, error) {
if path != DockerSocketPath {
t.Fatalf("stat path = %q", path)
}
statCalls++
return socket, nil
}
deps.perform = func(_ context.Context, path string, request Request) (operationResult, error) {
if path != DockerSocketPath || request.Method != http.MethodPost || request.Path != "/containers/"+testContainerID+"/resize" {
t.Fatalf("perform(%q, %+v)", path, request)
}
if request.Query.Encode() != "h=24&w=80" || len(request.Body) != 0 || len(request.SuccessStatuses) != 1 || request.SuccessStatuses[0] != http.StatusOK {
t.Fatalf("resize request = %+v", request)
}
return operationResult{StatusCode: http.StatusOK, PeerUID: 0}, nil
}
if err := resizeContainer(context.Background(), testContainerID, 24, 80, deps); err != nil {
t.Fatal(err)
}
if statCalls != 2 {
t.Fatalf("stat calls = %d", statCalls)
}
}

func TestResizeContainerRejectsInvalidInputsBeforeProof(t *testing.T) {
tests := map[string]struct {
ctx context.Context
containerID string
height uint16
width uint16
}{
"nil context": {containerID: testContainerID, height: 24, width: 80},
"short ID": {ctx: context.Background(), containerID: "abc", height: 24, width: 80},
"uppercase ID": {ctx: context.Background(), containerID: strings.ToUpper(testContainerID), height: 24, width: 80},
"non-hex ID": {ctx: context.Background(), containerID: strings.Repeat("g", 64), height: 24, width: 80},
"zero height": {ctx: context.Background(), containerID: testContainerID, width: 80},
"zero width": {ctx: context.Background(), containerID: testContainerID, height: 24},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
deps := validOperationDependencies(validSocketInfo())
deps.check = func(context.Context) (Result, error) { panic("proof reached for invalid resize") }
if err := resizeContainer(test.ctx, test.containerID, test.height, test.width, deps); err == nil {
t.Fatal("resizeContainer() succeeded")
}
})
}

if err := resizeContainer(context.Background(), testContainerID, 24, 80, operationDependencies{}); err == nil || !strings.Contains(err.Error(), "dependencies are incomplete") {
t.Fatalf("incomplete-dependencies error = %v", err)
}
}
5 changes: 3 additions & 2 deletions internal/wsldocker/wsldocker.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// Package wsldocker proves that a native WSL2 process is connected to Docker
// Desktop's supported WSL integration rather than an in-distribution or remote
// Docker Engine, and provides proof-bound bounded control requests plus
// separately constrained container-attach and wait transports. It does not
// enable the WSL frontend by itself.
// separately constrained container-attach and wait transports plus an exact
// proof-bound TTY-resize operation. It does not enable the WSL frontend by
// itself.
package wsldocker

import (
Expand Down
Loading