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 /*
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 #include "cfga_scsi.h"
30 #include <libgen.h>
31 #include <limits.h>
32 
33 /*
34  * This file contains helper routines for the SCSI plugin
35  */
36 
37 #if !defined(TEXT_DOMAIN)
38 #define	TEXT_DOMAIN	"SYS_TEST"
39 #endif
40 
41 typedef struct strlist {
42 	const char *str;
43 	struct strlist *next;
44 } strlist_t;
45 
46 typedef	struct {
47 	scfga_ret_t scsi_err;
48 	cfga_err_t  cfga_err;
49 } errcvt_t;
50 
51 typedef struct {
52 	scfga_cmd_t cmd;
53 	int type;
54 	int (*fcn)(const devctl_hdl_t);
55 } set_state_cmd_t;
56 
57 typedef struct {
58 	scfga_cmd_t cmd;
59 	int type;
60 	int (*state_fcn)(const devctl_hdl_t, uint_t *);
61 } get_state_cmd_t;
62 
63 /* Function prototypes */
64 static char *pathdup(const char *path, int *l_errnop);
65 static void msg_common(char **err_msgpp, int append_newline, int l_errno,
66     va_list ap);
67 
68 /*
69  * The string table contains most of the strings used by the scsi cfgadm plugin.
70  * All strings which are to be internationalized must be in this table.
71  * Some strings which are not internationalized are also included here.
72  * Arguments to messages are NOT internationalized.
73  */
74 msgcvt_t str_tbl[] = {
75 
76 /*
77  * The first element (ERR_UNKNOWN) MUST always be present in the array.
78  */
79 #define	UNKNOWN_ERR_IDX		0	/* Keep the index in sync */
80 
81 
82 /* msg_code	num_args, I18N	msg_string				*/
83 
84 /* ERRORS */
85 {ERR_UNKNOWN,		0, 1,	"unknown error"},
86 {ERR_OP_FAILED,		0, 1,	"operation failed"},
87 {ERR_CMD_INVAL,		0, 1,	"invalid command"},
88 {ERR_NOT_BUSAPID,	0, 1,	"not a SCSI bus apid"},
89 {ERR_APID_INVAL,	0, 1,	"invalid SCSI ap_id"},
90 {ERR_NOT_BUSOP,		0, 1,	"operation not supported for SCSI bus"},
91 {ERR_NOT_DEVOP,		0, 1,	"operation not supported for SCSI device"},
92 {ERR_UNAVAILABLE,	0, 1,	"unavailable"},
93 {ERR_CTRLR_CRIT,	0, 1,	"critical partition controlled by SCSI HBA"},
94 {ERR_BUS_GETSTATE,	0, 1,	"failed to get state for SCSI bus"},
95 {ERR_BUS_NOTCONNECTED,	0, 1,	"SCSI bus not connected"},
96 {ERR_BUS_CONNECTED,	0, 1,	"SCSI bus not disconnected"},
97 {ERR_BUS_QUIESCE,	0, 1,	"SCSI bus quiesce failed"},
98 {ERR_BUS_UNQUIESCE,	0, 1,	"SCSI bus unquiesce failed"},
99 {ERR_BUS_CONFIGURE,	0, 1,	"failed to configure devices on SCSI bus"},
100 {ERR_BUS_UNCONFIGURE,	0, 1,	"failed to unconfigure SCSI bus"},
101 {ERR_DEV_CONFIGURE,	0, 1,	"failed to configure SCSI device"},
102 {ERR_DEV_RECONFIGURE,	1, 1,	"failed to reconfigure device: "},
103 {ERR_DEV_UNCONFIGURE,	0, 1,	"failed to unconfigure SCSI device"},
104 {ERR_DEV_REMOVE,	0, 1,	"remove operation failed"},
105 {ERR_DEV_REPLACE,	0, 1,	"replace operation failed"},
106 {ERR_DEV_INSERT,	0, 1,	"insert operation failed"},
107 {ERR_DEV_GETSTATE,	0, 1,	"failed to get state for SCSI device"},
108 {ERR_RESET,		0, 1,	"reset failed"},
109 {ERR_LIST,		0, 1,	"list operation failed"},
110 {ERR_MAYBE_BUSY,	0, 1,	"device may be busy"},
111 {ERR_BUS_DEV_MISMATCH,	0, 1,	"mismatched SCSI bus and device"},
112 {ERR_VAR_RUN,		0, 1,	"/var/run is not mounted"},
113 {ERR_FORK,		0, 1,	"failed to fork cleanup handler"},
114 
115 /* Errors with arguments */
116 {ERRARG_OPT_INVAL,	1, 1,	"invalid option: "},
117 {ERRARG_HWCMD_INVAL,	1, 1,	"invalid command: "},
118 {ERRARG_DEVINFO,	1, 1,	"libdevinfo failed on path: "},
119 {ERRARG_OPEN,		1, 1,	"open failed: "},
120 {ERRARG_LOCK,		1, 1,	"lock failed: "},
121 {ERRARG_QUIESCE_LOCK,	1, 1,	"cannot acquire quiesce lock: "},
122 
123 /* RCM Errors */
124 {ERR_RCM_HANDLE,	0, 1,	"cannot get RCM handle"},
125 {ERRARG_RCM_SUSPEND,	0, 1,	"failed to suspend: "},
126 {ERRARG_RCM_RESUME,	0, 1,	"failed to resume: "},
127 {ERRARG_RCM_OFFLINE,	0, 1,	"failed to offline: "},
128 {ERRARG_RCM_ONLINE,	0, 1,	"failed to online: "},
129 {ERRARG_RCM_REMOVE,	0, 1,	"failed to remove: "},
130 
131 /* Commands */
132 {CMD_INSERT_DEV,	0, 0,	"insert_device"},
133 {CMD_REMOVE_DEV,	0, 0,	"remove_device"},
134 {CMD_LED_DEV,		0, 0,	"led"},
135 {CMD_LOCATOR_DEV,	0, 0,	"locator"},
136 {CMD_REPLACE_DEV,	0, 0,	"replace_device"},
137 {CMD_RESET_DEV,		0, 0,	"reset_device"},
138 {CMD_RESET_BUS,		0, 0,	"reset_bus"},
139 {CMD_RESET_ALL,		0, 0,	"reset_all"},
140 
141 /* help messages */
142 {MSG_HELP_HDR,		0, 1,	"\nSCSI specific commands and options:\n"},
143 {MSG_HELP_USAGE,	0, 0,	"\t-x insert_device ap_id [ap_id... ]\n"
144 				"\t-x remove_device ap_id [ap_id... ]\n"
145 				"\t-x replace_device ap_id [ap_id... ]\n"
146 				"\t-x locator[=on|off] ap_id [ap_id... ]\n"
147 				"\t-x led[=LED,mode=on|off|blink] "
148 				    "ap_id [ap_id... ]\n"
149 				"\t-x reset_device ap_id [ap_id... ]\n"
150 				"\t-x reset_bus ap_id [ap_id... ]\n"
151 				"\t-x reset_all ap_id [ap_id... ]\n"},
152 
153 /* hotplug messages */
154 {MSG_INSDEV,		1, 1,	"Adding device to SCSI HBA: "},
155 {MSG_RMDEV,		1, 1,	"Removing SCSI device: "},
156 {MSG_REPLDEV,		1, 1,	"Replacing SCSI device: "},
157 {MSG_WAIT_LOCK,		0, 1,	"Waiting for quiesce lock... "},
158 
159 /* Hotplugging confirmation prompts */
160 {CONF_QUIESCE_1,	1, 1,
161 	"This operation will suspend activity on SCSI bus: "},
162 
163 {CONF_QUIESCE_2,	0, 1,	"\nContinue"},
164 
165 {CONF_UNQUIESCE,	0, 1,
166 	"SCSI bus quiesced successfully.\n"
167 	"It is now safe to proceed with hotplug operation."
168 	"\nEnter y if operation is complete or n to abort"},
169 
170 {CONF_NO_QUIESCE,	0, 1,
171 	"Proceed with hotplug operation."
172 	"\nEnter y if operation is complete or n to abort"},
173 
174 /* Misc. */
175 {WARN_DISCONNECT,	0, 1,
176 	"WARNING: Disconnecting critical partitions may cause system hang."
177 	"\nContinue"},
178 
179 /* LED messages */
180 {MSG_LED_HDR,		0, 1,	"Disk                    Led"},
181 {MSG_MISSING_LED_NAME,	0, 1,	"Missing LED name"},
182 {MSG_MISSING_LED_MODE,	0, 1,	"Missing LED mode"}
183 };
184 
185 char *
186 led_strs[] = {
187 	"fault",
188 	"power",
189 	"attn",
190 	"active",
191 	"locator",
192 	NULL
193 };
194 
195 char *
196 led_mode_strs[] = {
197 	"off",
198 	"on",
199 	"blink",
200 	"faulted",
201 	"unknown",
202 	NULL
203 };
204 
205 
206 
207 
208 #define	N_STRS	(sizeof (str_tbl) / sizeof (str_tbl[0]))
209 
210 #define	GET_MSG_NARGS(i)	(str_tbl[msg_idx(i)].nargs)
211 #define	GET_MSG_INTL(i)		(str_tbl[msg_idx(i)].intl)
212 
213 static errcvt_t err_cvt_tbl[] = {
214 	{ SCFGA_OK,		CFGA_OK			},
215 	{ SCFGA_LIB_ERR,	CFGA_LIB_ERROR		},
216 	{ SCFGA_APID_NOEXIST,	CFGA_APID_NOEXIST	},
217 	{ SCFGA_NACK,		CFGA_NACK		},
218 	{ SCFGA_BUSY,		CFGA_BUSY		},
219 	{ SCFGA_SYSTEM_BUSY,	CFGA_SYSTEM_BUSY	},
220 	{ SCFGA_OPNOTSUPP,	CFGA_OPNOTSUPP		},
221 	{ SCFGA_PRIV,		CFGA_PRIV		},
222 	{ SCFGA_UNKNOWN_ERR,	CFGA_ERROR		},
223 	{ SCFGA_ERR,		CFGA_ERROR		}
224 };
225 
226 #define	N_ERR_CVT_TBL	(sizeof (err_cvt_tbl)/sizeof (err_cvt_tbl[0]))
227 
228 #define	DEV_OP	0
229 #define	BUS_OP	1
230 static set_state_cmd_t set_state_cmds[] = {
231 
232 { SCFGA_BUS_QUIESCE,		BUS_OP,		devctl_bus_quiesce	},
233 { SCFGA_BUS_UNQUIESCE,		BUS_OP,		devctl_bus_unquiesce	},
234 { SCFGA_BUS_CONFIGURE,		BUS_OP,		devctl_bus_configure	},
235 { SCFGA_BUS_UNCONFIGURE, 	BUS_OP,		devctl_bus_unconfigure	},
236 { SCFGA_RESET_BUS,		BUS_OP,		devctl_bus_reset	},
237 { SCFGA_RESET_ALL, 		BUS_OP,		devctl_bus_resetall	},
238 { SCFGA_DEV_CONFIGURE,		DEV_OP,		devctl_device_online	},
239 { SCFGA_DEV_UNCONFIGURE,	DEV_OP,		devctl_device_offline	},
240 { SCFGA_DEV_REMOVE,		DEV_OP,		devctl_device_remove	},
241 { SCFGA_RESET_DEV,		DEV_OP,		devctl_device_reset	}
242 
243 };
244 
245 #define	N_SET_STATE_CMDS (sizeof (set_state_cmds)/sizeof (set_state_cmds[0]))
246 
247 static get_state_cmd_t get_state_cmds[] = {
248 { SCFGA_BUS_GETSTATE,		BUS_OP,		devctl_bus_getstate	},
249 { SCFGA_DEV_GETSTATE,		DEV_OP,		devctl_device_getstate	}
250 };
251 
252 #define	N_GET_STATE_CMDS (sizeof (get_state_cmds)/sizeof (get_state_cmds[0]))
253 
254 /*
255  * SCSI hardware specific commands
256  */
257 static hw_cmd_t hw_cmds[] = {
258 	/* Command string	Command ID		Function	*/
259 
260 	{ CMD_INSERT_DEV,	SCFGA_INSERT_DEV,	dev_insert	},
261 	{ CMD_REMOVE_DEV,	SCFGA_REMOVE_DEV,	dev_remove	},
262 	{ CMD_REPLACE_DEV,	SCFGA_REPLACE_DEV,	dev_replace	},
263 	{ CMD_LED_DEV,		SCFGA_LED_DEV,		dev_led		},
264 	{ CMD_LOCATOR_DEV,	SCFGA_LOCATOR_DEV,	dev_led		},
265 	{ CMD_RESET_DEV,	SCFGA_RESET_DEV,	reset_common	},
266 	{ CMD_RESET_BUS,	SCFGA_RESET_BUS,	reset_common	},
267 	{ CMD_RESET_ALL,	SCFGA_RESET_ALL,	reset_common	},
268 };
269 #define	N_HW_CMDS (sizeof (hw_cmds) / sizeof (hw_cmds[0]))
270 
271 
272 cfga_err_t
273 err_cvt(scfga_ret_t s_err)
274 {
275 	int i;
276 
277 	for (i = 0; i < N_ERR_CVT_TBL; i++) {
278 		if (err_cvt_tbl[i].scsi_err == s_err) {
279 			return (err_cvt_tbl[i].cfga_err);
280 		}
281 	}
282 
283 	return (CFGA_ERROR);
284 }
285 
286 /*
287  * Removes duplicate slashes from a pathname and any trailing slashes.
288  * Returns "/" if input is "/"
289  */
290 static char *
291 pathdup(const char *path, int *l_errnop)
292 {
293 	int prev_was_slash = 0;
294 	char c, *dp = NULL, *dup = NULL;
295 	const char *sp = NULL;
296 
297 	*l_errnop = 0;
298 
299 	if (path == NULL) {
300 		return (NULL);
301 	}
302 
303 	if ((dup = calloc(1, strlen(path) + 1)) == NULL) {
304 		*l_errnop = errno;
305 		return (NULL);
306 	}
307 
308 	prev_was_slash = 0;
309 	for (sp = path, dp = dup; (c = *sp) != '\0'; sp++) {
310 		if (!prev_was_slash || c != '/') {
311 			*dp++ = c;
312 		}
313 		if (c == '/') {
314 			prev_was_slash = 1;
315 		} else {
316 			prev_was_slash = 0;
317 		}
318 	}
319 
320 	/* Remove trailing slash except if it is the first char */
321 	if (prev_was_slash && dp != dup && dp - 1 != dup) {
322 		*(--dp) = '\0';
323 	} else {
324 		*dp = '\0';
325 	}
326 
327 	return (dup);
328 }
329 
330 scfga_ret_t
331 apidt_create(const char *ap_id, apid_t *apidp, char **errstring)
332 {
333 	char *hba_phys = NULL, *dyn = NULL;
334 	char *dyncomp = NULL, *path = NULL;
335 	int l_errno = 0;
336 	size_t len = 0;
337 	scfga_ret_t ret;
338 
339 	if ((hba_phys = pathdup(ap_id, &l_errno)) == NULL) {
340 		cfga_err(errstring, l_errno, ERR_OP_FAILED, 0);
341 		return (SCFGA_LIB_ERR);
342 	}
343 
344 	/* Extract the base(hba) and dynamic(device) component if any */
345 	dyncomp = NULL;
346 	if ((dyn = GET_DYN(hba_phys)) != NULL) {
347 		len = strlen(DYN_TO_DYNCOMP(dyn)) + 1;
348 		dyncomp = calloc(1, len);
349 		if (dyncomp == NULL) {
350 			cfga_err(errstring, errno, ERR_OP_FAILED, 0);
351 			ret = SCFGA_LIB_ERR;
352 			goto err;
353 		}
354 		(void) strcpy(dyncomp, DYN_TO_DYNCOMP(dyn));
355 
356 		/* Remove the dynamic component from the base */
357 		*dyn = '\0';
358 	}
359 
360 	/* Create the path */
361 	if ((ret = apid_to_path(hba_phys, dyncomp, &path, &l_errno))
362 	    != SCFGA_OK) {
363 		cfga_err(errstring, l_errno, ERR_OP_FAILED, 0);
364 		goto err;
365 	}
366 
367 	assert(path != NULL);
368 	assert(hba_phys != NULL);
369 
370 	apidp->hba_phys = hba_phys;
371 	apidp->dyncomp = dyncomp;
372 	apidp->path = path;
373 	apidp->flags = 0;
374 
375 	return (SCFGA_OK);
376 
377 err:
378 	S_FREE(hba_phys);
379 	S_FREE(dyncomp);
380 	S_FREE(path);
381 	return (ret);
382 }
383 
384 void
385 apidt_free(apid_t *apidp)
386 {
387 	if (apidp == NULL)
388 		return;
389 
390 	S_FREE(apidp->hba_phys);
391 	S_FREE(apidp->dyncomp);
392 	S_FREE(apidp->path);
393 }
394 
395 scfga_ret_t
396 walk_tree(
397 	const char	*physpath,
398 	void		*arg,
399 	uint_t		init_flags,
400 	walkarg_t	*up,
401 	scfga_cmd_t	cmd,
402 	int		*l_errnop)
403 {
404 	int rv;
405 	di_node_t root, walk_root;
406 	char *root_path, *cp = NULL, *init_path;
407 	size_t len;
408 	scfga_ret_t ret;
409 
410 	*l_errnop = 0;
411 
412 	if ((root_path = strdup(physpath)) == NULL) {
413 		*l_errnop = errno;
414 		return (SCFGA_LIB_ERR);
415 	}
416 
417 	/* Fix up path for di_init() */
418 	len = strlen(DEVICES_DIR);
419 	if (strncmp(root_path, DEVICES_DIR SLASH,
420 	    len + strlen(SLASH)) == 0) {
421 		cp = root_path + len;
422 		(void) memmove(root_path, cp, strlen(cp) + 1);
423 	} else if (*root_path != '/') {
424 		*l_errnop = 0;
425 		ret = SCFGA_ERR;
426 		goto out;
427 	}
428 
429 	/* Remove dynamic component if any */
430 	if ((cp = GET_DYN(root_path)) != NULL) {
431 		*cp = '\0';
432 	}
433 
434 	/* Remove minor name if any */
435 	if ((cp = strrchr(root_path, ':')) != NULL) {
436 		*cp = '\0';
437 	}
438 
439 	/*
440 	 * Cached snapshots are always rooted at "/"
441 	 */
442 	init_path = root_path;
443 	if ((init_flags & DINFOCACHE) == DINFOCACHE) {
444 		init_path = "/";
445 	}
446 
447 	/* Get a snapshot */
448 	if ((root = di_init(init_path, init_flags)) == DI_NODE_NIL) {
449 		*l_errnop = errno;
450 		ret = SCFGA_LIB_ERR;
451 		goto out;
452 	}
453 
454 	/*
455 	 * Lookup the subtree of interest
456 	 */
457 	walk_root = root;
458 	if ((init_flags & DINFOCACHE) == DINFOCACHE) {
459 		walk_root = di_lookup_node(root, root_path);
460 	}
461 
462 	if (walk_root == DI_NODE_NIL) {
463 		*l_errnop = errno;
464 		di_fini(root);
465 		ret = SCFGA_LIB_ERR;
466 		goto out;
467 	}
468 
469 	/* Walk the tree */
470 	errno = 0;
471 	if (cmd == SCFGA_WALK_NODE) {
472 		rv = di_walk_node(walk_root, up->node_args.flags, arg,
473 		    up->node_args.fcn);
474 	} else {
475 		assert(cmd == SCFGA_WALK_MINOR);
476 		rv = di_walk_minor(walk_root, up->minor_args.nodetype, 0, arg,
477 		    up->minor_args.fcn);
478 	}
479 
480 	if (rv != 0) {
481 		*l_errnop = errno;
482 		ret = SCFGA_LIB_ERR;
483 	} else {
484 		*l_errnop = 0;
485 		ret = SCFGA_OK;
486 	}
487 
488 	di_fini(root);
489 
490 	/*FALLTHRU*/
491 out:
492 	S_FREE(root_path);
493 	return (ret);
494 }
495 
496 scfga_ret_t
497 invoke_cmd(
498 	const char *func,
499 	apid_t *apidtp,
500 	prompt_t *prp,
501 	cfga_flags_t flags,
502 	char **errstring)
503 {
504 	int i;
505 	int len;
506 
507 
508 	/*
509 	 * Determine if the func has an equal sign; only compare up to
510 	 * the equals
511 	 */
512 	for (len = 0; func[len] != 0 && func[len] != '='; len++);
513 
514 	for (i = 0; i < N_HW_CMDS; i++) {
515 		const char *s = GET_MSG_STR(hw_cmds[i].str_id);
516 		if (strncmp(func, s, len) == 0 && s[len] == 0) {
517 			return (hw_cmds[i].fcn(func, hw_cmds[i].cmd, apidtp,
518 			    prp, flags, errstring));
519 		}
520 	}
521 
522 	cfga_err(errstring, 0, ERRARG_HWCMD_INVAL, func, 0);
523 	return (SCFGA_ERR);
524 }
525 
526 int
527 msg_idx(msgid_t msgid)
528 {
529 	int idx = 0;
530 
531 	/* The string table index and the error id may or may not be same */
532 	if (msgid >= 0 && msgid <= N_STRS - 1 &&
533 	    str_tbl[msgid].msgid == msgid) {
534 		idx = msgid;
535 	} else {
536 		for (idx = 0; idx < N_STRS; idx++) {
537 			if (str_tbl[idx].msgid == msgid)
538 				break;
539 		}
540 		if (idx >= N_STRS) {
541 			idx =  UNKNOWN_ERR_IDX;
542 		}
543 	}
544 
545 	return (idx);
546 }
547 
548 /*
549  * cfga_err() accepts a variable number of message IDs and constructs
550  * a corresponding error string which is returned via the errstring argument.
551  * cfga_err() calls dgettext() to internationalize proper messages.
552  * May be called with a NULL argument.
553  */
554 void
555 cfga_err(char **errstring, int l_errno, ...)
556 {
557 	va_list ap;
558 	int append_newline = 0;
559 
560 	if (errstring == NULL || *errstring != NULL) {
561 		return;
562 	}
563 
564 	/*
565 	 * Don't append a newline, the application (for example cfgadm)
566 	 * should do that.
567 	 */
568 	append_newline = 0;
569 
570 	va_start(ap, l_errno);
571 	msg_common(errstring, append_newline, l_errno, ap);
572 	va_end(ap);
573 }
574 
575 /*
576  * This routine accepts a variable number of message IDs and constructs
577  * a corresponding message string which is printed via the message print
578  * routine argument.
579  */
580 void
581 cfga_msg(struct cfga_msg *msgp, ...)
582 {
583 	char *p = NULL;
584 	int append_newline = 0, l_errno = 0;
585 	va_list ap;
586 
587 	if (msgp == NULL || msgp->message_routine == NULL) {
588 		return;
589 	}
590 
591 	/* Append a newline after message */
592 	append_newline = 1;
593 	l_errno = 0;
594 
595 	va_start(ap, msgp);
596 	msg_common(&p, append_newline, l_errno, ap);
597 	va_end(ap);
598 
599 	(void) (*msgp->message_routine)(msgp->appdata_ptr, p);
600 
601 	S_FREE(p);
602 }
603 
604 
605 /*
606  * This routine prints the value of an led for a disk.
607  */
608 void
609 cfga_led_msg(struct cfga_msg *msgp, apid_t *apidp, led_strid_t led,
610     led_modeid_t mode)
611 {
612 	char led_msg[MAX_INPUT];	/* 512 bytes */
613 
614 	if ((msgp == NULL) || (msgp->message_routine == NULL)) {
615 		return;
616 	}
617 	if ((apidp == NULL) || (apidp->dyncomp == NULL)) {
618 		return;
619 	}
620 	snprintf(led_msg, sizeof (led_msg), "%-23s\t%s=%s\n",
621 			basename(apidp->dyncomp),
622 			dgettext(TEXT_DOMAIN, led_strs[led]),
623 			dgettext(TEXT_DOMAIN, led_mode_strs[mode]));
624 	(void) (*msgp->message_routine)(msgp->appdata_ptr, led_msg);
625 }
626 
627 /*
628  * Get internationalized string corresponding to message id
629  * Caller must free the memory allocated.
630  */
631 char *
632 cfga_str(int append_newline, ...)
633 {
634 	char *p = NULL;
635 	int l_errno = 0;
636 	va_list ap;
637 
638 	va_start(ap, append_newline);
639 	msg_common(&p, append_newline, l_errno, ap);
640 	va_end(ap);
641 
642 	return (p);
643 }
644 
645 static void
646 msg_common(char **msgpp, int append_newline, int l_errno, va_list ap)
647 {
648 	int a = 0;
649 	size_t len = 0;
650 	int i = 0, n = 0;
651 	char *s = NULL, *t = NULL;
652 	strlist_t dummy;
653 	strlist_t *savep = NULL, *sp = NULL, *tailp = NULL;
654 
655 	if (*msgpp != NULL) {
656 		return;
657 	}
658 
659 	dummy.next = NULL;
660 	tailp = &dummy;
661 	for (len = 0; (a = va_arg(ap, int)) != 0; ) {
662 		n = GET_MSG_NARGS(a); /* 0 implies no additional args */
663 		for (i = 0; i <= n; i++) {
664 			sp = calloc(1, sizeof (*sp));
665 			if (sp == NULL) {
666 				goto out;
667 			}
668 			if (i == 0 && GET_MSG_INTL(a)) {
669 				sp->str = dgettext(TEXT_DOMAIN, GET_MSG_STR(a));
670 			} else if (i == 0) {
671 				sp->str = GET_MSG_STR(a);
672 			} else {
673 				sp->str = va_arg(ap, char *);
674 			}
675 			len += (strlen(sp->str));
676 			sp->next = NULL;
677 			tailp->next = sp;
678 			tailp = sp;
679 		}
680 	}
681 
682 	len += 1;	/* terminating NULL */
683 
684 	s = t = NULL;
685 	if (l_errno) {
686 		s = dgettext(TEXT_DOMAIN, ": ");
687 		t = S_STR(strerror(l_errno));
688 		if (s != NULL && t != NULL) {
689 			len += strlen(s) + strlen(t);
690 		}
691 	}
692 
693 	if (append_newline) {
694 		len++;
695 	}
696 
697 	if ((*msgpp = calloc(1, len)) == NULL) {
698 		goto out;
699 	}
700 
701 	**msgpp = '\0';
702 	for (sp = dummy.next; sp != NULL; sp = sp->next) {
703 		(void) strcat(*msgpp, sp->str);
704 	}
705 
706 	if (s != NULL && t != NULL) {
707 		(void) strcat(*msgpp, s);
708 		(void) strcat(*msgpp, t);
709 	}
710 
711 	if (append_newline) {
712 		(void) strcat(*msgpp, dgettext(TEXT_DOMAIN, "\n"));
713 	}
714 
715 	/* FALLTHROUGH */
716 out:
717 	sp = dummy.next;
718 	while (sp != NULL) {
719 		savep = sp->next;
720 		S_FREE(sp);
721 		sp = savep;
722 	}
723 }
724 
725 scfga_ret_t
726 devctl_cmd(
727 	const char	*physpath,
728 	scfga_cmd_t	cmd,
729 	uint_t		*statep,
730 	int		*l_errnop)
731 {
732 	int rv = -1, i, type;
733 	devctl_hdl_t hdl = NULL;
734 	char *cp = NULL, *path = NULL;
735 	int (*func)(const devctl_hdl_t);
736 	int (*state_func)(const devctl_hdl_t, uint_t *);
737 
738 	*l_errnop = 0;
739 
740 	if (statep != NULL) *statep = 0;
741 
742 	func = NULL;
743 	state_func = NULL;
744 	type = 0;
745 
746 	for (i = 0; i < N_GET_STATE_CMDS; i++) {
747 		if (get_state_cmds[i].cmd == cmd) {
748 			state_func = get_state_cmds[i].state_fcn;
749 			type = get_state_cmds[i].type;
750 			assert(statep != NULL);
751 			break;
752 		}
753 	}
754 
755 	if (state_func == NULL) {
756 		for (i = 0; i < N_SET_STATE_CMDS; i++) {
757 			if (set_state_cmds[i].cmd == cmd) {
758 				func = set_state_cmds[i].fcn;
759 				type = set_state_cmds[i].type;
760 				assert(statep == NULL);
761 				break;
762 			}
763 		}
764 	}
765 
766 	assert(type == BUS_OP || type == DEV_OP);
767 
768 	if (func == NULL && state_func == NULL) {
769 		return (SCFGA_ERR);
770 	}
771 
772 	/*
773 	 * Fix up path for calling devctl.
774 	 */
775 	if ((path = strdup(physpath)) == NULL) {
776 		*l_errnop = errno;
777 		return (SCFGA_LIB_ERR);
778 	}
779 
780 	/* Remove dynamic component if any */
781 	if ((cp = GET_DYN(path)) != NULL) {
782 		*cp = '\0';
783 	}
784 
785 	/* Remove minor name */
786 	if ((cp = strrchr(path, ':')) != NULL) {
787 		*cp = '\0';
788 	}
789 
790 	errno = 0;
791 
792 	if (type == BUS_OP) {
793 		hdl = devctl_bus_acquire(path, 0);
794 	} else {
795 		hdl = devctl_device_acquire(path, 0);
796 	}
797 	*l_errnop = errno;
798 
799 	S_FREE(path);
800 
801 	if (hdl == NULL) {
802 		return (SCFGA_ERR);
803 	}
804 
805 	errno = 0;
806 	/* Only getstate functions require a second argument */
807 	if (func != NULL && statep == NULL) {
808 		rv = func(hdl);
809 		*l_errnop = errno;
810 	} else if (state_func != NULL && statep != NULL) {
811 		rv = state_func(hdl, statep);
812 		*l_errnop = errno;
813 	} else {
814 		rv = -1;
815 		*l_errnop = 0;
816 	}
817 
818 	devctl_release(hdl);
819 
820 	return ((rv == -1) ? SCFGA_ERR : SCFGA_OK);
821 }
822 
823 /*
824  * Is device in a known state ? (One of BUSY, ONLINE, OFFLINE)
825  *	BUSY --> One or more device special files are open. Implies online
826  *	ONLINE --> driver attached
827  *	OFFLINE --> CF1 with offline flag set.
828  *	UNKNOWN --> None of the above
829  */
830 int
831 known_state(di_node_t node)
832 {
833 	uint_t state;
834 
835 	state = di_state(node);
836 
837 	/*
838 	 * CF1 without offline flag set is considered unknown state.
839 	 * We are in a known state if either CF2 (driver attached) or
840 	 * offline.
841 	 */
842 	if ((state & DI_DEVICE_OFFLINE) == DI_DEVICE_OFFLINE ||
843 		(state & DI_DRIVER_DETACHED) != DI_DRIVER_DETACHED) {
844 		return (1);
845 	}
846 
847 	return (0);
848 }
849 
850 void
851 list_free(ldata_list_t **llpp)
852 {
853 	ldata_list_t *lp, *olp;
854 
855 	lp = *llpp;
856 	while (lp != NULL) {
857 		olp = lp;
858 		lp = olp->next;
859 		S_FREE(olp);
860 	}
861 
862 	*llpp = NULL;
863 }
864 
865 /*
866  * Obtain the devlink from a /devices path
867  */
868 typedef struct walk_link {
869 	char *path;
870 	char len;
871 	char **linkpp;
872 } walk_link_t;
873 
874 static int
875 get_link(di_devlink_t devlink, void *arg)
876 {
877 	walk_link_t *larg = (walk_link_t *)arg;
878 
879 	/*
880 	 * When path is specified, it's the node path without minor
881 	 * name. Therefore, the ../.. prefixes needs to be stripped.
882 	 */
883 	if (larg->path) {
884 		char *content = (char *)di_devlink_content(devlink);
885 		char *start = strstr(content, "/devices/");
886 
887 		/* line content must have minor node */
888 		if (start == NULL ||
889 		    strncmp(start, larg->path, larg->len) != 0 ||
890 		    start[larg->len] != ':')
891 			return (DI_WALK_CONTINUE);
892 	}
893 
894 	*(larg->linkpp) = strdup(di_devlink_path(devlink));
895 	return (DI_WALK_TERMINATE);
896 }
897 
898 scfga_ret_t
899 physpath_to_devlink(
900 	char *node_path,
901 	char **logpp,
902 	int *l_errnop,
903 	int match_minor)
904 {
905 	walk_link_t larg;
906 	di_devlink_handle_t hdl;
907 	char *minor_path;
908 
909 	if ((hdl = di_devlink_init(NULL, 0)) == NULL) {
910 		*l_errnop = errno;
911 		return (SCFGA_LIB_ERR);
912 	}
913 
914 	*logpp = NULL;
915 	larg.linkpp = logpp;
916 	if (match_minor) {
917 		minor_path = node_path + strlen(DEVICES_DIR);
918 		larg.path = NULL;
919 	} else {
920 		minor_path = NULL;
921 		larg.len = strlen(node_path);
922 		larg.path = node_path;
923 	}
924 
925 	(void) di_devlink_walk(hdl, NULL, minor_path, DI_PRIMARY_LINK,
926 	    (void *)&larg, get_link);
927 
928 	(void) di_devlink_fini(&hdl);
929 
930 	if (*logpp == NULL)
931 		return (SCFGA_LIB_ERR);
932 
933 	return (SCFGA_OK);
934 }
935 
936 int
937 hba_dev_cmp(const char *hba, const char *devpath)
938 {
939 	char *cp = NULL;
940 	int rv;
941 	size_t hba_len, dev_len;
942 	char l_hba[MAXPATHLEN], l_dev[MAXPATHLEN];
943 
944 	(void) snprintf(l_hba, sizeof (l_hba), "%s", hba);
945 	(void) snprintf(l_dev, sizeof (l_dev), "%s", devpath);
946 
947 	/* Remove dynamic component if any */
948 	if ((cp = GET_DYN(l_hba)) != NULL) {
949 		*cp = '\0';
950 	}
951 
952 	if ((cp = GET_DYN(l_dev)) != NULL) {
953 		*cp = '\0';
954 	}
955 
956 
957 	/* Remove minor names */
958 	if ((cp = strrchr(l_hba, ':')) != NULL) {
959 		*cp = '\0';
960 	}
961 
962 	if ((cp = strrchr(l_dev, ':')) != NULL) {
963 		*cp = '\0';
964 	}
965 
966 	hba_len = strlen(l_hba);
967 	dev_len = strlen(l_dev);
968 
969 	/* Check if HBA path is component of device path */
970 	if (rv = strncmp(l_hba, l_dev, hba_len)) {
971 		return (rv);
972 	}
973 
974 	/* devpath must have '/' and 1 char in addition to hba path */
975 	if (dev_len >= hba_len + 2 && l_dev[hba_len] == '/') {
976 		return (0);
977 	} else {
978 		return (-1);
979 	}
980 }
981 
982 int
983 dev_cmp(const char *dev1, const char *dev2, int match_minor)
984 {
985 	char l_dev1[MAXPATHLEN], l_dev2[MAXPATHLEN];
986 	char *mn1, *mn2;
987 	int rv;
988 
989 	(void) snprintf(l_dev1, sizeof (l_dev1), "%s", dev1);
990 	(void) snprintf(l_dev2, sizeof (l_dev2), "%s", dev2);
991 
992 	if ((mn1 = GET_DYN(l_dev1)) != NULL) {
993 		*mn1 = '\0';
994 	}
995 
996 	if ((mn2 = GET_DYN(l_dev2)) != NULL) {
997 		*mn2 = '\0';
998 	}
999 
1000 	/* Separate out the minor names */
1001 	if ((mn1 = strrchr(l_dev1, ':')) != NULL) {
1002 		*mn1++ = '\0';
1003 	}
1004 
1005 	if ((mn2 = strrchr(l_dev2, ':')) != NULL) {
1006 		*mn2++ = '\0';
1007 	}
1008 
1009 	if ((rv = strcmp(l_dev1, l_dev2)) != 0 || !match_minor) {
1010 		return (rv);
1011 	}
1012 
1013 	/*
1014 	 * Compare minor names
1015 	 */
1016 	if (mn1 == NULL && mn2 == NULL) {
1017 		return (0);
1018 	} else if (mn1 == NULL) {
1019 		return (-1);
1020 	} else if (mn2 == NULL) {
1021 		return (1);
1022 	} else {
1023 		return (strcmp(mn1, mn2));
1024 	}
1025 }
1026