1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22/*
23 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
24 * Use is subject to license terms.
25 */
26/*
27 * Copyright (c) 2019, Joyent, Inc. All rights reserved.
28 */
29
30#include <stdio.h>
31#include <strings.h>
32#include <time.h>
33#include <sys/types.h>
34#include <sys/fm/protocol.h>
35#include <sys/utsname.h>
36
37#include <topo_parse.h>
38#include <topo_prop.h>
39#include <topo_tree.h>
40
41/*
42 * In the XML representation of the topo snapshot, 32-bit integer values are
43 * represented as base-10 values.
44 *
45 * 10 bytes for base-10 value + 1 for sign + nul
46 */
47#define	INT32BUFSZ	12
48/*
49 * Buffer that is large enough to hold the string representation of any signed
50 * or unsigned 64-bit integer.
51 *
52 * 2 bytes for "0x" + 16 bytes for the base-16 value + nul
53 * or
54 * 19 bytes for base-10 value + 1 for sign + nul
55 */
56#define	INT64BUFSZ	21
57#define	XML_VERSION	"1.0"
58
59static int txml_print_range(topo_hdl_t *, FILE *, tnode_t *, int);
60
61void
62print_header(FILE *fp)
63{
64	char buf[32];
65	time_t tod = time(NULL);
66	struct utsname uts;
67
68	(void) fprintf(fp, "<?xml version=\"%s\"?>\n", XML_VERSION);
69	(void) fprintf(fp, "<!DOCTYPE topology SYSTEM \"%s\">\n",
70	    TOPO_DTD_PATH);
71
72	(void) uname(&uts);
73	(void) strftime(buf, sizeof (buf), "%b %d %T", localtime(&tod));
74	(void) fprintf(fp, "<!--\n");
75	(void) fprintf(fp, " This topology map file was generated on "
76	    "%-15s for %s\n", buf, uts.nodename);
77	(void) fprintf(fp, "<-->\n\n");
78}
79
80void
81begin_element(FILE *fp, const char *ename, ...)
82{
83	char *name, *value;
84	va_list ap;
85
86	(void) fprintf(fp, "<%s ", ename);
87	va_start(ap, ename);
88	name = va_arg(ap, char *);
89	while (name != NULL) {
90		value = va_arg(ap, char *);
91		(void) fprintf(fp, "%s='%s' ", name, value);
92		name = va_arg(ap, char *);
93	}
94	(void) fprintf(fp, ">\n");
95}
96
97void
98begin_end_element(FILE *fp, const char *ename, ...)
99{
100	char *name, *value;
101	va_list ap;
102
103	(void) fprintf(fp, "<%s ", ename);
104	va_start(ap, ename);
105	name = va_arg(ap, char *);
106	while (name != NULL) {
107		value = va_arg(ap, char *);
108		(void) fprintf(fp, "%s='%s' ", name, value);
109		name = va_arg(ap, char *);
110	}
111	(void) fprintf(fp, "/>\n");
112}
113
114void
115end_element(FILE *fp, const char *ename)
116{
117	(void) fprintf(fp, "</%s>\n", ename);
118}
119
120static void
121txml_print_prop(topo_hdl_t *thp, FILE *fp, tnode_t *node, const char *pgname,
122    topo_propval_t *pv)
123{
124	int err;
125	uint_t nelem;
126	char vbuf[INT64BUFSZ];
127
128	switch (pv->tp_type) {
129		case TOPO_TYPE_INT32: {
130			int32_t val;
131
132			if (topo_prop_get_int32(node, pgname, pv->tp_name, &val,
133			    &err) != 0)
134				return;
135
136			(void) snprintf(vbuf, INT64BUFSZ, "%d", val);
137			begin_end_element(fp, Propval, Name, pv->tp_name, Type,
138			    Int32, Value, vbuf, NULL);
139			break;
140		}
141		case TOPO_TYPE_UINT32: {
142			uint32_t val;
143
144			if (topo_prop_get_uint32(node, pgname, pv->tp_name,
145			    &val, &err) != 0)
146				return;
147
148			(void) snprintf(vbuf, INT64BUFSZ, "0x%x", val);
149			begin_end_element(fp, Propval, Name, pv->tp_name, Type,
150			    UInt32, Value, vbuf, NULL);
151			break;
152		}
153		case TOPO_TYPE_INT64: {
154			int64_t val;
155
156			if (topo_prop_get_int64(node, pgname, pv->tp_name, &val,
157			    &err) != 0)
158				return;
159
160			(void) snprintf(vbuf, INT64BUFSZ, "%" PRId64, val);
161			begin_end_element(fp, Propval, Name, pv->tp_name, Type,
162			    Int64, Value, vbuf, NULL);
163			break;
164		}
165		case TOPO_TYPE_UINT64: {
166			uint64_t val;
167
168			if (topo_prop_get_uint64(node, pgname, pv->tp_name,
169			    &val, &err) != 0)
170				return;
171
172			(void) snprintf(vbuf, INT64BUFSZ, "0x%" PRIx64, val);
173			begin_end_element(fp, Propval, Name, pv->tp_name, Type,
174			    UInt64, Value, vbuf, NULL);
175			break;
176		}
177		case TOPO_TYPE_DOUBLE: {
178			double val;
179			char *dblstr = NULL;
180
181			if (topo_prop_get_double(node, pgname, pv->tp_name,
182			    &val, &err) != 0)
183				return;
184
185			/*
186			 * The %a format specifier allows floating point values
187			 * to be serialized without losing precision.
188			 */
189			if (asprintf(&dblstr, "%a", val) < 0)
190				return;
191			begin_end_element(fp, Propval, Name, pv->tp_name, Type,
192			    Double, Value, dblstr, NULL);
193			free(dblstr);
194			break;
195		}
196		case TOPO_TYPE_STRING: {
197			char *strbuf = NULL;
198
199			if (topo_prop_get_string(node, pgname, pv->tp_name,
200			    &strbuf, &err) != 0)
201				return;
202
203			begin_end_element(fp, Propval, Name, pv->tp_name, Type,
204			    String, Value, strbuf, NULL);
205			topo_hdl_strfree(thp, strbuf);
206			break;
207		}
208		case TOPO_TYPE_FMRI: {
209			nvlist_t *val = NULL;
210			char *fmristr = NULL;
211
212			if (topo_prop_get_fmri(node, pgname, pv->tp_name, &val,
213			    &err) != 0 ||
214			    topo_fmri_nvl2str(thp, val, &fmristr, &err) != 0) {
215				nvlist_free(val);
216				return;
217			}
218			nvlist_free(val);
219			begin_end_element(fp, Propval, Name, pv->tp_name, Type,
220			    FMRI, Value, fmristr, NULL);
221			topo_hdl_strfree(thp, fmristr);
222			break;
223		}
224		case TOPO_TYPE_INT32_ARRAY: {
225			int32_t *val;
226
227			if (topo_prop_get_int32_array(node, pgname,
228			    pv->tp_name, &val, &nelem, &err) != 0)
229				return;
230
231			begin_element(fp, Propval, Name, pv->tp_name, Type,
232			    Int32_Arr, NULL);
233
234			for (uint_t i = 0; i < nelem; i++) {
235				(void) snprintf(vbuf, INT64BUFSZ, "%d", val[i]);
236				begin_end_element(fp, Propitem, Value, vbuf,
237				    NULL);
238			}
239
240			topo_hdl_free(thp, val, nelem * sizeof (int32_t));
241			end_element(fp, Propval);
242			break;
243		}
244		case TOPO_TYPE_UINT32_ARRAY: {
245			uint32_t *val;
246
247			if (topo_prop_get_uint32_array(node, pgname,
248			    pv->tp_name, &val, &nelem, &err) != 0)
249				return;
250
251			begin_element(fp, Propval, Name, pv->tp_name, Type,
252			    UInt32_Arr, NULL);
253
254			for (uint_t i = 0; i < nelem; i++) {
255				(void) snprintf(vbuf, INT64BUFSZ, "0x%x",
256				    val[i]);
257				begin_end_element(fp, Propitem, Value, vbuf,
258				    NULL);
259			}
260
261			topo_hdl_free(thp, val, nelem * sizeof (uint32_t));
262			end_element(fp, Propval);
263			break;
264		}
265		case TOPO_TYPE_INT64_ARRAY: {
266			int64_t *val;
267
268			if (topo_prop_get_int64_array(node, pgname,
269			    pv->tp_name, &val, &nelem, &err) != 0)
270				return;
271
272			begin_element(fp, Propval, Name, pv->tp_name, Type,
273			    Int64_Arr, NULL);
274
275			for (uint_t i = 0; i < nelem; i++) {
276				(void) snprintf(vbuf, INT64BUFSZ, "%" PRId64,
277				    val[i]);
278				begin_end_element(fp, Propitem, Value, vbuf,
279				    NULL);
280			}
281
282			topo_hdl_free(thp, val, nelem * sizeof (int64_t));
283			end_element(fp, Propval);
284			break;
285		}
286		case TOPO_TYPE_UINT64_ARRAY: {
287			uint64_t *val;
288
289			if (topo_prop_get_uint64_array(node, pgname,
290			    pv->tp_name, &val, &nelem, &err) != 0)
291				return;
292
293			begin_element(fp, Propval, Name, pv->tp_name, Type,
294			    UInt64_Arr, NULL);
295
296			for (uint_t i = 0; i < nelem; i++) {
297				(void) snprintf(vbuf, INT64BUFSZ, "0x%" PRIx64,
298				    val[i]);
299				begin_end_element(fp, Propitem, Value, vbuf,
300				    NULL);
301			}
302
303			topo_hdl_free(thp, val, nelem * sizeof (uint64_t));
304			end_element(fp, Propval);
305			break;
306		}
307		case TOPO_TYPE_STRING_ARRAY: {
308			char **val;
309
310			if (topo_prop_get_string_array(node, pgname,
311			    pv->tp_name, &val, &nelem, &err) != 0)
312				return;
313
314			begin_element(fp, Propval, Name, pv->tp_name, Type,
315			    String_Arr, NULL);
316
317			for (uint_t i = 0; i < nelem; i++) {
318				begin_end_element(fp, Propitem, Value, val[i],
319				    NULL);
320			}
321			topo_hdl_strfreev(thp, val, nelem);
322
323			end_element(fp, Propval);
324			break;
325		}
326		case TOPO_TYPE_FMRI_ARRAY: {
327			nvlist_t **val;
328			char *fmristr = NULL;
329			int ret;
330
331			if (topo_prop_get_fmri_array(node, pgname,
332			    pv->tp_name, &val, &nelem, &err) != 0)
333				return;
334
335			begin_element(fp, Propval, Name, pv->tp_name, Type,
336			    FMRI_Arr, NULL);
337
338			for (uint_t i = 0; i < nelem; i++) {
339				if ((ret = topo_fmri_nvl2str(thp, val[i],
340				    &fmristr, &err)) != 0)
341					break;
342				begin_end_element(fp, Propitem, Value, fmristr,
343				    NULL);
344				topo_hdl_strfree(thp, fmristr);
345			}
346			for (uint_t i = 0; i < nelem; i++) {
347				nvlist_free(val[i]);
348			}
349			topo_hdl_free(thp, val, nelem * sizeof (nvlist_t *));
350			end_element(fp, Propval);
351			break;
352		}
353		default:
354			return;
355	}
356}
357
358static void
359txml_print_pgroup(topo_hdl_t *thp, FILE *fp, tnode_t *node, topo_pgroup_t *pg)
360{
361	topo_ipgroup_info_t *pip = pg->tpg_info;
362	topo_proplist_t *plp;
363	const char *namestab, *datastab;
364	char version[INT32BUFSZ];
365
366	namestab = topo_stability2name(pip->tpi_namestab);
367	datastab = topo_stability2name(pip->tpi_datastab);
368	(void) snprintf(version, INT32BUFSZ, "%d", pip->tpi_version);
369	begin_element(fp, Propgrp, Name, pip->tpi_name, Namestab,
370	    namestab, Datastab, datastab, Version, version, NULL);
371	for (plp = topo_list_next(&pg->tpg_pvals); plp != NULL;
372	    plp = topo_list_next(plp)) {
373		txml_print_prop(thp, fp, node, pip->tpi_name, plp->tp_pval);
374	}
375	end_element(fp, Propgrp);
376}
377
378static void
379txml_print_dependents(topo_hdl_t *thp, FILE *fp, tnode_t *node)
380{
381	if (topo_list_next(&node->tn_children) == NULL)
382		return;
383
384	if (txml_print_range(thp, fp, node, 1) == 1)
385		end_element(fp, Dependents);
386}
387
388static void
389txml_print_node(topo_hdl_t *thp, FILE *fp, tnode_t *node)
390{
391	char inst[INT32BUFSZ];
392	topo_pgroup_t *pg;
393
394	(void) snprintf(inst, INT32BUFSZ, "%d", node->tn_instance);
395	/*
396	 * The "static" attribute for the "node" element controls whether the
397	 * node gets enumerated, if it doesn't already exist.  Setting it to
398	 * true causes the node to not be created.  The primary use-case for
399	 * setting it to true is when want to use XML to override a property
400	 * value on a topo node that was already created by an enumerator
401	 * module.  In this case we're trying to serialize the whole topology
402	 * in a fashion such that we could reconstitute it from the generated
403	 * XML. In which case, we relly need it to create all the nodes becuase
404	 * no enumerator modules will be running.  Hence, we set static to
405	 * false.
406	 */
407	begin_element(fp, Node, Instance, inst, Static, False, NULL);
408	for (pg = topo_list_next(&node->tn_pgroups); pg != NULL;
409	    pg = topo_list_next(pg)) {
410		txml_print_pgroup(thp, fp, node, pg);
411	}
412	txml_print_dependents(thp, fp, node);
413	end_element(fp, Node);
414
415}
416
417static int
418txml_print_range(topo_hdl_t *thp, FILE *fp, tnode_t *node, int dependent)
419{
420	int i, create = 0, ret = 0;
421	topo_nodehash_t *nhp;
422	char min[INT32BUFSZ], max[INT32BUFSZ];
423
424	for (nhp = topo_list_next(&node->tn_children); nhp != NULL;
425	    nhp = topo_list_next(nhp)) {
426		(void) snprintf(min, INT32BUFSZ, "%d", nhp->th_range.tr_min);
427		(void) snprintf(max, INT32BUFSZ, "%d", nhp->th_range.tr_max);
428
429		/*
430		 * Some enumerators create empty ranges: make sure there
431		 * are real nodes before creating this range
432		 */
433		for (i = 0; i < nhp->th_arrlen; ++i) {
434			if (nhp->th_nodearr[i] != NULL)
435				++create;
436		}
437		if (!create)
438			continue;
439
440		if (dependent) {
441			begin_element(fp, Dependents, Grouping, Children, NULL);
442			dependent = 0;
443			ret = 1;
444		}
445		begin_element(fp, Range, Name, nhp->th_name, Min, min, Max,
446		    max, NULL);
447		for (i = 0; i < nhp->th_arrlen; ++i) {
448			if (nhp->th_nodearr[i] != NULL)
449				txml_print_node(thp, fp, nhp->th_nodearr[i]);
450		}
451		end_element(fp, Range);
452	}
453
454	return (ret);
455}
456
457static void
458txml_print_topology(topo_hdl_t *thp, FILE *fp, char *scheme, tnode_t *node)
459{
460	char *name;
461
462	if (thp->th_product != NULL)
463		name = thp->th_product;
464	else
465		name = thp->th_platform;
466
467	begin_element(fp, Topology, Name, name, Scheme, scheme,
468	    NULL);
469	(void) txml_print_range(thp, fp, node, 0);
470	end_element(fp, Topology);
471
472}
473
474int
475topo_xml_print(topo_hdl_t *thp,  FILE *fp, const char *scheme, int *err)
476{
477	ttree_t *tp;
478
479	print_header(fp);
480	for (tp = topo_list_next(&thp->th_trees); tp != NULL;
481	    tp = topo_list_next(tp)) {
482		if (strcmp(scheme, tp->tt_scheme) == 0) {
483			txml_print_topology(thp, fp, tp->tt_scheme,
484			    tp->tt_root);
485			return (0);
486		}
487	}
488
489	*err = EINVAL;
490	return (-1);
491}
492