/*
 * Teimoso.c: exemplo de uso de sinais e tratadores de sinais. Este programa tem
 * dois loops infinitos que se tornam finitos pela influência de sinais.
 */

#include <stdio.h>     /* fprintf */
#include <stdlib.h>    /* exit */
#include <signal.h>    /* óbvio */
#include <errno.h>     /* errno, perror */
#include <sys/time.h>  /* setitimer */

/*
 * A interação entre o programa principal e os tratadores de sinais ocorre, usualmente,
 * através de variáveis globais.
 */

int forever = 1;           /* Usada em um loop afetado pelo sinal do usuário */
int time_signal_count = 0; /* Conta eventos de tempo */
int ctrlc_count = 0;       /* Conta os Ctrl-C do usuário   */

typedef void (*signal_handler_t)(int);  /* Tipo da função tratadora de sinal */

/*
 * Protótipo das funções utilizadas. 
 * As duas primeiras são os tratadores de sinais que serão usados.
 * As três últimas são "wrappers" das funções do sistema, 
 * só para simplificar o código final.
 */

void my_sigaction( int signum, signal_handler_t handler );
void my_sigsuspend(void);
void set_timer(int s);
void multi_signal_handler(int signum);
void time_handler(int signum);
void wait_for_a_signal(void);
void wait_for_a_number(void);

int main(int argc, char* argv[])
{

    my_sigaction(SIGUSR1,multi_signal_handler); /* Sinal do usuário */
    my_sigaction(SIGINT,multi_signal_handler);  /* Gerado pelo Ctrl-C, por exemplo */
    my_sigaction(SIGALRM,time_handler);         /* Contador de tempo de relógio */

    fprintf(stderr,"Vamos ver se você me tira daqui...\n");

    set_timer(1); /* Por enquanto, vamos contar de segundo em segundo */

    wait_for_a_signal();

    fprintf(stderr,"Levou mais de %d segundo(s) para fazer isso.\n", time_signal_count);

    set_timer(3);                    /* Alguns segundos entre contagens */

    wait_for_a_number();

    set_timer(0);  /* Agora, desligamos o relógio. */

    /* Se desejado, poderíamos retornar os sinais ao seu comportamento normal */
    my_sigaction(SIGUSR1,SIG_DFL); 
    my_sigaction(SIGINT,SIG_DFL); 
    my_sigaction(SIGALRM,SIG_DFL);

}

/* -----------------------------------------------------------------------
 * wait_for_a_signal: o loop só termina se o processo receber um SIGUSR1.
 * tentativas de terminar o programa com Ctrl-C não têm sucesso.
 */
void wait_for_a_signal(void)
{
    /* 
     * Este loop vai reagir a Ctrl-Cs digitados no teclado apenas escrevendo
     * uma mensagem na tela. Para terminar o loop, basta enviar um sinal SIGUSR1
     * para o programa.
     */
    int last_ctrlc = ctrlc_count;
    while (forever) {
        my_sigsuspend();
        if (ctrlc_count!=last_ctrlc) { /* suspend interrompido por Ctrl-C, escreve algo */
            last_ctrlc = ctrlc_count;
            fprintf(stderr,"Estou por aqui ainda, depois de %d Ctrl-C's.\n", ctrlc_count);
        } else { /* suspend foi interrompido por outro sinal (SIGALRM) */
            /* nada a fazer */
        }
    }
    fprintf(stderr,"\nOk, captei o seu sinal de usuário.\n");
}

/* -----------------------------------------------------------------------
 * wait_for_a_number: o loop só termina depois de se contar até três.
 * tentativas de terminar o programa com Ctrl-C ou SIGUSR1 não têm sucesso.
 */
void wait_for_a_number(void)
{
    int last_time;
    fprintf(stderr,"\nContando:\n");
    
    last_time = time_signal_count = 1; /* Vamos marcar o tempo a partir daqui, agora. */
    fprintf(stderr,"   %d...\n", time_signal_count);
    while (time_signal_count<3) {
        my_sigsuspend();
        if (time_signal_count!=last_time) { /* suspend interrompido pelo timer */
            last_time = time_signal_count;
            fprintf(stderr,"   %d...\n", time_signal_count);
        } else { /* suspend interrompido por algum outro sinal */
            fprintf(stderr,"      Não me perturbe, estou contando!\n");
        }
    }
    fprintf(stderr,"Fim.\n");
}

/* ---------------------------------------------------------------------------------
 * Para definir um sinal, pode-se usar signal ou sigaction. As versões mais recentes
 * do sistema recomendam a segunda. Como ela é mais chata de se usar, a função a 
 * seguir, my_sigaction, encapsula o tratamento dos parâmetros para o caso mais
 * simples, usado neste exemplo.
 */
void
my_sigaction( int signum, signal_handler_t handler )
{
    struct sigaction act;
    sigset_t         set;

    sigemptyset(&set);
    act.sa_handler = handler;
    act.sa_mask    = set;
    act.sa_flags   = 0;
    sigaction(signum, &act, NULL);
}

/* --------------------------------------------------------------------------------
 * Este programa não tem nada para fazer, senão esperar pelos sinais. Para não 
 * manter o programa em um loop infinito, sigsuspend permite que o programa pare
 * até que um sinal seja detectado.
 */
void
my_sigsuspend(void)
{
    /*
     * sigsuspend trava até que haja um sinal, por isso o único retorno esperado
     * é com valor -1 e errno com valor EINTR. Nesse caso, isso é o desejado; 
     * qualquer outra coisa é erro.
     */
    sigset_t themask; /* parâmetro obrigatório do sigsuspend, não usado aqui... */
    sigprocmask(SIG_SETMASK,NULL,&themask); /* ... mas precisa ser inicializado */

    if (sigsuspend(&themask)!=-1) {
        perror("sigsuspend voltou com valor inesperado!\n");
        exit(1);
    } else if (errno!=EINTR) {
        perror("sigsuspend voltou com erro!\n");
        exit(1);
    }
}

/* -----------------------------------------------------------------------------
 * A manipulação da temporização também exige um conjunto de parâmetros que
 * dá trabalho nos casos mais simples. Aqui, a função set_timer encapsula
 * a complicação toda, para facilitar o resto do programa.
 */
void set_timer(int s)
{
    struct  itimerval thetime;
    thetime.it_interval.tv_sec =  s;
    thetime.it_interval.tv_usec = 0;
    thetime.it_value.tv_sec =  s;
    thetime.it_value.tv_usec = 0;

    if (setitimer(ITIMER_REAL,&thetime,NULL)<0) {
        perror("setitimer");
        exit(1);
    }
}

/* --------------------------------------------------------------------------
 * Para mostrar os recursos, o tratador a seguir valerá para SIGUSR1 e Ctrl-C
 * Na prática, o melhor talvez seria ter um tratador para cada sinal
 */
void multi_signal_handler(int signum)
{
    if (signum==SIGINT) {
        ctrlc_count++;
    } else {
        forever = 0; /* Nada é para sempre... */
    }
    /* Pela semântica do Unix, o tratador tem que ser reiniciado a cada vez. */
    //signal(SIGUSR1,multi_signal_handler); /* Sinal do usuário */
    //signal(SIGINT,multi_signal_handler); /* Gerado pelo Ctrl-C, por exemplo */
}


/* ----------------------------------------------------------------------------
 * O tratador do relógio simplesmente "conta o tempo".
 */
void time_handler(int signum)
{
    time_signal_count++;
    /* Pela semântica do Unix, o tratador tem que ser reiniciado a cada vez. */
    //signal(SIGALRM,time_handler); 
}
