Sending "keystrokes" to a terminal in Linux

Introduction: the problem

Sometimes one needs to emulate keyboard input to an application running on a terminal (or a pseudoterminal). A seemingly obvious and simple approach is to just send data to that device, e.g.

echo 'ls -lA' > /dev/tty
This naive method doesn't work. You just get the sent data (ls -lA) printed on that terminal but with no effect, an application on that terminal does not see it, as the data goes directly to the terminal's output stream, rather than input queue.

So one needs a way to place data on input flow of a terminal. Sad fact is that there is no such functionality in POSIX, moreover there is no common relatively portable method for this, and even not every UNIX-like system has it, and they are all OS-specific. In Linux this can be done using TCTIOCSTI command of ioctl(2). BTW BSD had a similar functionality, but they has revoked it from their kernels.

ioctl()

Just see man 2 ioctl:

int ioctl(int fd, int request, …);
The ioctl() function manipulates the underlying device parameters of special files. In particular, many operating characteristics of character special files (e.g., terminals) may be controlled with ioctl() requests.

The argument fd must be an open file descriptor. The second argument is a device-dependent request code. The ioctl() call for terminals and serial ports accepts many possible command arguments. Most require a third argument, of varying type. The third argument is an untyped pointer to memory. It's traditionally char *argp (from the days before void * was valid C).

An ioctl() request has encoded in it whether the argument is an in parameter or out parameter, and the size of the argument argp in bytes. Macros and defines used in specifying an ioctl() request are located in the file <sys/ioctl.h>.

Use of ioctl() makes for nonportable programs. Use the POSIX interface described in termios(3) whenever possible.

TIOCSTI

From man 4 tty_ioctl:

TIOCSTI      int ioctl(int fd, TIOCSTI, const char *argp);
Insert the given byte in the input queue.

echo2tty

The program operates as a filter: it receives data from standard input and sends it byte by byte to a termial using ioctl(). Typical usage:

echo 'ls -lA' | echo2tty /dev/pts/66
Be aware of terminals possibly using different chars for “Enter”, so you may need to send specific newline char manually, e.g.:
echo -ne 'ls -lA\n' | echo2tty /dev/pts/66
— to send 0x0A (aka ^J, aka LF) as the end of line. Similarly for 0x0D (aka ^M, aka CR):
echo -ne 'ls -lA\r' | echo2tty /dev/pts/66

The utility has been implemented in C, Python and Perl. Nothing complex. Just a little access permissions pitfall.

Permissions issue

They say ioctl(TIOCSTI) requires root privileges (or CAP_SYS_ADMIN to be more specific, but that's usually the same in practice). Just device file permissions is NOT enough! This means ioctl(TIOCSTI) works for our own terminal (/dev/tty or /proc/self/fd/0) but FAILS for any other tty, even owned by the same user id. E.g. you may be able to write to /dev/pts/2 using

echo 'ls -lA' > /dev/pts/2
quite successfully but you will get Operation not permitted or even Bad address for ioctl(TIOCSTI) on /dev/pts/2 if this is not the terminal you issue these commands at.

C

You can download it here. It's quite sinple: the main loop just gets bytes from standard input via

read(0, cp, 1);
and sends them one by one to the file descriptor associated with the terminal via
ioctl(fd, TIOCSTI, cp);;
To get some debug info you may give -DECHO2TTY_DEBUG=1 flag to gcc.

Python

You can download it. It's quite simple, no need to comment it.

Perl

You can download it.

It's quite straightforward. The only tricky moment is with getting the input stream as a binary byte flow rather than a sequence of lines (with newline chars stripped). We need the so called “slurp mode”. To get it one has to set Perl's $/ variable to undef. This variable contains line delimiter, so if it is unset (set to undef), Perl will slurp <STDIN> without breaking it into separate lines and will process \n and/or \r characters just as any others, rather than “eating” them.


Tonop T