16

I'm taking a look at the code to the 'less' utility, specifically how it gets keyboard input. Interestingly, on line 80 of ttyin.c, it sets the file descriptor to read from:

     /*
      * Try /dev/tty.
      * If that doesn't work, use file descriptor 2,
      * which in Unix is usually attached to the screen,
      * but also usually lets you read from the keyboard.
      */
  #if OS2
      /* The __open() system call translates "/dev/tty" to "con". */
      tty = __open("/dev/tty", OPEN_READ);
  #else
      tty = open("/dev/tty", OPEN_READ);
  #endif
      if (tty < 0)
          tty = 2;

Isn't file descriptor 2 stderr? If so, WTH?! I thought keyboard input was sent through stdin.

Interestingly, even if you do ls -l * | less, after the file finishes loading, you can still use the keyboard to scroll up and down, but if you do ls -l * | vi, then vi will yell at you because it doesn't read from stdin. What's the big idea? How did I end up in this strange new land where stderr is both a way to report errors to the screen and read from the keyboard? I don't think I'm in Kansas anymore...

user207421
  • 305,947
  • 44
  • 307
  • 483
Michael
  • 794
  • 5
  • 15

4 Answers4

22
$ ls -l /dev/fd/
lrwx------ 1 me me 64 2009-09-17 16:52 0 -> /dev/pts/4
lrwx------ 1 me me 64 2009-09-17 16:52 1 -> /dev/pts/4
lrwx------ 1 me me 64 2009-09-17 16:52 2 -> /dev/pts/4

When logged in at an interative terminal, all three standard file descriptors point to the same thing: your TTY (or pseudo-TTY).

$ ls -fl /dev/std{in,out,err}
lrwxrwxrwx 1 root root 4 2009-09-13 01:57 /dev/stdin -> fd/0
lrwxrwxrwx 1 root root 4 2009-09-13 01:57 /dev/stdout -> fd/1
lrwxrwxrwx 1 root root 4 2009-09-13 01:57 /dev/stderr -> fd/2

By convention, we read from 0 and write to 1 and 2. However, nothing prevents us from doing otherwise.

When your shell runs ls -l * | less, it creates a pipe from ls's file descriptor 1 to less's file descriptor 0. Obviously, less can no longer read the user's keyboard input from file descriptor 0 – it tries to get the TTY back however it can.

If less has not been detached from the terminal, open("/dev/tty") will give it the TTY.

However, in case that fails... what can you do? less makes one last attempt at getting the TTY, assuming that file descriptor 2 is attached to the same thing that file descriptor 0 would be attached to, if it weren't redirected.

This is not failproof:

$ ls -l * | setsid less 2>/dev/null

Here, less is given its own session (so it is no longer a part of the terminal's active process group, causing open("/dev/tty") to fail), and its file descriptor 2 has been changed – now less exits immediately, because it is outputting to a TTY yet it fails to get any user input.

ephemient
  • 198,619
  • 38
  • 280
  • 391
  • Oh, I see it now. Because stderr is nothing more than juts a file descriptor that's actually connected to the terminal, it can read or write from it as it pleases. THAT'S COOL! Thank you, ephemient. – Michael Sep 18 '09 at 13:51
  • Wouldn't file descriptor `2` only be open for writing, though? – Blacklight Shining Feb 11 '14 at 16:40
  • 1
    @BlacklightShining Your terminal emulator probably started your shell using something like [`forkpty`](http://www.gnu.org/software/libc/manual/html_node/Pseudo_002dTerminal-Pairs.html), which creates a single read-write file descriptor to the pseudo-TTY and dup's it to 0,1,2. – ephemient Feb 13 '14 at 06:54
  • what I don't understand is that if it reads from /dev/tty isn't getting a bunch of data from stdin (the producer in the pipeline)? so I guess it's looking for certain special keystrokes like / ? – Alexander Mills Jul 07 '19 at 03:45
3

Well... first off, you seem to missing the open() call which opens '/dev/tty'. It only uses file descriptor 2 if the call to open() fails. On a standard Linux system, and probably many Unices, '/dev/tty' exists and is unlikely to cause a fail.

Secondly, the comment at the top provides a limited amount of explanation as to why they fall back to file descriptor 2. My guess is that stdin, stdout, and stderr are pretty much connected to '/dev/tty/' anyway, unless redirected. And since the most common redirections for for stdin and/ or stdout (via piping or < / >), but less often for stderr, odds on are that using stderr would be most likely to still be connect to the "keyboard".

Jason Musgrove
  • 3,574
  • 19
  • 14
  • The reason for using stderr is that stdin/stdout are more likely to be pipes created by the spawning shell. Piping into or out of less is a noop, but does work. But redirecting stderr of a less command specifically has little value and isn't likely to be done. So betting that stderr is "really" the terminal device is a reasonable guess. – Andy Ross Sep 17 '09 at 21:55
2

The same question with an answer ultimately from the person who asked it is on linuxquestions although they quote slightly different source from less. And no, I don't understand most of it so I can't help beyond that :)

Troubadour
  • 13,334
  • 2
  • 38
  • 57
-2

It appears to be Linux specific functionality that sends keyboard input to FD 2.

DVK
  • 126,886
  • 32
  • 213
  • 327