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:
- print some info;
fork()
to ensure thatsetsid()
works since a child will never be a process group leader;setsid()
, which detaches from terminal;- wait for parent to return and the child be adopted by
init
, just in case; - check that we cannot open
/dev/tty
; - 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?