xref: /openbmc/qemu/tests/qtest/libqos/qgraph.c (revision a6caeee8)
1 /*
2  * libqos driver framework
3  *
4  * Copyright (c) 2018 Emanuele Giuseppe Esposito <e.emanuelegiuseppe@gmail.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License version 2.1 as published by the Free Software Foundation.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, see <http://www.gnu.org/licenses/>
17  */
18 
19 #include "qemu/osdep.h"
20 #include "../libqtest.h"
21 #include "qemu/queue.h"
22 #include "qgraph_internal.h"
23 #include "qgraph.h"
24 
25 #define QGRAPH_PRINT_DEBUG 0
26 #define QOS_ROOT ""
27 typedef struct QOSStackElement QOSStackElement;
28 
29 /* Graph Edge.*/
30 struct QOSGraphEdge {
31     QOSEdgeType type;
32     char *dest;
33     void *arg;                /* just for QEDGE_CONTAINS
34                                * and QEDGE_CONSUMED_BY */
35     char *extra_device_opts;  /* added to -device option, "," is
36                                * automatically added
37                                */
38     char *before_cmd_line;    /* added before node cmd_line */
39     char *after_cmd_line;     /* added after -device options */
40     char *edge_name;          /* used by QEDGE_CONTAINS */
41     QSLIST_ENTRY(QOSGraphEdge) edge_list;
42 };
43 
44 typedef QSLIST_HEAD(, QOSGraphEdge) QOSGraphEdgeList;
45 
46 /**
47  * Stack used to keep track of the discovered path when using
48  * the DFS algorithm
49  */
50 struct QOSStackElement {
51     QOSGraphNode *node;
52     QOSStackElement *parent;
53     QOSGraphEdge *parent_edge;
54     int length;
55 };
56 
57 /* Each enty in these hash table will consist of <string, node/edge> pair. */
58 static GHashTable *edge_table;
59 static GHashTable *node_table;
60 
61 /* stack used by the DFS algorithm to store the path from machine to test */
62 static QOSStackElement qos_node_stack[QOS_PATH_MAX_ELEMENT_SIZE];
63 static int qos_node_tos;
64 
65 /**
66  * add_edge(): creates an edge of type @type
67  * from @source to @dest node, and inserts it in the
68  * edges hash table
69  *
70  * Nodes @source and @dest do not necessarily need to exist.
71  * Possibility to add also options (see #QOSGraphEdgeOptions)
72  * edge->edge_name is used as identifier for get_device relationships,
73  * so by default is equal to @dest.
74  */
75 static void add_edge(const char *source, const char *dest,
76                      QOSEdgeType type, QOSGraphEdgeOptions *opts)
77 {
78     char *key;
79     QOSGraphEdgeList *list = g_hash_table_lookup(edge_table, source);
80     QOSGraphEdgeOptions def_opts = { };
81 
82     if (!list) {
83         list = g_new0(QOSGraphEdgeList, 1);
84         key = g_strdup(source);
85         g_hash_table_insert(edge_table, key, list);
86     }
87 
88     if (!opts) {
89         opts = &def_opts;
90     }
91 
92     QOSGraphEdge *edge = g_new0(QOSGraphEdge, 1);
93     edge->type = type;
94     edge->dest = g_strdup(dest);
95     edge->edge_name = g_strdup(opts->edge_name ?: dest);
96     edge->arg = g_memdup2(opts->arg, opts->size_arg);
97 
98     edge->before_cmd_line =
99         opts->before_cmd_line ? g_strconcat(" ", opts->before_cmd_line, NULL) : NULL;
100     edge->extra_device_opts =
101         opts->extra_device_opts ? g_strconcat(",", opts->extra_device_opts, NULL) : NULL;
102     edge->after_cmd_line =
103         opts->after_cmd_line ? g_strconcat(" ", opts->after_cmd_line, NULL) : NULL;
104 
105     QSLIST_INSERT_HEAD(list, edge, edge_list);
106 }
107 
108 /* destroy_edges(): frees all edges inside a given @list */
109 static void destroy_edges(void *list)
110 {
111     QOSGraphEdge *temp;
112     QOSGraphEdgeList *elist = list;
113 
114     while (!QSLIST_EMPTY(elist)) {
115         temp = QSLIST_FIRST(elist);
116         QSLIST_REMOVE_HEAD(elist, edge_list);
117         g_free(temp->dest);
118         g_free(temp->before_cmd_line);
119         g_free(temp->after_cmd_line);
120         g_free(temp->extra_device_opts);
121         g_free(temp->edge_name);
122         g_free(temp->arg);
123         g_free(temp);
124     }
125     g_free(elist);
126 }
127 
128 /**
129  * create_node(): creates a node @name of type @type
130  * and inserts it to the nodes hash table.
131  * By default, node is not available.
132  */
133 static QOSGraphNode *create_node(const char *name, QOSNodeType type)
134 {
135     if (g_hash_table_lookup(node_table, name)) {
136         g_printerr("Node %s already created\n", name);
137         abort();
138     }
139 
140     QOSGraphNode *node = g_new0(QOSGraphNode, 1);
141     node->type = type;
142     node->available = false;
143     node->name = g_strdup(name);
144     g_hash_table_insert(node_table, node->name, node);
145     return node;
146 }
147 
148 /**
149  * destroy_node(): frees a node @val from the nodes hash table.
150  * Note that node->name is not free'd since it will represent the
151  * hash table key
152  */
153 static void destroy_node(void *val)
154 {
155     QOSGraphNode *node = val;
156     g_free(node->qemu_name);
157     g_free(node->command_line);
158     g_free(node);
159 }
160 
161 /**
162  * destroy_string(): frees @key from the nodes hash table.
163  * Actually frees the node->name
164  */
165 static void destroy_string(void *key)
166 {
167     g_free(key);
168 }
169 
170 /**
171  * search_node(): search for a node @key in the nodes hash table
172  * Returns the QOSGraphNode if found, #NULL otherwise
173  */
174 static QOSGraphNode *search_node(const char *key)
175 {
176     return g_hash_table_lookup(node_table, key);
177 }
178 
179 /**
180  * get_edgelist(): returns the edge list (value) assigned to
181  * the @key in the edge hash table.
182  * This list will contain all edges with source equal to @key
183  *
184  * Returns: on success: the %QOSGraphEdgeList
185  *          otherwise: abort()
186  */
187 static QOSGraphEdgeList *get_edgelist(const char *key)
188 {
189     return g_hash_table_lookup(edge_table, key);
190 }
191 
192 /**
193  * search_list_edges(): search for an edge with destination @dest
194  * in the given @edgelist.
195  *
196  * Returns: on success: the %QOSGraphEdge
197  *          otherwise: #NULL
198  */
199 static QOSGraphEdge *search_list_edges(QOSGraphEdgeList *edgelist,
200                                        const char *dest)
201 {
202     QOSGraphEdge *tmp, *next;
203     if (!edgelist) {
204         return NULL;
205     }
206     QSLIST_FOREACH_SAFE(tmp, edgelist, edge_list, next) {
207         if (g_strcmp0(tmp->dest, dest) == 0) {
208             break;
209         }
210     }
211     return tmp;
212 }
213 
214 /**
215  * search_machine(): search for a machine @name in the node hash
216  * table. A machine is the child of the root node.
217  * This function forces the research in the childs of the root,
218  * to check the node is a proper machine
219  *
220  * Returns: on success: the %QOSGraphNode
221  *          otherwise: #NULL
222  */
223 static QOSGraphNode *search_machine(const char *name)
224 {
225     QOSGraphNode *n;
226     QOSGraphEdgeList *root_list = get_edgelist(QOS_ROOT);
227     QOSGraphEdge *e = search_list_edges(root_list, name);
228     if (!e) {
229         return NULL;
230     }
231     n = search_node(e->dest);
232     if (n->type == QNODE_MACHINE) {
233         return n;
234     }
235     return NULL;
236 }
237 
238 /**
239  * create_interface(): checks if there is already
240  * a node @node in the node hash table, if not
241  * creates a node @node of type #QNODE_INTERFACE
242  * and inserts it. If there is one, check it's
243  * a #QNODE_INTERFACE and abort() if it's not.
244  */
245 static void create_interface(const char *node)
246 {
247     QOSGraphNode *interface;
248     interface = search_node(node);
249     if (!interface) {
250         create_node(node, QNODE_INTERFACE);
251     } else if (interface->type != QNODE_INTERFACE) {
252         fprintf(stderr, "Error: Node %s is not an interface\n", node);
253         abort();
254     }
255 }
256 
257 /**
258  * build_machine_cmd_line(): builds the command line for the machine
259  * @node. The node name must be a valid qemu identifier, since it
260  * will be used to build the command line.
261  *
262  * It is also possible to pass an optional @args that will be
263  * concatenated to the command line.
264  *
265  * For machines, prepend -M to the machine name. ", @rgs" is added
266  * after the -M <machine> command.
267  */
268 static void build_machine_cmd_line(QOSGraphNode *node, const char *args)
269 {
270     char *machine = qos_get_machine_type(node->name);
271     if (args) {
272         node->command_line = g_strconcat("-M ", machine, ",", args, NULL);
273     } else {
274         node->command_line = g_strconcat("-M ", machine, " ", NULL);
275     }
276 }
277 
278 /**
279  * build_driver_cmd_line(): builds the command line for the driver
280  * @node. The node name must be a valid qemu identifier, since it
281  * will be used to build the command line.
282  *
283  * Driver do not need additional command line, since it will be
284  * provided by the edge options.
285  *
286  * For drivers, prepend -device to the node name.
287  */
288 static void build_driver_cmd_line(QOSGraphNode *node)
289 {
290     const char *name = node->qemu_name ?: node->name;
291     node->command_line = g_strconcat(" -device ", name, NULL);
292 }
293 
294 /* qos_print_cb(): callback prints all path found by the DFS algorithm. */
295 static void qos_print_cb(QOSGraphNode *path, int length)
296 {
297     #if QGRAPH_PRINT_DEBUG
298         printf("%d elements\n", length);
299 
300         if (!path) {
301             return;
302         }
303 
304         while (path->path_edge) {
305             printf("%s ", path->name);
306             switch (path->path_edge->type) {
307             case QEDGE_PRODUCES:
308                 printf("--PRODUCES--> ");
309                 break;
310             case QEDGE_CONSUMED_BY:
311                 printf("--CONSUMED_BY--> ");
312                 break;
313             case QEDGE_CONTAINS:
314                 printf("--CONTAINS--> ");
315                 break;
316             }
317             path = search_node(path->path_edge->dest);
318         }
319 
320         printf("%s\n\n", path->name);
321     #endif
322 }
323 
324 /* qos_push(): push a node @el and edge @e in the qos_node_stack */
325 static void qos_push(QOSGraphNode *el, QOSStackElement *parent,
326                      QOSGraphEdge *e)
327 {
328     int len = 0; /* root is not counted */
329     if (qos_node_tos == QOS_PATH_MAX_ELEMENT_SIZE) {
330         g_printerr("QOSStack: full stack, cannot push");
331         abort();
332     }
333 
334     if (parent) {
335         len = parent->length + 1;
336     }
337     qos_node_stack[qos_node_tos++] = (QOSStackElement) {
338         .node = el,
339         .parent = parent,
340         .parent_edge = e,
341         .length = len,
342     };
343 }
344 
345 /* qos_tos(): returns the top of stack, without popping */
346 static QOSStackElement *qos_tos(void)
347 {
348     return &qos_node_stack[qos_node_tos - 1];
349 }
350 
351 /* qos_pop(): pops an element from the tos, setting it unvisited*/
352 static QOSStackElement *qos_pop(void)
353 {
354     if (qos_node_tos == 0) {
355         g_printerr("QOSStack: empty stack, cannot pop");
356         abort();
357     }
358     QOSStackElement *e = qos_tos();
359     e->node->visited = false;
360     qos_node_tos--;
361     return e;
362 }
363 
364 /**
365  * qos_reverse_path(): reverses the found path, going from
366  * test-to-machine to machine-to-test
367  */
368 static QOSGraphNode *qos_reverse_path(QOSStackElement *el)
369 {
370     if (!el) {
371         return NULL;
372     }
373 
374     el->node->path_edge = NULL;
375 
376     while (el->parent) {
377         el->parent->node->path_edge = el->parent_edge;
378         el = el->parent;
379     }
380 
381     return el->node;
382 }
383 
384 /**
385  * qos_traverse_graph(): graph-walking algorithm, using Depth First Search it
386  * starts from the root @machine and walks all possible path until it
387  * reaches a test node.
388  * At that point, it reverses the path found and invokes the @callback.
389  *
390  * Being Depth First Search, time complexity is O(|V| + |E|), while
391  * space is O(|V|). In this case, the maximum stack size is set by
392  * QOS_PATH_MAX_ELEMENT_SIZE.
393  */
394 static void qos_traverse_graph(QOSGraphNode *root, QOSTestCallback callback)
395 {
396     QOSGraphNode *v, *dest_node, *path;
397     QOSStackElement *s_el;
398     QOSGraphEdge *e, *next;
399     QOSGraphEdgeList *list;
400 
401     qos_push(root, NULL, NULL);
402 
403     while (qos_node_tos > 0) {
404         s_el = qos_tos();
405         v = s_el->node;
406         if (v->visited) {
407             qos_pop();
408             continue;
409         }
410         v->visited = true;
411         list = get_edgelist(v->name);
412         if (!list) {
413             qos_pop();
414             if (v->type == QNODE_TEST) {
415                 v->visited = false;
416                 path = qos_reverse_path(s_el);
417                 callback(path, s_el->length);
418             }
419         } else {
420             QSLIST_FOREACH_SAFE(e, list, edge_list, next) {
421                 dest_node = search_node(e->dest);
422 
423                 if (!dest_node) {
424                     fprintf(stderr, "node %s in %s -> %s does not exist\n",
425                             e->dest, v->name, e->dest);
426                     abort();
427                 }
428 
429                 if (!dest_node->visited && dest_node->available) {
430                     qos_push(dest_node, s_el, e);
431                 }
432             }
433         }
434     }
435 }
436 
437 /* QGRAPH API*/
438 
439 QOSGraphNode *qos_graph_get_node(const char *key)
440 {
441     return search_node(key);
442 }
443 
444 bool qos_graph_has_node(const char *node)
445 {
446     QOSGraphNode *n = search_node(node);
447     return n != NULL;
448 }
449 
450 QOSNodeType qos_graph_get_node_type(const char *node)
451 {
452     QOSGraphNode *n = search_node(node);
453     if (n) {
454         return n->type;
455     }
456     return -1;
457 }
458 
459 bool qos_graph_get_node_availability(const char *node)
460 {
461     QOSGraphNode *n = search_node(node);
462     if (n) {
463         return n->available;
464     }
465     return false;
466 }
467 
468 QOSGraphEdge *qos_graph_get_edge(const char *node, const char *dest)
469 {
470     QOSGraphEdgeList *list = get_edgelist(node);
471     return search_list_edges(list, dest);
472 }
473 
474 QOSEdgeType qos_graph_edge_get_type(QOSGraphEdge *edge)
475 {
476     if (!edge) {
477         return -1;
478     }
479     return edge->type;
480 }
481 
482 char *qos_graph_edge_get_dest(QOSGraphEdge *edge)
483 {
484     if (!edge) {
485         return NULL;
486     }
487     return edge->dest;
488 }
489 
490 void *qos_graph_edge_get_arg(QOSGraphEdge *edge)
491 {
492     if (!edge) {
493         return NULL;
494     }
495     return edge->arg;
496 }
497 
498 char *qos_graph_edge_get_after_cmd_line(QOSGraphEdge *edge)
499 {
500     if (!edge) {
501         return NULL;
502     }
503     return edge->after_cmd_line;
504 }
505 
506 char *qos_graph_edge_get_before_cmd_line(QOSGraphEdge *edge)
507 {
508     if (!edge) {
509         return NULL;
510     }
511     return edge->before_cmd_line;
512 }
513 
514 char *qos_graph_edge_get_extra_device_opts(QOSGraphEdge *edge)
515 {
516     if (!edge) {
517         return NULL;
518     }
519     return edge->extra_device_opts;
520 }
521 
522 char *qos_graph_edge_get_name(QOSGraphEdge *edge)
523 {
524     if (!edge) {
525         return NULL;
526     }
527     return edge->edge_name;
528 }
529 
530 bool qos_graph_has_edge(const char *start, const char *dest)
531 {
532     QOSGraphEdgeList *list = get_edgelist(start);
533     QOSGraphEdge *e = search_list_edges(list, dest);
534     return e != NULL;
535 }
536 
537 QOSGraphNode *qos_graph_get_machine(const char *node)
538 {
539     return search_machine(node);
540 }
541 
542 bool qos_graph_has_machine(const char *node)
543 {
544     QOSGraphNode *m = search_machine(node);
545     return m != NULL;
546 }
547 
548 void qos_print_graph(void)
549 {
550     qos_graph_foreach_test_path(qos_print_cb);
551 }
552 
553 void qos_graph_init(void)
554 {
555     if (!node_table) {
556         node_table = g_hash_table_new_full(g_str_hash, g_str_equal,
557                                            destroy_string, destroy_node);
558         create_node(QOS_ROOT, QNODE_DRIVER);
559     }
560 
561     if (!edge_table) {
562         edge_table = g_hash_table_new_full(g_str_hash, g_str_equal,
563                                            destroy_string, destroy_edges);
564     }
565 }
566 
567 void qos_graph_destroy(void)
568 {
569     if (node_table) {
570         g_hash_table_destroy(node_table);
571     }
572 
573     if (edge_table) {
574         g_hash_table_destroy(edge_table);
575     }
576 
577     node_table = NULL;
578     edge_table = NULL;
579 }
580 
581 void qos_node_destroy(void *key)
582 {
583     g_hash_table_remove(node_table, key);
584 }
585 
586 void qos_edge_destroy(void *key)
587 {
588     g_hash_table_remove(edge_table, key);
589 }
590 
591 void qos_add_test(const char *name, const char *interface,
592                   QOSTestFunc test_func, QOSGraphTestOptions *opts)
593 {
594     QOSGraphNode *node;
595     char *test_name = g_strdup_printf("%s-tests/%s", interface, name);
596     QOSGraphTestOptions def_opts = { };
597 
598     if (!opts) {
599         opts = &def_opts;
600     }
601     node = create_node(test_name, QNODE_TEST);
602     node->u.test.function = test_func;
603     node->u.test.arg = opts->arg;
604     assert(!opts->edge.arg);
605     assert(!opts->edge.size_arg);
606 
607     node->u.test.before = opts->before;
608     node->u.test.subprocess = opts->subprocess;
609     node->available = true;
610     add_edge(interface, test_name, QEDGE_CONSUMED_BY, &opts->edge);
611     g_free(test_name);
612 }
613 
614 void qos_node_create_machine(const char *name, QOSCreateMachineFunc function)
615 {
616     qos_node_create_machine_args(name, function, NULL);
617 }
618 
619 void qos_node_create_machine_args(const char *name,
620                                   QOSCreateMachineFunc function,
621                                   const char *opts)
622 {
623     QOSGraphNode *node = create_node(name, QNODE_MACHINE);
624     build_machine_cmd_line(node, opts);
625     node->u.machine.constructor = function;
626     add_edge(QOS_ROOT, name, QEDGE_CONTAINS, NULL);
627 }
628 
629 void qos_node_create_driver(const char *name, QOSCreateDriverFunc function)
630 {
631     QOSGraphNode *node = create_node(name, QNODE_DRIVER);
632     build_driver_cmd_line(node);
633     node->u.driver.constructor = function;
634 }
635 
636 void qos_node_create_driver_named(const char *name, const char *qemu_name,
637                                   QOSCreateDriverFunc function)
638 {
639     QOSGraphNode *node = create_node(name, QNODE_DRIVER);
640     node->qemu_name = g_strdup(qemu_name);
641     build_driver_cmd_line(node);
642     node->u.driver.constructor = function;
643 }
644 
645 void qos_node_contains(const char *container, const char *contained,
646                        QOSGraphEdgeOptions *opts, ...)
647 {
648     va_list va;
649 
650     if (opts == NULL) {
651         add_edge(container, contained, QEDGE_CONTAINS, NULL);
652         return;
653     }
654 
655     va_start(va, opts);
656     do {
657         add_edge(container, contained, QEDGE_CONTAINS, opts);
658         opts = va_arg(va, QOSGraphEdgeOptions *);
659     } while (opts != NULL);
660 
661     va_end(va);
662 }
663 
664 void qos_node_produces(const char *producer, const char *interface)
665 {
666     create_interface(interface);
667     add_edge(producer, interface, QEDGE_PRODUCES, NULL);
668 }
669 
670 void qos_node_consumes(const char *consumer, const char *interface,
671                        QOSGraphEdgeOptions *opts)
672 {
673     create_interface(interface);
674     add_edge(interface, consumer, QEDGE_CONSUMED_BY, opts);
675 }
676 
677 static void qos_graph_node_set_availability_explicit(const char *node, bool av)
678 {
679     QOSGraphEdgeList *elist;
680     QOSGraphNode *n = search_node(node);
681     QOSGraphEdge *e, *next;
682     if (!n) {
683         return;
684     }
685     n->available = av;
686     elist = get_edgelist(node);
687     if (!elist) {
688         return;
689     }
690     QSLIST_FOREACH_SAFE(e, elist, edge_list, next) {
691         if (e->type == QEDGE_CONTAINS || e->type == QEDGE_PRODUCES) {
692             qos_graph_node_set_availability_explicit(e->dest, av);
693         }
694     }
695 }
696 
697 /*
698  * Behaves as qos_graph_node_set_availability_explicit(), except that the
699  * former always matches by node name only, whereas this function matches both
700  * by node name and node's optional 'qemu_name' field.
701  */
702 void qos_graph_node_set_availability(const char *node, bool av)
703 {
704     GList *l;
705     QOSGraphEdgeList *elist;
706     QOSGraphEdge *e, *next;
707     QOSGraphNode *n;
708     GList *keys = g_hash_table_get_keys(node_table);
709 
710     for (l = keys; l != NULL; l = l->next) {
711         const gchar *key = l->data;
712         n = g_hash_table_lookup(node_table, key);
713         /*
714          * node's 'qemu_name' is set if there is more than one device with
715          * the same QEMU (QMP) device name
716          */
717         const char *node_name = n->qemu_name ?: n->name;
718         if (g_strcmp0(node_name, node) == 0) {
719             n->available = av;
720             elist = get_edgelist(n->name);
721             if (elist) {
722                 QSLIST_FOREACH_SAFE(e, elist, edge_list, next) {
723                     if (e->type == QEDGE_CONTAINS || e->type == QEDGE_PRODUCES)
724                     {
725                         qos_graph_node_set_availability_explicit(e->dest, av);
726                     }
727                 }
728             }
729         }
730     }
731     g_list_free(keys);
732 }
733 
734 void qos_graph_foreach_test_path(QOSTestCallback fn)
735 {
736     QOSGraphNode *root = qos_graph_get_node(QOS_ROOT);
737     qos_traverse_graph(root, fn);
738 }
739 
740 QOSGraphObject *qos_machine_new(QOSGraphNode *node, QTestState *qts)
741 {
742     QOSGraphObject *obj;
743 
744     g_assert(node->type == QNODE_MACHINE);
745     obj = node->u.machine.constructor(qts);
746     obj->free = g_free;
747     return obj;
748 }
749 
750 QOSGraphObject *qos_driver_new(QOSGraphNode *node, QOSGraphObject *parent,
751                                QGuestAllocator *alloc, void *arg)
752 {
753     QOSGraphObject *obj;
754 
755     g_assert(node->type == QNODE_DRIVER);
756     obj = node->u.driver.constructor(parent, alloc, arg);
757     obj->free = g_free;
758     return obj;
759 }
760 
761 void qos_object_destroy(QOSGraphObject *obj)
762 {
763     if (!obj) {
764         return;
765     }
766     if (obj->destructor) {
767         obj->destructor(obj);
768     }
769     if (obj->free) {
770         obj->free(obj);
771     }
772 }
773 
774 void qos_object_queue_destroy(QOSGraphObject *obj)
775 {
776     g_test_queue_destroy((GDestroyNotify) qos_object_destroy, obj);
777 }
778 
779 void qos_object_start_hw(QOSGraphObject *obj)
780 {
781     if (obj->start_hw) {
782         obj->start_hw(obj);
783     }
784 }
785 
786 char *qos_get_machine_type(char *name)
787 {
788     while (*name != '\0' && *name != '/') {
789         name++;
790     }
791 
792     if (!*name || !name[1]) {
793         fprintf(stderr, "Machine name has to be of the form <arch>/<machine>\n");
794         abort();
795     }
796 
797     return name + 1;
798 }
799 
800 void qos_delete_cmd_line(const char *name)
801 {
802     QOSGraphNode *node = search_node(name);
803     if (node) {
804         g_free(node->command_line);
805         node->command_line = NULL;
806     }
807 }
808 
809 void qos_dump_graph(void)
810 {
811     GList *keys;
812     GList *l;
813     QOSGraphEdgeList *list;
814     QOSGraphEdge *e, *next;
815     QOSGraphNode *dest_node, *node;
816 
817     qos_printf("ALL QGRAPH EDGES: {\n");
818     keys = g_hash_table_get_keys(edge_table);
819     for (l = keys; l != NULL; l = l->next) {
820         const gchar *key = l->data;
821         qos_printf("\t src='%s'\n", key);
822         list = get_edgelist(key);
823         QSLIST_FOREACH_SAFE(e, list, edge_list, next) {
824             dest_node = g_hash_table_lookup(node_table, e->dest);
825             qos_printf("\t\t|-> dest='%s' type=%d (node=%p)",
826                        e->dest, e->type, dest_node);
827             if (!dest_node) {
828                 qos_printf_literal(" <------- ERROR !");
829             }
830             qos_printf_literal("\n");
831         }
832     }
833     g_list_free(keys);
834     qos_printf("}\n");
835 
836     qos_printf("ALL QGRAPH NODES: {\n");
837     keys = g_hash_table_get_keys(node_table);
838     for (l = keys; l != NULL; l = l->next) {
839         const gchar *key = l->data;
840         node = g_hash_table_lookup(node_table, key);
841         qos_printf("\t name='%s' ", key);
842         if (node->qemu_name) {
843             qos_printf_literal("qemu_name='%s' ", node->qemu_name);
844         }
845         qos_printf_literal("type=%d cmd_line='%s' [%s]\n",
846                            node->type, node->command_line,
847                            node->available ? "available" : "UNAVAILABLE"
848         );
849     }
850     g_list_free(keys);
851     qos_printf("}\n");
852 }
853