
#include "Bootstrap.h"
#include <dlfcn.h>
#include <pthread.h>
#include <sys/event.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

typedef struct {
    int (*main)(int, const char *[]);
    int (*qemu_init)(int, const char *[], const char *[]);
    void (*qemu_main_loop)(void);
    void (*qemu_cleanup)(void);
    int (*swtpm_main)(int argc, const char *argv[], const char *prgname, const char *iface);
} qemu_main_t;

// http://mac-os-x.10953.n7.nabble.com/Ensure-NSTask-terminates-when-parent-application-does-td31477.html
static void *WatchForParentTermination(void *args) {
    pid_t ppid = getppid(); // get parent pid

    int kq = kqueue();
    if (kq != -1) {
        struct kevent procEvent; // wait for parent to exit
        EV_SET(&procEvent, // kevent
               ppid, // ident
               EVFILT_PROC, // filter
               EV_ADD, // flags
               NOTE_EXIT, // fflags
               0, // data
               0); // udata
        kevent(kq, &procEvent, 1, &procEvent, 1, NULL);
    }

    exit(0);
    return NULL;
}

static int loadQemu(const char *dylibPath, qemu_main_t *funcs) {
    void *dlctx;
    
    if ((dlctx = dlopen(dylibPath, RTLD_LOCAL | RTLD_LAZY | RTLD_FIRST)) == NULL) {
        fprintf(stderr, "Error loading %s: %s\n", dylibPath, dlerror());
        return -1;
    }
    funcs->main = dlsym(dlctx, "main");
    funcs->qemu_init = dlsym(dlctx, "qemu_init");
    funcs->qemu_main_loop = dlsym(dlctx, "qemu_main_loop");
    funcs->qemu_cleanup = dlsym(dlctx, "qemu_cleanup");
    funcs->swtpm_main = dlsym(dlctx, "swtpm_main");
    if (funcs->main == NULL && funcs->swtpm_main == NULL && (funcs->qemu_init == NULL || funcs->qemu_main_loop == NULL || funcs->qemu_cleanup == NULL)) {
        fprintf(stderr, "Error resolving %s: %s\n", dylibPath, dlerror());
        return -1;
    }
    return 0;
}

static void __attribute__((noreturn)) runQemu(qemu_main_t *funcs, int argc, const char **argv, const char **envp) {
    if (funcs->qemu_init) {
        funcs->qemu_init(argc, argv, envp);
    }
    pthread_t thread;
    pthread_create(&thread, NULL, WatchForParentTermination, NULL);
    pthread_detach(thread);
    if (funcs->main) {
        funcs->main(argc, argv);
    } else if (funcs->swtpm_main) {
        funcs->swtpm_main(argc, argv, "swtpm", "socket");
    } else {
        funcs->qemu_main_loop();
        funcs->qemu_cleanup();
    }
    exit(0);
}

pid_t startQemuFork(const char *dylibPath, int argc, const char **argv, const char **envp, int newStdout, int newStderr) {
    qemu_main_t funcs = {};
    int res = loadQemu(dylibPath, &funcs);
    if (res < 0) {
        return res;
    }
    pid_t pid = fork();
    if (pid != 0) { // parent or error
        return pid;
    }
    // set up console output
    dup2(newStdout, STDOUT_FILENO);
    dup2(newStderr, STDERR_FILENO);
    close(newStdout);
    close(newStderr);
    // set thread QoS
    pthread_set_qos_class_self_np(QOS_CLASS_USER_INTERACTIVE, 0);
    // launch qemu
    runQemu(&funcs, argc, argv, envp);
}

int startQemuProcess(const char *dylibPath, int argc, const char **argv, const char **envp) {
    qemu_main_t funcs = {};
    int res = loadQemu(dylibPath, &funcs);
    if (res < 0) {
        return res;
    }
    // launch qemu
    runQemu(&funcs, argc, argv, envp);
    return 0;
}
