xref: /illumos-gate/usr/src/lib/fm/topo/libtopo/common/topo_snap.c (revision 600d77457b335b6f448f13d5f33bf7e70dfbb39d)
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  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 /*
29  * Snapshot Library Interfaces
30  *
31  * Consumers of topology data may use the interfaces in this file to open,
32  * snapshot and close a topology exported by FMRI scheme (hc, mem and cpu)
33  * builtin plugins and their helper modules.  A topology handle is obtained
34  * by calling topo_open().  Upon a successful return, the caller may use this
35  * handle to open a new snapshot.  Each snapshot is assigned a Universally
36  * Unique Identifier that in a future enchancement to the libtopo API will be
37  * used as the file locator in /var/fm/topo to persist new snapshots or lookup
38  * a previously captured snapshot.  topo_snap_hold() will capture the current
39  * system topology.  All consumers of the topo_hdl_t argument will be
40  * blocked from accessing the topology trees until the snapshot completes.
41  *
42  * A snapshot may be cleared by calling topo_snap_rele().  As with
43  * topo_snap_hold(), all topology accesses are blocked until the topology
44  * trees have been released and deallocated.
45  *
46  * Walker Library Interfaces
47  *
48  * Once a snapshot has been taken with topo_snap_hold(), topo_hdl_t holders
49  * may initiate topology tree walks on a scheme-tree basis.  topo_walk_init()
50  * will initiate the data structures required to walk any one one of the
51  * FMRI scheme trees.  The walker data structure, topo_walk_t, is an opaque
52  * handle passed to topo_walk_step to begin the walk.  At each node in the
53  * topology tree, a callback function is called with access to the node at
54  * which our current walk falls.  The callback function is passed in during
55  * calls to topo_walk_init() and used throughout the walk_step of the
56  * scheme tree.  At any time, the callback may terminate the walk by returning
57  * TOPO_WALK_TERMINATE or TOPO_WALK_ERR.  TOPO_WALK_NEXT will continue the walk.
58  *
59  * The type of walk through the tree may be breadth first or depth first by
60  * respectively passing in TOPO_WALK_SIBLING or TOPO_WALK_CHILD to
61  * the topo_walk_step() function.  Topology nodes
62  * associated with an outstanding walk are held in place and will not be
63  * deallocated until the walk through that node completes.
64  *
65  * Once the walk has terminated, the walking process should call
66  * topo_walk_fini() to clean-up resources created in topo_walk_init()
67  * and release nodes that may be still held.
68  */
69 
70 #include <alloca.h>
71 #include <ctype.h>
72 #include <pthread.h>
73 #include <limits.h>
74 #include <assert.h>
75 #include <fcntl.h>
76 #include <smbios.h>
77 #include <sys/param.h>
78 #include <sys/types.h>
79 #include <sys/stat.h>
80 #include <sys/systeminfo.h>
81 #include <sys/utsname.h>
82 #include <uuid/uuid.h>
83 
84 #include <fm/libtopo.h>
85 
86 #include <topo_alloc.h>
87 #include <topo_builtin.h>
88 #include <topo_string.h>
89 #include <topo_error.h>
90 #include <topo_subr.h>
91 
92 static void topo_snap_destroy(topo_hdl_t *);
93 
94 static topo_hdl_t *
95 set_open_errno(topo_hdl_t *thp, int *errp, int err)
96 {
97 	if (thp != NULL) {
98 		topo_close(thp);
99 	}
100 	if (errp != NULL)
101 		*errp = err;
102 	return (NULL);
103 }
104 
105 static char *
106 smbios_fix(topo_hdl_t *thp, char *begin)
107 {
108 	char buf[MAXNAMELEN];
109 	size_t count;
110 	char *str, *end, *pp;
111 
112 	end = begin + strlen(begin);
113 
114 	while (begin < end && isspace(*begin))
115 		begin++;
116 	while (begin < end && isspace(*(end - 1)))
117 		end--;
118 
119 	if (begin >= end)
120 		return (NULL);
121 
122 	count = end - begin;
123 	count += 1;
124 
125 	if (count > sizeof (buf))
126 		return (NULL);
127 
128 	(void) snprintf(buf, count, "%s", begin);
129 	while ((str = strchr(buf, ' ')) != NULL)
130 		*str = '-';
131 
132 	pp = topo_hdl_strdup(thp, buf);
133 	return (pp);
134 }
135 
136 topo_hdl_t *
137 topo_open(int version, const char *rootdir, int *errp)
138 {
139 	topo_hdl_t *thp = NULL;
140 	topo_alloc_t *tap;
141 
142 	char platform[MAXNAMELEN];
143 	char isa[MAXNAMELEN];
144 	struct utsname uts;
145 	struct stat st;
146 
147 	smbios_hdl_t *shp;
148 	smbios_system_t s1;
149 	smbios_info_t s2;
150 	id_t id;
151 
152 	char *dbflags, *dbout;
153 
154 	if (version != TOPO_VERSION)
155 		return (set_open_errno(thp, errp, ETOPO_HDL_ABIVER));
156 
157 	if (rootdir != NULL && stat(rootdir, &st) < 0)
158 		return (set_open_errno(thp, errp, ETOPO_HDL_INVAL));
159 
160 	if ((thp = topo_zalloc(sizeof (topo_hdl_t), 0)) == NULL)
161 		return (set_open_errno(thp, errp, ETOPO_NOMEM));
162 
163 	(void) pthread_mutex_init(&thp->th_lock, NULL);
164 
165 	if ((tap = topo_zalloc(sizeof (topo_alloc_t), 0)) == NULL)
166 		return (set_open_errno(thp, errp, ETOPO_NOMEM));
167 
168 	/*
169 	 * Install default allocators
170 	 */
171 	tap->ta_flags = 0;
172 	tap->ta_alloc = topo_alloc;
173 	tap->ta_zalloc = topo_zalloc;
174 	tap->ta_free = topo_free;
175 	tap->ta_nvops.nv_ao_alloc = topo_nv_alloc;
176 	tap->ta_nvops.nv_ao_free = topo_nv_free;
177 	(void) nv_alloc_init(&tap->ta_nva, &tap->ta_nvops);
178 	thp->th_alloc = tap;
179 
180 	if ((thp->th_modhash = topo_modhash_create(thp)) == NULL)
181 		return (set_open_errno(thp, errp, ETOPO_NOMEM));
182 
183 	/*
184 	 * Set-up system information and search paths for modules
185 	 * and topology map files
186 	 */
187 	if (rootdir == NULL) {
188 		rootdir = topo_hdl_strdup(thp, "/");
189 		thp->th_rootdir = (char *)rootdir;
190 	} else {
191 		int len;
192 		char *rpath;
193 
194 		len = strlen(rootdir);
195 		if (len >= PATH_MAX)
196 			return (set_open_errno(thp, errp, EINVAL));
197 
198 		if (rootdir[len] != '/') {
199 			rpath = alloca(len + 1);
200 			(void) snprintf(rpath, len + 1, "%s/", rootdir);
201 		} else {
202 			rpath = (char *)rootdir;
203 		}
204 		thp->th_rootdir = topo_hdl_strdup(thp, rpath);
205 	}
206 
207 	platform[0] = '\0';
208 	isa[0] = '\0';
209 	(void) sysinfo(SI_PLATFORM, platform, sizeof (platform));
210 	(void) sysinfo(SI_ARCHITECTURE, isa, sizeof (isa));
211 	(void) uname(&uts);
212 	thp->th_platform = topo_hdl_strdup(thp, platform);
213 	thp->th_isa = topo_hdl_strdup(thp, isa);
214 	thp->th_machine = topo_hdl_strdup(thp, uts.machine);
215 	if ((shp = smbios_open(NULL, SMB_VERSION, 0, NULL)) != NULL) {
216 		if ((id = smbios_info_system(shp, &s1)) != SMB_ERR &&
217 		    smbios_info_common(shp, id, &s2) != SMB_ERR) {
218 
219 			if (strcmp(s2.smbi_product, SMB_DEFAULT1) != 0 &&
220 			    strcmp(s2.smbi_product, SMB_DEFAULT2) != 0) {
221 				thp->th_product = smbios_fix(thp,
222 				    (char *)s2.smbi_product);
223 			}
224 		}
225 		smbios_close(shp);
226 	} else {
227 		thp->th_product = topo_hdl_strdup(thp, thp->th_platform);
228 	}
229 
230 	if (thp->th_rootdir == NULL)
231 		return (set_open_errno(thp, errp, ETOPO_NOMEM));
232 
233 	dbflags	 = getenv("TOPO_DEBUG");
234 	dbout = getenv("TOPO_DEBUG_OUT");
235 	if (dbflags != NULL)
236 		topo_debug_set(thp, dbflags, dbout);
237 
238 	if (topo_builtin_create(thp, thp->th_rootdir) != 0) {
239 		topo_dprintf(thp, TOPO_DBG_ERR,
240 		    "failed to load builtin modules: %s\n",
241 		    topo_hdl_errmsg(thp));
242 		topo_close(thp);
243 		return (NULL);
244 	}
245 
246 	return (thp);
247 }
248 
249 void
250 topo_close(topo_hdl_t *thp)
251 {
252 	ttree_t *tp;
253 
254 	topo_hdl_lock(thp);
255 	if (thp->th_platform != NULL)
256 		topo_hdl_strfree(thp, thp->th_platform);
257 	if (thp->th_isa != NULL)
258 		topo_hdl_strfree(thp, thp->th_isa);
259 	if (thp->th_machine != NULL)
260 		topo_hdl_strfree(thp, thp->th_machine);
261 	if (thp->th_product != NULL)
262 		topo_hdl_strfree(thp, thp->th_product);
263 	if (thp->th_rootdir != NULL)
264 		topo_hdl_strfree(thp, thp->th_rootdir);
265 
266 	/*
267 	 * Clean-up snapshot
268 	 */
269 	topo_snap_destroy(thp);
270 
271 	/*
272 	 * Clean-up trees
273 	 */
274 	while ((tp = topo_list_next(&thp->th_trees)) != NULL) {
275 		topo_list_delete(&thp->th_trees, tp);
276 		topo_tree_destroy(tp);
277 	}
278 
279 	/*
280 	 * Unload all plugins
281 	 */
282 	topo_modhash_unload_all(thp);
283 
284 	if (thp->th_modhash != NULL)
285 		topo_modhash_destroy(thp);
286 	if (thp->th_alloc != NULL)
287 		topo_free(thp->th_alloc, sizeof (topo_alloc_t));
288 
289 	topo_hdl_unlock(thp);
290 
291 	topo_free(thp, sizeof (topo_hdl_t));
292 }
293 
294 static char *
295 topo_snap_create(topo_hdl_t *thp, int *errp)
296 {
297 	uuid_t uuid;
298 	char *ustr = NULL;
299 
300 	topo_hdl_lock(thp);
301 	if (thp->th_uuid != NULL) {
302 		*errp = ETOPO_HDL_UUID;
303 		topo_hdl_unlock(thp);
304 		return (NULL);
305 	}
306 
307 	if ((thp->th_uuid = topo_hdl_zalloc(thp, TOPO_UUID_SIZE)) == NULL) {
308 		*errp = ETOPO_NOMEM;
309 		topo_dprintf(thp, TOPO_DBG_ERR, "unable to allocate uuid: %s\n",
310 		    topo_strerror(*errp));
311 		topo_hdl_unlock(thp);
312 		return (NULL);
313 	}
314 
315 	uuid_generate(uuid);
316 	uuid_unparse(uuid, thp->th_uuid);
317 
318 	if (topo_tree_enum_all(thp) < 0) {
319 		topo_dprintf(thp, TOPO_DBG_ERR, "enumeration failure: %s\n",
320 		    topo_hdl_errmsg(thp));
321 		if (topo_hdl_errno(thp) == ETOPO_ENUM_FATAL) {
322 			*errp = thp->th_errno;
323 			topo_hdl_unlock(thp);
324 			return (NULL);
325 		}
326 	}
327 
328 	if ((ustr = topo_hdl_strdup(thp, thp->th_uuid)) == NULL)
329 		*errp = ETOPO_NOMEM;
330 
331 	thp->th_di = DI_NODE_NIL;
332 	thp->th_pi = DI_PROM_HANDLE_NIL;
333 
334 	topo_hdl_unlock(thp);
335 
336 	return (ustr);
337 }
338 
339 /*ARGSUSED*/
340 static char *
341 topo_snap_log_create(topo_hdl_t *thp, const char *uuid, int *errp)
342 {
343 	return ((char *)uuid);
344 }
345 
346 /*
347  * Return snapshot id
348  */
349 char *
350 topo_snap_hold(topo_hdl_t *thp, const char *uuid, int *errp)
351 {
352 	if (thp == NULL)
353 		return (NULL);
354 
355 	if (uuid == NULL)
356 		return (topo_snap_create(thp, errp));
357 	else
358 		return (topo_snap_log_create(thp, uuid, errp));
359 }
360 
361 /*ARGSUSED*/
362 static int
363 topo_walk_destroy(topo_hdl_t *thp, tnode_t *node, void *notused)
364 {
365 	tnode_t *cnode;
366 
367 	cnode = topo_child_first(node);
368 
369 	if (cnode != NULL)
370 		return (TOPO_WALK_NEXT);
371 
372 	topo_node_unbind(node);
373 
374 	return (TOPO_WALK_NEXT);
375 }
376 
377 static void
378 topo_snap_destroy(topo_hdl_t *thp)
379 {
380 	int i;
381 	ttree_t *tp;
382 	topo_walk_t *twp;
383 	tnode_t *root;
384 	topo_nodehash_t *nhp;
385 	topo_mod_t *mod;
386 
387 	for (tp = topo_list_next(&thp->th_trees); tp != NULL;
388 	    tp = topo_list_next(tp)) {
389 
390 		root = tp->tt_root;
391 		twp = tp->tt_walk;
392 		/*
393 		 * Clean-up tree nodes from the bottom-up
394 		 */
395 		if ((twp->tw_node = topo_child_first(root)) != NULL) {
396 			twp->tw_cb = topo_walk_destroy;
397 			topo_node_hold(root);
398 			topo_node_hold(twp->tw_node); /* released at walk end */
399 			(void) topo_walk_bottomup(twp, TOPO_WALK_CHILD);
400 			topo_node_rele(root);
401 		}
402 
403 		/*
404 		 * Tidy-up the root node
405 		 */
406 		while ((nhp = topo_list_next(&root->tn_children)) != NULL) {
407 			for (i = 0; i < nhp->th_arrlen; i++) {
408 				assert(nhp->th_nodearr[i] == NULL);
409 			}
410 			mod = nhp->th_enum;
411 			topo_mod_strfree(mod, nhp->th_name);
412 			topo_mod_free(mod, nhp->th_nodearr,
413 			    nhp->th_arrlen * sizeof (tnode_t *));
414 			topo_list_delete(&root->tn_children, nhp);
415 			topo_mod_free(mod, nhp, sizeof (topo_nodehash_t));
416 			topo_mod_rele(mod);
417 		}
418 
419 	}
420 
421 	if (thp->th_uuid != NULL) {
422 		topo_hdl_free(thp, thp->th_uuid, TOPO_UUID_SIZE);
423 		thp->th_uuid = NULL;
424 	}
425 }
426 
427 void
428 topo_snap_release(topo_hdl_t *thp)
429 {
430 	if (thp == NULL)
431 		return;
432 
433 	topo_hdl_lock(thp);
434 	topo_snap_destroy(thp);
435 	topo_hdl_unlock(thp);
436 
437 }
438 
439 topo_walk_t *
440 topo_walk_init(topo_hdl_t *thp, const char *scheme, topo_walk_cb_t cb_f,
441     void *pdata, int *errp)
442 {
443 	ttree_t *tp;
444 	topo_walk_t *wp;
445 
446 	for (tp = topo_list_next(&thp->th_trees); tp != NULL;
447 	    tp = topo_list_next(tp)) {
448 		if (strcmp(scheme, tp->tt_scheme) == 0) {
449 
450 			/*
451 			 * Hold the root node and start walk at the first
452 			 * child node
453 			 */
454 			assert(tp->tt_root != NULL);
455 
456 			if ((wp = topo_node_walk_init(thp, NULL, tp->tt_root,
457 			    cb_f, pdata, errp)) == NULL) /* errp set */
458 				return (NULL);
459 
460 			return (wp);
461 		}
462 	}
463 
464 	*errp = ETOPO_WALK_NOTFOUND;
465 	return (NULL);
466 }
467 
468 static int
469 step_child(tnode_t *cnp, topo_walk_t *wp, int bottomup)
470 {
471 	int status;
472 	tnode_t *nnp;
473 
474 	nnp = topo_child_first(cnp);
475 
476 	if (nnp == NULL) {
477 		topo_dprintf(wp->tw_thp, TOPO_DBG_WALK,
478 		    "step_child: TOPO_WALK_TERMINATE for %s=%d\n",
479 		    cnp->tn_name, cnp->tn_instance);
480 		return (TOPO_WALK_TERMINATE);
481 	}
482 
483 	topo_dprintf(wp->tw_thp, TOPO_DBG_WALK,
484 	    "step_child: walk through node %s=%d to %s=%d\n",
485 	    cnp->tn_name, cnp->tn_instance, nnp->tn_name, nnp->tn_instance);
486 
487 	topo_node_hold(nnp); /* released on return from walk_step */
488 	wp->tw_node = nnp;
489 	if (bottomup == 1)
490 		status = topo_walk_bottomup(wp, TOPO_WALK_CHILD);
491 	else
492 		status = topo_walk_step(wp, TOPO_WALK_CHILD);
493 
494 	return (status);
495 }
496 
497 static int
498 step_sibling(tnode_t *cnp, topo_walk_t *wp, int bottomup)
499 {
500 	int status;
501 	tnode_t *nnp;
502 
503 	nnp = topo_child_next(cnp->tn_parent, cnp);
504 
505 	if (nnp == NULL) {
506 		topo_dprintf(wp->tw_thp, TOPO_DBG_WALK,
507 		    "step_sibling: TOPO_WALK_TERMINATE for %s=%d\n",
508 		    cnp->tn_name, cnp->tn_instance);
509 		return (TOPO_WALK_TERMINATE);
510 	}
511 
512 	topo_dprintf(wp->tw_thp, TOPO_DBG_WALK,
513 	    "step_sibling: through sibling node %s=%d to %s=%d\n",
514 	    cnp->tn_name, cnp->tn_instance, nnp->tn_name, nnp->tn_instance);
515 
516 	topo_node_hold(nnp); /* released on return from walk_step */
517 	wp->tw_node = nnp;
518 	if (bottomup == 1)
519 		status = topo_walk_bottomup(wp, TOPO_WALK_CHILD);
520 	else
521 		status = topo_walk_step(wp, TOPO_WALK_CHILD);
522 
523 	return (status);
524 }
525 
526 int
527 topo_walk_byid(topo_walk_t *wp, const char *name, topo_instance_t inst)
528 {
529 	int status;
530 	tnode_t *nnp, *cnp;
531 
532 	cnp = wp->tw_node;
533 	nnp = topo_node_lookup(cnp, name, inst);
534 	if (nnp == NULL)
535 		return (TOPO_WALK_TERMINATE);
536 
537 	topo_node_hold(nnp);
538 	wp->tw_node = nnp;
539 	if (wp->tw_mod != NULL)
540 		status = wp->tw_cb(wp->tw_mod, nnp, wp->tw_pdata);
541 	else
542 		status = wp->tw_cb(wp->tw_thp, nnp, wp->tw_pdata);
543 	topo_node_rele(nnp);
544 	wp->tw_node = cnp;
545 
546 	return (status);
547 }
548 
549 int
550 topo_walk_step(topo_walk_t *wp, int flag)
551 {
552 	int status;
553 	tnode_t *cnp = wp->tw_node;
554 
555 	if (flag != TOPO_WALK_CHILD && flag != TOPO_WALK_SIBLING) {
556 		topo_node_rele(cnp);
557 		return (TOPO_WALK_ERR);
558 	}
559 
560 	/*
561 	 * No more nodes to walk
562 	 */
563 	if (cnp == NULL) {
564 		topo_dprintf(wp->tw_thp, TOPO_DBG_WALK,
565 		    "walk_step terminated\n");
566 		topo_node_rele(cnp);
567 		return (TOPO_WALK_TERMINATE);
568 	}
569 
570 
571 	if (wp->tw_mod != NULL)
572 		status = wp->tw_cb(wp->tw_mod, cnp, wp->tw_pdata);
573 	else
574 		status = wp->tw_cb(wp->tw_thp, cnp, wp->tw_pdata);
575 
576 	/*
577 	 * Walker callback says we're done
578 	 */
579 	if (status != TOPO_WALK_NEXT) {
580 		topo_node_rele(cnp);
581 		return (status);
582 	}
583 
584 	flag == TOPO_WALK_CHILD ? (status = step_child(cnp, wp, 0)) :
585 	    (status = step_sibling(cnp, wp, 0));
586 
587 	/*
588 	 * No more nodes in this hash, skip to next node hash by stepping
589 	 * to next sibling (depth-first walk) or next child (breadth-first
590 	 * walk).
591 	 */
592 	if (status == TOPO_WALK_TERMINATE) {
593 
594 		flag == TOPO_WALK_CHILD ? (status = step_sibling(cnp, wp, 0)) :
595 		    (status = step_child(cnp, wp, 0));
596 	}
597 
598 	topo_node_rele(cnp); /* done with current node */
599 
600 	return (status);
601 }
602 
603 void
604 topo_walk_fini(topo_walk_t *wp)
605 {
606 	if (wp == NULL)
607 		return;
608 
609 	topo_node_rele(wp->tw_root);
610 
611 	topo_hdl_free(wp->tw_thp, wp, sizeof (topo_walk_t));
612 }
613 
614 int
615 topo_walk_bottomup(topo_walk_t *wp, int flag)
616 {
617 	int status;
618 	tnode_t *cnp;
619 
620 	if (wp == NULL)
621 		return (TOPO_WALK_ERR);
622 
623 	cnp = wp->tw_node;
624 	if (flag != TOPO_WALK_CHILD && flag != TOPO_WALK_SIBLING) {
625 		topo_node_rele(cnp);
626 		return (TOPO_WALK_ERR);
627 	}
628 
629 	/*
630 	 * End of the line
631 	 */
632 	if (cnp == NULL) {
633 		topo_dprintf(wp->tw_thp, TOPO_DBG_WALK,
634 		    "walk_bottomup terminated\n");
635 		topo_node_rele(cnp);
636 		return (TOPO_WALK_TERMINATE);
637 	}
638 
639 	topo_dprintf(wp->tw_thp, TOPO_DBG_WALK,
640 	    "%s walk_bottomup through node %s=%d\n",
641 	    (flag == TOPO_WALK_CHILD ? "TOPO_WALK_CHILD" : "TOPO_WALK_SIBLING"),
642 	    cnp->tn_name, cnp->tn_instance);
643 
644 	if (flag == TOPO_WALK_CHILD)
645 		status = step_child(cnp, wp, 1);
646 	else
647 		status = step_sibling(cnp, wp, 1);
648 
649 	/*
650 	 * At a leaf, run the callback
651 	 */
652 	if (status == TOPO_WALK_TERMINATE) {
653 		if ((status = wp->tw_cb(wp->tw_thp, cnp, wp->tw_pdata))
654 		    != TOPO_WALK_NEXT) {
655 			topo_node_rele(cnp);
656 			return (status);
657 		}
658 	}
659 
660 	/*
661 	 * Try next child or sibling
662 	 */
663 	if (status == TOPO_WALK_NEXT) {
664 		if (flag == TOPO_WALK_CHILD)
665 			status = step_sibling(cnp, wp, 1);
666 		else
667 			status = step_child(cnp, wp, 1);
668 	}
669 
670 	topo_node_rele(cnp); /* done with current node */
671 
672 	return (status);
673 }
674