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:

  1. Opens a master psedoterminal device using posix_openpt (or openpty)
  2. Forks a new child (typically this is bound to become a shell)
  3. Severs its terminal connection using setsid()
  4. Opens the terminal device (created in step 1) which becomes its controlling terminal
  5. 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 - see clientloop.c and sets received_window_change_signal = 1 when it receives it

  • The 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.