root/usr.bin/dfregress/testcase.c
/*
 * Copyright (c) 2011 Alex Hornung <alex@alexhornung.com>.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include <sys/resource.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>

#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <pwd.h>

#include <err.h>

#include <libprop/proplib.h>

#include "parser.h"
#include "testcase.h"
#include "runlist.h"
#include "config.h"
#include <dfregress.h>

prop_dictionary_t
testcase_from_struct(struct testcase *testcase)
{
        int i, r;
        prop_dictionary_t dict, testcase_dict;
        prop_array_t a;
        char *s;

        testcase_dict = prop_dictionary_create();
        if (testcase_dict == NULL)
                err(1, "could not create testcase dict");
        r = prop_dictionary_set_cstring(testcase_dict, "name", testcase->name);
        if (r == 0)
                err(1, "prop_dictionary operation failed");
        r = prop_dictionary_set_cstring(testcase_dict, "type", testcase->type_str);
        if (r == 0)
                err(1, "prop_dictionary operation failed");

        r = prop_dictionary_set_int32(testcase_dict, "argc",
            (int32_t)testcase->argc);
        if (r == 0)
                err(1, "prop_dictionary operation failed");

        a = prop_array_create_with_capacity(testcase->argc+1);
        if (a == NULL)
                err(1, "prop_array_create for argv failed");

        s = strrchr(testcase->name, '/');
        r = prop_array_set_cstring(a, 0, (s == NULL) ? testcase->name : s+1);
        if (r == 0)
                err(1, "prop_array_set_cstring operation failed");

        for (i = 1; i <= testcase->argc; i++) {
                r = prop_array_set_cstring(a, i, testcase->argv[i-1]);
                if (r == 0)
                        err(1, "prop_array_set_cstring operation failed");
        }

        r = prop_dictionary_set(testcase_dict, "args", a);
        if (r == 0)
                err(1, "prop_dictionary_set \"args\" failed");

        dict = prop_dictionary_create();
        if (dict == NULL)
                err(1, "could not create dict");

        r = prop_dictionary_set_int32(dict, "timeout_in_secs",
            (int32_t)testcase->opts.timeout_in_secs);
        if (r == 0)
                err(1, "prop_dictionary operation failed");

        r = prop_dictionary_set_int32(dict, "rc",
            (int32_t)testcase->opts.rc);
        if (r == 0)
                err(1, "prop_dictionary operation failed");

        r = prop_dictionary_set_uint32(dict, "flags", testcase->opts.flags);
        if (r == 0)
                err(1, "prop_dictionary operation failed");

        if (testcase->opts.pre_cmd != NULL) {
                r = prop_dictionary_set_cstring(dict, "pre_cmd",
                    testcase->opts.pre_cmd);
                if (r == 0)
                        err(1, "prop_dictionary operation failed");
        }

        if (testcase->opts.post_cmd != NULL) {
                r = prop_dictionary_set_cstring(dict, "post_cmd",
                    testcase->opts.post_cmd);
                if (r == 0)
                        err(1, "prop_dictionary operation failed");
        }

        if (testcase->opts.interpreter != NULL) {
                r = prop_dictionary_set_cstring(dict, "interpreter",
                    testcase->opts.interpreter);
                if (r == 0)
                        err(1, "prop_dictionary operation failed");
        }

        r = prop_dictionary_set_uint32(dict, "runas_uid",
            (uint32_t)testcase->opts.runas_uid);
        if (r == 0)
                err(1, "prop_dictionary operation failed");

        r = prop_dictionary_set_cstring(dict, "make_cmd",
            (testcase->opts.make_cmd != NULL) ? testcase->opts.make_cmd : "make");
        if (r == 0)
                err(1, "prop_dictionary operation failed");

        r = prop_dictionary_set(testcase_dict, "opts", dict);
        if (r == 0)
                err(1, "prop_dictionary operation failed");

        return testcase_dict;
}

struct timeval *
testcase_get_timeout(prop_dictionary_t testcase)
{
        static struct timeval tv;
        int32_t val;
        int r;

        r = prop_dictionary_get_int32(prop_dictionary_get(testcase, "opts"),
            "timeout_in_secs", &val);
        if (r == 0)
                err(1, "prop_dictionary operation failed");

        tv.tv_usec = 0;
        tv.tv_sec = (long)val;

        return &tv;
}

int
testcase_get_type(prop_dictionary_t testcase)
{
        const char *type;
        int r;

        r = prop_dictionary_get_cstring_nocopy(testcase, "type", &type);
        if (r == 0)
                err(1, "prop_dictionary operation failed");

        if (strcmp(type, "userland") == 0)
                return TESTCASE_TYPE_USERLAND;
        else if (strcmp(type, "kernel") == 0)
                return TESTCASE_TYPE_KERNEL;
        else if (strcmp(type, "buildonly") == 0)
                return TESTCASE_TYPE_BUILDONLY;

        return 0;
}

const char *
testcase_get_type_desc(prop_dictionary_t testcase)
{
        const char *str;
        int r;

        r = prop_dictionary_get_cstring_nocopy(testcase, "type", &str);
        if (r == 0)
                err(1, "prop_dictionary operation failed");

        return str;
}

const char *
testcase_get_name(prop_dictionary_t testcase)
{
        const char *str;
        int r;

        r = prop_dictionary_get_cstring_nocopy(testcase, "name", &str);
        if (r == 0)
                err(1, "prop_dictionary operation failed");

        return str;
}

int
testcase_get_argc(prop_dictionary_t testcase)
{
        int32_t argc;
        int r;

        r = prop_dictionary_get_int32(testcase, "argc", &argc);
        if (r == 0)
                err(1, "prop_dictionary operation failed for argc");

        return argc;
}

const char **
testcase_get_args(prop_dictionary_t testcase)
{
        /* Sane limit of 63 arguments... who wants more than that? */
        static const char *argv[64];
        unsigned int i, count;
        prop_array_t a;
        int r;

        a = prop_dictionary_get(testcase, "args");
        if (a == NULL)
                err(1, "testcase_get_args NULL array");

        count = prop_array_count(a);

        for (i = 0; i < count; i++) {
                r = prop_array_get_cstring_nocopy(a, i, &argv[i]);
                if (r == 0)
                        err(1, "error building argv");
        }

        argv[i] = NULL;

        return argv;
}

uint32_t
testcase_get_flags(prop_dictionary_t testcase)
{
        uint32_t flags;
        int r;

        r = prop_dictionary_get_uint32(prop_dictionary_get(testcase, "opts"),
            "flags", &flags);
        if (r == 0)
                err(1, "prop_dictionary operation failed");

        return flags;
}

int
testcase_get_precmd_type(prop_dictionary_t testcase)
{
        uint32_t flags = testcase_get_flags(testcase);

        return (flags & (TESTCASE_INT_PRE | TESTCASE_CUSTOM_PRE));
}

int
testcase_get_rc(prop_dictionary_t testcase)
{
        int32_t rc;
        int r;

        r = prop_dictionary_get_int32(prop_dictionary_get(testcase, "opts"),
            "rc", &rc);
        if (r == 0)
                err(1, "prop_dictionary operation failed for rc");

        return rc;
}

int
testcase_get_postcmd_type(prop_dictionary_t testcase)
{
        uint32_t flags = testcase_get_flags(testcase);

        return (flags & (TESTCASE_INT_POST | TESTCASE_CUSTOM_POST));
}

int
testcase_needs_setuid(prop_dictionary_t testcase)
{
        uint32_t flags = testcase_get_flags(testcase);

        return (flags & TESTCASE_RUN_AS);
}

uid_t
testcase_get_runas_uid(prop_dictionary_t testcase)
{
        uint32_t uid = 0;
        int r;

        r = prop_dictionary_get_uint32(
            prop_dictionary_get(testcase, "opts"), "runas_uid", &uid);
        if (r == 0)
                err(1, "prop_dictionary operation failed");

        return (uid_t)uid;
}

const char *
testcase_get_custom_precmd(prop_dictionary_t testcase)
{
        const char *str;
        int r;

        r = prop_dictionary_get_cstring_nocopy(
            prop_dictionary_get(testcase, "opts"), "pre_cmd", &str);
        if (r == 0)
                err(1, "prop_dictionary operation failed");

        return str;
}

const char *
testcase_get_custom_postcmd(prop_dictionary_t testcase)
{
        const char *str;
        int r;

        r = prop_dictionary_get_cstring_nocopy(
            prop_dictionary_get(testcase, "opts"), "pre_cmd", &str);
        if (r == 0)
                err(1, "prop_dictionary operation failed");

        return str;
}

static const char *
_testcase_get_interpreter(prop_dictionary_t testcase, bool fatal)
{
        const char *str;
        int r;

        r = prop_dictionary_get_cstring_nocopy(
            prop_dictionary_get(testcase, "opts"), "interpreter", &str);
        if (r == 0) {
                if (fatal)
                        err(1, "prop_dictionary operation failed for interpreter");
                else
                        return NULL;
        }

        return str;
}

const char *
testcase_get_interpreter(prop_dictionary_t testcase)
{
        return _testcase_get_interpreter(testcase, true);
}

const char *
testcase_get_interpreter_noexit(prop_dictionary_t testcase)
{
        return _testcase_get_interpreter(testcase, false);
}

const char *
testcase_get_make_cmd(prop_dictionary_t testcase)
{
        const char *str;
        int r;

        r = prop_dictionary_get_cstring_nocopy(
            prop_dictionary_get(testcase, "opts"), "make_cmd", &str);
        if (r == 0)
                err(1, "prop_dictionary operation failed");

        return str;
}

prop_dictionary_t
testcase_get_result_dict(prop_dictionary_t testcase)
{
        prop_dictionary_t result_dict;
        int r;

        result_dict = prop_dictionary_get(testcase, "result");
        if (result_dict == NULL) {
                result_dict = prop_dictionary_create();
                if (result_dict == NULL)
                        err(1, "could not allocate new result dict");

                r = prop_dictionary_set(testcase, "result", result_dict);
                if (r == 0)
                        err(1, "prop_dictionary operation failed");
        }

        return result_dict;
}

int
testcase_set_build_buf(prop_dictionary_t testcase, const char *buf)
{
        prop_dictionary_t dict = testcase_get_result_dict(testcase);

        return !prop_dictionary_set_cstring(dict, "build_buf", buf);
}

int
testcase_set_cleanup_buf(prop_dictionary_t testcase, const char *buf)
{
        prop_dictionary_t dict = testcase_get_result_dict(testcase);

        return !prop_dictionary_set_cstring(dict, "cleanup_buf", buf);
}

int
testcase_set_sys_buf(prop_dictionary_t testcase, const char *buf)
{
        prop_dictionary_t dict = testcase_get_result_dict(testcase);

        return !prop_dictionary_set_cstring(dict, "sys_buf", buf);
}

int
testcase_set_precmd_buf(prop_dictionary_t testcase, const char *buf)
{
        prop_dictionary_t dict = testcase_get_result_dict(testcase);

        return !prop_dictionary_set_cstring(dict, "precmd_buf", buf);
}

int
testcase_set_postcmd_buf(prop_dictionary_t testcase, const char *buf)
{
        prop_dictionary_t dict = testcase_get_result_dict(testcase);

        return !prop_dictionary_set_cstring(dict, "postcmd_buf", buf);
}

int
testcase_set_stdout_buf(prop_dictionary_t testcase, const char *buf)
{
        prop_dictionary_t dict = testcase_get_result_dict(testcase);

        return !prop_dictionary_set_cstring(dict, "stdout_buf", buf);
}

int
testcase_set_stderr_buf(prop_dictionary_t testcase, const char *buf)
{
        prop_dictionary_t dict = testcase_get_result_dict(testcase);

        return !prop_dictionary_set_cstring(dict, "stderr_buf", buf);
}

int
testcase_set_result(prop_dictionary_t testcase, int result)
{
        prop_dictionary_t dict = testcase_get_result_dict(testcase);

        return !prop_dictionary_set_int32(dict, "result", result);
}

int
testcase_set_exit_value(prop_dictionary_t testcase, int exitval)
{
        prop_dictionary_t dict = testcase_get_result_dict(testcase);

        return !prop_dictionary_set_int32(dict, "exit_value", exitval);
}

int
testcase_set_signal(prop_dictionary_t testcase, int sig)
{
        prop_dictionary_t dict = testcase_get_result_dict(testcase);

        return !prop_dictionary_set_int32(dict, "signal", sig);
}

const char *
testcase_get_build_buf(prop_dictionary_t testcase)
{
        const char *str = "";

        prop_dictionary_t dict = testcase_get_result_dict(testcase);
        prop_dictionary_get_cstring_nocopy(dict, "build_buf", &str);

        return str;
}

const char *
testcase_get_cleanup_buf(prop_dictionary_t testcase)
{
        const char *str = "";

        prop_dictionary_t dict = testcase_get_result_dict(testcase);
        prop_dictionary_get_cstring_nocopy(dict, "cleanup_buf", &str);

        return str;
}

const char *
testcase_get_sys_buf(prop_dictionary_t testcase)
{
        const char *str = "";

        prop_dictionary_t dict = testcase_get_result_dict(testcase);
        prop_dictionary_get_cstring_nocopy(dict, "sys_buf", &str);

        return str;
}

const char *
testcase_get_precmd_buf(prop_dictionary_t testcase)
{
        const char *str = "";

        prop_dictionary_t dict = testcase_get_result_dict(testcase);
        prop_dictionary_get_cstring_nocopy(dict, "precmd_buf", &str);

        return str;
}

const char *
testcase_get_postcmd_buf(prop_dictionary_t testcase)
{
        const char *str = "";

        prop_dictionary_t dict = testcase_get_result_dict(testcase);
        prop_dictionary_get_cstring_nocopy(dict, "postcmd_buf", &str);

        return str;
}

const char *
testcase_get_stdout_buf(prop_dictionary_t testcase)
{
        const char *str = "";

        prop_dictionary_t dict = testcase_get_result_dict(testcase);
        prop_dictionary_get_cstring_nocopy(dict, "stdout_buf", &str);

        return str;
}

const char *
testcase_get_stderr_buf(prop_dictionary_t testcase)
{
        const char *str = "";

        prop_dictionary_t dict = testcase_get_result_dict(testcase);
        prop_dictionary_get_cstring_nocopy(dict, "stderr_buf", &str);

        return str;
}

int
testcase_get_result(prop_dictionary_t testcase)
{
        int32_t result = RESULT_NOTRUN;

        prop_dictionary_t dict = testcase_get_result_dict(testcase);
        prop_dictionary_get_int32(dict, "result", &result);

        return (int)result;
}

const char *
testcase_get_result_desc(prop_dictionary_t testcase)
{
        int result = testcase_get_result(testcase);

        switch(result) {
        case RESULT_TIMEOUT:    return "TIMEOUT";
        case RESULT_SIGNALLED:  return "SIGNALLED";
        case RESULT_NOTRUN:     return "NOT RUN";
        case RESULT_FAIL:       return "FAIL";
        case RESULT_PASS:       return "PASS";
        case RESULT_PREFAIL:    return "PREFAIL";
        case RESULT_POSTFAIL:   return "POSTFAIL";
        case RESULT_BUILDFAIL:  return "BUILDFAIL";
        default:                return "UNKNOWN";
        }
}

int
testcase_get_exit_value(prop_dictionary_t testcase)
{
        int32_t exitval;
        int r;

        prop_dictionary_t dict = testcase_get_result_dict(testcase);
        r = prop_dictionary_get_int32(dict, "exit_value", &exitval);
        if (r == 0)
                err(1, "prop_dictionary operation failed");

        return (int)exitval;
}

int
testcase_get_signal(prop_dictionary_t testcase)
{
        int32_t sig;
        int r;

        prop_dictionary_t dict = testcase_get_result_dict(testcase);
        r = prop_dictionary_get_int32(dict, "signal", &sig);
        if (r == 0)
                err(1, "prop_dictionary operation failed");

        return (int)sig;
}

int
parse_testcase_option(struct testcase_options *opts, char *option)
{
        struct passwd *pwd;
        char    *parameter, *endptr;
        long    lval;
        int     noparam = 0;

        parameter = strchr(option, '=');
        noparam = (parameter == NULL);
        if (!noparam)
        {
                *parameter = '\0';
                ++parameter;
        }

        if (strcmp(option, "timeout") == 0) {
                if (noparam)
                        syntax_error("The option 'timeout' needs a parameter");
                        /* NOTREACHED */

                lval = strtol(parameter, &endptr, 10);
                if (*endptr != '\0')
                        syntax_error("The option 'timeout' expects an integer "
                            "parameter, not '%s'", parameter);
                        /* NOTREACHED */

                opts->timeout_in_secs = (long int)lval;
        } else if (strcmp(option, "rc") == 0) {
                if (noparam)
                        syntax_error("The option 'timeout' needs a parameter");
                /* NOTREACHED */

                lval = strtol(parameter, &endptr, 10);
                if (*endptr != '\0')
                        syntax_error("The option 'timeout' expects an integer "
                            "parameter, not '%s'", parameter);
                        /* NOTREACHED */

                opts->rc = (int)lval;
        } else if (strcmp(option, "intpre") == 0) {
                opts->flags |= TESTCASE_INT_PRE;
        } else if (strcmp(option, "intpost") == 0) {
                opts->flags |= TESTCASE_INT_POST;
        } else if (strcmp(option, "pre") == 0) {
                if (noparam)
                        syntax_error("The option 'pre' needs a parameter");
                        /* NOTREACHED */

                opts->flags |= TESTCASE_CUSTOM_PRE;
                opts->pre_cmd = strdup(parameter);
        } else if (strcmp(option, "post") == 0) {
                if (noparam)
                        syntax_error("The option 'post' needs a parameter");
                        /* NOTREACHED */

                opts->flags |= TESTCASE_CUSTOM_POST;
                opts->post_cmd = strdup(parameter);
        } else if (strcmp(option, "runas") == 0) {
                if (noparam)
                        syntax_error("The option 'runas' needs a parameter");
                        /* NOTREACHED */

                if ((pwd = getpwnam(parameter))) {
                        opts->runas_uid = pwd->pw_uid;
                        opts->flags |= TESTCASE_RUN_AS;
                } else {
                        syntax_error("invalid user name for 'runas': %s",
                            parameter);
                }
        } else if (strcmp(option, "nobuild") == 0) {
                opts->flags |= TESTCASE_NOBUILD;
        } else if (strcmp(option, "interpreter") == 0) {
                if (noparam)
                        syntax_error("The option 'interpreter' needs a parameter");
                        /* NOTREACHED */
                opts->interpreter = strdup(parameter);
        } else if (strcmp(option, "make") == 0) {
                if (noparam)
                        syntax_error("The option 'make' needs a parameter");
                        /* NOTREACHED */

                opts->make_cmd = strdup(parameter);
        } else if (strcmp(option, "defaults") == 0) {
                /* Valid option, does nothing */
        } else {
                syntax_error("Unknown option: %s", option);
                /* NOTREACHED */
        }

        return 0;
}

void
testcase_entry_parser(void *arg, char **tokens)
{
        prop_array_t runlist;
        prop_dictionary_t testcase_dict;
        struct testcase *testcase;
        char *options[256];
        int i, r, nopts;

        runlist = (prop_array_t)arg;

        testcase = malloc(sizeof(struct testcase));
        if (testcase == NULL)
                err(1, "could not malloc testcase memory");

        bzero(testcase, sizeof(struct testcase));

        entry_check_num_args(tokens, 3);

        testcase->argv = &tokens[3];
        for (testcase->argc = 0; testcase->argv[testcase->argc] != NULL;
             testcase->argc++)
                ;

        nopts = parse_options(tokens[2], options);

        testcase->name = tokens[0];

        if (strcmp(tokens[1], "userland") == 0) {
                testcase->type = TESTCASE_TYPE_USERLAND;
        } else if (strcmp(tokens[1], "kernel") == 0) {
                testcase->type = TESTCASE_TYPE_KERNEL;
        } else if (strcmp(tokens[1], "buildonly") == 0) {
                testcase->type = TESTCASE_TYPE_BUILDONLY;
        } else {
                syntax_error("Unknown type: %s", tokens[1]);
                /* NOTREACHED */
        }

        testcase->type_str = tokens[1];

        config_get_defaults(&testcase->opts);

        for (i = 0; i < nopts; i++)
                parse_testcase_option(&testcase->opts, options[i]);

        if ((testcase->type != TESTCASE_TYPE_USERLAND) &&
            (testcase->opts.flags & (TESTCASE_INT_PRE | TESTCASE_INT_POST)))
                syntax_error("'intpre' and 'intpost' options are only valid "
                    "with testcase type 'userland'");

        if ((testcase->type == TESTCASE_TYPE_BUILDONLY) &&
            (testcase->opts.flags & TESTCASE_NOBUILD))
                syntax_error("'nobuild' option is incompatible with type "
                    "'buildonly'");

        testcase_dict = testcase_from_struct(testcase);
        if (testcase->opts.pre_cmd != NULL)
                free(testcase->opts.pre_cmd);
        if (testcase->opts.post_cmd != NULL)
                free(testcase->opts.post_cmd);
        if (testcase->opts.interpreter != NULL)
                free(testcase->opts.interpreter);
        if (testcase->opts.make_cmd != NULL)
                free(testcase->opts.make_cmd);
        free(testcase);

        r = prop_array_add(runlist, testcase_dict);
        if (r == 0)
                err(1, "prop_array_add failed");
}