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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 /*
30  * Snapshot Library Interfaces
31  *
32  * Consumers of topology data may use the interfaces in this file to open,
33  * snapshot and close a topology exported by FMRI scheme (hc, mem and cpu)
34  * builtin plugins and their helper modules.  A topology handle is obtained
35  * by calling topo_open().  Upon a successful return, the caller may use this
36  * handle to open a new snapshot.  Each snapshot is assigned a Universally
37  * Unique Identifier that in a future enchancement to the libtopo API will be
38  * used as the file locator in /var/fm/topo to persist new snapshots or lookup
39  * a previously captured snapshot.  topo_snap_hold() will capture the current
40  * system topology.  All consumers of the topo_hdl_t argument will be
41  * blocked from accessing the topology trees until the snapshot completes.
42  *
43  * A snapshot may be cleared by calling topo_snap_rele().  As with
44  * topo_snap_hold(), all topology accesses are blocked until the topology
45  * trees have been released and deallocated.
46  *
47  * Walker Library Interfaces
48  *
49  * Once a snapshot has been taken with topo_snap_hold(), topo_hdl_t holders
50  * may initiate topology tree walks on a scheme-tree basis.  topo_walk_init()
51  * will initiate the data structures required to walk any one one of the
52  * FMRI scheme trees.  The walker data structure, topo_walk_t, is an opaque
53  * handle passed to topo_walk_step to begin the walk.  At each node in the
54  * topology tree, a callback function is called with access to the node at
55  * which our current walk falls.  The callback function is passed in during
56  * calls to topo_walk_init() and used throughout the walk_step of the
57  * scheme tree.  At any time, the callback may terminate the walk by returning
58  * TOPO_WALK_TERMINATE or TOPO_WALK_ERR.  TOPO_WALK_NEXT will continue the
59  * walk.
60  *
61  * Walks through the tree may be breadth first or depth first by
62  * respectively passing in TOPO_WALK_SIBLING or TOPO_WALK_CHILD to
63  * the topo_walk_step() function.  Topology nodes associated with an
64  * outstanding walk are held in place and will not be deallocated until
65  * the walk through that node completes.
66  *
67  * Once the walk has terminated, the walking process should call
68  * topo_walk_fini() to clean-up resources created in topo_walk_init()
69  * and release nodes that may be still held.
70  */
71 
72 #include <pthread.h>
73 #include <limits.h>
74 #include <assert.h>
75 #include <fcntl.h>
76 #include <sys/types.h>
77 #include <sys/stat.h>
78 #include <uuid/uuid.h>
79 
80 #include <fm/libtopo.h>
81 
82 #include <topo_alloc.h>
83 #include <topo_builtin.h>
84 #include <topo_string.h>
85 #include <topo_error.h>
86 #include <topo_subr.h>
87 
88 static void topo_snap_destroy(topo_hdl_t *);
89 
90 static topo_hdl_t *
91 set_open_errno(topo_hdl_t *thp, int *errp, int err)
92 {
93 	if (thp != NULL) {
94 		topo_close(thp);
95 	}
96 	if (errp != NULL)
97 		*errp = err;
98 	return (NULL);
99 }
100 
101 topo_hdl_t *
102 topo_open(int version, const char *rootdir, int *errp)
103 {
104 	topo_hdl_t *thp = NULL;
105 	topo_alloc_t *tap;
106 	struct stat st;
107 
108 	if (version < TOPO_VERSION)
109 		return (set_open_errno(thp, errp, ETOPO_HDL_VER));
110 
111 	if (version > TOPO_VERSION)
112 		return (set_open_errno(thp, errp, ETOPO_HDL_VER));
113 
114 	if (rootdir != NULL && stat(rootdir, &st) < 0)
115 		return (set_open_errno(thp, errp, ETOPO_HDL_INVAL));
116 
117 	if ((thp = topo_zalloc(sizeof (topo_hdl_t), 0)) == NULL)
118 		return (set_open_errno(thp, errp, ETOPO_NOMEM));
119 
120 	if ((tap = topo_zalloc(sizeof (topo_alloc_t), 0)) == NULL)
121 		return (set_open_errno(thp, errp, ETOPO_NOMEM));
122 
123 	/*
124 	 * Install default allocators
125 	 */
126 	tap->ta_flags = 0;
127 	tap->ta_alloc = topo_alloc;
128 	tap->ta_zalloc = topo_zalloc;
129 	tap->ta_free = topo_free;
130 	tap->ta_nvops.nv_ao_alloc = topo_nv_alloc;
131 	tap->ta_nvops.nv_ao_free = topo_nv_free;
132 	(void) nv_alloc_init(&tap->ta_nva, &tap->ta_nvops);
133 	thp->th_alloc = tap;
134 
135 	if ((thp->th_modhash = topo_modhash_create(thp)) == NULL)
136 		return (set_open_errno(thp, errp, ETOPO_NOMEM));
137 
138 	if (rootdir == NULL) {
139 		rootdir = topo_hdl_strdup(thp, "/");
140 		thp->th_rootdir = (char *)rootdir;
141 	} else {
142 		if (strlen(rootdir) > PATH_MAX)
143 			return (set_open_errno(thp, errp, EINVAL));
144 
145 		thp->th_rootdir = topo_hdl_strdup(thp, rootdir);
146 	}
147 
148 	if (thp->th_rootdir == NULL)
149 		return (set_open_errno(thp, errp, ETOPO_NOMEM));
150 
151 	if (topo_builtin_create(thp, thp->th_rootdir) != 0) {
152 		topo_dprintf(TOPO_DBG_ERR, "failed to load builtin modules: "
153 		    "%s\n", topo_hdl_errmsg(thp));
154 		return (NULL);
155 	}
156 
157 	return (thp);
158 }
159 
160 void
161 topo_close(topo_hdl_t *thp)
162 {
163 	ttree_t *tp;
164 
165 	topo_hdl_lock(thp);
166 	if (thp->th_rootdir != NULL)
167 		topo_hdl_strfree(thp, thp->th_rootdir);
168 
169 	/*
170 	 * Clean-up snapshot
171 	 */
172 	topo_snap_destroy(thp);
173 
174 	/*
175 	 * Clean-up trees
176 	 */
177 	while ((tp = topo_list_next(&thp->th_trees)) != NULL) {
178 		topo_list_delete(&thp->th_trees, tp);
179 		topo_tree_destroy(thp, tp);
180 	}
181 
182 	/*
183 	 * Unload all plugins
184 	 */
185 	topo_modhash_unload_all(thp);
186 
187 	if (thp->th_modhash != NULL)
188 		topo_modhash_destroy(thp);
189 	if (thp->th_alloc != NULL)
190 		topo_free(thp->th_alloc, sizeof (topo_alloc_t));
191 
192 	topo_hdl_unlock(thp);
193 
194 	topo_free(thp, sizeof (topo_hdl_t));
195 }
196 
197 static char *
198 topo_snap_create(topo_hdl_t *thp, int *errp)
199 {
200 	uuid_t uuid;
201 	char *ustr = NULL;
202 
203 	topo_hdl_lock(thp);
204 	if (thp->th_uuid != NULL) {
205 		*errp = ETOPO_HDL_UUID;
206 		topo_hdl_unlock(thp);
207 		return (NULL);
208 	}
209 
210 	if ((thp->th_uuid = topo_hdl_zalloc(thp, TOPO_UUID_SIZE)) == NULL) {
211 		*errp = ETOPO_NOMEM;
212 		topo_dprintf(TOPO_DBG_ERR, "unable to allocate uuid: %s\n",
213 		    topo_strerror(*errp));
214 		topo_hdl_unlock(thp);
215 		return (NULL);
216 	}
217 
218 	uuid_generate(uuid);
219 	uuid_unparse(uuid, thp->th_uuid);
220 
221 	if (topo_tree_enum_all(thp) < 0) {
222 		topo_dprintf(TOPO_DBG_ERR, "enumeration failure: %s\n",
223 		    topo_hdl_errmsg(thp));
224 		if (topo_hdl_errno(thp) != ETOPO_ENUM_PARTIAL) {
225 			*errp = thp->th_errno;
226 			topo_hdl_unlock(thp);
227 			return (NULL);
228 		}
229 	}
230 
231 	if ((ustr = topo_hdl_strdup(thp, thp->th_uuid)) == NULL)
232 		*errp = ETOPO_NOMEM;
233 
234 	topo_hdl_unlock(thp);
235 
236 	return (ustr);
237 }
238 
239 /*ARGSUSED*/
240 static char *
241 topo_snap_log_create(topo_hdl_t *thp, const char *uuid, int *errp)
242 {
243 	return ((char *)uuid);
244 }
245 
246 /*
247  * Return snapshot id
248  */
249 char *
250 topo_snap_hold(topo_hdl_t *thp, const char *uuid, int *errp)
251 {
252 	if (thp == NULL)
253 		return (NULL);
254 
255 	if (uuid == NULL)
256 		return (topo_snap_create(thp, errp));
257 	else
258 		return (topo_snap_log_create(thp, uuid, errp));
259 }
260 
261 /*ARGSUSED*/
262 static int
263 topo_walk_destroy(topo_hdl_t *thp, tnode_t *node, void *notused)
264 {
265 	tnode_t *cnode;
266 
267 	cnode = topo_child_first(node);
268 
269 	if (cnode != NULL)
270 		return (TOPO_WALK_NEXT);
271 
272 	topo_node_unbind(node);
273 
274 	return (TOPO_WALK_NEXT);
275 }
276 
277 static void
278 topo_snap_destroy(topo_hdl_t *thp)
279 {
280 	int i;
281 	ttree_t *tp;
282 	topo_walk_t *twp;
283 	tnode_t *root;
284 	topo_nodehash_t *nhp;
285 	topo_mod_t *mod;
286 
287 	for (tp = topo_list_next(&thp->th_trees); tp != NULL;
288 	    tp = topo_list_next(tp)) {
289 
290 		root = tp->tt_root;
291 		twp = tp->tt_walk;
292 		/*
293 		 * Clean-up tree nodes from the bottom-up
294 		 */
295 		if ((twp->tw_node = topo_child_first(root)) != NULL) {
296 			twp->tw_cb = topo_walk_destroy;
297 			topo_node_hold(root);
298 			topo_node_hold(twp->tw_node); /* released at walk end */
299 			(void) topo_walk_bottomup(twp, TOPO_WALK_CHILD);
300 			topo_node_rele(root);
301 		}
302 
303 		/*
304 		 * Tidy-up the root node
305 		 */
306 		while ((nhp = topo_list_next(&root->tn_children)) != NULL) {
307 			for (i = 0; i < nhp->th_arrlen; i++) {
308 				assert(nhp->th_nodearr[i] == NULL);
309 			}
310 			mod = nhp->th_enum;
311 			topo_mod_strfree(mod, nhp->th_name);
312 			topo_mod_free(mod, nhp->th_nodearr,
313 			    nhp->th_arrlen * sizeof (tnode_t *));
314 			topo_list_delete(&root->tn_children, nhp);
315 			topo_mod_free(mod, nhp, sizeof (topo_nodehash_t));
316 			topo_mod_rele(mod);
317 		}
318 
319 		/*
320 		 * Release the file handle
321 		 */
322 		if (tp->tt_file != NULL)
323 			topo_file_unload(thp, tp);
324 
325 	}
326 
327 }
328 
329 void
330 topo_snap_release(topo_hdl_t *thp)
331 {
332 	if (thp == NULL)
333 		return;
334 
335 	topo_hdl_lock(thp);
336 	if (thp->th_uuid != NULL) {
337 		topo_hdl_free(thp, thp->th_uuid, TOPO_UUID_SIZE);
338 		topo_snap_destroy(thp);
339 		thp->th_uuid = NULL;
340 	}
341 	topo_hdl_unlock(thp);
342 
343 }
344 
345 topo_walk_t *
346 topo_walk_init(topo_hdl_t *thp, const char *scheme, topo_walk_cb_t cb_f,
347     void *pdata, int *errp)
348 {
349 	tnode_t *child;
350 	ttree_t *tp;
351 	topo_walk_t *wp;
352 
353 	for (tp = topo_list_next(&thp->th_trees); tp != NULL;
354 	    tp = topo_list_next(tp)) {
355 		if (strcmp(scheme, tp->tt_scheme) == 0) {
356 
357 			/*
358 			 * Hold the root node and start walk at the first
359 			 * child node
360 			 */
361 			assert(tp->tt_root != NULL);
362 
363 			topo_node_hold(tp->tt_root);
364 
365 			/*
366 			 * Nothing to walk
367 			 */
368 			if ((child = topo_child_first(tp->tt_root)) == NULL) {
369 				*errp = ETOPO_WALK_EMPTY;
370 				topo_node_rele(tp->tt_root);
371 				return (NULL);
372 			}
373 
374 			if ((wp	= topo_hdl_zalloc(thp, sizeof (topo_walk_t)))
375 			    == NULL) {
376 				*errp = ETOPO_NOMEM;
377 				topo_node_rele(tp->tt_root);
378 				return (NULL);
379 			}
380 
381 			topo_node_hold(child);
382 
383 			wp->tw_root = tp->tt_root;
384 			wp->tw_node = child;
385 			wp->tw_cb = cb_f;
386 			wp->tw_pdata = pdata;
387 			wp->tw_thp = thp;
388 
389 			return (wp);
390 		}
391 	}
392 
393 	*errp = ETOPO_WALK_NOTFOUND;
394 	return (NULL);
395 }
396 
397 static int
398 step_child(tnode_t *cnp, topo_walk_t *wp, int bottomup)
399 {
400 	int status;
401 	tnode_t *nnp;
402 
403 	nnp = topo_child_first(cnp);
404 
405 	if (nnp == NULL)
406 		return (TOPO_WALK_TERMINATE);
407 
408 	topo_dprintf(TOPO_DBG_WALK, "walk through child node %s=%d\n",
409 	    nnp->tn_name, nnp->tn_instance);
410 
411 	topo_node_hold(nnp); /* released on return from walk_step */
412 	wp->tw_node = nnp;
413 	if (bottomup == 1)
414 		status = topo_walk_bottomup(wp, TOPO_WALK_CHILD);
415 	else
416 		status = topo_walk_step(wp, TOPO_WALK_CHILD);
417 
418 	return (status);
419 }
420 
421 static int
422 step_sibling(tnode_t *cnp, topo_walk_t *wp, int bottomup)
423 {
424 	int status;
425 	tnode_t *nnp;
426 
427 	nnp = topo_child_next(cnp->tn_parent, cnp);
428 
429 	if (nnp == NULL)
430 		return (TOPO_WALK_TERMINATE);
431 
432 	topo_dprintf(TOPO_DBG_WALK, "walk through sibling node %s=%d\n",
433 	    nnp->tn_name, nnp->tn_instance);
434 
435 	topo_node_hold(nnp); /* released on return from walk_step */
436 	wp->tw_node = nnp;
437 	if (bottomup == 1)
438 		status = topo_walk_bottomup(wp, TOPO_WALK_CHILD);
439 	else
440 		status = topo_walk_step(wp, TOPO_WALK_CHILD);
441 
442 	return (status);
443 }
444 
445 int
446 topo_walk_step(topo_walk_t *wp, int flag)
447 {
448 	int status;
449 	tnode_t *cnp = wp->tw_node;
450 
451 	if (flag != TOPO_WALK_CHILD && flag != TOPO_WALK_SIBLING) {
452 		topo_node_rele(cnp);
453 		return (TOPO_WALK_ERR);
454 	}
455 
456 	/*
457 	 * End of the line
458 	 */
459 	if (cnp == NULL) {
460 		topo_dprintf(TOPO_DBG_WALK, "walk_step terminated\n");
461 		topo_node_rele(cnp);
462 		return (TOPO_WALK_TERMINATE);
463 	}
464 
465 	topo_dprintf(TOPO_DBG_WALK, "%s walk_step through node %s=%d\n",
466 	    (flag == TOPO_WALK_CHILD ? "TOPO_WALK_CHILD" : "TOPO_WALK_SIBLING"),
467 	    cnp->tn_name, cnp->tn_instance);
468 
469 	if ((status = wp->tw_cb(wp->tw_thp, cnp, wp->tw_pdata))
470 	    != TOPO_WALK_NEXT) {
471 		topo_node_rele(cnp);
472 		return (status);
473 	}
474 
475 	if (flag == TOPO_WALK_CHILD)
476 		status = step_child(cnp, wp, 0);
477 	else
478 		status = step_sibling(cnp, wp, 0);
479 
480 	/*
481 	 * End of the walk, try next child or sibling
482 	 */
483 	if (status == TOPO_WALK_TERMINATE) {
484 		if (flag == TOPO_WALK_CHILD)
485 			status = step_sibling(cnp, wp, 0);
486 		else
487 			status = step_child(cnp, wp, 0);
488 	}
489 
490 	topo_node_rele(cnp); /* done with current node */
491 
492 	return (status);
493 }
494 
495 void
496 topo_walk_fini(topo_walk_t *wp)
497 {
498 	if (wp == NULL)
499 		return;
500 
501 	topo_node_rele(wp->tw_root);
502 
503 	topo_hdl_free(wp->tw_thp, wp, sizeof (topo_walk_t));
504 }
505 
506 int
507 topo_walk_bottomup(topo_walk_t *wp, int flag)
508 {
509 	int status;
510 	tnode_t *cnp;
511 
512 	if (wp == NULL)
513 		return (TOPO_WALK_ERR);
514 
515 	cnp = wp->tw_node;
516 	if (flag != TOPO_WALK_CHILD && flag != TOPO_WALK_SIBLING) {
517 		topo_node_rele(cnp);
518 		return (TOPO_WALK_ERR);
519 	}
520 
521 	/*
522 	 * End of the line
523 	 */
524 	if (cnp == NULL) {
525 		topo_dprintf(TOPO_DBG_WALK, "walk_bottomup terminated\n");
526 		topo_node_rele(cnp);
527 		return (TOPO_WALK_TERMINATE);
528 	}
529 
530 	topo_dprintf(TOPO_DBG_WALK, "%s walk_bottomup through node %s=%d\n",
531 	    (flag == TOPO_WALK_CHILD ? "TOPO_WALK_CHILD" : "TOPO_WALK_SIBLING"),
532 	    cnp->tn_name, cnp->tn_instance);
533 
534 	if (flag == TOPO_WALK_CHILD)
535 		status = step_child(cnp, wp, 1);
536 	else
537 		status = step_sibling(cnp, wp, 1);
538 
539 	/*
540 	 * At a leaf, run the callback
541 	 */
542 	if (status == TOPO_WALK_TERMINATE) {
543 		if ((status = wp->tw_cb(wp->tw_thp, cnp, wp->tw_pdata))
544 		    != TOPO_WALK_NEXT) {
545 			topo_node_rele(cnp);
546 			return (status);
547 		}
548 	}
549 
550 	/*
551 	 * Try next child or sibling
552 	 */
553 	if (status == TOPO_WALK_NEXT) {
554 		if (flag == TOPO_WALK_CHILD)
555 			status = step_sibling(cnp, wp, 1);
556 		else
557 			status = step_child(cnp, wp, 1);
558 	}
559 
560 	topo_node_rele(cnp); /* done with current node */
561 
562 	return (status);
563 }
564