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