0

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)?

Thanks.

Tim
  • 101,790
  • 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 04 '19 at 17:20
  • http://man7.org/linux/man-pages/man4/tty.4.html might help - look at "If the process is the session leader ..." – Mark Plotnick Jan 04 '19 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.". –  Jan 04 '19 at 21:13
  • @JdeBP and here is the definition. Notice that a controlling process is always a session leader, but not vice-versa. –  Jan 04 '19 at 21:23
  • @JdeBP See my update. – Tim Jan 04 '19 at 22:59
  • 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 –  Jan 05 '19 at 00:04
  • @mosvy Thanks. Is controlling terminal a per-process concept, not just a per-process-session concept? – Tim Jan 05 '19 at 00:45
  • https://unix.stackexchange.com/questions/405755/ You're repeating yourself a lot recently, Tim. – JdeBP Jan 05 '19 at 02:59
  • mosvy, the question is tagged [tag:linux] and Tim asks about Lubuntu by default. SUS handwaving for accomodating historical Unices, whose descendants have not worked in the 4.2BSD and old System 5 ways in this area for almost as long as Linux has existed, does not apply. Linux has the concept of a session leader, as Mark Plotnick pointed out, but does not have a concept of a controlling process. Go and read the Linux doco for exit() (and indeed for the ioctl() in the question). – JdeBP Jan 05 '19 at 03:33
  • @JeBP It's the first time I know controlling process is different from session leader – 炸鱼薯条德里克 Jan 05 '19 at 09:55
  • 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. –  Jan 05 '19 at 12:55

1 Answers1

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)

void main(void) { /* 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)
        ERR(&quot;setsid&quot;);
}

int pid = fork();

if (pid &lt; 0)
    ERR(&quot;fork&quot;);

if (pid &gt; 0) {
    /* super-lazy clean-up and sync in one */
    wait(NULL);
    return;
}

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

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

pid = fork();

if (pid &lt; 0)
    ERR(&quot;fork&quot;);

if (pid &gt; 0) {
    int fd = open(ctermid(NULL), O_RDWR);
    if (fd &lt; 0)
        ERR(&quot;open&quot;);

    /* middle child gives up controlling terminal */
    if (ioctl(fd, TIOCNOTTY) &lt; 0) {
        close(fd);
        ERR(&quot;ioctl&quot;);
    }

    close(fd);
    printf(&quot;Child gave up ctty, now waiting\n&quot;);

    /* super-lazy clean-up and sync in one */
    wait(NULL);
    return;
}

/* even lazier sync */
sleep(1);

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

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

system(cmd);

/* the output of the command will not end with newline */
printf(&quot;\n&quot;);

}

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.

nert
  • 399