3

Am I right that all input typed from the keyboard goes through a controlling terminal? That means that if a program is run without a controlling terminal, it won't be able to receive any user input. Is that right for every kind of program in Linux?

UPDATE #1: To clarify the question, my pager module for Python crashes when stdin is redirected:

$ ./pager.py < README.rst
...
  File "pager.py", line 566, in <module>
    page(sys.stdin)
  File "pager.py", line 375, in page
    if pagecallback(pagenum) == False:
  File "pager.py", line 319, in prompt
    if getch() in [ESC_, CTRL_C_, 'q', 'Q']:
  File "pager.py", line 222, in _getch_unix
    old_settings = termios.tcgetattr(fd)
termios.error: (25, 'Inappropriate ioctl for device')

This is because I try to get descriptor to setup keyboard input as fd = sys.stdin.fileno(). When stdin is redirected, its file descriptor no longer associated with any keyboard input, so attempt to setup it fails with input-output control error.

I was told to get this controlling terminal instead, but I had no idea where does it come from. I understood that it is some kind of channel to send signals from user to running processes, but at the same time it is possible to run processes without it.

So the question is - should I always read my keyboard input from controlling terminal? And what happens if the pager process is run without it? Will keyboard input still matter to user? Should I care to get it from some other source?

  • 2
    What exactly do you mean when you say "controlling terminal" ? – schaiba Mar 13 '17 at 07:44
  • Please elaborate your question, maybe you give a detailed example. I have a vague idea of what you're asking but you should clarify. – countermode Mar 13 '17 at 08:06
  • 5
    @schaiba, see the POSIX specification for a definition of controlling terminal. That's a standard Unix concept. – Stéphane Chazelas Mar 13 '17 at 08:07
  • 1
    I've created a new tag, so that people won't vote to close the question, because they don't know Linux internals and may think about terminal apps and bash commands. It is waiting to be peer reviewed http://unix.stackexchange.com/tags/controlling-terminal/info Please improve the description it if you can. – anatoly techtonik Mar 13 '17 at 10:38

1 Answers1

10

No. Terminal applications read keyboard input from the device file (on Linux, something like /dev/ttyS0 or /dev/ttyUSB0... for a serial device, /dev/pts/0 for a pseudo-terminal device) corresponding to the terminal with the keyboard you're typing on.

That device doesn't have to be the controlling terminal of the process (or any process for that matters).

You can do cat /dev/pts/x provided you have read permission to that device file, and that would read what's being typed on the terminal (if any) at the other end.

Actually, if it is the controlling terminal of the process and the process is not in the foreground process group of the terminal, the process would typically be suspended if it attempted to read from it (and if it was in the foreground process group, it would receive a SIGINT/SIGTSTP/SIGQUIT if you sent a ^C/^Z/^\ regardless of whether the process is reading from the terminal device or not). Those things would not happen if the terminal device was not the controlling terminal of the process (if the process was part of a different session). That's what controlling terminal is about. That is intended for the job control mechanism as implemented by interactive shells. Beside those SIGTTIN/SIGTTOU and SIGINT/SIGTSTP/SIGQUIT signals, the controlling terminal is involved in the delivery of SIGHUP upon terminal hang hup, it's also the tty device that /dev/tty redirects to.

In any case, that's only for terminal input: real as in a terminal device connected over a serial cable, emulated like X11 terminal emulators such as xterm that make use of pseudo-terminal devices, or emulated by the kernel like the virtual terminals on Linux that interact with processes with /dev/tty<x> (and support more than the standard terminal interface).

Applications like the X server typically get keyboard input from the keyboard drivers. On Linux using common input abstraction layers. The X server, in turn provides an event mechanism to communicate keyboard events to applications connecting to it. For instance, xterm would receive X11 keyboard events which it translates to writing characters to the master side of a pseudo-terminal device, which translates to processes running "inside" xterm reading the corresponding characters when they read from the corresponding pseudo-terminal slave device (/dev/pts/x).

Now, there's no such thing as a terminal application. What we call terminal application above are applications that are typically used in a terminal, that are expected to be displayed in a terminal and take input from a terminal like vi, and interactive shell or less. But any application can be controlled by a terminal, and any application that reads or writes files or their stdin/stdout/stderr can be made to perform I/O to a terminal device.

For instance, if you run firefox, an application that connects to the X server for user I/O, from within a shell running in an xterm, firefox will inherit the controlling terminal from its shell parent. ^C in the terminal would kill it if it was started in foreground by the shell. It will also have its file descriptors 0, 1 and 2 (stdin, stdout and stderr) open on that /dev/pts/<x> file (again as inherited from its shell parent). And firefox may very well end up writing on the fd 2 (stderr) for some kind of errors (and if it was put in background and the terminal device was configured with stty tostop, it would then receive a SIGTTOU and be suspended).

If instead, firefox is started by your X session manager or Windows manager (when you click on some firefox icon on some menu), it will likely not get any controlling terminal and will have no file descriptor connected to any (you'll see that ps -fp <firefox-pid> shows ? as the tty and lsof -p <firefox-pid> shows no file descriptor on /dev/pts/* or /dev/tty*). If however you browsed to file:///dev/pts/<x>, firefox could still do some I/O to a terminal device. And if it opened that file without the O_NOCTTY flag and if it happened to be a session leader and if that /dev/pts/<x> didn't already have a session attached to it, that device would end up being the controlling terminal of that firefox process.

More reading at:

Edit

After your edit clarifies a bit the question and adds some context.

The above should make it clear that a process can read input from any terminal device they like (except the controlling terminal if the process is not in its foreground process group), but that's not really what is of interest to you here.

Your question would be: for an interactive terminal application, where to get the user input from when stdin no longer points to the terminal.

Applications like tr get their input from stdin and write on stdout. When stdin/stdout is a tty device with a terminal at the other end, they happen to be interactive in that they read and write data from/to the user.

Some terminal text editors (like ed/ex and even some vi implementations) continue reading their input from stdin when stdin is no longer a terminal so they can be scriptable.

A pager though is a typical application that still needs to interact with the user even when their input is not a terminal (at least when their output still goes to the terminal). So they need another channel to the terminal device to take user input. And the question is: which terminal device should they use?

Yes, it should be the controlling terminal. As that's typically what the controlling terminal is meant to be. That's the device that would send the pager a SIGINT/SIGTSTP when you press Ctrl-C/Z, so it makes sense for the pager to read other key strokes from that same terminal.

The typical way to get a file descriptor on the controlling terminal is to open /dev/tty that redirects there (note that it works even if the process has changed euid so that it doesn't have read permission to the original device. It's a lot better than trying to find a path to the original device (which can't be done portably anyway)).

Some pagers like less or most open /dev/tty even if stdin is a tty device (after all, one could do less < /dev/ttyS0 from within a terminal emulator to see what's being sent over serial).

If opening /dev/tty fails, that's typically because you don't have a controlling terminal. One might argue that it's because you've been explicitly detached from a terminal so shouldn't be attempting to do user interaction, but there are potential (unusual) situations where you have no controlling terminal device but your stdin/stdout is still a tty device and you'd still want to do user interaction (like an emergency shell in an initrd).

So you could fall back to get user interaction from stdin if it's a terminal.

One could argue that you'd want to check that stdout is a terminal device and that it points to the same terminal device as the controlling one (to account for things that do man -l /dev/stdin < /dev/ttyS0 > /dev/ttyS1 for instance where you don't want the pager spawned by man to do user interaction) but that's probably not worth the bother especially considering that it's not easy to do portably. That could also potentially break other weird use cases that expect the pager to be interactive as long as stdout is a terminal device.

  • Thanks. That's a lot of new info. I need some time to munch through all these terms. – anatoly techtonik Mar 13 '17 at 10:40
  • I narrowed my question to a specific problem with pager utility. I still don't understand how should I find this /dev/pts device for keyboard to read from? People told me to use controlling terminal, but now I am not sure. – anatoly techtonik Mar 21 '17 at 07:28
  • 2
    @anatolytechtonik, open a fd to /dev/tty which redirects to the controlling terminal for user interaction (when stdin no longer points to the terminal). That's what other pagers do. Since you probably won't want to page if stdout is not a terminal anyway, you could also use stdout (fd 1) to read user input (if it's a tty (isatty()), but that assumes the tty device has been open in read+write mode on stdout which is generally the case but not guaranteed. – Stéphane Chazelas Mar 21 '17 at 07:52
  • @anatolytechtonik, see edit for the longer version – Stéphane Chazelas Mar 21 '17 at 09:41
  • That helps, thanks. But it misses some info. tr, ed/ex will not be able to read Esc key presses if they just read stdin - they need to get to terminal device and setup it so that they can receive key presses as some ANSI codes. So should that device be a controlling terminal or there could be other device? If there is other device that is receiving user input, how to find it? So far ctty is the only option as it seems. – anatoly techtonik Mar 21 '17 at 09:56
  • If I read key strokes from ctty, will my new settings for reading ESC key make it impossible to send SIGINT with Ctrl-C to my program anymore? – anatoly techtonik Mar 21 '17 at 09:59
  • @anatolytechtonik, you probably did the equivalent of a stty raw -echo that turns off ISIG instead of doing only the equivalent of stty -icanon -echo min 1 time 0 – Stéphane Chazelas Mar 21 '17 at 10:11