1

The obvious answer is that I cannot print output to the terminal if I have detached from it. The problem is that I actually can send characters to a terminal from which I detached and these characters do appear in my terminal.

This is really a question about how unix deals with the controlling terminal, although it does contain a good deal of C code.

Anyhow, the controlling terminal is /dev/tty, and I certainly can print output to my xterm as follows:

[grochmal@haps term]$ echo yay > /dev/tty
yay

But if I detach from that terminal I should not be able to do it anymore. i.e. if /dev/tty does not exist it is because the current process do not have a controlling terminal. I'm taking this assumption from man 4 tty which states:

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.

Now, to detach from the terminal I use man 2 setsid since a new session will start without a controlling terminal. Here is the snippet that I'm using:

/* use latest but standard stuff */
#define _XOPEN_SOURCE 700

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

int
main (int argc, char **argv)
{
    int chk;
    char *def_term = "/dev/tty";

    /* print info to the terminal */
    printf("PID [%ld] PPID [%ld] GRPID [%ld] SESID [%ld]\n"
          , (long) getpid(), (long) getppid()
          , (long) getgid(), (long) getsid(0)
          );

    /* check terminal */
    chk = open(def_term, O_RDONLY);
    if (-1 != chk)
        printf("We have %s\n", def_term);
    else
        printf("NO %s\n", def_term);

    fflush(NULL);  /* flush stdio buffers */

    chk = fork();
    switch(chk) {
        case -1:
            printf("BOOM!");
            exit(1);  /* exit flushing buffers */
            break;

        case 0:
            /* ensure that the parent died, so we are adopted by init */
            sleep(2);

            chk = setsid();
            if (-1 != chk)
                printf("We got a new session.\n");
            else
                printf("Session failed! [%s]\n", strerror(errno));

            /* use the *non-existent!* terminal */
            chk = open(def_term, O_RDONLY);
            if (-1 != chk)
                printf("We have %s\n", def_term);
            else
                printf("NO %s\n", def_term);

            printf("PID [%ld] PPID [%ld] GRPID [%ld] SESID [%ld]\n"
                  , (long) getpid(), (long) getppid()
                  , (long) getgid(), (long) getsid(0)
                  );

            break;

        default:
            _exit(1);  /* do not flush, we have children */
            break;
    }
    return 0;
}

All that the code above does is to:

  1. print some info;
  2. fork() to ensure that setsid() works since a child will never be a process group leader;
  3. setsid(), which detaches from terminal;
  4. wait for parent to return and the child be adopted by init, just in case;
  5. check that we cannot open /dev/tty;
  6. print stuff, which must be sent somewhere.

Compiling this and running produces the following output (note the intermingled prompt, since the parent returned and the shell printed the prompt.)

[grochmal@haps term]$ gcc -Wall -o detach detach.c 
[grochmal@haps term]$ ./detach 
PID [29943] PPID [679] GRPID [100] SESID [679]
We have /dev/tty
[grochmal@haps term]$ We got a new session.
NO /dev/tty
PID [29944] PPID [1] GRPID [100] SESID [29944]

The question is: Why the last three lines are actually printed?

I have no controlling terminal, /dev/tty could not be opened. How the kernel figured out that it should redirect the output from the child to the xterm that I have opened and am running? Should this happen?

3

The three standard file descriptors (standard input, output and error) point to the terminal line (e.g. /dev/pts/0) by default if you start your program from a terminal. You do not modify these descriptors and hence they still refer to this terminal throughout your program.

You can always send data to a terminal line if you have the proper permissions. E.g. open two terminal emulators, in one of them execute tty, let's say it prints /dev/pts/0. Then from the other one execute something like echo foo > /dev/pts/0, it'll appear in the first one.

This has nothing to do with the controlling terminal.

  • Damn, I feel stupid now. You're completely right, I've added ttyname(fileno(stdout)) in the child, and it does give me the path to the tty used for output. i.e. controlling tty != IO tty. Thanks. – grochmal May 16 '17 at 1:58
  • 1
    This is one of the reasons why daemons should close(2) the standard descriptors when they go to background. Keeping them open is security problem. – Satō Katsura May 16 '17 at 6:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.