mirror of
https://github.com/AuxXxilium/kmod.git
synced 2024-11-24 07:20:52 +07:00
efc2e4b467
Signed-off-by: Emil Velikov <emil.velikov@collabora.com> Signed-off-by: Lucas De Marchi <lucas.de.marchi@gmail.com>
1120 lines
24 KiB
C
1120 lines
24 KiB
C
/*
|
|
* Copyright (C) 2012-2013 ProFUSION embedded systems
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <getopt.h>
|
|
#include <limits.h>
|
|
#include <regex.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <sys/epoll.h>
|
|
#include <sys/prctl.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/wait.h>
|
|
|
|
#include <shared/util.h>
|
|
|
|
#include "testsuite.h"
|
|
|
|
static const char *ANSI_HIGHLIGHT_GREEN_ON = "\x1B[1;32m";
|
|
static const char *ANSI_HIGHLIGHT_YELLOW_ON = "\x1B[1;33m";
|
|
static const char *ANSI_HIGHLIGHT_RED_ON = "\x1B[1;31m";
|
|
static const char *ANSI_HIGHLIGHT_OFF = "\x1B[0m";
|
|
|
|
static const char *progname;
|
|
static int oneshot = 0;
|
|
static const char options_short[] = "lhn";
|
|
static const struct option options[] = {
|
|
{ "list", no_argument, 0, 'l' },
|
|
{ "help", no_argument, 0, 'h' },
|
|
{ NULL, 0, 0, 0 }
|
|
};
|
|
|
|
#define OVERRIDE_LIBDIR ABS_TOP_BUILDDIR "/testsuite/.libs/"
|
|
#define TEST_TIMEOUT_USEC 2 * USEC_PER_SEC
|
|
|
|
static const struct {
|
|
const char *key;
|
|
const char *ldpreload;
|
|
} env_config[_TC_LAST] = {
|
|
[TC_UNAME_R] = { S_TC_UNAME_R, OVERRIDE_LIBDIR "uname.so" },
|
|
[TC_ROOTFS] = { S_TC_ROOTFS, OVERRIDE_LIBDIR "path.so" },
|
|
[TC_INIT_MODULE_RETCODES] = { S_TC_INIT_MODULE_RETCODES, OVERRIDE_LIBDIR "init_module.so" },
|
|
[TC_DELETE_MODULE_RETCODES] = { S_TC_DELETE_MODULE_RETCODES, OVERRIDE_LIBDIR "delete_module.so" },
|
|
};
|
|
|
|
static void help(void)
|
|
{
|
|
const struct option *itr;
|
|
const char *itr_short;
|
|
|
|
printf("Usage:\n"
|
|
"\t%s [options] <test>\n"
|
|
"Options:\n", basename(progname));
|
|
|
|
for (itr = options, itr_short = options_short;
|
|
itr->name != NULL; itr++, itr_short++)
|
|
printf("\t-%c, --%s\n", *itr_short, itr->name);
|
|
}
|
|
|
|
static void test_list(const struct test *start, const struct test *stop)
|
|
{
|
|
const struct test *t;
|
|
|
|
printf("Available tests:\n");
|
|
for (t = start; t < stop; t++)
|
|
printf("\t%s, %s\n", t->name, t->description);
|
|
}
|
|
|
|
int test_init(const struct test *start, const struct test *stop,
|
|
int argc, char *const argv[])
|
|
{
|
|
progname = argv[0];
|
|
|
|
for (;;) {
|
|
int c, idx = 0;
|
|
c = getopt_long(argc, argv, options_short, options, &idx);
|
|
if (c == -1)
|
|
break;
|
|
switch (c) {
|
|
case 'l':
|
|
test_list(start, stop);
|
|
return 0;
|
|
case 'h':
|
|
help();
|
|
return 0;
|
|
case 'n':
|
|
oneshot = 1;
|
|
break;
|
|
case '?':
|
|
return -1;
|
|
default:
|
|
ERR("unexpected getopt_long() value %c\n", c);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if (isatty(STDOUT_FILENO) == 0) {
|
|
ANSI_HIGHLIGHT_OFF = "";
|
|
ANSI_HIGHLIGHT_RED_ON = "";
|
|
ANSI_HIGHLIGHT_GREEN_ON = "";
|
|
}
|
|
|
|
return optind;
|
|
}
|
|
|
|
const struct test *test_find(const struct test *start,
|
|
const struct test *stop, const char *name)
|
|
{
|
|
const struct test *t;
|
|
|
|
for (t = start; t < stop; t++) {
|
|
if (streq(t->name, name))
|
|
return t;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int test_spawn_test(const struct test *t)
|
|
{
|
|
const char *const args[] = { progname, "-n", t->name, NULL };
|
|
|
|
execv(progname, (char *const *) args);
|
|
|
|
ERR("failed to spawn %s for %s: %m\n", progname, t->name);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
static int test_run_spawned(const struct test *t)
|
|
{
|
|
int err = t->func(t);
|
|
exit(err);
|
|
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
int test_spawn_prog(const char *prog, const char *const args[])
|
|
{
|
|
execv(prog, (char *const *) args);
|
|
|
|
ERR("failed to spawn %s\n", prog);
|
|
ERR("did you forget to build tools?\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
static void test_export_environ(const struct test *t)
|
|
{
|
|
char *preload = NULL;
|
|
size_t preloadlen = 0;
|
|
size_t i;
|
|
const struct keyval *env;
|
|
|
|
unsetenv("LD_PRELOAD");
|
|
|
|
for (i = 0; i < _TC_LAST; i++) {
|
|
const char *ldpreload;
|
|
size_t ldpreloadlen;
|
|
char *tmp;
|
|
|
|
if (t->config[i] == NULL)
|
|
continue;
|
|
|
|
setenv(env_config[i].key, t->config[i], 1);
|
|
|
|
ldpreload = env_config[i].ldpreload;
|
|
ldpreloadlen = strlen(ldpreload);
|
|
tmp = realloc(preload, preloadlen + 2 + ldpreloadlen);
|
|
if (tmp == NULL) {
|
|
ERR("oom: test_export_environ()\n");
|
|
return;
|
|
}
|
|
preload = tmp;
|
|
|
|
if (preloadlen > 0)
|
|
preload[preloadlen++] = ' ';
|
|
memcpy(preload + preloadlen, ldpreload, ldpreloadlen);
|
|
preloadlen += ldpreloadlen;
|
|
preload[preloadlen] = '\0';
|
|
}
|
|
|
|
if (preload != NULL)
|
|
setenv("LD_PRELOAD", preload, 1);
|
|
|
|
free(preload);
|
|
|
|
for (env = t->env_vars; env && env->key; env++)
|
|
setenv(env->key, env->val, 1);
|
|
}
|
|
|
|
static inline int test_run_child(const struct test *t, int fdout[2],
|
|
int fderr[2], int fdmonitor[2])
|
|
{
|
|
/* kill child if parent dies */
|
|
prctl(PR_SET_PDEATHSIG, SIGTERM);
|
|
|
|
test_export_environ(t);
|
|
|
|
/* Close read-fds and redirect std{out,err} to the write-fds */
|
|
if (t->output.out != NULL) {
|
|
close(fdout[0]);
|
|
if (dup2(fdout[1], STDOUT_FILENO) < 0) {
|
|
ERR("could not redirect stdout to pipe: %m\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
if (t->output.err != NULL) {
|
|
close(fderr[0]);
|
|
if (dup2(fderr[1], STDERR_FILENO) < 0) {
|
|
ERR("could not redirect stderr to pipe: %m\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
close(fdmonitor[0]);
|
|
|
|
if (t->config[TC_ROOTFS] != NULL) {
|
|
const char *stamp = TESTSUITE_ROOTFS "../stamp-rootfs";
|
|
const char *rootfs = t->config[TC_ROOTFS];
|
|
struct stat rootfsst, stampst;
|
|
|
|
if (stat(stamp, &stampst) != 0) {
|
|
ERR("could not stat %s\n - %m", stamp);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (stat(rootfs, &rootfsst) != 0) {
|
|
ERR("could not stat %s\n - %m", rootfs);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (stat_mstamp(&rootfsst) > stat_mstamp(&stampst)) {
|
|
ERR("rootfs %s is dirty, please run 'make rootfs' before runnning this test\n",
|
|
rootfs);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
if (t->need_spawn)
|
|
return test_spawn_test(t);
|
|
else
|
|
return test_run_spawned(t);
|
|
}
|
|
|
|
#define BUFSZ 4096
|
|
|
|
enum fd_cmp_type {
|
|
FD_CMP_MONITOR,
|
|
FD_CMP_OUT,
|
|
FD_CMP_ERR,
|
|
FD_CMP_MAX = FD_CMP_ERR,
|
|
};
|
|
|
|
struct fd_cmp {
|
|
enum fd_cmp_type type;
|
|
int fd;
|
|
int fd_match;
|
|
bool activity;
|
|
const char *path;
|
|
const char *name;
|
|
char buf[BUFSZ];
|
|
char buf_match[BUFSZ];
|
|
unsigned int head;
|
|
unsigned int head_match;
|
|
};
|
|
|
|
static int fd_cmp_check_activity(struct fd_cmp *fd_cmp)
|
|
{
|
|
struct stat st;
|
|
|
|
/* not monitoring or monitoring and it has activity */
|
|
if (fd_cmp == NULL || fd_cmp->fd < 0 || fd_cmp->activity)
|
|
return 0;
|
|
|
|
/* monitoring, there was no activity and size matches */
|
|
if (stat(fd_cmp->path, &st) == 0 && st.st_size == 0)
|
|
return 0;
|
|
|
|
ERR("Expecting output on %s, but test didn't produce any\n",
|
|
fd_cmp->name);
|
|
|
|
return -1;
|
|
}
|
|
|
|
static bool fd_cmp_is_active(struct fd_cmp *fd_cmp)
|
|
{
|
|
return fd_cmp->fd != -1;
|
|
}
|
|
|
|
static int fd_cmp_open_monitor(struct fd_cmp *fd_cmp, int fd, int fd_ep)
|
|
{
|
|
struct epoll_event ep = {};
|
|
|
|
ep.events = EPOLLHUP;
|
|
ep.data.ptr = fd_cmp;
|
|
if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd, &ep) < 0) {
|
|
ERR("could not add monitor fd to epoll: %m\n");
|
|
return -errno;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fd_cmp_open_std(struct fd_cmp *fd_cmp,
|
|
const char *fn, int fd, int fd_ep)
|
|
{
|
|
struct epoll_event ep = {};
|
|
int fd_match;
|
|
|
|
fd_match = open(fn, O_RDONLY);
|
|
if (fd_match < 0) {
|
|
ERR("could not open %s for read: %m\n", fn);
|
|
return -errno;
|
|
}
|
|
ep.events = EPOLLIN;
|
|
ep.data.ptr = fd_cmp;
|
|
if (epoll_ctl(fd_ep, EPOLL_CTL_ADD, fd, &ep) < 0) {
|
|
ERR("could not add fd to epoll: %m\n");
|
|
close(fd_match);
|
|
return -errno;
|
|
}
|
|
|
|
return fd_match;
|
|
}
|
|
|
|
/* opens output file AND adds descriptor to epoll */
|
|
static int fd_cmp_open(struct fd_cmp **fd_cmp_out,
|
|
enum fd_cmp_type type, const char *fn, int fd,
|
|
int fd_ep)
|
|
{
|
|
int err = 0;
|
|
struct fd_cmp *fd_cmp;
|
|
|
|
fd_cmp = calloc(1, sizeof(*fd_cmp));
|
|
if (fd_cmp == NULL) {
|
|
ERR("could not allocate fd_cmp\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
switch (type) {
|
|
case FD_CMP_MONITOR:
|
|
err = fd_cmp_open_monitor(fd_cmp, fd, fd_ep);
|
|
break;
|
|
case FD_CMP_OUT:
|
|
fd_cmp->name = "STDOUT";
|
|
err = fd_cmp_open_std(fd_cmp, fn, fd, fd_ep);
|
|
break;
|
|
case FD_CMP_ERR:
|
|
fd_cmp->name = "STDERR";
|
|
err = fd_cmp_open_std(fd_cmp, fn, fd, fd_ep);
|
|
break;
|
|
default:
|
|
ERR("unknown fd type %d\n", type);
|
|
err = -1;
|
|
}
|
|
|
|
if (err < 0) {
|
|
free(fd_cmp);
|
|
return err;
|
|
}
|
|
|
|
fd_cmp->fd_match = err;
|
|
fd_cmp->fd = fd;
|
|
fd_cmp->type = type;
|
|
fd_cmp->path = fn;
|
|
|
|
*fd_cmp_out = fd_cmp;
|
|
return 0;
|
|
}
|
|
|
|
static int fd_cmp_check_ev_in(struct fd_cmp *fd_cmp)
|
|
{
|
|
if (fd_cmp->type == FD_CMP_MONITOR) {
|
|
ERR("Unexpected activity on monitor pipe\n");
|
|
return -EINVAL;
|
|
}
|
|
fd_cmp->activity = true;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void fd_cmp_delete_ep(struct fd_cmp *fd_cmp, int fd_ep)
|
|
{
|
|
if (epoll_ctl(fd_ep, EPOLL_CTL_DEL, fd_cmp->fd, NULL) < 0) {
|
|
ERR("could not remove fd %d from epoll: %m\n", fd_cmp->fd);
|
|
}
|
|
fd_cmp->fd = -1;
|
|
}
|
|
|
|
static void fd_cmp_close(struct fd_cmp *fd_cmp)
|
|
{
|
|
if (fd_cmp == NULL)
|
|
return;
|
|
|
|
if (fd_cmp->fd >= 0)
|
|
close(fd_cmp->fd);
|
|
free(fd_cmp);
|
|
}
|
|
|
|
static bool fd_cmp_regex_one(const char *pattern, const char *s)
|
|
{
|
|
_cleanup_(regfree) regex_t re = { };
|
|
|
|
return !regcomp(&re, pattern, REG_EXTENDED|REG_NOSUB) &&
|
|
!regexec(&re, s, 0, NULL, 0);
|
|
}
|
|
|
|
/*
|
|
* read fd and fd_match, checking the first matches the regex of the second,
|
|
* line by line
|
|
*/
|
|
static bool fd_cmp_regex(struct fd_cmp *fd_cmp, const struct test *t)
|
|
{
|
|
char *p, *p_match;
|
|
int done = 0, done_match = 0, r;
|
|
|
|
if (fd_cmp->head >= sizeof(fd_cmp->buf)) {
|
|
ERR("Read %zu bytes without a newline\n", sizeof(fd_cmp->buf));
|
|
ERR("output: %.*s", (int)sizeof(fd_cmp->buf), fd_cmp->buf);
|
|
return false;
|
|
}
|
|
|
|
r = read(fd_cmp->fd, fd_cmp->buf + fd_cmp->head,
|
|
sizeof(fd_cmp->buf) - fd_cmp->head);
|
|
if (r <= 0)
|
|
return true;
|
|
|
|
fd_cmp->head += r;
|
|
|
|
/*
|
|
* Process as many lines as read from fd and that fits in the buffer -
|
|
* it's assumed that if we get N lines from fd, we should be able to
|
|
* get the same amount from fd_match
|
|
*/
|
|
for (;;) {
|
|
p = memchr(fd_cmp->buf + done, '\n', fd_cmp->head - done);
|
|
if (!p)
|
|
break;
|
|
*p = '\0';
|
|
|
|
p_match = memchr(fd_cmp->buf_match + done_match, '\n',
|
|
fd_cmp->head_match - done_match);
|
|
if (!p_match) {
|
|
if (fd_cmp->head_match >= sizeof(fd_cmp->buf_match)) {
|
|
ERR("Read %zu bytes without a match\n", sizeof(fd_cmp->buf_match));
|
|
ERR("output: %.*s", (int)sizeof(fd_cmp->buf_match), fd_cmp->buf_match);
|
|
return false;
|
|
}
|
|
|
|
/* pump more data from file */
|
|
r = read(fd_cmp->fd_match, fd_cmp->buf_match + fd_cmp->head_match,
|
|
sizeof(fd_cmp->buf_match) - fd_cmp->head_match);
|
|
if (r <= 0) {
|
|
ERR("could not read match fd %d\n", fd_cmp->fd_match);
|
|
return false;
|
|
}
|
|
fd_cmp->head_match += r;
|
|
p_match = memchr(fd_cmp->buf_match + done_match, '\n',
|
|
fd_cmp->head_match - done_match);
|
|
if (!p_match) {
|
|
ERR("could not find match line from fd %d\n", fd_cmp->fd_match);
|
|
return false;
|
|
}
|
|
}
|
|
*p_match = '\0';
|
|
|
|
if (!fd_cmp_regex_one(fd_cmp->buf_match + done_match, fd_cmp->buf + done)) {
|
|
ERR("Output does not match pattern on %s:\n", fd_cmp->name);
|
|
ERR("pattern: %s\n", fd_cmp->buf_match + done_match);
|
|
ERR("output : %s\n", fd_cmp->buf + done);
|
|
return false;
|
|
}
|
|
|
|
done = p - fd_cmp->buf + 1;
|
|
done_match = p_match - fd_cmp->buf_match + 1;
|
|
}
|
|
|
|
/*
|
|
* Prepare for the next call: anything we processed we remove from the
|
|
* buffer by memmoving the remaining bytes up to the beginning
|
|
*/
|
|
if (done) {
|
|
if (fd_cmp->head - done)
|
|
memmove(fd_cmp->buf, fd_cmp->buf + done, fd_cmp->head - done);
|
|
fd_cmp->head -= done;
|
|
}
|
|
|
|
if (done_match) {
|
|
if (fd_cmp->head_match - done_match)
|
|
memmove(fd_cmp->buf_match, fd_cmp->buf_match + done_match,
|
|
fd_cmp->head_match - done_match);
|
|
fd_cmp->head_match -= done_match;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* read fd and fd_match, checking they match exactly */
|
|
static bool fd_cmp_exact(struct fd_cmp *fd_cmp, const struct test *t)
|
|
{
|
|
int r, rmatch, done = 0;
|
|
|
|
r = read(fd_cmp->fd, fd_cmp->buf, sizeof(fd_cmp->buf) - 1);
|
|
if (r <= 0)
|
|
/* try again later */
|
|
return true;
|
|
|
|
/* read as much data from fd_match as we read from fd */
|
|
for (;;) {
|
|
rmatch = read(fd_cmp->fd_match, fd_cmp->buf_match + done, r - done);
|
|
if (rmatch == 0)
|
|
break;
|
|
|
|
if (rmatch < 0) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
ERR("could not read match fd %d\n", fd_cmp->fd_match);
|
|
return false;
|
|
}
|
|
|
|
done += rmatch;
|
|
}
|
|
|
|
fd_cmp->buf[r] = '\0';
|
|
fd_cmp->buf_match[r] = '\0';
|
|
|
|
if (t->print_outputs)
|
|
printf("%s: %s\n", fd_cmp->name, fd_cmp->buf);
|
|
|
|
if (!streq(fd_cmp->buf, fd_cmp->buf_match)) {
|
|
ERR("Outputs do not match on %s:\n", fd_cmp->name);
|
|
ERR("correct:\n%s\n", fd_cmp->buf_match);
|
|
ERR("wrong:\n%s\n", fd_cmp->buf);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool test_run_parent_check_outputs(const struct test *t,
|
|
int fdout, int fderr, int fdmonitor,
|
|
pid_t child)
|
|
{
|
|
int err, fd_ep;
|
|
unsigned long long end_usec, start_usec;
|
|
struct fd_cmp *fd_cmp_out = NULL;
|
|
struct fd_cmp *fd_cmp_err = NULL;
|
|
struct fd_cmp *fd_cmp_monitor = NULL;
|
|
int n_fd = 0;
|
|
|
|
fd_ep = epoll_create1(EPOLL_CLOEXEC);
|
|
if (fd_ep < 0) {
|
|
ERR("could not create epoll fd: %m\n");
|
|
return false;
|
|
}
|
|
|
|
if (t->output.out != NULL) {
|
|
err = fd_cmp_open(&fd_cmp_out,
|
|
FD_CMP_OUT, t->output.out, fdout, fd_ep);
|
|
if (err < 0)
|
|
goto out;
|
|
n_fd++;
|
|
}
|
|
|
|
if (t->output.err != NULL) {
|
|
err = fd_cmp_open(&fd_cmp_err,
|
|
FD_CMP_ERR, t->output.err, fderr, fd_ep);
|
|
if (err < 0)
|
|
goto out;
|
|
n_fd++;
|
|
}
|
|
|
|
err = fd_cmp_open(&fd_cmp_monitor, FD_CMP_MONITOR, NULL, fdmonitor, fd_ep);
|
|
if (err < 0)
|
|
goto out;
|
|
n_fd++;
|
|
|
|
start_usec = now_usec();
|
|
end_usec = start_usec + TEST_TIMEOUT_USEC;
|
|
|
|
for (err = 0; n_fd > 0;) {
|
|
int fdcount, i, timeout;
|
|
struct epoll_event ev[4];
|
|
unsigned long long curr_usec = now_usec();
|
|
|
|
if (curr_usec > end_usec)
|
|
break;
|
|
|
|
timeout = (end_usec - curr_usec) / USEC_PER_MSEC;
|
|
fdcount = epoll_wait(fd_ep, ev, 4, timeout);
|
|
if (fdcount < 0) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
err = -errno;
|
|
ERR("could not poll: %m\n");
|
|
goto out;
|
|
}
|
|
|
|
for (i = 0; i < fdcount; i++) {
|
|
struct fd_cmp *fd_cmp = ev[i].data.ptr;
|
|
bool ret;
|
|
|
|
if (ev[i].events & EPOLLIN) {
|
|
err = fd_cmp_check_ev_in(fd_cmp);
|
|
if (err < 0)
|
|
goto out;
|
|
|
|
if (t->output.regex)
|
|
ret = fd_cmp_regex(fd_cmp, t);
|
|
else
|
|
ret = fd_cmp_exact(fd_cmp, t);
|
|
|
|
if (!ret) {
|
|
err = -1;
|
|
goto out;
|
|
}
|
|
} else if (ev[i].events & EPOLLHUP) {
|
|
fd_cmp_delete_ep(fd_cmp, fd_ep);
|
|
n_fd--;
|
|
}
|
|
}
|
|
}
|
|
|
|
err = fd_cmp_check_activity(fd_cmp_out);
|
|
err |= fd_cmp_check_activity(fd_cmp_err);
|
|
|
|
if (err == 0 && fd_cmp_is_active(fd_cmp_monitor)) {
|
|
err = -EINVAL;
|
|
ERR("Test '%s' timed out, killing %d\n", t->name, child);
|
|
kill(child, SIGKILL);
|
|
}
|
|
|
|
out:
|
|
fd_cmp_close(fd_cmp_out);
|
|
fd_cmp_close(fd_cmp_err);
|
|
fd_cmp_close(fd_cmp_monitor);
|
|
close(fd_ep);
|
|
|
|
return err == 0;
|
|
}
|
|
|
|
static inline int safe_read(int fd, void *buf, size_t count)
|
|
{
|
|
int r;
|
|
|
|
while (1) {
|
|
r = read(fd, buf, count);
|
|
if (r == -1 && errno == EINTR)
|
|
continue;
|
|
break;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
static bool check_generated_files(const struct test *t)
|
|
{
|
|
const struct keyval *k;
|
|
|
|
/* This is not meant to be a diff replacement, just stupidly check if
|
|
* the files match. Bear in mind they can be binary files */
|
|
for (k = t->output.files; k && k->key; k++) {
|
|
struct stat sta, stb;
|
|
int fda = -1, fdb = -1;
|
|
char bufa[4096];
|
|
char bufb[4096];
|
|
|
|
fda = open(k->key, O_RDONLY);
|
|
if (fda < 0) {
|
|
ERR("could not open %s\n - %m\n", k->key);
|
|
goto fail;
|
|
}
|
|
|
|
fdb = open(k->val, O_RDONLY);
|
|
if (fdb < 0) {
|
|
ERR("could not open %s\n - %m\n", k->val);
|
|
goto fail;
|
|
}
|
|
|
|
if (fstat(fda, &sta) != 0) {
|
|
ERR("could not fstat %d %s\n - %m\n", fda, k->key);
|
|
goto fail;
|
|
}
|
|
|
|
if (fstat(fdb, &stb) != 0) {
|
|
ERR("could not fstat %d %s\n - %m\n", fdb, k->key);
|
|
goto fail;
|
|
}
|
|
|
|
if (sta.st_size != stb.st_size) {
|
|
ERR("sizes do not match %s %s\n", k->key, k->val);
|
|
goto fail;
|
|
}
|
|
|
|
for (;;) {
|
|
int r, done;
|
|
|
|
r = safe_read(fda, bufa, sizeof(bufa));
|
|
if (r < 0)
|
|
goto fail;
|
|
|
|
if (r == 0)
|
|
/* size is already checked, go to next file */
|
|
goto next;
|
|
|
|
for (done = 0; done < r;) {
|
|
int r2 = safe_read(fdb, bufb + done, r - done);
|
|
|
|
if (r2 <= 0)
|
|
goto fail;
|
|
|
|
done += r2;
|
|
}
|
|
|
|
if (memcmp(bufa, bufb, r) != 0)
|
|
goto fail;
|
|
}
|
|
|
|
next:
|
|
close(fda);
|
|
close(fdb);
|
|
continue;
|
|
|
|
fail:
|
|
if (fda >= 0)
|
|
close(fda);
|
|
if (fdb >= 0)
|
|
close(fdb);
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int cmp_modnames(const void *m1, const void *m2)
|
|
{
|
|
const char *s1 = *(char *const *)m1;
|
|
const char *s2 = *(char *const *)m2;
|
|
int i;
|
|
|
|
for (i = 0; s1[i] || s2[i]; i++) {
|
|
char c1 = s1[i], c2 = s2[i];
|
|
if (c1 == '-')
|
|
c1 = '_';
|
|
if (c2 == '-')
|
|
c2 = '_';
|
|
if (c1 != c2)
|
|
return c1 - c2;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Store the expected module names in buf and return a list of pointers to
|
|
* them.
|
|
*/
|
|
static const char **read_expected_modules(const struct test *t,
|
|
char **buf, int *count)
|
|
{
|
|
const char **res;
|
|
int len;
|
|
int i;
|
|
char *p;
|
|
|
|
if (t->modules_loaded[0] == '\0') {
|
|
*count = 0;
|
|
*buf = NULL;
|
|
return NULL;
|
|
}
|
|
*buf = strdup(t->modules_loaded);
|
|
if (!*buf) {
|
|
*count = -1;
|
|
return NULL;
|
|
}
|
|
len = 1;
|
|
for (p = *buf; *p; p++)
|
|
if (*p == ',')
|
|
len++;
|
|
res = malloc(sizeof(char *) * len);
|
|
if (!res) {
|
|
perror("malloc");
|
|
*count = -1;
|
|
free(*buf);
|
|
*buf = NULL;
|
|
return NULL;
|
|
}
|
|
i = 0;
|
|
res[i++] = *buf;
|
|
for (p = *buf; i < len; p++)
|
|
if (*p == ',') {
|
|
*p = '\0';
|
|
res[i++] = p + 1;
|
|
}
|
|
*count = len;
|
|
return res;
|
|
}
|
|
|
|
static char **read_loaded_modules(const struct test *t, char **buf, int *count)
|
|
{
|
|
char dirname[PATH_MAX];
|
|
DIR *dir;
|
|
struct dirent *dirent;
|
|
int i;
|
|
int len = 0, bufsz;
|
|
char **res = NULL;
|
|
char *p;
|
|
const char *rootfs = t->config[TC_ROOTFS] ? t->config[TC_ROOTFS] : "";
|
|
|
|
/* Store the entries in /sys/module to res */
|
|
if (snprintf(dirname, sizeof(dirname), "%s/sys/module", rootfs)
|
|
>= (int)sizeof(dirname)) {
|
|
ERR("rootfs path too long: %s\n", rootfs);
|
|
*buf = NULL;
|
|
len = -1;
|
|
goto out;
|
|
}
|
|
dir = opendir(dirname);
|
|
/* not an error, simply return empty list */
|
|
if (!dir) {
|
|
*buf = NULL;
|
|
goto out;
|
|
}
|
|
bufsz = 0;
|
|
while ((dirent = readdir(dir))) {
|
|
if (dirent->d_name[0] == '.')
|
|
continue;
|
|
len++;
|
|
bufsz += strlen(dirent->d_name) + 1;
|
|
}
|
|
res = malloc(sizeof(char *) * len);
|
|
if (!res) {
|
|
perror("malloc");
|
|
len = -1;
|
|
goto out_dir;
|
|
}
|
|
*buf = malloc(bufsz);
|
|
if (!*buf) {
|
|
perror("malloc");
|
|
free(res);
|
|
res = NULL;
|
|
len = -1;
|
|
goto out_dir;
|
|
}
|
|
rewinddir(dir);
|
|
i = 0;
|
|
p = *buf;
|
|
while ((dirent = readdir(dir))) {
|
|
int size;
|
|
|
|
if (dirent->d_name[0] == '.')
|
|
continue;
|
|
size = strlen(dirent->d_name) + 1;
|
|
memcpy(p, dirent->d_name, size);
|
|
res[i++] = p;
|
|
p += size;
|
|
}
|
|
out_dir:
|
|
closedir(dir);
|
|
out:
|
|
*count = len;
|
|
return res;
|
|
}
|
|
|
|
static int check_loaded_modules(const struct test *t)
|
|
{
|
|
int l1, l2, i1, i2;
|
|
const char **a1;
|
|
char **a2;
|
|
char *buf1, *buf2;
|
|
int err = false;
|
|
|
|
a1 = read_expected_modules(t, &buf1, &l1);
|
|
if (l1 < 0)
|
|
return err;
|
|
a2 = read_loaded_modules(t, &buf2, &l2);
|
|
if (l2 < 0)
|
|
goto out_a1;
|
|
qsort(a1, l1, sizeof(char *), cmp_modnames);
|
|
qsort(a2, l2, sizeof(char *), cmp_modnames);
|
|
i1 = i2 = 0;
|
|
err = true;
|
|
while (i1 < l1 || i2 < l2) {
|
|
int cmp;
|
|
|
|
if (i1 >= l1)
|
|
cmp = 1;
|
|
else if (i2 >= l2)
|
|
cmp = -1;
|
|
else
|
|
cmp = cmp_modnames(&a1[i1], &a2[i2]);
|
|
if (cmp == 0) {
|
|
i1++;
|
|
i2++;
|
|
} else if (cmp < 0) {
|
|
err = false;
|
|
ERR("module %s not loaded\n", a1[i1]);
|
|
i1++;
|
|
} else {
|
|
err = false;
|
|
ERR("module %s is loaded but should not be \n", a2[i2]);
|
|
i2++;
|
|
}
|
|
}
|
|
free(a2);
|
|
free(buf2);
|
|
out_a1:
|
|
free(a1);
|
|
free(buf1);
|
|
return err;
|
|
}
|
|
|
|
static inline int test_run_parent(const struct test *t, int fdout[2],
|
|
int fderr[2], int fdmonitor[2], pid_t child)
|
|
{
|
|
pid_t pid;
|
|
int err;
|
|
bool matchout, match_modules;
|
|
|
|
if (t->skip) {
|
|
LOG("%sSKIPPED%s: %s\n",
|
|
ANSI_HIGHLIGHT_YELLOW_ON, ANSI_HIGHLIGHT_OFF,
|
|
t->name);
|
|
err = EXIT_SUCCESS;
|
|
goto exit;
|
|
}
|
|
|
|
/* Close write-fds */
|
|
if (t->output.out != NULL)
|
|
close(fdout[1]);
|
|
if (t->output.err != NULL)
|
|
close(fderr[1]);
|
|
close(fdmonitor[1]);
|
|
|
|
matchout = test_run_parent_check_outputs(t, fdout[0], fderr[0],
|
|
fdmonitor[0], child);
|
|
|
|
/*
|
|
* break pipe on the other end: either child already closed or we want
|
|
* to stop it
|
|
*/
|
|
if (t->output.out != NULL)
|
|
close(fdout[0]);
|
|
if (t->output.err != NULL)
|
|
close(fderr[0]);
|
|
close(fdmonitor[0]);
|
|
|
|
do {
|
|
pid = wait(&err);
|
|
if (pid == -1) {
|
|
ERR("error waitpid(): %m\n");
|
|
err = EXIT_FAILURE;
|
|
goto exit;
|
|
}
|
|
} while (!WIFEXITED(err) && !WIFSIGNALED(err));
|
|
|
|
if (WIFEXITED(err)) {
|
|
if (WEXITSTATUS(err) != 0)
|
|
ERR("'%s' [%u] exited with return code %d\n",
|
|
t->name, pid, WEXITSTATUS(err));
|
|
else
|
|
LOG("'%s' [%u] exited with return code %d\n",
|
|
t->name, pid, WEXITSTATUS(err));
|
|
} else if (WIFSIGNALED(err)) {
|
|
ERR("'%s' [%u] terminated by signal %d (%s)\n", t->name, pid,
|
|
WTERMSIG(err), strsignal(WTERMSIG(err)));
|
|
err = t->expected_fail ? EXIT_SUCCESS : EXIT_FAILURE;
|
|
goto exit;
|
|
}
|
|
|
|
if (matchout)
|
|
matchout = check_generated_files(t);
|
|
if (t->modules_loaded)
|
|
match_modules = check_loaded_modules(t);
|
|
else
|
|
match_modules = true;
|
|
|
|
if (t->expected_fail == false) {
|
|
if (err == 0) {
|
|
if (matchout && match_modules)
|
|
LOG("%sPASSED%s: %s\n",
|
|
ANSI_HIGHLIGHT_GREEN_ON, ANSI_HIGHLIGHT_OFF,
|
|
t->name);
|
|
else {
|
|
ERR("%sFAILED%s: exit ok but %s do not match: %s\n",
|
|
ANSI_HIGHLIGHT_RED_ON, ANSI_HIGHLIGHT_OFF,
|
|
matchout ? "loaded modules" : "outputs",
|
|
t->name);
|
|
err = EXIT_FAILURE;
|
|
}
|
|
} else {
|
|
ERR("%sFAILED%s: %s\n",
|
|
ANSI_HIGHLIGHT_RED_ON, ANSI_HIGHLIGHT_OFF,
|
|
t->name);
|
|
}
|
|
} else {
|
|
if (err == 0) {
|
|
if (matchout) {
|
|
ERR("%sUNEXPECTED PASS%s: exit with 0: %s\n",
|
|
ANSI_HIGHLIGHT_RED_ON, ANSI_HIGHLIGHT_OFF,
|
|
t->name);
|
|
err = EXIT_FAILURE;
|
|
} else {
|
|
ERR("%sUNEXPECTED PASS%s: exit with 0 and outputs do not match: %s\n",
|
|
ANSI_HIGHLIGHT_RED_ON, ANSI_HIGHLIGHT_OFF,
|
|
t->name);
|
|
err = EXIT_FAILURE;
|
|
}
|
|
} else {
|
|
if (matchout) {
|
|
LOG("%sEXPECTED FAIL%s: %s\n",
|
|
ANSI_HIGHLIGHT_GREEN_ON, ANSI_HIGHLIGHT_OFF,
|
|
t->name);
|
|
err = EXIT_SUCCESS;
|
|
} else {
|
|
LOG("%sEXPECTED FAIL%s: exit with %d but outputs do not match: %s\n",
|
|
ANSI_HIGHLIGHT_GREEN_ON, ANSI_HIGHLIGHT_OFF,
|
|
WEXITSTATUS(err), t->name);
|
|
err = EXIT_FAILURE;
|
|
}
|
|
}
|
|
}
|
|
|
|
exit:
|
|
LOG("------\n");
|
|
return err;
|
|
}
|
|
|
|
static int prepend_path(const char *extra)
|
|
{
|
|
char *oldpath, *newpath;
|
|
int r;
|
|
|
|
if (extra == NULL)
|
|
return 0;
|
|
|
|
oldpath = getenv("PATH");
|
|
if (oldpath == NULL)
|
|
return setenv("PATH", extra, 1);
|
|
|
|
if (asprintf(&newpath, "%s:%s", extra, oldpath) < 0) {
|
|
ERR("failed to allocate memory to new PATH\n");
|
|
return -1;
|
|
}
|
|
|
|
r = setenv("PATH", newpath, 1);
|
|
free(newpath);
|
|
|
|
return r;
|
|
}
|
|
|
|
int test_run(const struct test *t)
|
|
{
|
|
pid_t pid;
|
|
int fdout[2];
|
|
int fderr[2];
|
|
int fdmonitor[2];
|
|
|
|
if (t->need_spawn && oneshot)
|
|
test_run_spawned(t);
|
|
|
|
if (t->output.out != NULL) {
|
|
if (pipe(fdout) != 0) {
|
|
ERR("could not create out pipe for %s\n", t->name);
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
if (t->output.err != NULL) {
|
|
if (pipe(fderr) != 0) {
|
|
ERR("could not create err pipe for %s\n", t->name);
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
if (pipe(fdmonitor) != 0) {
|
|
ERR("could not create monitor pipe for %s\n", t->name);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (prepend_path(t->path) < 0) {
|
|
ERR("failed to prepend '%s' to PATH\n", t->path);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
LOG("running %s, in forked context\n", t->name);
|
|
|
|
pid = fork();
|
|
if (pid < 0) {
|
|
ERR("could not fork(): %m\n");
|
|
LOG("FAILED: %s\n", t->name);
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (pid > 0)
|
|
return test_run_parent(t, fdout, fderr, fdmonitor, pid);
|
|
|
|
return test_run_child(t, fdout, fderr, fdmonitor);
|
|
}
|