diff options
35 files changed, 3962 insertions, 78 deletions
diff --git a/exception_lists/manlint b/exception_lists/manlint index 1726989d76..8c8039d6c5 100644 --- a/exception_lists/manlint +++ b/exception_lists/manlint @@ -10,13 +10,14 @@ # # # Copyright 2016 Toomas Soome <tsoome@me.com> -# Copyright (c) 2019, Joyent, Inc. +# Copyright 2020 Joyent, Inc. # usr/src/boot/* # Not actually a manual page usr/src/cmd/hal/fdi/fdi.dtd.1 usr/src/cmd/isns/isnsd/xml_def/isnsdata.dtd.1 usr/src/cmd/svc/dtd/service_bundle.dtd.1 +usr/src/lib/fm/topo/maps/common/digraph-topology.dtd.1 usr/src/lib/fm/topo/maps/common/topology.dtd.1 usr/src/lib/libbrand/dtd/brand.dtd.1 usr/src/lib/libbrand/dtd/zone_platform.dtd.1 diff --git a/usr/src/lib/fm/topo/libtopo/Makefile.com b/usr/src/lib/fm/topo/libtopo/Makefile.com index e3b18ffa3a..8592fc9881 100644 --- a/usr/src/lib/fm/topo/libtopo/Makefile.com +++ b/usr/src/lib/fm/topo/libtopo/Makefile.com @@ -20,7 +20,7 @@ # # # Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved. -# Copyright (c) 2019, Joyent, Inc. +# Copyright 2020 Joyent, Inc. # LIBRARY = libtopo.a @@ -43,6 +43,8 @@ LIBSRCS = \ topo_2xml.c \ topo_alloc.c \ topo_builtin.c \ + topo_digraph.c \ + topo_digraph_xml.c \ topo_error.c \ topo_file.c \ topo_fmri.c \ diff --git a/usr/src/lib/fm/topo/libtopo/common/hc.c b/usr/src/lib/fm/topo/libtopo/common/hc.c index cb53f87a51..6c527fcc8c 100644 --- a/usr/src/lib/fm/topo/libtopo/common/hc.c +++ b/usr/src/lib/fm/topo/libtopo/common/hc.c @@ -1138,7 +1138,7 @@ hc_fmri_create_meth(topo_mod_t *mod, tnode_t *node, topo_version_t version, int ret; nvlist_t *args, *pfmri = NULL; nvlist_t *auth; - uint32_t inst; + uint64_t inst; char *name, *serial, *rev, *part; if (version > TOPO_METH_FMRI_VERSION) @@ -1147,7 +1147,7 @@ hc_fmri_create_meth(topo_mod_t *mod, tnode_t *node, topo_version_t version, /* First the must-have fields */ if (nvlist_lookup_string(in, TOPO_METH_FMRI_ARG_NAME, &name) != 0) return (topo_mod_seterrno(mod, EMOD_METHOD_INVAL)); - if (nvlist_lookup_uint32(in, TOPO_METH_FMRI_ARG_INST, &inst) != 0) + if (nvlist_lookup_uint64(in, TOPO_METH_FMRI_ARG_INST, &inst) != 0) return (topo_mod_seterrno(mod, EMOD_METHOD_INVAL)); /* diff --git a/usr/src/lib/fm/topo/libtopo/common/libtopo.h b/usr/src/lib/fm/topo/libtopo/common/libtopo.h index 06f473a3b1..8094587fdf 100644 --- a/usr/src/lib/fm/topo/libtopo/common/libtopo.h +++ b/usr/src/lib/fm/topo/libtopo/common/libtopo.h @@ -42,7 +42,7 @@ extern "C" { typedef struct topo_hdl topo_hdl_t; typedef struct topo_node tnode_t; typedef struct topo_walk topo_walk_t; -typedef int32_t topo_instance_t; +typedef uint64_t topo_instance_t; typedef uint32_t topo_version_t; typedef struct topo_list { @@ -55,6 +55,21 @@ typedef struct topo_faclist { tnode_t *tf_node; } topo_faclist_t; +typedef struct topo_digraph topo_digraph_t; +typedef struct topo_vertex topo_vertex_t; +typedef struct topo_edge topo_edge_t; + +typedef struct topo_path { + const char *tsp_fmristr; + nvlist_t *tsp_fmri; + topo_list_t tsp_components; +} topo_path_t; + +typedef struct topo_path_component { + topo_list_t tspc_link; + topo_vertex_t *tspc_vertex; +} topo_path_component_t; + /* * The following functions, error codes and data structures are private * to libtopo snapshot consumers and enumerator modules. @@ -397,6 +412,23 @@ extern int topo_hdl_nvdup(topo_hdl_t *, nvlist_t *, nvlist_t **); extern char *topo_hdl_strdup(topo_hdl_t *, const char *); /* + * Interfaces for interacting with directed graph topologies + */ +extern topo_digraph_t *topo_digraph_get(topo_hdl_t *, const char *); +extern int topo_vertex_iter(topo_hdl_t *, topo_digraph_t *, + int (*)(topo_hdl_t *, topo_vertex_t *, boolean_t, void *), void *); +extern tnode_t *topo_vertex_node(topo_vertex_t *); +extern int topo_edge_iter(topo_hdl_t *, topo_vertex_t *, + int (*)(topo_hdl_t *, topo_edge_t *, boolean_t, void *), void *); +extern int topo_digraph_paths(topo_hdl_t *, topo_digraph_t *, + topo_vertex_t *, topo_vertex_t *, topo_path_t ***, uint_t *); +extern void topo_path_destroy(topo_hdl_t *, topo_path_t *); +extern int topo_digraph_serialize(topo_hdl_t *, topo_digraph_t *, FILE *); +extern topo_digraph_t *topo_digraph_deserialize(topo_hdl_t *, const char *, + size_t); +extern topo_vertex_t *topo_node_vertex(tnode_t *); + +/* * Interfaces for converting sensor/indicator types, units, states, etc to * a string */ diff --git a/usr/src/lib/fm/topo/libtopo/common/mapfile-vers b/usr/src/lib/fm/topo/libtopo/common/mapfile-vers index 5c8ab520fc..5d1b53f1f6 100644 --- a/usr/src/lib/fm/topo/libtopo/common/mapfile-vers +++ b/usr/src/lib/fm/topo/libtopo/common/mapfile-vers @@ -20,7 +20,7 @@ # # # Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved. -# Copyright 2019 Joyent, Inc. +# Copyright 2020 Joyent, Inc. # # @@ -43,6 +43,14 @@ SYMBOL_VERSION SUNWprivate { global: topo_close; topo_debug_set; + topo_digraph_deserialize; + topo_digraph_destroy; + topo_digraph_get; + topo_digraph_new; + topo_digraph_paths; + topo_digraph_serialize; + topo_edge_iter; + topo_edge_new; topo_fmri_asru; topo_fmri_compare; topo_fmri_contains; @@ -156,7 +164,9 @@ SYMBOL_VERSION SUNWprivate { topo_node_resource; topo_node_setspecific; topo_node_unbind; + topo_node_vertex; topo_open; + topo_path_destroy; topo_pgroup_create; topo_pgroup_destroy; topo_pgroup_info; @@ -204,6 +214,10 @@ SYMBOL_VERSION SUNWprivate { topo_snap_hold; topo_snap_release; topo_strerror; + topo_vertex_destroy; + topo_vertex_iter; + topo_vertex_node; + topo_vertex_new; topo_walk_fini; topo_walk_init; topo_walk_step; diff --git a/usr/src/lib/fm/topo/libtopo/common/topo_builtin.c b/usr/src/lib/fm/topo/libtopo/common/topo_builtin.c index fe979488bd..8acb9cf453 100644 --- a/usr/src/lib/fm/topo/libtopo/common/topo_builtin.c +++ b/usr/src/lib/fm/topo/libtopo/common/topo_builtin.c @@ -20,6 +20,7 @@ */ /* * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright 2020 Joyent, Inc. */ #include <pthread.h> @@ -42,17 +43,18 @@ #include <zfs.h> static const struct topo_builtin _topo_builtins[] = { - { "cpu", CPU_VERSION, cpu_init, cpu_fini }, - { "dev", DEV_VERSION, dev_init, dev_fini }, - { "fmd", FMD_VERSION, fmd_init, fmd_fini }, - { "mem", MEM_VERSION, mem_init, mem_fini }, - { "pkg", PKG_VERSION, pkg_init, pkg_fini }, - { "svc", SVC_VERSION, svc_init, svc_fini }, - { "sw", SW_VERSION, sw_init, sw_fini }, - { "zfs", ZFS_VERSION, zfs_init, zfs_fini }, - { "mod", MOD_VERSION, mod_init, mod_fini }, - { "hc", HC_VERSION, hc_init, hc_fini }, /* hc must go last */ - { NULL, 0, NULL, NULL } + { "cpu", CPU_VERSION, cpu_init, cpu_fini, TOPO_BLTIN_TYPE_TREE }, + { "dev", DEV_VERSION, dev_init, dev_fini, TOPO_BLTIN_TYPE_TREE }, + { "fmd", FMD_VERSION, fmd_init, fmd_fini, TOPO_BLTIN_TYPE_TREE }, + { "mem", MEM_VERSION, mem_init, mem_fini, TOPO_BLTIN_TYPE_TREE }, + { "pkg", PKG_VERSION, pkg_init, pkg_fini, TOPO_BLTIN_TYPE_TREE }, + { "svc", SVC_VERSION, svc_init, svc_fini, TOPO_BLTIN_TYPE_TREE }, + { "sw", SW_VERSION, sw_init, sw_fini, TOPO_BLTIN_TYPE_TREE }, + { "zfs", ZFS_VERSION, zfs_init, zfs_fini, TOPO_BLTIN_TYPE_TREE }, + { "mod", MOD_VERSION, mod_init, mod_fini, TOPO_BLTIN_TYPE_TREE }, + /* hc must go last */ + { "hc", HC_VERSION, hc_init, hc_fini, TOPO_BLTIN_TYPE_TREE }, + { NULL, 0, NULL, NULL, 0 } }; static int @@ -105,6 +107,7 @@ topo_builtin_create(topo_hdl_t *thp, const char *rootdir) topo_mod_t *mod; ttree_t *tp; tnode_t *rnode; + topo_digraph_t *tdg; /* * Create a scheme-specific topo tree for all builtins @@ -118,35 +121,49 @@ topo_builtin_create(topo_hdl_t *thp, const char *rootdir) &topo_bltin_ops, bp->bltin_version)) == NULL) { topo_dprintf(thp, TOPO_DBG_ERR, "unable to create scheme " - "tree for %s:%s\n", bp->bltin_name, + "topology for %s:%s\n", bp->bltin_name, topo_hdl_errmsg(thp)); return (-1); } - if ((tp = topo_tree_create(thp, mod, bp->bltin_name)) - == NULL) { - topo_dprintf(thp, TOPO_DBG_ERR, - "unable to create scheme " - "tree for %s:%s\n", bp->bltin_name, - topo_hdl_errmsg(thp)); + switch (bp->bltin_type) { + case TOPO_BLTIN_TYPE_TREE: + if ((tp = topo_tree_create(thp, mod, bp->bltin_name)) + == NULL) { + topo_dprintf(thp, TOPO_DBG_ERR, "unable to " + "create scheme tree for %s:%s\n", + bp->bltin_name, topo_hdl_errmsg(thp)); + return (-1); + } + topo_list_append(&thp->th_trees, tp); + + rnode = tp->tt_root; + break; + case TOPO_BLTIN_TYPE_DIGRAPH: + if ((tdg = topo_digraph_new(thp, mod, bp->bltin_name)) + == NULL) { + topo_dprintf(thp, TOPO_DBG_ERR, "unable to " + "create scheme digraph for %s:%s\n", + bp->bltin_name, topo_hdl_errmsg(thp)); + return (-1); + } + topo_list_append(&thp->th_digraphs, tdg); + + rnode = tdg->tdg_rootnode; + break; + default: + topo_dprintf(thp, TOPO_DBG_ERR, "unexpected topology " + "type: %u", bp->bltin_type); return (-1); } - topo_list_append(&thp->th_trees, tp); - - /* - * Call the enumerator on the root of the tree, with the - * scheme name as the name to enumerate. This will - * establish methods on the root node. - */ - rnode = tp->tt_root; - if (topo_mod_enumerate(mod, rnode, mod->tm_name, rnode->tn_name, - rnode->tn_instance, rnode->tn_instance, NULL) < 0) { + if (topo_mod_enumerate(mod, rnode, mod->tm_name, + rnode->tn_name, rnode->tn_instance, rnode->tn_instance, + NULL) < 0) { /* - * If we see a failure, note it in the handle and - * drive on + * If we see a failure, note it in the handle and drive + * on */ (void) topo_hdl_seterrno(thp, ETOPO_ENUM_PARTIAL); } - } return (0); diff --git a/usr/src/lib/fm/topo/libtopo/common/topo_builtin.h b/usr/src/lib/fm/topo/libtopo/common/topo_builtin.h index 8d34f643d0..7a387c9c51 100644 --- a/usr/src/lib/fm/topo/libtopo/common/topo_builtin.h +++ b/usr/src/lib/fm/topo/libtopo/common/topo_builtin.h @@ -23,19 +23,23 @@ * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ +/* + * Copyright 2020 Joyent, Inc. + */ #ifndef _TOPO_BUILTIN_H #define _TOPO_BUILTIN_H -#pragma ident "%Z%%M% %I% %E% SMI" - #ifdef __cplusplus extern "C" { #endif #include <topo_tree.h> #include <topo_module.h> +#include <topo_digraph.h> +#define TOPO_BLTIN_TYPE_TREE 1 +#define TOPO_BLTIN_TYPE_DIGRAPH 2 /* * topo_builtin.h * @@ -50,6 +54,7 @@ typedef struct topo_builtin { topo_version_t bltin_version; int (*bltin_init)(topo_mod_t *, topo_version_t version); void (*bltin_fini)(topo_mod_t *); + uint_t bltin_type; } topo_builtin_t; extern int topo_builtin_create(topo_hdl_t *, const char *); diff --git a/usr/src/lib/fm/topo/libtopo/common/topo_digraph.c b/usr/src/lib/fm/topo/libtopo/common/topo_digraph.c new file mode 100644 index 0000000000..8f66794ff5 --- /dev/null +++ b/usr/src/lib/fm/topo/libtopo/common/topo_digraph.c @@ -0,0 +1,1035 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2020 Joyent, Inc. + */ + +/* + * This file implements a set of APIs for creating, destroying and traversing + * directed-graph (digraph) topologies. The API is split into categories: + * client API and module API. + * + * The client API can be used by libtopo consumers for traversing an existing + * digraph topology. The module API can be used by topo plugins to create or + * destroy a digraph topology. + * + * client API + * ---------- + * topo_digraph_get + * topo_vertex_iter + * topo_vertex_node + * topo_edge_iter + * topo_digraph_paths + * topo_path_destroy + * + * module API + * ---------- + * topo_digraph_new + * topo_digraph_destroy + * topo_vertex_new + * topo_vertex_destroy + * topo_edge_new + * + * A digraph is represented in-core by a topo_digraph_t structure which + * maintains an adjacency list of vertices (see topo_digraph.h). Vertices + * are represented by topo_vertex_t structures which are essentially thin + * wrappers around topo node (tnode_t) structures. In addition to holding + * a pointer to the underlyng topo node, the topo_vertex_t maintains a list + * of incoming and outgoing edges and a pointer to the next and previous + * vertices in digraph's adjecency list. + * + * Locking + * ------- + * The module APIs should only be used during snapshot creation, which is + * single-threaded, or as the result of a call to topo_snap_release() or + * topo_close(). While libtopo does prevent concurrent calls to + * topo_snap_release() and topo_close() via the topo_hdl_t lock, there is no + * mechanism currently that prevents the situation where one or more threads + * were to access the snapshot (e.g. they've got a cached tnode_t ptr or are + * walking the snapshot) while another thread is calling topo_snap_release() + * or topo_close(). The same is true for tree topologies. It is up to the + * library consumer to provide their own synchronization or reference counting + * mechanism for that case. For example, fmd implements a reference counting + * mechanism around topo snapshots so that individual fmd modules can safely + * cache pointers to a given topo snapshot. + * + * None of the client APIs modify the state of the graph structure once + * the snapshot is created. The exception is the state of the underlyng topo + * nodes for each vertex, who's properties may change as a result of the + * following: + * + * 1) topo_prop_get operations for properties that are backed by topo methods + * 2) topo_prop_set operations. + * + * For both of the above situations, synchronization is enforced by the per-node + * locks. Thus there a no locks used for synchronizing access to + * the topo_digraph_t or topo_vertex_t structures. + * + * path scheme FMRIs + * ----------------- + * For digraph topologies it is useful to be able to treat paths between + * vertices as resources and thus have a way to represent a unique path + * between those vertices. The path FMRI scheme is used for this purpose and + * has the following form: + * + * path://scheme=<scheme>/<nodename>=<instance>/... + * + * The path FMRI for a path from one vertex to another vertex is represented by + * a sequence of nodename/instance pairs where each pair represents a vertex on + * the path between the two vertices. The first nodename/instance pair + * represents the "from" vertex and the last nodename/instance pair represents + * the "to" vertex. + * + * For example, the path FMRI to represent a path from an initiator to a + * target in a SAS scheme digraph might look like this: + * + * path://scheme=sas/initiator=5003048023567a00/port=5003048023567a00/ + * port=500304801861347f/expander=500304801861347f/port=500304801861347f/ + * port=5000c500adc881d5/target=5000c500adc881d4 + * + * This file implements NVL2STR and STR2NVL methods for path-scheme FMRIs. + */ + +#include <libtopo.h> +#include <sys/fm/protocol.h> + +#include <topo_digraph.h> +#include <topo_method.h> + +#define __STDC_FORMAT_MACROS +#include <inttypes.h> + + +extern int path_fmri_str2nvl(topo_mod_t *, tnode_t *, topo_version_t, + nvlist_t *, nvlist_t **); +extern int path_fmri_nvl2str(topo_mod_t *, tnode_t *, topo_version_t, + nvlist_t *, nvlist_t **); + +static const topo_method_t digraph_root_methods[] = { + { TOPO_METH_PATH_STR2NVL, TOPO_METH_STR2NVL_DESC, + TOPO_METH_STR2NVL_VERSION, TOPO_STABILITY_INTERNAL, + path_fmri_str2nvl }, + { TOPO_METH_PATH_NVL2STR, TOPO_METH_NVL2STR_DESC, + TOPO_METH_NVL2STR_VERSION, TOPO_STABILITY_INTERNAL, + path_fmri_nvl2str }, + { NULL } +}; + +/* + * On success, returns a pointer to the digraph for the specified FMRI scheme. + * On failure, returns NULL. + */ +topo_digraph_t * +topo_digraph_get(topo_hdl_t *thp, const char *scheme) +{ + topo_digraph_t *tdg; + + for (tdg = topo_list_next(&thp->th_digraphs); tdg != NULL; + tdg = topo_list_next(tdg)) { + if (strcmp(scheme, tdg->tdg_scheme) == 0) + return (tdg); + } + return (NULL); +} + +static topo_digraph_t * +find_digraph(topo_mod_t *mod) +{ + return (topo_digraph_get(mod->tm_hdl, mod->tm_info->tmi_scheme)); +} + +/* + * On successs, allocates a new topo_digraph_t structure for the requested + * scheme. The caller is responsible for free all memory allocated for this + * structure when done via a call to topo_digraph_destroy(). + * + * On failure, this function returns NULL and sets topo errno. + */ +topo_digraph_t * +topo_digraph_new(topo_hdl_t *thp, topo_mod_t *mod, const char *scheme) +{ + topo_digraph_t *tdg; + tnode_t *tn = NULL; + + if ((tdg = topo_mod_zalloc(mod, sizeof (topo_digraph_t))) == NULL) { + (void) topo_hdl_seterrno(thp, ETOPO_NOMEM); + return (NULL); + } + + tdg->tdg_mod = mod; + + if ((tdg->tdg_scheme = topo_mod_strdup(mod, scheme)) == NULL) { + (void) topo_hdl_seterrno(thp, ETOPO_NOMEM); + goto err; + } + + /* + * For digraph topologies, the "root" node, which gets passed in to + * the scheme module's enum method is not part of the actual graph + * structure per-se. + * Its purpose is simply to provide a place on which to register the + * scheme-specific methods. Client code then invokes these methods via + * the topo_fmri_* interfaces. + */ + if ((tn = topo_mod_zalloc(mod, sizeof (tnode_t))) == NULL) + goto err; + + /* + * Adding the TOPO_NODE_ROOT state to the node has the effect of + * preventing topo_node_destroy() from trying to clean up the parent + * node's node hash, which is only necessary in tree topologies. + */ + tn->tn_state = TOPO_NODE_ROOT | TOPO_NODE_INIT; + tn->tn_name = (char *)scheme; + tn->tn_instance = 0; + tn->tn_enum = mod; + tn->tn_hdl = thp; + topo_node_hold(tn); + + tdg->tdg_rootnode = tn; + if (topo_method_register(mod, tn, digraph_root_methods) != 0) { + topo_mod_dprintf(mod, "failed to register digraph root " + "methods"); + /* errno set */ + return (NULL); + } + + /* This is released during topo_digraph_destroy() */ + topo_mod_hold(mod); + + return (tdg); +err: + topo_mod_free(mod, tdg, sizeof (topo_digraph_t)); + return (NULL); +} + +/* + * Deallocates all memory associated with the specified topo_digraph_t. + * + * This only frees the memory allocated during topo_digraph_new(). To free the + * actual graph vertices one should call topo_snap_destroy() prior to calling + * topo_digraph_destroy(). + * + * Calling topo_close() will also result in a call to topo_snap_destroy() and + * topo_digraph_dstroy() for all digraphs associated with the library handle. + * + * This function is a NOP if NULL is passed in. + */ +void +topo_digraph_destroy(topo_digraph_t *tdg) +{ + topo_mod_t *mod; + + if (tdg == NULL) + return; + + mod = tdg->tdg_mod; + topo_method_unregister_all(mod, tdg->tdg_rootnode); + topo_mod_strfree(mod, (char *)tdg->tdg_scheme); + topo_mod_free(mod, tdg->tdg_rootnode, sizeof (tnode_t)); + topo_mod_free(mod, tdg, sizeof (topo_digraph_t)); + topo_mod_rele(mod); +} + +/* + * This function creates a new vertex and adds it to the digraph associated + * with the module. + * + * name: name of the vertex + * instance: instance number of the vertex + * + * On success, it returns a pointer to the allocated topo_vertex_t associated + * with the new vertex. The caller is responsible for free-ing this structure + * via a call to topo_vertex_destroy(). + * + * On failures, this function returns NULL and sets topo_mod_errno. + */ +topo_vertex_t * +topo_vertex_new(topo_mod_t *mod, const char *name, topo_instance_t inst) +{ + tnode_t *tn = NULL; + topo_vertex_t *vtx = NULL; + topo_digraph_t *tdg; + + topo_mod_dprintf(mod, "Creating vertex %s=%" PRIx64 "", name, inst); + if ((tdg = find_digraph(mod)) == NULL) { + topo_mod_dprintf(mod, "%s faild: no existing digraph for FMRI " + " scheme %s", __func__, mod->tm_info->tmi_scheme); + return (NULL); + } + if ((vtx = topo_mod_zalloc(mod, sizeof (topo_vertex_t))) == NULL || + (tn = topo_mod_zalloc(mod, sizeof (tnode_t))) == NULL) { + (void) topo_mod_seterrno(mod, EMOD_NOMEM); + goto err; + } + if ((tn->tn_name = topo_mod_strdup(mod, name)) == NULL) { + (void) topo_mod_seterrno(mod, EMOD_NOMEM); + goto err; + } + tn->tn_enum = mod; + tn->tn_hdl = mod->tm_hdl; + tn->tn_vtx = vtx; + tn->tn_instance = inst; + /* + * Adding the TOPO_NODE_ROOT state to the node has the effect of + * preventing topo_node_destroy() from trying to clean up the parent + * node's node hash, which is only necessary in tree topologies. + */ + tn->tn_state = TOPO_NODE_ROOT | TOPO_NODE_BOUND; + vtx->tvt_node = tn; + topo_node_hold(tn); + + /* Bump the refcnt on the module that's creating this vertex. */ + topo_mod_hold(mod); + + if (tdg->tdg_nvertices == UINT32_MAX) { + topo_mod_dprintf(mod, "Max vertices reached!"); + (void) topo_mod_seterrno(mod, EMOD_DIGRAPH_MAXSZ); + topo_mod_rele(mod); + goto err; + } + tdg->tdg_nvertices++; + topo_list_append(&tdg->tdg_vertices, vtx); + + return (vtx); +err: + topo_mod_dprintf(mod, "failed to add create vertex %s=%" PRIx64 "(%s)", + name, inst, topo_strerror(topo_mod_errno(mod))); + if (tn != NULL) { + topo_mod_strfree(mod, tn->tn_name); + topo_mod_free(mod, tn, sizeof (tnode_t)); + } + if (vtx != NULL) + topo_mod_free(mod, vtx, sizeof (topo_vertex_t)); + + return (NULL); +} + +/* + * Returns the underlying tnode_t structure for the specified vertex. + */ +tnode_t * +topo_vertex_node(topo_vertex_t *vtx) +{ + return (vtx->tvt_node); +} + +/* + * Convenience interface for deallocating a topo_vertex_t + * This function is a NOP if NULL is passed in. + */ +void +topo_vertex_destroy(topo_mod_t *mod, topo_vertex_t *vtx) +{ + topo_edge_t *edge; + + if (vtx == NULL) + return; + + topo_node_unbind(vtx->tvt_node); + + edge = topo_list_next(&vtx->tvt_incoming); + while (edge != NULL) { + topo_edge_t *tmp = edge; + + edge = topo_list_next(edge); + topo_mod_free(mod, tmp, sizeof (topo_edge_t)); + } + + edge = topo_list_next(&vtx->tvt_outgoing); + while (edge != NULL) { + topo_edge_t *tmp = edge; + + edge = topo_list_next(edge); + topo_mod_free(mod, tmp, sizeof (topo_edge_t)); + } + + topo_mod_free(mod, vtx, sizeof (topo_vertex_t)); +} + +/* + * This function can be used to iterate over all of the vertices in the + * specified digraph. The specified callback function is invoked for each + * vertices. Callback function should return the standard topo walker + * (TOPO_WALK_*) return values. + * + * On success, this function returns 0. + * + * On failure this function returns -1. + */ +int +topo_vertex_iter(topo_hdl_t *thp, topo_digraph_t *tdg, + int (*func)(topo_hdl_t *, topo_vertex_t *, boolean_t, void *), void *arg) +{ + uint_t n = 0; + + for (topo_vertex_t *vtx = topo_list_next(&tdg->tdg_vertices); + vtx != NULL; vtx = topo_list_next(vtx), n++) { + int ret; + boolean_t last_vtx = B_FALSE; + + if (n == (tdg->tdg_nvertices - 1)) + last_vtx = B_TRUE; + + ret = func(thp, vtx, last_vtx, arg); + + switch (ret) { + case TOPO_WALK_NEXT: + continue; + case TOPO_WALK_TERMINATE: + goto out; + case TOPO_WALK_ERR: + default: + return (-1); + } + } +out: + return (0); +} + +/* + * Add a new outgoing edge the vertex "from" to the vertex "to". + * + * On success, this functions returns 0. + * + * On failure, this function returns -1. + */ +int +topo_edge_new(topo_mod_t *mod, topo_vertex_t *from, topo_vertex_t *to) +{ + topo_digraph_t *tdg; + topo_edge_t *e_from = NULL, *e_to = NULL; + + topo_mod_dprintf(mod, "Adding edge from vertex %s=%" PRIx64 " to " + "%s=%" PRIx64"", topo_node_name(from->tvt_node), + topo_node_instance(from->tvt_node), + topo_node_name(to->tvt_node), topo_node_instance(to->tvt_node)); + + if ((tdg = find_digraph(mod)) == NULL) { + topo_mod_dprintf(mod, "Digraph lookup failed"); + return (topo_mod_seterrno(mod, EMOD_UNKNOWN)); + } + if (from->tvt_noutgoing == UINT32_MAX || + to->tvt_nincoming == UINT32_MAX || + tdg->tdg_nedges == UINT32_MAX) { + topo_mod_dprintf(mod, "Max edges reached!"); + return (topo_mod_seterrno(mod, EMOD_DIGRAPH_MAXSZ)); + + } + if ((e_from = topo_mod_zalloc(mod, sizeof (topo_edge_t))) == NULL || + (e_to = topo_mod_zalloc(mod, sizeof (topo_edge_t))) == NULL) { + topo_mod_free(mod, e_from, sizeof (topo_edge_t)); + topo_mod_free(mod, e_to, sizeof (topo_edge_t)); + return (topo_mod_seterrno(mod, EMOD_NOMEM)); + } + e_from->tve_vertex = from; + e_to->tve_vertex = to; + + topo_list_append(&from->tvt_outgoing, e_to); + from->tvt_noutgoing++; + topo_list_append(&to->tvt_incoming, e_from); + to->tvt_nincoming++; + tdg->tdg_nedges++; + + return (0); +} + +/* + * This function can be used to iterate over all of the outgoing edges in + * for specified vertex. The specified callback function is invoked for each + * edge. Callback function should return the standard topo walker + * (TOPO_WALK_*) return values. + * + * On success, this function returns 0. + * + * On failure this function returns -1. + */ +int +topo_edge_iter(topo_hdl_t *thp, topo_vertex_t *vtx, + int (*func)(topo_hdl_t *, topo_edge_t *, boolean_t, void *), void *arg) +{ + uint_t n = 0; + + for (topo_edge_t *edge = topo_list_next(&vtx->tvt_outgoing); + edge != NULL; edge = topo_list_next(edge), n++) { + int ret; + boolean_t last_edge = B_FALSE; + + if (n == (vtx->tvt_noutgoing - 1)) + last_edge = B_TRUE; + + ret = func(thp, edge, last_edge, arg); + + switch (ret) { + case TOPO_WALK_NEXT: + continue; + case TOPO_WALK_TERMINATE: + break; + case TOPO_WALK_ERR: + default: + return (-1); + } + } + return (0); +} + +/* + * Convenience interface for deallocating a topo_path_t + * This function is a NOP if NULL is passed in. + */ +void +topo_path_destroy(topo_hdl_t *thp, topo_path_t *path) +{ + topo_path_component_t *pathcomp; + + if (path == NULL) + return; + + topo_hdl_strfree(thp, (char *)path->tsp_fmristr); + nvlist_free(path->tsp_fmri); + + pathcomp = topo_list_next(&path->tsp_components); + while (pathcomp != NULL) { + topo_path_component_t *tmp = pathcomp; + + pathcomp = topo_list_next(pathcomp); + topo_hdl_free(thp, tmp, sizeof (topo_path_component_t)); + } + + topo_hdl_free(thp, path, sizeof (topo_path_t)); +} + +/* + * This just wraps topo_path_t so that visit_vertex() can build a linked list + * of paths. + */ +struct digraph_path { + topo_list_t dgp_link; + topo_path_t *dgp_path; +}; + +/* + * This is a callback function for the vertex iteration that gets initiated by + * topo_digraph_paths(). + * + * This is used to implement a depth-first search for all paths that lead to + * the vertex pointed to by the "to" parameter. As we walk the graph we + * maintain a linked list of the components (vertices) in the in-progress path + * as well as the string form of the current path being walked. Whenever we + * eoncounter the "to" vertex, we save the current path to the all_paths list + * (which keeps track of all the paths we've found to the "to" vertex) and + * increment npaths (which keeps track of the number of paths we've found to + * the "to" vertex). + */ +static int +visit_vertex(topo_hdl_t *thp, topo_vertex_t *vtx, topo_vertex_t *to, + topo_list_t *all_paths, const char *curr_path, + topo_list_t *curr_path_comps, uint_t *npaths) +{ + struct digraph_path *pathnode = NULL; + topo_path_t *path = NULL; + topo_path_component_t *pathcomp = NULL; + nvlist_t *fmri = NULL; + char *pathstr; + int err; + + if (asprintf(&pathstr, "%s/%s=%" PRIx64"", + curr_path, + topo_node_name(vtx->tvt_node), + topo_node_instance(vtx->tvt_node)) < 0) { + return (topo_hdl_seterrno(thp, ETOPO_NOMEM)); + } + + /* + * Check if this vertex is in the list of vertices in the + * curr_path_comps list. If it is, then we've encountered a cycle + * and need to turn back. + */ + for (topo_path_component_t *pc = topo_list_next(curr_path_comps); + pc != NULL; pc = topo_list_next(pc)) { + if (pc->tspc_vertex == vtx) { + topo_dprintf(thp, TOPO_DBG_WALK, "Cycle detected: %s", + pathstr); + free(pathstr); + return (0); + } + } + + if ((pathcomp = topo_hdl_zalloc(thp, sizeof (topo_path_component_t))) + == NULL) { + (void) topo_hdl_seterrno(thp, ETOPO_NOMEM); + goto err; + } + pathcomp->tspc_vertex = vtx; + topo_list_append(curr_path_comps, pathcomp); + + if (vtx == to) { + (*npaths)++; + pathnode = topo_hdl_zalloc(thp, sizeof (struct digraph_path)); + + if ((path = topo_hdl_zalloc(thp, sizeof (topo_path_t))) == + NULL || + (path->tsp_fmristr = topo_hdl_strdup(thp, pathstr)) == + NULL) { + (void) topo_hdl_seterrno(thp, ETOPO_NOMEM); + goto err; + } + + if (topo_list_deepcopy(thp, &path->tsp_components, + curr_path_comps, sizeof (topo_path_component_t)) != 0) { + (void) topo_hdl_seterrno(thp, ETOPO_NOMEM); + } + if (topo_fmri_str2nvl(thp, pathstr, &fmri, &err) != 0) { + /* errno set */ + goto err; + } + path->tsp_fmri = fmri; + pathnode->dgp_path = path; + + topo_list_append(all_paths, pathnode); + free(pathstr); + topo_list_delete(curr_path_comps, pathcomp); + topo_hdl_free(thp, pathcomp, sizeof (topo_path_component_t)); + return (0); + } + + for (topo_edge_t *edge = topo_list_next(&vtx->tvt_outgoing); + edge != NULL; edge = topo_list_next(edge)) { + + if (visit_vertex(thp, edge->tve_vertex, to, all_paths, pathstr, + curr_path_comps, npaths) != 0) + goto err; + } + + free(pathstr); + topo_list_delete(curr_path_comps, pathcomp); + topo_hdl_free(thp, pathcomp, sizeof (topo_path_component_t)); + return (0); + +err: + free(pathstr); + topo_hdl_free(thp, pathnode, sizeof (struct digraph_path)); + topo_path_destroy(thp, path); + return (-1); +} + +/* + * On success, 0 is returns and the "paths" parameter is populated with an + * array of topo_path_t structs representing all paths from the "from" vertex + * to the "to" vertex. The caller is responsible for freeing this array. The + * "npaths" parameter will be populated with the number of paths found or 0 if + * no paths were found. + * + * On error, -1 is returned. + */ +int +topo_digraph_paths(topo_hdl_t *thp, topo_digraph_t *tdg, topo_vertex_t *from, + topo_vertex_t *to, topo_path_t ***paths, uint_t *npaths) +{ + topo_list_t all_paths = { 0 }; + char *curr_path; + topo_path_component_t *pathcomp = NULL; + topo_list_t curr_path_comps = { 0 }; + struct digraph_path *path; + uint_t i; + int ret; + + if (asprintf(&curr_path, "%s://%s=%s/%s=%" PRIx64"", + FM_FMRI_SCHEME_PATH, FM_FMRI_SCHEME, tdg->tdg_scheme, + topo_node_name(from->tvt_node), + topo_node_instance(from->tvt_node)) < 1) { + return (topo_hdl_seterrno(thp, ETOPO_NOMEM)); + } + + if ((pathcomp = topo_hdl_zalloc(thp, sizeof (topo_path_component_t))) + == NULL) { + (void) topo_hdl_seterrno(thp, ETOPO_NOMEM); + goto err; + } + pathcomp->tspc_vertex = from; + topo_list_append(&curr_path_comps, pathcomp); + + *npaths = 0; + for (topo_edge_t *edge = topo_list_next(&from->tvt_outgoing); + edge != NULL; edge = topo_list_next(edge)) { + + ret = visit_vertex(thp, edge->tve_vertex, to, &all_paths, + curr_path, &curr_path_comps, npaths); + if (ret != 0) { + /* errno set */ + goto err; + } + } + topo_hdl_free(thp, pathcomp, sizeof (topo_path_component_t)); + + /* + * No paths were found between the "from" and "to" vertices, so + * we're done here. + */ + if (*npaths == 0) { + free(curr_path); + return (0); + } + + *paths = topo_hdl_zalloc(thp, (*npaths) * sizeof (topo_path_t *)); + if (*paths == NULL) { + (void) topo_hdl_seterrno(thp, ETOPO_NOMEM); + goto err; + } + + for (i = 0, path = topo_list_next(&all_paths); path != NULL; + i++, path = topo_list_next(path)) { + + *((*paths) + i) = path->dgp_path; + } + + path = topo_list_next(&all_paths); + while (path != NULL) { + struct digraph_path *tmp = path; + + path = topo_list_next(path); + topo_hdl_free(thp, tmp, sizeof (struct digraph_path)); + } + free(curr_path); + return (0); + +err: + free(curr_path); + path = topo_list_next(&all_paths); + while (path != NULL) { + struct digraph_path *tmp = path; + + path = topo_list_next(path); + topo_hdl_free(thp, tmp, sizeof (struct digraph_path)); + } + + topo_dprintf(thp, TOPO_DBG_ERR, "%s: failed (%s)", __func__, + topo_hdl_errmsg(thp)); + return (-1); +} + +/* Helper function for path_fmri_nvl2str() */ +static ssize_t +fmri_bufsz(nvlist_t *nvl) +{ + char *dg_scheme = NULL; + nvlist_t **hops, *auth; + uint_t nhops; + ssize_t bufsz = 1; + int ret; + + if (nvlist_lookup_nvlist(nvl, FM_FMRI_AUTHORITY, &auth) != 0 || + nvlist_lookup_string(auth, FM_FMRI_PATH_DIGRAPH_SCHEME, + &dg_scheme) != 0) { + return (0); + } + + if ((ret = snprintf(NULL, 0, "%s://%s=%s", FM_FMRI_SCHEME_PATH, + FM_FMRI_SCHEME, dg_scheme)) < 0) { + return (-1); + } + bufsz += ret; + + if (nvlist_lookup_nvlist_array(nvl, FM_FMRI_PATH, &hops, &nhops) != + 0) { + return (0); + } + + for (uint_t i = 0; i < nhops; i++) { + char *name; + uint64_t inst; + + if (nvlist_lookup_string(hops[i], FM_FMRI_PATH_NAME, &name) != + 0 || + nvlist_lookup_uint64(hops[i], FM_FMRI_PATH_INST, &inst) != + 0) { + return (0); + } + if ((ret = snprintf(NULL, 0, "/%s=%" PRIx64 "", name, inst)) < + 0) { + return (-1); + } + bufsz += ret; + } + return (bufsz); +} + +int +path_fmri_nvl2str(topo_mod_t *mod, tnode_t *node, topo_version_t version, + nvlist_t *in, nvlist_t **out) +{ + uint8_t scheme_vers; + nvlist_t *outnvl; + nvlist_t **paths, *auth; + uint_t nelem; + ssize_t bufsz, end = 0; + char *buf, *dg_scheme; + int ret; + + if (version > TOPO_METH_NVL2STR_VERSION) + return (topo_mod_seterrno(mod, EMOD_VER_NEW)); + + if (nvlist_lookup_uint8(in, FM_FMRI_PATH_VERSION, &scheme_vers) != 0) { + return (topo_mod_seterrno(mod, EMOD_NOMEM)); + } + + if (scheme_vers != FM_PATH_SCHEME_VERSION) { + return (topo_mod_seterrno(mod, EMOD_FMRI_NVL)); + } + + /* + * Get size of buffer needed to hold the string representation of the + * FMRI. + */ + bufsz = fmri_bufsz(in); + if (bufsz == 0) { + return (topo_mod_seterrno(mod, EMOD_FMRI_MALFORM)); + } else if (bufsz < 1) { + return (topo_mod_seterrno(mod, EMOD_UNKNOWN)); + } + + if ((buf = topo_mod_zalloc(mod, bufsz)) == NULL) { + return (topo_mod_seterrno(mod, EMOD_NOMEM)); + } + + /* + * We've already successfully done these nvlist lookups in fmri_bufsz() + * so we don't worry about checking retvals this time around. + */ + (void) nvlist_lookup_nvlist(in, FM_FMRI_AUTHORITY, &auth); + (void) nvlist_lookup_string(auth, FM_FMRI_PATH_DIGRAPH_SCHEME, + &dg_scheme); + (void) nvlist_lookup_nvlist_array(in, FM_FMRI_PATH, &paths, + &nelem); + if ((ret = snprintf(buf, bufsz, "%s://%s=%s", FM_FMRI_SCHEME_PATH, + FM_FMRI_SCHEME, dg_scheme)) < 0) { + topo_mod_free(mod, buf, bufsz); + return (topo_mod_seterrno(mod, EMOD_UNKNOWN)); + } + end += ret; + + for (uint_t i = 0; i < nelem; i++) { + char *pathname; + uint64_t pathinst; + + (void) nvlist_lookup_string(paths[i], FM_FMRI_PATH_NAME, + &pathname); + (void) nvlist_lookup_uint64(paths[i], FM_FMRI_PATH_INST, + &pathinst); + + if ((ret = snprintf(buf + end, (bufsz - end), "/%s=%" PRIx64 "", + pathname, pathinst)) < 0) { + topo_mod_free(mod, buf, bufsz); + return (topo_mod_seterrno(mod, EMOD_UNKNOWN)); + } + end += ret; + } + + if (topo_mod_nvalloc(mod, &outnvl, NV_UNIQUE_NAME) != 0) { + topo_mod_free(mod, buf, bufsz); + return (topo_mod_seterrno(mod, EMOD_FMRI_NVL)); + } + if (nvlist_add_string(outnvl, "fmri-string", buf) != 0) { + nvlist_free(outnvl); + topo_mod_free(mod, buf, bufsz); + return (topo_mod_seterrno(mod, EMOD_FMRI_NVL)); + } + topo_mod_free(mod, buf, bufsz); + *out = outnvl; + + return (0); +} + +int +path_fmri_str2nvl(topo_mod_t *mod, tnode_t *node, topo_version_t version, + nvlist_t *in, nvlist_t **out) +{ + char *fmristr, *tmp = NULL, *lastpair; + char *dg_scheme, *dg_scheme_end, *pathname, *path_start; + nvlist_t *fmri = NULL, *auth = NULL, **path = NULL; + uint_t npairs = 0, i = 0, fmrilen, path_offset; + + if (version > TOPO_METH_STR2NVL_VERSION) + return (topo_mod_seterrno(mod, EMOD_VER_NEW)); + + if (nvlist_lookup_string(in, "fmri-string", &fmristr) != 0) + return (topo_mod_seterrno(mod, EMOD_METHOD_INVAL)); + + if (strncmp(fmristr, "path://", 7) != 0) + return (topo_mod_seterrno(mod, EMOD_FMRI_MALFORM)); + + if (topo_mod_nvalloc(mod, &fmri, NV_UNIQUE_NAME) != 0) { + /* errno set */ + return (-1); + } + if (nvlist_add_string(fmri, FM_FMRI_SCHEME, + FM_FMRI_SCHEME_PATH) != 0 || + nvlist_add_uint8(fmri, FM_FMRI_PATH_VERSION, + FM_PATH_SCHEME_VERSION) != 0) { + (void) topo_mod_seterrno(mod, EMOD_NOMEM); + goto err; + } + + /* + * We need to make a copy of the fmri string because strtok will + * modify it. We can't use topo_mod_strdup/strfree because + * topo_mod_strfree will end up leaking part of the string because + * of the NUL chars that strtok inserts - which will cause + * topo_mod_strfree to miscalculate the length of the string. So we + * keep track of the length of the original string and use + * topo_mod_alloc/topo_mod_free. + */ + fmrilen = strlen(fmristr) + 1; + if ((tmp = topo_mod_alloc(mod, fmrilen)) == NULL) { + (void) topo_mod_seterrno(mod, EMOD_NOMEM); + goto err; + } + bcopy(fmristr, tmp, fmrilen); + + /* + * Find the offset of the "/" after the authority portion of the FMRI. + */ + if ((path_start = strchr(tmp + 7, '/')) == NULL) { + (void) topo_mod_seterrno(mod, EMOD_FMRI_MALFORM); + goto err; + } + + path_offset = path_start - tmp; + pathname = fmristr + path_offset + 1; + + /* + * Count the number of "=" chars after the "path:///" portion of the + * FMRI to determine how big the path array needs to be. + */ + (void) strtok_r(tmp + path_offset, "=", &lastpair); + while (strtok_r(NULL, "=", &lastpair) != NULL) + npairs++; + + if (npairs == 0) { + (void) topo_mod_seterrno(mod, EMOD_FMRI_MALFORM); + goto err; + } + + if ((path = topo_mod_zalloc(mod, npairs * sizeof (nvlist_t *))) == + NULL) { + (void) topo_mod_seterrno(mod, EMOD_NOMEM); + goto err; + } + + /* + * Build the auth nvlist. There is only one nvpair in the path FMRI + * scheme, which is the scheme of the underlying digraph. + */ + if (topo_mod_nvalloc(mod, &auth, NV_UNIQUE_NAME) != 0) { + (void) topo_mod_seterrno(mod, EMOD_NOMEM); + goto err; + } + + if ((dg_scheme = strchr(tmp + 7, '=')) == NULL || + dg_scheme > path_start) { + (void) topo_mod_seterrno(mod, EMOD_FMRI_MALFORM); + goto err; + } + dg_scheme_end = tmp + path_offset; + *dg_scheme_end = '\0'; + + if (nvlist_add_string(auth, FM_FMRI_PATH_DIGRAPH_SCHEME, dg_scheme) != + 0 || + nvlist_add_nvlist(fmri, FM_FMRI_AUTHORITY, auth) != 0) { + (void) topo_mod_seterrno(mod, EMOD_NOMEM); + goto err; + } + + while (i < npairs) { + nvlist_t *pathcomp; + uint64_t pathinst; + char *end, *addrstr, *estr; + + if (topo_mod_nvalloc(mod, &pathcomp, NV_UNIQUE_NAME) != 0) { + (void) topo_mod_seterrno(mod, EMOD_NOMEM); + goto err; + } + if ((end = strchr(pathname, '=')) == NULL) { + (void) topo_mod_seterrno(mod, EMOD_FMRI_MALFORM); + goto err; + } + *end = '\0'; + addrstr = end + 1; + + /* + * If this is the last pair, then addrstr will already be + * nul-terminated. + */ + if (i < (npairs - 1)) { + if ((end = strchr(addrstr, '/')) == NULL) { + (void) topo_mod_seterrno(mod, + EMOD_FMRI_MALFORM); + goto err; + } + *end = '\0'; + } + + /* + * Convert addrstr to a uint64_t + */ + errno = 0; + pathinst = strtoull(addrstr, &estr, 16); + if (errno != 0 || *estr != '\0') { + (void) topo_mod_seterrno(mod, EMOD_FMRI_MALFORM); + goto err; + } + + /* + * Add both nvpairs to the nvlist and then add the nvlist to + * the path nvlist array. + */ + if (nvlist_add_string(pathcomp, FM_FMRI_PATH_NAME, pathname) != + 0 || + nvlist_add_uint64(pathcomp, FM_FMRI_PATH_INST, pathinst) != + 0) { + (void) topo_mod_seterrno(mod, EMOD_NOMEM); + goto err; + } + path[i++] = pathcomp; + pathname = end + 1; + } + if (nvlist_add_nvlist_array(fmri, FM_FMRI_PATH, path, npairs) != 0) { + (void) topo_mod_seterrno(mod, EMOD_NOMEM); + goto err; + } + *out = fmri; + + if (path != NULL) { + for (i = 0; i < npairs; i++) + nvlist_free(path[i]); + + topo_mod_free(mod, path, npairs * sizeof (nvlist_t *)); + } + nvlist_free(auth); + topo_mod_free(mod, tmp, fmrilen); + return (0); + +err: + topo_mod_dprintf(mod, "%s failed: %s", __func__, + topo_strerror(topo_mod_errno(mod))); + if (path != NULL) { + for (i = 0; i < npairs; i++) + nvlist_free(path[i]); + + topo_mod_free(mod, path, npairs * sizeof (nvlist_t *)); + } + nvlist_free(auth); + nvlist_free(fmri); + if (tmp != NULL) + topo_mod_free(mod, tmp, fmrilen); + return (-1); +} diff --git a/usr/src/lib/fm/topo/libtopo/common/topo_digraph.h b/usr/src/lib/fm/topo/libtopo/common/topo_digraph.h new file mode 100644 index 0000000000..91e9d6139e --- /dev/null +++ b/usr/src/lib/fm/topo/libtopo/common/topo_digraph.h @@ -0,0 +1,64 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2020 Joyent, Inc. + */ + +#ifndef _TOPO_DIGRAPH_H +#define _TOPO_DIGRAPH_H + +#include <fm/topo_mod.h> + +#include <topo_list.h> +#include <topo_prop.h> +#include <topo_method.h> +#include <topo_alloc.h> +#include <topo_error.h> +#include <topo_file.h> +#include <topo_module.h> +#include <topo_string.h> +#include <topo_subr.h> +#include <topo_tree.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct topo_digraph { + topo_list_t tdg_list; /* next/prev pointers */ + const char *tdg_scheme; /* FMRI scheme */ + topo_mod_t *tdg_mod; /* builtin enumerator mod */ + tnode_t *tdg_rootnode; /* see topo_digraph_new() */ + topo_list_t tdg_vertices; /* adjacency list */ + uint_t tdg_nvertices; /* total num of vertices */ + uint_t tdg_nedges; /* total num of edges */ +}; + +struct topo_vertex { + topo_list_t tvt_list; /* next/prev pointers */ + tnode_t *tvt_node; + topo_list_t tvt_incoming; + topo_list_t tvt_outgoing; + uint_t tvt_nincoming; /* total num incoming edges */ + uint_t tvt_noutgoing; /* total num outgoing edges */ +}; + +struct topo_edge { + topo_list_t tve_list; /* next/prev pointers */ + topo_vertex_t *tve_vertex; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* _TOPO_DIGRAPH_H */ diff --git a/usr/src/lib/fm/topo/libtopo/common/topo_digraph_xml.c b/usr/src/lib/fm/topo/libtopo/common/topo_digraph_xml.c new file mode 100644 index 0000000000..52ad4292fc --- /dev/null +++ b/usr/src/lib/fm/topo/libtopo/common/topo_digraph_xml.c @@ -0,0 +1,1502 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2020 Joyent, Inc. + */ + +/* + * This file implements the following two routines for serializing and + * deserializing digraphs to/from XML, respectively: + * + * topo_digraph_serialize() + * topo_digraph_deserialize() + * + * Refer to the following file for the XML schema being used: + * usr/src/lib/fm/topo/maps/common/digraph-topology.dtd.1 + */ +#include <time.h> +#include <sys/utsname.h> +#include <libxml/parser.h> +#include <libtopo.h> + +#include <topo_digraph.h> +#include <topo_digraph_xml.h> + +#define __STDC_FORMAT_MACROS +#include <inttypes.h> + +extern int xmlattr_to_int(topo_mod_t *, xmlNodePtr, const char *, uint64_t *); +static int serialize_nvpair(topo_hdl_t *thp, FILE *, uint_t, const char *, + nvpair_t *); + +static void +tdg_xml_nvstring(FILE *fp, uint_t pad, const char *name, const char *value) +{ + (void) fprintf(fp, "%*s<%s %s='%s' %s='%s' %s='%s' />\n", pad, "", + TDG_XML_NVPAIR, TDG_XML_NAME, name, TDG_XML_TYPE, TDG_XML_STRING, + TDG_XML_VALUE, value); +} + +static void +tdg_xml_nvlist(FILE *fp, uint_t pad, const char *name) +{ + (void) fprintf(fp, "%*s<%s %s='%s' %s='%s'>\n", pad, "", + TDG_XML_NVPAIR, TDG_XML_NAME, name, TDG_XML_TYPE, TDG_XML_NVLIST); +} + +static void +tdg_xml_nvuint8(FILE *fp, uint_t pad, const char *name, const uint8_t value) +{ + (void) fprintf(fp, "%*s<%s %s='%s' %s='%s' %s='%u' />\n", pad, "", + TDG_XML_NVPAIR, TDG_XML_NAME, name, TDG_XML_TYPE, TDG_XML_UINT8, + TDG_XML_VALUE, value); +} + +static void +tdg_xml_nvint8(FILE *fp, uint_t pad, const char *name, const uint8_t value) +{ + (void) fprintf(fp, "%*s<%s %s='%s' %s='%s' %s='%d' />\n", pad, "", + TDG_XML_NVPAIR, TDG_XML_NAME, name, TDG_XML_TYPE, TDG_XML_INT8, + TDG_XML_VALUE, value); +} + +static void +tdg_xml_nvuint16(FILE *fp, uint_t pad, const char *name, const uint8_t value) +{ + (void) fprintf(fp, "%*s<%s %s='%s' %s='%s' %s='%u' />\n", pad, "", + TDG_XML_NVPAIR, TDG_XML_NAME, name, TDG_XML_TYPE, TDG_XML_UINT16, + TDG_XML_VALUE, value); +} + +static void +tdg_xml_nvint16(FILE *fp, uint_t pad, const char *name, const uint8_t value) +{ + (void) fprintf(fp, "%*s<%s %s='%s' %s='%s' %s='%d' />\n", pad, "", + TDG_XML_NVPAIR, TDG_XML_NAME, name, TDG_XML_TYPE, TDG_XML_INT16, + TDG_XML_VALUE, value); +} + +static void +tdg_xml_nvuint32(FILE *fp, uint_t pad, const char *name, const uint32_t value) +{ + (void) fprintf(fp, "%*s<%s %s='%s' %s='%s' %s='%u' />\n", pad, "", + TDG_XML_NVPAIR, TDG_XML_NAME, name, TDG_XML_TYPE, TDG_XML_UINT32, + TDG_XML_VALUE, value); +} + +static void +tdg_xml_nvint32(FILE *fp, uint_t pad, const char *name, const int32_t value) +{ + (void) fprintf(fp, "%*s<%s %s='%s' %s='%s' %s='%d' />\n", pad, "", + TDG_XML_NVPAIR, TDG_XML_NAME, name, TDG_XML_TYPE, TDG_XML_UINT32, + TDG_XML_VALUE, value); +} + +static void +tdg_xml_nvuint64(FILE *fp, uint_t pad, const char *name, const uint64_t value) +{ + (void) fprintf(fp, "%*s<%s %s='%s' %s='%s' %s='0x%" PRIx64 "' />\n", + pad, "", TDG_XML_NVPAIR, TDG_XML_NAME, name, TDG_XML_TYPE, + TDG_XML_UINT64, TDG_XML_VALUE, value); +} + +static void +tdg_xml_nvint64(FILE *fp, uint_t pad, const char *name, const int64_t value) +{ + (void) fprintf(fp, "%*s<%s %s='%s' %s='%s' %s='%" PRIi64 "' />\n", pad, + "", TDG_XML_NVPAIR, TDG_XML_NAME, name, TDG_XML_TYPE, + TDG_XML_UINT64, TDG_XML_VALUE, value); +} + +static void +tdg_xml_nvdbl(FILE *fp, uint_t pad, const char *name, const double value) +{ + (void) fprintf(fp, "%*s<%s %s='%s' %s='%s' %s='%lf' />\n", pad, "" + TDG_XML_NVPAIR, TDG_XML_NAME, name, TDG_XML_TYPE, TDG_XML_UINT64, + TDG_XML_VALUE, value); +} + +static void +tdg_xml_nvarray(FILE *fp, uint_t pad, const char *name, const char *type) +{ + (void) fprintf(fp, "%*s<%s %s='%s' %s='%s'>\n", pad, "", + TDG_XML_NVPAIR, TDG_XML_NAME, name, TDG_XML_TYPE, type); +} + +static void +tdg_xml_nvint32arr(FILE *fp, uint_t pad, const char *name, int32_t *val, + uint_t nelems) +{ + (void) fprintf(fp, "%*s<%s %s='%s' %s='%s'>\n", pad, "", + TDG_XML_NVPAIR, TDG_XML_NAME, name, TDG_XML_TYPE, + TDG_XML_INT32_ARR); + + for (uint_t i = 0; i < nelems; i++) { + (void) fprintf(fp, "%*s<%s %s='%d' />\n", (pad + 2), "", + TDG_XML_NVPAIR, TDG_XML_VALUE, val[i]); + } + (void) fprintf(fp, "%*s</%s>\n", pad, "", TDG_XML_NVPAIR); +} + +static void +tdg_xml_nvuint32arr(FILE *fp, uint_t pad, const char *name, uint32_t *val, + uint_t nelems) +{ + (void) fprintf(fp, "%*s<%s %s='%s' %s='%s'>\n", pad, "", + TDG_XML_NVPAIR, TDG_XML_NAME, name, TDG_XML_TYPE, + TDG_XML_UINT32_ARR); + + for (uint_t i = 0; i < nelems; i++) { + (void) fprintf(fp, "%*s<%s %s='%d' />\n", (pad + 2), "", + TDG_XML_NVPAIR, TDG_XML_VALUE, val[i]); + } + (void) fprintf(fp, "%*s</%s>\n", pad, "", TDG_XML_NVPAIR); +} + +static void +tdg_xml_nvint64arr(FILE *fp, uint_t pad, const char *name, int64_t *val, + uint_t nelems) +{ + (void) fprintf(fp, "%*s<%s %s='%s' %s='%s'>\n", pad, "", + TDG_XML_NVPAIR, TDG_XML_NAME, name, TDG_XML_TYPE, + TDG_XML_INT64_ARR); + + for (uint_t i = 0; i < nelems; i++) { + (void) fprintf(fp, "%*s<%s %s='%" PRIi64 "' />\n", (pad + 2), + "", TDG_XML_NVPAIR, TDG_XML_VALUE, val[i]); + } + (void) fprintf(fp, "%*s</%s>\n", pad, "", TDG_XML_NVPAIR); +} + +static void +tdg_xml_nvuint64arr(FILE *fp, uint_t pad, const char *name, uint64_t *val, + uint_t nelems) +{ + (void) fprintf(fp, "%*s<%s %s='%s' %s='%s'>\n", pad, "", + TDG_XML_NVPAIR, TDG_XML_NAME, name, TDG_XML_TYPE, + TDG_XML_UINT64_ARR); + + for (uint_t i = 0; i < nelems; i++) { + (void) fprintf(fp, "%*s<%s %s='0x%" PRIx64 "' />\n", (pad + 2), + "", TDG_XML_NVPAIR, TDG_XML_VALUE, val[i]); + } + (void) fprintf(fp, "%*s</%s>\n", pad, "", TDG_XML_NVPAIR); +} + +static int +serialize_nvpair_nvlist(topo_hdl_t *thp, FILE *fp, uint_t pad, + const char *name, nvlist_t *nvl) +{ + nvpair_t *elem = NULL; + + tdg_xml_nvlist(fp, pad, name); + + (void) fprintf(fp, "%*s<%s>\n", pad, "", TDG_XML_NVLIST); + + while ((elem = nvlist_next_nvpair(nvl, elem)) != NULL) { + char *nvname = nvpair_name(elem); + + if (serialize_nvpair(thp, fp, (pad + 2), nvname, elem) != 0) { + /* errno set */ + return (-1); + } + } + + (void) fprintf(fp, "%*s</%s>\n", pad, "", TDG_XML_NVLIST); + (void) fprintf(fp, "%*s</%s> <!-- %s -->\n", pad, "", TDG_XML_NVPAIR, + name); + + return (0); +} + +static int +serialize_nvpair(topo_hdl_t *thp, FILE *fp, uint_t pad, const char *pname, + nvpair_t *nvp) +{ + data_type_t type = nvpair_type(nvp); + + switch (type) { + case DATA_TYPE_INT8: { + int8_t val; + + if (nvpair_value_int8(nvp, &val) != 0) + return (topo_hdl_seterrno(thp, ETOPO_UNKNOWN)); + + tdg_xml_nvint8(fp, pad, pname, val); + break; + } + case DATA_TYPE_UINT8: { + uint8_t val; + + if (nvpair_value_uint8(nvp, &val) != 0) + return (topo_hdl_seterrno(thp, ETOPO_UNKNOWN)); + + tdg_xml_nvuint8(fp, pad, pname, val); + break; + } + case DATA_TYPE_INT16: { + int16_t val; + + if (nvpair_value_int16(nvp, &val) != 0) + return (topo_hdl_seterrno(thp, ETOPO_UNKNOWN)); + + tdg_xml_nvint16(fp, pad, pname, val); + break; + } + case DATA_TYPE_UINT16: { + uint16_t val; + + if (nvpair_value_uint16(nvp, &val) != 0) + return (topo_hdl_seterrno(thp, ETOPO_UNKNOWN)); + + tdg_xml_nvuint16(fp, pad, pname, val); + break; + } + case DATA_TYPE_INT32: { + int32_t val; + + if (nvpair_value_int32(nvp, &val) != 0) + return (topo_hdl_seterrno(thp, ETOPO_UNKNOWN)); + + tdg_xml_nvint32(fp, pad, pname, val); + break; + } + case DATA_TYPE_UINT32: { + uint32_t val; + + if (nvpair_value_uint32(nvp, &val) != 0) + return (topo_hdl_seterrno(thp, ETOPO_UNKNOWN)); + + tdg_xml_nvuint32(fp, pad, pname, val); + break; + } + case DATA_TYPE_INT64: { + int64_t val; + + if (nvpair_value_int64(nvp, &val) != 0) + return (topo_hdl_seterrno(thp, ETOPO_UNKNOWN)); + + tdg_xml_nvint64(fp, pad, pname, val); + break; + } + case DATA_TYPE_UINT64: { + uint64_t val; + + if (nvpair_value_uint64(nvp, &val) != 0) + return (topo_hdl_seterrno(thp, ETOPO_UNKNOWN)); + + tdg_xml_nvuint64(fp, pad, pname, val); + break; + } + case DATA_TYPE_DOUBLE: { + double val; + + if (nvpair_value_double(nvp, &val) != 0) + return (topo_hdl_seterrno(thp, ETOPO_UNKNOWN)); + + tdg_xml_nvdbl(fp, pad, pname, val); + break; + } + case DATA_TYPE_STRING: { + char *val; + + if (nvpair_value_string(nvp, &val) != 0) + return (topo_hdl_seterrno(thp, ETOPO_UNKNOWN)); + + tdg_xml_nvstring(fp, pad, pname, val); + break; + } + case DATA_TYPE_NVLIST: { + nvlist_t *nvl; + + if (nvpair_value_nvlist(nvp, &nvl) != 0) + return (topo_hdl_seterrno(thp, ETOPO_UNKNOWN)); + + if (serialize_nvpair_nvlist(thp, fp, pad + 2, pname, + nvl) != 0) { + return (topo_hdl_seterrno(thp, ETOPO_UNKNOWN)); + } + break; + } + case DATA_TYPE_INT32_ARRAY: { + uint_t nelems; + int32_t *val; + + if (nvpair_value_int32_array(nvp, &val, &nelems) != 0) + return (topo_hdl_seterrno(thp, ETOPO_UNKNOWN)); + + tdg_xml_nvint32arr(fp, pad + 2, pname, val, nelems); + + break; + } + case DATA_TYPE_UINT32_ARRAY: { + uint_t nelems; + uint32_t *val; + + if (nvpair_value_uint32_array(nvp, &val, &nelems) != 0) + return (topo_hdl_seterrno(thp, ETOPO_UNKNOWN)); + + tdg_xml_nvuint32arr(fp, pad + 2, pname, val, nelems); + + break; + } + case DATA_TYPE_INT64_ARRAY: { + uint_t nelems; + int64_t *val; + + if (nvpair_value_int64_array(nvp, &val, &nelems) != 0) + return (topo_hdl_seterrno(thp, ETOPO_UNKNOWN)); + + tdg_xml_nvint64arr(fp, pad + 2, pname, val, nelems); + + break; + } + case DATA_TYPE_UINT64_ARRAY: { + uint_t nelems; + uint64_t *val; + + if (nvpair_value_uint64_array(nvp, &val, &nelems) != 0) + return (topo_hdl_seterrno(thp, ETOPO_UNKNOWN)); + + tdg_xml_nvuint64arr(fp, pad + 2, pname, val, nelems); + + break; + } + case DATA_TYPE_STRING_ARRAY: { + uint_t nelems; + char **val; + + if (nvpair_value_string_array(nvp, &val, &nelems) != 0) + return (topo_hdl_seterrno(thp, ETOPO_UNKNOWN)); + + tdg_xml_nvarray(fp, pad, pname, TDG_XML_STRING_ARR); + for (uint_t i = 0; i < nelems; i++) { + (void) fprintf(fp, "%*s<%s %s='%s' />\n", + (pad + 2), "", TDG_XML_NVPAIR, + TDG_XML_VALUE, val[i]); + } + (void) fprintf(fp, "%*s</%s>\n", (pad + 2), "", + TDG_XML_NVPAIR); + + break; + } + case DATA_TYPE_NVLIST_ARRAY: { + uint_t nelems; + nvlist_t **val; + + if (nvpair_value_nvlist_array(nvp, &val, &nelems) != 0) + return (topo_hdl_seterrno(thp, ETOPO_UNKNOWN)); + + tdg_xml_nvarray(fp, pad, pname, TDG_XML_NVLIST_ARR); + for (uint_t i = 0; i < nelems; i++) { + nvpair_t *elem = NULL; + + (void) fprintf(fp, "%*s<%s>\n", (pad + 2), "", + TDG_XML_NVLIST); + + while ((elem = nvlist_next_nvpair(val[i], + elem)) != NULL) { + char *nvname = nvpair_name(elem); + + if (serialize_nvpair(thp, fp, + (pad + 4), nvname, elem) != 0) { + /* errno set */ + return (-1); + } + } + + (void) fprintf(fp, "%*s</%s>\n", (pad + 2), "", + TDG_XML_NVLIST); + } + (void) fprintf(fp, "%*s</%s>\n", pad, "", + TDG_XML_NVPAIR); + + break; + } + default: + topo_dprintf(thp, TOPO_DBG_XML, "Invalid nvpair data " + "type: %d\n", type); + (void) topo_hdl_seterrno(thp, ETOPO_MOD_XENUM); + return (-1); + } + return (0); +} + +static int +serialize_edge(topo_hdl_t *thp, topo_edge_t *edge, boolean_t last_edge, + void *arg) +{ + nvlist_t *fmri = NULL; + char *fmristr; + int err; + tnode_t *tn; + FILE *fp = (FILE *)arg; + + tn = topo_vertex_node(edge->tve_vertex); + if (topo_node_resource(tn, &fmri, &err) != 0 || + topo_fmri_nvl2str(thp, fmri, &fmristr, &err) != 0) { + /* errno set */ + nvlist_free(fmri); + return (TOPO_WALK_ERR); + } + nvlist_free(fmri); + + (void) fprintf(fp, "%*s<%s %s='%s' />\n", 4, "", TDG_XML_EDGE, + TDG_XML_FMRI, fmristr); + topo_hdl_strfree(thp, fmristr); + + return (TOPO_WALK_NEXT); +} + +/* + * Some node property values aren't available unless we go through the libtopo + * API's topo_prop_get_* routines. We do that here to make sure the nodes have + * all of their properties populated, then we vector off to type-specific + * XML serialization functions. + */ +static int +serialize_property(topo_hdl_t *thp, FILE *fp, uint_t pad, tnode_t *tn, + topo_propval_t *pv, const char *pgname) +{ + topo_type_t type = pv->tp_type; + const char *pname = pv->tp_name; + int err; + char *name = TDG_XML_PROP_VALUE; + + switch (type) { + case TOPO_TYPE_INT32: { + int32_t val; + + if (topo_prop_get_int32(tn, pgname, pname, &val, + &err) != 0) + return (-1); + + tdg_xml_nvint32(fp, pad, name, val); + break; + } + case TOPO_TYPE_UINT32: { + uint32_t val; + + if (topo_prop_get_uint32(tn, pgname, pname, &val, + &err) != 0) + return (-1); + + tdg_xml_nvuint32(fp, pad, name, val); + break; + } + case TOPO_TYPE_INT64: { + int64_t val; + + if (topo_prop_get_int64(tn, pgname, pname, &val, + &err) != 0) + return (-1); + + tdg_xml_nvint64(fp, pad, name, val); + break; + } + case TOPO_TYPE_UINT64: { + uint64_t val; + + if (topo_prop_get_uint64(tn, pgname, pname, &val, + &err) != 0) + return (-1); + + tdg_xml_nvuint64(fp, pad, name, val); + break; + } + case TOPO_TYPE_STRING: { + char *val; + + if (topo_prop_get_string(tn, pgname, pname, &val, + &err) != 0) + return (-1); + + tdg_xml_nvstring(fp, pad, name, val); + + topo_hdl_strfree(thp, val); + break; + } + case TOPO_TYPE_FMRI: { + nvlist_t *nvl; + + if (topo_prop_get_fmri(tn, pgname, pname, &nvl, + &err) != 0) + return (-1); + + if (serialize_nvpair_nvlist(thp, fp, pad + 2, name, + nvl) != 0) { + nvlist_free(nvl); + return (-1); + } + + nvlist_free(nvl); + break; + } + case TOPO_TYPE_INT32_ARRAY: { + uint_t nelems; + int32_t *val; + + if (topo_prop_get_int32_array(tn, pgname, pname, &val, + &nelems, &err) != 0) + return (-1); + + tdg_xml_nvint32arr(fp, pad, pname, val, nelems); + topo_hdl_free(thp, val, (sizeof (int32_t) * nelems)); + break; + } + case TOPO_TYPE_UINT32_ARRAY: { + uint_t nelems; + uint32_t *val; + + if (topo_prop_get_uint32_array(tn, pgname, pname, &val, + &nelems, &err) != 0) + return (-1); + + tdg_xml_nvuint32arr(fp, pad, pname, val, nelems); + topo_hdl_free(thp, val, (sizeof (uint32_t) * nelems)); + break; + } + case TOPO_TYPE_INT64_ARRAY: { + uint_t nelems; + int64_t *val; + + if (topo_prop_get_int64_array(tn, pgname, pname, &val, + &nelems, &err) != 0) + return (-1); + + tdg_xml_nvint64arr(fp, pad, pname, val, nelems); + topo_hdl_free(thp, val, (sizeof (int64_t) * nelems)); + break; + } + case TOPO_TYPE_UINT64_ARRAY: { + uint_t nelems; + uint64_t *val; + + if (topo_prop_get_uint64_array(tn, pgname, pname, &val, + &nelems, &err) != 0) + return (-1); + + tdg_xml_nvuint64arr(fp, pad, pname, val, nelems); + topo_hdl_free(thp, val, (sizeof (uint64_t) * nelems)); + break; + } + default: + topo_dprintf(thp, TOPO_DBG_XML, "Invalid nvpair data " + "type: %d\n", type); + (void) topo_hdl_seterrno(thp, ETOPO_MOD_XENUM); + return (-1); + } + return (0); +} + +static int +serialize_pgroups(topo_hdl_t *thp, FILE *fp, tnode_t *tn) +{ + topo_pgroup_t *pg; + uint_t npgs = 0; + + for (pg = topo_list_next(&tn->tn_pgroups); pg != NULL; + pg = topo_list_next(pg)) { + + npgs++; + } + + tdg_xml_nvarray(fp, 2, TDG_XML_PGROUPS, TDG_XML_NVLIST_ARR); + + for (pg = topo_list_next(&tn->tn_pgroups); pg != NULL; + pg = topo_list_next(pg)) { + + topo_proplist_t *pvl; + uint_t nprops = 0; + + (void) fprintf(fp, "%*s<%s>\n", 4, "", TDG_XML_NVLIST); + tdg_xml_nvstring(fp, 6, TOPO_PROP_GROUP_NAME, + pg->tpg_info->tpi_name); + + for (pvl = topo_list_next(&pg->tpg_pvals); pvl != NULL; + pvl = topo_list_next(pvl)) + nprops++; + + tdg_xml_nvarray(fp, 6, TDG_XML_PVALS, TDG_XML_NVLIST_ARR); + + for (pvl = topo_list_next(&pg->tpg_pvals); pvl != NULL; + pvl = topo_list_next(pvl)) { + + topo_propval_t *pv = pvl->tp_pval; + + (void) fprintf(fp, "%*s<%s>\n", 8, "", TDG_XML_NVLIST); + tdg_xml_nvstring(fp, 10, TDG_XML_PROP_NAME, + pv->tp_name); + tdg_xml_nvuint32(fp, 10, TDG_XML_PROP_TYPE, + pv->tp_type); + + if (serialize_property(thp, fp, 10, tn, pv, + pg->tpg_info->tpi_name) != 0) { + /* errno set */ + return (-1); + } + (void) fprintf(fp, "%*s</%s>\n", 8, "", + TDG_XML_NVLIST); + } + + (void) fprintf(fp, "%*s</%s> <!-- %s -->\n", 6, "", + TDG_XML_NVPAIR, TDG_XML_PVALS); + (void) fprintf(fp, "%*s</%s>\n", 4, "", TDG_XML_NVLIST); + } + (void) fprintf(fp, "%*s</%s> <!-- %s -->\n", 2, "", TDG_XML_NVPAIR, + TDG_XML_PGROUPS); + + return (0); +} + +static int +serialize_vertex(topo_hdl_t *thp, topo_vertex_t *vtx, boolean_t last_vtx, + void *arg) +{ + nvlist_t *fmri = NULL; + char *fmristr; + tnode_t *tn; + int err; + FILE *fp = (FILE *)arg; + + tn = topo_vertex_node(vtx); + if (topo_node_resource(tn, &fmri, &err) != 0 || + topo_fmri_nvl2str(thp, fmri, &fmristr, &err) != 0) { + /* errno set */ + nvlist_free(fmri); + return (TOPO_WALK_ERR); + } + nvlist_free(fmri); + + (void) fprintf(fp, "<%s %s='%s' %s='0x%" PRIx64 "' %s='%s'>\n", + TDG_XML_VERTEX, TDG_XML_NAME, topo_node_name(tn), + TDG_XML_INSTANCE, topo_node_instance(tn), + TDG_XML_FMRI, fmristr); + + topo_hdl_strfree(thp, fmristr); + + if (serialize_pgroups(thp, fp, tn) != 0) { + /* errno set */ + return (TOPO_WALK_ERR); + } + + if (vtx->tvt_noutgoing != 0) { + (void) fprintf(fp, " <%s>\n", TDG_XML_OUTEDGES); + + if (topo_edge_iter(thp, vtx, serialize_edge, fp) != 0) { + topo_dprintf(thp, TOPO_DBG_XML, "failed to iterate " + "edges on %s=%" PRIx64 "\n", topo_node_name(tn), + topo_node_instance(tn)); + /* errno set */ + return (TOPO_WALK_ERR); + } + (void) fprintf(fp, " </%s>\n", TDG_XML_OUTEDGES); + } + (void) fprintf(fp, "</%s>\n\n", TDG_XML_VERTEX); + + return (TOPO_WALK_NEXT); +} + +/* + * This function takes a topo_digraph_t and serializes it to XML. + * + * The schema is described in detail in: + * usr/src/lib/fm/topo/maps/common/digraph-topology.dtd.1 + * + * On success, this function writes the XML to the specified file and + * returns 0. + * + * On failure, this function returns -1. + */ +int +topo_digraph_serialize(topo_hdl_t *thp, topo_digraph_t *tdg, FILE *fp) +{ + struct utsname uts = { 0 }; + time_t utc_time; + char tstamp[64]; + int ret; + + if ((ret = uname(&uts)) < 0) { + topo_dprintf(thp, TOPO_DBG_XML, "uname failed (ret = %d)\n", + ret); + return (topo_hdl_seterrno(thp, ETOPO_UNKNOWN)); + } + + if (time(&utc_time) < 0) { + topo_dprintf(thp, TOPO_DBG_XML, "uname failed (%s)\n", + strerror(errno)); + return (topo_hdl_seterrno(thp, ETOPO_UNKNOWN)); + } + + /* + * strftime returns 0 if the size of the result is larger than the + * buffer size passed in to it. We've sized tstamp to be pretty + * large, so this really shouldn't happen. + */ + if (strftime(tstamp, sizeof (tstamp), "%Y-%m-%dT%H:%M:%SZ", + gmtime(&utc_time)) == 0) { + topo_dprintf(thp, TOPO_DBG_XML, "strftime failed\n"); + return (topo_hdl_seterrno(thp, ETOPO_UNKNOWN)); + } + + (void) fprintf(fp, "<?xml version=\"1.0\"?>\n"); + (void) fprintf(fp, "<!DOCTYPE topology SYSTEM \"%s\">\n", TDG_DTD); + (void) fprintf(fp, "<%s %s='%s' %s='%s' %s='%s' %s='%s' %s='%s'>\n", + TDG_XML_TOPO_DIGRAPH, TDG_XML_SCHEME, tdg->tdg_scheme, + TDG_XML_NODENAME, uts.nodename, TDG_XML_OSVERSION, uts.version, + TDG_XML_PRODUCT, thp->th_product, TDG_XML_TSTAMP, tstamp); + (void) fprintf(fp, "<%s>\n", TDG_XML_VERTICES); + + if (topo_vertex_iter(thp, tdg, serialize_vertex, fp) != 0) { + topo_dprintf(thp, TOPO_DBG_XML, "\nfailed to iterate " + "vertices\n"); + /* errno set */ + return (-1); + } + + (void) fprintf(fp, "</%s>\n", TDG_XML_VERTICES); + (void) fprintf(fp, "</%s>\n", TDG_XML_TOPO_DIGRAPH); + + if (ferror(fp) != 0) { + topo_dprintf(thp, TOPO_DBG_XML, "An unknown error ocurrred " + "while writing out the serialize topology."); + return (topo_hdl_seterrno(thp, ETOPO_UNKNOWN)); + } + return (0); +} + +static xmlNodePtr +get_child_by_name(xmlNodePtr xn, xmlChar *name) +{ + for (xmlNodePtr cn = xn->xmlChildrenNode; cn != NULL; cn = cn->next) + if (xmlStrcmp(cn->name, name) == 0) + return (cn); + + return (NULL); +} + +static void +dump_xml_node(topo_hdl_t *thp, xmlNodePtr xn) +{ + topo_dprintf(thp, TOPO_DBG_XML, "node: %s", (char *)xn->name); + for (xmlAttrPtr attr = xn->properties; attr != NULL; attr = attr->next) + topo_dprintf(thp, TOPO_DBG_XML, "attribute: %s", + (char *)attr->name); + + for (xmlNodePtr cn = xn->xmlChildrenNode; cn != NULL; cn = cn->next) + topo_dprintf(thp, TOPO_DBG_XML, "\tchild node: %s", + (char *)cn->name); +} + +struct edge_cb_arg { + const char *from_fmri; + const char *to_fmri; + topo_vertex_t *from_vtx; + topo_vertex_t *to_vtx; +}; + +static int +edge_cb(topo_hdl_t *thp, topo_vertex_t *vtx, boolean_t last_vtx, void *arg) +{ + struct edge_cb_arg *cbarg = arg; + tnode_t *tn; + nvlist_t *fmri = NULL; + char *fmristr = NULL; + int err; + + tn = topo_vertex_node(vtx); + if (topo_node_resource(tn, &fmri, &err) != 0 || + topo_fmri_nvl2str(thp, fmri, &fmristr, &err) != 0) { + topo_dprintf(thp, TOPO_DBG_XML, "failed to convert FMRI for " + "%s=%" PRIx64 " to a string\n", topo_node_name(tn), + topo_node_instance(tn)); + if (thp->th_debug & TOPO_DBG_XML) + nvlist_print(stdout, fmri); + nvlist_free(fmri); + return (TOPO_WALK_ERR); + } + nvlist_free(fmri); + + if (strcmp(fmristr, cbarg->from_fmri) == 0) + cbarg->from_vtx = vtx; + else if (strcmp(fmristr, cbarg->to_fmri) == 0) + cbarg->to_vtx = vtx; + + topo_hdl_strfree(thp, fmristr); + if (cbarg->from_vtx != NULL && cbarg->to_vtx != NULL) + return (TOPO_WALK_TERMINATE); + else + return (TOPO_WALK_NEXT); +} + +static int +deserialize_edges(topo_hdl_t *thp, topo_mod_t *mod, topo_digraph_t *tdg, + xmlChar *from_fmri, xmlNodePtr xn) +{ + for (xmlNodePtr cn = xn->xmlChildrenNode; cn != NULL; + cn = cn->next) { + xmlChar *fmri; + struct edge_cb_arg cbarg = { 0 }; + + if (xmlStrcmp(cn->name, (xmlChar *)TDG_XML_EDGE) != 0) + continue; + + if ((fmri = xmlGetProp(cn, (xmlChar *)TDG_XML_FMRI)) == NULL) { + topo_dprintf(thp, TOPO_DBG_XML, + "error parsing %s element", (char *)cn->name); + dump_xml_node(thp, cn); + return (-1); + } + cbarg.from_fmri = (char *)from_fmri; + cbarg.to_fmri = (char *)fmri; + + if (topo_vertex_iter(mod->tm_hdl, tdg, edge_cb, &cbarg) != 0) { + xmlFree(fmri); + return (-1); + } + xmlFree(fmri); + + if (cbarg.from_vtx == NULL || cbarg.to_vtx == NULL) { + return (-1); + } + if (topo_edge_new(mod, cbarg.from_vtx, cbarg.to_vtx) != 0) { + return (-1); + } + } + + return (0); +} + +static int +add_edges(topo_hdl_t *thp, topo_mod_t *mod, topo_digraph_t *tdg, + xmlNodePtr xn) +{ + int ret = -1; + nvlist_t *props = NULL; + xmlChar *name = NULL, *fmri = NULL; + xmlNodePtr cn; + uint64_t inst; + + if ((name = xmlGetProp(xn, (xmlChar *)TDG_XML_NAME)) == NULL || + (fmri = xmlGetProp(xn, (xmlChar *)TDG_XML_FMRI)) == NULL || + xmlattr_to_int(mod, xn, TDG_XML_INSTANCE, &inst) != 0) { + goto fail; + } + + if ((cn = get_child_by_name(xn, (xmlChar *)TDG_XML_OUTEDGES)) != + NULL) { + if (deserialize_edges(thp, mod, tdg, fmri, cn) != 0) + goto fail; + } + ret = 0; + +fail: + if (ret != 0) { + topo_dprintf(thp, TOPO_DBG_XML, "%s: error parsing %s element", + __func__, TDG_XML_VERTEX); + dump_xml_node(thp, xn); + } + nvlist_free(props); + if (name != NULL) + xmlFree(name); + if (fmri != NULL) + xmlFree(fmri); + + return (ret); +} + +static topo_pgroup_info_t pginfo = { + NULL, + TOPO_STABILITY_PRIVATE, + TOPO_STABILITY_PRIVATE, + 1 +}; + +static int +add_props(topo_hdl_t *thp, topo_vertex_t *vtx, nvlist_t *pgroups) +{ + tnode_t *tn; + nvlist_t **pgs; + uint_t npgs = 0; + + tn = topo_vertex_node(vtx); + if (nvlist_lookup_nvlist_array(pgroups, TDG_XML_PGROUPS, &pgs, + &npgs) != 0) { + goto fail; + } + + for (uint_t i = 0; i < npgs; i++) { + char *pgname; + nvlist_t **props; + uint_t nprops; + int err; + + if (nvlist_lookup_string(pgs[i], TDG_XML_PGROUP_NAME, + &pgname) != 0 || + nvlist_lookup_nvlist_array(pgs[i], TDG_XML_PVALS, &props, + &nprops) != 0) { + goto fail; + } + pginfo.tpi_name = pgname; + + if (topo_pgroup_create(tn, &pginfo, &err) != 0) { + topo_dprintf(thp, TOPO_DBG_XML, "failed to create " + "pgroup: %s", pgname); + goto fail; + } + for (uint_t j = 0; j < nprops; j++) { + if (topo_prop_setprop(tn, pgname, props[j], + TOPO_PROP_IMMUTABLE, props[j], &err) != 0) { + topo_dprintf(thp, TOPO_DBG_XML, "failed to " + "set properties in pgroup: %s", pgname); + goto fail; + } + } + } + return (0); +fail: + topo_dprintf(thp, TOPO_DBG_XML, "%s: error decoding properties for " + "%s=%" PRIx64, __func__, topo_node_name(tn), + topo_node_instance(tn)); + if (thp->th_debug & TOPO_DBG_XML) + nvlist_print(stdout, pgroups); + + return (-1); +} + +static void +free_nvlist_array(topo_hdl_t *thp, nvlist_t **nvlarr, uint_t nelems) +{ + for (uint_t i = 0; i < nelems; i++) { + if (nvlarr[i] != NULL) + nvlist_free(nvlarr[i]); + } + topo_hdl_free(thp, nvlarr, nelems * sizeof (nvlist_t *)); +} + +static boolean_t +is_overflow(topo_hdl_t *thp, uint64_t val, uint_t nbits) +{ + if ((val >> nbits) != 0) { + topo_dprintf(thp, TOPO_DBG_XML, "value exceeds %u bits", nbits); + return (B_TRUE); + } + return (B_FALSE); +} + +/* + * Recursive function for parsing nvpair XML elements, which can contain + * nested nvlist and nvpair elements. + */ +static int +deserialize_nvpair(topo_hdl_t *thp, topo_mod_t *mod, nvlist_t *nvl, + xmlNodePtr xn) +{ + int ret = -1; + xmlChar *name = NULL, *type = NULL, *sval = NULL; + uint64_t val; + + if ((name = xmlGetProp(xn, (xmlChar *)TDG_XML_NAME)) == NULL || + (type = xmlGetProp(xn, (xmlChar *)TDG_XML_TYPE)) == NULL) { + goto fail; + } + + if (xmlStrcmp(type, (xmlChar *)TDG_XML_NVLIST) == 0) { + nvlist_t *cnvl = NULL; + + if (topo_hdl_nvalloc(thp, &cnvl, NV_UNIQUE_NAME) != 0) { + goto fail; + } + + for (xmlNodePtr cn = xn->xmlChildrenNode; + cn != NULL; cn = cn->next) { + + if (xmlStrcmp(cn->name, (xmlChar *)TDG_XML_NVLIST) != 0) + continue; + + for (xmlNodePtr gcn = cn->xmlChildrenNode; + gcn != NULL; gcn = gcn->next) { + + if (xmlStrcmp(gcn->name, + (xmlChar *)TDG_XML_NVPAIR) != 0) + continue; + if (deserialize_nvpair(thp, mod, cnvl, gcn) != + 0) { + nvlist_free(cnvl); + goto fail; + } + } + if (nvlist_add_nvlist(nvl, (char *)name, cnvl) != 0) { + nvlist_free(cnvl); + goto fail; + } + nvlist_free(cnvl); + break; + } + } else if (xmlStrcmp(type, (xmlChar *)TDG_XML_INT8) == 0) { + if (xmlattr_to_int(mod, xn, TDG_XML_VALUE, &val) != 0 || + is_overflow(thp, val, 8) || + nvlist_add_int8(nvl, (char *)name, (int8_t)val) != 0) { + goto fail; + } + } else if (xmlStrcmp(type, (xmlChar *)TDG_XML_INT16) == 0) { + if (xmlattr_to_int(mod, xn, TDG_XML_VALUE, &val) != 0 || + is_overflow(thp, val, 16) || + nvlist_add_int16(nvl, (char *)name, (int16_t)val) != 0) { + goto fail; + } + } else if (xmlStrcmp(type, (xmlChar *)TDG_XML_INT32) == 0) { + if (xmlattr_to_int(mod, xn, TDG_XML_VALUE, &val) != 0 || + is_overflow(thp, val, 32) || + nvlist_add_int32(nvl, (char *)name, (int32_t)val) != 0) { + goto fail; + } + } else if (xmlStrcmp(type, (xmlChar *)TDG_XML_INT64) == 0) { + if (xmlattr_to_int(mod, xn, TDG_XML_VALUE, &val) != 0 || + nvlist_add_int64(nvl, (char *)name, (int64_t)val) != 0) { + goto fail; + } + } else if (xmlStrcmp(type, (xmlChar *)TDG_XML_UINT8) == 0) { + if (xmlattr_to_int(mod, xn, TDG_XML_VALUE, &val) != 0 || + is_overflow(thp, val, 8) || + nvlist_add_uint8(nvl, (char *)name, (uint8_t)val) != 0) { + goto fail; + } + } else if (xmlStrcmp(type, (xmlChar *)TDG_XML_UINT16) == 0) { + if (xmlattr_to_int(mod, xn, TDG_XML_VALUE, &val) != 0 || + is_overflow(thp, val, 16) || + nvlist_add_uint16(nvl, (char *)name, (uint16_t)val) != 0) { + goto fail; + } + } else if (xmlStrcmp(type, (xmlChar *)TDG_XML_UINT32) == 0) { + if (xmlattr_to_int(mod, xn, TDG_XML_VALUE, &val) != 0 || + is_overflow(thp, val, 32) || + nvlist_add_uint32(nvl, (char *)name, (uint32_t)val) != 0) { + goto fail; + } + } else if (xmlStrcmp(type, (xmlChar *)TDG_XML_UINT64) == 0) { + if (xmlattr_to_int(mod, xn, TDG_XML_VALUE, &val) != 0 || + nvlist_add_uint64(nvl, (char *)name, (uint64_t)val) != 0) { + goto fail; + } + } else if (xmlStrcmp(type, (xmlChar *)TDG_XML_STRING) == 0) { + if ((sval = xmlGetProp(xn, (xmlChar *)TDG_XML_VALUE)) == NULL || + nvlist_add_string(nvl, (char *)name, (char *)sval) != 0) { + goto fail; + } + } else if (xmlStrcmp(type, (xmlChar *)TDG_XML_NVLIST_ARR) == 0) { + uint64_t nelem = 0; + nvlist_t **nvlarr = NULL; + uint_t i = 0; + xmlNodePtr cn = xn->xmlChildrenNode; + + /* + * Count the number of child nvlist elements + */ + while (cn != NULL) { + if (xmlStrcmp(cn->name, (xmlChar *)TDG_XML_NVLIST) == + 0) { + nelem++; + } + cn = cn->next; + } + + if ((nvlarr = topo_hdl_zalloc(thp, + (nelem * sizeof (nvlist_t *)))) == NULL) { + goto fail; + } + + for (cn = xn->xmlChildrenNode; cn != NULL; cn = cn->next) { + if (xmlStrcmp(cn->name, (xmlChar *)TDG_XML_NVLIST) != + 0) + continue; + + if (topo_hdl_nvalloc(thp, &nvlarr[i], + NV_UNIQUE_NAME) != 0) { + free_nvlist_array(thp, nvlarr, nelem); + goto fail; + } + + for (xmlNodePtr gcn = cn->xmlChildrenNode; + gcn != NULL; gcn = gcn->next) { + if (xmlStrcmp(gcn->name, + (xmlChar *)TDG_XML_NVPAIR) != 0) + continue; + if (deserialize_nvpair(thp, mod, nvlarr[i], + gcn) != 0) { + free_nvlist_array(thp, nvlarr, nelem); + goto fail; + } + } + i++; + } + if (nvlist_add_nvlist_array(nvl, (char *)name, nvlarr, + nelem) != 0) { + free_nvlist_array(thp, nvlarr, nelem); + goto fail; + } + free_nvlist_array(thp, nvlarr, nelem); + } else if (xmlStrcmp(type, (xmlChar *)TDG_XML_UINT32_ARR) == 0) { + uint64_t nelem = 0; + uint32_t *arr = NULL; + uint_t i = 0; + xmlNodePtr cn = xn->xmlChildrenNode; + + /* + * Count the number of child nvpair elements + */ + while (cn != NULL) { + if (xmlStrcmp(cn->name, (xmlChar *)TDG_XML_NVPAIR) == + 0) { + nelem++; + } + cn = cn->next; + } + + if ((arr = topo_hdl_zalloc(thp, + (nelem * sizeof (uint32_t)))) == NULL) { + goto fail; + } + + for (cn = xn->xmlChildrenNode; cn != NULL; cn = cn->next) { + if (xmlStrcmp(cn->name, (xmlChar *)TDG_XML_NVPAIR) != 0) + continue; + + if (xmlattr_to_int(mod, cn, TDG_XML_VALUE, &val) != 0) { + topo_hdl_free(thp, arr, + (nelem * sizeof (uint32_t))); + goto fail; + } + + arr[i] = val; + i++; + } + if (nvlist_add_uint32_array(nvl, (char *)name, arr, + nelem) != 0) { + topo_hdl_free(thp, arr, (nelem * sizeof (uint32_t))); + goto fail; + } + topo_hdl_free(thp, arr, (nelem * sizeof (uint32_t))); + } else if (xmlStrcmp(type, (xmlChar *)TDG_XML_INT32_ARR) == 0) { + uint64_t nelem = 0; + int32_t *arr = NULL; + uint_t i = 0; + xmlNodePtr cn = xn->xmlChildrenNode; + + /* + * Count the number of child nvpair elements + */ + while (cn != NULL) { + if (xmlStrcmp(cn->name, (xmlChar *)TDG_XML_NVPAIR) == + 0) { + nelem++; + } + cn = cn->next; + } + + if ((arr = topo_hdl_zalloc(thp, + (nelem * sizeof (int32_t)))) == NULL) { + goto fail; + } + + for (cn = xn->xmlChildrenNode; cn != NULL; cn = cn->next) { + if (xmlStrcmp(cn->name, (xmlChar *)TDG_XML_NVPAIR) != 0) + continue; + + if (xmlattr_to_int(mod, cn, TDG_XML_VALUE, &val) != 0) { + topo_hdl_free(thp, arr, + (nelem * sizeof (int32_t))); + goto fail; + } + + arr[i] = val; + i++; + } + if (nvlist_add_int32_array(nvl, (char *)name, arr, + nelem) != 0) { + topo_hdl_free(thp, arr, (nelem * sizeof (int32_t))); + goto fail; + } + topo_hdl_free(thp, arr, (nelem * sizeof (int32_t))); + } else if (xmlStrcmp(type, (xmlChar *)TDG_XML_UINT64_ARR) == 0) { + uint64_t nelem = 0, *arr = NULL; + uint_t i = 0; + xmlNodePtr cn = xn->xmlChildrenNode; + + /* + * Count the number of child nvpair elements + */ + while (cn != NULL) { + if (xmlStrcmp(cn->name, (xmlChar *)TDG_XML_NVPAIR) == + 0) { + nelem++; + } + cn = cn->next; + } + + if ((arr = topo_hdl_zalloc(thp, + (nelem * sizeof (uint64_t)))) == NULL) { + goto fail; + } + + for (cn = xn->xmlChildrenNode; cn != NULL; cn = cn->next) { + if (xmlStrcmp(cn->name, (xmlChar *)TDG_XML_NVPAIR) != 0) + continue; + + if (xmlattr_to_int(mod, cn, TDG_XML_VALUE, &val) != 0) { + topo_hdl_free(thp, arr, + (nelem * sizeof (uint64_t))); + goto fail; + } + + arr[i] = val; + i++; + } + if (nvlist_add_uint64_array(nvl, (char *)name, arr, + nelem) != 0) { + topo_hdl_free(thp, arr, (nelem * sizeof (uint64_t))); + goto fail; + } + topo_hdl_free(thp, arr, (nelem * sizeof (uint64_t))); + } else if (xmlStrcmp(type, (xmlChar *)TDG_XML_INT64_ARR) == 0) { + uint64_t nelem = 0; + int64_t *arr = NULL; + uint_t i = 0; + xmlNodePtr cn = xn->xmlChildrenNode; + + /* + * Count the number of child nvpair elements + */ + while (cn != NULL) { + if (xmlStrcmp(cn->name, (xmlChar *)TDG_XML_NVPAIR) == + 0) { + nelem++; + } + cn = cn->next; + } + + if ((arr = topo_hdl_zalloc(thp, + (nelem * sizeof (int64_t)))) == NULL) { + goto fail; + } + + for (cn = xn->xmlChildrenNode; cn != NULL; cn = cn->next) { + if (xmlStrcmp(cn->name, (xmlChar *)TDG_XML_NVPAIR) != 0) + continue; + + if (xmlattr_to_int(mod, cn, TDG_XML_VALUE, &val) != 0) { + topo_hdl_free(thp, arr, + (nelem * sizeof (int64_t))); + goto fail; + } + + arr[i] = val; + i++; + } + if (nvlist_add_int64_array(nvl, (char *)name, arr, + nelem) != 0) { + topo_hdl_free(thp, arr, (nelem * sizeof (int64_t))); + goto fail; + } + topo_hdl_free(thp, arr, (nelem * sizeof (int64_t))); + } + ret = 0; +fail: + if (ret != 0) { + topo_dprintf(thp, TOPO_DBG_XML, "%s: error parsing %s " + "element: name: %s, type: %s, nvl: %p", __func__, xn->name, + (name != NULL) ? (char *)name : "MISSING!", + (type != NULL) ? (char *)type : "MISSING!", nvl); + dump_xml_node(thp, xn); + } + if (name != NULL) + xmlFree(name); + if (type != NULL) + xmlFree(type); + if (sval != NULL) + xmlFree(sval); + + return (ret); +} + +static int +deserialize_vertex(topo_hdl_t *thp, topo_mod_t *mod, topo_digraph_t *tdg, + xmlNodePtr xn) +{ + int ret = -1; + topo_vertex_t *vtx = NULL; + nvlist_t *props = NULL; + xmlChar *name = NULL, *fmri = NULL; + uint64_t inst; + + if ((name = xmlGetProp(xn, (xmlChar *)TDG_XML_NAME)) == NULL || + (fmri = xmlGetProp(xn, (xmlChar *)TDG_XML_FMRI)) == NULL || + xmlattr_to_int(mod, xn, TDG_XML_INSTANCE, &inst) != 0) { + goto fail; + } + + if ((vtx = topo_vertex_new(mod, (char *)name, inst)) == NULL) { + goto fail; + } + + for (xmlNodePtr cn = xn->xmlChildrenNode; cn != NULL; cn = cn->next) { + if (xmlStrcmp(cn->name, (xmlChar *)TDG_XML_NVPAIR) == 0) { + if (topo_hdl_nvalloc(thp, &props, NV_UNIQUE_NAME) != 0) + goto fail; + if (deserialize_nvpair(thp, mod, props, cn) != 0 || + add_props(thp, vtx, props) != 0) + goto fail; + } + } + ret = 0; + +fail: + if (ret != 0) { + topo_dprintf(thp, TOPO_DBG_XML, "%s: error parsing %s element", + __func__, TDG_XML_VERTEX); + dump_xml_node(thp, xn); + } + nvlist_free(props); + if (name != NULL) + xmlFree(name); + if (fmri != NULL) + xmlFree(fmri); + + return (ret); +} + +/* + * This function takes a buffer containing XML data describing a directed graph + * topology. This data is parsed to the original directed graph is rehydrated. + * + * On success, a pointer to a topo_digraph_t representing the graph is + * returned. The caller is responsible for destroying the graph via a call to + * topo_digraph_destroy() + * + * On failure, NULL is returned. + */ +topo_digraph_t * +topo_digraph_deserialize(topo_hdl_t *thp, const char *xml, size_t sz) +{ + xmlDocPtr doc; + xmlDtdPtr dtd = NULL; + xmlNodePtr root, vertices; + xmlChar *scheme = NULL; + topo_mod_t *mod; + topo_digraph_t *tdg, *ret = NULL; + + if ((doc = xmlReadMemory(xml, sz, "", NULL, 0)) == NULL) { + topo_dprintf(thp, TOPO_DBG_XML, "Failed to parse XML"); + goto fail; + } + + /* + * As a sanity check, extract the DTD from the XML and verify it + * matches the DTD for a digraph topology. + */ + if ((dtd = xmlGetIntSubset(doc)) == NULL) { + topo_dprintf(thp, TOPO_DBG_XML, "document has no DTD.\n"); + goto fail; + } + + if (strcmp((const char *)dtd->SystemID, TDG_DTD) != 0) { + topo_dprintf(thp, TOPO_DBG_XML, "unexpected DTD: %s", + dtd->SystemID); + goto fail; + } + + /* + * Verify the root element is what we're expecting and then grab the + * FMRI scheme from its attributes. + */ + if ((root = xmlDocGetRootElement(doc)) == NULL) { + topo_dprintf(thp, TOPO_DBG_XML, "document is empty.\n"); + goto fail; + } + + if (xmlStrcmp(root->name, (xmlChar *)TDG_XML_TOPO_DIGRAPH) != 0 || + (scheme = xmlGetProp(root, (xmlChar *)TDG_XML_SCHEME)) == + NULL) { + topo_dprintf(thp, TOPO_DBG_XML, + "failed to parse %s element", TDG_XML_TOPO_DIGRAPH); + goto fail; + } + + /* + * Load the topo module associated with this FMRI scheme. + */ + if ((mod = topo_mod_lookup(thp, (const char *)scheme, 1)) == NULL) { + topo_dprintf(thp, TOPO_DBG_XML, "failed to load %s module", + scheme); + goto fail; + } + /* + * If we have a builtin module for this scheme, then there will + * already be an empty digraph attached to the handle. Otherwise, + * create a new empty digraph and attach it to the handle. + */ + tdg = topo_digraph_get(mod->tm_hdl, mod->tm_info->tmi_scheme); + if (tdg == NULL) { + if ((tdg = topo_digraph_new(thp, mod, (const char *)scheme)) == + NULL) { + topo_dprintf(thp, TOPO_DBG_XML, "failed to create new " + "digraph"); + goto fail; + } else { + topo_list_append(&thp->th_digraphs, tdg); + } + } + + /* + * Iterate through the vertex XML elements to reconstruct the graph + */ + vertices = get_child_by_name(root, (xmlChar *)TDG_XML_VERTICES); + if (vertices == NULL || + xmlStrcmp(vertices->name, (xmlChar *)TDG_XML_VERTICES) != 0) { + topo_dprintf(thp, TOPO_DBG_XML, "failed to parse %s element", + TDG_XML_VERTICES); + dump_xml_node(thp, root); + goto fail; + } + + for (xmlNodePtr xn = vertices->xmlChildrenNode; xn != NULL; + xn = xn->next) { + if (xmlStrcmp(xn->name, (xmlChar *)TDG_XML_VERTEX) != 0) + continue; + if (deserialize_vertex(thp, mod, tdg, xn) != 0) + goto fail; + } + + /* + * Now that all of the vertices have been created, go back through + * the vertex XML elements and add the edges. + */ + for (xmlNodePtr xn = vertices->xmlChildrenNode; xn != NULL; + xn = xn->next) { + if (xmlStrcmp(xn->name, (xmlChar *)TDG_XML_VERTEX) != 0) + continue; + if (add_edges(thp, mod, tdg, xn) != 0) + goto fail; + } + + ret = tdg; + +fail: + if (scheme != NULL) + xmlFree(scheme); + + if (doc != NULL) + xmlFreeDoc(doc); + + (void) topo_hdl_seterrno(thp, ETOPO_MOD_XENUM); + return (ret); +} diff --git a/usr/src/lib/fm/topo/libtopo/common/topo_digraph_xml.h b/usr/src/lib/fm/topo/libtopo/common/topo_digraph_xml.h new file mode 100644 index 0000000000..d52d6b8867 --- /dev/null +++ b/usr/src/lib/fm/topo/libtopo/common/topo_digraph_xml.h @@ -0,0 +1,88 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2020 Joyent, Inc. + */ + +#ifndef _TOPO_DIGRAPH_XML_H +#define _TOPO_DIGRAPH_XML_H + +#include <fm/topo_mod.h> + +#include <topo_list.h> +#include <topo_prop.h> +#include <topo_method.h> +#include <topo_alloc.h> +#include <topo_error.h> +#include <topo_module.h> +#include <topo_string.h> +#include <topo_subr.h> + +#ifdef __cplusplus +extern "C" { +#endif + +#define TDG_DTD "/usr/share/lib/xml/dtd/digraph-topology.dtd.1" + +/* + * List of attribute names and values used when serializing a topo_digraph_t + * to XML. + * + * When deserializing an XML representation of a topo_digraph_t, the XML is + * first converted to an nvlist representation and then that nvlist is + * processed to produce a topo_digraph_t. These property names are also + * used as the nvpair names in that intermediate nvlist. + */ +#define TDG_XML_EDGE "edge" +#define TDG_XML_FMRI "fmri" +#define TDG_XML_SCHEME "fmri-scheme" +#define TDG_XML_NAME "name" +#define TDG_XML_NVLIST "nvlist" +#define TDG_XML_NVLIST_ARR "nvlist-array" +#define TDG_XML_NVPAIR "nvpair" +#define TDG_XML_INSTANCE "instance" +#define TDG_XML_INT8 "int8" +#define TDG_XML_INT16 "int16" +#define TDG_XML_INT32 "int32" +#define TDG_XML_INT32_ARR "int32-array" +#define TDG_XML_INT64 "int64" +#define TDG_XML_INT64_ARR "int64-array" +#define TDG_XML_OSVERSION "os-version" +#define TDG_XML_NODENAME "nodename" +#define TDG_XML_PGROUPS "property-groups" +#define TDG_XML_PGROUP_NAME "property-group-name" +#define TDG_XML_PRODUCT "product-id" +#define TDG_XML_PROP_NAME TOPO_PROP_VAL_NAME +#define TDG_XML_PROP_TYPE TOPO_PROP_VAL_TYPE +#define TDG_XML_PROP_VALUE TOPO_PROP_VAL_VAL +#define TDG_XML_PVALS "property-values" +#define TDG_XML_OUTEDGES "outgoing-edges" +#define TDG_XML_STRING "string" +#define TDG_XML_STRING_ARR "string-array" +#define TDG_XML_TOPO_DIGRAPH "topo-digraph" +#define TDG_XML_TSTAMP "timestamp" +#define TDG_XML_TYPE "type" +#define TDG_XML_UINT8 "uint8" +#define TDG_XML_UINT16 "uint16" +#define TDG_XML_UINT32 "uint32" +#define TDG_XML_UINT32_ARR "uint32-array" +#define TDG_XML_UINT64 "uint64" +#define TDG_XML_UINT64_ARR "uint64-array" +#define TDG_XML_VALUE "value" +#define TDG_XML_VERTEX "vertex" +#define TDG_XML_VERTICES "vertices" + +#ifdef __cplusplus +} +#endif + +#endif /* _TOPO_DIGRAPH_XML_H */ diff --git a/usr/src/lib/fm/topo/libtopo/common/topo_fmri.c b/usr/src/lib/fm/topo/libtopo/common/topo_fmri.c index c712cac908..13dfe11031 100644 --- a/usr/src/lib/fm/topo/libtopo/common/topo_fmri.c +++ b/usr/src/lib/fm/topo/libtopo/common/topo_fmri.c @@ -23,6 +23,9 @@ * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ +/* + * Copyright 2020 Joyent, Inc. + */ #include <ctype.h> #include <string.h> @@ -31,6 +34,7 @@ #include <fm/fmd_fmri.h> #include <sys/fm/protocol.h> #include <topo_alloc.h> +#include <topo_digraph.h> #include <topo_error.h> #include <topo_hc.h> #include <topo_method.h> @@ -132,20 +136,56 @@ int topo_fmri_str2nvl(topo_hdl_t *thp, const char *fmristr, nvlist_t **fmri, int *err) { - char *f, buf[PATH_MAX]; + char *f, buf[PATH_MAX], *method = TOPO_METH_STR2NVL; nvlist_t *out = NULL, *in = NULL; tnode_t *rnode; + boolean_t is_path = B_FALSE; - (void) strlcpy(buf, fmristr, sizeof (buf)); - if ((f = strchr(buf, ':')) == NULL) - return (set_error(thp, ETOPO_FMRI_MALFORM, err, - TOPO_METH_STR2NVL, in)); + /* + * For path FMRI's the scheme is encoded in the authority portion of + * the FMRI - e.g. + * + * path://scheme=<scheme>/... + */ + if (strncmp(fmristr, "path://", 7) == 0) { + char *scheme_start, *scheme_end; - *f = '\0'; /* strip trailing FMRI path */ + is_path = B_TRUE; + method = TOPO_METH_PATH_STR2NVL; - if ((rnode = topo_hdl_root(thp, buf)) == NULL) + if ((scheme_start = strchr(fmristr, '=')) == NULL) { + return (set_error(thp, ETOPO_FMRI_MALFORM, err, + TOPO_METH_STR2NVL, in)); + } + scheme_start++; + if ((scheme_end = strchr(scheme_start, '/')) == NULL) { + return (set_error(thp, ETOPO_FMRI_MALFORM, err, + TOPO_METH_STR2NVL, in)); + } + (void) strlcpy(buf, scheme_start, + (scheme_end - scheme_start) + 1); + } else { + (void) strlcpy(buf, fmristr, sizeof (buf)); + + if ((f = strchr(buf, ':')) == NULL) + return (set_error(thp, ETOPO_FMRI_MALFORM, err, + TOPO_METH_STR2NVL, in)); + + *f = '\0'; /* strip trailing FMRI path */ + } + + if (is_path) { + topo_digraph_t *tdg; + + if ((tdg = topo_digraph_get(thp, buf)) == NULL) { + return (set_error(thp, ETOPO_METHOD_NOTSUP, err, + TOPO_METH_STR2NVL, in)); + } + rnode = tdg->tdg_rootnode; + } else if ((rnode = topo_hdl_root(thp, buf)) == NULL) { return (set_error(thp, ETOPO_METHOD_NOTSUP, err, TOPO_METH_STR2NVL, in)); + } if (topo_hdl_nvalloc(thp, &in, NV_UNIQUE_NAME) != 0) return (set_error(thp, ETOPO_FMRI_NVL, err, TOPO_METH_STR2NVL, @@ -155,8 +195,8 @@ topo_fmri_str2nvl(topo_hdl_t *thp, const char *fmristr, nvlist_t **fmri, return (set_error(thp, ETOPO_FMRI_NVL, err, TOPO_METH_STR2NVL, in)); - if (topo_method_invoke(rnode, TOPO_METH_STR2NVL, - TOPO_METH_STR2NVL_VERSION, in, &out, err) != 0) + if (topo_method_invoke(rnode, method, TOPO_METH_STR2NVL_VERSION, in, + &out, err) != 0) return (set_error(thp, *err, err, TOPO_METH_STR2NVL, in)); nvlist_free(in); @@ -711,7 +751,7 @@ topo_fmri_create(topo_hdl_t *thp, const char *scheme, const char *name, TOPO_METH_FMRI, NULL)); if (nvlist_add_string(ins, TOPO_METH_FMRI_ARG_NAME, name) != 0 || - nvlist_add_uint32(ins, TOPO_METH_FMRI_ARG_INST, inst) != 0) { + nvlist_add_uint64(ins, TOPO_METH_FMRI_ARG_INST, inst) != 0) { return (set_nverror(thp, ETOPO_FMRI_NVL, err, TOPO_METH_FMRI, ins)); } @@ -787,17 +827,17 @@ topo_fmri_next_auth(const char *auth) * List of authority information we care about. Note that we explicitly ignore * things that are properties of the chassis and not the resource itself: * - * FM_FMRI_AUTH_PRODUCT_SN "product-sn" - * FM_FMRI_AUTH_PRODUCT "product-id" - * FM_FMRI_AUTH_DOMAIN "domain-id" - * FM_FMRI_AUTH_SERVER "server-id" - * FM_FMRI_AUTH_HOST "host-id" + * FM_FMRI_AUTH_PRODUCT_SN "product-sn" + * FM_FMRI_AUTH_PRODUCT "product-id" + * FM_FMRI_AUTH_DOMAIN "domain-id" + * FM_FMRI_AUTH_SERVER "server-id" + * FM_FMRI_AUTH_HOST "host-id" * * We also ignore the "revision" authority member, as that typically indicates * the firmware revision and is not a static property of the FRU. This leaves * the following interesting members: * - * FM_FMRI_AUTH_CHASSIS "chassis-id" + * FM_FMRI_AUTH_CHASSIS "chassis-id" * FM_FMRI_HC_SERIAL_ID "serial" * FM_FMRI_HC_PART "part" */ diff --git a/usr/src/lib/fm/topo/libtopo/common/topo_method.h b/usr/src/lib/fm/topo/libtopo/common/topo_method.h index c0ced1c188..1226e5e34a 100644 --- a/usr/src/lib/fm/topo/libtopo/common/topo_method.h +++ b/usr/src/lib/fm/topo/libtopo/common/topo_method.h @@ -24,7 +24,7 @@ * Use is subject to license terms. */ /* - * Copyright (c) 2019, Joyent, Inc. + * Copyright 2020 Joyent, Inc. */ #ifndef _TOPO_METHOD_H #define _TOPO_METHOD_H @@ -77,6 +77,9 @@ extern int topo_prop_method_version_register(tnode_t *, const char *, #define TOPO_METH_PROP_SET "topo_prop_set" #define TOPO_METH_FACILITY "topo_facility" #define TOPO_METH_OCCUPIED "topo_occupied" +#define TOPO_METH_PATH_STR2NVL "topo_path_str2nvl" +#define TOPO_METH_PATH_NVL2STR "topo_path_nvl2str" + #define TOPO_METH_FMRI_VERSION 0 #define TOPO_METH_FRU_COMPUTE_VERSION 0 diff --git a/usr/src/lib/fm/topo/libtopo/common/topo_mod.h b/usr/src/lib/fm/topo/libtopo/common/topo_mod.h index 577c3c1de8..4e2ec816fc 100644 --- a/usr/src/lib/fm/topo/libtopo/common/topo_mod.h +++ b/usr/src/lib/fm/topo/libtopo/common/topo_mod.h @@ -23,7 +23,7 @@ * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved. */ /* - * Copyright 2019 Joyent, Inc. + * Copyright 2020 Joyent, Inc. */ #ifndef _TOPO_MOD_H @@ -294,6 +294,7 @@ typedef enum topo_mod_errno { EMOD_NONCANON, /* non-canonical component name requested */ EMOD_MOD_NOENT, /* module lookup failed */ EMOD_UKNOWN_ENUM, /* unknown enumeration error */ + EMOD_DIGRAPH_MAXSZ, /* max digraph size exceeded */ EMOD_END /* end of mod errno list (to ease auto-merge) */ } topo_mod_errno_t; @@ -306,6 +307,19 @@ extern int topo_mod_file_search(topo_mod_t *, const char *file, int oflags); extern topo_method_f topo_mod_hc_occupied; +/* + * Directed Graph topology interfaces + */ +extern topo_digraph_t *topo_digraph_new(topo_hdl_t *, topo_mod_t *, + const char *); +extern void topo_digraph_destroy(topo_digraph_t *); + +extern topo_vertex_t *topo_vertex_new(topo_mod_t *, const char *, + topo_instance_t); +extern void topo_vertex_destroy(topo_mod_t *mod, topo_vertex_t *vtx); + +extern int topo_edge_new(topo_mod_t *, topo_vertex_t *, topo_vertex_t *); + #ifdef __cplusplus } #endif diff --git a/usr/src/lib/fm/topo/libtopo/common/topo_mod.map b/usr/src/lib/fm/topo/libtopo/common/topo_mod.map index b3652fcf49..040ba6a329 100644 --- a/usr/src/lib/fm/topo/libtopo/common/topo_mod.map +++ b/usr/src/lib/fm/topo/libtopo/common/topo_mod.map @@ -1,6 +1,6 @@ # # Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved. -# Copyright 2019 Joyent, Inc. +# Copyright 2020 Joyent, Inc. # # CDDL HEADER START # @@ -25,6 +25,9 @@ $mapfile_version 2 SYMBOL_SCOPE { + topo_digraph_destroy { TYPE = FUNCTION; FLAGS = extern }; + topo_digraph_new { TYPE = FUNCTION; FLAGS = extern }; + topo_edge_new { TYPE = FUNCTION; FLAGS = extern }; topo_node_range_create { TYPE = FUNCTION; FLAGS = extern }; topo_node_range_destroy { TYPE = FUNCTION; FLAGS = extern }; topo_node_bind { TYPE = FUNCTION; FLAGS = extern }; @@ -101,4 +104,7 @@ SYMBOL_SCOPE { topo_prop_inherit { TYPE = FUNCTION; FLAGS = extern }; topo_pgroup_create { TYPE = FUNCTION; FLAGS = extern }; topo_pgroup_hcset { TYPE = FUNCTION; FLAGS = extern }; + + topo_vertex_destroy { TYPE = FUNCTION; FLAGS = extern }; + topo_vertex_new { TYPE = FUNCTION; FLAGS = extern }; }; diff --git a/usr/src/lib/fm/topo/libtopo/common/topo_node.c b/usr/src/lib/fm/topo/libtopo/common/topo_node.c index 9a44e9e666..1d3460135d 100644 --- a/usr/src/lib/fm/topo/libtopo/common/topo_node.c +++ b/usr/src/lib/fm/topo/libtopo/common/topo_node.c @@ -20,7 +20,7 @@ */ /* * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2019, Joyent, Inc. All rights reserved. + * Copyright 2020 Joyent, Inc. */ /* @@ -196,6 +196,14 @@ topo_node_destroy(tnode_t *node) } /* + * Nodes in a directed graph structure have no children, so the node + * name is still intact. We must free it now. + */ + if (node->tn_vtx != NULL) { + topo_mod_strfree(mod, node->tn_name); + } + + /* * Destroy all property data structures, free the node and release * the module that created it */ @@ -257,6 +265,12 @@ topo_node_parent(tnode_t *node) return (node->tn_parent); } +topo_vertex_t * +topo_node_vertex(tnode_t *node) +{ + return (node->tn_vtx); +} + int topo_node_flags(tnode_t *node) { @@ -315,7 +329,7 @@ topo_node_range_create(topo_mod_t *mod, tnode_t *pnode, const char *name, EMOD_NODE_DUP)); } - if (min < 0 || max < min) + if (max < min) return (node_create_seterror(mod, pnode, NULL, EMOD_NODE_RANGE)); @@ -766,9 +780,7 @@ topo_node_unbind(tnode_t *node) topo_node_unlock(node); topo_dprintf(node->tn_hdl, TOPO_DBG_MODSVC, - "node unbound %s=%d/%s=%d refs = %d\n", - topo_node_name(node->tn_parent), - topo_node_instance(node->tn_parent), node->tn_name, + "node unbound %s=%d refs = %d\n", node->tn_name, node->tn_instance, node->tn_refs); topo_node_rele(node); diff --git a/usr/src/lib/fm/topo/libtopo/common/topo_snap.c b/usr/src/lib/fm/topo/libtopo/common/topo_snap.c index fe2efb60f6..8557bcb331 100644 --- a/usr/src/lib/fm/topo/libtopo/common/topo_snap.c +++ b/usr/src/lib/fm/topo/libtopo/common/topo_snap.c @@ -20,7 +20,7 @@ */ /* * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2018, Joyent, Inc. + * Copyright 2020 Joyent, Inc. */ /* @@ -219,6 +219,7 @@ void topo_close(topo_hdl_t *thp) { ttree_t *tp; + topo_digraph_t *tdg; topo_hdl_lock(thp); if (thp->th_platform != NULL) @@ -252,6 +253,14 @@ topo_close(topo_hdl_t *thp) } /* + * Clean-up digraphs + */ + while ((tdg = topo_list_next(&thp->th_digraphs)) != NULL) { + topo_list_delete(&thp->th_digraphs, tdg); + topo_digraph_destroy(tdg); + } + + /* * Unload all plugins */ topo_modhash_unload_all(thp); @@ -426,6 +435,7 @@ topo_snap_destroy(topo_hdl_t *thp) { int i; ttree_t *tp; + topo_digraph_t *tdg; topo_walk_t *twp; tnode_t *root; topo_nodehash_t *nhp; @@ -465,6 +475,29 @@ topo_snap_destroy(topo_hdl_t *thp) } + for (tdg = topo_list_next(&thp->th_digraphs); tdg != NULL; + tdg = topo_list_next(tdg)) { + + topo_vertex_t *vtx; + + if (tdg->tdg_nvertices == 0) + continue; + /* + * We maintain an adjacency list in the topo_digraph_t + * structure, so we can just walk the list to destroy all the + * vertices. + */ + mod = tdg->tdg_mod; + vtx = topo_list_next(&tdg->tdg_vertices); + while (vtx != NULL) { + topo_vertex_t *tmp = vtx; + + vtx = topo_list_next(vtx); + topo_vertex_destroy(mod, tmp); + } + tdg->tdg_nvertices = 0; + } + /* * Clean-up our cached devinfo and prom tree handles. */ @@ -477,7 +510,6 @@ topo_snap_destroy(topo_hdl_t *thp) thp->th_pi = DI_PROM_HANDLE_NIL; } - if (thp->th_uuid != NULL) { topo_hdl_free(thp, thp->th_uuid, TOPO_UUID_SIZE); thp->th_uuid = NULL; diff --git a/usr/src/lib/fm/topo/libtopo/common/topo_subr.c b/usr/src/lib/fm/topo/libtopo/common/topo_subr.c index a7afb3297a..bc59921f6c 100644 --- a/usr/src/lib/fm/topo/libtopo/common/topo_subr.c +++ b/usr/src/lib/fm/topo/libtopo/common/topo_subr.c @@ -23,7 +23,7 @@ * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved. */ /* - * Copyright (c) 2019, Joyent, Inc. + * Copyright 2020 Joyent, Inc. */ #include <alloca.h> @@ -37,6 +37,7 @@ #include <sys/utsname.h> #include <topo_error.h> +#include <topo_digraph.h> #include <topo_subr.h> void @@ -220,12 +221,18 @@ tnode_t * topo_hdl_root(topo_hdl_t *thp, const char *scheme) { ttree_t *tp; + topo_digraph_t *tdg; for (tp = topo_list_next(&thp->th_trees); tp != NULL; tp = topo_list_next(tp)) { if (strcmp(scheme, tp->tt_scheme) == 0) return (tp->tt_root); } + for (tdg = topo_list_next(&thp->th_digraphs); tdg != NULL; + tdg = topo_list_next(tdg)) { + if (strcmp(scheme, tdg->tdg_scheme) == 0) + return (tdg->tdg_rootnode); + } return (NULL); } diff --git a/usr/src/lib/fm/topo/libtopo/common/topo_tree.h b/usr/src/lib/fm/topo/libtopo/common/topo_tree.h index 4c9c48cb04..c8e8d1d30d 100644 --- a/usr/src/lib/fm/topo/libtopo/common/topo_tree.h +++ b/usr/src/lib/fm/topo/libtopo/common/topo_tree.h @@ -24,7 +24,7 @@ * Use is subject to license terms. */ /* - * Copyright (c) 2018, Joyent, Inc. + * Copyright 2020 Joyent, Inc. */ #ifndef _TOPO_TREE_H @@ -73,6 +73,7 @@ struct topo_node { topo_list_t tn_methods; /* Registered method list */ void *tn_priv; /* Private enumerator data */ int tn_refs; /* node reference count */ + topo_vertex_t *tn_vtx; /* NULL for tree topologies */ }; #define TOPO_NODE_INIT 0x0001 @@ -118,6 +119,7 @@ struct topo_hdl { di_prom_handle_t th_pi; /* handle to root of prom tree */ topo_modhash_t *th_modhash; /* Module hash */ topo_list_t th_trees; /* Scheme-specific topo tree list */ + topo_list_t th_digraphs; /* Scheme-specific topo digraph list */ topo_alloc_t *th_alloc; /* allocators */ int th_errno; /* errno */ int th_debug; /* Debug mask */ diff --git a/usr/src/lib/fm/topo/libtopo/common/topo_xml.c b/usr/src/lib/fm/topo/libtopo/common/topo_xml.c index 648d5f3467..289d356171 100644 --- a/usr/src/lib/fm/topo/libtopo/common/topo_xml.c +++ b/usr/src/lib/fm/topo/libtopo/common/topo_xml.c @@ -104,16 +104,18 @@ xmlattr_to_int(topo_mod_t *mp, xmlChar *str; xmlChar *estr; - topo_dprintf(mp->tm_hdl, TOPO_DBG_XML, "xmlattr_to_int(propname=%s)\n", - propname); - if ((str = xmlGetProp(n, (xmlChar *)propname)) == NULL) + if ((str = xmlGetProp(n, (xmlChar *)propname)) == NULL) { + topo_dprintf(mp->tm_hdl, TOPO_DBG_XML, + "%s: failed to lookup %s attribute", __func__, propname); return (topo_mod_seterrno(mp, ETOPO_PRSR_NOATTR)); - + } errno = 0; *value = strtoull((char *)str, (char **)&estr, 0); if (errno != 0 || *estr != '\0') { /* no conversion was done */ xmlFree(str); + topo_dprintf(mp->tm_hdl, TOPO_DBG_XML, + "%s: failed to convert %s attribute", __func__, propname); return (topo_mod_seterrno(mp, ETOPO_PRSR_BADNUM)); } xmlFree(str); diff --git a/usr/src/lib/fm/topo/maps/Makefile.map b/usr/src/lib/fm/topo/maps/Makefile.map index 8c1c33cca2..0ed01490ec 100644 --- a/usr/src/lib/fm/topo/maps/Makefile.map +++ b/usr/src/lib/fm/topo/maps/Makefile.map @@ -22,7 +22,8 @@ # # Copyright 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. -# Copyright (c) 2013, Joyent, Inc. All rights reserved. +# +# Copyright 2020 Joyent, Inc. # .KEEP_STATE: @@ -38,6 +39,11 @@ DTDSRC = $(DTDFILE:%=../common/%) DTDTARG = $(DTDFILE:%=%) ROOTDTDTARG = $(DTDTARG:%=$(ROOT)/usr/share/lib/xml/dtd/%) +DIGRAPH_DTD = digraph-topology.dtd.1 +DIGRAPH_DTD_SRC = ../common/$(DIGRAPH_DTD) +ROOT_DTD_DIR=$(ROOT)/usr/share/lib/xml/dtd +ROOT_DIGRAPH_DTD=$(ROOT_DTD_DIR)/$(DIGRAPH_DTD) + common_ROOTTOPOROOT = $(ROOT)/usr/lib/fm/topo/$(MODCLASS) arch_ROOTTOPOROOT = $(ROOT)/usr/platform/$(ARCH)/lib/fm/topo/$(MODCLASS) platform_ROOTTOPOROOT = \ @@ -51,7 +57,7 @@ install:= FILEMODE = 0444 # to avoid having to deal with things like 48 platform specific internal # storage bays by hand. .xmlgen.xml: - $(RM) $@ + $(RM) $@ $(CAT) ../common/xmlgen-header.xml > $@ $(PERL) $< >> $@ @@ -59,7 +65,7 @@ install:= FILEMODE = 0444 $(RM) $@ $(CAT) ../common/xmlgen-header-new.xml > $@ $(KSH93) $< >> $@ - + %.xml: ../common/%.xml $(RM) $@ $(CAT) $< > $@ @@ -92,4 +98,7 @@ $($(CLASS)_ROOTTOPOROOT)/%: % $(ROOTDTDTARG): $$(@D) $(RM) $@; $(INS) -s -m 0444 -f $(@D) $(DTDSRC) -install: all $(ROOTDTDTARG) $(ROOTTOPOROOT) $(ROOTTOPOMAPS) +$(ROOT_DIGRAPH_DTD): $(DIGRAPH_DTD_SRC) + $(RM) $@; $(INS) -s -m 0444 -f $(ROOT_DTD_DIR) $(DIGRAPH_DTD_SRC) + +install: all $(ROOTDTDTARG) $(ROOT_DIGRAPH_DTD) $(ROOTTOPOROOT) $(ROOTTOPOMAPS) diff --git a/usr/src/lib/fm/topo/maps/common/digraph-topology.dtd.1 b/usr/src/lib/fm/topo/maps/common/digraph-topology.dtd.1 new file mode 100644 index 0000000000..453a82a5e1 --- /dev/null +++ b/usr/src/lib/fm/topo/maps/common/digraph-topology.dtd.1 @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + This file and its contents are supplied under the terms of the + Common Development and Distribution License ("CDDL"), version 1.0. + You may only use this file in accordance with the terms of version + 1.0 of the CDDL. + + A full copy of the text of the CDDL should have accompanied this + source. A copy of the CDDL is also available via the Internet at + http://www.illumos.org/license/CDDL. + + Copyright 2020 Joyent, Inc. +--> + +<!-- + DTD for Directed Graph based topologies +--> + +<!ELEMENT topo-digraph (vertices) > + +<!ATTLIST topo-digraph + scheme CDATA #REQUIRED +> + +<!ELEMENT vertices> + +<!ATTLIST vertices + nelem CDATA #REQUIRED +> + +<!ELEMENT vertex (nvpair*, outgoing-edges?) > + +<!ELEMENT nvlist (nvpair*) > + +<!ELEMENT nvpair (nvlist?) > + +<!ATTLIST nvpair + name CDATA #REQUIRED + type ( int8 | uint8 | int16 | uint16 | int32 | uint32 | + int64 | uint64 | string | nvlist | int32-array | + uint32-array | int64-array | uint64-array | + string-array | nvlist-array | "") + value CDATA "" +> + +<!ELEMENT outgoing-edges (edge*) > + +<!ELEMENT edge (edge*) > + +<!ATTLIST edge + fmri CDATA #REQUIRED +> diff --git a/usr/src/lib/fm/topo/modules/common/ses/ses.c b/usr/src/lib/fm/topo/modules/common/ses/ses.c index bf92b4d7dd..85dc937932 100644 --- a/usr/src/lib/fm/topo/modules/common/ses/ses.c +++ b/usr/src/lib/fm/topo/modules/common/ses/ses.c @@ -66,6 +66,8 @@ static int ses_snap_freq = 250; /* in milliseconds */ #define HR_SECOND 1000000000 +#define SES_INST_NOTSET UINT64_MAX + /* * Because multiple SES targets can be part of a single chassis, we construct * our own hierarchy that takes this into account. These SES targets may refer @@ -3138,7 +3140,7 @@ ses_create_chassis(ses_enum_data_t *sdp, tnode_t *pnode, ses_enum_chassis_t *cp) goto error; } - if (cp->sec_maxinstance >= 0 && + if (cp->sec_maxinstance != SES_INST_NOTSET && (topo_node_range_create(mod, tn, SUBCHASSIS, 0, cp->sec_maxinstance) != 0)) { topo_mod_dprintf(mod, "topo_node_create_range() failed: %s", @@ -3362,7 +3364,7 @@ ses_enum_gather(ses_node_t *np, void *data) goto error; cp->sec_scinstance = SES_STARTING_SUBCHASSIS; - cp->sec_maxinstance = -1; + cp->sec_maxinstance = SES_INST_NOTSET; cp->sec_csn = csn; if (subchassis == NO_SUBCHASSIS) { diff --git a/usr/src/pkg/manifests/service-fault-management.mf b/usr/src/pkg/manifests/service-fault-management.mf index 7c0eb04eb8..645db705c0 100644 --- a/usr/src/pkg/manifests/service-fault-management.mf +++ b/usr/src/pkg/manifests/service-fault-management.mf @@ -863,8 +863,10 @@ file path=usr/sbin/fmadm mode=0555 variant.opensolaris.zone=__NODEFAULT file path=usr/sbin/fmdump mode=0555 variant.opensolaris.zone=__NODEFAULT file path=usr/sbin/fmstat mode=0555 variant.opensolaris.zone=__NODEFAULT # -# Topo DTD is also common +# Topo DTDs are also common # +file path=usr/share/lib/xml/dtd/digraph-topology.dtd.1 \ + variant.opensolaris.zone=__NODEFAULT file path=usr/share/lib/xml/dtd/topology.dtd.1 \ variant.opensolaris.zone=__NODEFAULT file path=usr/share/man/man1m/fmadm.1m diff --git a/usr/src/pkg/manifests/system-test-ostest.mf b/usr/src/pkg/manifests/system-test-ostest.mf index 96e6c2128b..21628f0413 100644 --- a/usr/src/pkg/manifests/system-test-ostest.mf +++ b/usr/src/pkg/manifests/system-test-ostest.mf @@ -28,6 +28,7 @@ dir path=opt/os-tests/tests dir path=opt/os-tests/tests/ddi_ufm dir path=opt/os-tests/tests/file-locking $(i386_ONLY)dir path=opt/os-tests/tests/i386 +dir path=opt/os-tests/tests/libtopo dir path=opt/os-tests/tests/pf_key dir path=opt/os-tests/tests/sdevfs dir path=opt/os-tests/tests/secflags @@ -50,6 +51,12 @@ $(i386_ONLY)file path=opt/os-tests/tests/i386/badseg mode=0555 $(i386_ONLY)file path=opt/os-tests/tests/i386/badseg_exec mode=0555 $(i386_ONLY)file path=opt/os-tests/tests/i386/ldt mode=0555 $(i386_ONLY)file path=opt/os-tests/tests/imc_test mode=0555 +file path=opt/os-tests/tests/libtopo/digraph-test mode=0555 +file path=opt/os-tests/tests/libtopo/digraph-test-in-badedge.xml mode=0444 +file path=opt/os-tests/tests/libtopo/digraph-test-in-badelement.xml mode=0444 +file path=opt/os-tests/tests/libtopo/digraph-test-in-badnum.xml mode=0444 +file path=opt/os-tests/tests/libtopo/digraph-test-in-badscheme.xml mode=0444 +file path=opt/os-tests/tests/libtopo/digraph-test-in.xml mode=0444 file path=opt/os-tests/tests/odirectory.32 mode=0555 file path=opt/os-tests/tests/odirectory.64 mode=0555 file path=opt/os-tests/tests/pf_key/acquire-compare mode=0555 diff --git a/usr/src/test/os-tests/runfiles/default.run b/usr/src/test/os-tests/runfiles/default.run index 650f023d77..f3f7c14a0e 100644 --- a/usr/src/test/os-tests/runfiles/default.run +++ b/usr/src/test/os-tests/runfiles/default.run @@ -90,3 +90,7 @@ arch = i86pc [/opt/os-tests/tests/uccid] arch = i86pc tests = ['atrparse'] + +[/opt/os-tests/tests/libtopo] +user = root +tests = ['digraph-test'] diff --git a/usr/src/test/os-tests/tests/Makefile b/usr/src/test/os-tests/tests/Makefile index f923125d5e..37c9a2029f 100644 --- a/usr/src/test/os-tests/tests/Makefile +++ b/usr/src/test/os-tests/tests/Makefile @@ -19,6 +19,7 @@ SUBDIRS_i386 = i386 imc SUBDIRS = \ ddi_ufm \ file-locking \ + libtopo \ pf_key \ poll \ sdevfs \ diff --git a/usr/src/test/os-tests/tests/libtopo/Makefile b/usr/src/test/os-tests/tests/libtopo/Makefile new file mode 100644 index 0000000000..8b7fe43bb7 --- /dev/null +++ b/usr/src/test/os-tests/tests/libtopo/Makefile @@ -0,0 +1,58 @@ +# +# This file and its contents are supplied under the terms of the +# Common Development and Distribution License ("CDDL"), version 1.0. +# You may only use this file in accordance with the terms of version +# 1.0 of the CDDL. +# +# A full copy of the text of the CDDL should have accompanied this +# source. A copy of the CDDL is also available via the Internet at +# http://www.illumos.org/license/CDDL. +# + +# +# Copyright 2020 Joyent, Inc. +# + +include $(SRC)/Makefile.master + +ROOTOPTPKG = $(ROOT)/opt/os-tests +TESTDIR = $(ROOTOPTPKG)/tests/libtopo + +PROGS = digraph-test + +XML = digraph-test-in.xml \ + digraph-test-in-badscheme.xml \ + digraph-test-in-badnum.xml \ + digraph-test-in-badedge.xml \ + digraph-test-in-badelement.xml + +include $(SRC)/cmd/Makefile.cmd +include $(SRC)/test/Makefile.com + +LDLIBS += -L$(ROOT)/usr/lib/fm -ltopo -R/usr/lib/fm +CFLAGS += -I$(SRC)/lib/fm/topo/libtopo/ +CSTD= $(CSTD_GNU99) + +CMDS = $(PROGS:%=$(TESTDIR)/%) +$(CMDS) := FILEMODE = 0555 + +FILES = $(XML:%=$(TESTDIR)/%) +$(FILES) := FILEMODE = 0444 + +all: $(PROGS) + +install: all $(CMDS) $(FILES) + +clobber: clean + -$(RM) $(PROGS) $(FILES) + +clean: + -$(RM) *.o + +$(CMDS): $(TESTDIR) $(PROGS) + +$(TESTDIR): + $(INS.dir) + +$(TESTDIR)/%: % + $(INS.file) diff --git a/usr/src/test/os-tests/tests/libtopo/digraph-test-in-badedge.xml b/usr/src/test/os-tests/tests/libtopo/digraph-test-in-badedge.xml new file mode 100644 index 0000000000..db86874817 --- /dev/null +++ b/usr/src/test/os-tests/tests/libtopo/digraph-test-in-badedge.xml @@ -0,0 +1,46 @@ +<?xml version="1.0"?> +<!DOCTYPE topology SYSTEM "/usr/share/lib/xml/dtd/digraph-topology.dtd.1"> + +<!-- + Copyright 2020 Joyent, Inc. + +--> + +<topo-digraph fmri-scheme='hc' nodename='test-nodename' os-version='test-os-version' product-id='test-product-id' timestamp='2019-12-20T01:51:26Z'> +<vertices> +<vertex name='node' instance='0x0' fmri='hc:///node=0'> + + <nvpair name='property-groups' type='nvlist-array'> + <nvlist> + <nvpair name='property-group-name' type='string' value='protocol' /> + <nvpair name='property-values' type='nvlist-array'> + <nvlist> + <nvpair name='property-name' type='string' value='resource' /> + <nvpair name='property-type' type='uint32' value='9' /> + <nvpair name='property-value' type='nvlist'> + <nvlist> + <nvpair name='scheme' type='string' value='hc' /> + <nvpair name='version' type='uint8' value='0' /> + <nvpair name='hc-list' type='nvlist-array'> + <nvlist> + <nvpair name='hc-name' type='string' value='node' /> + <nvpair name='hc-id' type='string' value='0' /> + </nvlist> + </nvpair> + </nvlist> + </nvpair> <!-- property-value --> + </nvlist> + </nvpair> <!-- property-values --> + </nvlist> + + </nvpair> <!-- property-groups --> + + <outgoing-edges> + <!-- non-existent edge --> + <edge fmri='hc:///node=1' /> + </outgoing-edges> + +</vertex> + +</vertices> +</topo-digraph> diff --git a/usr/src/test/os-tests/tests/libtopo/digraph-test-in-badelement.xml b/usr/src/test/os-tests/tests/libtopo/digraph-test-in-badelement.xml new file mode 100644 index 0000000000..374bcc85cc --- /dev/null +++ b/usr/src/test/os-tests/tests/libtopo/digraph-test-in-badelement.xml @@ -0,0 +1,42 @@ +<?xml version="1.0"?> +<!DOCTYPE topology SYSTEM "/usr/share/lib/xml/dtd/digraph-topology.dtd.1"> + +<!-- + Copyright 2020 Joyent, Inc. + +--> + +<topo-digraph fmri-scheme='hc' nodename='test-nodename' os-version='test-os-version' product-id='test-product-id' timestamp='2019-12-20T01:51:26Z'> +<vertices> +<vertex name='node' instance='0x0' fmri='hc:///node=0'> + + <nvpair name='property-groups' type='nvlist-array'> + <!-- DTD violation: bad element --> + <badelement> + <nvpair name='property-group-name' type='string' value='protocol' /> + <nvpair name='property-values' type='nvlist-array'> + <nvlist> + <nvpair name='property-name' type='string' value='resource' /> + <nvpair name='property-type' type='uint32' value='9' /> + <nvpair name='property-value' type='nvlist'> + <nvlist> + <nvpair name='scheme' type='string' value='hc' /> + <nvpair name='version' type='uint8' value='0' /> + <nvpair name='hc-list' type='nvlist-array'> + <nvlist> + <nvpair name='hc-name' type='string' value='node' /> + <nvpair name='hc-id' type='string' value='0' /> + </nvlist> + </nvpair> + </nvlist> + </nvpair> <!-- property-value --> + </nvlist> + </nvpair> <!-- property-values --> + </nvlist> + + </nvpair> <!-- property-groups --> + +</vertex> + +</vertices> +</topo-digraph> diff --git a/usr/src/test/os-tests/tests/libtopo/digraph-test-in-badnum.xml b/usr/src/test/os-tests/tests/libtopo/digraph-test-in-badnum.xml new file mode 100644 index 0000000000..28f737dfb6 --- /dev/null +++ b/usr/src/test/os-tests/tests/libtopo/digraph-test-in-badnum.xml @@ -0,0 +1,42 @@ +<?xml version="1.0"?> +<!DOCTYPE topology SYSTEM "/usr/share/lib/xml/dtd/digraph-topology.dtd.1"> + +<!-- + Copyright 2020 Joyent, Inc. + +--> + +<topo-digraph fmri-scheme='hc' nodename='test-nodename' os-version='test-os-version' product-id='test-product-id' timestamp='2019-12-20T01:51:26Z'> +<vertices> +<vertex name='node' instance='0x0' fmri='hc:///node=0'> + + <nvpair name='property-groups' type='nvlist-array'> + <nvlist> + <nvpair name='property-group-name' type='string' value='protocol' /> + <nvpair name='property-values' type='nvlist-array'> + <nvlist> + <nvpair name='property-name' type='string' value='resource' /> + <!-- bad numeric value --> + <nvpair name='property-type' type='uint32' value='gdfgdfgdffg' /> + <nvpair name='property-value' type='nvlist'> + <nvlist> + <nvpair name='scheme' type='string' value='hc' /> + <nvpair name='version' type='uint8' value='0' /> + <nvpair name='hc-list' type='nvlist-array'> + <nvlist> + <nvpair name='hc-name' type='string' value='node' /> + <nvpair name='hc-id' type='string' value='0' /> + </nvlist> + </nvpair> + </nvlist> + </nvpair> <!-- property-value --> + </nvlist> + </nvpair> <!-- property-values --> + </nvlist> + + </nvpair> <!-- property-groups --> + +</vertex> + +</vertices> +</topo-digraph> diff --git a/usr/src/test/os-tests/tests/libtopo/digraph-test-in-badscheme.xml b/usr/src/test/os-tests/tests/libtopo/digraph-test-in-badscheme.xml new file mode 100644 index 0000000000..254519692d --- /dev/null +++ b/usr/src/test/os-tests/tests/libtopo/digraph-test-in-badscheme.xml @@ -0,0 +1,42 @@ +<?xml version="1.0"?> +<!DOCTYPE topology SYSTEM "/usr/share/lib/xml/dtd/digraph-topology.dtd.1"> + +<!-- + Copyright 2020 Joyent, Inc. + +--> + +<!-- invalid fmri-scheme value --> +<topo-digraph fmri-scheme='badscheme' nodename='test-nodename' os-version='test-os-version' product-id='test-product-id' timestamp='2019-12-20T01:51:26Z'> +<vertices> +<vertex name='node' instance='0x0' fmri='hc:///node=0'> + + <nvpair name='property-groups' type='nvlist-array'> + <nvlist> + <nvpair name='property-group-name' type='string' value='protocol' /> + <nvpair name='property-values' type='nvlist-array'> + <nvlist> + <nvpair name='property-name' type='string' value='resource' /> + <nvpair name='property-type' type='uint32' value='9' /> + <nvpair name='property-value' type='nvlist'> + <nvlist> + <nvpair name='scheme' type='string' value='hc' /> + <nvpair name='version' type='uint8' value='0' /> + <nvpair name='hc-list' type='nvlist-array'> + <nvlist> + <nvpair name='hc-name' type='string' value='node' /> + <nvpair name='hc-id' type='string' value='0' /> + </nvlist> + </nvpair> + </nvlist> + </nvpair> <!-- property-value --> + </nvlist> + </nvpair> <!-- property-values --> + </nvlist> + + </nvpair> <!-- property-groups --> + +</vertex> + +</vertices> +</topo-digraph> diff --git a/usr/src/test/os-tests/tests/libtopo/digraph-test-in.xml b/usr/src/test/os-tests/tests/libtopo/digraph-test-in.xml new file mode 100644 index 0000000000..679e1f834b --- /dev/null +++ b/usr/src/test/os-tests/tests/libtopo/digraph-test-in.xml @@ -0,0 +1,308 @@ +<?xml version="1.0"?> +<!DOCTYPE topology SYSTEM "/usr/share/lib/xml/dtd/digraph-topology.dtd.1"> + +<!-- + Copyright 2020 Joyent, Inc. + + This XML represents a directed graph that looks like the following: + + |===> node=2 === + | | + node=0 ====> node=1 === |===> node=4 == + | | | + |===> node=3 === | + ^ ^ | + node=5 | | | + | | | + node=6 ======================= ====================== + +--> + +<topo-digraph fmri-scheme='hc' nodename='test-nodename' os-version='test-os-version' product-id='test-product-id' timestamp='2019-12-20T01:51:26Z'> +<vertices> +<vertex name='node' instance='0x0' fmri='hc:///node=0'> + + <nvpair name='property-groups' type='nvlist-array'> + <nvlist> + <nvpair name='property-group-name' type='string' value='test-pg-1' /> + <nvpair name='property-values' type='nvlist-array'> + + <nvlist> + <nvpair name='property-name' type='string' value='string-prop' /> + <nvpair name='property-type' type='uint32' value='6' /> + <nvpair name='property-value' type='string' value='blahblahblah' /> + </nvlist> + + <nvlist> + <nvpair name='property-name' type='string' value='uint64-array-prop' /> + <nvpair name='property-type' type='uint32' value='13' /> + <nvpair name='property-value' type='uint64-array'> + <nvpair value='0x1' /> + <nvpair value='0x2' /> + </nvpair> + </nvlist> + + <nvlist> + <nvpair name='property-name' type='string' value='int64-array-prop' /> + <nvpair name='property-type' type='uint32' value='12' /> + <nvpair name='property-value' type='int64-array'> + <nvpair value='1' /> + <nvpair value='2' /> + </nvpair> + </nvlist> + + <nvlist> + <nvpair name='property-name' type='string' value='uint32-array-prop' /> + <nvpair name='property-type' type='uint32' value='11' /> + <nvpair name='property-value' type='uint32-array'> + <nvpair value='1' /> + <nvpair value='2' /> + </nvpair> + </nvlist> + + <nvlist> + <nvpair name='property-name' type='string' value='int32-array-prop' /> + <nvpair name='property-type' type='uint32' value='10' /> + <nvpair name='property-value' type='int32-array'> + <nvpair value='1' /> + <nvpair value='2' /> + </nvpair> + </nvlist> + + <nvlist> + <nvpair name='property-name' type='string' value='uint64-prop' /> + <nvpair name='property-type' type='uint32' value='5' /> + <nvpair name='property-value' type='uint64' value='0x5003048023567a00' /> + </nvlist> + + <nvlist> + <nvpair name='property-name' type='string' value='uint32-prop' /> + <nvpair name='property-type' type='uint32' value='3' /> + <nvpair name='property-value' type='uint32' value='1' /> + </nvlist> + + </nvpair> <!-- property-values --> + </nvlist> + <nvlist> + <nvpair name='property-group-name' type='string' value='protocol' /> + <nvpair name='property-values' type='nvlist-array'> + <nvlist> + <nvpair name='property-name' type='string' value='resource' /> + <nvpair name='property-type' type='uint32' value='9' /> + <nvpair name='property-value' type='nvlist'> + <nvlist> + <nvpair name='scheme' type='string' value='hc' /> + <nvpair name='version' type='uint8' value='0' /> + <nvpair name='hc-list' type='nvlist-array'> + <nvlist> + <nvpair name='hc-name' type='string' value='node' /> + <nvpair name='hc-id' type='string' value='0' /> + </nvlist> + </nvpair> + </nvlist> + </nvpair> <!-- property-value --> + </nvlist> + </nvpair> <!-- property-values --> + </nvlist> + + </nvpair> <!-- property-groups --> + <outgoing-edges> + <edge fmri='hc:///node=1' /> + </outgoing-edges> + +</vertex> + +<vertex name='node' instance='0x1' fmri='hc:///node=1'> + + <nvpair name='property-groups' type='nvlist-array'> + + <nvlist> + <nvpair name='property-group-name' type='string' value='protocol' /> + <nvpair name='property-values' type='nvlist-array'> + <nvlist> + <nvpair name='property-name' type='string' value='resource' /> + <nvpair name='property-type' type='uint32' value='9' /> + <nvpair name='property-value' type='nvlist'> + <nvlist> + <nvpair name='scheme' type='string' value='hc' /> + <nvpair name='version' type='uint8' value='0' /> + <nvpair name='hc-list' type='nvlist-array'> + <nvlist> + <nvpair name='hc-name' type='string' value='node' /> + <nvpair name='hc-id' type='string' value='1' /> + </nvlist> + </nvpair> + </nvlist> + </nvpair> <!-- property-value --> + </nvlist> + </nvpair> <!-- property-values --> + </nvlist> + </nvpair> <!-- property-groups --> + <outgoing-edges> + <edge fmri='hc:///node=2' /> + <edge fmri='hc:///node=3' /> + </outgoing-edges> + +</vertex> + +<vertex name='node' instance='0x2' fmri='hc:///node=2'> + + <nvpair name='property-groups' type='nvlist-array'> + + <nvlist> + <nvpair name='property-group-name' type='string' value='protocol' /> + <nvpair name='property-values' type='nvlist-array'> + <nvlist> + <nvpair name='property-name' type='string' value='resource' /> + <nvpair name='property-type' type='uint32' value='9' /> + <nvpair name='property-value' type='nvlist'> + <nvlist> + <nvpair name='scheme' type='string' value='hc' /> + <nvpair name='version' type='uint8' value='0' /> + <nvpair name='hc-list' type='nvlist-array'> + <nvlist> + <nvpair name='hc-name' type='string' value='node' /> + <nvpair name='hc-id' type='string' value='2' /> + </nvlist> + </nvpair> + </nvlist> + </nvpair> <!-- property-value --> + </nvlist> + </nvpair> <!-- property-values --> + </nvlist> + + </nvpair> <!-- property-groups --> + <outgoing-edges> + <edge fmri='hc:///node=4' /> + </outgoing-edges> + +</vertex> + +<vertex name='node' instance='0x3' fmri='hc:///node=3'> + + <nvpair name='property-groups' type='nvlist-array'> + + <nvlist> + <nvpair name='property-group-name' type='string' value='protocol' /> + <nvpair name='property-values' type='nvlist-array'> + <nvlist> + <nvpair name='property-name' type='string' value='resource' /> + <nvpair name='property-type' type='uint32' value='9' /> + <nvpair name='property-value' type='nvlist'> + <nvlist> + <nvpair name='scheme' type='string' value='hc' /> + <nvpair name='version' type='uint8' value='0' /> + <nvpair name='hc-list' type='nvlist-array'> + <nvlist> + <nvpair name='hc-name' type='string' value='node' /> + <nvpair name='hc-id' type='string' value='3' /> + </nvlist> + </nvpair> + </nvlist> + </nvpair> <!-- property-value --> + </nvlist> + </nvpair> <!-- property-values --> + </nvlist> + + </nvpair> <!-- property-groups --> + <outgoing-edges> + <edge fmri='hc:///node=4' /> + </outgoing-edges> + +</vertex> + +<vertex name='node' instance='0x4' fmri='hc:///node=4'> + + <nvpair name='property-groups' type='nvlist-array'> + <nvlist> + <nvpair name='property-group-name' type='string' value='protocol' /> + <nvpair name='property-values' type='nvlist-array'> + <nvlist> + <nvpair name='property-name' type='string' value='resource' /> + <nvpair name='property-type' type='uint32' value='9' /> + <nvpair name='property-value' type='nvlist'> + <nvlist> + <nvpair name='scheme' type='string' value='hc' /> + <nvpair name='version' type='uint8' value='0' /> + <nvpair name='hc-list' type='nvlist-array'> + <nvlist> + <nvpair name='hc-name' type='string' value='node' /> + <nvpair name='hc-id' type='string' value='4' /> + </nvlist> + </nvpair> + </nvlist> + </nvpair> <!-- property-value --> + </nvlist> + </nvpair> <!-- property-values --> + </nvlist> + + </nvpair> <!-- property-groups --> + <outgoing-edges> + <edge fmri='hc:///node=3' /> + </outgoing-edges> + +</vertex> + +<vertex name='node' instance='0x5' fmri='hc:///node=5'> + + <nvpair name='property-groups' type='nvlist-array'> + <nvlist> + <nvpair name='property-group-name' type='string' value='protocol' /> + <nvpair name='property-values' type='nvlist-array'> + <nvlist> + <nvpair name='property-name' type='string' value='resource' /> + <nvpair name='property-type' type='uint32' value='9' /> + <nvpair name='property-value' type='nvlist'> + <nvlist> + <nvpair name='scheme' type='string' value='hc' /> + <nvpair name='version' type='uint8' value='0' /> + <nvpair name='hc-list' type='nvlist-array'> + <nvlist> + <nvpair name='hc-name' type='string' value='node' /> + <nvpair name='hc-id' type='string' value='5' /> + </nvlist> + </nvpair> + </nvlist> + </nvpair> <!-- property-value --> + </nvlist> + </nvpair> <!-- property-values --> + </nvlist> + + </nvpair> <!-- property-groups --> + +</vertex> + +<vertex name='node' instance='0x6' fmri='hc:///node=6'> + + <nvpair name='property-groups' type='nvlist-array'> + <nvlist> + <nvpair name='property-group-name' type='string' value='protocol' /> + <nvpair name='property-values' type='nvlist-array'> + <nvlist> + <nvpair name='property-name' type='string' value='resource' /> + <nvpair name='property-type' type='uint32' value='9' /> + <nvpair name='property-value' type='nvlist'> + <nvlist> + <nvpair name='scheme' type='string' value='hc' /> + <nvpair name='version' type='uint8' value='0' /> + <nvpair name='hc-list' type='nvlist-array'> + <nvlist> + <nvpair name='hc-name' type='string' value='node' /> + <nvpair name='hc-id' type='string' value='6' /> + </nvlist> + </nvpair> + </nvlist> + </nvpair> <!-- property-value --> + </nvlist> + </nvpair> <!-- property-values --> + </nvlist> + + </nvpair> <!-- property-groups --> + <outgoing-edges> + <edge fmri='hc:///node=3' /> + </outgoing-edges> + +</vertex> + +</vertices> +</topo-digraph> diff --git a/usr/src/test/os-tests/tests/libtopo/digraph-test.c b/usr/src/test/os-tests/tests/libtopo/digraph-test.c new file mode 100644 index 0000000000..b829f0bb8f --- /dev/null +++ b/usr/src/test/os-tests/tests/libtopo/digraph-test.c @@ -0,0 +1,380 @@ +/* + * This file and its contents are supplied under the terms of the + * Common Development and Distribution License ("CDDL"), version 1.0. + * You may only use this file in accordance with the terms of version + * 1.0 of the CDDL. + * + * A full copy of the text of the CDDL should have accompanied this + * source. A copy of the CDDL is also available via the Internet at + * http://www.illumos.org/license/CDDL. + */ + +/* + * Copyright 2020 Joyent, Inc. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <fcntl.h> +#include <libnvpair.h> +#include <string.h> +#include <stropts.h> +#include <unistd.h> +#include <fm/libtopo.h> +#include <sys/debug.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/varargs.h> + + +#define TEST_HOME "/opt/os-tests/tests/libtopo/" +#define TEST_XML_IN "digraph-test-in.xml" +#define TEST_XML_IN_BADSCHEME "digraph-test-in-badscheme.xml" +#define TEST_XML_IN_BADNUM "digraph-test-in-badnum.xml" +#define TEST_XML_IN_BADEDGE "digraph-test-in-badedge.xml" +#define TEST_XML_IN_BADELEMENT "digraph-test-in-badelement.xml" +#define TEST_GRAPH_SZ 7 +#define TEST_XML_OUT_DIR "/var/tmp" +#define TEST_XML_OUT_PREFIX "digraph-test-out" + +static const char *pname; + +extern int topo_hdl_errno(topo_hdl_t *); + +/* + * Generate an ISO 8601 timestamp + */ +static void +get_timestamp(char *buf, size_t bufsize) +{ + time_t utc_time; + struct tm *p_tm; + + (void) time(&utc_time); + p_tm = localtime(&utc_time); + + (void) strftime(buf, bufsize, "%FT%TZ", p_tm); +} + +/* PRINTFLIKE1 */ +static void +logmsg(const char *format, ...) +{ + char timestamp[128]; + va_list ap; + + get_timestamp(timestamp, sizeof (timestamp)); + (void) fprintf(stdout, "%s ", timestamp); + va_start(ap, format); + (void) vfprintf(stdout, format, ap); + va_end(ap); + (void) fprintf(stdout, "\n"); + (void) fflush(stdout); +} + +static topo_digraph_t * +test_deserialize(topo_hdl_t *thp, const char *path) +{ + struct stat statbuf = { 0 }; + char *buf = NULL; + int fd = -1; + topo_digraph_t *tdg = NULL; + + logmsg("\tOpening test XML topology"); + if ((fd = open(path, O_RDONLY)) < 0) { + logmsg("\tfailed to open %s (%s)", path, strerror(errno)); + goto out; + } + if (fstat(fd, &statbuf) != 0) { + logmsg("\tfailed to stat %s (%s)", path, strerror(errno)); + goto out; + } + if ((buf = malloc(statbuf.st_size)) == NULL) { + logmsg("\tfailed to alloc read buffer: (%s)", strerror(errno)); + goto out; + } + if (read(fd, buf, statbuf.st_size) != statbuf.st_size) { + logmsg("\tfailed to read file: (%s)", strerror(errno)); + goto out; + } + + logmsg("\tDeserializing XML topology"); + tdg = topo_digraph_deserialize(thp, buf, statbuf.st_size); + if (tdg == NULL) { + logmsg("\ttopo_digraph_deserialize() failed!"); + goto out; + } + logmsg("\ttopo_digraph_deserialize() succeeded"); +out: + free(buf); + if (fd > 0) { + (void) close(fd); + } + return (tdg); +} + +struct cb_arg { + topo_vertex_t **vertices; +}; + +static int +test_paths_cb(topo_hdl_t *thp, topo_vertex_t *vtx, boolean_t last_vtx, + void *arg) +{ + struct cb_arg *cbarg = arg; + uint_t idx = topo_node_instance(topo_vertex_node(vtx)); + + cbarg->vertices[idx] = vtx; + + return (TOPO_WALK_NEXT); +} + +static int +test_paths(topo_hdl_t *thp, topo_digraph_t *tdg) +{ + topo_vertex_t *vertices[TEST_GRAPH_SZ]; + struct cb_arg cbarg = { 0 }; + int ret = -1; + topo_path_t **paths; + uint_t np; + + cbarg.vertices = vertices; + if (topo_vertex_iter(thp, tdg, test_paths_cb, &cbarg) != 0) { + logmsg("\tfailed to iterate over graph vertices"); + goto out; + } + + logmsg("\tCalculating number of paths between node 0 and node 4"); + if (topo_digraph_paths(thp, tdg, vertices[0], vertices[4], &paths, + &np) < 0) { + logmsg("\ttopo_digraph_paths() failed"); + goto out; + } + if (np != 2) { + logmsg("\t%d paths found (expected 2)", np); + goto out; + } + for (uint_t i = 0; i < np; i++) { + topo_path_destroy(thp, paths[i]); + } + topo_hdl_free(thp, paths, np * sizeof (topo_path_t *)); + + logmsg("\tCalculating number of paths between node 6 and node 4"); + if (topo_digraph_paths(thp, tdg, vertices[6], vertices[4], &paths, + &np) < 0) { + logmsg("\ttopo_digraph_paths() failed"); + goto out; + } + if (np != 1) { + logmsg("\t%d paths found (expected 1)", np); + goto out; + } + for (uint_t i = 0; i < np; i++) { + topo_path_destroy(thp, paths[i]); + } + topo_hdl_free(thp, paths, np * sizeof (topo_path_t *)); + + logmsg("\tCalculating number of paths between node 5 and node 1"); + if (topo_digraph_paths(thp, tdg, vertices[5], vertices[1], &paths, + &np) < 0) { + logmsg("\ttopo_digraph_paths() failed"); + goto out; + } + if (np != 0) { + logmsg("\t%d paths found (expected 0)", np); + goto out; + } + ret = 0; + +out: + if (np > 0) { + for (uint_t i = 0; i < np; i++) { + topo_path_destroy(thp, paths[i]); + } + topo_hdl_free(thp, paths, np * sizeof (topo_path_t *)); + } + return (ret); +} + +static int +test_serialize(topo_hdl_t *thp, topo_digraph_t *tdg, const char *path) +{ + FILE *xml_out; + + if ((xml_out = fopen(path, "w")) == NULL) { + logmsg("\tfailed to open %s for writing (%s)", + strerror(errno)); + return (-1); + } + logmsg("\tSerializing topology to XML (%s)", path); + if (topo_digraph_serialize(thp, tdg, xml_out) != 0) { + logmsg("\ttopo_digraph_serialize() failed!"); + (void) fclose(xml_out); + return (-1); + } + (void) fclose(xml_out); + return (0); +} + +int +main(int argc, char **argv) +{ + topo_hdl_t *thp = NULL; + topo_digraph_t *tdg; + char *root = "/", *out_path = NULL; + boolean_t abort_on_exit = B_FALSE; + int err, status = EXIT_FAILURE; + + pname = argv[0]; + + /* + * Setting DIGRAPH_TEST_CORE causes us to abort and dump core before + * exiting. This is useful for examining for memory leaks. + */ + if (getenv("DIGRAPH_TEST_CORE") != NULL) { + abort_on_exit = B_TRUE; + } + + logmsg("Opening libtopo"); + if ((thp = topo_open(TOPO_VERSION, root, &err)) == NULL) { + logmsg("failed to get topo handle: %s", topo_strerror(err)); + goto out; + } + + logmsg("TEST: Deserialize directed graph topology"); + if ((tdg = test_deserialize(thp, TEST_HOME TEST_XML_IN)) == NULL) { + logmsg("FAIL"); + goto out; + } + logmsg("PASS"); + + logmsg("TEST: Serialize directed graph topology"); + if ((out_path = tempnam(TEST_XML_OUT_DIR, TEST_XML_OUT_PREFIX)) == + NULL) { + logmsg("\tFailed to create temporary file name under %s (%s)", + TEST_XML_OUT_DIR, strerror(errno)); + logmsg("FAIL"); + goto out; + } + if (test_serialize(thp, tdg, out_path) != 0) { + logmsg("FAIL"); + goto out; + } + logmsg("PASS"); + + logmsg("Closing libtopo"); + topo_close(thp); + + logmsg("Reopening libtopo"); + if ((thp = topo_open(TOPO_VERSION, root, &err)) == NULL) { + logmsg("failed to get topo handle: %s", topo_strerror(err)); + goto out; + } + + logmsg("TEST: Deserialize directed graph topology (pass 2)"); + if ((tdg = test_deserialize(thp, out_path)) == NULL) { + logmsg("FAIL"); + goto out; + } + logmsg("PASS"); + + logmsg("TEST: Calculating paths between vertices"); + if (test_paths(thp, tdg) != 0) { + logmsg("FAIL"); + goto out; + } + logmsg("PASS"); + + logmsg("Closing libtopo"); + topo_close(thp); + + logmsg("Reopening libtopo"); + if ((thp = topo_open(TOPO_VERSION, root, &err)) == NULL) { + logmsg("failed to get topo handle: %s", topo_strerror(err)); + goto out; + } + + /* + * The following tests attempt to deserialize XML files that either + * violate the DTD or contain invalid attribute values. + * + * The expection is that topo_digraph_deserialize() should fail + * gracefully (i.e. not segfault) and topo_errno should be set. + */ + logmsg("TEST: Deserialize directed graph topology (bad scheme)"); + if ((tdg = test_deserialize(thp, TEST_HOME TEST_XML_IN_BADSCHEME)) != + NULL) { + logmsg("FAIL"); + goto out; + } else if (topo_hdl_errno(thp) == 0) { + logmsg("\texpected topo_errno to be non-zero"); + logmsg("FAIL"); + goto out; + } else { + logmsg("PASS"); + } + + logmsg("TEST: Deserialize directed graph topology (bad number)"); + if ((tdg = test_deserialize(thp, TEST_HOME TEST_XML_IN_BADNUM)) != + NULL) { + logmsg("FAIL"); + goto out; + } else if (topo_hdl_errno(thp) == 0) { + logmsg("\texpected topo_errno to be non-zero"); + logmsg("FAIL"); + goto out; + } else { + logmsg("PASS"); + } + + logmsg("TEST: Deserialize directed graph topology (bad edge)"); + if ((tdg = test_deserialize(thp, TEST_HOME TEST_XML_IN_BADEDGE)) != + NULL) { + logmsg("FAIL"); + goto out; + } else if (topo_hdl_errno(thp) == 0) { + logmsg("\texpected topo_errno to be non-zero"); + logmsg("FAIL"); + goto out; + } else { + logmsg("PASS"); + } + + logmsg("TEST: Deserialize directed graph topology (bad element)"); + if ((tdg = test_deserialize(thp, TEST_HOME TEST_XML_IN_BADELEMENT)) != + NULL) { + logmsg("FAIL"); + goto out; + } else if (topo_hdl_errno(thp) == 0) { + logmsg("\texpected topo_errno to be non-zero"); + logmsg("FAIL"); + goto out; + } else { + logmsg("PASS"); + } + + /* + * If any tests failed, we don't unlink the temp file, as its contents + * may be useful for root-causing the test failure. + */ + if (unlink(out_path) != 0) { + logmsg("Failed to unlink temp file: %s (%s)", out_path, + strerror(errno)); + } + status = EXIT_SUCCESS; +out: + if (thp != NULL) { + topo_close(thp); + } + if (out_path != NULL) { + free(out_path); + } + logmsg("digraph tests %s", + status == EXIT_SUCCESS ? "passed" : "failed"); + + if (abort_on_exit) { + abort(); + } + return (status); +} diff --git a/usr/src/uts/common/sys/fm/protocol.h b/usr/src/uts/common/sys/fm/protocol.h index 5eca760dad..e0140bb0fb 100644 --- a/usr/src/uts/common/sys/fm/protocol.h +++ b/usr/src/uts/common/sys/fm/protocol.h @@ -21,6 +21,7 @@ /* * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright 2020 Joyent, Inc. */ #ifndef _SYS_FM_PROTOCOL_H @@ -200,6 +201,7 @@ extern "C" { #define FM_FMRI_SCHEME_LEGACY "legacy-hc" #define FM_FMRI_SCHEME_ZFS "zfs" #define FM_FMRI_SCHEME_SW "sw" +#define FM_FMRI_SCHEME_PATH "path" /* Scheme versions */ #define FMD_SCHEME_VERSION0 0 @@ -225,6 +227,8 @@ extern "C" { #define FM_ZFS_SCHEME_VERSION ZFS_SCHEME_VERSION0 #define SW_SCHEME_VERSION0 0 #define FM_SW_SCHEME_VERSION SW_SCHEME_VERSION0 +#define PATH_SCHEME_VERSION0 0 +#define FM_PATH_SCHEME_VERSION PATH_SCHEME_VERSION0 /* hc scheme member names */ #define FM_FMRI_HC_SERIAL_ID "serial" @@ -328,6 +332,13 @@ extern "C" { #define FM_FMRI_SW_CTXT_CTID "ctid" #define FM_FMRI_SW_CTXT_STACK "stack" +/* path scheme member names */ +#define FM_FMRI_PATH_VERSION "path-scheme-version" +#define FM_FMRI_PATH "path" +#define FM_FMRI_PATH_NAME "path-name" +#define FM_FMRI_PATH_INST "path-instance" +#define FM_FMRI_PATH_DIGRAPH_SCHEME "path-digraph-scheme" + extern nv_alloc_t *fm_nva_xcreate(char *, size_t); extern void fm_nva_xdestroy(nv_alloc_t *); |