/*
** $Id: cattester.c 1654 2006-05-29 18:18:02Z andre $
** cattester - Watchcat Tester
** See copyright notice in watchcatd distro's COPYRIGHT file
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <errno.h>
#include <sched.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/un.h>
#include <sys/wait.h>
#include <dirent.h>

#include <watchcat.h>

#define NCHILDREN 5

static volatile sig_atomic_t dead_child_count;
static volatile sig_atomic_t sig_usr1_count;
static int fds[NCHILDREN][2];

static void dream(int sec)
{
    while ((sec = sleep(sec)) > 0)
        ;
}

static void exec_master(char *rconf)
{
    char real_path[256];
    char master[256];
    char conf[256];

    assert(getcwd(conf, sizeof(conf)));
    strcat(conf, "/");
    strcat(conf, rconf);

    assert(getcwd(real_path, sizeof(real_path)));
    *strrchr(real_path, '/') = '\0'; /* real_path/.. */

    sprintf(master, "%s/catmaster", real_path);

    assert(execl(master, master, "-Xf", conf, "-p", real_path, NULL) == 0);
}

static void sig_usr1(int sig)
{
    sig_usr1_count++;
}

static void test_lib_use(void)
{
    int cat;
    struct sigaction act, oact;

    sig_usr1_count = 0;
    
    /* Handle SIGUSR1. */
    act.sa_handler = sig_usr1;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    assert(sigaction(SIGUSR1, &act, &oact) == 0);

    /* Test normal use. */
    cat = cat_open1(5, SIGUSR1, "sample");
    assert(cat >= 0);
    dream(2);
    assert(cat_heartbeat(cat) == 0);
    dream(2);
    assert(cat_heartbeat(cat) == 0);
    dream(3);
    assert(cat_close(cat) == 0);

    /* Test valid use. */
    cat = cat_open1(5, SIGUSR1, 
            "123456789012345678901234567890123456789012345678901234567890"
            "123456789012345678901234567890123456789012345678901234567890"
            "123456789012345678901234567890123456789012345678901234567890"
            "123456789012345678901234567890123456789012345678901234567890");
    assert(cat >= 0);
    assert(cat_close(cat) == 0);
    cat = cat_open1(5, SIGUSR1, NULL);
    assert(cat >= 0);
    assert(cat_close(cat) == 0);
    cat = cat_open1(5, SIGUSR1, "");
    assert(cat >= 0);
    assert(cat_close(cat) == 0);
    cat = cat_open1(5, SIGUSR1, "sácánágem\n\0test");
    assert(cat >= 0);
    assert(cat_heartbeat(cat) == 0);
    dream(2);
    assert(cat_heartbeat(cat) == 0);
    dream(2);
    assert(cat_heartbeat(cat) == 0);
    assert(cat_heartbeat(cat) == 0);
    dream(3);
    assert(cat_heartbeat(cat) == 0);
    assert(cat_heartbeat(cat) == 0);
    assert(cat_close(cat) == 0);

    /* Teste invalid use. */
    assert(cat_heartbeat(cat) == -1);
    assert(errno == EBADF);
    assert(cat_close(cat) == -1);
    assert(errno == EBADF);
    assert(cat_open1(0, SIGUSR1, NULL) == -1); /* Invalid timeout. */
    assert(errno == EPERM);
    assert(cat_open1(10, 1024, NULL) == -1);   /* Invalid signal. */
    assert(errno == EPERM);

    /* Restore SIGUSR1 handler. */
    assert(sigaction(SIGUSR1, &oact, NULL) == 0);
    assert(sig_usr1_count == 0);
}

static void TELL_WAIT(void)
{
    int i;

    for (i = 0; i < NCHILDREN; i++)
        assert(socketpair(AF_UNIX, SOCK_STREAM, 0, fds[i]) == 0);
}

static void TELL_PARENT(int i)
{
    assert(write(fds[i][1], "c", 1) == 1);
}

static void WAIT_PARENT(int i)
{
    char c;

    assert(read(fds[i][1], &c, 1) == 1);
    assert(c == 'p');
}

static void TELL_CHILDREN(void)
{
    int i;

    for (i = 0; i < NCHILDREN; i++)
        assert(write(fds[i][0], "p", 1) == 1);
}

static void WAIT_CHILDREN(void)
{
    char c;
    int i, n, count = NCHILDREN;
    fd_set rset, saved_set;

    FD_ZERO(&saved_set);
    for (i = 0; i < NCHILDREN; i++)
        FD_SET(fds[i][0], &saved_set);

    while (count > 0) {
        rset = saved_set;
        n = select(fds[NCHILDREN-1][0] + 1, &rset, NULL, NULL, NULL);
        if (n == -1) {
            if (errno == EINTR)
                continue;
            perror("select");
            exit(errno);
        }
        for (i = 0; i < NCHILDREN; i++)
            if (FD_ISSET(fds[i][0], &rset)) {
                assert(read(fds[i][0], &c, 1) == 1);
                assert(c == 'c');
                FD_CLR(fds[i][0], &saved_set);
            }
        count -= n;
    }
}

static void sig_chld(int sig)
{
    pid_t pid;
    int status;

    while ((pid = waitpid(-1, &status, WNOHANG)) > 0)
        dead_child_count++;
}

static void test_max_conections(void)
{
    int i;
    pid_t daemon_pid;
    struct sigaction act, oact;
 
    dead_child_count = 0;

    /* Handle SIGCHLD. */
    act.sa_handler = sig_chld;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    assert(sigaction(SIGCHLD, &act, &oact) == 0);

    daemon_pid = fork();
    assert(daemon_pid >= 0);

    if (daemon_pid == 0) {
        /* Son. */
        exec_master("test_max_conections.conf");
    }
    dream(3);

    /* Test global max conections and max conections by pid. */
    TELL_WAIT();
    for (i = 0; i < NCHILDREN; i++) {
        pid_t pid = fork();
        assert(pid != -1);

        if (pid == 0) {
            /* Son. */
            int cat;
            cat = cat_open1(20, SIGKILL, NULL);
            assert(cat >= 0);
            assert(cat_open1(10, SIGKILL, "explode the cat") >= 0);
            assert(cat_open() == -1);

            TELL_PARENT(i);
            WAIT_PARENT(i);

            while (1) {
                /* Try produce a SIGPIPE. If this occurs is a BUG! */
                cat_open();

                /* Complicate the life of watchcatd. */
                assert(cat_heartbeat(cat) == 0);
            }
            /* NOTREACHED */
        }
    }
    WAIT_CHILDREN();
    assert(cat_open1(10, SIGKILL, NULL) == -1);

    TELL_CHILDREN();
    dream(11);

    assert(kill(daemon_pid, SIGTERM) == 0);
    dream(3);

    /* Restore SIGCHLD handler. */
    assert(sigaction(SIGCHLD, &oact, NULL) == 0);
    assert(dead_child_count == NCHILDREN + 1);
}

static void test_configuration_parser(void)
{
    pid_t chld_pid;
    int status;
    char path[256];
    struct dirent *d;
    DIR *dp;

    dp = opendir("confs");
    assert(dp != NULL);

    while ((d = readdir(dp)) != NULL) {
        if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0
            || strcmp(d->d_name, "CVS") == 0)
            continue;

        assert(snprintf(path, sizeof path,"confs/%s", d->d_name) < sizeof path);

        chld_pid = fork();
        assert(chld_pid >= 0);
        if (chld_pid == 0) /* child */
            exec_master(path);

        /* parent */
        waitpid(chld_pid, &status, 0);
        if (WIFEXITED(&status)) {
            assert(WEXITSTATUS(&status) != 0);
            assert(WIFSIGNALED(&status) == 0);
        }
    }
    closedir(dp);
}

static int timediff(struct timeval tv1, struct timeval tv2)
{
    return (tv1.tv_sec - tv2.tv_sec) * 1000 +
           (tv1.tv_usec - tv2.tv_usec) / 1000;
}

static void test_timeout(void)
{
    int i, cat, ntmouts;
    int tmouts[5] = { 5, 10, 15, 30, 60 };
    sigset_t newmask, oldmask;
    struct sigaction act, oact;
    struct timeval tv_now, tv_sig;

    sig_usr1_count = 0;

    /* Block all signals except SIGUSR1 */
    sigfillset(&newmask);
    sigdelset(&newmask, SIGUSR1);
    assert(sigprocmask(SIG_BLOCK, &newmask, &oldmask) == 0);

    act.sa_handler = sig_usr1;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    assert(sigaction(SIGUSR1, &act, &oact) == 0);

    ntmouts = sizeof tmouts;
    for (i = 0; i < ntmouts; i++) {
        cat = cat_open1(tmouts[i], SIGUSR1, "timeout");
        assert(gettimeofday(&tv_now, NULL) == 0);
        assert(cat >= 0);
        pause();
        /* we got a SIGUSR1 */
        assert(gettimeofday(&tv_sig, NULL) == 0);
        assert(cat_close(cat) == 0);
        assert(timediff(tv_sig, tv_now) <= 500);
    }

    /* Restore old mask and SIGUSR1 handler. */
    assert(sigprocmask(SIG_SETMASK, &oldmask, NULL) == 0);
    assert(sigaction(SIGUSR1, &oact, NULL) == 0);
    assert(ntmouts == sig_usr1_count);
}

static ssize_t safe_write(int fd, const void *buf, size_t count)
{   
    struct sigaction act, oact;
    ssize_t r;
    int e, s;

    /* Ignore SIGPIPE. */
    act.sa_handler = SIG_IGN;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    while ((s = sigaction(SIGPIPE, &act, &oact)) == -1 &&
           errno == EINTR)
        ;
    if (s != 0)
        return -1;

    do {
        r = write(fd, buf, count);
    } while(r == -1 && errno == EINTR);
    e = errno;

    /* Restore SIGPIPE handler. */
    while ((s = sigaction(SIGPIPE, &oact, NULL)) == -1 &&
           errno == EINTR)
    if (s != 0)
        abort();

    errno = e;
    return r;
}

static ssize_t safe_read(int fd, void *buf, size_t count)
{
    ssize_t r;
    do {
        r = read(fd, buf, count);
    } while(r == -1 && errno == EINTR);
    return r;
}

static int connect_to_cat(void)
{
    int sockfd;
    socklen_t len;
    struct sockaddr_un sun;

    assert((sockfd = socket(PF_UNIX, SOCK_STREAM, 0)) > 0);

    memset(&sun, 0, sizeof sun);
    sun.sun_family = PF_UNIX;
    strncpy(sun.sun_path, "/tmp/watchcat", sizeof(sun.sun_path) - 1);
    sun.sun_path[sizeof(sun.sun_path) - 1] = '\0';

    len = sizeof(sun.sun_family) + strlen(sun.sun_path);
    assert(connect(sockfd, (struct sockaddr *)&sun, len) == 0);

    return sockfd;
}

static void test_bad_conections(void)
{
    int n, r, sockfd;
    char sendbuf[128], recvbuf[128];
	char bigbuf[1024];
    FILE *fp;

    /*
     * Header with typos
     */
    sockfd = connect_to_cat();
    n = snprintf(sendbuf, sizeof sendbuf,
                 "vesrion: 1\ntmout: %i\nsignal: %i\n\n", 15, SIGURG);
    assert(n > 0 && n < sizeof sendbuf);

    memset(recvbuf, 0, sizeof recvbuf);
    assert((r = safe_write(sockfd, sendbuf, n)) == n);
    assert((r = safe_read(sockfd, recvbuf, sizeof recvbuf)) != -1);
    assert(strcmp(recvbuf, "error\n") == 0);

    /*
     * Missing version on header.
     */
    sockfd = connect_to_cat();
    n = snprintf(sendbuf, sizeof sendbuf,
                 "timeout: %i\nsignal: %i\n\n", 15, SIGURG);
    assert(n > 0 && n < sizeof sendbuf);

    memset(recvbuf, 0, sizeof recvbuf);
    assert((r = safe_write(sockfd, sendbuf, n)) == n);
    assert((r = safe_read(sockfd, recvbuf, sizeof recvbuf)) != -1);
    assert(strcmp(recvbuf, "error\n") == 0);

    /*
     * Missing `\n'.
     */
    sockfd = connect_to_cat();
    n = snprintf(sendbuf, sizeof sendbuf,
                 "version: 1\ntimeout: %i\nsignal: %i\n", 15, SIGKILL);
    assert(n > 0 && n < sizeof sendbuf);

    memset(recvbuf, 0, sizeof recvbuf);
    assert((r = safe_write(sockfd, sendbuf, n)) == n);
    assert((r = safe_read(sockfd, recvbuf, sizeof recvbuf)) != -1);
    assert(strcmp(recvbuf, "timeout\n") == 0);

    /*
     * Sending big random data.
     */
#define RANDOM_DATA "asdçlasfgklwcvrvmfnbflbmf.blg.pwoefievggfçvl,z\asdfçldfm.bmvdbnxvx//gf;f;d.vdfvw;dfçwepqeweldklfgdfmçblfbçgmblçg,bvlxcvbxncvdfkvjfkglçfgjfkgfgjsdlfklsdgçfgkfsglfdbv,dfblçdvwieporeirwopeturiotyer89t0345u34iotu438t9023ruweiruseofijeriogyiwooo13112po4j23opurrgpgh9tu8-e0gek vlksdskdqweopdkiweopf ,vowmwcwecvojmvl mwcvwimecpowmca\niopwdmcposmczxpcm\t\n\tndfosdkfsdçlcm,sdçlksçmtçkdçsvglsdfm,vçldfmvçlam,dç\açldkfsdçfksdvfçdlvgkfvvsaqçksçdkwef,wcvçvltmobhpms^sdf Ç bdfvdfvsac*DQWD&#r28ery68787687686*&$% %&S$A%S4%4567clsdaçckdcçdlskcvdsç,vdsssssgfdsgfdgçlfkkkkasdsd,dfd,gmmg,fmgf,gmflgkfjsdklfkdlfkdlfkdsfcsdmcknaqkwlqeqwprpé2-39123904320r23kqasod=q=-+_{{}{`````AS''''''''ASDFASF SDCLÇSDKCSDÇLVKSÇDVL ÀW feÈRGFvs ËFGSDF ADLÇ     ASLDKÇSDFDSFDSFMDS,FMKDLFJJIOOPBVJFBVDFVNDFJVNNNASJDANSDPÉRQWOERPQWER,.;Q.,M,  .mwefwelkrjfqwqoeiqwoireiouiyidmandçv,s"

#define NRAND sizeof(RANDOM_DATA)

    sockfd = connect_to_cat();
    memset(recvbuf, 0, sizeof recvbuf);
    assert((r = safe_write(sockfd, RANDOM_DATA, NRAND)) == NRAND);
    assert((r = safe_read(sockfd, recvbuf, sizeof recvbuf)) != -1);
    assert(strcmp(recvbuf, "error\n") == 0);

    /*
     * Sending binary data.
     */
    assert((fp = fopen("/bin/sh", "r")) != NULL);
    assert(fread(sendbuf, sizeof sendbuf, 1, fp) == 1);
	n = sizeof(sendbuf);
    
    sockfd = connect_to_cat();

    memset(recvbuf, 0, sizeof recvbuf);
    assert((r = safe_write(sockfd, sendbuf, n)) == n);
    assert((r = safe_read(sockfd, recvbuf, sizeof recvbuf)) != -1);
    assert(strcmp(recvbuf, "error\n") == 0);

    /*
     * Sending a big line.
     */
#define LONG_INFO "asdaskdjlskdslackwdaclkwdac,waokfwedkowedsaxcld,scaslckaadcmsdkvnsdsdcksdjclksmcskldcsdkcsdlaasksdfklasdflkawefowifpwiftaasdqwr"
    sockfd = connect_to_cat();
    n = snprintf(bigbuf, sizeof bigbuf,
                 "version: 1\ntimeout: %i\nsignal: %i\ninfo: %s\n\n",
				 15, SIGKILL, LONG_INFO);
    assert(n > 0 && n < sizeof bigbuf);

    memset(recvbuf, 0, sizeof recvbuf);
    assert((r = safe_write(sockfd, bigbuf, n)) == n);
    assert((r = safe_read(sockfd, recvbuf, sizeof recvbuf)) != -1);
    assert(strcmp(recvbuf, "error\n") == 0);
}

static void test_child_all(void)
{
    int N = 5;

    /* Repeat all tests N times. */
    while (N--) {
        int T = 10;
        while (T--) {
            pid_t pid = fork();
            assert(pid >= 0);
            if (pid == 0) {
                /* Son. */
                test_lib_use();
                test_timeout();
                test_bad_conections();
                exit(0);
            }
        }
        dream(60);
    }
}

int main(void)
{
    struct sigaction act, oact;
    pid_t daemon_pid;
    struct sched_param sp;

    printf("This will take a long time...\n");
    cat_set_device("/tmp/watchcat");

    test_configuration_parser();
    test_max_conections();
    
    /* Ignore SIGCHLD. */
    act.sa_handler = SIG_IGN;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    assert(sigaction(SIGCHLD, &act, &oact) == 0);

    daemon_pid = fork();
    assert(daemon_pid >= 0);

    if (daemon_pid == 0) {
        /* Son. */
        exec_master("test_general.conf");
    }
    dream(3);

    test_child_all();

    sp.sched_priority = 99;
    assert(sched_setscheduler(0, SCHED_RR, &sp) == 0);
    test_child_all();

    assert(kill(daemon_pid, SIGTERM) == 0);
    dream(3);
    assert(unlink("/tmp/watchcat") == 0);

    /* Restore SIGCHLD handler. */
    assert(sigaction(SIGCHLD, &oact, NULL) == 0);

    return 0;
}
