| /* |
| * |
| * 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, |
| ¶ms.channels)) |
| params.channels = 1; |
| |
| if (!l_settings_get_bool(hw_settings, wiphy->name, |
| HW_CONFIG_PHY_P2P, ¶ms.p2p_device)) |
| params.p2p_device = true; |
| |
| if (!l_settings_get_bool(hw_settings, wiphy->name, |
| HW_CONFIG_PHY_CHANCTX, |
| ¶ms.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, ¶ms); |
| 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; |
| } |