blob: cd63d1d470bbbd167d442c99893f2233bcd0cde2 [file] [log] [blame]
/****************************************************************************
* Common test functions for ethtool
* Copyright 2011 Solarflare Communications Inc.
*
* Partly derived from kernel <linux/list.h>.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published
* by the Free Software Foundation, incorporated herein by reference.
*/
#include <assert.h>
#include <errno.h>
#include <setjmp.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <sys/fcntl.h>
#include <unistd.h>
#define TEST_NO_WRAPPERS
#include "internal.h"
/* List utilities */
struct list_head {
struct list_head *next, *prev;
};
#define LIST_HEAD_INIT(name) { &(name), &(name) }
static void init_list_head(struct list_head *list)
{
list->next = list;
list->prev = list;
}
static void list_add(struct list_head *new, struct list_head *head)
{
head->next->prev = new;
new->next = head->next;
new->prev = head;
head->next = new;
}
static void list_del(struct list_head *entry)
{
entry->next->prev = entry->prev;
entry->prev->next = entry->next;
entry->next = NULL;
entry->prev = NULL;
}
#define list_for_each_safe(pos, n, head) \
for (pos = (head)->next, n = pos->next; pos != (head); \
pos = n, n = pos->next)
/* Free memory at end of test */
static struct list_head malloc_list = LIST_HEAD_INIT(malloc_list);
void *test_malloc(size_t size)
{
struct list_head *block = malloc(sizeof(*block) + size);
if (!block)
return NULL;
list_add(block, &malloc_list);
return block + 1;
}
void *test_calloc(size_t nmemb, size_t size)
{
void *ptr = test_malloc(nmemb * size);
if (ptr)
memset(ptr, 0, nmemb * size);
return ptr;
}
char *test_strdup(const char *s)
{
size_t size = strlen(s) + 1;
char *dup = test_malloc(size);
if (dup)
memcpy(dup, s, size);
return dup;
}
void test_free(void *ptr)
{
struct list_head *block;
if (!ptr)
return;
block = (struct list_head *)ptr - 1;
list_del(block);
free(block);
}
void *test_realloc(void *ptr, size_t size)
{
struct list_head *block = NULL;
if (ptr) {
block = (struct list_head *)ptr - 1;
list_del(block);
}
block = realloc(block, sizeof(*block) + size);
if (!block)
return NULL;
list_add(block, &malloc_list);
return block + 1;
}
static void test_free_all(void)
{
struct list_head *block, *next;
list_for_each_safe(block, next, &malloc_list)
free(block);
init_list_head(&malloc_list);
}
/* Close files at end of test */
struct file_node {
struct list_head link;
FILE *fh;
int fd;
};
static struct list_head file_list = LIST_HEAD_INIT(file_list);
int test_open(const char *pathname, int flag, ...)
{
struct file_node *node;
mode_t mode;
if (flag & O_CREAT) {
va_list ap;
va_start(ap, flag);
mode = va_arg(ap, mode_t);
va_end(ap);
} else {
mode = 0;
}
node = malloc(sizeof(*node));
if (!node)
return -1;
node->fd = open(pathname, flag, mode);
if (node->fd < 0) {
free(node);
return -1;
}
node->fh = NULL;
list_add(&node->link, &file_list);
return node->fd;
}
int test_socket(int domain, int type, int protocol)
{
struct file_node *node;
node = malloc(sizeof(*node));
if (!node)
return -1;
node->fd = socket(domain, type, protocol);
if (node->fd < 0) {
free(node);
return -1;
}
node->fh = NULL;
list_add(&node->link, &file_list);
return node->fd;
}
int test_close(int fd)
{
struct list_head *head, *next;
if (fd >= 0) {
list_for_each_safe(head, next, &file_list) {
if (((struct file_node *)head)->fd == fd) {
list_del(head);
free(head);
break;
}
}
}
return close(fd);
}
FILE *test_fopen(const char *path, const char *mode)
{
struct file_node *node;
node = malloc(sizeof(*node));
if (!node)
return NULL;
node->fh = fopen(path, mode);
if (!node->fh) {
free(node);
return NULL;
}
node->fd = -1;
list_add(&node->link, &file_list);
return node->fh;
}
int test_fclose(FILE *fh)
{
struct list_head *head, *next;
assert(fh);
list_for_each_safe(head, next, &file_list) {
if (((struct file_node *)head)->fh == fh) {
list_del(head);
free(head);
break;
}
}
return fclose(fh);
}
static void test_close_all(void)
{
struct list_head *head, *next;
struct file_node *node;
list_for_each_safe(head, next, &file_list) {
node = (struct file_node *)head;
if (node->fh)
fclose(node->fh);
else
close(node->fd);
free(node);
}
init_list_head(&file_list);
}
/* Wrap test main function */
static jmp_buf test_return;
static FILE *orig_stderr;
void test_exit(int rc)
{
longjmp(test_return, rc + 1);
}
int test_ioctl(const struct cmd_expect *expect, void *cmd)
{
int rc;
if (!expect->cmd || *(u32 *)cmd != *(const u32 *)expect->cmd) {
/* We have no idea how long this command structure is */
fprintf(orig_stderr, "Unexpected ioctl: cmd=%#10x\n",
*(u32 *)cmd);
return TEST_IOCTL_MISMATCH;
}
if (memcmp(cmd, expect->cmd, expect->cmd_len)) {
fprintf(orig_stderr, "Expected ioctl structure:\n");
dump_hex(orig_stderr, expect->cmd, expect->cmd_len, 0);
fprintf(orig_stderr, "Actual ioctl structure:\n");
dump_hex(orig_stderr, cmd, expect->cmd_len, 0);
return TEST_IOCTL_MISMATCH;
}
if (expect->resp)
memcpy(cmd, expect->resp, expect->resp_len);
rc = expect->rc;
/* Convert kernel return code according to libc convention */
if (rc >= 0) {
return rc;
} else {
errno = -rc;
return -1;
}
}
int test_cmdline(const char *args)
{
int argc, i;
char **argv;
const char *arg;
size_t len;
int dev_null = -1, orig_stdout_fd = -1, orig_stderr_fd = -1;
int rc;
/* Convert line to argv */
argc = 1;
arg = args;
for (;;) {
len = strcspn(arg, " ");
if (len == 0)
break;
argc++;
if (arg[len] == 0)
break;
arg += len + 1;
}
argv = test_calloc(argc + 1, sizeof(argv[0]));
argv[0] = test_strdup("ethtool");
arg = args;
for (i = 1; i < argc; i++) {
len = strcspn(arg, " ");
argv[i] = test_malloc(len + 1);
memcpy(argv[i], arg, len);
argv[i][len] = 0;
arg += len + 1;
}
dev_null = open("/dev/null", O_RDWR);
if (dev_null < 0) {
perror("open /dev/null");
rc = -1;
goto out;
}
fflush(NULL);
dup2(dev_null, STDIN_FILENO);
if (getenv("TEST_TEST_VERBOSE")) {
orig_stderr = stderr;
} else {
orig_stdout_fd = dup(STDOUT_FILENO);
if (orig_stdout_fd < 0) {
perror("dup stdout");
rc = -1;
goto out;
}
dup2(dev_null, STDOUT_FILENO);
orig_stderr_fd = dup(STDERR_FILENO);
if (orig_stderr_fd < 0) {
perror("dup stderr");
rc = -1;
goto out;
}
orig_stderr = fdopen(orig_stderr_fd, "w");
if (orig_stderr == NULL) {
perror("fdopen orig_stderr_fd");
rc = -1;
goto out;
}
dup2(dev_null, STDERR_FILENO);
}
rc = setjmp(test_return);
rc = rc ? rc - 1 : test_main(argc, argv);
out:
fflush(NULL);
if (orig_stderr_fd >= 0) {
dup2(orig_stderr_fd, STDERR_FILENO);
if (orig_stderr)
fclose(orig_stderr);
else
close(orig_stderr_fd);
}
orig_stderr = NULL;
if (orig_stdout_fd >= 0) {
dup2(orig_stdout_fd, STDOUT_FILENO);
close(orig_stdout_fd);
}
if (dev_null >= 0)
close(dev_null);
test_free_all();
test_close_all();
return rc;
}