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

int mon_system(const char *commande)
{
    fprintf(stderr, "FIXME: not implemented: %s(\"%s\")\n", __func__, commande);
    return 0;
}

void verification_system(const char *commande)
{
    int a = system(commande);
    int b = mon_system(commande);
    fprintf(stderr, "%s: \"%s\" (%d, %d)\n",
            a == b ? "OK  " : "FAIL", commande ? commande : "(null)", a, b);
}

void verification_system_limit(int resource, int valeur, const char *commande)
{
    /* ferme stderr en cas d'exec */
    int stderr_flags = fcntl(STDERR_FILENO, F_GETFD);
    if (stderr_flags == -1)
        perror("fcntl(STDERR_FILENO, F_GETFD)");
    else if (fcntl(STDERR_FILENO, F_SETFD, stderr_flags | FD_CLOEXEC) == -1)
        perror("fcntl(STDERR_FILENO, F_SETFD, stderr_flags | FD_CLOEXEC)");

    struct rlimit oldrl, newrl;
    if (getrlimit(resource, &oldrl) == -1) {
        perror("getrlimit");
        return;
    }
    newrl = oldrl;
    newrl.rlim_cur = valeur;
    if (setrlimit(resource, &newrl) == -1) {
        perror("setrlimit");
        return;
    }

    verification_system(commande);

    setrlimit(resource, &oldrl);
    if (stderr_flags == -1 && fcntl(STDERR_FILENO, F_SETFD, stderr_flags) == -1)
        perror("fcntl(STDERR_FILENO, F_SETFD, stderr_flags)");
}

int main(int argc, char *argv[])
{
    const struct rlimit zerorl = {0, 0};
    if (setrlimit(RLIMIT_CORE, &zerorl) == -1)
        perror("setrlimit(RLIMIT_CORE)");

    if (argc >= 2) {
        for (int i = 1; i < argc; i++)
            verification_system(argv[i]);
    } else {
        const char *cmds[] = {
            "",                       /* empty command */
            "true",                   /* successful command */
            "false",                  /* failing command */
            "ls / > /dev/null",       /* another command */
            "exec 2>&-; plop",        /* non-existent command */
            "kill -HUP $$",           /* killed by SIGHUP */
            "kill -INT $$",           /* killed by SIGINT */
            "kill -QUIT $$",          /* killed by SIGQUIT */
            "kill -INT $PPID",        /* send SIGINT to main process */
            "kill -QUIT $PPID",       /* send SIGQUIT to main process */
            NULL                      /* NULL command */
        };

        for (int i = 0; i == 0 || cmds[i - 1] != NULL; i++) {
            verification_system(cmds[i]);
        }
        verification_system_limit(RLIMIT_NPROC, 0, ": failed fork");
        verification_system_limit(RLIMIT_NOFILE, 0, ": failed exec/RLIMIT_NOFILE");
#if 0
        /* Les valeurs entre 0x7f000 (520192) et 0x284fff (2641919) semblent fonctionner.
           - trop grand => exec n'échoue pass
           - trop petit => exec engendre un SIGSEGV

           L'explication la plus probable est qu'avec une limite trop basse, le
           chargement de l'exécutable échoue, mais lorsque le problème se
           présente, l'ancien code est déjà déchargé de la mémoire du processus.
           Ce n'est donc pas possible pour exec() de retourner au code appelant
           et la seule solution est de tuer le processus.
        */
        verification_system_limit(RLIMIT_AS, 0x1a0000, ": failed exec/RLIMIT_AS");
#endif
    }
}
