A resposta óbvia é que não posso imprimir a saída para o terminal se eu tiver desconectado dela. O problema é que, na verdade, posso enviar caracteres para um terminal do qual eu desanexei e esses caracteres aparecem no meu terminal.
Esta é realmente uma questão sobre como o unix lida com o terminal de controle, embora contenha uma boa quantidade de código C.
De qualquer forma, o terminal de controle é /dev/tty
, e eu certamente posso imprimir a saída para o meu xterm
da seguinte forma:
[grochmal@haps term]$ echo yay > /dev/tty
yay
Mas se eu me soltar desse terminal, não poderei mais fazer isso. ou seja, se /dev/tty
não existe, é porque o processo atual não possui um terminal de controle. Estou aceitando essa suposição de man 4 tty
, que afirma:
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.
Agora, para desanexar do terminal, eu uso man 2 setsid
, pois uma nova sessão será iniciada sem um terminal de controle. Aqui está o trecho que estou usando:
/* 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;
}
Tudo o que o código acima faz é:
- imprima algumas informações;
-
fork()
para garantir que setsid()
funcione, pois a criança nunca será líder do grupo de processos;
-
setsid()
, que se destaca do terminal;
- esperar que o pai retorne e o filho seja adotado por
init
, apenas no caso;
- verifique se não podemos abrir
/dev/tty
;
- imprima o material, que deve ser enviado para algum lugar.
Compilar isso e executar produz a saída a seguir (observe o prompt intercalado, já que o pai retornou e o shell imprimiu o 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]
A pergunta é: Por que as três últimas linhas são realmente impressas?
Não tenho terminal de controle, /dev/tty
não pôde ser aberto. Como o kernel descobriu que deveria redirecionar a saída do filho para o xterm
que eu abri e estou executando? Isso deveria acontecer?