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 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 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 * 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 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 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 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 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