#include "handler.hpp"
#include <sys/wait.h>
#include <fcntl.h>


// execs and writes stdout either to given fd or buf
// returns <0 upon error, 0 upon fd-write success or no data, >0 for buffer length
int handler(const char* bin, const char* arg, int wd, int outfd, char*& outbuf) {
    outbuf = NULL;

    // get common null fd - XXX: will be leaked
    static int nullfd = -1;
    if (nullfd == -1) {
        nullfd = open("/dev/null", O_RDWR); // XXX: additional leak upon mt race
        if (nullfd == -1) {
            return -errno;
        }
    }

    // create pipe in case we don't already have an fd to write to
    int infd = -1;
    if (outfd == -1) {
        int fds[2];
        if (pipe(fds) == -1) {
            return -errno;
        }
        infd = fds[0];
        outfd = fds[1];
    }

    // fork
    const pid_t pid = fork();
    if (pid == -1) {
        return -errno;
    }

    // child: exec
    if (pid == 0) {
        dup2(nullfd, STDIN_FILENO);
        dup2(nullfd, STDERR_FILENO);
        dup2(outfd, STDOUT_FILENO);
        if (fchdir(wd) == -1) { // not in the mountpoint but the original one
            _exit(1);
        }
        for (int i=3; i<getdtablesize(); ++i) {
            close(i);
        }
        const char* argv[] = {
            bin, arg, NULL
        };
        (void)execvpe(bin, (char* const*)argv, NULL);
        _exit(1);
    }
    LOG_DEBUG("spawned #%d: '%s' '%s'", pid, bin, arg);

    // read from pipe into given buffer, if needed
    int rv = 0;
    if (infd != -1) {
        (void)close(outfd);
        size_t len = 0;
        size_t alloc_len = 0;
        while (true) {
            if (alloc_len - len < 4096) {
                alloc_len += 4096;
                outbuf = (char*)realloc(outbuf, alloc_len);
            }
            ssize_t r = read(infd, outbuf+len, alloc_len-len-1);
            int e = errno;
            errno = e;
            if (r == -1) {
                rv = -errno;
                break;
            } else if (r == 0) { // eof -> can wait
                rv = len;
                outbuf[len] = '\0';
                //LOG("> %d:'%s'", len, outbuf);
                break;
            } else { // go on
                len += (size_t)r;
            }
        }
        (void)close(infd);
    }

    // wait
    int status;
    if (waitpid(pid, &status, 0) == -1) {
        rv = -errno;
    } else if (WIFEXITED(status)) {
        rv = (WEXITSTATUS(status) == 0)? rv: -EFAULT;
    } else if (WIFSIGNALED(status)) {
        rv = -EINTR;
    } else {
        rv = -EINVAL; // ??
    }

    // cleanup & done
    if (rv < 0 && outbuf) {
        free(outbuf);
        outbuf = NULL;
    }
    if (rv < 0) {
        LOG_ERR(-rv, "'%s' done", bin);
    } else {
        LOG_DEBUG_ERR(0, "'%s' done: %d", bin, rv);
    }
    return rv;
}