#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <dirent.h>
#include <atomic.h>
#include <sys/ktest.h>
#include <sys/systeminfo.h>
#include <sys/modctl.h>
#include <upanic.h>
#include "libktest_impl.h"
ktest_hdl_t *
ktest_init(void)
{
int fd = open(KTEST_DEV_PATH, O_RDONLY | O_CLOEXEC, 0);
if (fd < 0) {
return (NULL);
}
ktest_hdl_t *hdl = malloc(sizeof (ktest_hdl_t));
if (hdl == NULL) {
const int err = errno;
(void) close(fd);
errno = err;
return (NULL);
}
hdl->kt_fd = fd;
return (hdl);
}
void
ktest_fini(ktest_hdl_t *hdl)
{
if (hdl == NULL) {
return;
}
if (hdl->kt_fd >= 0) {
(void) close(hdl->kt_fd);
hdl->kt_fd = -1;
}
free(hdl);
}
#define DEFAULT_LIST_BUF_SZ (64 * 1024)
static nvlist_t *
ktest_query_tests(ktest_hdl_t *hdl)
{
char *resp = malloc(DEFAULT_LIST_BUF_SZ);
ktest_list_op_t op = {
.klo_resp = resp,
.klo_resp_len = DEFAULT_LIST_BUF_SZ,
};
if (resp == NULL) {
return (NULL);
}
int ret;
ret = ioctl(hdl->kt_fd, KTEST_IOCTL_LIST_TESTS, &op);
if (ret == -1 && errno == ENOBUFS) {
free(resp);
if ((resp = malloc(op.klo_resp_len)) == NULL) {
return (NULL);
}
op.klo_resp = resp;
ret = ioctl(hdl->kt_fd, KTEST_IOCTL_LIST_TESTS, &op);
}
if (ret == -1) {
free(resp);
return (NULL);
}
nvlist_t *tests = NULL;
if ((ret = nvlist_unpack(resp, op.klo_resp_len, &tests, 0)) != 0) {
free(resp);
errno = ret;
return (NULL);
}
free(resp);
uint64_t vsn = 0;
if (nvlist_lookup_uint64(tests, KTEST_SER_FMT_KEY, &vsn) != 0 ||
vsn != KTEST_SER_FMT_VSN) {
nvlist_free(tests);
errno = EINVAL;
return (NULL);
}
fnvlist_remove(tests, KTEST_SER_FMT_KEY);
return (tests);
}
static void
ktest_iter_next_test(ktest_list_iter_t *iter, bool do_reset)
{
if (iter->kli_test == NULL && !do_reset) {
return;
}
if (iter->kli_tests == NULL) {
return;
}
iter->kli_test = nvlist_next_nvpair(iter->kli_tests, iter->kli_test);
while (iter->kli_test != NULL) {
boolean_t requires_input;
if (nvpair_type(iter->kli_test) == DATA_TYPE_NVLIST &&
nvlist_lookup_boolean_value(
fnvpair_value_nvlist(iter->kli_test), KTEST_TEST_INPUT_KEY,
&requires_input) == 0) {
iter->kli_req_input = requires_input;
break;
}
iter->kli_test =
nvlist_next_nvpair(iter->kli_tests, iter->kli_test);
}
}
static void
ktest_iter_next_suite(ktest_list_iter_t *iter, bool do_reset)
{
if (iter->kli_suite == NULL && !do_reset) {
return;
}
if (iter->kli_suites == NULL) {
return;
}
iter->kli_suite = nvlist_next_nvpair(iter->kli_suites, iter->kli_suite);
iter->kli_tests = NULL;
iter->kli_test = NULL;
while (iter->kli_suite != NULL) {
if (nvpair_type(iter->kli_suite) == DATA_TYPE_NVLIST &&
nvlist_lookup_nvlist(fnvpair_value_nvlist(iter->kli_suite),
KTEST_SUITE_TESTS_KEY, &iter->kli_tests) == 0) {
break;
}
iter->kli_suite = nvlist_next_nvpair(iter->kli_suites,
iter->kli_suite);
}
ktest_iter_next_test(iter, true);
}
static void
ktest_iter_next_module(ktest_list_iter_t *iter, bool do_reset)
{
if (iter->kli_module == NULL && !do_reset) {
return;
}
VERIFY(iter->kli_modules != NULL);
iter->kli_module = nvlist_next_nvpair(iter->kli_modules,
iter->kli_module);
iter->kli_suites = NULL;
iter->kli_suite = NULL;
while (iter->kli_module != NULL) {
if (nvpair_type(iter->kli_module) == DATA_TYPE_NVLIST &&
nvlist_lookup_nvlist(fnvpair_value_nvlist(iter->kli_module),
KTEST_MODULE_SUITES_KEY, &iter->kli_suites) == 0) {
break;
}
iter->kli_module =
nvlist_next_nvpair(iter->kli_modules, iter->kli_module);
}
ktest_iter_next_suite(iter, true);
}
ktest_list_iter_t *
ktest_list(ktest_hdl_t *hdl)
{
nvlist_t *tests = ktest_query_tests(hdl);
if (tests == NULL) {
return (NULL);
}
ktest_list_iter_t *iter = malloc(sizeof (ktest_list_iter_t));
if (iter == NULL) {
const int err = errno;
nvlist_free(tests);
errno = err;
return (NULL);
}
iter->kli_hdl = hdl;
iter->kli_modules = tests;
iter->kli_module = NULL;
ktest_iter_next_module(iter, true);
return (iter);
}
bool
ktest_list_next(ktest_list_iter_t *iter, ktest_entry_t *entry)
{
while (iter->kli_module != NULL) {
if (iter->kli_test != NULL) {
entry->ke_module = nvpair_name(iter->kli_module);
entry->ke_suite = nvpair_name(iter->kli_suite);
entry->ke_test = nvpair_name(iter->kli_test);
entry->ke_requires_input = iter->kli_req_input;
ktest_iter_next_test(iter, false);
return (true);
}
ktest_iter_next_suite(iter, false);
if (iter->kli_suite != NULL) {
continue;
}
ktest_iter_next_module(iter, false);
}
return (false);
}
void
ktest_list_reset(ktest_list_iter_t *iter)
{
iter->kli_module = NULL;
ktest_iter_next_module(iter, true);
}
void
ktest_list_free(ktest_list_iter_t *iter)
{
if (iter == NULL) {
return;
}
iter->kli_test = iter->kli_suite = iter->kli_module = NULL;
if (iter->kli_modules != NULL) {
nvlist_free(iter->kli_modules);
iter->kli_modules = NULL;
}
free(iter);
}
bool
ktest_run(ktest_hdl_t *hdl, const ktest_run_req_t *req, ktest_run_result_t *res)
{
ktest_run_op_t kro = {
.kro_input_bytes = req->krq_input,
.kro_input_len = req->krq_input_len,
};
(void) strncpy(kro.kro_module, req->krq_module,
sizeof (kro.kro_module));
(void) strncpy(kro.kro_suite, req->krq_suite, sizeof (kro.kro_suite));
(void) strncpy(kro.kro_test, req->krq_test, sizeof (kro.kro_test));
if (ioctl(hdl->kt_fd, KTEST_IOCTL_RUN_TEST, &kro) == -1) {
return (false);
}
const ktest_result_t *kres = &kro.kro_result;
res->krr_code = (ktest_code_t)kres->kr_type;
res->krr_line = (uint_t)kres->kr_line;
const size_t msg_len =
strnlen(kres->kr_msg_prepend, sizeof (kres->kr_msg_prepend)) +
strnlen(kres->kr_msg, sizeof (kres->kr_msg));
if (msg_len != 0) {
if (asprintf(&res->krr_msg, "%s%s", kres->kr_msg_prepend,
kres->kr_msg) == -1) {
return (false);
}
} else {
res->krr_msg = NULL;
}
return (true);
}
const char *
ktest_code_name(ktest_code_t code)
{
switch (code) {
case KTEST_CODE_NONE:
return ("NONE");
case KTEST_CODE_PASS:
return ("PASS");
case KTEST_CODE_FAIL:
return ("FAIL");
case KTEST_CODE_SKIP:
return ("SKIP");
case KTEST_CODE_ERROR:
return ("ERROR");
default:
break;
}
const char errmsg[] = "unexpected ktest_code value";
upanic(errmsg, sizeof (errmsg));
}
#define KTEST_MODULE_SUFFIX "_ktest"
#define KTEST_BASE_MODULE_DIR "/usr/kernel/misc/ktest"
static char *ktest_cached_module_dir;
static const char *
ktest_mod_directory(void)
{
if (ktest_cached_module_dir != NULL) {
return (ktest_cached_module_dir);
}
char archbuf[20];
if (sysinfo(SI_ARCHITECTURE_64, archbuf, sizeof (archbuf)) < 0) {
return (NULL);
}
char *path = NULL;
if (asprintf(&path, "%s/%s", KTEST_BASE_MODULE_DIR, archbuf) < 0) {
return (NULL);
}
char *old = atomic_cas_ptr(&ktest_cached_module_dir, NULL, path);
if (old == NULL) {
return (path);
} else {
free(path);
return (ktest_cached_module_dir);
}
}
static bool
ktest_mod_path(const char *name, char *buf)
{
const char *base = ktest_mod_directory();
if (base == NULL) {
return (false);
}
(void) snprintf(buf, MAXPATHLEN, "%s/%s" KTEST_MODULE_SUFFIX, base,
name);
return (true);
}
static int
ktest_mod_id_for_name(const char *name)
{
struct modinfo modinfo = {
.mi_info = MI_INFO_ONE | MI_INFO_BY_NAME,
};
(void) snprintf(modinfo.mi_name, sizeof (modinfo.mi_name),
"%s" KTEST_MODULE_SUFFIX, name);
if (modctl(MODINFO, 0, &modinfo) < 0) {
return (-1);
}
return (modinfo.mi_id);
}
bool
ktest_mod_load(const char *name)
{
if (ktest_mod_id_for_name(name) > 0) {
return (true);
}
char path[MAXPATHLEN];
if (!ktest_mod_path(name, path)) {
return (false);
}
int id = 0;
if (modctl(MODLOAD, 0, path, &id) != 0) {
return (false);
}
return (true);
}
void
ktest_mod_unload(const char *name)
{
const int id = ktest_mod_id_for_name(name);
if (id > 0) {
(void) modctl(MODUNLOAD, id);
}
}
static bool
ktest_mod_for_each(void (*cb)(const char *))
{
const char *dpath;
if ((dpath = ktest_mod_directory()) == NULL) {
return (false);
}
DIR *dp = opendir(dpath);
if (dp == NULL) {
return (false);
}
struct dirent *de;
while ((de = readdir(dp)) != NULL) {
char *suffix = strrchr(de->d_name, '_');
if (suffix == NULL ||
strcmp(suffix, KTEST_MODULE_SUFFIX) != 0) {
continue;
}
*suffix = '\0';
const size_t name_sz = strnlen(de->d_name, MODMAXNAMELEN);
if (name_sz == 0 || name_sz >= MODMAXNAMELEN) {
continue;
}
cb(de->d_name);
}
return (true);
}
static void
ktest_mod_load_cb(const char *name)
{
(void) ktest_mod_load(name);
}
bool
ktest_mod_load_all(void)
{
return (ktest_mod_for_each(ktest_mod_load_cb));
}
static void
ktest_mod_unload_cb(const char *name)
{
ktest_mod_unload(name);
}
bool
ktest_mod_unload_all(void)
{
return (ktest_mod_for_each(ktest_mod_unload_cb));
}
size_t
ktest_max_input_size(void)
{
return (KTEST_IOCTL_MAX_LEN);
}