summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRob Johnston <rob.johnston@joyent.com>2020-02-20 16:38:42 -0800
committerDan McDonald <danmcd@joyent.com>2020-04-03 15:03:01 -0400
commitc559157643fef9f9afb0414e00a3579407ba3052 (patch)
tree108a39e69361244fbd351d0ad1994bc50c3f6025
parent3afb2a2ae94e9183977495f0d248b766e58e4016 (diff)
downloadillumos-joyent-c559157643fef9f9afb0414e00a3579407ba3052.tar.gz
12330 libtopo: add support for directed graph based topologies
Portions contributed by: Kody Kantor <kody.kantor@joyent.com> Reviewed by: Robert Mustacchi <rm@fingolfin.org> Approved by: Dan McDonald <danmcd@joyent.com>
-rw-r--r--exception_lists/manlint3
-rw-r--r--usr/src/lib/fm/topo/libtopo/Makefile.com4
-rw-r--r--usr/src/lib/fm/topo/libtopo/common/hc.c4
-rw-r--r--usr/src/lib/fm/topo/libtopo/common/libtopo.h34
-rw-r--r--usr/src/lib/fm/topo/libtopo/common/mapfile-vers16
-rw-r--r--usr/src/lib/fm/topo/libtopo/common/topo_builtin.c79
-rw-r--r--usr/src/lib/fm/topo/libtopo/common/topo_builtin.h9
-rw-r--r--usr/src/lib/fm/topo/libtopo/common/topo_digraph.c1035
-rw-r--r--usr/src/lib/fm/topo/libtopo/common/topo_digraph.h64
-rw-r--r--usr/src/lib/fm/topo/libtopo/common/topo_digraph_xml.c1502
-rw-r--r--usr/src/lib/fm/topo/libtopo/common/topo_digraph_xml.h88
-rw-r--r--usr/src/lib/fm/topo/libtopo/common/topo_fmri.c72
-rw-r--r--usr/src/lib/fm/topo/libtopo/common/topo_method.h5
-rw-r--r--usr/src/lib/fm/topo/libtopo/common/topo_mod.h16
-rw-r--r--usr/src/lib/fm/topo/libtopo/common/topo_mod.map8
-rw-r--r--usr/src/lib/fm/topo/libtopo/common/topo_node.c22
-rw-r--r--usr/src/lib/fm/topo/libtopo/common/topo_snap.c36
-rw-r--r--usr/src/lib/fm/topo/libtopo/common/topo_subr.c9
-rw-r--r--usr/src/lib/fm/topo/libtopo/common/topo_tree.h4
-rw-r--r--usr/src/lib/fm/topo/libtopo/common/topo_xml.c10
-rw-r--r--usr/src/lib/fm/topo/maps/Makefile.map17
-rw-r--r--usr/src/lib/fm/topo/maps/common/digraph-topology.dtd.152
-rw-r--r--usr/src/lib/fm/topo/modules/common/ses/ses.c6
-rw-r--r--usr/src/pkg/manifests/service-fault-management.mf4
-rw-r--r--usr/src/pkg/manifests/system-test-ostest.mf7
-rw-r--r--usr/src/test/os-tests/runfiles/default.run4
-rw-r--r--usr/src/test/os-tests/tests/Makefile1
-rw-r--r--usr/src/test/os-tests/tests/libtopo/Makefile58
-rw-r--r--usr/src/test/os-tests/tests/libtopo/digraph-test-in-badedge.xml46
-rw-r--r--usr/src/test/os-tests/tests/libtopo/digraph-test-in-badelement.xml42
-rw-r--r--usr/src/test/os-tests/tests/libtopo/digraph-test-in-badnum.xml42
-rw-r--r--usr/src/test/os-tests/tests/libtopo/digraph-test-in-badscheme.xml42
-rw-r--r--usr/src/test/os-tests/tests/libtopo/digraph-test-in.xml308
-rw-r--r--usr/src/test/os-tests/tests/libtopo/digraph-test.c380
-rw-r--r--usr/src/uts/common/sys/fm/protocol.h11
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 *);