#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/wait.h>


// available types
#define TYPE_STR 1
#define TYPE_CSTR 2
#define TYPE_INT 3

// lookup type names and format strings
#define TYPE_STR_1 char*
#define TYPE_STR_2 const char*
#define TYPE_STR_3 int
#define SCAN_STR_3 "%d"
#define GET_TYPE_STR_(T) TYPE_STR_##T
#define GET_TYPE_STR(T) GET_TYPE_STR_(T)
#define GET_SCAN_STR_(T) SCAN_STR_##T
#define GET_SCAN_STR(T) GET_SCAN_STR_(T)

// stringify
#define XSTR(s) STR(s)
#define STR(s) #s


// actual user configuration
// required defines: NAME, BINARY, RV, ERROR_RV, ARG1..3
#include "config.h"


// the exported function
GET_TYPE_STR(RV) NAME (
#define NUMARGS 0
#ifdef ARG1
    GET_TYPE_STR(ARG1) arg1
    #undef NUMARGS
    #define NUMARGS 1
#ifdef ARG2
    , GET_TYPE_STR(ARG2) arg2
    #undef NUMARGS
    #define NUMARGS 2
#ifdef ARG3
    , GET_TYPE_STR(ARG3) arg3
    #undef NUMARGS
    #define NUMARGS 3
#endif
#endif
#endif
    ) {

    // create argv to exec
    static const char* binary = XSTR(BINARY);
    const char* argv[NUMARGS+2];
    argv[0] = binary;
    argv[NUMARGS+1] = NULL;
    #define ARG2STR(i, t) char arg_s_##i[32]; argv[i+1] = arg_s_##i; sprintf(arg_s_##i, GET_SCAN_STR(t), arg##i)
    #if NUMARGS > 0
        #if ARG1 == TYPE_STR || ARG1 == TYPE_CSTR
            argv[1] = arg1;
        #else
            ARG2STR(1, ARG1);
        #endif
    #endif
    #if NUMARGS > 1
        #if ARG2 == TYPE_STR || ARG2 == TYPE_CSTR
            argv[2] = arg2;
        #else
            ARG2STR(2, ARG2);
        #endif
    #endif
    #if NUMARGS > 2
        #if ARG3 == TYPE_STR || ARG3 == TYPE_CSTR
            argv[3] = arg3;
        #else
            ARG2STR(3, ARG3);
        #endif
    #endif

    // pipe + fork + exec
    int pfd[2];
    if (pipe(pfd) == -1) {
        return ERROR_RV;
    }
    const pid_t pid = fork();
    if (pid == -1) {
        close(pfd[0]);
        close(pfd[1]);
        return ERROR_RV;
    } else if (pid == 0) {
        int null = open("/dev/null", O_RDWR);
        if (null == -1) raise(SIGTERM); // so we can distinguish real exit codes
        if (dup2(null, 0) == -1) raise(SIGTERM);
        if (dup2(pfd[1], 1) == -1) raise(SIGTERM);
        if (dup2(null, 2) == -1) raise(SIGTERM);
        int i;
        for (i=3; i<getdtablesize(); ++i) close(i);

        execvp(argv[0], (char* const*)argv);
        raise(SIGTERM);
        exit(1);
    } else {
        close(pfd[1]);
    }

    // read from pipe
    size_t len = 0;
    #if RV == TYPE_CSTR
        static char buf[4096];
        size_t alen = sizeof(buf);
    #else
        size_t alen = 32;
        char* buf = (char*)malloc(alen);
    #endif
    while (1) {
        ssize_t rv = read(pfd[0], buf+len, alen-len-1);
        if (rv == -1) {
            if (errno == EINTR) {
                continue;
            } else { // EPIPE?
                alen = 0; // indicates error
                break; // send some kill here?
            }
        } else if (rv == 0) {
            break; // eof
        } else {
            len += rv;
            if (len >= alen-1) {
                #if RV == TYPE_CSTR
                    alen = 0;
                    break;
                #else
                    alen *= 2;
                    buf = realloc(buf, alen);
                #endif
            }
        }
    }
    close(pfd[0]);

    // wait in any case
    int status;
    while (1) {
        pid_t rv = waitpid(pid, &status, 0);
        if (rv == -1) {
            if (errno == EINTR) {
                continue;
            } else {
                #if RV != TYPE_CSTR
                    free(buf);
                #endif
                return ERROR_RV;
            }
        } else {
            break;
        }
    }
    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
        #if RV != TYPE_CSTR
            free(buf);
        #endif
        return ERROR_RV;
    }

    // parse into rv
    if (!alen) {
        #if RV != TYPE_CSTR
            free(buf);
        #endif
        return ERROR_RV;
    }
    buf[len] = '\0';
    #if RV == TYPE_STR || RV == TYPE_CSTR
        return buf;
    #else
        GET_TYPE_STR(RV) rv;
        if (sscanf(buf, GET_SCAN_STR(RV), &rv) != 1) {
            rv = ERROR_RV;
        }
        free(buf);
        return rv;
    #endif
}