Forward terminal window size (resize) to guest PTY via vsock #72

Closed
opened 2026-03-26 14:01:58 +00:00 by rawan · 2 comments
Member

Parent Issue

Split from #70 (PTY allocation for shell server). The PTY + controlling terminal fix is handled in #70. This issue covers the resize forwarding feature.

Problem

Terminal programs like vim, htop, less, and multi-column ls output need to know the terminal dimensions (rows x cols). Without resize forwarding:

  • The guest PTY defaults to 0x0 or 80x24
  • Resizing the host terminal window has no effect on the guest
  • Full-screen programs render incorrectly

Proposed Solution

In-band resize protocol

Define an 8-byte escape sequence sent over the existing vsock byte stream:

[0x01, 0x52, 0x53, 0x5a] [rows: u16 BE] [cols: u16 BE]
 \____ magic \x01RSZ ____/  \_______ payload _______/

0x01 (SOH) is chosen because it essentially never appears in normal terminal output.

Changes needed

  1. crates/my_hypervisor-lib/src/vsock/protocol.rs — Define RESIZE_MAGIC, RESIZE_MSG_LEN, and encode_resize() helper
  2. crates/my_hypervisor-lib/src/vsock/proxy.rs — In interactive_session:
    • Send initial window size (TIOCGWINSZ) to guest before proxy loop
    • Handle SIGWINCH signal to forward resize events in real-time
  3. crates/my_hypervisor-init/src/shell_handler.rs — In vsock→PTY forwarding thread:
    • Parse incoming bytes for the resize magic prefix (state machine)
    • On match, extract rows/cols and call TIOCSWINSZ on PTY master
    • Do not forward resize bytes to the PTY (control messages only)

Acceptance Criteria

  • stty size inside the guest reflects the host terminal dimensions
  • Resizing the host terminal updates the guest PTY (verified with stty size before and after)
  • Full-screen programs (vim, htop) render correctly
  • Normal terminal data containing 0x01 bytes is not corrupted
  • cargo test --workspace passes
  • cargo clippy -- -D warnings passes
## Parent Issue Split from #70 (PTY allocation for shell server). The PTY + controlling terminal fix is handled in #70. This issue covers the resize forwarding feature. ## Problem Terminal programs like `vim`, `htop`, `less`, and multi-column `ls` output need to know the terminal dimensions (rows x cols). Without resize forwarding: - The guest PTY defaults to 0x0 or 80x24 - Resizing the host terminal window has no effect on the guest - Full-screen programs render incorrectly ## Proposed Solution ### In-band resize protocol Define an 8-byte escape sequence sent over the existing vsock byte stream: ``` [0x01, 0x52, 0x53, 0x5a] [rows: u16 BE] [cols: u16 BE] \____ magic \x01RSZ ____/ \_______ payload _______/ ``` `0x01` (SOH) is chosen because it essentially never appears in normal terminal output. ### Changes needed 1. **`crates/my_hypervisor-lib/src/vsock/protocol.rs`** — Define `RESIZE_MAGIC`, `RESIZE_MSG_LEN`, and `encode_resize()` helper 2. **`crates/my_hypervisor-lib/src/vsock/proxy.rs`** — In `interactive_session`: - Send initial window size (`TIOCGWINSZ`) to guest before proxy loop - Handle `SIGWINCH` signal to forward resize events in real-time 3. **`crates/my_hypervisor-init/src/shell_handler.rs`** — In vsock→PTY forwarding thread: - Parse incoming bytes for the resize magic prefix (state machine) - On match, extract rows/cols and call `TIOCSWINSZ` on PTY master - Do not forward resize bytes to the PTY (control messages only) ### Acceptance Criteria - [ ] `stty size` inside the guest reflects the host terminal dimensions - [ ] Resizing the host terminal updates the guest PTY (verified with `stty size` before and after) - [ ] Full-screen programs (`vim`, `htop`) render correctly - [ ] Normal terminal data containing `0x01` bytes is not corrupted - [ ] `cargo test --workspace` passes - [ ] `cargo clippy -- -D warnings` passes
rawan self-assigned this 2026-03-26 14:38:15 +00:00
rawan added this to the ACTIVE project 2026-03-26 14:38:41 +00:00
Author
Member

Implementation Spec for Issue #72

Objective

Implement terminal resize forwarding from the host to the guest VM via the existing vsock byte stream. When the host terminal window is resized, the new dimensions (rows, cols) are sent as an in-band escape sequence to the guest, which applies them to the PTY master fd using TIOCSWINSZ.

Requirements

  1. Define an 8-byte in-band resize protocol: [0x01, 0x52, 0x53, 0x5a] [rows: u16 BE] [cols: u16 BE]
  2. On interactive_session start, send the current host terminal dimensions to the guest before entering the proxy loop
  3. Handle SIGWINCH on the host and forward updated dimensions in real-time
  4. In the guest shell_handler, parse the vsock-to-PTY stream for the resize magic, extract rows/cols, apply via TIOCSWINSZ, and do NOT forward the control bytes to the PTY
  5. Normal data containing 0x01 bytes must not be corrupted
  6. All existing tests pass (cargo test --workspace)
  7. cargo clippy -- -D warnings passes

Files to Modify

File Description
crates/my_hypervisor-lib/src/vsock/protocol.rs Add resize protocol constants and encode_resize() helper
crates/my_hypervisor-lib/src/vsock/proxy.rs Send initial winsize, handle SIGWINCH, forward resize messages
crates/my_hypervisor-init/src/shell_handler.rs Parse resize escape in vsock-to-PTY thread, apply TIOCSWINSZ

Implementation Plan

Step 1: Define the resize protocol in protocol.rs

  • Add RESIZE_MAGIC: [u8; 4] constant ([0x01, 0x52, 0x53, 0x5a])
  • Add RESIZE_MSG_LEN: usize = 8
  • Add encode_resize(rows: u16, cols: u16) -> [u8; 8] function
  • Add unit tests for encode/decode roundtrip

Step 2: Send initial winsize & handle SIGWINCH in proxy.rs

  • Add get_winsize() -> Option<(u16, u16)> helper using TIOCGWINSZ
  • Send initial terminal dimensions before proxy loop starts
  • Register SIGWINCH handler via tokio::signal::unix::signal(SignalKind::window_change())
  • Add SIGWINCH branch inside the stdin-forwarding task's select loop

Step 3: Parse resize escape in guest shell_handler.rs

  • Add duplicated protocol constants (with sync comment)
  • Add apply_winsize(master_fd, rows, cols) helper using TIOCSWINSZ
  • Replace direct vsock-to-PTY copy with state-machine parser
  • Parser: match magic byte-by-byte, accumulate 8-byte message, extract rows/cols, apply winsize
  • Flush partially matched bytes on mismatch (no data corruption)

Step 4: Add unit tests

  • test_encode_resize_known_values and test_encode_resize_roundtrip in protocol.rs
  • Parser tests in shell_handler.rs: normal data passthrough, resize detection, partial magic mismatch, split across reads, 0x01 in normal data, back-to-back resize

Step 5: Build, lint, and verify

  • cargo build --workspace
  • cargo test --workspace
  • cargo clippy -- -D warnings

Acceptance Criteria

  • stty size inside the guest reflects the host terminal dimensions at session start
  • Resizing the host terminal updates the guest PTY
  • Full-screen programs (vim, htop) render correctly after resize
  • Normal terminal data containing 0x01 bytes is not corrupted
  • cargo test --workspace passes (including new unit tests)
  • cargo clippy -- -D warnings passes

Notes

  • No new dependencies required (tokio signal support and libc already available)
  • Protocol constants duplicated in guest init crate (cannot depend on host lib)
  • 0x01RSZ magic is safe — 4-byte sequence virtually never appears in terminal data
  • TIOCSWINSZ on PTY master automatically sends SIGWINCH to guest foreground process group
## Implementation Spec for Issue #72 ### Objective Implement terminal resize forwarding from the host to the guest VM via the existing vsock byte stream. When the host terminal window is resized, the new dimensions (rows, cols) are sent as an in-band escape sequence to the guest, which applies them to the PTY master fd using `TIOCSWINSZ`. ### Requirements 1. Define an 8-byte in-band resize protocol: `[0x01, 0x52, 0x53, 0x5a] [rows: u16 BE] [cols: u16 BE]` 2. On `interactive_session` start, send the current host terminal dimensions to the guest before entering the proxy loop 3. Handle `SIGWINCH` on the host and forward updated dimensions in real-time 4. In the guest `shell_handler`, parse the vsock-to-PTY stream for the resize magic, extract rows/cols, apply via `TIOCSWINSZ`, and do NOT forward the control bytes to the PTY 5. Normal data containing `0x01` bytes must not be corrupted 6. All existing tests pass (`cargo test --workspace`) 7. `cargo clippy -- -D warnings` passes ### Files to Modify | File | Description | |------|-------------| | `crates/my_hypervisor-lib/src/vsock/protocol.rs` | Add resize protocol constants and `encode_resize()` helper | | `crates/my_hypervisor-lib/src/vsock/proxy.rs` | Send initial winsize, handle SIGWINCH, forward resize messages | | `crates/my_hypervisor-init/src/shell_handler.rs` | Parse resize escape in vsock-to-PTY thread, apply TIOCSWINSZ | ### Implementation Plan #### Step 1: Define the resize protocol in `protocol.rs` - Add `RESIZE_MAGIC: [u8; 4]` constant (`[0x01, 0x52, 0x53, 0x5a]`) - Add `RESIZE_MSG_LEN: usize = 8` - Add `encode_resize(rows: u16, cols: u16) -> [u8; 8]` function - Add unit tests for encode/decode roundtrip #### Step 2: Send initial winsize & handle SIGWINCH in `proxy.rs` - Add `get_winsize() -> Option<(u16, u16)>` helper using `TIOCGWINSZ` - Send initial terminal dimensions before proxy loop starts - Register `SIGWINCH` handler via `tokio::signal::unix::signal(SignalKind::window_change())` - Add SIGWINCH branch inside the stdin-forwarding task's select loop #### Step 3: Parse resize escape in guest `shell_handler.rs` - Add duplicated protocol constants (with sync comment) - Add `apply_winsize(master_fd, rows, cols)` helper using `TIOCSWINSZ` - Replace direct vsock-to-PTY copy with state-machine parser - Parser: match magic byte-by-byte, accumulate 8-byte message, extract rows/cols, apply winsize - Flush partially matched bytes on mismatch (no data corruption) #### Step 4: Add unit tests - `test_encode_resize_known_values` and `test_encode_resize_roundtrip` in protocol.rs - Parser tests in shell_handler.rs: normal data passthrough, resize detection, partial magic mismatch, split across reads, 0x01 in normal data, back-to-back resize #### Step 5: Build, lint, and verify - `cargo build --workspace` - `cargo test --workspace` - `cargo clippy -- -D warnings` ### Acceptance Criteria - [ ] `stty size` inside the guest reflects the host terminal dimensions at session start - [ ] Resizing the host terminal updates the guest PTY - [ ] Full-screen programs (vim, htop) render correctly after resize - [ ] Normal terminal data containing `0x01` bytes is not corrupted - [ ] `cargo test --workspace` passes (including new unit tests) - [ ] `cargo clippy -- -D warnings` passes ### Notes - No new dependencies required (`tokio` signal support and `libc` already available) - Protocol constants duplicated in guest init crate (cannot depend on host lib) - `0x01RSZ` magic is safe — 4-byte sequence virtually never appears in terminal data - `TIOCSWINSZ` on PTY master automatically sends `SIGWINCH` to guest foreground process group
Author
Member

Test Results

  • Total: 195 (17 cli + 13 init + 165 lib)
  • Passed: 195
  • Failed: 0
  • Clippy: Clean (0 warnings with -D warnings)

All new resize protocol tests pass:

  • test_encode_resize_known_values
  • test_encode_resize_roundtrip
  • test_parser_passes_through_normal_data
  • test_parser_detects_resize_message
  • test_parser_handles_partial_magic_mismatch
  • test_parser_handles_split_across_reads
  • test_parser_0x01_in_normal_data
  • test_parser_back_to_back_resize
  • test_parser_data_around_resize

Implementation Summary

Files Modified

File Changes
crates/my_hypervisor-lib/src/vsock/protocol.rs Added RESIZE_MAGIC, RESIZE_MSG_LEN, encode_resize() + 2 unit tests
crates/my_hypervisor-lib/src/vsock/proxy.rs Added get_winsize(), initial winsize send, SIGWINCH handling in interactive_session()
crates/my_hypervisor-init/src/shell_handler.rs Added ResizeParser state machine, apply_winsize(), replaced direct vsock-to-PTY copy with parser + 7 unit tests

How it works

  1. Protocol: 8-byte in-band escape [\x01RSZ][rows:u16 BE][cols:u16 BE]
  2. Host side (proxy.rs): Sends initial terminal size before proxy loop; handles SIGWINCH via tokio::signal to forward resize events in real-time
  3. Guest side (shell_handler.rs): State-machine parser in vsock-to-PTY thread detects resize messages, applies TIOCSWINSZ on PTY master (which auto-sends SIGWINCH to foreground process), and strips control bytes from the data stream

Notes

  • No new dependencies added
  • Protocol constants intentionally duplicated between host lib and guest init (separate crates)
  • Parser correctly handles: partial magic mismatches, split reads, back-to-back resize messages, and 0x01 bytes in normal data
## Test Results - **Total**: 195 (17 cli + 13 init + 165 lib) - **Passed**: 195 - **Failed**: 0 - **Clippy**: Clean (0 warnings with `-D warnings`) All new resize protocol tests pass: - `test_encode_resize_known_values` ✅ - `test_encode_resize_roundtrip` ✅ - `test_parser_passes_through_normal_data` ✅ - `test_parser_detects_resize_message` ✅ - `test_parser_handles_partial_magic_mismatch` ✅ - `test_parser_handles_split_across_reads` ✅ - `test_parser_0x01_in_normal_data` ✅ - `test_parser_back_to_back_resize` ✅ - `test_parser_data_around_resize` ✅ ## Implementation Summary ### Files Modified | File | Changes | |------|--------| | `crates/my_hypervisor-lib/src/vsock/protocol.rs` | Added `RESIZE_MAGIC`, `RESIZE_MSG_LEN`, `encode_resize()` + 2 unit tests | | `crates/my_hypervisor-lib/src/vsock/proxy.rs` | Added `get_winsize()`, initial winsize send, SIGWINCH handling in `interactive_session()` | | `crates/my_hypervisor-init/src/shell_handler.rs` | Added `ResizeParser` state machine, `apply_winsize()`, replaced direct vsock-to-PTY copy with parser + 7 unit tests | ### How it works 1. **Protocol**: 8-byte in-band escape `[\x01RSZ][rows:u16 BE][cols:u16 BE]` 2. **Host side** (`proxy.rs`): Sends initial terminal size before proxy loop; handles `SIGWINCH` via `tokio::signal` to forward resize events in real-time 3. **Guest side** (`shell_handler.rs`): State-machine parser in vsock-to-PTY thread detects resize messages, applies `TIOCSWINSZ` on PTY master (which auto-sends `SIGWINCH` to foreground process), and strips control bytes from the data stream ### Notes - No new dependencies added - Protocol constants intentionally duplicated between host lib and guest init (separate crates) - Parser correctly handles: partial magic mismatches, split reads, back-to-back resize messages, and `0x01` bytes in normal data
rawan closed this issue 2026-04-01 13:13:52 +00:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
geomind_code/my_hypervisor#72
No description provided.