blob: 232c0ba8f6aaea22b76bb39718250bff6bc2ce39 [file] [log] [blame]
/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2012-2019 Intel Corporation. All rights reserved.
*
*
* This library 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 library 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, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#define _GNU_SOURCE
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <getopt.h>
#include <poll.h>
#include <dirent.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mount.h>
#include <sys/param.h>
#include <sys/reboot.h>
#include <sys/time.h>
#include <glob.h>
#include <time.h>
#include <ell/ell.h>
#include "linux/nl80211.h"
#ifndef WAIT_ANY
#define WAIT_ANY (-1)
#endif
#define CMDLINE_MAX 2048
#define BIN_IW "iw"
#define BIN_HWSIM "hwsim"
#define BIN_OFONO "ofonod"
#define BIN_PHONESIM "phonesim"
#define BIN_HOSTAPD "hostapd"
#define BIN_IWD "iwd"
#define HWSIM_RADIOS_MAX 100
#define TEST_MAX_EXEC_TIME_SEC 20
static enum action {
ACTION_AUTO_TEST,
ACTION_UNIT_TEST,
} test_action;
static const char *own_binary;
static char **test_argv;
static int test_argc;
static char **verbose_apps;
static char *verbose_opt;
static bool valgrind;
static char *gdb_opt;
static bool enable_debug;
const char *debug_filter;
static struct l_settings *hw_config;
static bool native_hw;
static bool shell;
static bool log;
static char log_dir[PATH_MAX];
static uid_t log_uid;
static gid_t log_gid;
static const char *qemu_binary;
static const char *kernel_image;
static const char *exec_home;
static const char *test_action_params;
static char top_level_path[PATH_MAX];
static struct l_queue *wiphy_list;
#if defined(__i386__)
/*
* If iwd is being compiled for i386, prefer the i386 qemu but try the
* X86-64 version as a fallback.
*/
static const char * const qemu_table[] = {
"qemu-system-i386",
"/usr/bin/qemu-system-i386",
"qemu-system-x86_64",
"/usr/bin/qemu-system-x86_64",
NULL
};
#elif defined(__x86_64__)
/*
* If iwd is being built for X86-64 bits there's no point booting a 32-bit
* only system.
*/
static const char * const qemu_table[] = {
"qemu-system-x86_64",
"/usr/bin/qemu-system-x86_64",
NULL
};
#elif defined(__arm__)
/*
* If iwd is being built for ARM look for 32-bit version.
*/
static const char * const qemu_table[] = {
"qemu-system-arm",
"/usr/bin/qemu-system-arm",
NULL
};
#elif defined(__aarch64__)
/*
* If iwd is being built for AARCH64 look for 64-bit version.
*/
static const char * const qemu_table[] = {
"qemu-system-aarch64",
"/usr/bin/qemu-system-aarch64",
NULL
};
#elif defined(__powerpc__)
/*
* If iwd is being built for PowerPC look for 32-bit version.
*/
static const char * const qemu_table[] = {
"qemu-system-ppc",
"/usr/bin/qemu-system-ppc",
NULL
};
#elif defined(__powerpc64__)
/*
* If iwd is being built for PowerPC-64 look for 64-bit version.
*/
static const char * const qemu_table[] = {
"qemu-system-ppc64",
"/usr/bin/qemu-system-ppc64",
NULL
};
#else
#warning Qemu binary name not defined for this architecture yet
static const char * const qemu_table[] = { NULL };
#endif
struct wiphy {
char name[20];
int id;
unsigned int interface_index;
bool interface_created : 1;
bool used_by_hostapd : 1;
char *interface_name;
char *hostapd_ctrl_interface;
char *hostapd_config;
bool can_ap;
};
static bool check_verbosity(const char *app)
{
char **apps = verbose_apps;
/*
* All processes are verbose if logging is enabled. Kernel is a bit
* different as we just pipe dmesg into a log file at the end of
* execution.
*/
if (log && strcmp(app, "kernel") != 0)
return true;
/*
* Turn on output if this is a unit test run. Nothing should output
* anything except the tests themselves and the kernel.
*/
if (test_action == ACTION_UNIT_TEST && strcmp(app, "kernel") != 0)
return true;
if (!apps)
return false;
while (*apps) {
if (!strcmp(app, *apps))
return true;
apps++;
}
return false;
}
static bool path_exist(const char *path_name)
{
struct stat st;
if (!stat(path_name, &st))
return true;
return false;
}
static const char *find_qemu(void)
{
int i;
for (i = 0; qemu_table[i]; i++)
if (path_exist(qemu_table[i]))
return qemu_table[i];
return NULL;
}
static const char * const kernel_table[] = {
"bzImage",
"arch/x86/boot/bzImage",
"vmlinux",
"arch/x86/boot/vmlinux",
NULL
};
static const char *find_kernel(void)
{
int i;
for (i = 0; kernel_table[i]; i++)
if (path_exist(kernel_table[i]))
return kernel_table[i];
return NULL;
}
static const struct {
const char *target;
const char *linkpath;
} dev_table[] = {
{ "/proc/self/fd", "/dev/fd" },
{ "/proc/self/fd/0", "/dev/stdin" },
{ "/proc/self/fd/1", "/dev/stdout" },
{ "/proc/self/fd/2", "/dev/stderr" },
{ }
};
static const struct {
const char *fstype;
const char *target;
const char *options;
unsigned long flags;
} mount_table[] = {
{ "sysfs", "/sys", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV },
{ "proc", "/proc", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV },
{ "devpts", "/dev/pts", "mode=0620", MS_NOSUID|MS_NOEXEC },
{ "tmpfs", "/dev/shm", "mode=1777",
MS_NOSUID|MS_NODEV|MS_STRICTATIME },
{ "tmpfs", "/run", "mode=0755",
MS_NOSUID|MS_NODEV|MS_STRICTATIME },
{ "tmpfs", "/var/lib/iwd", "mode=0755", 0 },
{ "tmpfs", "/tmp", NULL, 0 },
{ "debugfs", "/sys/kernel/debug", NULL, 0 },
{ }
};
static const char * const config_table[] = {
"/usr/share/dbus-1",
NULL
};
static void prepare_sandbox(void)
{
int i;
for (i = 0; mount_table[i].fstype; i++) {
struct stat st;
if (lstat(mount_table[i].target, &st) < 0) {
mkdir(mount_table[i].target, 0755);
}
if (mount(mount_table[i].fstype,
mount_table[i].target,
mount_table[i].fstype,
mount_table[i].flags,
mount_table[i].options) < 0) {
l_error("Error: Failed to mount filesystem %s",
mount_table[i].target);
}
}
for (i = 0; dev_table[i].target; i++) {
if (symlink(dev_table[i].target, dev_table[i].linkpath) < 0)
l_error("Failed to create device symlink: %s",
strerror(errno));
}
setsid();
ioctl(STDIN_FILENO, TIOCSCTTY, 1);
for (i = 0; config_table[i]; i++) {
if (mount("tmpfs", config_table[i], "tmpfs",
MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME,
"mode=0755") < 0)
l_error("Failed to create filesystem: %s",
strerror(errno));
}
}
static char *const qemu_argv[] = {
"",
"-machine", "type=q35,accel=kvm:tcg",
"-nodefaults",
"-no-user-config",
"-monitor", "none",
"-display", "none",
"-m", "192M",
"-nographic",
"-vga", "none",
"-net", "none",
"-no-acpi",
"-no-hpet",
"-no-reboot",
"-fsdev", "local,id=fsdev-root,path=/,readonly,security_model=none",
"-device", "virtio-9p-pci,fsdev=fsdev-root,mount_tag=/dev/root",
"-chardev", "stdio,id=chardev-serial0,signal=off",
"-device", "pci-serial,chardev=chardev-serial0",
"-device", "virtio-rng-pci",
NULL
};
static char *const qemu_envp[] = {
"HOME=/",
NULL
};
static bool check_virtualization(void)
{
#if defined(__GNUC__) && (defined(__i386__) || defined(__amd64__))
uint32_t ecx;
__asm__ __volatile__("cpuid" : "=c" (ecx) :
"a" (1) : "%ebx", "%edx");
if (!!(ecx & (1 << 5))) {
l_info("Found support for Virtual Machine eXtensions");
return true;
}
__asm__ __volatile__("cpuid" : "=c" (ecx) :
"a" (0x80000001) : "%ebx", "%edx");
if (ecx & (1 << 2)) {
l_info("Found support for Secure Virtual Machine extension");
return true;
}
#endif
return false;
}
static bool start_qemu(void)
{
char cwd[PATH_MAX], testargs[PATH_MAX];
char *initcmd, *cmdline;
char **argv;
int i, pos;
bool has_virt;
int num_pci = 0, num_usb = 0;
char **pci_keys = NULL;
char **usb_keys = NULL;
L_AUTO_FREE_VAR(char *, log_option) = NULL;
has_virt = check_virtualization();
if (!getcwd(cwd, sizeof(cwd)))
strcat(cwd, "/");
if (own_binary[0] == '/')
initcmd = l_strdup_printf("%s", own_binary);
else
initcmd = l_strdup_printf("%s/%s", cwd, own_binary);
pos = snprintf(testargs, sizeof(testargs), "%s", test_argv[0]);
for (i = 1; i < test_argc; i++) {
int len;
len = sizeof(testargs) - pos;
pos += snprintf(testargs + pos, len, " %s", test_argv[i]);
}
cmdline = l_strdup_printf(
"console=ttyS0,115200n8 earlyprintk=serial "
"rootfstype=9p "
"root=/dev/root "
"rootflags=trans=virtio,version=9p2000.u "
"acpi=off pci=noacpi noapic %s ro "
"mac80211_hwsim.radios=0 init=%s TESTHOME=%s "
"TESTVERBOUT=\'%s\' DEBUG_FILTER=\'%s\'"
"TEST_ACTION=%u TEST_ACTION_PARAMS=\'%s\' "
"TESTARGS=\'%s\' PATH=\'%s\' VALGRIND=%u "
"GDB=\'%s\' HW=\'%s\' SHELL=%u "
"LOG_PATH=\'%s\' LOG_UID=\'%d\' LOG_GID=\'%d\'",
check_verbosity("kernel") ? "ignore_loglevel" : "quiet",
initcmd, cwd, verbose_opt ? verbose_opt : "none",
enable_debug ? debug_filter : "",
test_action,
test_action_params ? test_action_params : "",
testargs,
getenv("PATH"),
valgrind,
gdb_opt ? gdb_opt : "none",
hw_config ? "real" : "virtual",
shell,
log ? log_dir : "none",
log_uid, log_gid);
if (hw_config) {
if (l_settings_has_group(hw_config, "PCIAdapters")) {
pci_keys = l_settings_get_keys(hw_config, "PCIAdapters");
for (num_pci = 0; pci_keys[num_pci]; num_pci++);
}
if (l_settings_has_group(hw_config, "USBAdapters")) {
usb_keys = l_settings_get_keys(hw_config, "USBAdapters");
for (num_usb = 0; usb_keys[num_usb]; num_usb++);
}
if (!pci_keys && !usb_keys) {
l_error("hs config had no PCIAdapters or USBAdapters");
l_free(initcmd);
l_free(cmdline);
return false;
}
}
/*
* This got quite confusing. We need enough room for:
*
* qemu_argv (static list above with default parameters)
* -kernel,-append,-cpu,-host parameters (7)
* -enable-kvm and/or -usb (2)
* PCI and/or USB parameters (num_pci * 2) (num_usb * 2)
* Logging directory/device (2)
*/
argv = alloca(sizeof(qemu_argv) + sizeof(char *) *
(7 + (2 + (num_pci * 2) + (num_usb * 2) + 2)));
memcpy(argv, qemu_argv, sizeof(qemu_argv));
pos = (sizeof(qemu_argv) / sizeof(char *)) - 1;
argv[0] = (char *) qemu_binary;
argv[pos++] = "-kernel";
argv[pos++] = (char *) kernel_image;
argv[pos++] = "-append";
argv[pos++] = (char *) cmdline;
argv[pos++] = "-cpu";
argv[pos++] = has_virt ? "host" : "max";
if (pci_keys) {
argv[pos++] = "-enable-kvm";
for (i = 0; pci_keys[i]; i++) {
argv[pos++] = "-device";
argv[pos] = alloca(22);
sprintf(argv[pos], "vfio-pci,host=%s",
l_settings_get_value(hw_config,
"PCIAdapters", pci_keys[i]));
pos++;
}
}
if (usb_keys) {
argv[pos++] = "-usb";
for (i = 0; usb_keys[i]; i++) {
const char *value = l_settings_get_value(hw_config,
"USBAdapters", usb_keys[i]);
char **info = l_strsplit(value, ',');
if (l_strv_length(info) != 2) {
l_error("hw config formatting error");
l_strv_free(info);
return false;
}
argv[pos++] = "-device";
argv[pos] = alloca(32);
sprintf(argv[pos], "usb-host,hostbus=%s,hostaddr=%s",
info[0], info[1]);
pos++;
l_strv_free(info);
}
}
if (log) {
/*
* Create a virtfs device and tag it. This allows the guest to
* mount 'logdir' in the path specified with --log.
*/
log_option = l_strdup_printf("local,path=%s,mount_tag=logdir,"
"security_model=passthrough,id=logdir",
log_dir);
argv[pos++] = "-virtfs";
argv[pos++] = log_option;
}
argv[pos] = NULL;
execve(argv[0], argv, qemu_envp);
/* Don't expect to reach here */
free(initcmd);
free(cmdline);
return true;
}
static pid_t execute_program(char *argv[], char *envp[], bool wait,
const char *test_name)
{
int status;
pid_t pid, child_pid;
char *str;
bool verbose;
char *log_name = argv[0];
if (!argv[0])
return -1;
/*
* We have a few special cases here:
*
* Since execute_program automatically logs to <process>.log this would
* put all iwd output into valgrind.log rather than iwd.log. Since we
* are explicitly having valgrind output to a log file we can assume any
* output from this is only IWD, and not valgrind.
*
* python3 is special cased so that tests which start IWD manually can
* still show IWD output when using -v iwd.
*/
if (!strcmp(log_name, "valgrind") || !strcmp(log_name, "python3"))
log_name = "iwd";
str = l_strjoinv(argv, ' ');
l_debug("Executing: %s", str);
l_free(str);
child_pid = fork();
if (child_pid < 0) {
l_error("Failed to fork new process");
return -1;
}
if (child_pid == 0) {
int fd = -1;
L_AUTO_FREE_VAR(char *, log_file) = NULL;
verbose = check_verbosity(log_name);
/* No stdout and no logging */
if (!verbose && !log)
fd = open("/dev/null", O_WRONLY);
else if (log && verbose) {
/*
* Create the log file for this process. If no test name
* was specified this is a 'global' process (only run
* once, not per-test).
*/
if (test_name) {
log_file = l_strdup_printf("%s/%s/%s.log",
log_dir, test_name, log_name);
} else
log_file = l_strdup_printf("%s/%s.log",
log_dir, log_name);
fd = open(log_file, O_WRONLY | O_CREAT | O_APPEND,
S_IRUSR | S_IWUSR);
if (fchown(fd, log_uid, log_gid) < 0)
l_error("failed to fchown %s", log_file);
}
if (fd > -1) {
dup2(fd, 1);
dup2(fd, 2);
close(fd);
}
execvpe(argv[0], argv, envp);
l_error("Failed to call execvpe for: %s. Error: %s", argv[0],
strerror(errno));
exit(EXIT_FAILURE);
}
if (!wait)
goto exit;
do {
pid = waitpid(child_pid, &status, 0);
} while (!WIFEXITED(status) && pid == child_pid);
if (WEXITSTATUS(status) != EXIT_SUCCESS)
return -1;
exit:
return child_pid;
}
static void kill_process(pid_t pid)
{
int status;
int i = 0;
l_debug("Terminate pid: %d", pid);
kill(pid, SIGTERM);
do {
if (waitpid(pid, &status, WNOHANG) == pid)
return;
usleep(100000);
} while (!WIFEXITED(status) && !WIFSIGNALED(status) && i++ < 20);
l_error("Failed to kill process %d gracefully", pid);
kill(pid, SIGKILL);
/* SIGKILL shouldn't need WNOHANG */
waitpid(pid, &status, 0);
}
static bool wait_for_socket(const char *socket, useconds_t wait_time)
{
int i = 0;
do {
if (path_exist(socket))
return true;
usleep(wait_time);
} while ((wait_time > 0) ? i++ < 20 : true);
l_error("Error: cannot find socket: %s", socket);
return false;
}
static void create_dbus_system_conf(void)
{
FILE *fp;
fp = fopen("/usr/share/dbus-1/system.conf", "we");
if (!fp)
return;
fputs("<!DOCTYPE busconfig PUBLIC ", fp);
fputs("\"-//freedesktop//DTD D-Bus Bus Configuration 1.0//EN\" ", fp);
fputs("\"http://www.freedesktop.org/standards/dbus/1.0/", fp);
fputs("busconfig.dtd\">\n", fp);
fputs("<busconfig>\n", fp);
fputs("<type>system</type>\n", fp);
fputs("<listen>unix:path=/run/dbus/system_bus_socket</listen>\n", fp);
fputs("<limit name=\"reply_timeout\">2147483647</limit>", fp);
fputs("<policy context=\"default\">\n", fp);
fputs("<allow user=\"*\"/>\n", fp);
fputs("<allow own=\"*\"/>\n", fp);
fputs("<allow send_type=\"method_call\"/>\n", fp);
fputs("<allow send_type=\"signal\"/>\n", fp);
fputs("<allow send_type=\"method_return\"/>\n", fp);
fputs("<allow send_type=\"error\"/>\n", fp);
fputs("<allow receive_type=\"method_call\"/>\n", fp);
fputs("<allow receive_type=\"signal\"/>\n", fp);
fputs("<allow receive_type=\"method_return\"/>\n", fp);
fputs("<allow receive_type=\"error\"/>\n", fp);
fputs("</policy>\n", fp);
fputs("</busconfig>\n", fp);
fclose(fp);
mkdir("/run/dbus", 0755);
}
static bool start_dbus_daemon(void)
{
char *argv[4];
pid_t pid;
argv[0] = "dbus-daemon";
argv[1] = "--system";
argv[2] = "--nosyslog";
argv[3] = NULL;
if (check_verbosity("dbus"))
setenv("DBUS_VERBOSE", "1", true);
pid = execute_program(argv, environ, false, NULL);
if (pid < 0)
return false;
if (!wait_for_socket("/run/dbus/system_bus_socket", 25 * 10000))
return false;
if (check_verbosity("dbus-monitor")) {
argv[0] = "dbus-monitor";
argv[1] = "--system";
argv[2] = NULL;
execute_program(argv, environ, false, NULL);
}
l_debug("D-Bus is running");
return true;
}
static bool start_haveged(void)
{
char *argv[2];
pid_t pid;
argv[0] = "haveged";
argv[1] = NULL;
pid = execute_program(argv, environ, true, NULL);
if (pid < 0)
return false;
return true;
}
static bool set_interface_state(const char *if_name, bool isUp)
{
char *state, *argv[4];
pid_t pid;
if (isUp)
state = "up";
else
state = "down";
argv[0] = "ifconfig";
argv[1] = (char *) if_name;
argv[2] = state;
argv[3] = NULL;
pid = execute_program(argv, environ, true, NULL);
if (pid < 0)
return false;
return true;
}
static bool create_interface(const char *if_name, const char *phy_name)
{
char *argv[9];
pid_t pid;
argv[0] = BIN_IW;
argv[1] = "phy";
argv[2] = (char *) phy_name;
argv[3] = "interface";
argv[4] = "add";
argv[5] = (char *) if_name;
argv[6] = "type";
argv[7] = "managed";
argv[8] = NULL;
pid = execute_program(argv, environ, true, NULL);
if (pid < 0)
return false;
return true;
}
static bool delete_interface(const char *if_name)
{
char *argv[5];
pid_t pid;
argv[0] = BIN_IW;
argv[1] = "dev";
argv[2] = (char *) if_name;
argv[3] = "del";
argv[4] = NULL;
pid = execute_program(argv, environ, true, NULL);
if (pid < 0)
return false;
return true;
}
static bool list_interfaces(void)
{
char *argv[3];
pid_t pid;
argv[0] = "ifconfig";
argv[1] = "-a";
argv[2] = NULL;
pid = execute_program(argv, environ, true, NULL);
if (pid < 0)
return false;
return true;
}
static bool list_hwsim_radios(void)
{
char *argv[3];
pid_t pid;
argv[0] = BIN_HWSIM;
argv[1] = "--list";
argv[2] = NULL;
pid = execute_program(argv, environ, true, NULL);
if (pid < 0)
return false;
return true;
}
static int read_radio_id(void)
{
static int current_radio_id;
return current_radio_id++;
}
struct hwsim_radio_params {
unsigned int channels;
bool p2p_device;
bool use_chanctx;
char *iftype_disable;
char *cipher_disable;
};
static int create_hwsim_radio(const char *radio_name,
struct hwsim_radio_params *params)
{
char *argv[10];
pid_t pid;
int idx = 0;
/*TODO add the rest of params*/
argv[idx++] = BIN_HWSIM;
argv[idx++] = "--create";
argv[idx++] = "--name";
argv[idx++] = (char *) radio_name;
argv[idx++] = "--nointerface";
if (params->iftype_disable) {
argv[idx++] = "--iftype-disable";
argv[idx++] = params->iftype_disable;
}
if (params->cipher_disable) {
argv[idx++] = "--cipher-disable";
argv[idx++] = params->cipher_disable;
}
argv[idx] = NULL;
pid = execute_program(argv, environ, true, NULL);
if (pid < 0)
return -1;
return read_radio_id();
}
static bool destroy_hwsim_radio(int radio_id)
{
char *argv[4];
char destroy_param[20];
pid_t pid;
sprintf(destroy_param, "--destroy=%d", radio_id);
argv[0] = BIN_HWSIM;
argv[1] = destroy_param;
argv[2] = NULL;
pid = execute_program(argv, environ, true, NULL);
if (pid < 0)
return false;
return true;
}
static pid_t register_hwsim_as_trans_medium(void)
{
char *argv[16];
unsigned int idx = 0;
if (strcmp(gdb_opt, "hwsim") == 0) {
argv[idx++] = "gdb";
argv[idx++] = "--args";
}
argv[idx++] = BIN_HWSIM;
argv[idx++] = NULL;
return execute_program(argv, environ, false, NULL);
}
static void terminate_medium(pid_t medium_pid)
{
kill_process(medium_pid);
}
#define HOSTAPD_CTRL_INTERFACE_PREFIX "/var/run/hostapd"
static bool loopback_started;
static void start_loopback(void)
{
char *argv[7];
if (loopback_started)
return;
argv[0] = "ifconfig";
argv[1] = "lo";
argv[2] = "127.0.0.1";
argv[3] = "up";
argv[4] = NULL;
execute_program(argv, environ, false, NULL);
argv[0] = "route";
argv[1] = "add";
argv[2] = "127.0.0.1";
argv[3] = NULL;
execute_program(argv, environ, false, NULL);
loopback_started = true;
}
static pid_t start_phonesim(const char *test_name)
{
char *argv[5];
argv[0] = BIN_PHONESIM;
argv[1] = "-p";
argv[2] = "12345";
argv[3] = "/usr/share/phonesim/default.xml";
argv[4] = NULL;
start_loopback();
setenv("OFONO_PHONESIM_CONFIG", "/tmp/phonesim.conf", true);
return execute_program(argv, environ, false, test_name);
}
static void stop_phonesim(pid_t pid)
{
kill_process(pid);
}
static pid_t start_ofono(const char *test_name)
{
char *argv[5];
bool verbose = check_verbosity(BIN_OFONO);
argv[0] = BIN_OFONO;
argv[1] = "-n";
argv[2] = "--plugin=atmodem,phonesim";
if (verbose)
argv[3] = "-d";
else
argv[3] = NULL;
argv[4] = NULL;
start_loopback();
return execute_program(argv, environ, false, test_name);
}
static void stop_ofono(pid_t pid)
{
kill_process(pid);
}
static pid_t start_hostapd(char **config_files, struct wiphy **wiphys,
const char *test_name, const char *radius_conf)
{
char **argv;
pid_t pid;
int idx = 0;
uint32_t wait = 25 * 10000;
bool verbose = check_verbosity(BIN_HOSTAPD);
size_t ifnames_size;
char *ifnames;
int i;
for (i = 0, ifnames_size = 0; wiphys[i]; i++)
ifnames_size += 1 + strlen(wiphys[i]->interface_name);
argv = alloca(sizeof(char *) * (9 + i));
if (strcmp(gdb_opt, "hostapd") == 0) {
argv[idx++] = "gdb";
argv[idx++] = "--args";
wait = 0;
}
argv[idx++] = BIN_HOSTAPD;
ifnames = alloca(ifnames_size);
argv[idx++] = "-i";
argv[idx++] = ifnames;
argv[idx++] = "-g";
argv[idx++] = wiphys[0]->hostapd_ctrl_interface;
for (i = 0, ifnames_size = 0; wiphys[i]; i++) {
if (ifnames_size)
ifnames[ifnames_size++] = ',';
strcpy(ifnames + ifnames_size, wiphys[i]->interface_name);
ifnames_size += strlen(wiphys[i]->interface_name);
argv[idx++] = config_files[i];
}
if (radius_conf)
argv[idx++] = (void *)radius_conf;
if (verbose) {
argv[idx++] = "-d";
argv[idx++] = NULL;
} else {
argv[idx++] = NULL;
}
pid = execute_program(argv, environ, false, log ? test_name : NULL);
if (pid < 0) {
goto exit;
}
if (!wait_for_socket(wiphys[0]->hostapd_ctrl_interface, wait))
pid = -1;
exit:
return pid;
}
static void destroy_hostapd_instances(pid_t hostapd_pids[])
{
int i = 0;
while (hostapd_pids[i] != -1) {
kill_process(hostapd_pids[i]);
l_debug("hostapd instance with pid=%d is destroyed",
hostapd_pids[i]);
hostapd_pids[i] = -1;
i++;
}
}
#define TEST_TOP_DIR_DEFAULT_NAME "autotests"
#define TEST_DIR_PREFIX "test"
static bool is_test_file(const char *file)
{
size_t i;
static const char * const test_file_extension_table[] = {
"test",
"test.py",
"Test",
"Test.py",
NULL
};
for (i = 0; test_file_extension_table[i]; i++) {
if (l_str_has_suffix(file, test_file_extension_table[i]))
return true;
}
return false;
}
static int is_test_dir(const char *dir)
{
return strncmp(dir, TEST_DIR_PREFIX, strlen(TEST_DIR_PREFIX)) == 0;
}
static bool find_test_configuration(const char *path, int level,
struct l_queue *config_queue);
struct test_entry {
struct l_queue *test_queue;
char *path;
};
static int insert_py_test(const void *a, const void *b, void *user_data)
{
return strcmp((const char *)a, (const char *)b);
}
static int insert_test_entry(const void *a, const void *b, void *user_data)
{
const struct test_entry *entry_a = a;
const struct test_entry *entry_b = b;
return strcmp(entry_a->path, entry_b->path);
}
static bool add_path(const char *path, int level, struct l_queue *config_queue)
{
DIR *dir = NULL;
struct l_queue *py_test_queue = NULL;
struct dirent *entry;
char *npath;
dir = opendir(path);
if (!dir) {
l_error("Test directory does not exist: %s", path);
return false;
}
while ((entry = readdir(dir))) {
if (entry->d_type == DT_DIR) {
if (!strcmp(entry->d_name, ".") ||
!strcmp(entry->d_name, ".."))
continue;
if (level == 0 && is_test_dir(entry->d_name)) {
npath = l_strdup_printf("%s/%s", path,
entry->d_name);
find_test_configuration(npath, 1, config_queue);
l_free(npath);
}
} else if (level == 1 && is_test_file(entry->d_name)) {
if (!py_test_queue)
py_test_queue = l_queue_new();
l_queue_insert(py_test_queue, l_strdup(entry->d_name),
insert_py_test, NULL);
}
}
if (py_test_queue && !l_queue_isempty(py_test_queue)) {
struct test_entry *entry = l_new(struct test_entry, 1);
entry->test_queue = py_test_queue;
entry->path = l_strdup(path);
l_queue_insert(config_queue, entry, insert_test_entry, NULL);
}
closedir(dir);
return true;
}
static bool find_test_configuration(const char *path, int level,
struct l_queue *config_queue)
{
glob_t glist;
int i = 0;
int ret;
if (!config_queue)
return false;
ret = glob(path, 0, NULL, &glist);
if (ret != 0) {
l_error("Could not match glob %s", path);
return false;
}
while (glist.gl_pathv[i]) {
if (!add_path(glist.gl_pathv[i], level, config_queue))
return false;
i++;
}
return true;
}
#define HW_CONFIG_FILE_NAME "hw.conf"
#define HW_CONFIG_GROUP_HOSTAPD "HOSTAPD"
#define HW_CONFIG_GROUP_SETUP "SETUP"
#define HW_CONFIG_SETUP_NUM_RADIOS "num_radios"
#define HW_CONFIG_SETUP_RADIO_CONFS "radio_confs"
#define HW_CONFIG_SETUP_MAX_EXEC_SEC "max_test_exec_interval_sec"
#define HW_CONFIG_SETUP_TMPFS_EXTRAS "tmpfs_extra_stuff"
#define HW_CONFIG_SETUP_START_IWD "start_iwd"
#define HW_CONFIG_SETUP_IWD_CONF_DIR "iwd_config_dir"
#define HW_CONFIG_SETUP_REG_DOMAIN "reg_domain"
#define HW_CONFIG_SETUP_NEEDS_HWSIM "needs_hwsim"
static struct l_settings *read_hw_config(const char *test_dir_path)
{
struct l_settings *hw_settings;
char *hw_file;
hw_file = l_strdup_printf("%s/%s", test_dir_path, HW_CONFIG_FILE_NAME);
hw_settings = l_settings_new();
if (!l_settings_load_from_file(hw_settings, hw_file)) {
l_error("No %s file found", HW_CONFIG_FILE_NAME);
goto error_exit;
}
if (!l_settings_has_group(hw_settings, HW_CONFIG_GROUP_SETUP)) {
l_error("No %s setting group found in %s",
HW_CONFIG_GROUP_SETUP, hw_file);
goto error_exit;
}
l_free(hw_file);
return hw_settings;
error_exit:
l_free(hw_file);
l_settings_free(hw_settings);
return NULL;
}
#define HW_CONFIG_PHY_CHANNELS "channels"
#define HW_CONFIG_PHY_CHANCTX "use_chanctx"
#define HW_CONFIG_PHY_P2P "p2p_device"
#define HW_CONFIG_PHY_IFTYPE_DISABLE "iftype_disable"
#define HW_CONFIG_PHY_CIPHER_DISABLE "cipher_disable"
#define HW_MIN_NUM_RADIOS 1
#define HW_INTERFACE_PREFIX "wln"
#define HW_INTERFACE_STATE_UP true
#define HW_INTERFACE_STATE_DOWN false
static bool configure_hw_radios(struct l_settings *hw_settings,
struct l_queue *wiphy_list)
{
char **radio_conf_list;
int i, num_radios_requested;
bool status = false;
l_settings_get_int(hw_settings, HW_CONFIG_GROUP_SETUP,
HW_CONFIG_SETUP_NUM_RADIOS,
&num_radios_requested);
if (num_radios_requested < HW_MIN_NUM_RADIOS) {
l_error("%s must be greater or equal to %d",
HW_CONFIG_SETUP_NUM_RADIOS, HW_MIN_NUM_RADIOS);
return false;
}
radio_conf_list =
l_settings_get_string_list(hw_settings, HW_CONFIG_GROUP_SETUP,
HW_CONFIG_SETUP_RADIO_CONFS,
':');
for (i = 0; i < num_radios_requested; i++) {
struct wiphy *wiphy;
struct hwsim_radio_params params = { 0 };
wiphy = l_new(struct wiphy, 1);
sprintf(wiphy->name, "rad%d", i);
/* radio not in radio_confs, use default parameters */
if (!l_strv_contains(radio_conf_list, wiphy->name)) {
params.channels = 1;
params.p2p_device = true;
params.use_chanctx = true;
goto create;
}
if (!l_settings_get_uint(hw_settings, wiphy->name,
HW_CONFIG_PHY_CHANNELS,
&params.channels))
params.channels = 1;
if (!l_settings_get_bool(hw_settings, wiphy->name,
HW_CONFIG_PHY_P2P, &params.p2p_device))
params.p2p_device = true;
if (!l_settings_get_bool(hw_settings, wiphy->name,
HW_CONFIG_PHY_CHANCTX,
&params.use_chanctx))
params.use_chanctx = true;
params.iftype_disable = l_settings_get_string(hw_settings,
wiphy->name,
HW_CONFIG_PHY_IFTYPE_DISABLE);
params.cipher_disable = l_settings_get_string(hw_settings,
wiphy->name,
HW_CONFIG_PHY_CIPHER_DISABLE);
create:
wiphy->id = create_hwsim_radio(wiphy->name, &params);
wiphy->can_ap = true;
if (wiphy->id < 0) {
l_free(wiphy);
goto exit;
}
l_queue_push_tail(wiphy_list, wiphy);
}
status = true;
exit:
l_strfreev(radio_conf_list);
return status;
}
static void wiphy_free(void *data)
{
struct wiphy *wiphy = data;
if (wiphy->interface_created) {
set_interface_state(wiphy->interface_name,
HW_INTERFACE_STATE_DOWN);
if (delete_interface(wiphy->interface_name))
l_debug("Removed interface %s", wiphy->interface_name);
else
l_error("Failed to remove interface %s",
wiphy->interface_name);
}
/* Native interfaces cannot be destroyed */
if (native_hw) {
set_interface_state(wiphy->interface_name,
HW_INTERFACE_STATE_DOWN);
} else {
destroy_hwsim_radio(wiphy->id);
l_debug("Removed radio id %d", wiphy->id);
}
l_free(wiphy->hostapd_config);
l_free(wiphy->hostapd_ctrl_interface);
l_free(wiphy->interface_name);
l_free(wiphy);
}
static bool configure_hostapd_instances(struct l_settings *hw_settings,
char *config_dir_path,
struct l_queue *wiphy_list,
pid_t hostapd_pids_out[],
int *phys_used)
{
char **hostap_keys;
int i;
char **hostapd_config_file_paths;
struct wiphy **wiphys;
const char *radius_config = NULL;
*phys_used = 0;
if (!l_settings_has_group(hw_settings, HW_CONFIG_GROUP_HOSTAPD)) {
l_info("No hostapd instances to create");
return true;
}
hostap_keys =
l_settings_get_keys(hw_settings, HW_CONFIG_GROUP_HOSTAPD);
for (i = 0; hostap_keys[i]; i++);
hostapd_config_file_paths = l_new(char *, i + 1);
wiphys = alloca(sizeof(struct wiphy *) * (i + 1));
memset(wiphys, 0, sizeof(struct wiphy *) * (i + 1));
hostapd_pids_out[0] = -1;
for (i = 0; hostap_keys[i]; i++) {
const struct l_queue_entry *wiphy_entry;
const char *hostapd_config_file;
unsigned wiphy_idx = 0;
hostapd_config_file =
l_settings_get_value(hw_settings,
HW_CONFIG_GROUP_HOSTAPD,
hostap_keys[i]);
hostapd_config_file_paths[i] =
l_strdup_printf("%s/%s", config_dir_path,
hostapd_config_file);
if (!path_exist(hostapd_config_file_paths[i])) {
l_error("%s : hostapd configuration file [%s] "
"does not exist.", HW_CONFIG_FILE_NAME,
hostapd_config_file_paths[i]);
goto done;
}
if (!strcmp(hostap_keys[i], "radius_server")) {
radius_config = l_settings_get_value(hw_settings,
HW_CONFIG_GROUP_HOSTAPD,
"radius_server");
continue;
}
for (wiphy_entry = l_queue_get_entries(wiphy_list);
wiphy_entry;
wiphy_entry = wiphy_entry->next,
wiphy_idx++) {
struct wiphy *wiphy = wiphy_entry->data;
/*
* We can skip this check in native mode since we have
* no control over the phy name. Any test requiring a
* "special" radio should not be ran in native mode.
*/
if (!native_hw && strcmp(wiphy->name, hostap_keys[i]))
continue;
if (wiphy->used_by_hostapd) {
/*
* Since we bypass the above check in native
* mode we could still get here. We can just
* continue searching for more adapters if this
* one is already in use.
*/
if (native_hw)
continue;
l_error("Wiphy %s already used by hostapd",
wiphy->name);
goto done;
}
if (!wiphy->can_ap)
continue;
wiphys[i] = wiphy;
break;
}
if (!wiphy_entry) {
l_error("Failed to find available wiphy.");
goto done;
}
if (native_hw)
goto hostapd_done;
wiphys[i]->interface_name = l_strdup_printf("%s%d",
HW_INTERFACE_PREFIX,
wiphy_idx);
if (!create_interface(wiphys[i]->interface_name,
wiphys[i]->name)) {
l_error("Failed to create hostapd interface %s on "
"radio %s",
wiphys[i]->interface_name, wiphys[i]->name);
goto done;
}
wiphys[i]->interface_created = true;
l_info("Created hostapd interface %s on %s radio",
wiphys[i]->interface_name, wiphys[i]->name);
if (!native_hw && !set_interface_state(wiphys[i]->interface_name,
HW_INTERFACE_STATE_UP)) {
l_error("Failed to set %s state UP",
wiphys[i]->interface_name);
goto done;
}
hostapd_done:
wiphys[i]->used_by_hostapd = true;
wiphys[i]->hostapd_ctrl_interface =
l_strdup_printf("%s/%s", HOSTAPD_CTRL_INTERFACE_PREFIX,
wiphys[0]->interface_name);
wiphys[i]->hostapd_config = l_strdup(hostapd_config_file);
(*phys_used)++;
}
hostapd_pids_out[0] = start_hostapd(hostapd_config_file_paths, wiphys,
basename(config_dir_path),
radius_config);
hostapd_pids_out[1] = -1;
done:
l_strfreev(hostapd_config_file_paths);
if (hostapd_pids_out[0] < 1)
return false;
return true;
}
static pid_t start_iwd(const char *config_dir, struct l_queue *wiphy_list,
const char *ext_options, int num_phys, const char *test_name)
{
char *argv[13], **envp;
char *iwd_phys = NULL;
pid_t ret;
int idx = 0;
L_AUTO_FREE_VAR(char *, fd_option) = NULL;
if (valgrind) {
L_AUTO_FREE_VAR(char *, valgrind_log);
int fd;
argv[idx++] = "valgrind";
argv[idx++] = "--leak-check=full";
/*
* Valgrind needs --log-fd if we want both stderr and stdout
*/
if (log)
valgrind_log = l_strdup_printf("%s/%s/valgrind.log",
log_dir, test_name);
else
valgrind_log = l_strdup("/tmp/valgrind.log");
fd = open(valgrind_log, O_WRONLY | O_CREAT | O_APPEND,
S_IRUSR | S_IWUSR);
if (log) {
if (fchown(fd, log_uid, log_gid) < 0)
l_error("chown failed");
}
fd_option = l_strdup_printf("--log-fd=%d", fd);
argv[idx++] = fd_option;
}
if (strcmp(gdb_opt, "iwd") == 0) {
argv[idx++] = "gdb";
argv[idx++] = "--args";
}
argv[idx++] = BIN_IWD;
if (check_verbosity(BIN_IWD) || shell)
argv[idx++] = "-d";
argv[idx] = NULL;
if (wiphy_list) {
const struct l_queue_entry *wiphy_entry;
struct l_string *list = l_string_new(64);
for (wiphy_entry = l_queue_get_entries(wiphy_list);
wiphy_entry;
wiphy_entry = wiphy_entry->next) {
struct wiphy *wiphy = wiphy_entry->data;
if (wiphy->used_by_hostapd)
continue;
/*
* Break out, only adding the required number of phys
* for this test.
*/
if (num_phys == 0)
break;
l_string_append_printf(list, "%s,", wiphy->name);
num_phys--;
}
iwd_phys = l_string_unwrap(list);
/* Take care of last comma */
iwd_phys[strlen(iwd_phys) - 1] = '\0';
argv[idx++] = "-p";
argv[idx++] = iwd_phys;
argv[idx] = NULL;
}
argv[idx++] = (char *)ext_options;
argv[idx] = NULL;
envp = l_strv_copy(environ);
envp = l_strv_append_printf(envp, "CONFIGURATION_DIRECTORY=%s",
config_dir);
envp = l_strv_append_printf(envp, "STATE_DIRECTORY=%s",
DAEMON_STORAGEDIR);
ret = execute_program(argv, envp, false, test_name);
l_strv_free(envp);
l_free(iwd_phys);
return ret;
}
static void terminate_iwd(pid_t iwd_pid)
{
kill_process(iwd_pid);
}
static pid_t start_monitor(const char *test_name)
{
char *argv[6];
char *write_arg;
pid_t pid;
write_arg = l_strdup_printf("%s/%s/monitor.pcap", log_dir, test_name);
argv[0] = "iwmon";
argv[1] = "--nortnl";
argv[2] = "--nowiphy";
argv[3] = "--write";
argv[4] = write_arg;
argv[5] = NULL;
pid = execute_program(argv, environ, false, test_name);
l_free(write_arg);
return pid;
}
static bool create_tmpfs_extra_stuff(char **tmpfs_extra_stuff)
{
size_t i = 0;
if (!tmpfs_extra_stuff)
return true;
while (tmpfs_extra_stuff[i]) {
char *link_dir;
char *target_dir;
target_dir = realpath(tmpfs_extra_stuff[i], NULL);
if (!path_exist(target_dir)) {
l_error("No such directory: %s", target_dir);
l_free(target_dir);
return false;
}
link_dir = l_strdup_printf("%s%s", "/tmp",
rindex(target_dir, '/'));
if (symlink(target_dir, link_dir) < 0) {
l_error("Failed to create symlink %s for %s: %s",
link_dir, target_dir, strerror(errno));
l_free(target_dir);
l_free(link_dir);
return false;
}
l_free(tmpfs_extra_stuff[i]);
l_free(target_dir);
tmpfs_extra_stuff[i] = link_dir;
i++;
}
return true;
}
static bool remove_absolute_path_dirs(char **tmpfs_extra_stuff)
{
size_t i = 0;
if (!tmpfs_extra_stuff)
return true;
while (tmpfs_extra_stuff[i]) {
if (unlink(tmpfs_extra_stuff[i]) < 0) {
l_error("Failed to remove symlink for %s: %s",
tmpfs_extra_stuff[i], strerror(errno));
return false;
}
i++;
}
return true;
}
#define CONSOLE_LN_DEFAULT "\x1B[0m"
#define CONSOLE_LN_RED "\x1B[31m"
#define CONSOLE_LN_GREEN "\x1B[32m"
#define CONSOLE_LN_BLACK "\x1B[30m"
#define CONSOLE_LN_YELLOW "\x1B[33m"
#define CONSOLE_LN_RESET "\033[0m"
#define CONSOLE_LN_BOLD "\x1b[1m"
#define CONSOLE_BG_WHITE "\e[47m"
#define CONSOLE_BG_DEFAULT "\e[0m"
enum test_status {
TEST_STATUS_STARTED,
TEST_STATUS_PASSED,
TEST_STATUS_FAILED,
TEST_STATUS_TIMEDOUT,
};
static void print_test_status(char *test_name, enum test_status ts,
double interval)
{
const char *clear_line = "\r";
int int_len;
char *color_str;
char *status_str;
char *interval_str;
char *line_end = "";
switch (ts) {
case TEST_STATUS_STARTED:
color_str = CONSOLE_LN_RESET;
status_str = "STARTED ";
if (strcmp(verbose_opt, "none"))
line_end = "\n";
break;
case TEST_STATUS_PASSED:
printf("%s", clear_line);
color_str = CONSOLE_LN_GREEN;
status_str = "PASSED ";
line_end = "\n";
break;
case TEST_STATUS_FAILED:
printf("%s", clear_line);
color_str = CONSOLE_LN_RED;
status_str = "FAILED ";
line_end = "\n";
break;
case TEST_STATUS_TIMEDOUT:
printf("%s", clear_line);
color_str = CONSOLE_LN_YELLOW;
status_str = "TIMED OUT ";
line_end = "\n";
break;
}
if (interval > 0)
int_len = snprintf(NULL, 0, "%.3f", interval);
else
int_len = 3;
int_len++;
interval_str = l_malloc(int_len);
memset(interval_str, ' ', int_len);
interval_str[int_len - 1] = '\0';
if (interval > 0)
sprintf(interval_str, "%.3f sec", interval);
else
sprintf(interval_str, "%s", "...");
printf("%s%s%s%-60s%7s%s", color_str, status_str, CONSOLE_LN_RESET,
test_name, interval_str, line_end);
fflush(stdout);
l_free(interval_str);
}
static void test_timeout_timer_tick(struct l_timeout *timeout, void *user_data)
{
pid_t *test_exec_pid = (pid_t *) user_data;
kill_process(*test_exec_pid);
l_main_quit();
}
static void test_timeout_signal_handler(uint32_t signo, void *user_data)
{
switch (signo) {
case SIGINT:
case SIGTERM:
l_main_quit();
break;
}
}
static pid_t start_execution_timeout_timer(unsigned int max_exec_interval_sec,
pid_t *test_exec_pid)
{
struct l_timeout *test_exec_timeout;
pid_t test_timer_pid;
test_timer_pid = fork();
if (test_timer_pid < 0) {
l_error("Failed to fork new process");
return -1;
}
if (test_timer_pid == 0) {
if (!l_main_init())
exit(EXIT_FAILURE);
test_exec_timeout =
l_timeout_create(max_exec_interval_sec,
test_timeout_timer_tick,
test_exec_pid,
NULL);
l_main_run_with_signal(test_timeout_signal_handler, NULL);
l_timeout_remove(test_exec_timeout);
l_main_exit();
exit(EXIT_SUCCESS);
}
return test_timer_pid;
}
struct test_stats {
char *config_cycle_name;
unsigned int num_passed;
unsigned int num_failed;
unsigned int num_timedout;
double py_run_time;
};
static void run_py_tests(struct l_settings *hw_settings,
struct l_queue *test_queue,
struct l_queue *test_stats_queue,
const char *test_name)
{
char *argv[3];
pid_t test_exec_pid, test_timer_pid = -1;
struct timeval time_before, time_after, time_elapsed;
unsigned int max_exec_interval;
char *py_test = NULL;
struct test_stats *test_stats;
pid_t monitor_pid = -1;
if (!l_settings_get_uint(hw_settings, HW_CONFIG_GROUP_SETUP,
HW_CONFIG_SETUP_MAX_EXEC_SEC,
&max_exec_interval))
max_exec_interval = TEST_MAX_EXEC_TIME_SEC;
l_info(CONSOLE_LN_BOLD "%-10s%-60s%s" CONSOLE_LN_RESET, "Status",
"Test", "Duration");
start_next_test:
if (l_queue_isempty(test_queue))
return;
py_test = (char *) l_queue_pop_head(test_queue);
if (!py_test)
return;
if (log) {
char *test_path;
char *ext;
char *full_path;
test_path = l_strdup_printf("%s/%s", test_name, py_test);
ext = strchr(test_path, '.');
ext[0] = '\0';
full_path = l_strdup_printf("%s/%s", log_dir, test_path);
mkdir(full_path, 0755);
if (chown(full_path, log_uid, log_gid) < 0)
l_error("chown failed %s", full_path);
l_free(full_path);
monitor_pid = start_monitor(test_path);
l_free(test_path);
}
argv[0] = "python3";
argv[1] = py_test;
argv[2] = NULL;
print_test_status(py_test, TEST_STATUS_STARTED, 0);
test_exec_pid = execute_program(argv, environ, false, test_name);
gettimeofday(&time_before, NULL);
if (!strcmp(gdb_opt, "none"))
test_timer_pid = start_execution_timeout_timer(
max_exec_interval, &test_exec_pid);
test_stats = (struct test_stats *) l_queue_peek_tail(test_stats_queue);
while (true) {
pid_t corpse;
int status;
double interval;
corpse = waitpid(WAIT_ANY, &status, 0);
if (corpse < 0 || corpse == 0)
continue;
if (test_exec_pid == corpse) {
gettimeofday(&time_after, NULL);
if (test_timer_pid != -1)
kill_process(test_timer_pid);
timersub(&time_after, &time_before, &time_elapsed);
interval = time_elapsed.tv_sec +
1e-6 * time_elapsed.tv_usec;
if (WIFEXITED(status) &&
WEXITSTATUS(status) == EXIT_SUCCESS) {
print_test_status(py_test, TEST_STATUS_PASSED,
interval);
test_stats->num_passed++;
} else if (WIFSIGNALED(status)) {
print_test_status(py_test, TEST_STATUS_TIMEDOUT,
interval);
test_stats->num_timedout++;
} else {
print_test_status(py_test, TEST_STATUS_FAILED,
interval);
test_stats->num_failed++;
}
test_stats->py_run_time += interval;
break;
} else if (WIFSTOPPED(status))
l_info("Process %d stopped with signal %d", corpse,
WSTOPSIG(status));
else if (WIFCONTINUED(status))
l_info("Process %d continued", corpse);
}
l_free(py_test);
py_test = NULL;
if (monitor_pid != -1) {
kill_process(monitor_pid);
monitor_pid = -1;
}
goto start_next_test;
}
static void set_config_cycle_info(const char *config_dir_path,
struct l_queue *test_stats_queue)
{
char sep_line[80];
char *config_name_ptr;
struct test_stats *test_stats;
memset(sep_line, '_', sizeof(sep_line) - 1);
sep_line[sizeof(sep_line) - 1] = '\0';
config_name_ptr = strrchr(config_dir_path, '/');
config_name_ptr++;
l_info("%s", sep_line);
l_info(CONSOLE_LN_BOLD "Starting configuration cycle No: %d [%s]"
CONSOLE_LN_RESET, l_queue_length(test_stats_queue) + 1,
config_name_ptr);
test_stats = l_new(struct test_stats, 1);
test_stats->config_cycle_name = strdup(config_name_ptr);
l_queue_push_tail(test_stats_queue, test_stats);
}
static void set_wiphy_list(struct l_queue *wiphy_list)
{
const struct l_queue_entry *wiphy_entry;
int size = 32;
char *var;
for (wiphy_entry = l_queue_get_entries(wiphy_list);
wiphy_entry; wiphy_entry = wiphy_entry->next) {
struct wiphy *wiphy = wiphy_entry->data;
size += 32 + strlen(wiphy->name);
if (wiphy->used_by_hostapd) {
size += 32 + strlen(wiphy->interface_name) +
strlen(wiphy->hostapd_ctrl_interface) +
strlen(wiphy->hostapd_config);
}
}
var = alloca(size);
size = 0;
for (wiphy_entry = l_queue_get_entries(wiphy_list);
wiphy_entry; wiphy_entry = wiphy_entry->next) {
struct wiphy *wiphy = wiphy_entry->data;
if (size)
var[size++] = '\n';
size += sprintf(var + size, "%s=", wiphy->name);
if (wiphy->used_by_hostapd)
size += sprintf(var + size,
"hostapd,name=%s,ctrl_interface=%s,"
"config=%s",
wiphy->interface_name,
wiphy->hostapd_ctrl_interface,
wiphy->hostapd_config);
else
size += sprintf(var + size, "iwd");
}
var[size++] = '\0';
setenv("TEST_WIPHY_LIST", var, true);
}
static void set_reg_domain(const char *domain)
{
char *argv[5];
argv[0] = "iw";
argv[1] = "reg";
argv[2] = "set";
argv[3] = (char *) domain;
argv[4] = NULL;
execute_program(argv, environ, false, NULL);
}
static void wiphy_up(void *data, void *user_data)
{
struct wiphy *wiphy = data;
set_interface_state(wiphy->interface_name, true);
}
static void wiphy_reset(void *data, void *user_data)
{
struct wiphy *wiphy = data;
wiphy->used_by_hostapd = false;
l_free(wiphy->hostapd_config);
wiphy->hostapd_config = NULL;
l_free(wiphy->hostapd_ctrl_interface);
wiphy->hostapd_ctrl_interface = NULL;
}
static void create_network_and_run_tests(void *data, void *user_data)
{
pid_t hostapd_pids[HWSIM_RADIOS_MAX];
pid_t iwd_pid = -1;
pid_t medium_pid = -1;
pid_t ofono_pid = -1;
pid_t phonesim_pid = -1;
char *config_dir_path;
char *iwd_config_dir;
char **tmpfs_extra_stuff = NULL;
struct l_settings *hw_settings;
struct l_queue *test_queue;
struct l_queue *test_stats_queue;
bool start_iwd_daemon = true;
bool needs_hwsim = false;
bool ofono_req = false;
const char *sim_keys;
const char *iwd_ext_options = NULL;
const char *reg_domain;
int phys_used;
int num_radios;
struct test_entry *entry = data;
char *test_name = NULL;
memset(hostapd_pids, -1, sizeof(hostapd_pids));
config_dir_path = (char *) entry->path;
test_queue = (struct l_queue *) entry->test_queue;
test_stats_queue = (struct l_queue *) user_data;
if (l_queue_isempty(test_queue)) {
l_error("No Python IWD tests have been found in %s",
config_dir_path);
return;
}
set_config_cycle_info(config_dir_path, test_stats_queue);
hw_settings = read_hw_config(config_dir_path);
if (!hw_settings)
return;
l_info("Configuring network...");
if (log) {
char *log_path;
test_name = basename(config_dir_path);
log_path = l_strdup_printf("%s/%s", log_dir, test_name);
mkdir(log_path, 0755);
if (chown(log_path, log_uid, log_gid) < 0)
l_error("chown failed");
l_free(log_path);
}
if (chdir(config_dir_path) < 0) {
l_error("Failed to change to test directory: %s",
strerror(errno));
goto free_hw_settings;
}
tmpfs_extra_stuff =
l_settings_get_string_list(hw_settings, HW_CONFIG_GROUP_SETUP,
HW_CONFIG_SETUP_TMPFS_EXTRAS,
':');
sim_keys = l_settings_get_value(hw_settings, HW_CONFIG_GROUP_SETUP,
"sim_keys");
if (sim_keys) {
if (!strcmp(sim_keys, "ofono")) {
bool ofono_found = false;
bool phonesim_found = false;
if (!system("which ofonod > /dev/null 2>&1"))
ofono_found = true;
if (!system("which phonesim > /dev/null 2>&1"))
phonesim_found = true;
if (!ofono_found || !phonesim_found) {
l_info("ofono or phonesim not found, skipping");
goto free_tmpfs_extra;
}
ofono_req = true;
iwd_ext_options = "--plugin=ofono";
} else {
setenv("IWD_SIM_KEYS", sim_keys, true);
iwd_ext_options = "--plugin=sim_hardcoded";
}
}
/* turn on/off timeouts if GDB is being used */
if (!strcmp(gdb_opt, "none"))
setenv("IWD_TEST_TIMEOUTS", "on", true);
else
setenv("IWD_TEST_TIMEOUTS", "off", true);
if (!create_tmpfs_extra_stuff(tmpfs_extra_stuff))
goto remove_abs_paths;
l_settings_get_int(hw_settings, HW_CONFIG_GROUP_SETUP,
HW_CONFIG_SETUP_NUM_RADIOS,
&num_radios);
if (!native_hw) {
reg_domain = l_settings_get_value(hw_settings,
HW_CONFIG_GROUP_SETUP,
HW_CONFIG_SETUP_REG_DOMAIN);
if (reg_domain)
set_reg_domain(reg_domain);
wiphy_list = l_queue_new();
if (!configure_hw_radios(hw_settings, wiphy_list))
goto remove_abs_paths;
medium_pid = register_hwsim_as_trans_medium();
if (medium_pid < 0)
goto remove_abs_paths;
if (check_verbosity("hwsim")) {
list_hwsim_radios();
list_interfaces();
}
} else {
int len;
l_settings_get_bool(hw_settings, HW_CONFIG_GROUP_SETUP,
HW_CONFIG_SETUP_NEEDS_HWSIM, &needs_hwsim);
/* Skip test that require hwsim dbus APIs (hwsim not running) */
if (needs_hwsim) {
l_error("test requires hwsim, skipping");
goto remove_abs_paths;
}
len = l_queue_length(wiphy_list);
/* Skip tests that need more radios than we have */
if (num_radios > len) {
l_error("test requires %d radios, only %d found",
num_radios, len);
goto remove_abs_paths;
}
l_queue_foreach(wiphy_list, wiphy_up, NULL);
}
if (check_verbosity("tls"))
setenv("IWD_TLS_DEBUG", "on", true);
if (!configure_hostapd_instances(hw_settings, config_dir_path,
wiphy_list, hostapd_pids,
&phys_used))
goto exit_hostapd;
l_settings_get_bool(hw_settings, HW_CONFIG_GROUP_SETUP,
HW_CONFIG_SETUP_START_IWD, &start_iwd_daemon);
if (start_iwd_daemon) {
/*
* In native mode we may have more radios than a test actually
* needs. This would result in IWD managing all phys that
* hostapd wasn't using, which could throw off test results.
* By passing the number of phys the test expects IWD to have
* we can leave the remaining (unneeded) phys unmanaged.
*/
int iwd_phys = num_radios - phys_used;
iwd_config_dir =
l_settings_get_string(hw_settings,
HW_CONFIG_GROUP_SETUP,
HW_CONFIG_SETUP_IWD_CONF_DIR);
if (!iwd_config_dir)
iwd_config_dir = "/tmp";
iwd_pid = start_iwd(iwd_config_dir, wiphy_list,
iwd_ext_options, iwd_phys, test_name);
if (iwd_pid == -1)
goto exit_hostapd;
} else {
/* tells pytest to start iwd with valgrind */
if (valgrind)
setenv("IWD_TEST_VALGRIND", "on", true);
}
if (ofono_req) {
phonesim_pid = start_phonesim(test_name);
ofono_pid = start_ofono(test_name);
}
set_wiphy_list(wiphy_list);
if (!shell)
run_py_tests(hw_settings, test_queue, test_stats_queue,
test_name);
else {
if (system("/bin/sh"))
l_info("executing /bin/sh failed");
}
l_info("Destructing network...");
/* Script has responsibility to cleanup any iwd instances it started */
if (iwd_pid > 0)
terminate_iwd(iwd_pid);
/* /tmp/valgrind.log will only exist without logging turned on */
if (valgrind && !log) {
if (system("cat /tmp/valgrind.log"))
l_info("cat /tmp/valgrind.log failed");
if (system("echo \"\" > /tmp/valgrind.log"))
l_info("Failed to reset /tmp/valgrind.log");
}
if (log) {
L_AUTO_FREE_VAR(char *, dmesg);
L_AUTO_FREE_VAR(char *, kernel_log);
kernel_log = l_strdup_printf("%s/kernel.log", log_dir);
dmesg = l_strdup_printf("dmesg > %s", kernel_log);
if (system(dmesg))
l_error("dmesg failed");
if (chown(kernel_log, log_uid, log_gid))
l_error("chown failed");
}
if (ofono_req) {
loopback_started = false;
stop_ofono(ofono_pid);
stop_phonesim(phonesim_pid);
}
exit_hostapd:
destroy_hostapd_instances(hostapd_pids);
if (!native_hw)
terminate_medium(medium_pid);
remove_abs_paths:
remove_absolute_path_dirs(tmpfs_extra_stuff);
/*
* If running in hwsim mode, we want to completely free/destroy the
* wiphy list since it will be re-populated on the next test. For the
* native case we want to reset the list as if it was freshly
* discovered. This ensures that all the hostapd flags get reset.
*/
if (!native_hw)
l_queue_destroy(wiphy_list, wiphy_free);
else
l_queue_foreach(wiphy_list, wiphy_reset, NULL);
free_tmpfs_extra:
l_strfreev(tmpfs_extra_stuff);
free_hw_settings:
l_settings_free(hw_settings);
}
struct stat_totals {
unsigned int total_passed;
unsigned int total_failed;
unsigned int total_timedout;
double total_duration;
};
static void print_test_stat(void *data, void *user_data)
{
struct test_stats *test_stats;
struct stat_totals *stat_totals;
char *str_runtime, *str_passed, *str_failed, *str_timedout;
test_stats = (struct test_stats *) data;
stat_totals = (struct stat_totals *) user_data;
stat_totals->total_duration += test_stats->py_run_time;
stat_totals->total_passed += test_stats->num_passed;
stat_totals->total_failed += test_stats->num_failed;
stat_totals->total_timedout += test_stats->num_timedout;
if (test_stats->py_run_time)
str_runtime = l_strdup_printf("| %9.3f sec",
test_stats->py_run_time);
else
str_runtime = l_strdup_printf("| %9s", "Skipped");
if (test_stats->num_passed)
str_passed = l_strdup_printf(" %6d ", test_stats->num_passed);
else
str_passed = l_strdup_printf(" %6c ", '-');
if (test_stats->num_failed)
str_failed = l_strdup_printf(" %6d ", test_stats->num_failed);
else
str_failed = l_strdup_printf(" %6c ", '-');
if (test_stats->num_timedout)
str_timedout = l_strdup_printf(" %9d ",
test_stats->num_timedout);
else
str_timedout = l_strdup_printf(" %9c ", '-');
l_info(CONSOLE_LN_BOLD "%27s "
CONSOLE_LN_DEFAULT "|" CONSOLE_LN_GREEN "%s"
CONSOLE_LN_DEFAULT "|" CONSOLE_LN_RED "%s"
CONSOLE_LN_DEFAULT "|" CONSOLE_LN_YELLOW "%s"
CONSOLE_LN_RESET "%s", test_stats->config_cycle_name,
str_passed, str_failed, str_timedout, str_runtime);
l_free(str_passed);
l_free(str_failed);
l_free(str_timedout);
l_free(str_runtime);
}
static void print_results(struct l_queue *test_stat_queue)
{
struct stat_totals stat_totals = { 0, 0, 0, 0 };
char sep_line[80];
memset(sep_line, '_', sizeof(sep_line) - 1);
sep_line[sizeof(sep_line) - 1] = '\0';
l_info("%s\n" CONSOLE_LN_RESET, sep_line);
l_info("%27s " CONSOLE_LN_DEFAULT "|" CONSOLE_LN_GREEN " %s "
CONSOLE_LN_DEFAULT "|" CONSOLE_LN_RED " %5s "
CONSOLE_LN_DEFAULT "|" CONSOLE_LN_YELLOW " %9s "
CONSOLE_LN_RESET "| Duration",
"Configuration cycle", "PASSED", "FAILED", "TIMED OUT");
memset(sep_line, '-', sizeof(sep_line) - 1);
sep_line[sizeof(sep_line) - 1] = '\0';
l_info("%s" CONSOLE_LN_RESET, sep_line);
l_queue_foreach(test_stat_queue, print_test_stat, &stat_totals);
l_info("%s" CONSOLE_LN_RESET, sep_line);
l_info("%27s "
CONSOLE_LN_DEFAULT "|" CONSOLE_LN_GREEN " %6d "
CONSOLE_LN_DEFAULT "|" CONSOLE_LN_RED " %6d "
CONSOLE_LN_DEFAULT "|" CONSOLE_LN_YELLOW " %9d "
CONSOLE_LN_RESET "| %9.3f sec",
"Total", stat_totals.total_passed, stat_totals.total_failed,
stat_totals.total_timedout, stat_totals.total_duration);
memset(sep_line, '_', sizeof(sep_line) - 1);
sep_line[sizeof(sep_line) - 1] = '\0';
l_info("%s" CONSOLE_LN_RESET, sep_line);
}
static void test_stat_queue_entry_destroy(void *data)
{
struct test_stats *ts;
ts = (struct test_stats *) data;
l_free(ts->config_cycle_name);
l_free(ts);
}
static void free_test_entry(void *data)
{
struct test_entry *entry = data;
l_free(entry->path);
l_free(entry);
}
static void run_auto_tests(void)
{
L_AUTO_FREE_VAR(char*, test_home_path) = NULL;
L_AUTO_FREE_VAR(char*, env_path) = NULL;
int i;
struct l_queue *test_config_queue;
struct l_queue *test_stat_queue;
char **test_config_dirs;
if (log) {
if (mount("logdir", log_dir, "9p", 0,
"trans=virtio,version=9p2000.L") < 0) {
l_error("Mounting %s failed", log_dir);
return;
}
}
env_path = l_strdup_printf("%s/src:%s/tools:%s", top_level_path,
top_level_path, getenv("PATH"));
setenv("PATH", env_path, true);
test_home_path = l_strdup_printf("%s/%s", top_level_path,
TEST_TOP_DIR_DEFAULT_NAME);
if (!path_exist(test_home_path)) {
l_error("Test directory %s does not exist", test_home_path);
return;
}
test_config_queue = l_queue_new();
if (!test_config_queue)
return;
test_config_dirs = l_strsplit(test_action_params, ',');
if (test_config_dirs[0]) {
i = 0;
while (test_config_dirs[i]) {
if (strchr(test_config_dirs[i], '/')) {
if (!find_test_configuration(
test_config_dirs[i], 1,
test_config_queue))
goto exit;
} else {
char *config_dir_path;
config_dir_path =
l_strdup_printf("%s/%s", test_home_path,
test_config_dirs[i]);
if (!find_test_configuration(config_dir_path, 1,
test_config_queue)) {
l_free(config_dir_path);
goto exit;
}
l_free(config_dir_path);
}
i++;
}
} else {
/*
* --shell without any specific tests implies 'shell' test
*/
if (shell) {
char *config_dir_path;
config_dir_path = l_strdup_printf("%s/shell",
test_home_path);
if (!find_test_configuration(config_dir_path, 1,
test_config_queue)) {
l_free(config_dir_path);
goto exit;
}
l_free(config_dir_path);
} else {
l_info("Automatic test execution requested");
l_info("Searching for the test configurations...");
if (!find_test_configuration(test_home_path, 0,
test_config_queue))
goto exit;
}
}
if (l_queue_isempty(test_config_queue)) {
l_error("No test configuration discovered");
goto exit;
}
create_dbus_system_conf();
if (!start_dbus_daemon())
goto exit;
if (!start_haveged()) {
l_error("Failed to start haveged");
goto exit;
}
test_stat_queue = l_queue_new();
l_queue_foreach(test_config_queue, create_network_and_run_tests,
test_stat_queue);
print_results(test_stat_queue);
l_queue_destroy(test_stat_queue, test_stat_queue_entry_destroy);
exit:
l_strfreev(verbose_apps);
l_strfreev(test_config_dirs);
l_queue_destroy(test_config_queue, free_test_entry);
}
static void run_unit_tests(void)
{
DIR *d;
struct dirent *dirent;
char *argv[2];
char *unit_test_abs_path = NULL;
char **unit_tests = NULL;
if (strcmp(test_action_params, "")) {
unit_tests = l_strsplit(test_action_params, ',');
if (!unit_tests || !unit_tests[0])
goto exit;
}
if (chdir(top_level_path) < 0)
goto exit;
d = opendir("unit/");
if (!d)
goto exit;
while ((dirent = readdir(d)) != NULL) {
struct stat st;
if (dirent->d_type != DT_REG)
continue;
unit_test_abs_path = l_strdup_printf("%s%s%s", top_level_path,
"/unit/", dirent->d_name);
if (stat(unit_test_abs_path, &st) < 0)
goto next;
if (!(st.st_mode & S_IEXEC))
goto next;
if (unit_tests) {
if (!l_strv_contains(unit_tests, dirent->d_name))
goto next;
}
argv[0] = unit_test_abs_path;
argv[1] = NULL;
l_info("\n---------- Unit %s ----------", dirent->d_name);
execute_program(argv, environ, true, NULL);
next:
l_free(unit_test_abs_path);
}
closedir(d);
exit:
l_strfreev(unit_tests);
}
static bool wiphy_match(const void *a, const void *b)
{
const struct wiphy *wiphy = a;
int id = L_PTR_TO_INT(b);
return (wiphy->id == id);
}
static struct wiphy *wiphy_find(int wiphy_id)
{
return l_queue_find(wiphy_list, wiphy_match, L_INT_TO_PTR(wiphy_id));
}
static void parse_supported_iftypes(uint16_t *iftypes,
struct l_genl_attr *attr)
{
uint16_t type, len;
const void *data;
while (l_genl_attr_next(attr, &type, &len, &data)) {
/*
* NL80211_IFTYPE_UNSPECIFIED can be ignored, so we start
* at the first bit
*/
if (type > sizeof(uint16_t) * 8) {
l_warn("unsupported iftype: %u", type);
continue;
}
*iftypes |= 1 << (type - 1);
}
}
static void wiphy_dump_callback(struct l_genl_msg *msg, void *user_data)
{
struct wiphy *wiphy;
struct l_genl_attr attr;
struct l_genl_attr nested;
uint32_t id = UINT32_MAX;
uint16_t type, len;
const void *data;
const char *name = NULL;
uint32_t name_len = 0;
uint16_t iftypes = 0;
if (!l_genl_attr_init(&attr, msg))
return;
while (l_genl_attr_next(&attr, &type, &len, &data)) {
switch (type) {
case NL80211_ATTR_WIPHY:
if (len != sizeof(uint32_t))
return;
id = *((uint32_t *) data);
if (wiphy_find(id))
return;
break;
case NL80211_ATTR_WIPHY_NAME:
if (len > sizeof(((struct wiphy *) 0)->name))
return;
name = data;
name_len = len;
break;
case NL80211_ATTR_SUPPORTED_IFTYPES:
if (l_genl_attr_recurse(&attr, &nested))
parse_supported_iftypes(&iftypes, &nested);
break;
}
}
if (id == UINT32_MAX || !name)
return;
wiphy = l_new(struct wiphy, 1);
strncpy(wiphy->name, name, name_len);
wiphy->id = id;
wiphy->can_ap = iftypes & (1 << NL80211_IFTYPE_AP);
l_queue_push_tail(wiphy_list, wiphy);
}
static void iface_dump_callback(struct l_genl_msg *msg, void *user_data)
{
struct l_genl_attr attr;
uint16_t type, len;
const void *data;
const char *ifname = NULL;
struct wiphy *wiphy = NULL;
if (!l_genl_attr_init(&attr, msg))
return;
while (l_genl_attr_next(&attr, &type, &len, &data)) {
switch (type) {
case NL80211_ATTR_IFNAME:
if (len > 16) {
l_warn("Invalid interface name attribute");
return;
}
ifname = data;
break;
case NL80211_ATTR_WIPHY:
if (len != sizeof(uint32_t)) {
l_warn("Invalid wiphy attribute");
return;
}
wiphy = wiphy_find(*((uint32_t *) data));
break;
}
}
if (!ifname || !wiphy)
return;
wiphy->interface_name = l_strdup(ifname);
wiphy->interface_created = false;
l_info("Discovered interface %s", wiphy->interface_name);
}
struct nl_data {
struct l_genl *genl;
struct l_genl_family *nl80211;
};
static void iface_dump_done(void *user_data)
{
struct nl_data *data = user_data;
l_debug("Interface discovery complete, running tests");
list_interfaces();
run_auto_tests();
l_queue_destroy(wiphy_list, wiphy_free);
l_genl_family_free(data->nl80211);
l_genl_unref(data->genl);
l_free(data);
l_main_quit();
}
static void wiphy_dump_done(void *user_data)
{
struct nl_data *data = user_data;
struct l_genl_msg *msg;
l_debug("Wiphy discovery complete, discovering interfaces");
msg = l_genl_msg_new(NL80211_CMD_GET_INTERFACE);
if (!l_genl_family_dump(data->nl80211, msg, iface_dump_callback,
data, iface_dump_done))
l_error("Getting all interface information failed");
}
static void nl80211_requested(const struct l_genl_family_info *info,
void *user_data)
{
struct nl_data *data = user_data;
struct l_genl_msg *msg;
if (info == NULL) {
l_info("No nl80211 family found");
goto done;
}
l_debug("Found nl80211 interface");
data->nl80211 = l_genl_family_new(data->genl, NL80211_GENL_NAME);
wiphy_list = l_queue_new();
msg = l_genl_msg_new(NL80211_CMD_GET_WIPHY);
if (!l_genl_family_dump(data->nl80211, msg, wiphy_dump_callback,
data, wiphy_dump_done))
l_error("Getting all wiphy devices failed");
return;
done:
l_main_quit();
}
static void start_hw_discovery(void)
{
struct nl_data *data = l_new(struct nl_data, 1);
data->genl = l_genl_new();
l_genl_request_family(data->genl, NL80211_GENL_NAME,
nl80211_requested, data, NULL);
/*
* This is somewhat of a mystery, but it appears that
* calling lshw causes the OS to re-enumerate the USB
* bus. Without this no USB adapters are found when
* doing the wiphy/iface dump from nl80211.
*
* This also conveniently prints all the network
* adapters and their iface name, so its much easier
* to know which adapter are being used by iwd/hostapd
* after the test.
*/
if (system("lshw -C network"))
l_info("lshw failed");
l_main_run();
}
static void run_tests(void)
{
char cmdline[CMDLINE_MAX], *ptr, *cmds;
char *test_action_str;
FILE *fp;
int i;
fp = fopen("/proc/cmdline", "re");
if (!fp) {
l_error("Failed to open kernel command line");
return;
}
ptr = fgets(cmdline, sizeof(cmdline), fp);
fclose(fp);
if (!ptr) {
l_error("Failed to read kernel command line");
return;
}
ptr = strstr(cmdline, "LOG_GID=");
if (ptr) {
*ptr = '\0';
test_action_str = ptr + 9;
ptr = strchr(test_action_str, '\'');
*ptr = '\0';
log_gid = atoi(test_action_str);
}
ptr = strstr(cmdline, "LOG_UID=");
if (ptr) {
*ptr = '\0';
test_action_str = ptr + 9;
ptr = strchr(test_action_str, '\'');
*ptr = '\0';
log_uid = atoi(test_action_str);
}
ptr = strstr(cmdline, "LOG_PATH=");
if (ptr) {
*ptr = '\0';
test_action_str = ptr + 10;
ptr = strchr(test_action_str, '\'');
*ptr = '\0';
if (strcmp(test_action_str, "none")) {
log = true;
strcpy(log_dir, test_action_str);
}
}
ptr = strstr(cmdline, "SHELL=");
if (ptr) {
*ptr = '\0';
test_action_str = ptr + 6;
shell = atoi(test_action_str);
}
ptr = strstr(cmdline, "HW=");
if (ptr) {
*ptr = '\0';
test_action_str = ptr + 4;
ptr = strchr(test_action_str, '\'');
if (ptr)
*ptr = '\0';
if (!strcmp(test_action_str, "virtual"))
native_hw = false;
else
native_hw = true;
}
ptr = strstr(cmdline, "GDB=");
if (ptr) {
*ptr = '\0';
test_action_str = ptr + 5;
ptr = strchr(test_action_str, '\'');
*ptr = '\0';
gdb_opt = l_strdup(test_action_str);
}
ptr = strstr(cmdline, "VALGRIND=");
if (ptr) {
char *end;
unsigned long v;
*ptr = '\0';
test_action_str = ptr + 9;
v = strtoul(test_action_str, &end, 10);
if ((v != 0 && v != 1) || end != test_action_str + 1) {
l_error("malformed valgrind option");
return;
}
valgrind = (bool) v;
}
ptr = strstr(cmdline, "PATH=");
if (!ptr) {
l_error("No $PATH section found");
return;
}
*ptr = '\0';
test_action_str = ptr + 6;
ptr = strchr(test_action_str, '\'');
*ptr = '\0';
l_info("%s", test_action_str);
setenv("PATH", test_action_str, true);
ptr = strstr(cmdline, "TESTARGS=");
if (!ptr) {
l_error("No test command section found");
return;
}
cmds = ptr + 10;
ptr = strchr(cmds, '\'');
if (!ptr) {
l_error("Malformed test command section");
return;
}
*ptr = '\0';
ptr = strstr(cmdline, "TEST_ACTION_PARAMS=");
if (ptr) {
test_action_params = ptr + 20;
ptr = strchr(test_action_params, '\'');
if (!ptr) {
l_error("Malformed test action parameters section");
return;
}
*ptr = '\0';
}
ptr = strstr(cmdline, "TEST_ACTION=");
if (ptr) {
test_action_str = ptr + 12;
ptr = strchr(test_action_str, ' ');
if (!ptr) {
l_error("Malformed test action parameters section");
return;
}
*ptr = '\0';
test_action = (enum action) atoi(test_action_str);
}
ptr = strstr(cmdline, "DEBUG_FILTER=");
if (ptr) {
debug_filter = ptr + 14;
ptr = strchr(debug_filter, '\'');
if (!ptr) {
l_error("Malformed debug filter section");
return;
}
*ptr = '\0';
if (debug_filter[0] != '\0') {
enable_debug = true;
l_debug_enable(debug_filter);
setenv("HWSIM_DEBUG", "", true);
}
}
ptr = strstr(cmdline, "TESTVERBOUT=");
if (ptr) {
verbose_opt = ptr + 13;
ptr = strchr(verbose_opt, '\'');
if (!ptr) {
l_error("Malformed verbose parameter");
return;
}
*ptr = '\0';
l_info("Enable verbose output for %s", verbose_opt);
verbose_apps = l_strsplit(verbose_opt, ',');
}
ptr = strstr(cmdline, "TESTHOME=");
if (ptr) {
exec_home = ptr + 4;
ptr = strpbrk(exec_home + 9, " \r\n");
if (ptr)
*ptr = '\0';
}
ptr = strrchr(exec_home, '/');
if (!ptr)
exit(EXIT_FAILURE);
i = ptr - exec_home;
strncpy(top_level_path, exec_home + 5, i - 5);
top_level_path[i - 5] = '\0';
switch (test_action) {
case ACTION_AUTO_TEST:
if (native_hw)
start_hw_discovery();
else
run_auto_tests();
break;
case ACTION_UNIT_TEST:
run_unit_tests();
break;
}
}
static void usage(void)
{
l_info("test-runner - Automated test execution utility\n"
"Usage:\n");
l_info("\ttest-runner [options] [--] <command> [args]\n");
l_info("Options:\n"
"\t-q, --qemu <path> QEMU binary\n"
"\t-k, --kernel <image> Kernel image (bzImage)\n"
"\t-v, --verbose <apps> Comma separated list of "
"applications to enable\n"
"\t\t\t\tverbose output\n"
"\t-h, --help Show help options\n"
"\t-V, --valgrind Run valgrind on iwd. Note: \"-v"
" iwd\" is required\n"
"\t\t\t\tto see valgrind"
" output\n"
"\t-g, --gdb <iwd|hostapd> Run gdb on the specified"
" executable\n"
"\t-w, --hw <config> Run using a physical hardware "
"configuration\n"
"\t-s, --shell Boot into shell. If -A is used the"
" environment\n"
"\t\t\t\twill be setup exactly as it is"
" in the test,\n"
"\t\t\t\tbut no test will be run. If no"
" test is specified\n"
"\t\t\t\tthe 'shell' test"
" will be used\n"
"\t-l, --log <dir> Directory used for log output. "
"This option sets \n"
"\t\t\t\t--verbose on all apps");
l_info("Commands:\n"
"\t-A, --auto-tests <dirs> Comma separated list of the "
"test configuration\n\t\t\t\t"
"directories to run\n"
"\t-U, --unit-tests <tests> Comma separated list of the "
"unit tests to run\n");
}
static const struct option main_options[] = {
{ "auto-tests", required_argument, NULL, 'A' },
{ "unit-tests", optional_argument, NULL, 'U' },
{ "qemu", required_argument, NULL, 'q' },
{ "kernel", required_argument, NULL, 'k' },
{ "verbose", required_argument, NULL, 'v' },
{ "debug", optional_argument, NULL, 'd' },
{ "gdb", required_argument, NULL, 'g' },
{ "valgrind", no_argument, NULL, 'V' },
{ "hw", required_argument, NULL, 'w' },
{ "shell", optional_argument, NULL, 's' },
{ "log", required_argument, NULL, 'l' },
{ "help", no_argument, NULL, 'h' },
{ }
};
int main(int argc, char *argv[])
{
uint8_t actions = 0;
struct tm *timeinfo;
time_t t;
char *gid;
char *uid;
l_log_set_stderr();
if (getpid() == 1 && getppid() == 0) {
if (!l_main_init())
return EXIT_FAILURE;
prepare_sandbox();
run_tests();
sync();
l_info("Done running tests. Rebooting...");
reboot(RB_AUTOBOOT);
return EXIT_SUCCESS;
}
for (;;) {
int opt;
opt = getopt_long(argc, argv, "A:q:k:v:g:sl:UVdh", main_options,
NULL);
if (opt < 0)
break;
switch (opt) {
case 'A':
test_action = ACTION_AUTO_TEST;
test_action_params = optarg;
actions++;
break;
case 'U':
test_action = ACTION_UNIT_TEST;
test_action_params = optarg;
actions++;
break;
case 'q':
qemu_binary = optarg;
break;
case 'k':
kernel_image = optarg;
break;
case 'd':
enable_debug = true;
if (optarg)
debug_filter = optarg;
else
debug_filter = "*";
l_debug_enable(debug_filter);
break;
case 'v':
verbose_opt = optarg;
verbose_apps = l_strsplit(optarg, ',');
break;
case 'V':
valgrind = true;
break;
case 'g':
gdb_opt = optarg;
if (!gdb_opt || (strcmp(gdb_opt, "iwd") &&
strcmp(gdb_opt, "hostapd") &&
strcmp(gdb_opt, "hwsim"))) {
l_error("--gdb can only be used with iwd"
", hwsim or hostapd");
return EXIT_FAILURE;
}
break;
case 'w':
hw_config = l_settings_new();
if (!l_settings_load_from_file(hw_config, optarg)) {
l_error("could not read hw config from %s",
optarg);
l_settings_free(hw_config);
return EXIT_FAILURE;
}
break;
case 's':
shell = true;
break;
case 'l':
/*
* Setup the log directory. This is created under the
* passed in log dir (--log) in the format:
* <logdir>/run-<year>-<month>-<day>-<PID>
*
* The created log dir is then chown'ed to the user
* who started test-runner, as are all files created
* under this directory.
*/
log = true;
if (!optarg)
optarg = ".";
time(&t);
timeinfo = localtime(&t);
gid = getenv("SUDO_GID");
uid = getenv("SUDO_UID");
if (!gid || !uid) {
log_gid = getgid();
log_uid = getuid();
} else {
log_gid = strtol(gid, NULL, 10);
log_uid = strtol(uid, NULL, 10);
}
snprintf(log_dir, sizeof(log_dir), "%s/run-%d-%d-%d-%d",
optarg, timeinfo->tm_year + 1900,
timeinfo->tm_mon + 1, timeinfo->tm_mday,
getpid());
mkdir(log_dir, 0755);
if (chown(log_dir, log_uid, log_gid) < 0)
l_error("failed to fchown %s", log_dir);
break;
case 'h':
usage();
return EXIT_SUCCESS;
default:
return EXIT_FAILURE;
}
}
if (argc - optind > 0) {
l_error("Invalid command line parameters");
return EXIT_FAILURE;
}
if (actions > 1) {
l_error("Only one action can be specified");
return EXIT_FAILURE;
}
if (!actions)
test_action = ACTION_AUTO_TEST;
own_binary = argv[0];
test_argv = argv + optind;
test_argc = argc - optind;
if (!qemu_binary) {
qemu_binary = find_qemu();
if (!qemu_binary) {
l_error("No default QEMU binary found");
return EXIT_FAILURE;
}
}
if (!kernel_image) {
kernel_image = find_kernel();
if (!kernel_image) {
l_error("No default kernel image found");
return EXIT_FAILURE;
}
}
l_info("Using QEMU binary %s", qemu_binary);
l_info("Using kernel image %s", kernel_image);
if (!start_qemu())
return EXIT_FAILURE;
return EXIT_SUCCESS;
}