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  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
22  * Use is subject to license terms.
23  */
24 
25 #include <alloca.h>
26 #include <arpa/inet.h>
27 #include <assert.h>
28 #include <errno.h>
29 #include <ipmp_admin.h>
30 #include <ipmp_query.h>
31 #include <libintl.h>
32 #include <libnvpair.h>
33 #include <libsysevent.h>
34 #include <locale.h>
35 #include <netdb.h>
36 #include <signal.h>
37 #include <stdarg.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42 #include <sys/sysevent/eventdefs.h>
43 #include <sys/sysevent/ipmp.h>
44 #include <sys/sysmacros.h>
45 #include <sys/termios.h>
46 #include <sys/types.h>
47 
48 /*
49  * ipmpstat -- display IPMP subsystem status.
50  *
51  * This utility makes extensive use of libipmp and IPMP sysevents to gather
52  * and pretty-print the status of the IPMP subsystem.  All output formats
53  * except for -p (probe) use libipmp to create a point-in-time snapshot of the
54  * IPMP subsystem (unless the test-special -L flag is used), and then output
55  * the contents of that snapshot in a user-specified manner.  Because the
56  * output format and requested fields aren't known until run-time, three sets
57  * of function pointers and two core data structures are used. Specifically:
58  *
59  *      * The ipmpstat_walker_t function pointers (walk_*) iterate through
60  *	  all instances of a given IPMP object (group, interface, or address).
61  *	  At most one ipmpstat_walker_t is used per ipmpstat invocation.
62  *	  Since target information is included with the interface information,
63  *	  both -i and -t use the interface walker (walk_if()).
64  *
65  *      * The ipmpstat_sfunc_t function pointers (sfunc_*) obtain a given
66  *	  value for a given IPMP object.  Each ipmpstat_sunc_t is passed a
67  *	  buffer to write its result into, the buffer's size, and an
68  *	  ipmpstat_sfunc_arg_t state structure.  The state structure consists
69  *	  of a pointer to the IPMP object to obtain information from
70  *	  (sa_data), and an open libipmp handle (sa_ih) which can be used to
71  *	  do additional libipmp queries, if necessary (e.g., because the
72  *	  object does not have all of the needed information).
73  *
74  *	* The ipmpstat_field_t structure provides the list of supported fields
75  *	  for a given output format, along with output formatting information
76  *	  (e.g., field width), and a pointer to an ipmpstat_sfunc_t function
77  *	  that can obtain the value for a IPMP given object.  For a given
78  *	  ipmpstat output format, there's a corresponding array of
79  *	  ipmpstat_field_t structures.  Thus, one ipmpstat_field_t array is
80  *	  used per ipmpstat invocation.
81  *
82  *	* The ipmpstat_ofmt_t provides an ordered list of the requested
83  *	  ipmpstat_field_t's (e.g., via -o) for a given ipmpstat invocation.
84  *	  It is built at runtime from the command-line arguments.  This
85  *	  structure (and a given IPMP object) is used by ofmt_output() to
86  *	  output a single line of information about that IPMP object.
87  *
88  *	* The ipmpstat_cbfunc_t function pointers (*_cbfunc) are called back
89  *	  by the walkers.  They are used both internally to implement nested
90  *	  walks, and by the ipmpstat output logic to provide the glue between
91  *	  the IPMP object walkers and the ofmt_output() logic.  Usually, a
92  *	  single line is output for each IPMP object, and thus ofmt_output()
93  *	  can be directly invoked (see info_output_cbfunc()).  However, if
94  *	  multiple lines need to be output, then a more complex cbfunc is
95  *	  needed (see targinfo_output_cbfunc()).  At most one cbfunc is used
96  *	  per ipmpstat invocation.
97  */
98 
99 /*
100  * Data type used by the sfunc callbacks to obtain the requested information
101  * from the agreed-upon object.
102  */
103 typedef struct ipmpstat_sfunc_arg {
104 	ipmp_handle_t		sa_ih;
105 	void			*sa_data;
106 } ipmpstat_sfunc_arg_t;
107 
108 typedef void ipmpstat_sfunc_t(ipmpstat_sfunc_arg_t *, char *, uint_t);
109 
110 /*
111  * Data type that describes how to output a field; used by ofmt_output*().
112  */
113 typedef struct ipmpstat_field {
114 	const char		*f_name;	/* field name */
115 	uint_t			f_width;	/* output width */
116 	ipmpstat_sfunc_t	*f_sfunc;	/* value->string function */
117 } ipmpstat_field_t;
118 
119 /*
120  * Data type that specifies the output field order; used by ofmt_output*()
121  */
122 typedef struct ipmpstat_ofmt {
123 	const ipmpstat_field_t	*o_field;	/* current field info */
124 	struct ipmpstat_ofmt	*o_next;	/* next field */
125 } ipmpstat_ofmt_t;
126 
127 /*
128  * Function pointers used to iterate through IPMP objects.
129  */
130 typedef void ipmpstat_cbfunc_t(ipmp_handle_t, void *, void *);
131 typedef void ipmpstat_walker_t(ipmp_handle_t, ipmpstat_cbfunc_t *, void *);
132 
133 /*
134  * Data type used to implement nested walks.
135  */
136 typedef struct ipmpstat_walkdata {
137 	ipmpstat_cbfunc_t	*iw_func; 	/* caller-specified callback */
138 	void			*iw_funcarg; 	/* caller-specified arg */
139 } ipmpstat_walkdata_t;
140 
141 /*
142  * Data type used by enum2str() to map an enumerated value to a string.
143  */
144 typedef struct ipmpstat_enum {
145 	const char		*e_name;	/* string */
146 	int			e_val;		/* value */
147 } ipmpstat_enum_t;
148 
149 /*
150  * Data type used to pass state between probe_output() and probe_event().
151  */
152 typedef struct ipmpstat_probe_state {
153 	ipmp_handle_t	ps_ih;			/* open IPMP handle */
154 	ipmpstat_ofmt_t	*ps_ofmt; 		/* requested ofmt string */
155 } ipmpstat_probe_state_t;
156 
157 /*
158  * Options that modify the output mode; more than one may be lit.
159  */
160 typedef enum {
161 	IPMPSTAT_OPT_NUMERIC	= 0x1,
162 	IPMPSTAT_OPT_PARSABLE 	= 0x2
163 } ipmpstat_opt_t;
164 
165 /*
166  * Indices for the FLAGS field of the `-i' output format.
167  */
168 enum {
169 	IPMPSTAT_IFLAG_INDEX,	IPMPSTAT_SFLAG_INDEX,	IPMPSTAT_M4FLAG_INDEX,
170 	IPMPSTAT_BFLAG_INDEX,	IPMPSTAT_M6FLAG_INDEX,	IPMPSTAT_DFLAG_INDEX,
171 	IPMPSTAT_HFLAG_INDEX,	IPMPSTAT_NUM_FLAGS
172 };
173 
174 #define	IPMPSTAT_NCOL	80
175 #define	NS2FLOATMS(ns)	((float)(ns) / (NANOSEC / MILLISEC))
176 #define	MS2FLOATSEC(ms)	((float)(ms) / 1000)
177 
178 static const char	*progname;
179 static hrtime_t		probe_output_start;
180 static struct winsize	winsize;
181 static ipmpstat_opt_t	opt;
182 static ipmpstat_enum_t	addr_state[], group_state[], if_state[], if_link[];
183 static ipmpstat_enum_t	if_probe[], targ_mode[];
184 static ipmpstat_field_t addr_fields[], group_fields[], if_fields[];
185 static ipmpstat_field_t probe_fields[], targ_fields[];
186 static ipmpstat_cbfunc_t walk_addr_cbfunc, walk_if_cbfunc;
187 static ipmpstat_cbfunc_t info_output_cbfunc, targinfo_output_cbfunc;
188 static ipmpstat_walker_t walk_addr, walk_if, walk_group;
189 
190 static int probe_event(sysevent_t *, void *);
191 static void probe_output(ipmp_handle_t, ipmpstat_ofmt_t *);
192 static ipmpstat_field_t *field_find(ipmpstat_field_t *, const char *);
193 static ipmpstat_ofmt_t *ofmt_create(const char *, ipmpstat_field_t []);
194 static void ofmt_output(const ipmpstat_ofmt_t *, ipmp_handle_t, void *);
195 static void ofmt_destroy(ipmpstat_ofmt_t *);
196 static void enum2str(const ipmpstat_enum_t *, int, char *, uint_t);
197 static void sockaddr2str(const struct sockaddr_storage *, char *, uint_t);
198 static void sighandler(int);
199 static void usage(void);
200 static void die(const char *, ...);
201 static void die_ipmperr(int, const char *, ...);
202 static void warn(const char *, ...);
203 static void warn_ipmperr(int, const char *, ...);
204 
205 int
206 main(int argc, char **argv)
207 {
208 	int c;
209 	int err;
210 	const char *ofields = NULL;
211 	ipmp_handle_t ih;
212 	ipmp_qcontext_t qcontext = IPMP_QCONTEXT_SNAP;
213 	ipmpstat_ofmt_t *ofmt;
214 	ipmpstat_field_t *fields = NULL;
215 	ipmpstat_cbfunc_t *cbfunc;
216 	ipmpstat_walker_t *walker;
217 
218 	if ((progname = strrchr(argv[0], '/')) == NULL)
219 		progname = argv[0];
220 	else
221 		progname++;
222 
223 	(void) setlocale(LC_ALL, "");
224 	(void) textdomain(TEXT_DOMAIN);
225 
226 	while ((c = getopt(argc, argv, "nLPo:agipt")) != EOF) {
227 		if (fields != NULL && strchr("agipt", c) != NULL)
228 			die("only one output format may be specified\n");
229 
230 		switch (c) {
231 		case 'n':
232 			opt |= IPMPSTAT_OPT_NUMERIC;
233 			break;
234 		case 'L':
235 			/* Undocumented option: for testing use ONLY */
236 			qcontext = IPMP_QCONTEXT_LIVE;
237 			break;
238 		case 'P':
239 			opt |= IPMPSTAT_OPT_PARSABLE;
240 			break;
241 		case 'o':
242 			ofields = optarg;
243 			break;
244 		case 'a':
245 			walker = walk_addr;
246 			cbfunc = info_output_cbfunc;
247 			fields = addr_fields;
248 			break;
249 		case 'g':
250 			walker = walk_group;
251 			cbfunc = info_output_cbfunc;
252 			fields = group_fields;
253 			break;
254 		case 'i':
255 			walker = walk_if;
256 			cbfunc = info_output_cbfunc;
257 			fields = if_fields;
258 			break;
259 		case 'p':
260 			fields = probe_fields;
261 			break;
262 		case 't':
263 			walker = walk_if;
264 			cbfunc = targinfo_output_cbfunc;
265 			fields = targ_fields;
266 			break;
267 		default:
268 			usage();
269 			break;
270 		}
271 	}
272 
273 	if (argc > optind || fields == NULL)
274 		usage();
275 
276 	if (opt & IPMPSTAT_OPT_PARSABLE) {
277 		if (ofields == NULL) {
278 			die("output field list (-o) required in parsable "
279 			    "output mode\n");
280 		} else if (strcasecmp(ofields, "all") == 0) {
281 			die("\"all\" not allowed in parsable output mode\n");
282 		}
283 	}
284 
285 	/*
286 	 * Obtain the window size and monitor changes to the size.  This data
287 	 * is used to redisplay the output headers when necessary.
288 	 */
289 	(void) sigset(SIGWINCH, sighandler);
290 	sighandler(SIGWINCH);
291 
292 	if ((err = ipmp_open(&ih)) != IPMP_SUCCESS)
293 		die_ipmperr(err, "cannot create IPMP handle");
294 
295 	if (ipmp_ping_daemon(ih) != IPMP_SUCCESS)
296 		die("cannot contact in.mpathd(1M) -- is IPMP in use?\n");
297 
298 	/*
299 	 * Create the ofmt linked list that will eventually be passed to
300 	 * to ofmt_output() to output the fields.
301 	 */
302 	ofmt = ofmt_create(ofields, fields);
303 
304 	/*
305 	 * If we've been asked to display probes, then call the probe output
306 	 * function.  Otherwise, snapshot IPMP state (or use live state) and
307 	 * invoke the specified walker with the specified callback function.
308 	 */
309 	if (fields == probe_fields) {
310 		probe_output(ih, ofmt);
311 	} else {
312 		if ((err = ipmp_setqcontext(ih, qcontext)) != IPMP_SUCCESS) {
313 			if (qcontext == IPMP_QCONTEXT_SNAP)
314 				die_ipmperr(err, "cannot snapshot IPMP state");
315 			else
316 				die_ipmperr(err, "cannot use live IPMP state");
317 		}
318 		(*walker)(ih, cbfunc, ofmt);
319 	}
320 
321 	ofmt_destroy(ofmt);
322 	ipmp_close(ih);
323 
324 	return (EXIT_SUCCESS);
325 }
326 
327 /*
328  * Walks all IPMP groups on the system and invokes `cbfunc' on each, passing
329  * it `ih', the ipmp_groupinfo_t pointer, and `arg'.
330  */
331 static void
332 walk_group(ipmp_handle_t ih, ipmpstat_cbfunc_t *cbfunc, void *arg)
333 {
334 	int err;
335 	uint_t i;
336 	ipmp_groupinfo_t *grinfop;
337 	ipmp_grouplist_t *grlistp;
338 
339 	if ((err = ipmp_getgrouplist(ih, &grlistp)) != IPMP_SUCCESS)
340 		die_ipmperr(err, "cannot get IPMP group list");
341 
342 	for (i = 0; i < grlistp->gl_ngroup; i++) {
343 		err = ipmp_getgroupinfo(ih, grlistp->gl_groups[i], &grinfop);
344 		if (err != IPMP_SUCCESS) {
345 			warn_ipmperr(err, "cannot get info for group `%s'",
346 			    grlistp->gl_groups[i]);
347 			continue;
348 		}
349 		(*cbfunc)(ih, grinfop, arg);
350 		ipmp_freegroupinfo(grinfop);
351 	}
352 
353 	ipmp_freegrouplist(grlistp);
354 }
355 
356 /*
357  * Walks all IPMP interfaces on the system and invokes `cbfunc' on each,
358  * passing it `ih', the ipmp_ifinfo_t pointer, and `arg'.
359  */
360 static void
361 walk_if(ipmp_handle_t ih, ipmpstat_cbfunc_t *cbfunc, void *arg)
362 {
363 	ipmpstat_walkdata_t iw = { cbfunc, arg };
364 
365 	walk_group(ih, walk_if_cbfunc, &iw);
366 }
367 
368 /*
369  * Walks all IPMP data addresses on the system and invokes `cbfunc' on each.
370  * passing it `ih', the ipmp_addrinfo_t pointer, and `arg'.
371  */
372 static void
373 walk_addr(ipmp_handle_t ih, ipmpstat_cbfunc_t *cbfunc, void *arg)
374 {
375 	ipmpstat_walkdata_t iw = { cbfunc, arg };
376 
377 	walk_group(ih, walk_addr_cbfunc, &iw);
378 }
379 
380 /*
381  * Nested walker callback function for walk_if().
382  */
383 static void
384 walk_if_cbfunc(ipmp_handle_t ih, void *infop, void *arg)
385 {
386 	int err;
387 	uint_t i;
388 	ipmp_groupinfo_t *grinfop = infop;
389 	ipmp_ifinfo_t *ifinfop;
390 	ipmp_iflist_t *iflistp = grinfop->gr_iflistp;
391 	ipmpstat_walkdata_t *iwp = arg;
392 
393 	for (i = 0; i < iflistp->il_nif; i++) {
394 		err = ipmp_getifinfo(ih, iflistp->il_ifs[i], &ifinfop);
395 		if (err != IPMP_SUCCESS) {
396 			warn_ipmperr(err, "cannot get info for interface `%s'",
397 			    iflistp->il_ifs[i]);
398 			continue;
399 		}
400 		(*iwp->iw_func)(ih, ifinfop, iwp->iw_funcarg);
401 		ipmp_freeifinfo(ifinfop);
402 	}
403 }
404 
405 /*
406  * Nested walker callback function for walk_addr().
407  */
408 static void
409 walk_addr_cbfunc(ipmp_handle_t ih, void *infop, void *arg)
410 {
411 	int err;
412 	uint_t i;
413 	ipmp_groupinfo_t *grinfop = infop;
414 	ipmp_addrinfo_t *adinfop;
415 	ipmp_addrlist_t *adlistp = grinfop->gr_adlistp;
416 	ipmpstat_walkdata_t *iwp = arg;
417 	char addr[INET6_ADDRSTRLEN];
418 	struct sockaddr_storage *addrp;
419 
420 	for (i = 0; i < adlistp->al_naddr; i++) {
421 		addrp = &adlistp->al_addrs[i];
422 		err = ipmp_getaddrinfo(ih, grinfop->gr_name, addrp, &adinfop);
423 		if (err != IPMP_SUCCESS) {
424 			sockaddr2str(addrp, addr, sizeof (addr));
425 			warn_ipmperr(err, "cannot get info for `%s'", addr);
426 			continue;
427 		}
428 		(*iwp->iw_func)(ih, adinfop, iwp->iw_funcarg);
429 		ipmp_freeaddrinfo(adinfop);
430 	}
431 }
432 
433 static void
434 sfunc_nvwarn(const char *nvname, char *buf, uint_t bufsize)
435 {
436 	warn("cannot retrieve %s\n", nvname);
437 	(void) strlcpy(buf, "?", bufsize);
438 }
439 
440 static void
441 sfunc_addr_address(ipmpstat_sfunc_arg_t *arg, char *buf, uint_t bufsize)
442 {
443 	ipmp_addrinfo_t *adinfop = arg->sa_data;
444 
445 	sockaddr2str(&adinfop->ad_addr, buf, bufsize);
446 }
447 
448 static void
449 sfunc_addr_group(ipmpstat_sfunc_arg_t *arg, char *buf, uint_t bufsize)
450 {
451 	int err;
452 	ipmp_addrinfo_t *adinfop = arg->sa_data;
453 	ipmp_groupinfo_t *grinfop;
454 
455 	err = ipmp_getgroupinfo(arg->sa_ih, adinfop->ad_group, &grinfop);
456 	if (err != IPMP_SUCCESS) {
457 		warn_ipmperr(err, "cannot get info for group `%s'",
458 		    adinfop->ad_group);
459 		(void) strlcpy(buf, "?", bufsize);
460 		return;
461 	}
462 	(void) strlcpy(buf, grinfop->gr_ifname, bufsize);
463 	ipmp_freegroupinfo(grinfop);
464 }
465 
466 static void
467 sfunc_addr_state(ipmpstat_sfunc_arg_t *arg, char *buf, uint_t bufsize)
468 {
469 	ipmp_addrinfo_t *adinfop = arg->sa_data;
470 
471 	enum2str(addr_state, adinfop->ad_state, buf, bufsize);
472 }
473 
474 static void
475 sfunc_addr_inbound(ipmpstat_sfunc_arg_t *arg, char *buf, uint_t bufsize)
476 {
477 	ipmp_addrinfo_t *adinfop = arg->sa_data;
478 
479 	(void) strlcpy(buf, adinfop->ad_binding, bufsize);
480 }
481 
482 static void
483 sfunc_addr_outbound(ipmpstat_sfunc_arg_t *arg, char *buf, uint_t bufsize)
484 {
485 	int err;
486 	uint_t i, nactive = 0;
487 	ipmp_ifinfo_t *ifinfop;
488 	ipmp_iflist_t *iflistp;
489 	ipmp_addrinfo_t *adinfop = arg->sa_data;
490 	ipmp_groupinfo_t *grinfop;
491 
492 	if (adinfop->ad_state == IPMP_ADDR_DOWN)
493 		return;
494 
495 	/*
496 	 * If there's no inbound interface for this address, there can't
497 	 * be any outbound traffic.
498 	 */
499 	if (adinfop->ad_binding[0] == '\0')
500 		return;
501 
502 	/*
503 	 * The address can use any active interface in the group, so
504 	 * obtain all of those.
505 	 */
506 	err = ipmp_getgroupinfo(arg->sa_ih, adinfop->ad_group, &grinfop);
507 	if (err != IPMP_SUCCESS) {
508 		warn_ipmperr(err, "cannot get info for group `%s'",
509 		    adinfop->ad_group);
510 		(void) strlcpy(buf, "?", bufsize);
511 		return;
512 	}
513 
514 	iflistp = grinfop->gr_iflistp;
515 	for (i = 0; i < iflistp->il_nif; i++) {
516 		err = ipmp_getifinfo(arg->sa_ih, iflistp->il_ifs[i], &ifinfop);
517 		if (err != IPMP_SUCCESS) {
518 			warn_ipmperr(err, "cannot get info for interface `%s'",
519 			    iflistp->il_ifs[i]);
520 			continue;
521 		}
522 
523 		if (ifinfop->if_flags & IPMP_IFFLAG_ACTIVE) {
524 			if (nactive++ != 0)
525 				(void) strlcat(buf, " ", bufsize);
526 			(void) strlcat(buf, ifinfop->if_name, bufsize);
527 		}
528 		ipmp_freeifinfo(ifinfop);
529 	}
530 	ipmp_freegroupinfo(grinfop);
531 }
532 
533 static void
534 sfunc_group_name(ipmpstat_sfunc_arg_t *arg, char *buf, uint_t bufsize)
535 {
536 	ipmp_groupinfo_t *grinfop = arg->sa_data;
537 
538 	(void) strlcpy(buf, grinfop->gr_name, bufsize);
539 }
540 
541 static void
542 sfunc_group_ifname(ipmpstat_sfunc_arg_t *arg, char *buf, uint_t bufsize)
543 {
544 	ipmp_groupinfo_t *grinfop = arg->sa_data;
545 
546 	(void) strlcpy(buf, grinfop->gr_ifname, bufsize);
547 }
548 
549 static void
550 sfunc_group_state(ipmpstat_sfunc_arg_t *arg, char *buf, uint_t bufsize)
551 {
552 	ipmp_groupinfo_t *grinfop = arg->sa_data;
553 
554 	enum2str(group_state, grinfop->gr_state, buf, bufsize);
555 }
556 
557 static void
558 sfunc_group_fdt(ipmpstat_sfunc_arg_t *arg, char *buf, uint_t bufsize)
559 {
560 	ipmp_groupinfo_t *grinfop = arg->sa_data;
561 
562 	if (grinfop->gr_fdt == 0)
563 		return;
564 
565 	(void) snprintf(buf, bufsize, "%.2fs", MS2FLOATSEC(grinfop->gr_fdt));
566 }
567 
568 static void
569 sfunc_group_interfaces(ipmpstat_sfunc_arg_t *arg, char *buf, uint_t bufsize)
570 {
571 	int err;
572 	uint_t i;
573 	char *active, *inactive, *unusable;
574 	uint_t nactive = 0, ninactive = 0, nunusable = 0;
575 	ipmp_groupinfo_t *grinfop = arg->sa_data;
576 	ipmp_iflist_t *iflistp = grinfop->gr_iflistp;
577 	ipmp_ifinfo_t *ifinfop;
578 
579 	active = alloca(bufsize);
580 	active[0] = '\0';
581 	inactive = alloca(bufsize);
582 	inactive[0] = '\0';
583 	unusable = alloca(bufsize);
584 	unusable[0] = '\0';
585 
586 	for (i = 0; i < iflistp->il_nif; i++) {
587 		err = ipmp_getifinfo(arg->sa_ih, iflistp->il_ifs[i], &ifinfop);
588 		if (err != IPMP_SUCCESS) {
589 			warn_ipmperr(err, "cannot get info for interface `%s'",
590 			    iflistp->il_ifs[i]);
591 			continue;
592 		}
593 
594 		if (ifinfop->if_flags & IPMP_IFFLAG_ACTIVE) {
595 			if (nactive++ != 0)
596 				(void) strlcat(active, " ", bufsize);
597 			(void) strlcat(active, ifinfop->if_name, bufsize);
598 		} else if (ifinfop->if_flags & IPMP_IFFLAG_INACTIVE) {
599 			if (ninactive++ != 0)
600 				(void) strlcat(inactive, " ", bufsize);
601 			(void) strlcat(inactive, ifinfop->if_name, bufsize);
602 		} else {
603 			if (nunusable++ != 0)
604 				(void) strlcat(unusable, " ", bufsize);
605 			(void) strlcat(unusable, ifinfop->if_name, bufsize);
606 		}
607 
608 		ipmp_freeifinfo(ifinfop);
609 	}
610 
611 	(void) strlcpy(buf, active, bufsize);
612 
613 	if (ninactive > 0) {
614 		if (nactive != 0)
615 			(void) strlcat(buf, " ", bufsize);
616 
617 		(void) strlcat(buf, "(", bufsize);
618 		(void) strlcat(buf, inactive, bufsize);
619 		(void) strlcat(buf, ")", bufsize);
620 	}
621 
622 	if (nunusable > 0) {
623 		if (nactive + ninactive != 0)
624 			(void) strlcat(buf, " ", bufsize);
625 
626 		(void) strlcat(buf, "[", bufsize);
627 		(void) strlcat(buf, unusable, bufsize);
628 		(void) strlcat(buf, "]", bufsize);
629 	}
630 }
631 
632 static void
633 sfunc_if_name(ipmpstat_sfunc_arg_t *arg, char *buf, uint_t bufsize)
634 {
635 	ipmp_ifinfo_t *ifinfop = arg->sa_data;
636 
637 	(void) strlcpy(buf, ifinfop->if_name, bufsize);
638 }
639 
640 static void
641 sfunc_if_active(ipmpstat_sfunc_arg_t *arg, char *buf, uint_t bufsize)
642 {
643 	ipmp_ifinfo_t *ifinfop = arg->sa_data;
644 
645 	if (ifinfop->if_flags & IPMP_IFFLAG_ACTIVE)
646 		(void) strlcpy(buf, "yes", bufsize);
647 	else
648 		(void) strlcpy(buf, "no", bufsize);
649 }
650 
651 static void
652 sfunc_if_group(ipmpstat_sfunc_arg_t *arg, char *buf, uint_t bufsize)
653 {
654 	int err;
655 	ipmp_ifinfo_t *ifinfop = arg->sa_data;
656 	ipmp_groupinfo_t *grinfop;
657 
658 	err = ipmp_getgroupinfo(arg->sa_ih, ifinfop->if_group, &grinfop);
659 	if (err != IPMP_SUCCESS) {
660 		warn_ipmperr(err, "cannot get info for group `%s'",
661 		    ifinfop->if_group);
662 		(void) strlcpy(buf, "?", bufsize);
663 		return;
664 	}
665 
666 	(void) strlcpy(buf, grinfop->gr_ifname, bufsize);
667 	ipmp_freegroupinfo(grinfop);
668 }
669 
670 static void
671 sfunc_if_flags(ipmpstat_sfunc_arg_t *arg, char *buf, uint_t bufsize)
672 {
673 	int err;
674 	ipmp_ifinfo_t *ifinfop = arg->sa_data;
675 	ipmp_groupinfo_t *grinfop;
676 
677 	assert(bufsize > IPMPSTAT_NUM_FLAGS);
678 
679 	(void) memset(buf, '-', IPMPSTAT_NUM_FLAGS);
680 	buf[IPMPSTAT_NUM_FLAGS] = '\0';
681 
682 	if (ifinfop->if_type == IPMP_IF_STANDBY)
683 		buf[IPMPSTAT_SFLAG_INDEX] = 's';
684 
685 	if (ifinfop->if_flags & IPMP_IFFLAG_INACTIVE)
686 		buf[IPMPSTAT_IFLAG_INDEX] = 'i';
687 
688 	if (ifinfop->if_flags & IPMP_IFFLAG_DOWN)
689 		buf[IPMPSTAT_DFLAG_INDEX] = 'd';
690 
691 	if (ifinfop->if_flags & IPMP_IFFLAG_HWADDRDUP)
692 		buf[IPMPSTAT_HFLAG_INDEX] = 'h';
693 
694 	err = ipmp_getgroupinfo(arg->sa_ih, ifinfop->if_group, &grinfop);
695 	if (err != IPMP_SUCCESS) {
696 		warn_ipmperr(err, "cannot get broadcast/multicast info for "
697 		    "group `%s'", ifinfop->if_group);
698 		return;
699 	}
700 
701 	if (strcmp(grinfop->gr_m4ifname, ifinfop->if_name) == 0)
702 		buf[IPMPSTAT_M4FLAG_INDEX] = 'm';
703 
704 	if (strcmp(grinfop->gr_m6ifname, ifinfop->if_name) == 0)
705 		buf[IPMPSTAT_M6FLAG_INDEX] = 'M';
706 
707 	if (strcmp(grinfop->gr_bcifname, ifinfop->if_name) == 0)
708 		buf[IPMPSTAT_BFLAG_INDEX] = 'b';
709 
710 	ipmp_freegroupinfo(grinfop);
711 }
712 
713 static void
714 sfunc_if_link(ipmpstat_sfunc_arg_t *arg, char *buf, uint_t bufsize)
715 {
716 	ipmp_ifinfo_t *ifinfop = arg->sa_data;
717 
718 	enum2str(if_link, ifinfop->if_linkstate, buf, bufsize);
719 }
720 
721 static void
722 sfunc_if_probe(ipmpstat_sfunc_arg_t *arg, char *buf, uint_t bufsize)
723 {
724 	ipmp_ifinfo_t *ifinfop = arg->sa_data;
725 
726 	enum2str(if_probe, ifinfop->if_probestate, buf, bufsize);
727 }
728 
729 static void
730 sfunc_if_state(ipmpstat_sfunc_arg_t *arg, char *buf, uint_t bufsize)
731 {
732 	ipmp_ifinfo_t *ifinfop = arg->sa_data;
733 
734 	enum2str(if_state, ifinfop->if_state, buf, bufsize);
735 }
736 
737 static void
738 sfunc_probe_id(ipmpstat_sfunc_arg_t *arg, char *buf, uint_t bufsize)
739 {
740 	uint32_t probe_id;
741 	nvlist_t *nvl = arg->sa_data;
742 
743 	if (nvlist_lookup_uint32(nvl, IPMP_PROBE_ID, &probe_id) != 0) {
744 		sfunc_nvwarn("IPMP_PROBE_ID", buf, bufsize);
745 		return;
746 	}
747 
748 	(void) snprintf(buf, bufsize, "%u", probe_id);
749 }
750 
751 static void
752 sfunc_probe_ifname(ipmpstat_sfunc_arg_t *arg, char *buf, uint_t bufsize)
753 {
754 	char *ifname;
755 	nvlist_t *nvl = arg->sa_data;
756 
757 	if (nvlist_lookup_string(nvl, IPMP_IF_NAME, &ifname) != 0) {
758 		sfunc_nvwarn("IPMP_IF_NAME", buf, bufsize);
759 		return;
760 	}
761 
762 	(void) strlcpy(buf, ifname, bufsize);
763 }
764 
765 static void
766 sfunc_probe_time(ipmpstat_sfunc_arg_t *arg, char *buf, uint_t bufsize)
767 {
768 	hrtime_t start;
769 	nvlist_t *nvl = arg->sa_data;
770 
771 	if (nvlist_lookup_hrtime(nvl, IPMP_PROBE_START_TIME, &start) != 0) {
772 		sfunc_nvwarn("IPMP_PROBE_START_TIME", buf, bufsize);
773 		return;
774 	}
775 
776 	(void) snprintf(buf, bufsize, "%.2fs",
777 	    (float)(start - probe_output_start) / NANOSEC);
778 }
779 
780 static void
781 sfunc_probe_target(ipmpstat_sfunc_arg_t *arg, char *buf, uint_t bufsize)
782 {
783 	uint_t nelem;
784 	struct sockaddr_storage *target;
785 	nvlist_t *nvl = arg->sa_data;
786 
787 	if (nvlist_lookup_byte_array(nvl, IPMP_PROBE_TARGET,
788 	    (uchar_t **)&target, &nelem) != 0) {
789 		sfunc_nvwarn("IPMP_PROBE_TARGET", buf, bufsize);
790 		return;
791 	}
792 
793 	sockaddr2str(target, buf, bufsize);
794 }
795 
796 static void
797 sfunc_probe_rtt(ipmpstat_sfunc_arg_t *arg, char *buf, uint_t bufsize)
798 {
799 	hrtime_t start, ackproc;
800 	nvlist_t *nvl = arg->sa_data;
801 	uint32_t state;
802 
803 	if (nvlist_lookup_uint32(nvl, IPMP_PROBE_STATE, &state) != 0) {
804 		sfunc_nvwarn("IPMP_PROBE_STATE", buf, bufsize);
805 		return;
806 	}
807 
808 	if (state != IPMP_PROBE_ACKED)
809 		return;
810 
811 	if (nvlist_lookup_hrtime(nvl, IPMP_PROBE_START_TIME, &start) != 0) {
812 		sfunc_nvwarn("IPMP_PROBE_START_TIME", buf, bufsize);
813 		return;
814 	}
815 
816 	if (nvlist_lookup_hrtime(nvl, IPMP_PROBE_ACKPROC_TIME, &ackproc) != 0) {
817 		sfunc_nvwarn("IPMP_PROBE_ACKPROC_TIME", buf, bufsize);
818 		return;
819 	}
820 
821 	(void) snprintf(buf, bufsize, "%.2fms", NS2FLOATMS(ackproc - start));
822 }
823 
824 static void
825 sfunc_probe_netrtt(ipmpstat_sfunc_arg_t *arg, char *buf, uint_t bufsize)
826 {
827 	hrtime_t sent, ackrecv;
828 	nvlist_t *nvl = arg->sa_data;
829 	uint32_t state;
830 
831 	if (nvlist_lookup_uint32(nvl, IPMP_PROBE_STATE, &state) != 0) {
832 		sfunc_nvwarn("IPMP_PROBE_STATE", buf, bufsize);
833 		return;
834 	}
835 
836 	if (state != IPMP_PROBE_ACKED)
837 		return;
838 
839 	if (nvlist_lookup_hrtime(nvl, IPMP_PROBE_SENT_TIME, &sent) != 0) {
840 		sfunc_nvwarn("IPMP_PROBE_SENT_TIME", buf, bufsize);
841 		return;
842 	}
843 
844 	if (nvlist_lookup_hrtime(nvl, IPMP_PROBE_ACKRECV_TIME, &ackrecv) != 0) {
845 		sfunc_nvwarn("IPMP_PROBE_ACKRECV_TIME", buf, bufsize);
846 		return;
847 	}
848 
849 	(void) snprintf(buf, bufsize, "%.2fms", NS2FLOATMS(ackrecv - sent));
850 }
851 
852 static void
853 sfunc_probe_rttavg(ipmpstat_sfunc_arg_t *arg, char *buf, uint_t bufsize)
854 {
855 	int64_t rttavg;
856 	nvlist_t *nvl = arg->sa_data;
857 
858 	if (nvlist_lookup_int64(nvl, IPMP_PROBE_TARGET_RTTAVG, &rttavg) != 0) {
859 		sfunc_nvwarn("IPMP_PROBE_TARGET_RTTAVG", buf, bufsize);
860 		return;
861 	}
862 
863 	if (rttavg != 0)
864 		(void) snprintf(buf, bufsize, "%.2fms", NS2FLOATMS(rttavg));
865 }
866 
867 static void
868 sfunc_probe_rttdev(ipmpstat_sfunc_arg_t *arg, char *buf, uint_t bufsize)
869 {
870 	int64_t rttdev;
871 	nvlist_t *nvl = arg->sa_data;
872 
873 	if (nvlist_lookup_int64(nvl, IPMP_PROBE_TARGET_RTTDEV, &rttdev) != 0) {
874 		sfunc_nvwarn("IPMP_PROBE_TARGET_RTTDEV", buf, bufsize);
875 		return;
876 	}
877 
878 	if (rttdev != 0)
879 		(void) snprintf(buf, bufsize, "%.2fms", NS2FLOATMS(rttdev));
880 }
881 
882 /* ARGSUSED */
883 static void
884 probe_enabled_cbfunc(ipmp_handle_t ih, void *infop, void *arg)
885 {
886 	uint_t *nenabledp = arg;
887 	ipmp_ifinfo_t *ifinfop = infop;
888 
889 	if (ifinfop->if_probestate != IPMP_PROBE_DISABLED)
890 		(*nenabledp)++;
891 }
892 
893 static void
894 probe_output(ipmp_handle_t ih, ipmpstat_ofmt_t *ofmt)
895 {
896 	char sub[MAX_SUBID_LEN];
897 	evchan_t *evch;
898 	ipmpstat_probe_state_t ps = { ih, ofmt };
899 	uint_t nenabled = 0;
900 
901 	/*
902 	 * Check if any interfaces are enabled for probe-based failure
903 	 * detection.  If not, immediately fail.
904 	 */
905 	walk_if(ih, probe_enabled_cbfunc, &nenabled);
906 	if (nenabled == 0)
907 		die("probe-based failure detection is disabled\n");
908 
909 	probe_output_start = gethrtime();
910 
911 	/*
912 	 * Unfortunately, until 4791900 is fixed, only privileged processes
913 	 * can bind and thus receive sysevents.
914 	 */
915 	errno = sysevent_evc_bind(IPMP_EVENT_CHAN, &evch, EVCH_CREAT);
916 	if (errno != 0) {
917 		if (errno == EPERM)
918 			die("insufficient privileges for -p\n");
919 		die("sysevent_evc_bind to channel %s failed", IPMP_EVENT_CHAN);
920 	}
921 
922 	/*
923 	 * The subscriber must be unique in order for sysevent_evc_subscribe()
924 	 * to succeed, so combine our name and pid.
925 	 */
926 	(void) snprintf(sub, sizeof (sub), "%d-%s", getpid(), progname);
927 
928 	errno = sysevent_evc_subscribe(evch, sub, EC_IPMP, probe_event, &ps, 0);
929 	if (errno != 0)
930 		die("sysevent_evc_subscribe for class %s failed", EC_IPMP);
931 
932 	for (;;)
933 		(void) pause();
934 }
935 
936 static int
937 probe_event(sysevent_t *ev, void *arg)
938 {
939 	nvlist_t *nvl;
940 	uint32_t state;
941 	uint32_t version;
942 	ipmpstat_probe_state_t *psp = arg;
943 
944 	if (strcmp(sysevent_get_subclass_name(ev), ESC_IPMP_PROBE_STATE) != 0)
945 		return (0);
946 
947 	if (sysevent_get_attr_list(ev, &nvl) != 0) {
948 		warn("sysevent_get_attr_list failed; dropping event");
949 		return (0);
950 	}
951 
952 	if (nvlist_lookup_uint32(nvl, IPMP_EVENT_VERSION, &version) != 0) {
953 		warn("dropped event with no IPMP_EVENT_VERSION\n");
954 		goto out;
955 	}
956 
957 	if (version != IPMP_EVENT_CUR_VERSION) {
958 		warn("dropped event with unsupported IPMP_EVENT_VERSION %d\n",
959 		    version);
960 		goto out;
961 	}
962 
963 	if (nvlist_lookup_uint32(nvl, IPMP_PROBE_STATE, &state) != 0) {
964 		warn("dropped event with no IPMP_PROBE_STATE\n");
965 		goto out;
966 	}
967 
968 	if (state == IPMP_PROBE_ACKED || state == IPMP_PROBE_LOST)
969 		ofmt_output(psp->ps_ofmt, psp->ps_ih, nvl);
970 out:
971 	nvlist_free(nvl);
972 	return (0);
973 }
974 
975 static void
976 sfunc_targ_ifname(ipmpstat_sfunc_arg_t *arg, char *buf, uint_t bufsize)
977 {
978 	ipmp_targinfo_t *targinfop = arg->sa_data;
979 
980 	(void) strlcpy(buf, targinfop->it_name, bufsize);
981 }
982 
983 static void
984 sfunc_targ_mode(ipmpstat_sfunc_arg_t *arg, char *buf, uint_t bufsize)
985 {
986 	ipmp_targinfo_t *targinfop = arg->sa_data;
987 
988 	enum2str(targ_mode, targinfop->it_targmode, buf, bufsize);
989 }
990 
991 static void
992 sfunc_targ_testaddr(ipmpstat_sfunc_arg_t *arg, char *buf, uint_t bufsize)
993 {
994 	ipmp_targinfo_t *targinfop = arg->sa_data;
995 
996 	if (targinfop->it_targmode != IPMP_TARG_DISABLED)
997 		sockaddr2str(&targinfop->it_testaddr, buf, bufsize);
998 }
999 
1000 static void
1001 sfunc_targ_targets(ipmpstat_sfunc_arg_t *arg, char *buf, uint_t bufsize)
1002 {
1003 	uint_t i;
1004 	char *targname = alloca(bufsize);
1005 	ipmp_targinfo_t *targinfop = arg->sa_data;
1006 	ipmp_addrlist_t *targlistp = targinfop->it_targlistp;
1007 
1008 	for (i = 0; i < targlistp->al_naddr; i++) {
1009 		sockaddr2str(&targlistp->al_addrs[i], targname, bufsize);
1010 		(void) strlcat(buf, targname, bufsize);
1011 		if ((i + 1) < targlistp->al_naddr)
1012 			(void) strlcat(buf, " ", bufsize);
1013 	}
1014 }
1015 
1016 static void
1017 info_output_cbfunc(ipmp_handle_t ih, void *infop, void *arg)
1018 {
1019 	ofmt_output(arg, ih, infop);
1020 }
1021 
1022 static void
1023 targinfo_output_cbfunc(ipmp_handle_t ih, void *infop, void *arg)
1024 {
1025 	ipmp_ifinfo_t *ifinfop = infop;
1026 	ipmp_if_targmode_t targmode4 = ifinfop->if_targinfo4.it_targmode;
1027 	ipmp_if_targmode_t targmode6 = ifinfop->if_targinfo6.it_targmode;
1028 
1029 	/*
1030 	 * Usually, either IPv4 or IPv6 probing will be enabled, but the admin
1031 	 * may enable both.  If only one is enabled, omit the other one so as
1032 	 * to not encourage the admin to enable both.  If neither is enabled,
1033 	 * we still print one just so the admin can see a MODE of "disabled".
1034 	 */
1035 	if (targmode4 != IPMP_TARG_DISABLED || targmode6 == IPMP_TARG_DISABLED)
1036 		ofmt_output(arg, ih, &ifinfop->if_targinfo4);
1037 	if (targmode6 != IPMP_TARG_DISABLED)
1038 		ofmt_output(arg, ih, &ifinfop->if_targinfo6);
1039 }
1040 
1041 /*
1042  * Creates an ipmpstat_ofmt_t field list from the comma-separated list of
1043  * user-specified fields passed via `ofields'.  The table of known fields
1044  * (and their attributes) is passed via `fields'.
1045  */
1046 static ipmpstat_ofmt_t *
1047 ofmt_create(const char *ofields, ipmpstat_field_t fields[])
1048 {
1049 	char *token, *lasts, *ofields_dup;
1050 	const char *fieldname;
1051 	ipmpstat_ofmt_t *ofmt, *ofmt_head = NULL, *ofmt_tail;
1052 	ipmpstat_field_t *fieldp;
1053 	uint_t cols = 0;
1054 
1055 	/*
1056 	 * If "-o" was omitted or "-o all" was specified, build a list of
1057 	 * field names.  If "-o" was omitted, stop building the list when
1058 	 * we run out of columns.
1059 	 */
1060 	if (ofields == NULL || strcasecmp(ofields, "all") == 0) {
1061 		for (fieldp = fields; fieldp->f_name != NULL; fieldp++) {
1062 			cols += fieldp->f_width;
1063 			if (ofields == NULL && cols > IPMPSTAT_NCOL)
1064 				break;
1065 
1066 			if ((ofmt = calloc(sizeof (*ofmt), 1)) == NULL)
1067 				die("cannot allocate output format list");
1068 
1069 			ofmt->o_field = fieldp;
1070 			if (ofmt_head == NULL) {
1071 				ofmt_head = ofmt;
1072 				ofmt_tail = ofmt;
1073 			} else {
1074 				ofmt_tail->o_next = ofmt;
1075 				ofmt_tail = ofmt;
1076 			}
1077 		}
1078 		return (ofmt_head);
1079 	}
1080 
1081 	if ((ofields_dup = strdup(ofields)) == NULL)
1082 		die("cannot allocate output format list");
1083 
1084 	token = ofields_dup;
1085 	while ((fieldname = strtok_r(token, ",", &lasts)) != NULL) {
1086 		token = NULL;
1087 
1088 		if ((fieldp = field_find(fields, fieldname)) == NULL) {
1089 			/*
1090 			 * Since machine parsers are unlikely to be able to
1091 			 * gracefully handle missing fields, die if we're in
1092 			 * parsable mode.  Otherwise, just print a warning.
1093 			 */
1094 			if (opt & IPMPSTAT_OPT_PARSABLE)
1095 				die("unknown output field `%s'\n", fieldname);
1096 
1097 			warn("ignoring unknown output field `%s'\n", fieldname);
1098 			continue;
1099 		}
1100 
1101 		if ((ofmt = calloc(sizeof (*ofmt), 1)) == NULL)
1102 			die("cannot allocate output format list");
1103 
1104 		ofmt->o_field = fieldp;
1105 		if (ofmt_head == NULL) {
1106 			ofmt_head = ofmt;
1107 			ofmt_tail = ofmt;
1108 		} else {
1109 			ofmt_tail->o_next = ofmt;
1110 			ofmt_tail = ofmt;
1111 		}
1112 	}
1113 
1114 	free(ofields_dup);
1115 	if (ofmt_head == NULL)
1116 		die("no valid output fields specified\n");
1117 
1118 	return (ofmt_head);
1119 }
1120 
1121 /*
1122  * Destroys the provided `ofmt' field list.
1123  */
1124 static void
1125 ofmt_destroy(ipmpstat_ofmt_t *ofmt)
1126 {
1127 	ipmpstat_ofmt_t *ofmt_next;
1128 
1129 	for (; ofmt != NULL; ofmt = ofmt_next) {
1130 		ofmt_next = ofmt->o_next;
1131 		free(ofmt);
1132 	}
1133 }
1134 
1135 /*
1136  * Outputs a header for the fields named by `ofmt'.
1137  */
1138 static void
1139 ofmt_output_header(const ipmpstat_ofmt_t *ofmt)
1140 {
1141 	const ipmpstat_field_t *fieldp;
1142 
1143 	for (; ofmt != NULL; ofmt = ofmt->o_next) {
1144 		fieldp = ofmt->o_field;
1145 
1146 		if (ofmt->o_next == NULL)
1147 			(void) printf("%s", fieldp->f_name);
1148 		else
1149 			(void) printf("%-*s", fieldp->f_width, fieldp->f_name);
1150 	}
1151 	(void) printf("\n");
1152 }
1153 
1154 /*
1155  * Outputs one row of values for the fields named by `ofmt'.  The values to
1156  * output are obtained through the `ofmt' function pointers, which are
1157  * indirectly passed the `ih' and `arg' structures for state; see the block
1158  * comment at the start of this file for details.
1159  */
1160 static void
1161 ofmt_output(const ipmpstat_ofmt_t *ofmt, ipmp_handle_t ih, void *arg)
1162 {
1163 	int i;
1164 	char buf[1024];
1165 	boolean_t escsep;
1166 	static int nrow;
1167 	const char *value;
1168 	uint_t width, valwidth;
1169 	uint_t compress, overflow = 0;
1170 	const ipmpstat_field_t *fieldp;
1171 	ipmpstat_sfunc_arg_t sfunc_arg;
1172 
1173 	/*
1174 	 * For each screenful of data, display the header.
1175 	 */
1176 	if ((nrow++ % winsize.ws_row) == 0 && !(opt & IPMPSTAT_OPT_PARSABLE)) {
1177 		ofmt_output_header(ofmt);
1178 		nrow++;
1179 	}
1180 
1181 	/*
1182 	 * Check if we'll be displaying multiple fields per line, and thus
1183 	 * need to escape the field separator.
1184 	 */
1185 	escsep = (ofmt != NULL && ofmt->o_next != NULL);
1186 
1187 	for (; ofmt != NULL; ofmt = ofmt->o_next) {
1188 		fieldp = ofmt->o_field;
1189 
1190 		sfunc_arg.sa_ih = ih;
1191 		sfunc_arg.sa_data = arg;
1192 
1193 		buf[0] = '\0';
1194 		(*fieldp->f_sfunc)(&sfunc_arg, buf, sizeof (buf));
1195 
1196 		if (opt & IPMPSTAT_OPT_PARSABLE) {
1197 			for (i = 0; buf[i] != '\0'; i++) {
1198 				if (escsep && (buf[i] == ':' || buf[i] == '\\'))
1199 					(void) putchar('\\');
1200 				(void) putchar(buf[i]);
1201 			}
1202 			if (ofmt->o_next != NULL)
1203 				(void) putchar(':');
1204 		} else {
1205 			value = (buf[0] == '\0') ? "--" : buf;
1206 
1207 			/*
1208 			 * To avoid needless line-wraps, for the last field,
1209 			 * don't include any trailing whitespace.
1210 			 */
1211 			if (ofmt->o_next == NULL) {
1212 				(void) printf("%s", value);
1213 				continue;
1214 			}
1215 
1216 			/*
1217 			 * For other fields, grow the width as necessary to
1218 			 * ensure the value completely fits.  However, if
1219 			 * there's unused whitespace in subsequent fields,
1220 			 * then "compress" that whitespace to attempt to get
1221 			 * the columns to line up again.
1222 			 */
1223 			width = fieldp->f_width;
1224 			valwidth = strlen(value);
1225 
1226 			if (valwidth + overflow >= width) {
1227 				overflow += valwidth - width + 1;
1228 				(void) printf("%s ", value);
1229 				continue;
1230 			}
1231 
1232 			if (overflow > 0) {
1233 				compress = MIN(overflow, width - valwidth);
1234 				overflow -= compress;
1235 				width -= compress;
1236 			}
1237 			(void) printf("%-*s", width, value);
1238 		}
1239 	}
1240 	(void) printf("\n");
1241 
1242 	/*
1243 	 * In case stdout has been redirected to e.g. a pipe, flush stdout so
1244 	 * that commands can act on our output immediately.
1245 	 */
1246 	(void) fflush(stdout);
1247 }
1248 
1249 /*
1250  * Searches the `fields' array for a field matching `fieldname'.  Returns
1251  * a pointer to that field on success, or NULL on failure.
1252  */
1253 static ipmpstat_field_t *
1254 field_find(ipmpstat_field_t *fields, const char *fieldname)
1255 {
1256 	ipmpstat_field_t *fieldp;
1257 
1258 	for (fieldp = fields; fieldp->f_name != NULL; fieldp++) {
1259 		if (strcasecmp(fieldp->f_name, fieldname) == 0)
1260 			return (fieldp);
1261 	}
1262 	return (NULL);
1263 }
1264 
1265 /*
1266  * Uses `enums' to map `enumval' to a string, and stores at most `bufsize'
1267  * bytes of that string into `buf'.
1268  */
1269 static void
1270 enum2str(const ipmpstat_enum_t *enums, int enumval, char *buf, uint_t bufsize)
1271 {
1272 	const ipmpstat_enum_t *enump;
1273 
1274 	for (enump = enums; enump->e_name != NULL; enump++) {
1275 		if (enump->e_val == enumval) {
1276 			(void) strlcpy(buf, enump->e_name, bufsize);
1277 			return;
1278 		}
1279 	}
1280 	(void) snprintf(buf, bufsize, "<%d>", enumval);
1281 }
1282 
1283 /*
1284  * Stores the stringified value of the sockaddr_storage pointed to by `ssp'
1285  * into at most `bufsize' bytes of `buf'.
1286  */
1287 static void
1288 sockaddr2str(const struct sockaddr_storage *ssp, char *buf, uint_t bufsize)
1289 {
1290 	int flags = NI_NOFQDN;
1291 	socklen_t socklen;
1292 	struct sockaddr *sp = (struct sockaddr *)ssp;
1293 
1294 	/*
1295 	 * Sadly, getnameinfo() does not allow the socklen to be oversized for
1296 	 * a given family -- so we must determine the exact size to pass to it.
1297 	 */
1298 	switch (ssp->ss_family) {
1299 	case AF_INET:
1300 		socklen = sizeof (struct sockaddr_in);
1301 		break;
1302 	case AF_INET6:
1303 		socklen = sizeof (struct sockaddr_in6);
1304 		break;
1305 	default:
1306 		(void) strlcpy(buf, "?", bufsize);
1307 		return;
1308 	}
1309 
1310 	if (opt & IPMPSTAT_OPT_NUMERIC)
1311 		flags |= NI_NUMERICHOST;
1312 
1313 	(void) getnameinfo(sp, socklen, buf, bufsize, NULL, 0, flags);
1314 }
1315 
1316 static void
1317 sighandler(int sig)
1318 {
1319 	assert(sig == SIGWINCH);
1320 
1321 	if (ioctl(1, TIOCGWINSZ, &winsize) == -1 ||
1322 	    winsize.ws_col == 0 || winsize.ws_row == 0) {
1323 		winsize.ws_col = 80;
1324 		winsize.ws_row = 24;
1325 	}
1326 }
1327 
1328 static void
1329 usage(void)
1330 {
1331 	const char *argstr = gettext("[-n] [-o <field> [-P]] -a|-g|-i|-p|-t");
1332 
1333 	(void) fprintf(stderr, gettext("usage: %s %s\n"), progname, argstr);
1334 	exit(EXIT_FAILURE);
1335 }
1336 
1337 /* PRINTFLIKE1 */
1338 static void
1339 warn(const char *format, ...)
1340 {
1341 	va_list alist;
1342 	int error = errno;
1343 
1344 	format = gettext(format);
1345 	(void) fprintf(stderr, gettext("%s: warning: "), progname);
1346 
1347 	va_start(alist, format);
1348 	(void) vfprintf(stderr, format, alist);
1349 	va_end(alist);
1350 
1351 	if (strchr(format, '\n') == NULL)
1352 		(void) fprintf(stderr, ": %s\n", strerror(error));
1353 }
1354 
1355 /* PRINTFLIKE2 */
1356 static void
1357 warn_ipmperr(int ipmperr, const char *format, ...)
1358 {
1359 	va_list alist;
1360 
1361 	format = gettext(format);
1362 	(void) fprintf(stderr, gettext("%s: warning: "), progname);
1363 
1364 	va_start(alist, format);
1365 	(void) vfprintf(stderr, format, alist);
1366 	va_end(alist);
1367 
1368 	(void) fprintf(stderr, ": %s\n", ipmp_errmsg(ipmperr));
1369 }
1370 
1371 /* PRINTFLIKE1 */
1372 static void
1373 die(const char *format, ...)
1374 {
1375 	va_list alist;
1376 	int error = errno;
1377 
1378 	format = gettext(format);
1379 	(void) fprintf(stderr, "%s: ", progname);
1380 
1381 	va_start(alist, format);
1382 	(void) vfprintf(stderr, format, alist);
1383 	va_end(alist);
1384 
1385 	if (strchr(format, '\n') == NULL)
1386 		(void) fprintf(stderr, ": %s\n", strerror(error));
1387 
1388 	exit(EXIT_FAILURE);
1389 }
1390 
1391 /* PRINTFLIKE2 */
1392 static void
1393 die_ipmperr(int ipmperr, const char *format, ...)
1394 {
1395 	va_list alist;
1396 
1397 	format = gettext(format);
1398 	(void) fprintf(stderr, "%s: ", progname);
1399 
1400 	va_start(alist, format);
1401 	(void) vfprintf(stderr, format, alist);
1402 	va_end(alist);
1403 	(void) fprintf(stderr, ": %s\n", ipmp_errmsg(ipmperr));
1404 
1405 	exit(EXIT_FAILURE);
1406 }
1407 
1408 static ipmpstat_field_t addr_fields[] = {
1409 	{ "ADDRESS",    26,	sfunc_addr_address	},
1410 	{ "STATE",	7,	sfunc_addr_state	},
1411 	{ "GROUP",	12,	sfunc_addr_group	},
1412 	{ "INBOUND",	12,	sfunc_addr_inbound	},
1413 	{ "OUTBOUND",	23,	sfunc_addr_outbound	},
1414 	{ NULL,		0, 	NULL			}
1415 };
1416 
1417 static ipmpstat_field_t group_fields[] = {
1418 	{ "GROUP",	12, 	sfunc_group_ifname	},
1419 	{ "GROUPNAME",	12,	sfunc_group_name 	},
1420 	{ "STATE",	10,	sfunc_group_state	},
1421 	{ "FDT",	10,	sfunc_group_fdt		},
1422 	{ "INTERFACES",	30,	sfunc_group_interfaces	},
1423 	{ NULL,		0, 	NULL			}
1424 };
1425 
1426 static ipmpstat_field_t if_fields[] = {
1427 	{ "INTERFACE",	12,	sfunc_if_name		},
1428 	{ "ACTIVE",	8, 	sfunc_if_active		},
1429 	{ "GROUP",	12,	sfunc_if_group		},
1430 	{ "FLAGS",	10,	sfunc_if_flags		},
1431 	{ "LINK",	10,	sfunc_if_link		},
1432 	{ "PROBE",	10,	sfunc_if_probe		},
1433 	{ "STATE",	10, 	sfunc_if_state		},
1434 	{ NULL,		0, 	NULL			}
1435 };
1436 
1437 static ipmpstat_field_t probe_fields[] = {
1438 	{ "TIME",	10,	sfunc_probe_time	},
1439 	{ "INTERFACE",	12,	sfunc_probe_ifname	},
1440 	{ "PROBE",	7,	sfunc_probe_id		},
1441 	{ "NETRTT",	10,	sfunc_probe_netrtt	},
1442 	{ "RTT",	10,	sfunc_probe_rtt		},
1443 	{ "RTTAVG",	10,	sfunc_probe_rttavg	},
1444 	{ "TARGET",	20,	sfunc_probe_target	},
1445 	{ "RTTDEV",	10,	sfunc_probe_rttdev	},
1446 	{ NULL,		0, 	NULL			}
1447 };
1448 
1449 static ipmpstat_field_t targ_fields[] = {
1450 	{ "INTERFACE",	12,	sfunc_targ_ifname	},
1451 	{ "MODE",	10,	sfunc_targ_mode		},
1452 	{ "TESTADDR",	20,	sfunc_targ_testaddr	},
1453 	{ "TARGETS",	38,	sfunc_targ_targets	},
1454 	{ NULL,		0, 	NULL			}
1455 };
1456 
1457 static ipmpstat_enum_t	addr_state[] = {
1458 	{ "up",		IPMP_ADDR_UP			},
1459 	{ "down",	IPMP_ADDR_DOWN			},
1460 	{ NULL,		0 				}
1461 };
1462 
1463 static ipmpstat_enum_t	group_state[] = {
1464 	{ "ok",		IPMP_GROUP_OK 			},
1465 	{ "failed",	IPMP_GROUP_FAILED		},
1466 	{ "degraded",	IPMP_GROUP_DEGRADED		},
1467 	{ NULL,		0 				}
1468 };
1469 
1470 static ipmpstat_enum_t	if_link[] = {
1471 	{ "up",		IPMP_LINK_UP 			},
1472 	{ "down",	IPMP_LINK_DOWN			},
1473 	{ "unknown",	IPMP_LINK_UNKNOWN		},
1474 	{ NULL,		0 				}
1475 };
1476 
1477 static ipmpstat_enum_t	if_probe[] = {
1478 	{ "ok",		IPMP_PROBE_OK 			},
1479 	{ "failed",	IPMP_PROBE_FAILED		},
1480 	{ "unknown",	IPMP_PROBE_UNKNOWN		},
1481 	{ "disabled",	IPMP_PROBE_DISABLED		},
1482 	{ NULL,		0 				}
1483 };
1484 
1485 static ipmpstat_enum_t	if_state[] = {
1486 	{ "ok",		IPMP_IF_OK 			},
1487 	{ "failed",	IPMP_IF_FAILED			},
1488 	{ "unknown",	IPMP_IF_UNKNOWN			},
1489 	{ "offline",	IPMP_IF_OFFLINE			},
1490 	{ NULL,		0 				}
1491 };
1492 
1493 static ipmpstat_enum_t	targ_mode[] = {
1494 	{ "disabled",	IPMP_TARG_DISABLED		},
1495 	{ "routes",	IPMP_TARG_ROUTES		},
1496 	{ "multicast",	IPMP_TARG_MULTICAST		},
1497 	{ NULL,		0 				}
1498 };
1499