#include <unistd.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <time.h>
#include "signal_action.h"
#include "file_lock.h"

#include "babysit.h"

/* TODO:
 * - instead of using bytes, maybe use lines of text. But bytes are
 *   easier right now
 */

/* Thanks to Dean Gaudet for the idea to use a fd to replace a signal */
int sigchld_pipe[2];
#define READ_END 0
#define WRITE_END 1

/* child_pid == 0 means no child is running */
pid_t child_pid = 0;

/* Kill if pid != 0 */
#define kill_if(a, b) ((a) ? kill(a, b) : 0)

/* takes an fd.
 * Returns it if it can be close-on-execed. 
 * Otherwise, closes fd and returns -1
 */
int coe(int fd) {
    if (fd == -1) {
        return fd;
    }
    if (fcntl(fd, F_SETFD, FD_CLOEXEC) == 0) {
        return fd;
    }
    close(fd);
    return -1;
}

/* Check if the current working directory is valid.
 * return 1 on success, 0 on failure, sets errno
 */
int valid_wd(void) {
    struct stat wd;

    if (stat(".", &wd)) {
        return 0;
    }
    if (wd.st_nlink == 0) {
        errno = ENOENT;
        return 0;
    }
    return 1;
}
        
void sigchld_handler(int sig) {
    /* Have to worry about pids other than child_pid, because of a shell
     * script like:
     *
     * xterm &
     * exec babysit /blah/blah
     *
     * followed by an exit of the xterm. Have to waitpid(-1) to avoid
     * zombies.
     */

    const char *byte_ptr = "";
    pid_t pid;
       
    /* Looping here because there isn't necessarily one signal per child
     * in the queue */
    while ((pid = waitpid(-1, NULL, WNOHANG)) > 0) {
        if (pid == child_pid) {
            write(sigchld_pipe[WRITE_END], byte_ptr, 1);
            child_pid = 0;
        }
    }
    if ((pid == -1) && (errno != ECHILD)) {
        perror("waitpid");
        exit(1);
    }
}

void child(void) {
    char *const run_argv[] = {"./run", NULL};

    execv(*run_argv, run_argv);
    /* Must have failed */
    perror("execv ./run");
    exit(1);
}

/* Sleep at least as long as we tell it to, even through signals */
void sleep_dammit(int sec, int nsec) {
    struct timespec want; /* Time we want to sleep */
    struct timespec remain; /* Time remaining to sleep */

    want.tv_sec = sec;
    want.tv_nsec = nsec;
    while (nanosleep(&want, &remain) != 0) {
        if (errno == EINTR) {
            want = remain;
            continue;
        }
        perror("nanosleep");
        exit(1);
    }
}

/* Make a pipe that disappears on exec */
int no_exec_pipe(int filedes[2]) {
    int rc;

    rc = pipe(filedes);
    if (rc != 0) return rc;
    if ((coe(filedes[0]) == -1) || (coe(filedes[1]) == -1)) {
        return -1;
    }
    return 0;
}

/* Make a lock (+ file) that disappears on exec */
int no_exec_file_trylock(const char *lock_file_name) {
    int fd;

    fd = coe(open(lock_file_name, O_WRONLY|O_CREAT, 0600));
    if (fd == -1) {
        perror("open lock file");
        exit(1);
    }

    if (file_trylock(fd) != 0) {
        close(fd);
        return -1;
    }

    return fd;
}

int main(int argc, const char * const argv[]) {
    /* argv[1] is always set, so this is safe */
    const char *service_dir = argv[1];
    int fifo_fd;
    int rc;
    struct stat unused_stat_struct;
    char byte;
    struct pollfd pollarray[2];
/* For pollarray */
#define CONTROL_FIFO_SLOT 0
#define SIGCHLD_PIPE_SLOT 1
    int want_exit = 0;          /* Exit when child exits */
    int want_new_child = 1;     /* Start new child when child exits */
    int keep_wanting_new_child = 1; /* Maintain want_new_child when child exits */
    int need_a_break = 0;       /* Sleep 1 second after fork */

    sigset_t sigfullmask, sigemptymask;
    sigfillset(&sigfullmask);
    sigemptyset(&sigemptymask);

    /* Blocking signals everywhere possible */
    sigprocmask(SIG_SETMASK, &sigfullmask, NULL);

    if (argc != 2) {
       fprintf(stderr, "Usage: %s [service_dir]\n", argv[0]);
       exit(1);
    }

    if (signal_action(SIGCHLD, sigchld_handler) != 0) {
        perror("signal_action SIGCHLD");
        exit(1);
    }

    if (chdir(service_dir) != 0) {
        fprintf(stderr, "unable to chdir to %s: %s\n", service_dir, strerror(errno));
        exit(1);
    }

    if ((mkdir(BABYSIT_DIR, 0700) != 0) && (errno != EEXIST)) {
        perror("mkdir");
        exit(1);
    }

    file_lock_init();
    /* Don't need the fd, the unlock will happen on exit */
    if (no_exec_file_trylock(LOCK_FILE) == -1) {
        perror("Can't lock file, babysit is probably running already");
        exit(1);
    }

    if ((mkfifo(CONTROL_FIFO, 0600) != 0) && (errno != EEXIST)) {
        perror("mkfifo control fifo");
        exit(1);
    }

    /* Need to open both a read and write fd to the pipe so that it
     * doesn't go EOF on us. Linux lets us accomplish this with an
     * O_RDWR pipe, but others don't. We don't need the writable fd */
    fifo_fd = coe(open(CONTROL_FIFO, O_RDONLY|O_NONBLOCK));
    if ((fifo_fd == -1) || (coe(open(CONTROL_FIFO, O_WRONLY)) == -1)) {
        perror("open control fifo");
        exit(1);
    }

    if (stat(DOWN_FILE, &unused_stat_struct) == 0) {
        want_new_child = 0;
    }
     
    if (no_exec_pipe(sigchld_pipe) != 0) {
        perror("no_exec_pipe");
        exit(1);
    }

    pollarray[SIGCHLD_PIPE_SLOT].fd = sigchld_pipe[READ_END];
    pollarray[SIGCHLD_PIPE_SLOT].events = POLLIN;
    pollarray[CONTROL_FIFO_SLOT].fd = fifo_fd;
    pollarray[CONTROL_FIFO_SLOT].events = POLLIN;

    while (1) {
        if (!child_pid) {
            if (want_exit) {
                exit(0);
            }
            /* If the cwd doesn't exist, we really can't do anything.
             * babysit/control is inaccessible, and so is the run
             * script, so just give up.
             * Users: Don't depend on this behavior. */
            if (!valid_wd()) {
                perror("Bad babysit directory");
                exit(1);
            }
            if (want_new_child) {
                child_pid = fork();
                if (child_pid == 0) {
                    sigprocmask(SIG_SETMASK, &sigemptymask, NULL);
                    child();
                }
                else if (child_pid < 0) {
                    perror("fork");
                    exit(1);
                }
                want_new_child = keep_wanting_new_child;
                need_a_break = 1;
            }
        }

        sigprocmask(SIG_SETMASK, &sigemptymask, NULL);
        if (need_a_break) {
            /* Wait one second after fork to avoid insanity */
            sleep_dammit(1, 0);
            need_a_break = 0;
        }
        /* Block until either a signal or a byte on the control fifo */
        do {
            rc = poll(pollarray, 2, -1);
        } while ((rc == -1) && (errno == EINTR));
        sigprocmask(SIG_SETMASK, &sigfullmask, NULL);

        if (rc == -1) {
            perror("poll");
            exit(1);
        }
        if ((pollarray[SIGCHLD_PIPE_SLOT].revents
             | pollarray[CONTROL_FIFO_SLOT].revents)
            & (POLLERR|POLLHUP|POLLNVAL)) {
            fputs("poll error\n", stderr);
            exit(1);
        }

        if (pollarray[SIGCHLD_PIPE_SLOT].revents & POLLIN) {
            /* Just clear out the pipe */
            if (read(pollarray[SIGCHLD_PIPE_SLOT].fd, &byte, 1) != 1) {
                fputs("sigchld pipe read error\n", stderr);
                exit(1);
            }
        }
        if (pollarray[CONTROL_FIFO_SLOT].revents & POLLIN) {
            if (read(pollarray[CONTROL_FIFO_SLOT].fd, &byte, 1) == 1) {
                /* All the known error conditions for kill are not
                 * useful to check for */
                switch (byte) {
                    case 'u':
                        want_new_child = keep_wanting_new_child = 1;
                        break;
                    case 'd':
                        want_new_child = 0;
                        kill_if(child_pid, SIGTERM);
                        kill_if(child_pid, SIGCONT);
                        break;
                    case 'o':
                        want_new_child = 0;
                        break;
                    case 'p':
                        kill_if(child_pid, SIGSTOP);
                        break;
                    case 'c':
                        kill_if(child_pid, SIGCONT);
                        break;
                    case 'h':
                        kill_if(child_pid, SIGHUP);
                        break;
                    case 'a':
                        kill_if(child_pid, SIGALRM);
                        break;
                    case 'i':
                        kill_if(child_pid, SIGINT);
                        break;
                    case 't':
                        kill_if(child_pid, SIGTERM);
                        break;
                    case 'k':
                        kill_if(child_pid, SIGKILL);
                        break;
                    case 'x':
                        want_exit = 1;
                        break;
                }
            }
            else {
                fputs("control fifo read error\n", stderr);
                exit(1);
            }
        }
    }
}


syntax highlighted by Code2HTML, v. 0.9.1