I learned something new today. It helped solve a long-standing bug in Bullet.
Bullet is an application deployment tool that I wrote several years ago. It is a simple tool that SSHs into a server and uses Docker to run applications.
I use it in production for some of my projects.
Bullet can SSH into a remote server, spin up a one-off container and attach the terminal to it. You could run commands in that container as if you were SSH’ed directly into that environment.
Except control characters didn’t work.
You couldn’t press
Ctrl+C to interrupt the currently running program. You couldn’t press
Ctrl+D to signal EOF.
In fact, for example, pressing
Ctrl+C would kill Bullet, ending the SSH session.
And why didn’t control characters work? Because I didn’t put the terminal into raw mode before connecting to the container over SSH.
Here is a simplified Go code that connects to a remote server over SSH and acts like an SSH client:
It is almost like running
In fact, after running the Go code, I was greeted with a familiar prompt.
[hjr265@Potato gossh]$ go run . [hjr265@Carrot ~]$
But, unlike the
ssh program, pressing
Ctrl+C here terminated the Go program instead of sending a
SIGINT to the remote server.
[hjr265@Potato gossh]$ go run . [hjr265@Carrot ~]$ signal: interrupt [hjr265@Potato gossh]$
The solution turned out to be fairly simple. But it is something that I only came to learn about today.
Terminals can run in raw mode or cooked mode.
In cooked mode data is preprocessed before being given to a program, while raw mode passes the data as-is to the program without interpreting any of the special characters. […]
What cooked mode means is dependent on the operating system. However, in cooked mode, control characters are handled by the operating system.
Ctrl+C is pressed, the operating system sends
SIGINT to the currently running program. In most cases, the program aborts immediately.
But what we want here is to handle that control character ourselves.
And we can do that by putting the terminal into raw mode:
term.MakeRaw comes with the
A call to this function will put the terminal into raw mode and return a copy of the old state. This old state can be used to restore the terminal to its previous mode with the
Now I can send
SIGINTs to my heart’s content:
[hjr265@Potato gossh]$ go run . [hjr265@Carrot ~]$ sleep 5 ^C [hjr265@Carrot ~]$
comments powered by Disqus