// SPDX-License-Identifier: GPL-3.0-or-later

#include "../libnetdata.h"

/*
struct mypopen {
    pid_t pid;
    FILE *fp;
    struct mypopen *next;
    struct mypopen *prev;
};

static struct mypopen *mypopen_root = NULL;

static void mypopen_add(FILE *fp, pid_t *pid) {
    struct mypopen *mp = malloc(sizeof(struct mypopen));
    if(!mp) {
        fatal("Cannot allocate %zu bytes", sizeof(struct mypopen))
        return;
    }

    mp->fp = fp;
    mp->pid = pid;
    mp->next = popen_root;
    mp->prev = NULL;
    if(mypopen_root) mypopen_root->prev = mp;
    mypopen_root = mp;
}

static void mypopen_del(FILE *fp) {
    struct mypopen *mp;

    for(mp = mypopen_root; mp; mp = mp->next)
        if(mp->fd == fp) break;

    if(!mp) error("Cannot find mypopen() file pointer in open childs.");
    else {
        if(mp->next) mp->next->prev = mp->prev;
        if(mp->prev) mp->prev->next = mp->next;
        if(mypopen_root == mp) mypopen_root = mp->next;
        free(mp);
    }
}
*/
#define PIPE_READ 0
#define PIPE_WRITE 1

static inline FILE *custom_popene(const char *command, volatile pid_t *pidptr, char **env) {
    FILE *fp;
    int pipefd[2], error;
    pid_t pid;
    char *const spawn_argv[] = {
            "sh",
            "-c",
            (char *)command,
            NULL
    };
    posix_spawnattr_t attr;
    posix_spawn_file_actions_t fa;

    if(pipe(pipefd) == -1)
        return NULL;
    if ((fp = fdopen(pipefd[PIPE_READ], "r")) == NULL) {
        goto error_after_pipe;
    }

    // Mark all files to be closed by the exec() stage of posix_spawn()
    int i;
    for(i = (int) (sysconf(_SC_OPEN_MAX) - 1); i >= 0; i--)
        if(i != STDIN_FILENO && i != STDERR_FILENO)
            fcntl(i, F_SETFD, FD_CLOEXEC);

    if (!posix_spawn_file_actions_init(&fa)) {
        // move the pipe to stdout in the child
        if (posix_spawn_file_actions_adddup2(&fa, pipefd[PIPE_WRITE], STDOUT_FILENO)) {
            error("posix_spawn_file_actions_adddup2() failed");
            goto error_after_posix_spawn_file_actions_init;
        }
    } else {
        error("posix_spawn_file_actions_init() failed.");
        goto error_after_pipe;
    }
    if (!(error = posix_spawnattr_init(&attr))) {
        // reset all signals in the child
        sigset_t mask;

        if (posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF))
            error("posix_spawnattr_setflags() failed.");
        sigemptyset(&mask);
        if (posix_spawnattr_setsigmask(&attr, &mask))
            error("posix_spawnattr_setsigmask() failed.");
    } else {
        error("posix_spawnattr_init() failed.");
    }
    if (!posix_spawn(&pid, "/bin/sh", &fa, &attr, spawn_argv, env)) {
        *pidptr = pid;
        debug(D_CHILDS, "Spawned command: '%s' on pid %d from parent pid %d.", command, pid, getpid());
    } else {
        error("Failed to spawn command: '%s' from parent pid %d.", command, getpid());
        close(pipefd[PIPE_READ]);
        fp = NULL;
    }
    close(pipefd[PIPE_WRITE]);

    if (!error) {
        // posix_spawnattr_init() succeeded
        if (posix_spawnattr_destroy(&attr))
            error("posix_spawnattr_destroy");
    }
    if (posix_spawn_file_actions_destroy(&fa))
        error("posix_spawn_file_actions_destroy");

    return fp;

error_after_posix_spawn_file_actions_init:
    if (posix_spawn_file_actions_destroy(&fa))
        error("posix_spawn_file_actions_destroy");
error_after_pipe:
    close(pipefd[PIPE_READ]);
    close(pipefd[PIPE_WRITE]);
    return NULL;
}

// See man environ
extern char **environ;

FILE *mypopen(const char *command, volatile pid_t *pidptr) {
    return custom_popene(command, pidptr, environ);
}

FILE *mypopene(const char *command, volatile pid_t *pidptr, char **env) {
    return custom_popene(command, pidptr, env);
}

int mypclose(FILE *fp, pid_t pid) {
    debug(D_EXIT, "Request to mypclose() on pid %d", pid);

    /*mypopen_del(fp);*/

    // close the pipe fd
    // this is required in musl
    // without it the childs do not exit
    close(fileno(fp));

    // close the pipe file pointer
    fclose(fp);

    errno = 0;

    siginfo_t info;
    if(waitid(P_PID, (id_t) pid, &info, WEXITED) != -1) {
        switch(info.si_code) {
            case CLD_EXITED:
                if(info.si_status)
                    error("child pid %d exited with code %d.", info.si_pid, info.si_status);
                return(info.si_status);

            case CLD_KILLED:
                error("child pid %d killed by signal %d.", info.si_pid, info.si_status);
                return(-1);

            case CLD_DUMPED:
                error("child pid %d core dumped by signal %d.", info.si_pid, info.si_status);
                return(-2);

            case CLD_STOPPED:
                error("child pid %d stopped by signal %d.", info.si_pid, info.si_status);
                return(0);

            case CLD_TRAPPED:
                error("child pid %d trapped by signal %d.", info.si_pid, info.si_status);
                return(-4);

            case CLD_CONTINUED:
                error("child pid %d continued by signal %d.", info.si_pid, info.si_status);
                return(0);

            default:
                error("child pid %d gave us a SIGCHLD with code %d and status %d.", info.si_pid, info.si_code, info.si_status);
                return(-5);
        }
    }
    else
        error("Cannot waitid() for pid %d", pid);
    
    return 0;
}
