/**
 * This file group all function which interact with system operation
 */

#ifndef _SYSTEM
#define _SYSTEM

#include "prototype.h"

#include <errno.h>
#include <unistd.h>
#include <sys/mount.h>
#include <sys/syscall.h>
#include <signal.h>
#include <sys/stat.h>
#include <stdbool.h>

#include <sys/wait.h>
#include <string.h>

#define OLDROOT "/.old"

/**
 * Wrapper for pivot root syscall
 */
int pivot_root(char *mount_dir)
{
    char *inner_mount_dir = malloc(
        (sizeof(*(mount_dir)) * strlen(mount_dir) + 1) +
        strlen(OLDROOT));
    strcpy(inner_mount_dir, mount_dir);
    strcat(inner_mount_dir, OLDROOT);

    if (mount("/", "/", "bind", MS_REC | MS_PRIVATE, NULL))
    {
        error("error remount / with MS_PRIVATE");
    }

    if (mount(mount_dir, mount_dir, "bind", MS_BIND | MS_REC, NULL) < 0)
    {
        error("error mount");
    }
    if (mkdir(inner_mount_dir, 0755) != 0)
    {
        if (errno != EEXIST)
        {
            error("error mkdir");
        }
    }

    int syscall_result = syscall(SYS_pivot_root, mount_dir, inner_mount_dir);

    // deal with old mount directory
    if (umount2(OLDROOT, MNT_DETACH) != 0)
    {
        error("umount old directory failed");
    }
    if (rmdir(inner_mount_dir) != 0)
    {
        error("rm old directory failed");
    }
    free(inner_mount_dir);
    return syscall_result;
}

/**
 * Umount pivot root folder
 */
int clean_pivot_root(char *a)
{
    if (umount2(a, MNT_DETACH) != 0)
    {
        error("umount pivot_root directory failed");
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}

/**
 * Mount tmpfs into /dev and remount proc into /proc
 */
void remount()
{
    // remount devtmpfs device to /dev with type devtmpfs
    if (mount(NULL, "/dev", "devtmpfs", MS_NOSUID | MS_STRICTATIME, NULL))
    {
        error("failed mount tmpfs to /dev");
    }

    // remount proc device to /proc with type proc
    if (mount(NULL, "/proc", "proc", MS_MGC_VAL, NULL))
    {
        error("failed to re mount proc into /proc");
    }

    // remount sys device to /sys with type sysfs
    if (mount(NULL, "/sys", "sysfs", MS_MGC_VAL, NULL))
    {
        error("failed to re mount proc into /proc");
    }
}

/**
 * Run external binary
 */
int run_subprocess(char *const *argv)
{
    errno = execvp(argv[0], argv);
    return errno;
}

/**
 * Run external binary into new sub process
 */
int run_subprocess_async(int argc, char *const *argv)
{
    pid_t pid;
    bool run_interactive = contain_interactive(argc, argv);
    // if container is running in interactive mode remove "-i" from argv
    if (run_interactive)
    {
        char **dst_argv = (char **)malloc(sizeof(char *) * argc);
        argc = filter_char_array(argc, (char **)argv, dst_argv, is_not_interactive);
        argv = dst_argv;
    }

    // pipe to permit parent => child comunication
    int pipe_parent_to_child_fd[2];
    pipe(pipe_parent_to_child_fd);

    if ((pid = fork()) == -1)
    {
        error("fork() subprocess error");
    }
    else if (pid == 0)
    {
        close(pipe_parent_to_child_fd[1]); // close writing end in the child

        printf("[CHILD] : running\n");
        dup2(pipe_parent_to_child_fd[0], STDIN_FILENO); // send pipe to the stdin

        errno = execvp(argv[0], argv);
        return errno;
    }
    else
    {
        int child_status;

        close(pipe_parent_to_child_fd[0]); // close the read end of the pipe in the parent

        // if container is running in interactive mode
        if (run_interactive)
        {
            // read stdin write infinity
            write_infinite_fd(pipe_parent_to_child_fd[1]);
        }

        close(pipe_parent_to_child_fd[1]); // this descriptor is no longer needed

        // wait child if long task is in progress
        waitpid(pid, &child_status, 0);
        if (WIFEXITED(child_status))
        {
            int es = WEXITSTATUS(child_status);
            printf("[PARENT] Exit status was %d\n", es);
            return es;
        }
    }
    // reach when wait child failed
    return EXIT_FAILURE;
}

/**
 * Return true if char* str is equal to "-i"
 */
bool is_interactive(char *str)
{
    if (strcmp(str, "-i") == 0)
    {
        return true;
    }
    return false;
}

/**
 * Return true if char* str is not equal to "-i"
 * opposite of is_interactive
 */
bool is_not_interactive(char *str)
{
    return !is_interactive(str);
}

/**
 * Return true if argv char* array contain "-i"
 */
bool contain_interactive(int argc, char *const *argv)
{
    bool res = false;
    for (int i = 0; i < argc; i++)
    {
        if (is_interactive(argv[i]))
        {
            res = true;
            break;
        }
    }
    return res;
}

#endif
