/* * libqos driver framework * * Copyright (c) 2018 Emanuele Giuseppe Esposito * * 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 */ #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 entry in these hash table will consist of 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_memdup2(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->qemu_name); 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 children 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 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) { const char *name = node->qemu_name ?: node->name; node->command_line = g_strconcat(" -device ", 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_create_driver_named(const char *name, const char *qemu_name, QOSCreateDriverFunc function) { QOSGraphNode *node = create_node(name, QNODE_DRIVER); node->qemu_name = g_strdup(qemu_name); 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); } static void qos_graph_node_set_availability_explicit(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_explicit(e->dest, av); } } } /* * Behaves as qos_graph_node_set_availability_explicit(), except that the * former always matches by node name only, whereas this function matches both * by node name and node's optional 'qemu_name' field. */ void qos_graph_node_set_availability(const char *node, bool av) { GList *l; QOSGraphEdgeList *elist; QOSGraphEdge *e, *next; QOSGraphNode *n; GList *keys = g_hash_table_get_keys(node_table); for (l = keys; l != NULL; l = l->next) { const gchar *key = l->data; n = g_hash_table_lookup(node_table, key); /* * node's 'qemu_name' is set if there is more than one device with * the same QEMU (QMP) device name */ const char *node_name = n->qemu_name ?: n->name; if (g_strcmp0(node_name, node) == 0) { n->available = av; elist = get_edgelist(n->name); if (elist) { QSLIST_FOREACH_SAFE(e, elist, edge_list, next) { if (e->type == QEDGE_CONTAINS || e->type == QEDGE_PRODUCES) { qos_graph_node_set_availability_explicit(e->dest, av); } } } } } g_list_free(keys); } 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 /\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; } } void qos_dump_graph(void) { GList *keys; GList *l; QOSGraphEdgeList *list; QOSGraphEdge *e, *next; QOSGraphNode *dest_node, *node; qos_printf("ALL QGRAPH EDGES: {\n"); keys = g_hash_table_get_keys(edge_table); for (l = keys; l != NULL; l = l->next) { const gchar *key = l->data; qos_printf("\t src='%s'\n", key); list = get_edgelist(key); QSLIST_FOREACH_SAFE(e, list, edge_list, next) { dest_node = g_hash_table_lookup(node_table, e->dest); qos_printf("\t\t|-> dest='%s' type=%d (node=%p)", e->dest, e->type, dest_node); if (!dest_node) { qos_printf_literal(" <------- ERROR !"); } qos_printf_literal("\n"); } } g_list_free(keys); qos_printf("}\n"); qos_printf("ALL QGRAPH NODES: {\n"); keys = g_hash_table_get_keys(node_table); for (l = keys; l != NULL; l = l->next) { const gchar *key = l->data; node = g_hash_table_lookup(node_table, key); qos_printf("\t name='%s' ", key); if (node->qemu_name) { qos_printf_literal("qemu_name='%s' ", node->qemu_name); } qos_printf_literal("type=%d cmd_line='%s' [%s]\n", node->type, node->command_line, node->available ? "available" : "UNAVAILABLE" ); } g_list_free(keys); qos_printf("}\n"); }