How do terminal size changes get sent to command line applications though ssh or telnet?
This is the messy world of pseudo terminals.
Locally, when you resize your terminal your foreground process group gets a SIGWINCH
and you can use ioctl
to fetch the new size. But what does this have to do with the remote vim process ?
The subject is quite complicated but the gist of it is that the remove server (sshd) does this:
- Opens a master psedoterminal device using
posix_openpt
(oropenpty
) - Forks a new child (typically this is bound to become a shell)
- Severs its terminal connection using
setsid()
- Opens the terminal device (created in step 1) which becomes its controlling terminal
- Replaces the standard descriptors (
STDIN_FILENO
and friends) with the fd from step 4
At this point anything the server process writes to the master side ends up as input to the slave side BUT with a terminal line discipline so the kernel does a bit of magic - like sending signals - when writing certain combinations, and you can also issue ioctl
calls with useful effects.
The best way to think about this is to explore the openssh
suite.
The client monitors for
SIGWINCH
- seeclientloop.c
and setsreceived_window_change_signal = 1
when it receives itThe function
client_check_window_change
checks that flag and tells the server:packet_start(SSH_CMSG_WINDOW_SIZE); packet_put_int((u_int)ws.ws_row); ...
So now the server should receive a packet which specifies a (potentially new) size.
The server calls
pty_change_window_size
with the received sizes which does the real magic:struct winsize w; w.ws_row = row; ... (void) ioctl(ptyfd, TIOCSWINSZ, &w); /* This is it! */
This sets the new window size of the slave. If the new size differs from the old, the kernel sends a SIGWINCH
to the foreground process group associated with that pty. Thus vim
also gets that signal and can update its idea of the terminal size.