| /**************************************************************************** |
| * 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; |
| } |