1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2020 Joyent, Inc.
14  */
15 
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <errno.h>
19 #include <fcntl.h>
20 #include <libnvpair.h>
21 #include <string.h>
22 #include <stropts.h>
23 #include <unistd.h>
24 #include <fm/libtopo.h>
25 #include <sys/debug.h>
26 #include <sys/stat.h>
27 #include <sys/types.h>
28 #include <sys/varargs.h>
29 
30 
31 #define	TEST_HOME		"/opt/os-tests/tests/libtopo/"
32 #define	TEST_XML_IN		"digraph-test-in.xml"
33 #define	TEST_XML_IN_BADSCHEME	"digraph-test-in-badscheme.xml"
34 #define	TEST_XML_IN_BADNUM	"digraph-test-in-badnum.xml"
35 #define	TEST_XML_IN_BADEDGE	"digraph-test-in-badedge.xml"
36 #define	TEST_XML_IN_BADELEMENT	"digraph-test-in-badelement.xml"
37 #define	TEST_GRAPH_SZ		7
38 #define	TEST_XML_OUT_DIR	"/var/tmp"
39 #define	TEST_XML_OUT_PREFIX	"digraph-test-out"
40 
41 static const char *pname;
42 
43 extern int topo_hdl_errno(topo_hdl_t *);
44 
45 /*
46  * Generate an ISO 8601 timestamp
47  */
48 static void
get_timestamp(char * buf,size_t bufsize)49 get_timestamp(char *buf, size_t bufsize)
50 {
51 	time_t utc_time;
52 	struct tm *p_tm;
53 
54 	(void) time(&utc_time);
55 	p_tm = localtime(&utc_time);
56 
57 	(void) strftime(buf, bufsize, "%FT%TZ", p_tm);
58 }
59 
60 /* PRINTFLIKE1 */
61 static void
logmsg(const char * format,...)62 logmsg(const char *format, ...)
63 {
64 	char timestamp[128];
65 	va_list ap;
66 
67 	get_timestamp(timestamp, sizeof (timestamp));
68 	(void) fprintf(stdout, "%s ", timestamp);
69 	va_start(ap, format);
70 	(void) vfprintf(stdout, format, ap);
71 	va_end(ap);
72 	(void) fprintf(stdout, "\n");
73 	(void) fflush(stdout);
74 }
75 
76 static topo_digraph_t *
test_deserialize(topo_hdl_t * thp,const char * path)77 test_deserialize(topo_hdl_t *thp, const char *path)
78 {
79 	struct stat statbuf = { 0 };
80 	char *buf = NULL;
81 	int fd = -1;
82 	topo_digraph_t *tdg = NULL;
83 
84 	logmsg("\tOpening test XML topology");
85 	if ((fd = open(path, O_RDONLY)) < 0) {
86 		logmsg("\tfailed to open %s (%s)", path, strerror(errno));
87 		goto out;
88 	}
89 	if (fstat(fd, &statbuf) != 0) {
90 		logmsg("\tfailed to stat %s (%s)", path, strerror(errno));
91 		goto out;
92 	}
93 	if ((buf = malloc(statbuf.st_size)) == NULL) {
94 		logmsg("\tfailed to alloc read buffer: (%s)", strerror(errno));
95 		goto out;
96 	}
97 	if (read(fd, buf, statbuf.st_size) != statbuf.st_size) {
98 		logmsg("\tfailed to read file: (%s)", strerror(errno));
99 		goto out;
100 	}
101 
102 	logmsg("\tDeserializing XML topology");
103 	tdg = topo_digraph_deserialize(thp, buf, statbuf.st_size);
104 	if (tdg == NULL) {
105 		logmsg("\ttopo_digraph_deserialize() failed!");
106 		goto out;
107 	}
108 	logmsg("\ttopo_digraph_deserialize() succeeded");
109 out:
110 	free(buf);
111 	if (fd > 0) {
112 		(void) close(fd);
113 	}
114 	return (tdg);
115 }
116 
117 struct cb_arg {
118 	topo_vertex_t	**vertices;
119 };
120 
121 static int
test_paths_cb(topo_hdl_t * thp,topo_vertex_t * vtx,boolean_t last_vtx,void * arg)122 test_paths_cb(topo_hdl_t *thp, topo_vertex_t *vtx, boolean_t last_vtx,
123     void *arg)
124 {
125 	struct cb_arg *cbarg = arg;
126 	uint_t idx = topo_node_instance(topo_vertex_node(vtx));
127 
128 	cbarg->vertices[idx] = vtx;
129 
130 	return (TOPO_WALK_NEXT);
131 }
132 
133 static int
test_paths(topo_hdl_t * thp,topo_digraph_t * tdg)134 test_paths(topo_hdl_t *thp, topo_digraph_t *tdg)
135 {
136 	topo_vertex_t *vertices[TEST_GRAPH_SZ];
137 	struct cb_arg cbarg = { 0 };
138 	int ret = -1;
139 	topo_path_t **paths;
140 	uint_t np;
141 
142 	cbarg.vertices = vertices;
143 	if (topo_vertex_iter(thp, tdg, test_paths_cb, &cbarg) != 0) {
144 		logmsg("\tfailed to iterate over graph vertices");
145 		goto out;
146 	}
147 
148 	logmsg("\tCalculating number of paths between node 0 and node 4");
149 	if (topo_digraph_paths(thp, tdg, vertices[0], vertices[4], &paths,
150 	    &np) < 0) {
151 		logmsg("\ttopo_digraph_paths() failed");
152 		goto out;
153 	}
154 	if (np != 2) {
155 		logmsg("\t%d paths found (expected 2)", np);
156 		goto out;
157 	}
158 	for (uint_t i = 0; i < np; i++) {
159 		topo_path_destroy(thp, paths[i]);
160 	}
161 	topo_hdl_free(thp, paths, np * sizeof (topo_path_t *));
162 
163 	logmsg("\tCalculating number of paths between node 6 and node 4");
164 	if (topo_digraph_paths(thp, tdg, vertices[6], vertices[4], &paths,
165 	    &np) < 0) {
166 		logmsg("\ttopo_digraph_paths() failed");
167 		goto out;
168 	}
169 	if (np != 1) {
170 		logmsg("\t%d paths found (expected 1)", np);
171 		goto out;
172 	}
173 	for (uint_t i = 0; i < np; i++) {
174 		topo_path_destroy(thp, paths[i]);
175 	}
176 	topo_hdl_free(thp, paths, np * sizeof (topo_path_t *));
177 
178 	logmsg("\tCalculating number of paths between node 5 and node 1");
179 	if (topo_digraph_paths(thp, tdg, vertices[5], vertices[1], &paths,
180 	    &np) < 0) {
181 		logmsg("\ttopo_digraph_paths() failed");
182 		goto out;
183 	}
184 	if (np != 0) {
185 		logmsg("\t%d paths found (expected 0)", np);
186 		goto out;
187 	}
188 	ret = 0;
189 
190 out:
191 	if (np > 0) {
192 		for (uint_t i = 0; i < np; i++) {
193 			topo_path_destroy(thp, paths[i]);
194 		}
195 		topo_hdl_free(thp, paths, np * sizeof (topo_path_t *));
196 	}
197 	return (ret);
198 }
199 
200 static int
test_serialize(topo_hdl_t * thp,topo_digraph_t * tdg,const char * path)201 test_serialize(topo_hdl_t *thp, topo_digraph_t *tdg, const char *path)
202 {
203 	FILE *xml_out;
204 
205 	if ((xml_out = fopen(path, "w")) == NULL) {
206 		logmsg("\tfailed to open %s for writing (%s)",
207 		    strerror(errno));
208 		return (-1);
209 	}
210 	logmsg("\tSerializing topology to XML (%s)", path);
211 	if (topo_digraph_serialize(thp, tdg, xml_out) != 0) {
212 		logmsg("\ttopo_digraph_serialize() failed!");
213 		(void) fclose(xml_out);
214 		return (-1);
215 	}
216 	(void) fclose(xml_out);
217 	return (0);
218 }
219 
220 int
main(int argc,char ** argv)221 main(int argc, char **argv)
222 {
223 	topo_hdl_t *thp = NULL;
224 	topo_digraph_t *tdg;
225 	char *root = "/", *out_path = NULL;
226 	boolean_t abort_on_exit = B_FALSE;
227 	int err, status = EXIT_FAILURE;
228 
229 	pname = argv[0];
230 
231 	/*
232 	 * Setting DIGRAPH_TEST_CORE causes us to abort and dump core before
233 	 * exiting.  This is useful for examining for memory leaks.
234 	 */
235 	if (getenv("DIGRAPH_TEST_CORE") != NULL) {
236 		abort_on_exit = B_TRUE;
237 	}
238 
239 	logmsg("Opening libtopo");
240 	if ((thp = topo_open(TOPO_VERSION, root, &err)) == NULL) {
241 		logmsg("failed to get topo handle: %s", topo_strerror(err));
242 		goto out;
243 	}
244 
245 	logmsg("TEST: Deserialize directed graph topology");
246 	if ((tdg = test_deserialize(thp, TEST_HOME TEST_XML_IN)) == NULL) {
247 		logmsg("FAIL");
248 		goto out;
249 	}
250 	logmsg("PASS");
251 
252 	logmsg("TEST: Serialize directed graph topology");
253 	if ((out_path = tempnam(TEST_XML_OUT_DIR, TEST_XML_OUT_PREFIX)) ==
254 	    NULL) {
255 		logmsg("\tFailed to create temporary file name under %s (%s)",
256 		    TEST_XML_OUT_DIR, strerror(errno));
257 		logmsg("FAIL");
258 		goto out;
259 	}
260 	if (test_serialize(thp, tdg, out_path) != 0) {
261 		logmsg("FAIL");
262 		goto out;
263 	}
264 	logmsg("PASS");
265 
266 	logmsg("Closing libtopo");
267 	topo_close(thp);
268 
269 	logmsg("Reopening libtopo");
270 	if ((thp = topo_open(TOPO_VERSION, root, &err)) == NULL) {
271 		logmsg("failed to get topo handle: %s", topo_strerror(err));
272 		goto out;
273 	}
274 
275 	logmsg("TEST: Deserialize directed graph topology (pass 2)");
276 	if ((tdg = test_deserialize(thp, out_path)) == NULL) {
277 		logmsg("FAIL");
278 		goto out;
279 	}
280 	logmsg("PASS");
281 
282 	logmsg("TEST: Calculating paths between vertices");
283 	if (test_paths(thp, tdg) != 0) {
284 		logmsg("FAIL");
285 		goto out;
286 	}
287 	logmsg("PASS");
288 
289 	logmsg("Closing libtopo");
290 	topo_close(thp);
291 
292 	logmsg("Reopening libtopo");
293 	if ((thp = topo_open(TOPO_VERSION, root, &err)) == NULL) {
294 		logmsg("failed to get topo handle: %s", topo_strerror(err));
295 		goto out;
296 	}
297 
298 	/*
299 	 * The following tests attempt to deserialize XML files that either
300 	 * violate the DTD or contain invalid attribute values.
301 	 *
302 	 * The expection is that topo_digraph_deserialize() should fail
303 	 * gracefully (i.e. not segfault) and topo_errno should be set.
304 	 */
305 	logmsg("TEST: Deserialize directed graph topology (bad scheme)");
306 	if ((tdg = test_deserialize(thp, TEST_HOME TEST_XML_IN_BADSCHEME)) !=
307 	    NULL) {
308 		logmsg("FAIL");
309 		goto out;
310 	} else if (topo_hdl_errno(thp) == 0) {
311 		logmsg("\texpected topo_errno to be non-zero");
312 		logmsg("FAIL");
313 		goto out;
314 	} else {
315 		logmsg("PASS");
316 	}
317 
318 	logmsg("TEST: Deserialize directed graph topology (bad number)");
319 	if ((tdg = test_deserialize(thp, TEST_HOME TEST_XML_IN_BADNUM)) !=
320 	    NULL) {
321 		logmsg("FAIL");
322 		goto out;
323 	} else if (topo_hdl_errno(thp) == 0) {
324 		logmsg("\texpected topo_errno to be non-zero");
325 		logmsg("FAIL");
326 		goto out;
327 	} else {
328 		logmsg("PASS");
329 	}
330 
331 	logmsg("TEST: Deserialize directed graph topology (bad edge)");
332 	if ((tdg = test_deserialize(thp, TEST_HOME TEST_XML_IN_BADEDGE)) !=
333 	    NULL) {
334 		logmsg("FAIL");
335 		goto out;
336 	} else if (topo_hdl_errno(thp) == 0) {
337 		logmsg("\texpected topo_errno to be non-zero");
338 		logmsg("FAIL");
339 		goto out;
340 	} else {
341 		logmsg("PASS");
342 	}
343 
344 	logmsg("TEST: Deserialize directed graph topology (bad element)");
345 	if ((tdg = test_deserialize(thp, TEST_HOME TEST_XML_IN_BADELEMENT)) !=
346 	    NULL) {
347 		logmsg("FAIL");
348 		goto out;
349 	} else if (topo_hdl_errno(thp) == 0) {
350 		logmsg("\texpected topo_errno to be non-zero");
351 		logmsg("FAIL");
352 		goto out;
353 	} else {
354 		logmsg("PASS");
355 	}
356 
357 	/*
358 	 * If any tests failed, we don't unlink the temp file, as its contents
359 	 * may be useful for root-causing the test failure.
360 	 */
361 	if (unlink(out_path) != 0) {
362 		logmsg("Failed to unlink temp file: %s (%s)", out_path,
363 		    strerror(errno));
364 	}
365 	status = EXIT_SUCCESS;
366 out:
367 	if (thp != NULL) {
368 		topo_close(thp);
369 	}
370 	if (out_path != NULL) {
371 		free(out_path);
372 	}
373 	logmsg("digraph tests %s",
374 	    status == EXIT_SUCCESS ? "passed" : "failed");
375 
376 	if (abort_on_exit) {
377 		abort();
378 	}
379 	return (status);
380 }
381