xref: /illumos-gate/usr/src/cmd/hotplug/hotplug.c (revision 1a2d662a91cee3bf82f41cd47c7ae6f3825d9db2)
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 <string.h>
29 #include <locale.h>
30 #include <libintl.h>
31 #include <alloca.h>
32 #include <getopt.h>
33 #include <libhotplug.h>
34 #include <sys/types.h>
35 #include <sys/sunddi.h>
36 #include <sys/ddi_hp.h>
37 
38 #if !defined(TEXT_DOMAIN)		/* should be defined by cc -D */
39 #define	TEXT_DOMAIN	"SYS_TEST"	/* Use this only if it wasn't */
40 #endif
41 
42 /*
43  * Function prototypes.
44  */
45 static int	cmd_list(int, char **, const char *);
46 static int	cmd_online(int, char **, const char *);
47 static int	cmd_offline(int, char **, const char *);
48 static int	cmd_enable(int, char **, const char *);
49 static int	cmd_disable(int, char **, const char *);
50 static int	cmd_poweron(int, char **, const char *);
51 static int	cmd_poweroff(int, char **, const char *);
52 static int	cmd_getpriv(int, char **, const char *);
53 static int	cmd_setpriv(int, char **, const char *);
54 static int	cmd_changestate(int, char **, const char *);
55 static void	parse_common(int, char **, const char *);
56 static void	parse_flags(int, char **, int *, const char *);
57 static void	parse_target(int, char **, char **, char **, const char *);
58 static void	parse_options(int, char **, char **, const char *);
59 static void	bad_option(int, int, const char *);
60 static void	usage(const char *);
61 static int	list_cb(hp_node_t, void *);
62 static int	list_long_cb(hp_node_t, void *);
63 static int	error_cb(hp_node_t, void *);
64 static void	print_options(const char *);
65 static void	print_error(int);
66 static int	state_atoi(char *);
67 static char	*state_itoa(int);
68 static short	valid_target(int);
69 
70 /*
71  * Define a conversion table for hotplug states.
72  */
73 typedef struct {
74 	int	state;
75 	char	*state_str;
76 	short	valid_target;
77 } hpstate_t;
78 
79 static hpstate_t hpstates[] = {
80 	{ DDI_HP_CN_STATE_EMPTY,	"EMPTY",	0 },
81 	{ DDI_HP_CN_STATE_PRESENT,	"PRESENT",	1 },
82 	{ DDI_HP_CN_STATE_POWERED,	"POWERED",	1 },
83 	{ DDI_HP_CN_STATE_ENABLED,	"ENABLED",	1 },
84 	{ DDI_HP_CN_STATE_PORT_EMPTY,	"PORT-EMPTY",	0 },
85 	{ DDI_HP_CN_STATE_PORT_PRESENT,	"PORT-PRESENT",	1 },
86 	{ DDI_HP_CN_STATE_OFFLINE,	"OFFLINE",	1 },
87 	{ DDI_HP_CN_STATE_ATTACHED,	"ATTACHED",	0 },
88 	{ DDI_HP_CN_STATE_MAINTENANCE,	"MAINTENANCE",	0 },
89 	{ DDI_HP_CN_STATE_ONLINE,	"ONLINE",	1 },
90 	{ 0, 0, 0 }
91 };
92 
93 /*
94  * Define tables of supported subcommands.
95  */
96 typedef struct {
97 	char		*usage_str;
98 	char		*cmd_str;
99 	int		(*func)(int argc, char *argv[], const char *usage_str);
100 } subcmd_t;
101 
102 static subcmd_t	subcmds[] = {
103 	{ "list       [-l] [-v] [<path> [<connection>]]", "list", cmd_list },
104 	{ "online     <path> <port>", "online", cmd_online },
105 	{ "offline    [-f] [-q] <path> <port>", "offline", cmd_offline },
106 	{ "enable     <path> <connector>", "enable", cmd_enable },
107 	{ "disable    [-f] [-q] <path> <connector>", "disable", cmd_disable },
108 	{ "poweron    <path> <connector>", "poweron", cmd_poweron },
109 	{ "poweroff   [-f] [-q] <path> <connector>", "poweroff", cmd_poweroff },
110 	{ "get        -o <options> <path> <connector>", "get", cmd_getpriv },
111 	{ "set        -o <options> <path> <connector>", "set", cmd_setpriv }
112 };
113 
114 static subcmd_t hidden_subcmds[] = {
115 	{ "changestate  [-f] [-q] -s <state> <path> <connection>",
116 	    "changestate", cmd_changestate }
117 };
118 
119 /*
120  * Define tables of command line options.
121  */
122 static const struct option common_opts[] = {
123 	{ "help",	no_argument,		0, '?' },
124 	{ "version",	no_argument,		0, 'V' },
125 	{ 0, 0, 0, 0 }
126 };
127 
128 static const struct option list_opts[] = {
129 	{ "list-path",	no_argument,		0, 'l' },
130 	{ "verbose",	no_argument,		0, 'v' },
131 	{ 0, 0,	0, 0 }
132 };
133 
134 static const struct option flag_opts[] = {
135 	{ "force",	no_argument,		0, 'f' },
136 	{ "query",	no_argument,		0, 'q' },
137 	{ 0, 0,	0, 0 }
138 };
139 
140 static const struct option private_opts[] = {
141 	{ "options",	required_argument,	0, 'o' },
142 	{ 0, 0,	0, 0 }
143 };
144 
145 static const struct option changestate_opts[] = {
146 	{ "force",	no_argument,		0, 'f' },
147 	{ "query",	no_argument,		0, 'q' },
148 	{ "state",	required_argument,	0, 's' },
149 	{ 0, 0,	0, 0 }
150 };
151 
152 /*
153  * Define exit codes.
154  */
155 #define	EXIT_OK		0
156 #define	EXIT_EINVAL	1	/* invalid arguments */
157 #define	EXIT_ENOENT	2	/* path or connection doesn't exist */
158 #define	EXIT_FAILED	3	/* operation failed */
159 #define	EXIT_UNAVAIL	4	/* service not available */
160 
161 /*
162  * Global variables.
163  */
164 static char 	*prog;
165 static char	version[] = "1.0";
166 extern int	errno;
167 
168 /*
169  * main()
170  *
171  *	The main routine determines which subcommand is used,
172  *	and dispatches control to the corresponding function.
173  */
174 int
175 main(int argc, char *argv[])
176 {
177 	int 		i, rv;
178 
179 	(void) setlocale(LC_ALL, "");
180 	(void) textdomain(TEXT_DOMAIN);
181 
182 	if ((prog = strrchr(argv[0], '/')) == NULL)
183 		prog = argv[0];
184 	else
185 		prog++;
186 
187 	if (argc < 2) {
188 		usage(NULL);
189 		return (EXIT_EINVAL);
190 	}
191 
192 	parse_common(argc, argv, NULL);
193 
194 	/* Check the list of defined subcommands. */
195 	for (i = 0; i < (sizeof (subcmds) / sizeof (subcmd_t)); i++) {
196 		if (strcmp(argv[1], subcmds[i].cmd_str) == 0) {
197 			rv = subcmds[i].func(argc - 1, &argv[1],
198 			    subcmds[i].usage_str);
199 			goto finished;
200 		}
201 	}
202 
203 	/* Check the list of hidden subcommands. */
204 	for (i = 0; i < (sizeof (hidden_subcmds) / sizeof (subcmd_t)); i++) {
205 		if (strcmp(argv[1], hidden_subcmds[i].cmd_str) == 0) {
206 			rv = hidden_subcmds[i].func(argc - 1, &argv[1],
207 			    hidden_subcmds[i].usage_str);
208 			goto finished;
209 		}
210 	}
211 
212 	/* No matching subcommand found. */
213 	(void) fprintf(stderr, gettext("ERROR: %s: unknown subcommand '%s'\n"),
214 	    prog, argv[1]);
215 	usage(NULL);
216 	exit(EXIT_EINVAL);
217 
218 finished:
219 	/* Determine exit code */
220 	switch (rv) {
221 	case 0:
222 		break;
223 	case EINVAL:
224 		return (EXIT_EINVAL);
225 	case ENXIO:
226 	case ENOENT:
227 		return (EXIT_ENOENT);
228 	case EBADF:
229 		return (EXIT_UNAVAIL);
230 	default:
231 		return (EXIT_FAILED);
232 	}
233 
234 	return (EXIT_OK);
235 }
236 
237 /*
238  * cmd_list()
239  *
240  *	Subcommand to list hotplug information.
241  */
242 static int
243 cmd_list(int argc, char *argv[], const char *usage_str)
244 {
245 	hp_node_t	root;
246 	char		*path = NULL;
247 	char		*connection = NULL;
248 	boolean_t	long_flag = B_FALSE;
249 	int		flags = 0;
250 	int		opt;
251 
252 	/* Parse command line options */
253 	parse_common(argc, argv, usage_str);
254 	while ((opt = getopt_clip(argc, argv, "lv", list_opts, NULL)) != -1) {
255 		switch (opt) {
256 		case 'l':
257 			long_flag = B_TRUE;
258 			break;
259 		case 'v':
260 			flags |= HPINFOUSAGE;
261 			break;
262 		default:
263 			bad_option(opt, optopt, usage_str);
264 			break;
265 		}
266 	}
267 	parse_target(argc, argv, &path, &connection, usage_str);
268 
269 	/* Default path is "/" */
270 	if (path == NULL)
271 		path = "/";
272 
273 	/* Get hotplug information snapshot */
274 	if ((root = hp_init(path, connection, flags)) == NULL) {
275 		print_error(errno);
276 		return (errno);
277 	}
278 
279 	/* Display hotplug information */
280 	(void) hp_traverse(root, NULL, long_flag ? list_long_cb : list_cb);
281 
282 	/* Discard hotplug information snapshot */
283 	hp_fini(root);
284 
285 	return (0);
286 }
287 
288 /*
289  * cmd_online()
290  *
291  *	Subcommand to online a hotplug port.
292  */
293 static int
294 cmd_online(int argc, char *argv[], const char *usage_str)
295 {
296 	hp_node_t	root;
297 	hp_node_t	results = NULL;
298 	char		*path = NULL;
299 	char		*connection = NULL;
300 	int		rv;
301 
302 	/* Parse command line options */
303 	parse_common(argc, argv, usage_str);
304 	parse_target(argc, argv, &path, &connection, usage_str);
305 
306 	/* Path and connection are required */
307 	if ((path == NULL) || (connection == NULL)) {
308 		(void) fprintf(stderr, gettext("ERROR: too few arguments.\n"));
309 		usage(usage_str);
310 		return (EINVAL);
311 	}
312 
313 	/* Get hotplug information snapshot */
314 	if ((root = hp_init(path, connection, 0)) == NULL) {
315 		print_error(errno);
316 		return (errno);
317 	}
318 
319 	/* Verify target is a port */
320 	if (hp_type(root) != HP_NODE_PORT) {
321 		(void) fprintf(stderr,
322 		    gettext("ERROR: invalid target (must be a port).\n"));
323 		hp_fini(root);
324 		return (EINVAL);
325 	}
326 
327 	/* Do state change */
328 	rv = hp_set_state(root, 0, DDI_HP_CN_STATE_ONLINE, &results);
329 
330 	/* Display results */
331 	if (rv == EIO) {
332 		(void) fprintf(stderr, gettext("ERROR: failed to attach device "
333 		    "drivers or other internal errors.\n"));
334 	} else if (rv != 0) {
335 		print_error(rv);
336 	}
337 	if (results != NULL) {
338 		(void) hp_traverse(results, NULL, error_cb);
339 		hp_fini(results);
340 	}
341 
342 	/* Discard hotplug information snapshot */
343 	hp_fini(root);
344 
345 	return (rv);
346 }
347 
348 /*
349  * cmd_offline()
350  *
351  *	Subcommand to offline a hotplug port.
352  */
353 static int
354 cmd_offline(int argc, char *argv[], const char *usage_str)
355 {
356 	hp_node_t	root;
357 	hp_node_t	results = NULL;
358 	char		*path = NULL;
359 	char		*connection = NULL;
360 	int		flags = 0;
361 	int		rv;
362 
363 	/* Parse command line options */
364 	parse_common(argc, argv, usage_str);
365 	parse_flags(argc, argv, &flags, usage_str);
366 	parse_target(argc, argv, &path, &connection, usage_str);
367 
368 	/* Path and connection are required */
369 	if ((path == NULL) || (connection == NULL)) {
370 		(void) fprintf(stderr, gettext("ERROR: too few arguments.\n"));
371 		usage(usage_str);
372 		return (EINVAL);
373 	}
374 
375 	/* Get hotplug information snapshot */
376 	if ((root = hp_init(path, connection, 0)) == NULL) {
377 		print_error(errno);
378 		return (errno);
379 	}
380 
381 	/* Verify target is a port */
382 	if (hp_type(root) != HP_NODE_PORT) {
383 		(void) fprintf(stderr,
384 		    gettext("ERROR: invalid target (must be a port).\n"));
385 		hp_fini(root);
386 		return (EINVAL);
387 	}
388 
389 	/* Do state change */
390 	rv = hp_set_state(root, flags, DDI_HP_CN_STATE_OFFLINE, &results);
391 
392 	/* Display results */
393 	print_error(rv);
394 	if (results != NULL) {
395 		(void) hp_traverse(results, NULL, error_cb);
396 		hp_fini(results);
397 	}
398 
399 	/* Discard hotplug information snapshot */
400 	hp_fini(root);
401 
402 	return (rv);
403 }
404 
405 /*
406  * cmd_enable()
407  *
408  *	Subcommand to enable a hotplug connector.
409  */
410 static int
411 cmd_enable(int argc, char *argv[], const char *usage_str)
412 {
413 	hp_node_t	root;
414 	hp_node_t	results = NULL;
415 	char		*path = NULL;
416 	char		*connection = NULL;
417 	int		rv;
418 
419 	/* Parse command line options */
420 	parse_common(argc, argv, usage_str);
421 	parse_target(argc, argv, &path, &connection, usage_str);
422 
423 	/* Path and connection are required */
424 	if ((path == NULL) || (connection == NULL)) {
425 		(void) fprintf(stderr, gettext("ERROR: too few arguments.\n"));
426 		usage(usage_str);
427 		return (EINVAL);
428 	}
429 
430 	/* Get hotplug information snapshot */
431 	if ((root = hp_init(path, connection, 0)) == NULL) {
432 		print_error(errno);
433 		return (errno);
434 	}
435 
436 	/* Verify target is a connector */
437 	if (hp_type(root) != HP_NODE_CONNECTOR) {
438 		(void) fprintf(stderr,
439 		    gettext("ERROR: invalid target (must be a connector).\n"));
440 		hp_fini(root);
441 		return (EINVAL);
442 	}
443 
444 	/* Do state change */
445 	rv = hp_set_state(root, 0, DDI_HP_CN_STATE_ENABLED, &results);
446 
447 	/* Display results */
448 	print_error(rv);
449 	if (results != NULL) {
450 		(void) hp_traverse(results, NULL, error_cb);
451 		hp_fini(results);
452 	}
453 
454 	/* Discard hotplug information snapshot */
455 	hp_fini(root);
456 
457 	return (rv);
458 }
459 
460 /*
461  * cmd_disable()
462  *
463  *	Subcommand to disable a hotplug connector.
464  */
465 static int
466 cmd_disable(int argc, char *argv[], const char *usage_str)
467 {
468 	hp_node_t	root;
469 	hp_node_t	results = NULL;
470 	char		*path = NULL;
471 	char		*connection = NULL;
472 	int		flags = 0;
473 	int		rv;
474 
475 	/* Parse command line options */
476 	parse_common(argc, argv, usage_str);
477 	parse_flags(argc, argv, &flags, usage_str);
478 	parse_target(argc, argv, &path, &connection, usage_str);
479 
480 	/* Path and connection are required */
481 	if ((path == NULL) || (connection == NULL)) {
482 		(void) fprintf(stderr, gettext("ERROR: too few arguments.\n"));
483 		usage(usage_str);
484 		return (EINVAL);
485 	}
486 
487 	/* Get hotplug information snapshot */
488 	if ((root = hp_init(path, connection, 0)) == NULL) {
489 		print_error(errno);
490 		return (errno);
491 	}
492 
493 	/* Verify target is a connector */
494 	if (hp_type(root) != HP_NODE_CONNECTOR) {
495 		(void) fprintf(stderr,
496 		    gettext("ERROR: invalid target (must be a connector).\n"));
497 		hp_fini(root);
498 		return (EINVAL);
499 	}
500 
501 	/*
502 	 * Do nothing unless the connector is in the ENABLED state.
503 	 * Otherwise this subcommand becomes an alias for 'poweron.'
504 	 */
505 	if (hp_state(root) != DDI_HP_CN_STATE_ENABLED) {
506 		hp_fini(root);
507 		return (0);
508 	}
509 
510 	/* Do state change */
511 	rv = hp_set_state(root, flags, DDI_HP_CN_STATE_POWERED, &results);
512 
513 	/* Display results */
514 	print_error(rv);
515 	if (results != NULL) {
516 		(void) hp_traverse(results, NULL, error_cb);
517 		hp_fini(results);
518 	}
519 
520 	/* Discard hotplug information snapshot */
521 	hp_fini(root);
522 
523 	return (rv);
524 }
525 
526 /*
527  * cmd_poweron()
528  *
529  *	Subcommand to power on a hotplug connector.
530  */
531 static int
532 cmd_poweron(int argc, char *argv[], const char *usage_str)
533 {
534 	hp_node_t	root;
535 	hp_node_t	results = NULL;
536 	char		*path = NULL;
537 	char		*connection = NULL;
538 	int		rv;
539 
540 	/* Parse command line options */
541 	parse_common(argc, argv, usage_str);
542 	parse_target(argc, argv, &path, &connection, usage_str);
543 
544 	/* Path and connection are required */
545 	if ((path == NULL) || (connection == NULL)) {
546 		(void) fprintf(stderr, gettext("ERROR: too few arguments.\n"));
547 		usage(usage_str);
548 		return (EINVAL);
549 	}
550 
551 	/* Get hotplug information snapshot */
552 	if ((root = hp_init(path, connection, 0)) == NULL) {
553 		print_error(errno);
554 		return (errno);
555 	}
556 
557 	/* Verify target is a connector */
558 	if (hp_type(root) != HP_NODE_CONNECTOR) {
559 		(void) fprintf(stderr,
560 		    gettext("ERROR: invalid target (must be a connector).\n"));
561 		hp_fini(root);
562 		return (EINVAL);
563 	}
564 
565 	/*
566 	 * Do nothing if the connector is already powered.
567 	 * Otherwise this subcommand becomes an alias for 'disable.'
568 	 */
569 	if (hp_state(root) >= DDI_HP_CN_STATE_POWERED) {
570 		hp_fini(root);
571 		return (0);
572 	}
573 
574 	/* Do state change */
575 	rv = hp_set_state(root, 0, DDI_HP_CN_STATE_POWERED, &results);
576 
577 	/* Display results */
578 	print_error(rv);
579 	if (results != NULL) {
580 		(void) hp_traverse(results, NULL, error_cb);
581 		hp_fini(results);
582 	}
583 
584 	/* Discard hotplug information snapshot */
585 	hp_fini(root);
586 
587 	return (rv);
588 }
589 
590 /*
591  * cmd_poweroff()
592  *
593  *	Subcommand to power off a hotplug connector.
594  */
595 static int
596 cmd_poweroff(int argc, char *argv[], const char *usage_str)
597 {
598 	hp_node_t	root;
599 	hp_node_t	results = NULL;
600 	char		*path = NULL;
601 	char		*connection = NULL;
602 	int		flags = 0;
603 	int		rv;
604 
605 	/* Parse command line options */
606 	parse_common(argc, argv, usage_str);
607 	parse_flags(argc, argv, &flags, usage_str);
608 	parse_target(argc, argv, &path, &connection, usage_str);
609 
610 	/* Path and connection are required */
611 	if ((path == NULL) || (connection == NULL)) {
612 		(void) fprintf(stderr, gettext("ERROR: too few arguments.\n"));
613 		usage(usage_str);
614 		return (EINVAL);
615 	}
616 
617 	/* Get hotplug information snapshot */
618 	if ((root = hp_init(path, connection, 0)) == NULL) {
619 		print_error(errno);
620 		return (errno);
621 	}
622 
623 	/* Verify target is a connector */
624 	if (hp_type(root) != HP_NODE_CONNECTOR) {
625 		(void) fprintf(stderr,
626 		    gettext("ERROR: invalid target (must be a connector).\n"));
627 		hp_fini(root);
628 		return (EINVAL);
629 	}
630 
631 	/* Do state change */
632 	rv = hp_set_state(root, flags, DDI_HP_CN_STATE_PRESENT, &results);
633 
634 	/* Display results */
635 	print_error(rv);
636 	if (results != NULL) {
637 		(void) hp_traverse(results, NULL, error_cb);
638 		hp_fini(results);
639 	}
640 
641 	/* Discard hotplug information snapshot */
642 	hp_fini(root);
643 
644 	return (rv);
645 }
646 
647 /*
648  * cmd_getpriv()
649  *
650  *	Subcommand to get and display bus private options.
651  */
652 static int
653 cmd_getpriv(int argc, char *argv[], const char *usage_str)
654 {
655 	hp_node_t	root;
656 	char		*path = NULL;
657 	char		*connection = NULL;
658 	char		*options = NULL;
659 	char		*results = NULL;
660 	int		rv;
661 
662 	/* Parse command line options */
663 	parse_common(argc, argv, usage_str);
664 	parse_options(argc, argv, &options, usage_str);
665 	parse_target(argc, argv, &path, &connection, usage_str);
666 
667 	/* Options, path, and connection are all required */
668 	if ((options == NULL) || (path == NULL) || (connection == NULL)) {
669 		(void) fprintf(stderr, gettext("ERROR: too few arguments.\n"));
670 		usage(usage_str);
671 		return (EINVAL);
672 	}
673 
674 	/* Get hotplug information snapshot */
675 	if ((root = hp_init(path, connection, 0)) == NULL) {
676 		print_error(errno);
677 		return (errno);
678 	}
679 
680 	/* Verify target is a connector */
681 	if (hp_type(root) != HP_NODE_CONNECTOR) {
682 		(void) fprintf(stderr,
683 		    gettext("ERROR: invalid target (must be a connector).\n"));
684 		hp_fini(root);
685 		return (EINVAL);
686 	}
687 
688 	/* Do the operation */
689 	rv = hp_get_private(root, options, &results);
690 
691 	/* Display results */
692 	if (rv == ENOTSUP) {
693 		(void) fprintf(stderr,
694 		    gettext("ERROR: unsupported property name or value.\n"));
695 		(void) fprintf(stderr,
696 		    gettext("(Properties may depend upon connector state.)\n"));
697 	} else if (rv != 0) {
698 		print_error(rv);
699 	}
700 	if (results != NULL) {
701 		print_options(results);
702 		free(results);
703 	}
704 
705 	/* Discard hotplug information snapshot */
706 	hp_fini(root);
707 
708 	return (rv);
709 }
710 
711 /*
712  * cmd_setpriv()
713  *
714  *	Subcommand to set bus private options.
715  */
716 static int
717 cmd_setpriv(int argc, char *argv[], const char *usage_str)
718 {
719 	hp_node_t	root;
720 	char		*path = NULL;
721 	char		*connection = NULL;
722 	char		*options = NULL;
723 	char		*results = NULL;
724 	int		rv;
725 
726 	/* Parse command line options */
727 	parse_common(argc, argv, usage_str);
728 	parse_options(argc, argv, &options, usage_str);
729 	parse_target(argc, argv, &path, &connection, usage_str);
730 
731 	/* Options, path, and connection are all required */
732 	if ((options == NULL) || (path == NULL) || (connection == NULL)) {
733 		(void) fprintf(stderr, gettext("ERROR: too few arguments.\n"));
734 		usage(usage_str);
735 		return (EINVAL);
736 	}
737 
738 	/* Get hotplug information snapshot */
739 	if ((root = hp_init(path, connection, 0)) == NULL) {
740 		print_error(errno);
741 		return (errno);
742 	}
743 
744 	/* Verify target is a connector */
745 	if (hp_type(root) != HP_NODE_CONNECTOR) {
746 		(void) fprintf(stderr,
747 		    gettext("ERROR: invalid target (must be a connector).\n"));
748 		hp_fini(root);
749 		return (EINVAL);
750 	}
751 
752 	/* Do the operation */
753 	rv = hp_set_private(root, options, &results);
754 
755 	/* Display results */
756 	if (rv == ENOTSUP) {
757 		(void) fprintf(stderr,
758 		    gettext("ERROR: unsupported property name or value.\n"));
759 		(void) fprintf(stderr,
760 		    gettext("(Properties may depend upon connector state.)\n"));
761 	} else if (rv != 0) {
762 		print_error(rv);
763 	}
764 	if (results != NULL) {
765 		print_options(results);
766 		free(results);
767 	}
768 
769 	/* Discard hotplug information snapshot */
770 	hp_fini(root);
771 
772 	return (rv);
773 }
774 
775 /*
776  * cmd_changestate()
777  *
778  *	Subcommand to initiate a state change operation.  This is
779  *	a hidden subcommand to directly set a connector or port to
780  *	a specific target state.
781  */
782 static int
783 cmd_changestate(int argc, char *argv[], const char *usage_str)
784 {
785 	hp_node_t	root;
786 	hp_node_t	results = NULL;
787 	char		*path = NULL;
788 	char		*connection = NULL;
789 	int		state = -1;
790 	int		flags = 0;
791 	int		opt, rv;
792 
793 	/* Parse command line options */
794 	parse_common(argc, argv, usage_str);
795 	while ((opt = getopt_clip(argc, argv, "fqs:", changestate_opts,
796 	    NULL)) != -1) {
797 		switch (opt) {
798 		case 'f':
799 			flags |= HPFORCE;
800 			break;
801 		case 'q':
802 			flags |= HPQUERY;
803 			break;
804 		case 's':
805 			if ((state = state_atoi(optarg)) == -1) {
806 				(void) printf("ERROR: invalid target state\n");
807 				return (EINVAL);
808 			}
809 			break;
810 		default:
811 			bad_option(opt, optopt, usage_str);
812 			break;
813 		}
814 	}
815 	parse_target(argc, argv, &path, &connection, usage_str);
816 
817 	/* State, path, and connection are all required */
818 	if ((state == -1) || (path == NULL) || (connection == NULL)) {
819 		(void) fprintf(stderr, gettext("ERROR: too few arguments.\n"));
820 		usage(usage_str);
821 		return (EINVAL);
822 	}
823 
824 	/* Check that target state is valid */
825 	if (valid_target(state) == 0) {
826 		(void) fprintf(stderr,
827 		    gettext("ERROR: invalid target state\n"));
828 		return (EINVAL);
829 	}
830 
831 	/* Get hotplug information snapshot */
832 	if ((root = hp_init(path, connection, 0)) == NULL) {
833 		print_error(errno);
834 		return (errno);
835 	}
836 
837 	/* Initiate state change operation on root of snapshot */
838 	rv = hp_set_state(root, flags, state, &results);
839 
840 	/* Display results */
841 	print_error(rv);
842 	if (results) {
843 		(void) hp_traverse(results, NULL, error_cb);
844 		hp_fini(results);
845 	}
846 
847 	/* Discard hotplug information snapshot */
848 	hp_fini(root);
849 
850 	return (rv);
851 }
852 
853 /*
854  * parse_common()
855  *
856  *	Parse command line options that are common to the
857  *	entire program, and to each of its subcommands.
858  */
859 static void
860 parse_common(int argc, char *argv[], const char *usage_str)
861 {
862 	int		opt;
863 	extern int	opterr;
864 	extern int	optind;
865 
866 	/* Turn off error reporting */
867 	opterr = 0;
868 
869 	while ((opt = getopt_clip(argc, argv, "?V", common_opts, NULL)) != -1) {
870 		switch (opt) {
871 		case '?':
872 			if (optopt == '?') {
873 				usage(usage_str);
874 				exit(0);
875 			}
876 			break;
877 		case 'V':
878 			(void) printf(gettext("%s: Version %s\n"),
879 			    prog, version);
880 			exit(0);
881 		default:
882 			break;
883 		}
884 	}
885 
886 	/* Reset option index */
887 	optind = 1;
888 }
889 
890 /*
891  * parse_flags()
892  *
893  *	Parse command line flags common to all downward state
894  *	change operations (offline, disable, poweoff).
895  */
896 static void
897 parse_flags(int argc, char *argv[], int *flagsp, const char *usage_str)
898 {
899 	int	opt;
900 	int	flags = 0;
901 
902 	while ((opt = getopt_clip(argc, argv, "fq", flag_opts, NULL)) != -1) {
903 		switch (opt) {
904 		case 'f':
905 			flags |= HPFORCE;
906 			break;
907 		case 'q':
908 			flags |= HPQUERY;
909 			break;
910 		default:
911 			bad_option(opt, optopt, usage_str);
912 			break;
913 		}
914 	}
915 
916 	*flagsp = flags;
917 }
918 
919 /*
920  * parse_options()
921  *
922  *	Parse command line options common to the bus private set and
923  *	get subcommands.
924  */
925 static void
926 parse_options(int argc, char *argv[], char **optionsp, const char *usage_str)
927 {
928 	int	opt;
929 
930 	while ((opt = getopt_clip(argc, argv, "o:", private_opts,
931 	    NULL)) != -1) {
932 		switch (opt) {
933 		case 'o':
934 			*optionsp = optarg;
935 			break;
936 		default:
937 			bad_option(opt, optopt, usage_str);
938 			break;
939 		}
940 	}
941 }
942 
943 /*
944  * parse_target()
945  *
946  *	Parse the target path and connection name from the command line.
947  */
948 static void
949 parse_target(int argc, char *argv[], char **pathp, char **connectionp,
950     const char *usage_str)
951 {
952 	extern int	optind;
953 
954 	if (optind < argc)
955 		*pathp = argv[optind++];
956 
957 	if (optind < argc)
958 		*connectionp = argv[optind++];
959 
960 	if (optind < argc) {
961 		(void) fprintf(stderr, gettext("ERROR: too many arguments.\n"));
962 		usage(usage_str);
963 		exit(EINVAL);
964 	}
965 }
966 
967 /*
968  * bad_option()
969  *
970  *	Routine to handle bad command line options.
971  */
972 static void
973 bad_option(int opt, int optopt, const char *usage_str)
974 {
975 	switch (opt) {
976 	case ':':
977 		(void) fprintf(stderr,
978 		    gettext("ERROR: option '%c' requires an argument.\n"),
979 		    optopt);
980 		break;
981 	default:
982 		if (optopt == '?') {
983 			usage(usage_str);
984 			exit(EXIT_OK);
985 		}
986 		(void) fprintf(stderr,
987 		    gettext("ERROR: unrecognized option '%c'.\n"), optopt);
988 		break;
989 	}
990 
991 	usage(usage_str);
992 
993 	exit(EXIT_EINVAL);
994 }
995 
996 /*
997  * usage()
998  *
999  *	Display general usage of the command.  Including
1000  *	the usage synopsis of each defined subcommand.
1001  */
1002 static void
1003 usage(const char *usage_str)
1004 {
1005 	int	i;
1006 
1007 	if (usage_str != NULL) {
1008 		(void) fprintf(stderr, gettext("Usage:   %s  %s\n\n"),
1009 		    prog, usage_str);
1010 		return;
1011 	}
1012 
1013 	(void) fprintf(stderr, gettext("Usage:  %s  <subcommand> [<args>]\n\n"),
1014 	    prog);
1015 
1016 	(void) fprintf(stderr, gettext("Subcommands:\n\n"));
1017 
1018 	for (i = 0; i < (sizeof (subcmds) / sizeof (subcmd_t)); i++)
1019 		(void) fprintf(stderr, "   %s\n\n", subcmds[i].usage_str);
1020 }
1021 
1022 /*
1023  * list_cb()
1024  *
1025  *	Callback function for hp_traverse(), to display nodes
1026  *	of a hotplug information snapshot.  (Short version.)
1027  */
1028 /*ARGSUSED*/
1029 static int
1030 list_cb(hp_node_t node, void *arg)
1031 {
1032 	hp_node_t	parent;
1033 
1034 	/* Indent */
1035 	for (parent = hp_parent(node); parent; parent = hp_parent(parent))
1036 		if (hp_type(parent) == HP_NODE_DEVICE)
1037 			(void) printf("     ");
1038 
1039 	switch (hp_type(node)) {
1040 	case HP_NODE_DEVICE:
1041 		(void) printf("%s\n", hp_name(node));
1042 		break;
1043 
1044 	case HP_NODE_CONNECTOR:
1045 		(void) printf("[%s]", hp_name(node));
1046 		(void) printf("  (%s)", state_itoa(hp_state(node)));
1047 		(void) printf("\n");
1048 		break;
1049 
1050 	case HP_NODE_PORT:
1051 		(void) printf("<%s>", hp_name(node));
1052 		(void) printf("  (%s)", state_itoa(hp_state(node)));
1053 		(void) printf("\n");
1054 		break;
1055 
1056 	case HP_NODE_USAGE:
1057 		(void) printf("{ %s }\n", hp_usage(node));
1058 		break;
1059 	}
1060 
1061 	return (HP_WALK_CONTINUE);
1062 }
1063 
1064 /*
1065  * list_long_cb()
1066  *
1067  *	Callback function for hp_traverse(), to display nodes
1068  *	of a hotplug information snapshot.  (Long version.)
1069  */
1070 /*ARGSUSED*/
1071 static int
1072 list_long_cb(hp_node_t node, void *arg)
1073 {
1074 	char	path[MAXPATHLEN];
1075 	char	connection[MAXPATHLEN];
1076 
1077 	if (hp_type(node) != HP_NODE_USAGE) {
1078 		if (hp_path(node, path, connection) != 0)
1079 			return (HP_WALK_CONTINUE);
1080 		(void) printf("%s", path);
1081 	}
1082 
1083 	switch (hp_type(node)) {
1084 	case HP_NODE_CONNECTOR:
1085 		(void) printf(" [%s]", connection);
1086 		(void) printf(" (%s)", state_itoa(hp_state(node)));
1087 		break;
1088 
1089 	case HP_NODE_PORT:
1090 		(void) printf(" <%s>", connection);
1091 		(void) printf(" (%s)", state_itoa(hp_state(node)));
1092 		break;
1093 
1094 	case HP_NODE_USAGE:
1095 		(void) printf("    { %s }", hp_usage(node));
1096 		break;
1097 	}
1098 
1099 	(void) printf("\n");
1100 
1101 	return (HP_WALK_CONTINUE);
1102 }
1103 
1104 /*
1105  * error_cb()
1106  *
1107  *	Callback function for hp_traverse(), to display
1108  *	error results from a state change operation.
1109  */
1110 /*ARGSUSED*/
1111 static int
1112 error_cb(hp_node_t node, void *arg)
1113 {
1114 	hp_node_t	child;
1115 	char		*usage_str;
1116 	static char	path[MAXPATHLEN];
1117 	static char	connection[MAXPATHLEN];
1118 
1119 	if (((child = hp_child(node)) != NULL) &&
1120 	    (hp_type(child) == HP_NODE_USAGE)) {
1121 		if (hp_path(node, path, connection) == 0)
1122 			(void) printf("%s:\n", path);
1123 		return (HP_WALK_CONTINUE);
1124 	}
1125 
1126 	if ((hp_type(node) == HP_NODE_USAGE) &&
1127 	    ((usage_str = hp_usage(node)) != NULL))
1128 		(void) printf("   { %s }\n", usage_str);
1129 
1130 	return (HP_WALK_CONTINUE);
1131 }
1132 
1133 /*
1134  * print_options()
1135  *
1136  *	Parse and display bus private options.  The options are
1137  *	formatted as a string which conforms to the getsubopt(3C)
1138  *	format.  This routine only splits the string elements as
1139  *	separated by commas, and displays each portion on its own
1140  *	separate line of output.
1141  */
1142 static void
1143 print_options(const char *options)
1144 {
1145 	char	*buf, *curr, *next;
1146 	size_t	len;
1147 
1148 	/* Do nothing if options string is empty */
1149 	if ((len = strlen(options)) == 0)
1150 		return;
1151 
1152 	/* To avoid modifying the input string, make a copy on the stack */
1153 	if ((buf = (char *)alloca(len + 1)) == NULL) {
1154 		(void) printf("%s\n", options);
1155 		return;
1156 	}
1157 	(void) strlcpy(buf, options, len + 1);
1158 
1159 	/* Iterate through each comma-separated name/value pair */
1160 	curr = buf;
1161 	do {
1162 		if ((next = strchr(curr, ',')) != NULL) {
1163 			*next = '\0';
1164 			next++;
1165 		}
1166 		(void) printf("%s\n", curr);
1167 	} while ((curr = next) != NULL);
1168 }
1169 
1170 /*
1171  * print_error()
1172  *
1173  *	Common routine to print error numbers in an appropriate way.
1174  *	Prints nothing if error code is 0.
1175  */
1176 static void
1177 print_error(int error)
1178 {
1179 	switch (error) {
1180 	case 0:
1181 		/* No error */
1182 		return;
1183 	case EACCES:
1184 		(void) fprintf(stderr,
1185 		    gettext("ERROR: operation not authorized.\n"));
1186 		break;
1187 	case EBADF:
1188 		(void) fprintf(stderr,
1189 		    gettext("ERROR: hotplug service is not available.\n"));
1190 		break;
1191 	case EBUSY:
1192 		(void) fprintf(stderr,
1193 		    gettext("ERROR: devices or resources are busy.\n"));
1194 		break;
1195 	case EEXIST:
1196 		(void) fprintf(stderr,
1197 		    gettext("ERROR: resource already exists.\n"));
1198 		break;
1199 	case EFAULT:
1200 		(void) fprintf(stderr,
1201 		    gettext("ERROR: internal failure in hotplug service.\n"));
1202 		break;
1203 	case EINVAL:
1204 		(void) fprintf(stderr,
1205 		    gettext("ERROR: invalid arguments.\n"));
1206 		break;
1207 	case ENOENT:
1208 		(void) fprintf(stderr,
1209 		    gettext("ERROR: there are no connections to display.\n"));
1210 		(void) fprintf(stderr,
1211 		    gettext("(See hotplug(8) for more information.)\n"));
1212 		break;
1213 	case ENXIO:
1214 		(void) fprintf(stderr,
1215 		    gettext("ERROR: no such path or connection.\n"));
1216 		break;
1217 	case ENOMEM:
1218 		(void) fprintf(stderr,
1219 		    gettext("ERROR: not enough memory.\n"));
1220 		break;
1221 	case ENOTSUP:
1222 		(void) fprintf(stderr,
1223 		    gettext("ERROR: operation not supported.\n"));
1224 		break;
1225 	case EIO:
1226 		(void) fprintf(stderr,
1227 		    gettext("ERROR: hardware or driver specific failure.\n"));
1228 		break;
1229 	default:
1230 		(void) fprintf(stderr, gettext("ERROR: operation failed: %s\n"),
1231 		    strerror(error));
1232 		break;
1233 	}
1234 }
1235 
1236 /*
1237  * state_atoi()
1238  *
1239  *	Convert a hotplug state from a string to an integer.
1240  */
1241 static int
1242 state_atoi(char *state)
1243 {
1244 	int	i;
1245 
1246 	for (i = 0; hpstates[i].state_str != NULL; i++)
1247 		if (strcasecmp(state, hpstates[i].state_str) == 0)
1248 			return (hpstates[i].state);
1249 
1250 	return (-1);
1251 }
1252 
1253 /*
1254  * state_itoa()
1255  *
1256  *	Convert a hotplug state from an integer to a string.
1257  */
1258 static char *
1259 state_itoa(int state)
1260 {
1261 	static char	unknown[] = "UNKNOWN";
1262 	int		i;
1263 
1264 	for (i = 0; hpstates[i].state_str != NULL; i++)
1265 		if (state == hpstates[i].state)
1266 			return (hpstates[i].state_str);
1267 
1268 	return (unknown);
1269 }
1270 
1271 /*
1272  * valid_target()
1273  *
1274  *	Check if a state is a valid target for a changestate command.
1275  */
1276 static short
1277 valid_target(int state)
1278 {
1279 	int	i;
1280 
1281 	for (i = 0; hpstates[i].state_str != NULL; i++)
1282 		if (state == hpstates[i].state)
1283 			return (hpstates[i].valid_target);
1284 
1285 	return (0);
1286 }
1287