what relations are between my current controlling terminal and `/dev/tty`?

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.


/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).