xref: /illumos-gate/usr/src/lib/libhotplug/common/libhotplug.c (revision c65ebfc7045424bd04a6c7719a27b0ad3399ad54)
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 2009 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <stdarg.h>
29 #include <unistd.h>
30 #include <fcntl.h>
31 #include <errno.h>
32 #include <string.h>
33 #include <door.h>
34 #include <libnvpair.h>
35 #include <libhotplug.h>
36 #include <libhotplug_impl.h>
37 #include <sys/sunddi.h>
38 #include <sys/ddi_hp.h>
39 
40 static void	i_hp_dprintf(const char *fmt, ...);
41 static int	i_hp_pack_branch(hp_node_t, char **, size_t *);
42 static int	i_hp_pack_node(hp_node_t, char **, size_t *);
43 static int	i_hp_unpack_node(char *, size_t, hp_node_t, hp_node_t *);
44 static int	i_hp_unpack_branch(char *, size_t, hp_node_t, hp_node_t *);
45 static int	i_hp_call_hotplugd(nvlist_t *, nvlist_t **);
46 static nvlist_t	*i_hp_set_args(hp_cmd_t, const char *, const char *, uint_t,
47 		    const char *, int);
48 static int	i_hp_parse_results(nvlist_t *, hp_node_t *, char **);
49 
50 /*
51  * Global flag to enable debug features.
52  */
53 int	libhotplug_debug = 0;
54 
55 /*
56  * hp_init()
57  *
58  *	Initialize a hotplug information snapshot.
59  */
60 hp_node_t
61 hp_init(const char *path, const char *connection, uint_t flags)
62 {
63 	nvlist_t	*args;
64 	nvlist_t	*results;
65 	hp_node_t	root = NULL;
66 	int		rv;
67 
68 	i_hp_dprintf("hp_init: path=%p, connection=%p, flags=0x%x\n",
69 	    (void *)path, (void *)connection, flags);
70 
71 	/* Check arguments */
72 	if ((path == NULL) || !HP_INIT_FLAGS_VALID(flags)) {
73 		i_hp_dprintf("hp_init: invalid arguments.\n");
74 		errno = EINVAL;
75 		return (NULL);
76 	}
77 
78 	/* Build arguments for door call */
79 	if ((args = i_hp_set_args(HP_CMD_GETINFO, path, connection, flags,
80 	    NULL, 0)) == NULL) {
81 		i_hp_dprintf("hp_init: cannot build arguments nvlist.\n");
82 		errno = ENOMEM;
83 		return (NULL);
84 	}
85 
86 	/* Make the door call to hotplugd */
87 	rv = i_hp_call_hotplugd(args, &results);
88 
89 	/* Arguments no longer needed */
90 	nvlist_free(args);
91 
92 	/* Parse additional results, if any */
93 	if ((rv == 0) && (results != NULL)) {
94 		rv = i_hp_parse_results(results, &root, NULL);
95 		nvlist_free(results);
96 	}
97 
98 	/* Check for errors */
99 	if (rv != 0) {
100 		i_hp_dprintf("hp_init: failure (%s).\n", strerror(rv));
101 		if (root)
102 			hp_fini(root);
103 		errno = rv;
104 		return (NULL);
105 	}
106 
107 	/* Success requires an info snapshot */
108 	if (root == NULL) {
109 		i_hp_dprintf("hp_init: missing info snapshot.\n");
110 		errno = EFAULT;
111 		return (NULL);
112 	}
113 
114 	/* Success */
115 	return (root);
116 }
117 
118 /*
119  * hp_fini()
120  *
121  *	Terminate and clean-up a hotplug information snapshot.
122  */
123 void
124 hp_fini(hp_node_t root)
125 {
126 	hp_node_t	node;
127 	hp_node_t	sibling;
128 	char		*basepath;
129 
130 	i_hp_dprintf("hp_fini: root=%p\n", (void *)root);
131 
132 	if (root == NULL) {
133 		i_hp_dprintf("hp_fini: invalid arguments.\n");
134 		return;
135 	}
136 
137 	/* Extract and free base path */
138 	if (root->hp_basepath) {
139 		basepath = root->hp_basepath;
140 		for (node = root; node != NULL; node = node->hp_sibling)
141 			node->hp_basepath = NULL;
142 		free(basepath);
143 	}
144 
145 	/* Destroy the nodes */
146 	node = root;
147 	while (node) {
148 		sibling = node->hp_sibling;
149 		if (node->hp_child)
150 			hp_fini(node->hp_child);
151 		if (node->hp_name)
152 			free(node->hp_name);
153 		if (node->hp_usage)
154 			free(node->hp_usage);
155 		if (node->hp_description)
156 			free(node->hp_description);
157 		free(node);
158 		node = sibling;
159 	}
160 }
161 
162 /*
163  * hp_traverse()
164  *
165  *	Walk a graph of hotplug nodes, executing a callback on each node.
166  */
167 int
168 hp_traverse(hp_node_t root, void *arg, int (*hp_callback)(hp_node_t, void *arg))
169 {
170 	int		rv;
171 	hp_node_t	node;
172 
173 	i_hp_dprintf("hp_traverse: root=%p, arg=%p, hp_callback=%p\n",
174 	    (void *)root, arg, (void *)hp_callback);
175 
176 	/* Check arguments */
177 	if ((root == NULL) || (hp_callback == NULL)) {
178 		i_hp_dprintf("hp_traverse: invalid arguments.\n");
179 		errno = EINVAL;
180 		return (-1);
181 	}
182 
183 	for (node = root; node; node = node->hp_sibling) {
184 		rv = hp_callback(node, arg);
185 
186 		if (rv == HP_WALK_TERMINATE) {
187 			i_hp_dprintf("hp_traverse: walk terminated.\n");
188 			return (HP_WALK_TERMINATE);
189 		}
190 
191 		if (node->hp_child && (rv != HP_WALK_PRUNECHILD))
192 			if (hp_traverse(node->hp_child, arg, hp_callback) ==
193 			    HP_WALK_TERMINATE) {
194 				i_hp_dprintf("hp_traverse: walk terminated.\n");
195 				return (HP_WALK_TERMINATE);
196 			}
197 
198 		if (rv == HP_WALK_PRUNESIBLING)
199 			break;
200 	}
201 
202 	return (0);
203 }
204 
205 /*
206  * hp_type()
207  *
208  *	Return a node's type.
209  */
210 int
211 hp_type(hp_node_t node)
212 {
213 	i_hp_dprintf("hp_type: node=%p\n", (void *)node);
214 
215 	if (node == NULL) {
216 		i_hp_dprintf("hp_type: invalid arguments.\n");
217 		errno = EINVAL;
218 		return (-1);
219 	}
220 
221 	return (node->hp_type);
222 }
223 
224 /*
225  * hp_name()
226  *
227  *	Return a node's name.
228  */
229 char *
230 hp_name(hp_node_t node)
231 {
232 	i_hp_dprintf("hp_name: node=%p\n", (void *)node);
233 
234 	if (node == NULL) {
235 		i_hp_dprintf("hp_name: invalid arguments.\n");
236 		errno = EINVAL;
237 		return (NULL);
238 	}
239 
240 	if (node->hp_name == NULL) {
241 		i_hp_dprintf("hp_name: missing name value.\n");
242 		errno = EFAULT;
243 	}
244 
245 	return (node->hp_name);
246 }
247 
248 /*
249  * hp_state()
250  *
251  *	Return a node's current state.
252  */
253 int
254 hp_state(hp_node_t node)
255 {
256 	i_hp_dprintf("hp_state: node=%p\n", (void *)node);
257 
258 	if (node == NULL) {
259 		i_hp_dprintf("hp_state: invalid arguments.\n");
260 		errno = EINVAL;
261 		return (-1);
262 	}
263 
264 	if ((node->hp_type != HP_NODE_CONNECTOR) &&
265 	    (node->hp_type != HP_NODE_PORT)) {
266 		i_hp_dprintf("hp_state: operation not supported.\n");
267 		errno = ENOTSUP;
268 		return (-1);
269 	}
270 
271 	return (node->hp_state);
272 }
273 
274 /*
275  * hp_usage()
276  *
277  *	Return a usage description for usage nodes.
278  */
279 char *
280 hp_usage(hp_node_t node)
281 {
282 	i_hp_dprintf("hp_usage: node=%p\n", (void *)node);
283 
284 	if (node == NULL) {
285 		i_hp_dprintf("hp_usage: invalid arguments.\n");
286 		errno = EINVAL;
287 		return (NULL);
288 	}
289 
290 	if (node->hp_type != HP_NODE_USAGE) {
291 		i_hp_dprintf("hp_usage: operation not supported.\n");
292 		errno = ENOTSUP;
293 		return (NULL);
294 	}
295 
296 	if (node->hp_usage == NULL) {
297 		i_hp_dprintf("hp_usage: missing usage value.\n");
298 		errno = EFAULT;
299 	}
300 
301 	return (node->hp_usage);
302 }
303 
304 /*
305  * hp_description()
306  *
307  *	Return a type description (e.g. "PCI slot") for connection nodes.
308  */
309 char *
310 hp_description(hp_node_t node)
311 {
312 	i_hp_dprintf("hp_description: node=%p\n", (void *)node);
313 
314 	if (node == NULL) {
315 		i_hp_dprintf("hp_description: invalid arguments.\n");
316 		errno = EINVAL;
317 		return (NULL);
318 	}
319 
320 	if ((node->hp_type != HP_NODE_CONNECTOR) &&
321 	    (node->hp_type != HP_NODE_PORT)) {
322 		i_hp_dprintf("hp_description: operation not supported.\n");
323 		errno = ENOTSUP;
324 		return (NULL);
325 	}
326 
327 	if (node->hp_description == NULL) {
328 		i_hp_dprintf("hp_description: missing description value.\n");
329 		errno = EFAULT;
330 	}
331 
332 	return (node->hp_description);
333 }
334 
335 /*
336  * hp_last_change()
337  *
338  *	Return when the state of a connection was last changed.
339  */
340 time_t
341 hp_last_change(hp_node_t node)
342 {
343 	i_hp_dprintf("hp_last_change: node=%p\n", (void *)node);
344 
345 	if (node == NULL) {
346 		i_hp_dprintf("hp_last_change: invalid arguments.\n");
347 		errno = EINVAL;
348 		return (NULL);
349 	}
350 
351 	if ((node->hp_type != HP_NODE_CONNECTOR) &&
352 	    (node->hp_type != HP_NODE_PORT)) {
353 		i_hp_dprintf("hp_last_change: operation not supported.\n");
354 		errno = ENOTSUP;
355 		return (NULL);
356 	}
357 
358 	return (node->hp_last_change);
359 }
360 
361 /*
362  * hp_parent()
363  *
364  *	Return a node's parent node.
365  */
366 hp_node_t
367 hp_parent(hp_node_t node)
368 {
369 	i_hp_dprintf("hp_parent: node=%p\n", (void *)node);
370 
371 	if (node == NULL) {
372 		i_hp_dprintf("hp_parent: invalid arguments.\n");
373 		errno = EINVAL;
374 		return (NULL);
375 	}
376 
377 	if (node->hp_parent == NULL) {
378 		i_hp_dprintf("hp_parent: node has no parent.\n");
379 		errno = ENXIO;
380 	}
381 
382 	return (node->hp_parent);
383 }
384 
385 /*
386  * hp_child()
387  *
388  *	Return a node's first child node.
389  */
390 hp_node_t
391 hp_child(hp_node_t node)
392 {
393 	i_hp_dprintf("hp_child: node=%p\n", (void *)node);
394 
395 	if (node == NULL) {
396 		i_hp_dprintf("hp_child: invalid arguments.\n");
397 		errno = EINVAL;
398 		return (NULL);
399 	}
400 
401 	if (node->hp_child == NULL) {
402 		i_hp_dprintf("hp_child: node has no child.\n");
403 		errno = ENXIO;
404 	}
405 
406 	return (node->hp_child);
407 }
408 
409 /*
410  * hp_sibling()
411  *
412  *	Return a node's next sibling node.
413  */
414 hp_node_t
415 hp_sibling(hp_node_t node)
416 {
417 	i_hp_dprintf("hp_sibling: node=%p\n", (void *)node);
418 
419 	if (node == NULL) {
420 		i_hp_dprintf("hp_sibling: invalid arguments.\n");
421 		errno = EINVAL;
422 		return (NULL);
423 	}
424 
425 	if (node->hp_sibling == NULL) {
426 		i_hp_dprintf("hp_sibling: node has no sibling.\n");
427 		errno = ENXIO;
428 	}
429 
430 	return (node->hp_sibling);
431 }
432 
433 /*
434  * hp_path()
435  *
436  *	Return the path (and maybe connection name) of a node.
437  *	The caller must supply two buffers, each MAXPATHLEN size.
438  */
439 int
440 hp_path(hp_node_t node, char *path, char *connection)
441 {
442 	hp_node_t	root = NULL;
443 	hp_node_t	parent;
444 	int		i;
445 	char		*s;
446 	char		components[MAXPATHLEN];
447 
448 	i_hp_dprintf("hp_path: node=%p, path=%p, connection=%p\n", (void *)node,
449 	    (void *)path, (void *)connection);
450 
451 	if ((node == NULL) || (path == NULL) || (connection == NULL)) {
452 		i_hp_dprintf("hp_path: invalid arguments.\n");
453 		return (EINVAL);
454 	}
455 
456 	(void) memset(path, 0, MAXPATHLEN);
457 	(void) memset(connection, 0, MAXPATHLEN);
458 	(void) memset(components, 0, MAXPATHLEN);
459 
460 	/*  Set 'connection' only for connectors and ports */
461 	if ((node->hp_type == HP_NODE_CONNECTOR) ||
462 	    (node->hp_type == HP_NODE_PORT))
463 		(void) strlcpy(connection, node->hp_name, MAXPATHLEN);
464 
465 	/* Trace back to the root node, accumulating components */
466 	for (parent = node; parent != NULL; parent = parent->hp_parent) {
467 		if (parent->hp_type == HP_NODE_DEVICE) {
468 			(void) strlcat(components, "/", MAXPATHLEN);
469 			(void) strlcat(components, parent->hp_name, MAXPATHLEN);
470 		}
471 		if (parent->hp_parent == NULL)
472 			root = parent;
473 	}
474 
475 	/* Ensure the snapshot actually contains a base path */
476 	if (root->hp_basepath == NULL) {
477 		i_hp_dprintf("hp_path: missing base pathname.\n");
478 		return (EFAULT);
479 	}
480 
481 	/*
482 	 * Construct the path.  Start with the base path from the root
483 	 * node, then append the accumulated components in reverse order.
484 	 */
485 	if (strcmp(root->hp_basepath, "/") != 0) {
486 		(void) strlcat(path, root->hp_basepath, MAXPATHLEN);
487 		if ((root->hp_type == HP_NODE_DEVICE) &&
488 		    ((s = strrchr(path, '/')) != NULL))
489 			*s = '\0';
490 	}
491 	for (i = strlen(components) - 1; i >= 0; i--) {
492 		if (components[i] == '/') {
493 			(void) strlcat(path, &components[i], MAXPATHLEN);
494 			components[i] = '\0';
495 		}
496 	}
497 
498 	return (0);
499 }
500 
501 /*
502  * hp_set_state()
503  *
504  *	Initiate a state change operation on a node.
505  */
506 int
507 hp_set_state(hp_node_t node, uint_t flags, int state, hp_node_t *resultsp)
508 {
509 	hp_node_t	root = NULL;
510 	nvlist_t	*args;
511 	nvlist_t	*results;
512 	int		rv;
513 	char		path[MAXPATHLEN];
514 	char		connection[MAXPATHLEN];
515 
516 	i_hp_dprintf("hp_set_state: node=%p, flags=0x%x, state=0x%x, "
517 	    "resultsp=%p\n", (void *)node, flags, state, (void *)resultsp);
518 
519 	/* Check arguments */
520 	if ((node == NULL) || (resultsp == NULL) ||
521 	    !HP_SET_STATE_FLAGS_VALID(flags)) {
522 		i_hp_dprintf("hp_set_state: invalid arguments.\n");
523 		return (EINVAL);
524 	}
525 
526 	/* Check node type */
527 	if ((node->hp_type != HP_NODE_CONNECTOR) &&
528 	    (node->hp_type != HP_NODE_PORT)) {
529 		i_hp_dprintf("hp_set_state: operation not supported.\n");
530 		return (ENOTSUP);
531 	}
532 
533 	/* Check that target state is valid */
534 	switch (state) {
535 	case DDI_HP_CN_STATE_PRESENT:
536 	case DDI_HP_CN_STATE_POWERED:
537 	case DDI_HP_CN_STATE_ENABLED:
538 		if (node->hp_type != HP_NODE_CONNECTOR) {
539 			i_hp_dprintf("hp_set_state: mismatched target.\n");
540 			return (ENOTSUP);
541 		}
542 		break;
543 	case DDI_HP_CN_STATE_PORT_PRESENT:
544 	case DDI_HP_CN_STATE_OFFLINE:
545 	case DDI_HP_CN_STATE_ONLINE:
546 		if (node->hp_type != HP_NODE_PORT) {
547 			i_hp_dprintf("hp_set_state: mismatched target.\n");
548 			return (ENOTSUP);
549 		}
550 		break;
551 	default:
552 		i_hp_dprintf("hp_set_state: invalid target state.\n");
553 		return (EINVAL);
554 	}
555 
556 	/* Get path and connection of specified node */
557 	if ((rv = hp_path(node, path, connection)) != 0)
558 		return (rv);
559 
560 	/* Build arguments for door call */
561 	if ((args = i_hp_set_args(HP_CMD_CHANGESTATE, path, connection, flags,
562 	    NULL, state)) == NULL)
563 		return (ENOMEM);
564 
565 	/* Make the door call to hotplugd */
566 	rv = i_hp_call_hotplugd(args, &results);
567 
568 	/* Arguments no longer needed */
569 	nvlist_free(args);
570 
571 	/* Parse additional results, if any */
572 	if ((rv == 0) && (results != NULL)) {
573 		rv = i_hp_parse_results(results, &root, NULL);
574 		nvlist_free(results);
575 		*resultsp = root;
576 	}
577 
578 	/* Done */
579 	return (rv);
580 }
581 
582 /*
583  * hp_set_private()
584  *
585  *	Set bus private options on the hotplug connection
586  *	indicated by the given hotplug information node.
587  */
588 int
589 hp_set_private(hp_node_t node, const char *options, char **resultsp)
590 {
591 	int		rv;
592 	nvlist_t	*args;
593 	nvlist_t	*results;
594 	char		*values = NULL;
595 	char		path[MAXPATHLEN];
596 	char		connection[MAXPATHLEN];
597 
598 	i_hp_dprintf("hp_set_private: node=%p, options=%p, resultsp=%p\n",
599 	    (void *)node, (void *)options, (void *)resultsp);
600 
601 	/* Check arguments */
602 	if ((node == NULL) || (options == NULL) || (resultsp == NULL)) {
603 		i_hp_dprintf("hp_set_private: invalid arguments.\n");
604 		return (EINVAL);
605 	}
606 
607 	/* Check node type */
608 	if (node->hp_type != HP_NODE_CONNECTOR) {
609 		i_hp_dprintf("hp_set_private: operation not supported.\n");
610 		return (ENOTSUP);
611 	}
612 
613 	/* Initialize results */
614 	*resultsp = NULL;
615 
616 	/* Get path and connection of specified node */
617 	if ((rv = hp_path(node, path, connection)) != 0)
618 		return (rv);
619 
620 	/* Build arguments for door call */
621 	if ((args = i_hp_set_args(HP_CMD_SETPRIVATE, path, connection, 0,
622 	    options, 0)) == NULL)
623 		return (ENOMEM);
624 
625 	/* Make the door call to hotplugd */
626 	rv = i_hp_call_hotplugd(args, &results);
627 
628 	/* Arguments no longer needed */
629 	nvlist_free(args);
630 
631 	/* Parse additional results, if any */
632 	if ((rv == 0) && (results != NULL)) {
633 		rv = i_hp_parse_results(results, NULL, &values);
634 		nvlist_free(results);
635 		*resultsp = values;
636 	}
637 
638 	/* Done */
639 	return (rv);
640 }
641 
642 /*
643  * hp_get_private()
644  *
645  *	Get bus private options on the hotplug connection
646  *	indicated by the given hotplug information node.
647  */
648 int
649 hp_get_private(hp_node_t node, const char *options, char **resultsp)
650 {
651 	int		rv;
652 	nvlist_t	*args;
653 	nvlist_t	*results;
654 	char		*values = NULL;
655 	char		path[MAXPATHLEN];
656 	char		connection[MAXPATHLEN];
657 
658 	i_hp_dprintf("hp_get_private: node=%p, options=%p, resultsp=%p\n",
659 	    (void *)node, (void *)options, (void *)resultsp);
660 
661 	/* Check arguments */
662 	if ((node == NULL) || (options == NULL) || (resultsp == NULL)) {
663 		i_hp_dprintf("hp_get_private: invalid arguments.\n");
664 		return (EINVAL);
665 	}
666 
667 	/* Check node type */
668 	if (node->hp_type != HP_NODE_CONNECTOR) {
669 		i_hp_dprintf("hp_get_private: operation not supported.\n");
670 		return (ENOTSUP);
671 	}
672 
673 	/* Initialize results */
674 	*resultsp = NULL;
675 
676 	/* Get path and connection of specified node */
677 	if ((rv = hp_path(node, path, connection)) != 0)
678 		return (rv);
679 
680 	/* Build arguments for door call */
681 	if ((args = i_hp_set_args(HP_CMD_GETPRIVATE, path, connection, 0,
682 	    options, 0)) == NULL)
683 		return (ENOMEM);
684 
685 	/* Make the door call to hotplugd */
686 	rv = i_hp_call_hotplugd(args, &results);
687 
688 	/* Arguments no longer needed */
689 	nvlist_free(args);
690 
691 	/* Parse additional results, if any */
692 	if ((rv == 0) && (results != NULL)) {
693 		rv = i_hp_parse_results(results, NULL, &values);
694 		nvlist_free(results);
695 		*resultsp = values;
696 	}
697 
698 	/* Done */
699 	return (rv);
700 }
701 
702 /*
703  * hp_pack()
704  *
705  *	Given the root of a hotplug information snapshot, pack
706  *	it into a contiguous byte array so that it is suitable
707  *	for network transport.
708  */
709 int
710 hp_pack(hp_node_t root, char **bufp, size_t *lenp)
711 {
712 	hp_node_t	node;
713 	nvlist_t	*nvl;
714 	char		*buf;
715 	size_t		len;
716 	int		rv;
717 
718 	i_hp_dprintf("hp_pack: root=%p, bufp=%p, lenp=%p\n", (void *)root,
719 	    (void *)bufp, (void *)lenp);
720 
721 	if ((root == NULL) || (bufp == NULL) || (lenp == NULL)) {
722 		i_hp_dprintf("hp_pack: invalid arguments.\n");
723 		return (EINVAL);
724 	}
725 
726 	*lenp = 0;
727 	*bufp = NULL;
728 
729 	if (nvlist_alloc(&nvl, 0, 0) != 0) {
730 		i_hp_dprintf("hp_pack: nvlist_alloc() failed (%s).\n",
731 		    strerror(errno));
732 		return (ENOMEM);
733 	}
734 
735 	if (root->hp_basepath != NULL) {
736 		rv = nvlist_add_string(nvl, HP_INFO_BASE, root->hp_basepath);
737 		if (rv != 0) {
738 			nvlist_free(nvl);
739 			return (rv);
740 		}
741 	}
742 
743 	for (node = root; node != NULL; node = node->hp_sibling) {
744 		if ((rv = i_hp_pack_branch(node, &buf, &len)) == 0) {
745 			rv = nvlist_add_byte_array(nvl, HP_INFO_BRANCH,
746 			    (uchar_t *)buf, len);
747 			free(buf);
748 		}
749 		if (rv != 0) {
750 			nvlist_free(nvl);
751 			return (rv);
752 		}
753 	}
754 
755 	len = 0;
756 	buf = NULL;
757 	if ((rv = nvlist_pack(nvl, &buf, &len, NV_ENCODE_NATIVE, 0)) == 0) {
758 		*lenp = len;
759 		*bufp = buf;
760 	}
761 
762 	nvlist_free(nvl);
763 
764 	return (rv);
765 }
766 
767 /*
768  * hp_unpack()
769  *
770  *	Unpack a hotplug information snapshot for normal usage.
771  */
772 int
773 hp_unpack(char *packed_buf, size_t packed_len, hp_node_t *retp)
774 {
775 	hp_node_t	root;
776 	hp_node_t	root_list = NULL;
777 	hp_node_t	prev_root = NULL;
778 	nvlist_t	*nvl = NULL;
779 	nvpair_t	*nvp;
780 	char		*basepath = NULL;
781 	int		rv;
782 
783 	i_hp_dprintf("hp_unpack: packed_buf=%p, packed_len=%u, retp=%p\n",
784 	    (void *)packed_buf, (uint32_t)packed_len, (void *)retp);
785 
786 	if ((packed_buf == NULL) || (packed_len == 0) || (retp == NULL)) {
787 		i_hp_dprintf("hp_unpack: invalid arguments.\n");
788 		return (EINVAL);
789 	}
790 
791 	if ((rv = nvlist_unpack(packed_buf, packed_len, &nvl, 0)) != 0)
792 		return (rv);
793 
794 	if (nvlist_next_nvpair(nvl, NULL) == NULL) {
795 		nvlist_free(nvl);
796 		errno = EINVAL;
797 		return (NULL);
798 	}
799 
800 	for (nvp = NULL; nvp = nvlist_next_nvpair(nvl, nvp); ) {
801 
802 		rv = EINVAL;
803 
804 		if (strcmp(nvpair_name(nvp), HP_INFO_BASE) == 0) {
805 			char	*val_string;
806 
807 			if ((rv = nvpair_value_string(nvp, &val_string)) == 0) {
808 				if ((basepath = strdup(val_string)) == NULL)
809 					rv = ENOMEM;
810 			}
811 
812 		} else if (strcmp(nvpair_name(nvp), HP_INFO_BRANCH) == 0) {
813 			size_t		len = 0;
814 			char		*buf = NULL;
815 
816 			if ((rv = nvpair_value_byte_array(nvp,
817 			    (uchar_t **)&buf, (uint_t *)&len)) == 0) {
818 				rv = i_hp_unpack_branch(buf, len, NULL, &root);
819 			}
820 
821 			if (rv == 0) {
822 				if (prev_root) {
823 					prev_root->hp_sibling = root;
824 				} else {
825 					root_list = root;
826 				}
827 				prev_root = root;
828 			}
829 		}
830 
831 		if (rv != 0) {
832 			if (basepath)
833 				free(basepath);
834 			nvlist_free(nvl);
835 			hp_fini(root_list);
836 			*retp = NULL;
837 			return (rv);
838 		}
839 	}
840 
841 	/* Store the base path in each root node */
842 	if (basepath) {
843 		for (root = root_list; root; root = root->hp_sibling)
844 			root->hp_basepath = basepath;
845 	}
846 
847 	nvlist_free(nvl);
848 	*retp = root_list;
849 	return (0);
850 }
851 
852 /*
853  * i_hp_dprintf()
854  *
855  *	Print debug messages to stderr, but only when the debug flag
856  *	(libhotplug_debug) is set.
857  */
858 /*PRINTFLIKE1*/
859 static void
860 i_hp_dprintf(const char *fmt, ...)
861 {
862 	va_list	ap;
863 
864 	if (libhotplug_debug) {
865 		va_start(ap, fmt);
866 		(void) vfprintf(stderr, fmt, ap);
867 		va_end(ap);
868 	}
869 }
870 
871 /*
872  * i_hp_pack_branch()
873  *
874  *	Pack an individual branch of a hotplug information snapshot.
875  */
876 static int
877 i_hp_pack_branch(hp_node_t root, char **bufp, size_t *lenp)
878 {
879 	hp_node_t	child;
880 	nvlist_t	*nvl;
881 	char		*buf;
882 	size_t		len;
883 	int		rv;
884 
885 	*lenp = 0;
886 	*bufp = NULL;
887 
888 	/* Allocate an nvlist for this branch */
889 	if (nvlist_alloc(&nvl, 0, 0) != 0)
890 		return (ENOMEM);
891 
892 	/* Pack the root of the branch and add it to the nvlist */
893 	if ((rv = i_hp_pack_node(root, &buf, &len)) == 0) {
894 		rv = nvlist_add_byte_array(nvl, HP_INFO_NODE,
895 		    (uchar_t *)buf, len);
896 		free(buf);
897 	}
898 	if (rv != 0) {
899 		nvlist_free(nvl);
900 		return (rv);
901 	}
902 
903 	/* Pack each subordinate branch, and add it to the nvlist */
904 	for (child = root->hp_child; child != NULL; child = child->hp_sibling) {
905 		if ((rv = i_hp_pack_branch(child, &buf, &len)) == 0) {
906 			rv = nvlist_add_byte_array(nvl, HP_INFO_BRANCH,
907 			    (uchar_t *)buf, len);
908 			free(buf);
909 		}
910 		if (rv != 0) {
911 			nvlist_free(nvl);
912 			return (rv);
913 		}
914 	}
915 
916 	/* Pack the resulting nvlist into a single buffer */
917 	len = 0;
918 	buf = NULL;
919 	if ((rv = nvlist_pack(nvl, &buf, &len, NV_ENCODE_NATIVE, 0)) == 0) {
920 		*lenp = len;
921 		*bufp = buf;
922 	}
923 
924 	/* Free the nvlist */
925 	nvlist_free(nvl);
926 
927 	return (rv);
928 }
929 
930 /*
931  * i_hp_pack_node()
932  *
933  *	Pack an individual node of a hotplug information snapshot.
934  */
935 static int
936 i_hp_pack_node(hp_node_t node, char **bufp, size_t *lenp)
937 {
938 	nvlist_t	*nvl;
939 	char		*buf = NULL;
940 	size_t		len = 0;
941 	int		rv;
942 
943 	if (nvlist_alloc(&nvl, 0, 0) != 0)
944 		return (ENOMEM);
945 
946 	if ((rv = nvlist_add_uint32(nvl, HP_INFO_TYPE,
947 	    (uint32_t)node->hp_type)) != 0)
948 		goto fail;
949 
950 	if ((node->hp_name) &&
951 	    ((rv = nvlist_add_string(nvl, HP_INFO_NAME, node->hp_name)) != 0))
952 		goto fail;
953 
954 	if ((node->hp_usage) &&
955 	    ((rv = nvlist_add_string(nvl, HP_INFO_USAGE, node->hp_usage)) != 0))
956 		goto fail;
957 
958 	if ((node->hp_description) &&
959 	    ((rv = nvlist_add_string(nvl, HP_INFO_DESC,
960 	    node->hp_description)) != 0))
961 		goto fail;
962 
963 	if ((rv = nvlist_add_uint32(nvl, HP_INFO_STATE, node->hp_state)) != 0)
964 		goto fail;
965 
966 	if ((node->hp_last_change != 0) &&
967 	    ((rv = nvlist_add_uint32(nvl, HP_INFO_TIME,
968 	    node->hp_last_change)) != 0))
969 		goto fail;
970 
971 	if ((rv = nvlist_pack(nvl, &buf, &len, NV_ENCODE_NATIVE, 0)) != 0)
972 		goto fail;
973 
974 	*bufp = buf;
975 	*lenp = len;
976 	nvlist_free(nvl);
977 	return (0);
978 
979 fail:
980 	*bufp = NULL;
981 	*lenp = 0;
982 	nvlist_free(nvl);
983 	return (rv);
984 }
985 
986 /*
987  * i_hp_unpack_branch()
988  *
989  *	Unpack a branch of hotplug information nodes.
990  */
991 static int
992 i_hp_unpack_branch(char *packed_buf, size_t packed_len, hp_node_t parent,
993     hp_node_t *retp)
994 {
995 	hp_node_t	node = NULL;
996 	hp_node_t	child;
997 	hp_node_t	prev_child = NULL;
998 	nvlist_t	*nvl = NULL;
999 	nvpair_t	*nvp;
1000 	char		*buf;
1001 	size_t		len;
1002 	int		rv;
1003 
1004 	/* Initialize results */
1005 	*retp = NULL;
1006 
1007 	/* Unpack the nvlist for this branch */
1008 	if ((rv = nvlist_unpack(packed_buf, packed_len, &nvl, 0)) != 0)
1009 		return (rv);
1010 
1011 	/*
1012 	 * Unpack the branch.  The first item in the nvlist is
1013 	 * always the root node.  And zero or more subordinate
1014 	 * branches may be packed afterward.
1015 	 */
1016 	for (nvp = NULL; nvp = nvlist_next_nvpair(nvl, nvp); ) {
1017 
1018 		len = 0;
1019 		buf = NULL;
1020 
1021 		if (strcmp(nvpair_name(nvp), HP_INFO_NODE) == 0) {
1022 
1023 			/* Check that there is only one root node */
1024 			if (node != NULL) {
1025 				hp_fini(node);
1026 				nvlist_free(nvl);
1027 				return (EFAULT);
1028 			}
1029 
1030 			if ((rv = nvpair_value_byte_array(nvp, (uchar_t **)&buf,
1031 			    (uint_t *)&len)) == 0)
1032 				rv = i_hp_unpack_node(buf, len, parent, &node);
1033 
1034 			if (rv != 0) {
1035 				nvlist_free(nvl);
1036 				return (rv);
1037 			}
1038 
1039 		} else if (strcmp(nvpair_name(nvp), HP_INFO_BRANCH) == 0) {
1040 
1041 			if ((rv = nvpair_value_byte_array(nvp, (uchar_t **)&buf,
1042 			    (uint_t *)&len)) == 0)
1043 				rv = i_hp_unpack_branch(buf, len, node, &child);
1044 
1045 			if (rv != 0) {
1046 				hp_fini(node);
1047 				nvlist_free(nvl);
1048 				return (rv);
1049 			}
1050 
1051 			if (prev_child) {
1052 				prev_child->hp_sibling = child;
1053 			} else {
1054 				node->hp_child = child;
1055 			}
1056 			prev_child = child;
1057 		}
1058 	}
1059 
1060 	nvlist_free(nvl);
1061 	*retp = node;
1062 	return (0);
1063 }
1064 
1065 /*
1066  * i_hp_unpack_node()
1067  *
1068  *	Unpack an individual hotplug information node.
1069  */
1070 static int
1071 i_hp_unpack_node(char *buf, size_t len, hp_node_t parent, hp_node_t *retp)
1072 {
1073 	hp_node_t	node;
1074 	nvlist_t	*nvl;
1075 	nvpair_t	*nvp;
1076 	uint32_t	val_uint32;
1077 	char		*val_string;
1078 	int		rv = 0;
1079 
1080 	/* Initialize results */
1081 	*retp = NULL;
1082 
1083 	/* Unpack node into an nvlist */
1084 	if ((nvlist_unpack(buf, len, &nvl, 0) != 0))
1085 		return (EINVAL);
1086 
1087 	/* Allocate the new node */
1088 	if ((node = (hp_node_t)calloc(1, sizeof (struct hp_node))) == NULL) {
1089 		nvlist_free(nvl);
1090 		return (ENOMEM);
1091 	}
1092 
1093 	/* Iterate through nvlist, unpacking each field */
1094 	for (nvp = NULL; nvp = nvlist_next_nvpair(nvl, nvp); ) {
1095 
1096 		if ((strcmp(nvpair_name(nvp), HP_INFO_TYPE) == 0) &&
1097 		    (nvpair_type(nvp) == DATA_TYPE_UINT32)) {
1098 
1099 			(void) nvpair_value_uint32(nvp, &val_uint32);
1100 			node->hp_type = val_uint32;
1101 
1102 		} else if ((strcmp(nvpair_name(nvp), HP_INFO_NAME) == 0) &&
1103 		    (nvpair_type(nvp) == DATA_TYPE_STRING)) {
1104 
1105 			(void) nvpair_value_string(nvp, &val_string);
1106 			if ((node->hp_name = strdup(val_string)) == NULL) {
1107 				rv = ENOMEM;
1108 				break;
1109 			}
1110 
1111 		} else if ((strcmp(nvpair_name(nvp), HP_INFO_STATE) == 0) &&
1112 		    (nvpair_type(nvp) == DATA_TYPE_UINT32)) {
1113 
1114 			(void) nvpair_value_uint32(nvp, &val_uint32);
1115 			node->hp_state = val_uint32;
1116 
1117 		} else if ((strcmp(nvpair_name(nvp), HP_INFO_USAGE) == 0) &&
1118 		    (nvpair_type(nvp) == DATA_TYPE_STRING)) {
1119 
1120 			(void) nvpair_value_string(nvp, &val_string);
1121 			if ((node->hp_usage = strdup(val_string)) == NULL) {
1122 				rv = ENOMEM;
1123 				break;
1124 			}
1125 
1126 		} else if ((strcmp(nvpair_name(nvp), HP_INFO_DESC) == 0) &&
1127 		    (nvpair_type(nvp) == DATA_TYPE_STRING)) {
1128 
1129 			(void) nvpair_value_string(nvp, &val_string);
1130 			if ((node->hp_description = strdup(val_string))
1131 			    == NULL) {
1132 				rv = ENOMEM;
1133 				break;
1134 			}
1135 
1136 		} else if ((strcmp(nvpair_name(nvp), HP_INFO_TIME) == 0) &&
1137 		    (nvpair_type(nvp) == DATA_TYPE_UINT32)) {
1138 
1139 			(void) nvpair_value_uint32(nvp, &val_uint32);
1140 			node->hp_last_change = (time_t)val_uint32;
1141 
1142 		} else {
1143 			i_hp_dprintf("i_hp_unpack_node: unrecognized: '%s'\n",
1144 			    nvpair_name(nvp));
1145 		}
1146 	}
1147 
1148 	/* Unpacked nvlist no longer needed */
1149 	nvlist_free(nvl);
1150 
1151 	/* Check for errors */
1152 	if (rv != 0) {
1153 		hp_fini(node);
1154 		return (rv);
1155 	}
1156 
1157 	/* Success */
1158 	node->hp_parent = parent;
1159 	*retp = node;
1160 	return (0);
1161 }
1162 
1163 /*
1164  * i_hp_call_hotplugd()
1165  *
1166  *	Perform a door call to the hotplug daemon.
1167  */
1168 static int
1169 i_hp_call_hotplugd(nvlist_t *args, nvlist_t **resultsp)
1170 {
1171 	door_arg_t	door_arg;
1172 	nvlist_t	*results = NULL;
1173 	char		*buf = NULL;
1174 	size_t		len = 0;
1175 	uint64_t	seqnum;
1176 	int		door_fd;
1177 	int		rv;
1178 
1179 	/* Initialize results */
1180 	*resultsp = NULL;
1181 
1182 	/* Open door */
1183 	if ((door_fd = open(HOTPLUGD_DOOR, O_RDONLY)) < 0) {
1184 		i_hp_dprintf("i_hp_call_hotplugd: cannot open door (%s)\n",
1185 		    strerror(errno));
1186 		return (EBADF);
1187 	}
1188 
1189 	/* Pack the nvlist of arguments */
1190 	if ((rv = nvlist_pack(args, &buf, &len, NV_ENCODE_NATIVE, 0)) != 0) {
1191 		i_hp_dprintf("i_hp_call_hotplugd: cannot pack arguments (%s)\n",
1192 		    strerror(rv));
1193 		return (rv);
1194 	}
1195 
1196 	/* Set the door argument using the packed arguments */
1197 	door_arg.data_ptr = buf;
1198 	door_arg.data_size = len;
1199 	door_arg.desc_ptr = NULL;
1200 	door_arg.desc_num = 0;
1201 	door_arg.rbuf = (char *)(uintptr_t)&rv;
1202 	door_arg.rsize = sizeof (rv);
1203 
1204 	/* Attempt the door call */
1205 	if (door_call(door_fd, &door_arg) != 0) {
1206 		rv = errno;
1207 		i_hp_dprintf("i_hp_call_hotplugd: door call failed (%s)\n",
1208 		    strerror(rv));
1209 		(void) close(door_fd);
1210 		free(buf);
1211 		return (rv);
1212 	}
1213 
1214 	/* The arguments are no longer needed */
1215 	free(buf);
1216 
1217 	/*
1218 	 * If results are not in the original buffer provided,
1219 	 * then check and process the new results buffer.
1220 	 */
1221 	if (door_arg.rbuf != (char *)(uintptr_t)&rv) {
1222 
1223 		/*
1224 		 * First check that the buffer is valid.  Then check for
1225 		 * the simple case where a short result code was sent.
1226 		 * The last case is a packed nvlist was returned, which
1227 		 * needs to be unpacked.
1228 		 */
1229 		if ((door_arg.rbuf == NULL) ||
1230 		    (door_arg.data_size < sizeof (rv))) {
1231 			i_hp_dprintf("i_hp_call_hotplugd: invalid results.\n");
1232 			rv = EFAULT;
1233 
1234 		} else if (door_arg.data_size == sizeof (rv)) {
1235 			rv = *(int *)(uintptr_t)door_arg.rbuf;
1236 
1237 		} else if ((rv = nvlist_unpack(door_arg.rbuf,
1238 		    door_arg.data_size, &results, 0)) != 0) {
1239 			i_hp_dprintf("i_hp_call_hotplugd: "
1240 			    "cannot unpack results (%s).\n", strerror(rv));
1241 			results = NULL;
1242 			rv = EFAULT;
1243 		}
1244 
1245 		/* Unmap the results buffer */
1246 		if (door_arg.rbuf != NULL)
1247 			(void) munmap(door_arg.rbuf, door_arg.rsize);
1248 
1249 		/*
1250 		 * In the case of a packed nvlist, notify the daemon
1251 		 * that it can free the result buffer from its heap.
1252 		 */
1253 		if ((results != NULL) &&
1254 		    (nvlist_lookup_uint64(results, HPD_SEQNUM, &seqnum) == 0)) {
1255 			door_arg.data_ptr = (char *)(uintptr_t)&seqnum;
1256 			door_arg.data_size = sizeof (seqnum);
1257 			door_arg.desc_ptr = NULL;
1258 			door_arg.desc_num = 0;
1259 			door_arg.rbuf = NULL;
1260 			door_arg.rsize = 0;
1261 			(void) door_call(door_fd, &door_arg);
1262 			if (door_arg.rbuf != NULL)
1263 				(void) munmap(door_arg.rbuf, door_arg.rsize);
1264 		}
1265 
1266 		*resultsp = results;
1267 	}
1268 
1269 	(void) close(door_fd);
1270 	return (rv);
1271 }
1272 
1273 /*
1274  * i_hp_set_args()
1275  *
1276  *	Construct an nvlist of arguments for a hotplugd door call.
1277  */
1278 static nvlist_t *
1279 i_hp_set_args(hp_cmd_t cmd, const char *path, const char *connection,
1280     uint_t flags, const char *options, int state)
1281 {
1282 	nvlist_t	*args;
1283 
1284 	/* Allocate a new nvlist */
1285 	if (nvlist_alloc(&args, NV_UNIQUE_NAME_TYPE, 0) != 0)
1286 		return (NULL);
1287 
1288 	/* Add common arguments */
1289 	if ((nvlist_add_int32(args, HPD_CMD, cmd) != 0) ||
1290 	    (nvlist_add_string(args, HPD_PATH, path) != 0)) {
1291 		nvlist_free(args);
1292 		return (NULL);
1293 	}
1294 
1295 	/* Add connection, but only if defined */
1296 	if ((connection != NULL) && (connection[0] != '\0') &&
1297 	    (nvlist_add_string(args, HPD_CONNECTION, connection) != 0)) {
1298 		nvlist_free(args);
1299 		return (NULL);
1300 	}
1301 
1302 	/* Add flags, but only if defined */
1303 	if ((flags != 0) && (nvlist_add_uint32(args, HPD_FLAGS, flags) != 0)) {
1304 		nvlist_free(args);
1305 		return (NULL);
1306 	}
1307 
1308 	/* Add options, but only if defined */
1309 	if ((options != NULL) &&
1310 	    (nvlist_add_string(args, HPD_OPTIONS, options) != 0)) {
1311 		nvlist_free(args);
1312 		return (NULL);
1313 	}
1314 
1315 	/* Add state, but only for CHANGESTATE command */
1316 	if ((cmd == HP_CMD_CHANGESTATE) &&
1317 	    (nvlist_add_int32(args, HPD_STATE, state) != 0)) {
1318 		nvlist_free(args);
1319 		return (NULL);
1320 	}
1321 
1322 	return (args);
1323 }
1324 
1325 /*
1326  * i_hp_parse_results()
1327  *
1328  *	Parse out individual fields of an nvlist of results from
1329  *	a hotplugd door call.
1330  */
1331 static int
1332 i_hp_parse_results(nvlist_t *results, hp_node_t *rootp, char **optionsp)
1333 {
1334 	int	rv;
1335 
1336 	/* Parse an information snapshot */
1337 	if (rootp) {
1338 		char	*buf = NULL;
1339 		size_t	len = 0;
1340 
1341 		*rootp = NULL;
1342 		if (nvlist_lookup_byte_array(results, HPD_INFO,
1343 		    (uchar_t **)&buf, (uint_t *)&len) == 0) {
1344 			if ((rv = hp_unpack(buf, len, rootp)) != 0)
1345 				return (rv);
1346 		}
1347 	}
1348 
1349 	/* Parse a bus private option string */
1350 	if (optionsp) {
1351 		char	*str;
1352 
1353 		*optionsp = NULL;
1354 		if ((nvlist_lookup_string(results, HPD_OPTIONS, &str) == 0) &&
1355 		    ((*optionsp = strdup(str)) == NULL)) {
1356 			return (ENOMEM);
1357 		}
1358 	}
1359 
1360 	/* Parse result code of the operation */
1361 	if (nvlist_lookup_int32(results, HPD_STATUS, &rv) != 0) {
1362 		i_hp_dprintf("i_hp_call_hotplugd: missing status.\n");
1363 		return (EFAULT);
1364 	}
1365 
1366 	return (rv);
1367 }
1368