Assume a session has a controlling terminal. If a process in the session that is not the session leader, calls ioctl(fd, TIOCNOTTY), is it correct that it only closes the fd for itself? Is it the same as close(fd)?


  • 1
    This question takes as a premise the false notion that terminals have controlling processes, and is thus unanswerable. Terminals have foreground process groups, and sessions have controlling terminals. The general notion of a controlling process for a terminal does not exist.
    – JdeBP
    Jan 4, 2019 at 17:20
  • man7.org/linux/man-pages/man4/tty.4.html might help - look at "If the process is the session leader ..." Jan 4, 2019 at 19:02
  • @JdeBP from the susv4 _exit: "If the process is a controlling process, the SIGHUP signal shall be sent to each process in the foreground process group of the controlling terminal belonging to the calling process.".
    – user313992
    Jan 4, 2019 at 21:13
  • 1
    @Tim why do you think that ioctl(fd, TIOCNOTTY) is closing that fd? It isn't. echo 'int main(void){ ioctl(1, TIOCNOTTY); write(1, "foo\n", 4); }' | cc -include sys/ioctl.h -include unistd.h -Wall -x c - && ./a.out
    – user313992
    Jan 5, 2019 at 0:04
  • 1
    @JdeBP a "controlling process" is a session leader with a controlling terminal. The linux _exit(2) manpage describes the same behavior as the _exit manpage from susv4, only that it's using the full defininition instead of that term. I don't care to debate whether it's "better" to always call a spade a pointed shovel and an imago "an insect at its last stage", but, for anybody reading this: a session leader on linux does behave differently if it has a controlling terminal and afaik, does behave exactly as specified by the susv4 standard.
    – user313992
    Jan 5, 2019 at 12:55

1 Answer 1


It does not close the file descriptor, merely gives up the controlling terminal for the process. Since the process is not the session leader then nothing else happens. That is how the man page on my Linux machine describes it. Even though the documentation is what you should rely on as testing the implementation will not prove it works in all cases, trying out is pretty trivial if you are okay with terrible code:

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#define ERR(x) \
    do { \
    perror(x); \
    return; \
    } while (0)

    /* create a new session */
    pid_t leader = setsid();

    if (leader == (pid_t) -1) {
        /* EPERM means we are already a process group leader */
        if (errno != EPERM)

    int pid = fork();

    if (pid < 0)

    if (pid > 0) {
        /* super-lazy clean-up and sync in one */

    pid_t parent = getppid();
    pid_t child = getpid();

    printf("Child PID: %lld\n", (long long) child);

    pid = fork();

    if (pid < 0)

    if (pid > 0) {
        int fd = open(ctermid(NULL), O_RDWR);
        if (fd < 0)

        /* middle child gives up controlling terminal */
        if (ioctl(fd, TIOCNOTTY) < 0) {

        printf("Child gave up ctty, now waiting\n");

        /* super-lazy clean-up and sync in one */

    /* even lazier sync */

    pid_t grandchild = getpid();
    printf("Grandchild PID: %lld\n", (long long) grandchild);

    char cmd[256] = {0};
    snprintf(cmd, 256,
             "head -c 60 /proc/%lld/stat /proc/%lld/stat /proc/%lld/stat",
             (long long) parent, (long long) child, (long long) grandchild);


    /* the output of the command will not end with newline */

Running this shows:

Child PID: 293750
Child gave up ctty, now waiting
Grandchild PID: 293751
==> /proc/293749/stat <==
293749 (test) S 290544 293749 290544 34822 293749 4210688 10
==> /proc/293750/stat <==
293750 (test) S 293749 293749 290544 0 -1 4210752 40 0 0 0 0
==> /proc/293751/stat <==
293751 (test) S 293750 293749 290544 34822 293749 4210752 32

And if you look at the output the seventh field is the tty_nr from proc(5) man page and it shows that only the child that gave up it's controlling terminal lost it (tty_nr = 0), not the other processes.

All that said I think this should not be used anywhere other than for research/learning purposes as usually you would call setsid(2) before spawning any children and, if that's what you desire, open a (new) terminal without O_NOCTTY if you want it to be assigned a controlling terminal.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .