8

On Lubuntu 18.04, I run a shell in lxterminal. Its controlling terminal is the current pseudoterminal slave:

$ tty
/dev/pts/2

I would like to know what relations are between my current controlling terminal /dev/pts/2 and /dev/tty.

  1. /dev/tty acts like my current controlling terminal /dev/pts/2:

    $ echo hello > /dev/tty
    hello
    
    $ cat < /dev/tty
    world
    world
    ^C
    
  2. But they seem to be unrelated files, instead of one being a symlink or hardlink to the other:

    $ ls -lai /dev/tty /dev/pts/2
     5 crw--w---- 1 t    tty 136, 2 May 31 16:38 /dev/pts/2
    13 crw-rw-rw- 1 root tty   5, 0 May 31 16:36 /dev/tty
    

For different sessions with different controlling terminals, if /dev/tty is guaranteed to be their controlling terminals. How can it be different controlling terminals, without being a symlink or hardlink?

So what are their relations and differences? Any help is much appreciated!

This post is originated from an earlier one Do the output of command `tty` and the file `/dev/tty` both refer to the controlling terminal of the current bash process?

Tim
  • 101,790
  • As mentioned at that Q&A you link to, tty doesn't report the controlling terminal, but the terminal open on stdin if any. See ps -o tty= -p "$$" for the controlling terminal (in practice, they are generally the same at the prompt of an interactive shell). – Stéphane Chazelas Jul 01 '18 at 05:30
  • @StéphaneChazelas Thanks. ps -o tty= -p "$$" works well! Is it not possible to get the controlling terminal of the current shell from /dev/tty? – Tim Dec 01 '18 at 02:59
  • 1
    It is possible by opening /dev/tty, calling ioctl(TIOCGDEV, &dev) to obtain the maj:min devnum of the real tty, and then looking for it through /dev. But that will give ambiguous results when more than one devpts filesystem is mounted. –  Oct 09 '19 at 15:50

2 Answers2

13

The tty manpage in section 4 claims the following:

The file /dev/tty is a character file with major number 5 and minor number 0, usually of mode 0666 and owner.group root.tty. It is a synonym for the controlling terminal of a process, if any.

In addition to the ioctl(2) requests supported by the device that tty refers to, the ioctl(2) request TIOCNOTTY is supported.

TIOCNOTTY

Detach the calling process from its controlling terminal.

If the process is the session leader, then SIGHUP and SIGCONT signals are sent to the foreground process group and all processes in the current session lose their controlling tty.

This ioctl(2) call works only on file descriptors connected to /dev/tty. It is used by daemon processes when they are invoked by a user at a terminal. The process attempts to open /dev/tty. If the open succeeds, it detaches itself from the terminal by using TIOCNOTTY, while if the open fails, it is obviously not attached to a terminal and does not need to detach itself.

This would explain in part why /dev/tty isn’t a symlink to the controlling terminal: it would support an additional ioctl, and there might not be a controlling terminal (but a process can always try to access /dev/tty). However the documentation is incorrect: the additional ioctl isn’t only accessible via /dev/tty (see mosvy’s answer, which also gives a more sensible explanation for the nature of /dev/tty).

/dev/tty can represent different controlling terminals, without being a link, because the driver which implements it determines what the calling process’ controlling terminal is, if any.

You can think of this as /dev/tty being the controlling terminal, and thus offering functionality which only makes sense for a controlling terminal, whereas /dev/pts/2 etc. are plain terminals, one of which might happen to be the controlling terminal for a given process.

Stephen Kitt
  • 434,908
6

/dev/tty is a "magical" character device which when opened, will return a handle to the current terminal.

Assuming that the controlling terminal is /dev/pts/1, a file descriptor opened via /dev/pts/1 and one opened via /dev/tty will refer to the same device; any write, read or other file operation will work the same on either of them.

In particular, they will accept the same set of ioctls, and TIOCNOTTY is not an extra ioctl only available via /dev/tty.

ioctl(fd, TIOCNOTTY) works the same on any file descriptor referring to a terminal, provided that it's the controlling terminal of the process which calls it.

It doesn't matter if the descriptor was obtained by opening /dev/tty, /dev/pts/1, /dev/ptmx (in which case the ioctl will act on its corresponding slave), or more recently, by a call to ioctl(master, TIOCGPTPEER, flags).

Example:

$ cat <<'EOT' >tiocnotty.c
#include <sys/ioctl.h>
#include <unistd.h>
#include <err.h>

int main(int ac, char **av){
        if(ioctl(0, TIOCNOTTY)) err(1, "io(TIOCNOTTY)");
        if(ac < 2) return 0;
        execvp(av[1], av + 1);
        err(1, "execvp %s", av[1]);
}
EOT
$ cc -W -Wall tiocnotty.c -o tiocnotty
$ ./tiocnotty
$ ./tiocnotty </dev/tty
$ tty
/dev/pts/0
$ ./tiocnotty </dev/pts/0

Also, it will not really "detach" the current process from the tty; the process will still be able to read from it, a ^C on the terminal will kill it, etc. Its only effect on a process which is not a session leader is that the tty will no longer be accessible via /dev/tty, and it will no longer be listed as the controlling tty in /proc/PID/stat:

$ ./tiocnotty cat
^C
$ ./tiocnotty cat
^Z
[2]+  Stopped                 ./tiocnotty cat
$ ./tiocnotty cat
foo
foo
^D
$ ./tiocnotty cat /dev/tty
cat: /dev/tty: No such device or address
$ ./tiocnotty awk '{print$7}' /proc/self/stat
0

[the 7th field of /proc/<pid>/stat is the device id of the controlling tty, see proc(5)]

If the process calling it is the session leader, it will really detach the session from the tty and will send a SIGHUP/SIGCONT pair to the foreground process group from the session. But even then, the terminal will not be closed, and the process will still be able to read from it, if it survives the SIGHUP:

$ script /dev/null -c 'trap "" HUP; exec ./tiocnotty cat'
Script started, file is /dev/null
lol
lol
^C^C^C^C^C  # no controlling tty anymore

wtf  
wtf
^D   # but still reading fine from it
Script done, file is /dev/null

/dev/tty is not a symlink like /dev/stdin => /dev/fd/0 => /proc/self/fd/0 => /dev/pts/0 because it was invented long before virtual dynamic filesystems like procfs (and long before symlinks in general). And many programs have come to depend on its particular semantics (eg. /dev/tty failing with ENODEV when the controlling terminal is not accessible).

  • Thanks, do you mind if I submit a patch to clarify the manpage? – Stephen Kitt Oct 10 '19 at 05:07
  • That would be great. The earliest system I've tested this on was RedHat 5.1 (Linux 2.0.34) of 21 years go. I don't know if earlier versions really did work as suggested by that manpage. –  Oct 10 '19 at 08:14
  • I don’t think the manpage has ever been right, the tty code has handled TTIOCNOTTY in a generic manner since forever. – Stephen Kitt Oct 10 '19 at 08:15
  • Thank you for reply. That is sweet – Tim Oct 10 '19 at 18:15
  • I might be totally off here but is this similar to when you use & to put a command to bg and it can still write to terminal? – d9ngle Apr 11 '20 at 12:46
  • 1
    @d9ngle The situation is different. The commands started with & (either real background jobs, or background-lite as from scripts, shells without job control, etc) are still "attached to" the controlling terminal, they're not even pretending to detach from it. –  Apr 11 '20 at 23:19