| /* |
| * libqos driver framework |
| * |
| * Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com> |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License version 2.1 as published by the Free Software Foundation. |
| * |
| * 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, see <http://www.gnu.org/licenses/> |
| */ |
| |
| #include "qemu/osdep.h" |
| #include "libqtest.h" |
| #include "qemu/queue.h" |
| #include "qgraph_internal.h" |
| #include "qgraph.h" |
| |
| #define QGRAPH_PRINT_DEBUG 0 |
| #define QOS_ROOT "" |
| typedef struct QOSStackElement QOSStackElement; |
| |
| /* Graph Edge.*/ |
| struct QOSGraphEdge { |
| QOSEdgeType type; |
| char *dest; |
| void *arg; /* just for QEDGE_CONTAINS |
| * and QEDGE_CONSUMED_BY */ |
| char *extra_device_opts; /* added to -device option, "," is |
| * automatically added |
| */ |
| char *before_cmd_line; /* added before node cmd_line */ |
| char *after_cmd_line; /* added after -device options */ |
| char *edge_name; /* used by QEDGE_CONTAINS */ |
| QSLIST_ENTRY(QOSGraphEdge) edge_list; |
| }; |
| |
| typedef QSLIST_HEAD(, QOSGraphEdge) QOSGraphEdgeList; |
| |
| /** |
| * Stack used to keep track of the discovered path when using |
| * the DFS algorithm |
| */ |
| struct QOSStackElement { |
| QOSGraphNode *node; |
| QOSStackElement *parent; |
| QOSGraphEdge *parent_edge; |
| int length; |
| }; |
| |
| /* Each enty in these hash table will consist of <string, node/edge> pair. */ |
| static GHashTable *edge_table; |
| static GHashTable *node_table; |
| |
| /* stack used by the DFS algorithm to store the path from machine to test */ |
| static QOSStackElement qos_node_stack[QOS_PATH_MAX_ELEMENT_SIZE]; |
| static int qos_node_tos; |
| |
| /** |
| * add_edge(): creates an edge of type @type |
| * from @source to @dest node, and inserts it in the |
| * edges hash table |
| * |
| * Nodes @source and @dest do not necessarily need to exist. |
| * Possibility to add also options (see #QOSGraphEdgeOptions) |
| * edge->edge_name is used as identifier for get_device relationships, |
| * so by default is equal to @dest. |
| */ |
| static void add_edge(const char *source, const char *dest, |
| QOSEdgeType type, QOSGraphEdgeOptions *opts) |
| { |
| char *key; |
| QOSGraphEdgeList *list = g_hash_table_lookup(edge_table, source); |
| QOSGraphEdgeOptions def_opts = { }; |
| |
| if (!list) { |
| list = g_new0(QOSGraphEdgeList, 1); |
| key = g_strdup(source); |
| g_hash_table_insert(edge_table, key, list); |
| } |
| |
| if (!opts) { |
| opts = &def_opts; |
| } |
| |
| QOSGraphEdge *edge = g_new0(QOSGraphEdge, 1); |
| edge->type = type; |
| edge->dest = g_strdup(dest); |
| edge->edge_name = g_strdup(opts->edge_name ?: dest); |
| edge->arg = g_memdup(opts->arg, opts->size_arg); |
| |
| edge->before_cmd_line = |
| opts->before_cmd_line ? g_strconcat(" ", opts->before_cmd_line, NULL) : NULL; |
| edge->extra_device_opts = |
| opts->extra_device_opts ? g_strconcat(",", opts->extra_device_opts, NULL) : NULL; |
| edge->after_cmd_line = |
| opts->after_cmd_line ? g_strconcat(" ", opts->after_cmd_line, NULL) : NULL; |
| |
| QSLIST_INSERT_HEAD(list, edge, edge_list); |
| } |
| |
| /* destroy_edges(): frees all edges inside a given @list */ |
| static void destroy_edges(void *list) |
| { |
| QOSGraphEdge *temp; |
| QOSGraphEdgeList *elist = list; |
| |
| while (!QSLIST_EMPTY(elist)) { |
| temp = QSLIST_FIRST(elist); |
| QSLIST_REMOVE_HEAD(elist, edge_list); |
| g_free(temp->dest); |
| g_free(temp->before_cmd_line); |
| g_free(temp->after_cmd_line); |
| g_free(temp->extra_device_opts); |
| g_free(temp->edge_name); |
| g_free(temp->arg); |
| g_free(temp); |
| } |
| g_free(elist); |
| } |
| |
| /** |
| * create_node(): creates a node @name of type @type |
| * and inserts it to the nodes hash table. |
| * By default, node is not available. |
| */ |
| static QOSGraphNode *create_node(const char *name, QOSNodeType type) |
| { |
| if (g_hash_table_lookup(node_table, name)) { |
| g_printerr("Node %s already created\n", name); |
| abort(); |
| } |
| |
| QOSGraphNode *node = g_new0(QOSGraphNode, 1); |
| node->type = type; |
| node->available = false; |
| node->name = g_strdup(name); |
| g_hash_table_insert(node_table, node->name, node); |
| return node; |
| } |
| |
| /** |
| * destroy_node(): frees a node @val from the nodes hash table. |
| * Note that node->name is not free'd since it will represent the |
| * hash table key |
| */ |
| static void destroy_node(void *val) |
| { |
| QOSGraphNode *node = val; |
| g_free(node->command_line); |
| g_free(node); |
| } |
| |
| /** |
| * destroy_string(): frees @key from the nodes hash table. |
| * Actually frees the node->name |
| */ |
| static void destroy_string(void *key) |
| { |
| g_free(key); |
| } |
| |
| /** |
| * search_node(): search for a node @key in the nodes hash table |
| * Returns the QOSGraphNode if found, #NULL otherwise |
| */ |
| static QOSGraphNode *search_node(const char *key) |
| { |
| return g_hash_table_lookup(node_table, key); |
| } |
| |
| /** |
| * get_edgelist(): returns the edge list (value) assigned to |
| * the @key in the edge hash table. |
| * This list will contain all edges with source equal to @key |
| * |
| * Returns: on success: the %QOSGraphEdgeList |
| * otherwise: abort() |
| */ |
| static QOSGraphEdgeList *get_edgelist(const char *key) |
| { |
| return g_hash_table_lookup(edge_table, key); |
| } |
| |
| /** |
| * search_list_edges(): search for an edge with destination @dest |
| * in the given @edgelist. |
| * |
| * Returns: on success: the %QOSGraphEdge |
| * otherwise: #NULL |
| */ |
| static QOSGraphEdge *search_list_edges(QOSGraphEdgeList *edgelist, |
| const char *dest) |
| { |
| QOSGraphEdge *tmp, *next; |
| if (!edgelist) { |
| return NULL; |
| } |
| QSLIST_FOREACH_SAFE(tmp, edgelist, edge_list, next) { |
| if (g_strcmp0(tmp->dest, dest) == 0) { |
| break; |
| } |
| } |
| return tmp; |
| } |
| |
| /** |
| * search_machine(): search for a machine @name in the node hash |
| * table. A machine is the child of the root node. |
| * This function forces the research in the childs of the root, |
| * to check the node is a proper machine |
| * |
| * Returns: on success: the %QOSGraphNode |
| * otherwise: #NULL |
| */ |
| static QOSGraphNode *search_machine(const char *name) |
| { |
| QOSGraphNode *n; |
| QOSGraphEdgeList *root_list = get_edgelist(QOS_ROOT); |
| QOSGraphEdge *e = search_list_edges(root_list, name); |
| if (!e) { |
| return NULL; |
| } |
| n = search_node(e->dest); |
| if (n->type == QNODE_MACHINE) { |
| return n; |
| } |
| return NULL; |
| } |
| |
| /** |
| * create_interface(): checks if there is already |
| * a node @node in the node hash table, if not |
| * creates a node @node of type #QNODE_INTERFACE |
| * and inserts it. If there is one, check it's |
| * a #QNODE_INTERFACE and abort() if it's not. |
| */ |
| static void create_interface(const char *node) |
| { |
| QOSGraphNode *interface; |
| interface = search_node(node); |
| if (!interface) { |
| create_node(node, QNODE_INTERFACE); |
| } else if (interface->type != QNODE_INTERFACE) { |
| fprintf(stderr, "Error: Node %s is not an interface\n", node); |
| abort(); |
| } |
| } |
| |
| /** |
| * build_machine_cmd_line(): builds the command line for the machine |
| * @node. The node name must be a valid qemu identifier, since it |
| * will be used to build the command line. |
| * |
| * It is also possible to pass an optional @args that will be |
| * concatenated to the command line. |
| * |
| * For machines, prepend -M to the machine name. ", @rgs" is added |
| * after the -M <machine> command. |
| */ |
| static void build_machine_cmd_line(QOSGraphNode *node, const char *args) |
| { |
| char *machine = qos_get_machine_type(node->name); |
| if (args) { |
| node->command_line = g_strconcat("-M ", machine, ",", args, NULL); |
| } else { |
| node->command_line = g_strconcat("-M ", machine, " ", NULL); |
| } |
| } |
| |
| /** |
| * build_driver_cmd_line(): builds the command line for the driver |
| * @node. The node name must be a valid qemu identifier, since it |
| * will be used to build the command line. |
| * |
| * Driver do not need additional command line, since it will be |
| * provided by the edge options. |
| * |
| * For drivers, prepend -device to the node name. |
| */ |
| static void build_driver_cmd_line(QOSGraphNode *node) |
| { |
| node->command_line = g_strconcat(" -device ", node->name, NULL); |
| } |
| |
| /* qos_print_cb(): callback prints all path found by the DFS algorithm. */ |
| static void qos_print_cb(QOSGraphNode *path, int length) |
| { |
| #if QGRAPH_PRINT_DEBUG |
| printf("%d elements\n", length); |
| |
| if (!path) { |
| return; |
| } |
| |
| while (path->path_edge) { |
| printf("%s ", path->name); |
| switch (path->path_edge->type) { |
| case QEDGE_PRODUCES: |
| printf("--PRODUCES--> "); |
| break; |
| case QEDGE_CONSUMED_BY: |
| printf("--CONSUMED_BY--> "); |
| break; |
| case QEDGE_CONTAINS: |
| printf("--CONTAINS--> "); |
| break; |
| } |
| path = search_node(path->path_edge->dest); |
| } |
| |
| printf("%s\n\n", path->name); |
| #endif |
| } |
| |
| /* qos_push(): push a node @el and edge @e in the qos_node_stack */ |
| static void qos_push(QOSGraphNode *el, QOSStackElement *parent, |
| QOSGraphEdge *e) |
| { |
| int len = 0; /* root is not counted */ |
| if (qos_node_tos == QOS_PATH_MAX_ELEMENT_SIZE) { |
| g_printerr("QOSStack: full stack, cannot push"); |
| abort(); |
| } |
| |
| if (parent) { |
| len = parent->length + 1; |
| } |
| qos_node_stack[qos_node_tos++] = (QOSStackElement) { |
| .node = el, |
| .parent = parent, |
| .parent_edge = e, |
| .length = len, |
| }; |
| } |
| |
| /* qos_tos(): returns the top of stack, without popping */ |
| static QOSStackElement *qos_tos(void) |
| { |
| return &qos_node_stack[qos_node_tos - 1]; |
| } |
| |
| /* qos_pop(): pops an element from the tos, setting it unvisited*/ |
| static QOSStackElement *qos_pop(void) |
| { |
| if (qos_node_tos == 0) { |
| g_printerr("QOSStack: empty stack, cannot pop"); |
| abort(); |
| } |
| QOSStackElement *e = qos_tos(); |
| e->node->visited = false; |
| qos_node_tos--; |
| return e; |
| } |
| |
| /** |
| * qos_reverse_path(): reverses the found path, going from |
| * test-to-machine to machine-to-test |
| */ |
| static QOSGraphNode *qos_reverse_path(QOSStackElement *el) |
| { |
| if (!el) { |
| return NULL; |
| } |
| |
| el->node->path_edge = NULL; |
| |
| while (el->parent) { |
| el->parent->node->path_edge = el->parent_edge; |
| el = el->parent; |
| } |
| |
| return el->node; |
| } |
| |
| /** |
| * qos_traverse_graph(): graph-walking algorithm, using Depth First Search it |
| * starts from the root @machine and walks all possible path until it |
| * reaches a test node. |
| * At that point, it reverses the path found and invokes the @callback. |
| * |
| * Being Depth First Search, time complexity is O(|V| + |E|), while |
| * space is O(|V|). In this case, the maximum stack size is set by |
| * QOS_PATH_MAX_ELEMENT_SIZE. |
| */ |
| static void qos_traverse_graph(QOSGraphNode *root, QOSTestCallback callback) |
| { |
| QOSGraphNode *v, *dest_node, *path; |
| QOSStackElement *s_el; |
| QOSGraphEdge *e, *next; |
| QOSGraphEdgeList *list; |
| |
| qos_push(root, NULL, NULL); |
| |
| while (qos_node_tos > 0) { |
| s_el = qos_tos(); |
| v = s_el->node; |
| if (v->visited) { |
| qos_pop(); |
| continue; |
| } |
| v->visited = true; |
| list = get_edgelist(v->name); |
| if (!list) { |
| qos_pop(); |
| if (v->type == QNODE_TEST) { |
| v->visited = false; |
| path = qos_reverse_path(s_el); |
| callback(path, s_el->length); |
| } |
| } else { |
| QSLIST_FOREACH_SAFE(e, list, edge_list, next) { |
| dest_node = search_node(e->dest); |
| |
| if (!dest_node) { |
| fprintf(stderr, "node %s in %s -> %s does not exist\n", |
| e->dest, v->name, e->dest); |
| abort(); |
| } |
| |
| if (!dest_node->visited && dest_node->available) { |
| qos_push(dest_node, s_el, e); |
| } |
| } |
| } |
| } |
| } |
| |
| /* QGRAPH API*/ |
| |
| QOSGraphNode *qos_graph_get_node(const char *key) |
| { |
| return search_node(key); |
| } |
| |
| bool qos_graph_has_node(const char *node) |
| { |
| QOSGraphNode *n = search_node(node); |
| return n != NULL; |
| } |
| |
| QOSNodeType qos_graph_get_node_type(const char *node) |
| { |
| QOSGraphNode *n = search_node(node); |
| if (n) { |
| return n->type; |
| } |
| return -1; |
| } |
| |
| bool qos_graph_get_node_availability(const char *node) |
| { |
| QOSGraphNode *n = search_node(node); |
| if (n) { |
| return n->available; |
| } |
| return false; |
| } |
| |
| QOSGraphEdge *qos_graph_get_edge(const char *node, const char *dest) |
| { |
| QOSGraphEdgeList *list = get_edgelist(node); |
| return search_list_edges(list, dest); |
| } |
| |
| QOSEdgeType qos_graph_edge_get_type(QOSGraphEdge *edge) |
| { |
| if (!edge) { |
| return -1; |
| } |
| return edge->type; |
| } |
| |
| char *qos_graph_edge_get_dest(QOSGraphEdge *edge) |
| { |
| if (!edge) { |
| return NULL; |
| } |
| return edge->dest; |
| } |
| |
| void *qos_graph_edge_get_arg(QOSGraphEdge *edge) |
| { |
| if (!edge) { |
| return NULL; |
| } |
| return edge->arg; |
| } |
| |
| char *qos_graph_edge_get_after_cmd_line(QOSGraphEdge *edge) |
| { |
| if (!edge) { |
| return NULL; |
| } |
| return edge->after_cmd_line; |
| } |
| |
| char *qos_graph_edge_get_before_cmd_line(QOSGraphEdge *edge) |
| { |
| if (!edge) { |
| return NULL; |
| } |
| return edge->before_cmd_line; |
| } |
| |
| char *qos_graph_edge_get_extra_device_opts(QOSGraphEdge *edge) |
| { |
| if (!edge) { |
| return NULL; |
| } |
| return edge->extra_device_opts; |
| } |
| |
| char *qos_graph_edge_get_name(QOSGraphEdge *edge) |
| { |
| if (!edge) { |
| return NULL; |
| } |
| return edge->edge_name; |
| } |
| |
| bool qos_graph_has_edge(const char *start, const char *dest) |
| { |
| QOSGraphEdgeList *list = get_edgelist(start); |
| QOSGraphEdge *e = search_list_edges(list, dest); |
| return e != NULL; |
| } |
| |
| QOSGraphNode *qos_graph_get_machine(const char *node) |
| { |
| return search_machine(node); |
| } |
| |
| bool qos_graph_has_machine(const char *node) |
| { |
| QOSGraphNode *m = search_machine(node); |
| return m != NULL; |
| } |
| |
| void qos_print_graph(void) |
| { |
| qos_graph_foreach_test_path(qos_print_cb); |
| } |
| |
| void qos_graph_init(void) |
| { |
| if (!node_table) { |
| node_table = g_hash_table_new_full(g_str_hash, g_str_equal, |
| destroy_string, destroy_node); |
| create_node(QOS_ROOT, QNODE_DRIVER); |
| } |
| |
| if (!edge_table) { |
| edge_table = g_hash_table_new_full(g_str_hash, g_str_equal, |
| destroy_string, destroy_edges); |
| } |
| } |
| |
| void qos_graph_destroy(void) |
| { |
| if (node_table) { |
| g_hash_table_destroy(node_table); |
| } |
| |
| if (edge_table) { |
| g_hash_table_destroy(edge_table); |
| } |
| |
| node_table = NULL; |
| edge_table = NULL; |
| } |
| |
| void qos_node_destroy(void *key) |
| { |
| g_hash_table_remove(node_table, key); |
| } |
| |
| void qos_edge_destroy(void *key) |
| { |
| g_hash_table_remove(edge_table, key); |
| } |
| |
| void qos_add_test(const char *name, const char *interface, |
| QOSTestFunc test_func, QOSGraphTestOptions *opts) |
| { |
| QOSGraphNode *node; |
| char *test_name = g_strdup_printf("%s-tests/%s", interface, name); |
| QOSGraphTestOptions def_opts = { }; |
| |
| if (!opts) { |
| opts = &def_opts; |
| } |
| node = create_node(test_name, QNODE_TEST); |
| node->u.test.function = test_func; |
| node->u.test.arg = opts->arg; |
| assert(!opts->edge.arg); |
| assert(!opts->edge.size_arg); |
| |
| node->u.test.before = opts->before; |
| node->u.test.subprocess = opts->subprocess; |
| node->available = true; |
| add_edge(interface, test_name, QEDGE_CONSUMED_BY, &opts->edge); |
| g_free(test_name); |
| } |
| |
| void qos_node_create_machine(const char *name, QOSCreateMachineFunc function) |
| { |
| qos_node_create_machine_args(name, function, NULL); |
| } |
| |
| void qos_node_create_machine_args(const char *name, |
| QOSCreateMachineFunc function, |
| const char *opts) |
| { |
| QOSGraphNode *node = create_node(name, QNODE_MACHINE); |
| build_machine_cmd_line(node, opts); |
| node->u.machine.constructor = function; |
| add_edge(QOS_ROOT, name, QEDGE_CONTAINS, NULL); |
| } |
| |
| void qos_node_create_driver(const char *name, QOSCreateDriverFunc function) |
| { |
| QOSGraphNode *node = create_node(name, QNODE_DRIVER); |
| build_driver_cmd_line(node); |
| node->u.driver.constructor = function; |
| } |
| |
| void qos_node_contains(const char *container, const char *contained, |
| QOSGraphEdgeOptions *opts, ...) |
| { |
| va_list va; |
| |
| if (opts == NULL) { |
| add_edge(container, contained, QEDGE_CONTAINS, NULL); |
| return; |
| } |
| |
| va_start(va, opts); |
| do { |
| add_edge(container, contained, QEDGE_CONTAINS, opts); |
| opts = va_arg(va, QOSGraphEdgeOptions *); |
| } while (opts != NULL); |
| |
| va_end(va); |
| } |
| |
| void qos_node_produces(const char *producer, const char *interface) |
| { |
| create_interface(interface); |
| add_edge(producer, interface, QEDGE_PRODUCES, NULL); |
| } |
| |
| void qos_node_consumes(const char *consumer, const char *interface, |
| QOSGraphEdgeOptions *opts) |
| { |
| create_interface(interface); |
| add_edge(interface, consumer, QEDGE_CONSUMED_BY, opts); |
| } |
| |
| void qos_graph_node_set_availability(const char *node, bool av) |
| { |
| QOSGraphEdgeList *elist; |
| QOSGraphNode *n = search_node(node); |
| QOSGraphEdge *e, *next; |
| if (!n) { |
| return; |
| } |
| n->available = av; |
| elist = get_edgelist(node); |
| if (!elist) { |
| return; |
| } |
| QSLIST_FOREACH_SAFE(e, elist, edge_list, next) { |
| if (e->type == QEDGE_CONTAINS || e->type == QEDGE_PRODUCES) { |
| qos_graph_node_set_availability(e->dest, av); |
| } |
| } |
| } |
| |
| void qos_graph_foreach_test_path(QOSTestCallback fn) |
| { |
| QOSGraphNode *root = qos_graph_get_node(QOS_ROOT); |
| qos_traverse_graph(root, fn); |
| } |
| |
| QOSGraphObject *qos_machine_new(QOSGraphNode *node, QTestState *qts) |
| { |
| QOSGraphObject *obj; |
| |
| g_assert(node->type == QNODE_MACHINE); |
| obj = node->u.machine.constructor(qts); |
| obj->free = g_free; |
| return obj; |
| } |
| |
| QOSGraphObject *qos_driver_new(QOSGraphNode *node, QOSGraphObject *parent, |
| QGuestAllocator *alloc, void *arg) |
| { |
| QOSGraphObject *obj; |
| |
| g_assert(node->type == QNODE_DRIVER); |
| obj = node->u.driver.constructor(parent, alloc, arg); |
| obj->free = g_free; |
| return obj; |
| } |
| |
| void qos_object_destroy(QOSGraphObject *obj) |
| { |
| if (!obj) { |
| return; |
| } |
| if (obj->destructor) { |
| obj->destructor(obj); |
| } |
| if (obj->free) { |
| obj->free(obj); |
| } |
| } |
| |
| void qos_object_queue_destroy(QOSGraphObject *obj) |
| { |
| g_test_queue_destroy((GDestroyNotify) qos_object_destroy, obj); |
| } |
| |
| void qos_object_start_hw(QOSGraphObject *obj) |
| { |
| if (obj->start_hw) { |
| obj->start_hw(obj); |
| } |
| } |
| |
| char *qos_get_machine_type(char *name) |
| { |
| while (*name != '\0' && *name != '/') { |
| name++; |
| } |
| |
| if (!*name || !name[1]) { |
| fprintf(stderr, "Machine name has to be of the form <arch>/<machine>\n"); |
| abort(); |
| } |
| |
| return name + 1; |
| } |
| |
| void qos_delete_cmd_line(const char *name) |
| { |
| QOSGraphNode *node = search_node(name); |
| if (node) { |
| g_free(node->command_line); |
| node->command_line = NULL; |
| } |
| } |