OpenSolaris_b135/cmd/flowadm/flowadm.c

Compare this file to the similar file:
Show the results in this format:

/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <stdio.h>
#include <locale.h>
#include <stdarg.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <stropts.h>
#include <errno.h>
#include <kstat.h>
#include <strings.h>
#include <getopt.h>
#include <unistd.h>
#include <priv.h>
#include <netdb.h>
#include <libintl.h>
#include <libdlflow.h>
#include <libdllink.h>
#include <libdlstat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ethernet.h>
#include <inet/ip.h>
#include <inet/ip6.h>
#include <stddef.h>
#include <ofmt.h>

typedef struct show_usage_state_s {
	boolean_t	us_plot;
	boolean_t	us_parsable;
	boolean_t	us_printheader;
	boolean_t	us_first;
	boolean_t	us_showall;
	ofmt_handle_t	us_ofmt;
} show_usage_state_t;

typedef struct show_flow_state {
	boolean_t		fs_firstonly;
	boolean_t		fs_donefirst;
	pktsum_t		fs_prevstats;
	uint32_t		fs_flags;
	dladm_status_t		fs_status;
	ofmt_handle_t		fs_ofmt;
	const char		*fs_flow;
	const char		*fs_link;
	boolean_t		fs_parsable;
	boolean_t		fs_persist;
	boolean_t		fs_stats;
	uint64_t		fs_mask;
} show_flow_state_t;

typedef void cmdfunc_t(int, char **);

static cmdfunc_t do_add_flow, do_remove_flow, do_init_flow, do_show_flow;
static cmdfunc_t do_show_flowprop, do_set_flowprop, do_reset_flowprop;
static cmdfunc_t do_show_usage;

static int	show_flow(dladm_handle_t, dladm_flow_attr_t *, void *);
static int	show_flows_onelink(dladm_handle_t, datalink_id_t, void *);

static void	flow_stats(const char *, datalink_id_t,  uint_t, char *,
		    show_flow_state_t *);
static void	get_flow_stats(const char *, pktsum_t *);
static int	show_flow_stats(dladm_handle_t, dladm_flow_attr_t *, void *);
static int	show_link_flow_stats(dladm_handle_t, datalink_id_t, void *);

static int	remove_flow(dladm_handle_t, dladm_flow_attr_t *, void *);

static int	show_flowprop(dladm_handle_t, dladm_flow_attr_t *, void *);
static void	show_flowprop_one_flow(void *, const char *);
static int	show_flowprop_onelink(dladm_handle_t, datalink_id_t, void *);

static void	die(const char *, ...);
static void	die_optdup(int);
static void	die_opterr(int, int);
static void	die_dlerr(dladm_status_t, const char *, ...);
static void	warn(const char *, ...);
static void	warn_dlerr(dladm_status_t, const char *, ...);

/* callback functions for printing output */
static ofmt_cb_t print_flowprop_cb, print_default_cb, print_flow_stats_cb;
static void flowadm_ofmt_check(ofmt_status_t, boolean_t, ofmt_handle_t);

typedef struct	cmd {
	char	*c_name;
	void	(*c_fn)(int, char **);
} cmd_t;

static cmd_t	cmds[] = {
	{ "add-flow", do_add_flow },
	{ "remove-flow", do_remove_flow },
	{ "show-flowprop", do_show_flowprop },
	{ "set-flowprop", do_set_flowprop },
	{ "reset-flowprop", do_reset_flowprop },
	{ "show-flow", do_show_flow },
	{ "init-flow", do_init_flow },
	{ "show-usage", do_show_usage }
};

static const struct option longopts[] = {
	{"link",		required_argument,	0, 'l'},
	{"parsable",		no_argument,		0, 'p'},
	{"parseable",		no_argument,		0, 'p'},
	{"statistics",		no_argument,		0, 's'},
	{"interval",		required_argument,	0, 'i'},
	{"temporary",		no_argument,		0, 't'},
	{"root-dir",		required_argument,	0, 'R'},
	{ 0, 0, 0, 0 }
};

static const struct option prop_longopts[] = {
	{"link",		required_argument,	0, 'l'},
	{"temporary",		no_argument,		0, 't'},
	{"root-dir",		required_argument,	0, 'R'},
	{"prop",		required_argument,	0, 'p'},
	{"attr",		required_argument,	0, 'a'},
	{ 0, 0, 0, 0 }
};

/*
 * structures for 'flowadm remove-flow'
 */
typedef struct remove_flow_state {
	boolean_t	fs_tempop;
	const char	*fs_altroot;
	dladm_status_t	fs_status;
} remove_flow_state_t;

#define	PROTO_MAXSTR_LEN	7
#define	PORT_MAXSTR_LEN		6
#define	DSFIELD_MAXSTR_LEN	10
#define	NULL_OFMT		{NULL, 0, 0, NULL}

typedef struct flow_fields_buf_s
{
	char flow_name[MAXFLOWNAMELEN];
	char flow_link[MAXLINKNAMELEN];
	char flow_ipaddr[INET6_ADDRSTRLEN+4];
	char flow_proto[PROTO_MAXSTR_LEN];
	char flow_lport[PORT_MAXSTR_LEN];
	char flow_rport[PORT_MAXSTR_LEN];
	char flow_dsfield[DSFIELD_MAXSTR_LEN];
} flow_fields_buf_t;

static ofmt_field_t flow_fields[] = {
/* name,	field width,	index */
{  "FLOW",	12,
	offsetof(flow_fields_buf_t, flow_name), print_default_cb},
{  "LINK",	12,
	offsetof(flow_fields_buf_t, flow_link), print_default_cb},
{  "IPADDR",	25,
	offsetof(flow_fields_buf_t, flow_ipaddr), print_default_cb},
{  "PROTO",	7,
	offsetof(flow_fields_buf_t, flow_proto), print_default_cb},
{  "LPORT",	8,
	offsetof(flow_fields_buf_t, flow_lport), print_default_cb},
{  "RPORT",	8,
	offsetof(flow_fields_buf_t, flow_rport), print_default_cb},
{  "DSFLD",	10,
	offsetof(flow_fields_buf_t, flow_dsfield), print_default_cb},
NULL_OFMT}
;

/*
 * structures for 'flowadm show-flowprop'
 */
typedef enum {
	FLOWPROP_FLOW,
	FLOWPROP_PROPERTY,
	FLOWPROP_VALUE,
	FLOWPROP_DEFAULT,
	FLOWPROP_POSSIBLE
} flowprop_field_index_t;

static ofmt_field_t flowprop_fields[] = {
/* name,	fieldwidth,	index, 		callback */
{ "FLOW",	13,	FLOWPROP_FLOW,		print_flowprop_cb},
{ "PROPERTY",	16,	FLOWPROP_PROPERTY,	print_flowprop_cb},
{ "VALUE",	15,	FLOWPROP_VALUE,		print_flowprop_cb},
{ "DEFAULT",	15,	FLOWPROP_DEFAULT,	print_flowprop_cb},
{ "POSSIBLE",	21,	FLOWPROP_POSSIBLE,	print_flowprop_cb},
NULL_OFMT}
;

#define	MAX_PROP_LINE		512

typedef struct show_flowprop_state {
	const char		*fs_flow;
	datalink_id_t		fs_linkid;
	char			*fs_line;
	char			**fs_propvals;
	dladm_arg_list_t	*fs_proplist;
	boolean_t		fs_parsable;
	boolean_t		fs_persist;
	boolean_t		fs_header;
	dladm_status_t		fs_status;
	dladm_status_t		fs_retstatus;
	ofmt_handle_t		fs_ofmt;
} show_flowprop_state_t;

typedef struct set_flowprop_state {
	const char	*fs_name;
	boolean_t	fs_reset;
	boolean_t	fs_temp;
	dladm_status_t	fs_status;
} set_flowprop_state_t;

typedef struct flowprop_args_s {
	show_flowprop_state_t	*fs_state;
	char			*fs_propname;
	char			*fs_flowname;
} flowprop_args_t;
/*
 * structures for 'flowadm show-flow -s' (print statistics)
 */
typedef enum {
	FLOW_S_FLOW,
	FLOW_S_IPKTS,
	FLOW_S_RBYTES,
	FLOW_S_IERRORS,
	FLOW_S_OPKTS,
	FLOW_S_OBYTES,
	FLOW_S_OERRORS
} flow_s_field_index_t;

static ofmt_field_t flow_s_fields[] = {
/* name,	field width,	index,		callback */
{ "FLOW",	15,	FLOW_S_FLOW,	print_flow_stats_cb},
{ "IPACKETS",	10,	FLOW_S_IPKTS,	print_flow_stats_cb},
{ "RBYTES",	8,	FLOW_S_RBYTES,	print_flow_stats_cb},
{ "IERRORS",	10,	FLOW_S_IERRORS,	print_flow_stats_cb},
{ "OPACKETS",	12,	FLOW_S_OPKTS,	print_flow_stats_cb},
{ "OBYTES",	12,	FLOW_S_OBYTES,	print_flow_stats_cb},
{ "OERRORS",	8,	FLOW_S_OERRORS,	print_flow_stats_cb},
NULL_OFMT}
;

typedef struct flow_args_s {
	char		*flow_s_flow;
	pktsum_t	*flow_s_psum;
} flow_args_t;

/*
 * structures for 'flowadm show-usage'
 */
typedef struct  usage_fields_buf_s {
	char	usage_flow[12];
	char	usage_duration[10];
	char	usage_ipackets[9];
	char	usage_rbytes[10];
	char	usage_opackets[9];
	char	usage_obytes[10];
	char	usage_bandwidth[14];
} usage_fields_buf_t;

static ofmt_field_t usage_fields[] = {
/* name,	field width,	offset */
{ "FLOW",	13,
	offsetof(usage_fields_buf_t, usage_flow), print_default_cb},
{ "DURATION",	11,
	offsetof(usage_fields_buf_t, usage_duration), print_default_cb},
{ "IPACKETS",	10,
	offsetof(usage_fields_buf_t, usage_ipackets), print_default_cb},
{ "RBYTES",	11,
	offsetof(usage_fields_buf_t, usage_rbytes), print_default_cb},
{ "OPACKETS",	10,
	offsetof(usage_fields_buf_t, usage_opackets), print_default_cb},
{ "OBYTES",	11,
	offsetof(usage_fields_buf_t, usage_obytes), print_default_cb},
{ "BANDWIDTH",	15,
	offsetof(usage_fields_buf_t, usage_bandwidth), print_default_cb},
NULL_OFMT}
;

/*
 * structures for 'dladm show-usage link'
 */

typedef struct  usage_l_fields_buf_s {
	char	usage_l_flow[12];
	char	usage_l_stime[13];
	char	usage_l_etime[13];
	char	usage_l_rbytes[8];
	char	usage_l_obytes[8];
	char	usage_l_bandwidth[14];
} usage_l_fields_buf_t;

static ofmt_field_t usage_l_fields[] = {
/* name,	field width,	offset */
{ "FLOW",	13,
	offsetof(usage_l_fields_buf_t, usage_l_flow), print_default_cb},
{ "START",	14,
	offsetof(usage_l_fields_buf_t, usage_l_stime), print_default_cb},
{ "END",	14,
	offsetof(usage_l_fields_buf_t, usage_l_etime), print_default_cb},
{ "RBYTES",	9,
	offsetof(usage_l_fields_buf_t, usage_l_rbytes), print_default_cb},
{ "OBYTES",	9,
	offsetof(usage_l_fields_buf_t, usage_l_obytes), print_default_cb},
{ "BANDWIDTH",	15,
	offsetof(usage_l_fields_buf_t, usage_l_bandwidth), print_default_cb},
NULL_OFMT}
;

#define	PRI_HI		100
#define	PRI_LO 		10
#define	PRI_NORM	50

#define	FLOWADM_CONF	"/etc/dladm/flowadm.conf"
#define	BLANK_LINE(s)	((s[0] == '\0') || (s[0] == '#') || (s[0] == '\n'))

static char *progname;

boolean_t		t_arg = B_FALSE; /* changes are persistent */
char			*altroot = NULL;

/*
 * Handle to libdladm.  Opened in main() before the sub-command
 * specific function is called.
 */
static dladm_handle_t handle = NULL;

static const char *attr_table[] =
	{"local_ip", "remote_ip", "transport", "local_port", "remote_port",
	    "dsfield"};

#define	NATTR	(sizeof (attr_table)/sizeof (char *))

static void
usage(void)
{
	(void) fprintf(stderr, gettext("usage: flowadm <subcommand>"
	    " <args>...\n"
	    "    add-flow       [-t] -l <link> -a <attr>=<value>[,...]\n"
	    "\t\t   [-p <prop>=<value>,...] <flow>\n"
	    "    remove-flow    [-t] {-l <link> | <flow>}\n"
	    "    show-flow      [-p] [-s [-i <interval>]] [-l <link>] "
	    "[<flow>]\n\n"
	    "    set-flowprop   [-t] -p <prop>=<value>[,...] <flow>\n"
	    "    reset-flowprop [-t] [-p <prop>,...] <flow>\n"
	    "    show-flowprop  [-cP] [-l <link>] [-p <prop>,...] "
	    "[<flow>]\n\n"
	    "    show-usage     [-a] [-d | -F <format>] "
	    "[-s <DD/MM/YYYY,HH:MM:SS>]\n"
	    "\t\t   [-e <DD/MM/YYYY,HH:MM:SS>] -f <logfile> [<flow>]\n"));

	/* close dladm handle if it was opened */
	if (handle != NULL)
		dladm_close(handle);

	exit(1);
}

int
main(int argc, char *argv[])
{
	int	i, arglen, cmdlen;
	cmd_t	*cmdp;
	dladm_status_t status;

	(void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN)
#define	TEXT_DOMAIN "SYS_TEST"
#endif
	(void) textdomain(TEXT_DOMAIN);

	progname = argv[0];

	if (argc < 2)
		usage();

	for (i = 0; i < sizeof (cmds) / sizeof (cmds[0]); i++) {
		cmdp = &cmds[i];
		arglen = strlen(argv[1]);
		cmdlen = strlen(cmdp->c_name);
		if ((arglen == cmdlen) && (strncmp(argv[1], cmdp->c_name,
		    cmdlen) == 0)) {
			/* Open the libdladm handle */
			if ((status = dladm_open(&handle)) != DLADM_STATUS_OK) {
				die_dlerr(status,
				    "could not open /dev/dld");
			}

			cmdp->c_fn(argc - 1, &argv[1]);

			dladm_close(handle);
			exit(EXIT_SUCCESS);
		}
	}

	(void) fprintf(stderr, gettext("%s: unknown subcommand '%s'\n"),
	    progname, argv[1]);
	usage();

	return (0);
}

static const char *
match_attr(char *attr)
{
	int i;

	for (i = 0; i < NATTR; i++) {
		if (strlen(attr) == strlen(attr_table[i]) &&
		    strncmp(attr, attr_table[i], strlen(attr_table[i])) == 0) {
			return (attr);
		}
	}
	return (NULL);
}

/* ARGSUSED */
static void
do_init_flow(int argc, char *argv[])
{
	dladm_status_t status;

	status = dladm_flow_init(handle);
	if (status != DLADM_STATUS_OK)
		die_dlerr(status, "flows initialization failed");
}

/* ARGSUSED */
static int
show_usage_date(dladm_usage_t *usage, void *arg)
{
	show_usage_state_t	*state = (show_usage_state_t *)arg;
	time_t			stime;
	char			timebuf[20];
	dladm_flow_attr_t	attr;
	dladm_status_t		status;

	/*
	 * Only show usage information for existing flows unless '-a'
	 * is specified.
	 */
	if (!state->us_showall && ((status = dladm_flow_info(handle,
	    usage->du_name, &attr)) != DLADM_STATUS_OK)) {
		return (status);
	}

	stime = usage->du_stime;
	(void) strftime(timebuf, sizeof (timebuf), "%m/%d/%Y",
	    localtime(&stime));
	(void) printf("%s\n", timebuf);

	return (DLADM_STATUS_OK);
}

static int
show_usage_time(dladm_usage_t *usage, void *arg)
{
	show_usage_state_t	*state = (show_usage_state_t *)arg;
	char			buf[DLADM_STRSIZE];
	usage_l_fields_buf_t 	ubuf;
	time_t			time;
	double			bw;
	dladm_flow_attr_t	attr;
	dladm_status_t		status;

	/*
	 * Only show usage information for existing flows unless '-a'
	 * is specified.
	 */
	if (!state->us_showall && ((status = dladm_flow_info(handle,
	    usage->du_name, &attr)) != DLADM_STATUS_OK)) {
		return (status);
	}

	if (state->us_plot) {
		if (!state->us_printheader) {
			if (state->us_first) {
				(void) printf("# Time");
				state->us_first = B_FALSE;
			}
			(void) printf(" %s", usage->du_name);
			if (usage->du_last) {
				(void) printf("\n");
				state->us_first = B_TRUE;
				state->us_printheader = B_TRUE;
			}
		} else {
			if (state->us_first) {
				time = usage->du_etime;
				(void) strftime(buf, sizeof (buf), "%T",
				    localtime(&time));
				state->us_first = B_FALSE;
				(void) printf("%s", buf);
			}
			bw = (double)usage->du_bandwidth/1000;
			(void) printf(" %.2f", bw);
			if (usage->du_last) {
				(void) printf("\n");
				state->us_first = B_TRUE;
			}
		}
		return (DLADM_STATUS_OK);
	}

	bzero(&ubuf, sizeof (ubuf));

	(void) snprintf(ubuf.usage_l_flow, sizeof (ubuf.usage_l_flow), "%s",
	    usage->du_name);
	time = usage->du_stime;
	(void) strftime(buf, sizeof (buf), "%T", localtime(&time));
	(void) snprintf(ubuf.usage_l_stime, sizeof (ubuf.usage_l_stime), "%s",
	    buf);
	time = usage->du_etime;
	(void) strftime(buf, sizeof (buf), "%T", localtime(&time));
	(void) snprintf(ubuf.usage_l_etime, sizeof (ubuf.usage_l_etime), "%s",
	    buf);
	(void) snprintf(ubuf.usage_l_rbytes, sizeof (ubuf.usage_l_rbytes),
	    "%llu", usage->du_rbytes);
	(void) snprintf(ubuf.usage_l_obytes, sizeof (ubuf.usage_l_obytes),
	    "%llu", usage->du_obytes);
	(void) snprintf(ubuf.usage_l_bandwidth, sizeof (ubuf.usage_l_bandwidth),
	    "%s Mbps", dladm_bw2str(usage->du_bandwidth, buf));

	ofmt_print(state->us_ofmt, (void *)&ubuf);
	return (DLADM_STATUS_OK);
}

static int
show_usage_res(dladm_usage_t *usage, void *arg)
{
	show_usage_state_t	*state = (show_usage_state_t *)arg;
	char			buf[DLADM_STRSIZE];
	usage_fields_buf_t	ubuf;
	dladm_flow_attr_t	attr;
	dladm_status_t		status;

	/*
	 * Only show usage information for existing flows unless '-a'
	 * is specified.
	 */
	if (!state->us_showall && ((status = dladm_flow_info(handle,
	    usage->du_name, &attr)) != DLADM_STATUS_OK)) {
		return (status);
	}

	bzero(&ubuf, sizeof (ubuf));

	(void) snprintf(ubuf.usage_flow, sizeof (ubuf.usage_flow), "%s",
	    usage->du_name);
	(void) snprintf(ubuf.usage_duration, sizeof (ubuf.usage_duration),
	    "%llu", usage->du_duration);
	(void) snprintf(ubuf.usage_ipackets, sizeof (ubuf.usage_ipackets),
	    "%llu", usage->du_ipackets);
	(void) snprintf(ubuf.usage_rbytes, sizeof (ubuf.usage_rbytes),
	    "%llu", usage->du_rbytes);
	(void) snprintf(ubuf.usage_opackets, sizeof (ubuf.usage_opackets),
	    "%llu", usage->du_opackets);
	(void) snprintf(ubuf.usage_obytes, sizeof (ubuf.usage_obytes),
	    "%llu", usage->du_obytes);
	(void) snprintf(ubuf.usage_bandwidth, sizeof (ubuf.usage_bandwidth),
	    "%s Mbps", dladm_bw2str(usage->du_bandwidth, buf));

	ofmt_print(state->us_ofmt, (void *)&ubuf);

	return (DLADM_STATUS_OK);
}

static boolean_t
valid_formatspec(char *formatspec_str)
{
	if (strcmp(formatspec_str, "gnuplot") == 0)
		return (B_TRUE);
	return (B_FALSE);
}

/* ARGSUSED */
static void
do_show_usage(int argc, char *argv[])
{
	char			*file = NULL;
	int			opt;
	dladm_status_t		status;
	boolean_t		d_arg = B_FALSE;
	char			*stime = NULL;
	char			*etime = NULL;
	char			*resource = NULL;
	show_usage_state_t	state;
	boolean_t		o_arg = B_FALSE;
	boolean_t		F_arg = B_FALSE;
	char			*fields_str = NULL;
	char			*formatspec_str = NULL;
	char			*all_fields =
	    "flow,duration,ipackets,rbytes,opackets,obytes,bandwidth";
	char			*all_l_fields =
	    "flow,start,end,rbytes,obytes,bandwidth";
	ofmt_handle_t		ofmt;
	ofmt_status_t		oferr;
	uint_t			ofmtflags = 0;

	bzero(&state, sizeof (show_usage_state_t));
	state.us_parsable = B_FALSE;
	state.us_printheader = B_FALSE;
	state.us_plot = B_FALSE;
	state.us_first = B_TRUE;

	while ((opt = getopt(argc, argv, "das:e:o:f:F:")) != -1) {
		switch (opt) {
		case 'd':
			d_arg = B_TRUE;
			break;
		case 'a':
			state.us_showall = B_TRUE;
			break;
		case 'f':
			file = optarg;
			break;
		case 's':
			stime = optarg;
			break;
		case 'e':
			etime = optarg;
			break;
		case 'o':
			o_arg = B_TRUE;
			fields_str = optarg;
			break;
		case 'F':
			state.us_plot = F_arg = B_TRUE;
			formatspec_str = optarg;
			break;
		default:
			die_opterr(optopt, opt);
		}
	}

	if (file == NULL)
		die("show-usage requires a file");

	if (optind == (argc-1)) {
		dladm_flow_attr_t	attr;

		if (!state.us_showall &&
		    dladm_flow_info(handle, resource, &attr) !=
		    DLADM_STATUS_OK) {
			die("invalid flow: '%s'", resource);
		}
		resource = argv[optind];
	}

	if (state.us_parsable)
		ofmtflags |= OFMT_PARSABLE;
	if (resource == NULL && stime == NULL && etime == NULL) {
		if (!o_arg || (o_arg && strcasecmp(fields_str, "all") == 0))
			fields_str = all_fields;
		oferr = ofmt_open(fields_str, usage_fields, ofmtflags,
		    0, &ofmt);
	} else {
		if (!o_arg || (o_arg && strcasecmp(fields_str, "all") == 0))
			fields_str = all_l_fields;
		oferr = ofmt_open(fields_str, usage_l_fields, ofmtflags,
		    0, &ofmt);
	}

	flowadm_ofmt_check(oferr, state.us_parsable, ofmt);
	state.us_ofmt = ofmt;

	if (F_arg && d_arg)
		die("incompatible -d and -F options");

	if (F_arg && valid_formatspec(formatspec_str) == B_FALSE)
		die("Format specifier %s not supported", formatspec_str);

	if (d_arg) {
		/* Print log dates */
		status = dladm_usage_dates(show_usage_date,
		    DLADM_LOGTYPE_FLOW, file, resource, &state);
	} else if (resource == NULL && stime == NULL && etime == NULL &&
	    !F_arg) {
		/* Print summary */
		status = dladm_usage_summary(show_usage_res,
		    DLADM_LOGTYPE_FLOW, file, &state);
	} else if (resource != NULL) {
		/* Print log entries for named resource */
		status = dladm_walk_usage_res(show_usage_time,
		    DLADM_LOGTYPE_FLOW, file, resource, stime, etime, &state);
	} else {
		/* Print time and information for each link */
		status = dladm_walk_usage_time(show_usage_time,
		    DLADM_LOGTYPE_FLOW, file, stime, etime, &state);
	}

	ofmt_close(ofmt);
	if (status != DLADM_STATUS_OK)
		die_dlerr(status, "show-usage");
}

static void
do_add_flow(int argc, char *argv[])
{
	char			devname[MAXLINKNAMELEN];
	char			*name = NULL;
	uint_t			index;
	datalink_id_t		linkid;

	char			option;
	boolean_t		l_arg = B_FALSE;
	char			propstr[DLADM_STRSIZE];
	char			attrstr[DLADM_STRSIZE];
	dladm_arg_list_t	*proplist = NULL;
	dladm_arg_list_t	*attrlist = NULL;
	dladm_status_t		status;

	bzero(propstr, DLADM_STRSIZE);
	bzero(attrstr, DLADM_STRSIZE);

	while ((option = getopt_long(argc, argv, "tR:l:a:p:",
	    prop_longopts, NULL)) != -1) {
		switch (option) {
		case 't':
			t_arg = B_TRUE;
			break;
		case 'R':
			altroot = optarg;
			break;
		case 'l':
			if (strlcpy(devname, optarg,
			    MAXLINKNAMELEN) >= MAXLINKNAMELEN) {
				die("link name too long");
			}
			if (dladm_name2info(handle, devname, &linkid, NULL,
			    NULL, NULL) != DLADM_STATUS_OK)
				die("invalid link '%s'", devname);
			l_arg = B_TRUE;
			break;
		case 'a':
			(void) strlcat(attrstr, optarg, DLADM_STRSIZE);
			if (strlcat(attrstr, ",", DLADM_STRSIZE) >=
			    DLADM_STRSIZE)
				die("attribute list too long '%s'", attrstr);
			break;
		case 'p':
			(void) strlcat(propstr, optarg, DLADM_STRSIZE);
			if (strlcat(propstr, ",", DLADM_STRSIZE) >=
			    DLADM_STRSIZE)
				die("property list too long '%s'", propstr);
			break;
		default:
			die_opterr(optopt, option);
		}
	}
	if (!l_arg) {
		die("link is required");
	}

	opterr = 0;
	index = optind;

	if ((index != (argc - 1)) || match_attr(argv[index]) != NULL) {
		die("flow name is required");
	} else {
		/* get flow name; required last argument */
		if (strlen(argv[index]) >= MAXFLOWNAMELEN)
			die("flow name too long");
		name = argv[index];
	}

	if (dladm_parse_flow_attrs(attrstr, &attrlist, B_FALSE)
	    != DLADM_STATUS_OK)
		die("invalid flow attribute specified");
	if (dladm_parse_flow_props(propstr, &proplist, B_FALSE)
	    != DLADM_STATUS_OK)
		die("invalid flow property specified");

	status = dladm_flow_add(handle, linkid, attrlist, proplist, name,
	    t_arg, altroot);
	if (status != DLADM_STATUS_OK)
		die_dlerr(status, "add flow failed");

	dladm_free_attrs(attrlist);
	dladm_free_props(proplist);
}

static void
do_remove_flow(int argc, char *argv[])
{
	char			option;
	char			*flowname = NULL;
	char			linkname[MAXLINKNAMELEN];
	datalink_id_t		linkid = DATALINK_ALL_LINKID;
	boolean_t		l_arg = B_FALSE;
	remove_flow_state_t	state;
	dladm_status_t		status;

	bzero(&state, sizeof (state));

	opterr = 0;
	while ((option = getopt_long(argc, argv, ":tR:l:",
	    longopts, NULL)) != -1) {
		switch (option) {
		case 't':
			t_arg = B_TRUE;
			break;
		case 'R':
			altroot = optarg;
			break;
		case 'l':
			if (strlcpy(linkname, optarg,
			    MAXLINKNAMELEN) >= MAXLINKNAMELEN) {
				die("link name too long");
			}
			if (dladm_name2info(handle, linkname, &linkid, NULL,
			    NULL, NULL) != DLADM_STATUS_OK) {
				die("invalid link '%s'", linkname);
			}
			l_arg = B_TRUE;
			break;
		default:
			die_opterr(optopt, option);
			break;
		}
	}

	/* when link not specified get flow name */
	if (!l_arg) {
		if (optind != (argc-1)) {
			usage();
		} else {
			if (strlen(argv[optind]) >= MAXFLOWNAMELEN)
				die("flow name too long");
			flowname = argv[optind];
		}
		status = dladm_flow_remove(handle, flowname, t_arg, altroot);
	} else {
		/* if link is specified then flow name should not be there */
		if (optind == argc-1)
			usage();
		/* walk the link to find flows and remove them */
		state.fs_tempop = t_arg;
		state.fs_altroot = altroot;
		state.fs_status = DLADM_STATUS_OK;
		status = dladm_walk_flow(remove_flow, handle, linkid, &state,
		    B_FALSE);
		/*
		 * check if dladm_walk_flow terminated early and see if the
		 * walker function as any status for us
		 */
		if (status == DLADM_STATUS_OK)
			status = state.fs_status;
	}

	if (status != DLADM_STATUS_OK)
		die_dlerr(status, "remove flow failed");
}

/*
 * Walker function for removing a flow through dladm_walk_flow();
 */
/*ARGSUSED*/
static int
remove_flow(dladm_handle_t handle, dladm_flow_attr_t *attr, void *arg)
{
	remove_flow_state_t	*state = (remove_flow_state_t *)arg;

	state->fs_status = dladm_flow_remove(handle, attr->fa_flowname,
	    state->fs_tempop, state->fs_altroot);

	if (state->fs_status == DLADM_STATUS_OK)
		return (DLADM_WALK_CONTINUE);
	else
		return (DLADM_WALK_TERMINATE);
}

/*ARGSUSED*/
static dladm_status_t
print_flow(show_flow_state_t *state, dladm_flow_attr_t *attr,
    flow_fields_buf_t *fbuf)
{
	char		link[MAXLINKNAMELEN];
	dladm_status_t	status;

	if ((status = dladm_datalink_id2info(handle, attr->fa_linkid, NULL,
	    NULL, NULL, link, sizeof (link))) != DLADM_STATUS_OK) {
		return (status);
	}

	(void) snprintf(fbuf->flow_name, sizeof (fbuf->flow_name),
	    "%s", attr->fa_flowname);
	(void) snprintf(fbuf->flow_link, sizeof (fbuf->flow_link),
	    "%s", link);

	(void) dladm_flow_attr_ip2str(attr, fbuf->flow_ipaddr,
	    sizeof (fbuf->flow_ipaddr));
	(void) dladm_flow_attr_proto2str(attr, fbuf->flow_proto,
	    sizeof (fbuf->flow_proto));
	if ((attr->fa_flow_desc.fd_mask & FLOW_ULP_PORT_LOCAL) != 0) {
		(void) dladm_flow_attr_port2str(attr, fbuf->flow_lport,
		    sizeof (fbuf->flow_lport));
	}
	if ((attr->fa_flow_desc.fd_mask & FLOW_ULP_PORT_REMOTE) != 0) {
		(void) dladm_flow_attr_port2str(attr, fbuf->flow_rport,
		    sizeof (fbuf->flow_rport));
	}
	(void) dladm_flow_attr_dsfield2str(attr, fbuf->flow_dsfield,
	    sizeof (fbuf->flow_dsfield));

	return (DLADM_STATUS_OK);
}

/*
 * Walker function for showing flow attributes through dladm_walk_flow().
 */
/*ARGSUSED*/
static int
show_flow(dladm_handle_t handle, dladm_flow_attr_t *attr, void *arg)
{
	show_flow_state_t	*statep = arg;
	dladm_status_t		status;
	flow_fields_buf_t	fbuf;

	/*
	 * first get all the flow attributes into fbuf;
	 */
	bzero(&fbuf, sizeof (fbuf));
	status = print_flow(statep, attr, &fbuf);

	if (status != DLADM_STATUS_OK)
		goto done;

	ofmt_print(statep->fs_ofmt, (void *)&fbuf);

done:
	statep->fs_status = status;
	return (DLADM_WALK_CONTINUE);
}

static void
show_one_flow(void *arg, const char *name)
{
	dladm_flow_attr_t	attr;

	if (dladm_flow_info(handle, name, &attr) != DLADM_STATUS_OK)
		die("invalid flow: '%s'", name);
	else
		(void) show_flow(handle, &attr, arg);
}

/*
 * Wrapper of dladm_walk_flow(show_flow,...) to make it usable to
 * dladm_walk_datalink_id(). Used for showing flow attributes for
 * all flows on all links.
 */
static int
show_flows_onelink(dladm_handle_t dh, datalink_id_t linkid, void *arg)
{
	show_flow_state_t *state = arg;

	(void) dladm_walk_flow(show_flow, dh, linkid, arg, state->fs_persist);

	return (DLADM_WALK_CONTINUE);
}

static void
get_flow_stats(const char *flowname, pktsum_t *stats)
{
	kstat_ctl_t	*kcp;
	kstat_t		*ksp;

	bzero(stats, sizeof (*stats));

	if ((kcp = kstat_open()) == NULL) {
		warn("kstat open operation failed");
		return;
	}

	ksp = dladm_kstat_lookup(kcp, NULL, -1, flowname, "flow");

	if (ksp != NULL)
		dladm_get_stats(kcp, ksp, stats);

	(void) kstat_close(kcp);
}

static boolean_t
print_flow_stats_cb(ofmt_arg_t *of_arg, char *buf, uint_t bufsize)
{
	flow_args_t	*fargs = of_arg->ofmt_cbarg;
	pktsum_t	*diff_stats = fargs->flow_s_psum;

	switch (of_arg->ofmt_id) {
	case FLOW_S_FLOW:
		(void) snprintf(buf, bufsize, "%s", fargs->flow_s_flow);
		break;
	case FLOW_S_IPKTS:
		(void) snprintf(buf, bufsize, "%llu",
		    diff_stats->ipackets);
		break;
	case FLOW_S_RBYTES:
		(void) snprintf(buf, bufsize, "%llu",
		    diff_stats->rbytes);
		break;
	case FLOW_S_IERRORS:
		(void) snprintf(buf, bufsize, "%u",
		    diff_stats->ierrors);
		break;
	case FLOW_S_OPKTS:
		(void) snprintf(buf, bufsize, "%llu",
		    diff_stats->opackets);
		break;
	case FLOW_S_OBYTES:
		(void) snprintf(buf, bufsize, "%llu",
		    diff_stats->obytes);
		break;
	case FLOW_S_OERRORS:
		(void) snprintf(buf, bufsize, "%u",
		    diff_stats->oerrors);
		break;
	default:
		die("invalid input");
		break;
	}
	return (B_TRUE);
}

/* ARGSUSED */
static int
show_flow_stats(dladm_handle_t handle, dladm_flow_attr_t *attr, void *arg)
{
	show_flow_state_t	*state = (show_flow_state_t *)arg;
	char			*name = attr->fa_flowname;
	pktsum_t		stats, diff_stats;
	flow_args_t		fargs;

	if (state->fs_firstonly) {
		if (state->fs_donefirst)
			return (DLADM_WALK_TERMINATE);
		state->fs_donefirst = B_TRUE;
	} else {
		bzero(&state->fs_prevstats, sizeof (state->fs_prevstats));
	}

	get_flow_stats(name, &stats);
	dladm_stats_diff(&diff_stats, &stats, &state->fs_prevstats);

	fargs.flow_s_flow = name;
	fargs.flow_s_psum = &diff_stats;
	ofmt_print(state->fs_ofmt, (void *)&fargs);
	state->fs_prevstats = stats;

	return (DLADM_WALK_CONTINUE);
}

/*
 * Wrapper of dladm_walk_flow(show_flow,...) to make it usable for
 * dladm_walk_datalink_id(). Used for showing flow stats for
 * all flows on all links.
 */
static int
show_link_flow_stats(dladm_handle_t dh, datalink_id_t linkid, void * arg)
{
	if (dladm_walk_flow(show_flow_stats, dh, linkid, arg, B_FALSE)
	    == DLADM_STATUS_OK)
		return (DLADM_WALK_CONTINUE);
	else
		return (DLADM_WALK_TERMINATE);
}

/* ARGSUSED */
static void
flow_stats(const char *flow, datalink_id_t linkid,  uint_t interval,
    char *fields_str, show_flow_state_t *state)
{
	dladm_flow_attr_t	attr;
	ofmt_handle_t		ofmt;
	ofmt_status_t		oferr;
	uint_t			ofmtflags = 0;

	oferr = ofmt_open(fields_str, flow_s_fields, ofmtflags, 0, &ofmt);
	flowadm_ofmt_check(oferr, state->fs_parsable, ofmt);
	state->fs_ofmt = ofmt;

	if (flow != NULL &&
	    dladm_flow_info(handle, flow, &attr) != DLADM_STATUS_OK)
		die("invalid flow %s", flow);

	/*
	 * If an interval is specified, continuously show the stats
	 * for only the first flow.
	 */
	state->fs_firstonly = (interval != 0);

	for (;;) {
		state->fs_donefirst = B_FALSE;

		/* Show stats for named flow */
		if (flow != NULL)  {
			state->fs_flow = flow;
			(void) show_flow_stats(handle, &attr, state);

		/* Show all stats on a link */
		} else if (linkid != DATALINK_INVALID_LINKID) {
			(void) dladm_walk_flow(show_flow_stats, handle, linkid,
			    state, B_FALSE);

		/* Show all stats by datalink */
		} else {
			(void) dladm_walk_datalink_id(show_link_flow_stats,
			    handle, state, DATALINK_CLASS_ALL,
			    DATALINK_ANY_MEDIATYPE, DLADM_OPT_ACTIVE);
		}

		if (interval == 0)
			break;

		(void) fflush(stdout);
		(void) sleep(interval);
	}
	ofmt_close(ofmt);
}

static void
do_show_flow(int argc, char *argv[])
{
	char			flowname[MAXFLOWNAMELEN];
	char			linkname[MAXLINKNAMELEN];
	datalink_id_t		linkid = DATALINK_ALL_LINKID;
	int			option;
	boolean_t		s_arg = B_FALSE;
	boolean_t		S_arg = B_FALSE;
	boolean_t		i_arg = B_FALSE;
	boolean_t		l_arg = B_FALSE;
	boolean_t		o_arg = B_FALSE;
	uint32_t		interval = 0;
	show_flow_state_t	state;
	char			*fields_str = NULL;
	ofmt_handle_t		ofmt;
	ofmt_status_t		oferr;
	uint_t			ofmtflags = 0;

	bzero(&state, sizeof (state));

	opterr = 0;
	while ((option = getopt_long(argc, argv, ":pPsSi:l:o:",
	    longopts, NULL)) != -1) {
		switch (option) {
		case 'p':
			state.fs_parsable = B_TRUE;
			ofmtflags |= OFMT_PARSABLE;
			break;
		case 'P':
			state.fs_persist = B_TRUE;
			break;
		case 's':
			if (s_arg)
				die_optdup(option);

			s_arg = B_TRUE;
			break;
		case 'S':
			if (S_arg)
				die_optdup(option);

			S_arg = B_TRUE;
			break;
		case 'o':
			if (o_arg)
				die_optdup(option);

			o_arg = B_TRUE;
			fields_str = optarg;
			break;
		case 'i':
			if (i_arg)
				die_optdup(option);

			i_arg = B_TRUE;

			if (!dladm_str2interval(optarg, &interval))
				die("invalid interval value '%s'", optarg);
			break;
		case 'l':
			if (strlcpy(linkname, optarg, MAXLINKNAMELEN)
			    >= MAXLINKNAMELEN)
				die("link name too long\n");
			if (dladm_name2info(handle, linkname, &linkid, NULL,
			    NULL, NULL) != DLADM_STATUS_OK)
				die("invalid link '%s'", linkname);
			l_arg = B_TRUE;
			break;
		default:
			die_opterr(optopt, option);
			break;
		}
	}
	if (i_arg && !(s_arg || S_arg))
		die("the -i option can be used only with -s or -S");

	if (s_arg && S_arg)
		die("the -s option cannot be used with -S");

	/* get flow name (optional last argument */
	if (optind == (argc-1)) {
		if (strlcpy(flowname, argv[optind], MAXFLOWNAMELEN)
		    >= MAXFLOWNAMELEN)
			die("flow name too long");
		state.fs_flow = flowname;
	}

	if (S_arg) {
		dladm_continuous(handle, linkid, state.fs_flow, interval,
		    FLOW_REPORT);
		return;
	}

	if (s_arg) {
		flow_stats(state.fs_flow, linkid, interval, fields_str, &state);
		return;
	}

	oferr = ofmt_open(fields_str, flow_fields, ofmtflags, 0, &ofmt);
	flowadm_ofmt_check(oferr, state.fs_parsable, ofmt);
	state.fs_ofmt = ofmt;

	/* Show attributes of one flow */
	if (state.fs_flow != NULL) {
		show_one_flow(&state, state.fs_flow);

	/* Show attributes of flows on one link */
	} else if (l_arg) {
		(void) show_flows_onelink(handle, linkid, &state);

	/* Show attributes of all flows on all links */
	} else {
		(void) dladm_walk_datalink_id(show_flows_onelink, handle,
		    &state, DATALINK_CLASS_ALL, DATALINK_ANY_MEDIATYPE,
		    DLADM_OPT_ACTIVE);
	}
	ofmt_close(ofmt);
}

static dladm_status_t
set_flowprop_persist(const char *flow, const char *prop_name, char **prop_val,
    uint_t val_cnt, boolean_t reset)
{
	dladm_status_t	status;
	char		*errprop;

	status = dladm_set_flowprop(handle, flow, prop_name, prop_val, val_cnt,
	    DLADM_OPT_PERSIST, &errprop);

	if (status != DLADM_STATUS_OK) {
		warn_dlerr(status, "cannot persistently %s flow "
		    "property '%s' on '%s'", reset? "reset": "set",
		    errprop, flow);
	}
	return (status);
}

static void
set_flowprop(int argc, char **argv, boolean_t reset)
{
	int			i, option;
	char			errmsg[DLADM_STRSIZE];
	const char		*flow = NULL;
	char			propstr[DLADM_STRSIZE];
	dladm_arg_list_t	*proplist = NULL;
	boolean_t		temp = B_FALSE;
	dladm_status_t		status = DLADM_STATUS_OK;

	opterr = 0;
	bzero(propstr, DLADM_STRSIZE);

	while ((option = getopt_long(argc, argv, ":p:R:t",
	    prop_longopts, NULL)) != -1) {
		switch (option) {
		case 'p':
			(void) strlcat(propstr, optarg, DLADM_STRSIZE);
			if (strlcat(propstr, ",", DLADM_STRSIZE) >=
			    DLADM_STRSIZE)
				die("property list too long '%s'", propstr);
			break;
		case 't':
			temp = B_TRUE;
			break;
		case 'R':
			status = dladm_set_rootdir(optarg);
			if (status != DLADM_STATUS_OK) {
				die_dlerr(status, "invalid directory "
				    "specified");
			}
			break;
		default:
			die_opterr(optopt, option);
			break;
		}
	}

	if (optind == (argc - 1)) {
		if (strlen(argv[optind]) >= MAXFLOWNAMELEN)
			die("flow name too long");
		flow = argv[optind];
	} else if (optind != argc) {
		usage();
	}
	if (flow == NULL)
		die("flow name must be specified");

	if (dladm_parse_flow_props(propstr, &proplist, reset)
	    != DLADM_STATUS_OK)
		die("invalid flow property specified");

	if (proplist == NULL) {
		char *errprop;

		if (!reset)
			die("flow property must be specified");

		status = dladm_set_flowprop(handle, flow, NULL, NULL, 0,
		    DLADM_OPT_ACTIVE, &errprop);
		if (status != DLADM_STATUS_OK) {
			warn_dlerr(status, "cannot reset flow property '%s' "
			    "on '%s'", errprop, flow);
		}
		if (!temp) {
			dladm_status_t	s;

			s = set_flowprop_persist(flow, NULL, NULL, 0, reset);
			if (s != DLADM_STATUS_OK)
				status = s;
		}
		goto done;
	}

	for (i = 0; i < proplist->al_count; i++) {
		dladm_arg_info_t	*aip = &proplist->al_info[i];
		char		**val;
		uint_t		count;
		dladm_status_t	s;

		if (reset) {
			val = NULL;
			count = 0;
		} else {
			val = aip->ai_val;
			count = aip->ai_count;
			if (count == 0) {
				warn("no value specified for '%s'",
				    aip->ai_name);
				status = DLADM_STATUS_BADARG;
				continue;
			}
		}
		s = dladm_set_flowprop(handle, flow, aip->ai_name, val, count,
		    DLADM_OPT_ACTIVE, NULL);
		if (s == DLADM_STATUS_OK) {
			if (!temp) {
				s = set_flowprop_persist(flow,
				    aip->ai_name, val, count, reset);
				if (s != DLADM_STATUS_OK)
					status = s;
			}
			continue;
		}
		status = s;
		switch (s) {
		case DLADM_STATUS_NOTFOUND:
			warn("invalid flow property '%s'", aip->ai_name);
			break;
		case DLADM_STATUS_BADVAL: {
			int		j;
			char		*ptr, *lim;
			char		**propvals = NULL;
			uint_t		valcnt = DLADM_MAX_PROP_VALCNT;

			ptr = malloc((sizeof (char *) +
			    DLADM_PROP_VAL_MAX) * DLADM_MAX_PROP_VALCNT +
			    MAX_PROP_LINE);

			if (ptr == NULL)
				die("insufficient memory");
			propvals = (char **)(void *)ptr;

			for (j = 0; j < DLADM_MAX_PROP_VALCNT; j++) {
				propvals[j] = ptr + sizeof (char *) *
				    DLADM_MAX_PROP_VALCNT +
				    j * DLADM_PROP_VAL_MAX;
			}
			s = dladm_get_flowprop(handle, flow,
			    DLADM_PROP_VAL_MODIFIABLE, aip->ai_name, propvals,
			    &valcnt);

			ptr = errmsg;
			lim = ptr + DLADM_STRSIZE;
			*ptr = '\0';
			for (j = 0; j < valcnt && s == DLADM_STATUS_OK; j++) {
				ptr += snprintf(ptr, lim - ptr, "%s,",
				    propvals[j]);
				if (ptr >= lim)
					break;
			}
			if (ptr > errmsg) {
				*(ptr - 1) = '\0';
				warn("flow property '%s' must be one of: %s",
				    aip->ai_name, errmsg);
			} else
				warn("%s is an invalid value for "
				    "flow property %s", *val, aip->ai_name);
			free(propvals);
			break;
		}
		default:
			if (reset) {
				warn_dlerr(status, "cannot reset flow property "
				    "'%s' on '%s'", aip->ai_name, flow);
			} else {
				warn_dlerr(status, "cannot set flow property "
				    "'%s' on '%s'", aip->ai_name, flow);
			}
			break;
		}
	}
done:
	dladm_free_props(proplist);
	if (status != DLADM_STATUS_OK) {
		dladm_close(handle);
		exit(EXIT_FAILURE);
	}
}

static void
do_set_flowprop(int argc, char **argv)
{
	set_flowprop(argc, argv, B_FALSE);
}

static void
do_reset_flowprop(int argc, char **argv)
{
	set_flowprop(argc, argv, B_TRUE);
}

static void
warn(const char *format, ...)
{
	va_list alist;

	format = gettext(format);
	(void) fprintf(stderr, "%s: warning: ", progname);

	va_start(alist, format);
	(void) vfprintf(stderr, format, alist);
	va_end(alist);

	(void) putchar('\n');
}

/* PRINTFLIKE2 */
static void
warn_dlerr(dladm_status_t err, const char *format, ...)
{
	va_list alist;
	char    errmsg[DLADM_STRSIZE];

	format = gettext(format);
	(void) fprintf(stderr, gettext("%s: warning: "), progname);

	va_start(alist, format);
	(void) vfprintf(stderr, format, alist);
	va_end(alist);
	(void) fprintf(stderr, ": %s\n", dladm_status2str(err, errmsg));
}

/* PRINTFLIKE1 */
static void
die(const char *format, ...)
{
	va_list alist;

	format = gettext(format);
	(void) fprintf(stderr, "%s: ", progname);

	va_start(alist, format);
	(void) vfprintf(stderr, format, alist);
	va_end(alist);

	(void) putchar('\n');

	/* close dladm handle if it was opened */
	if (handle != NULL)
		dladm_close(handle);

	exit(EXIT_FAILURE);
}

static void
die_optdup(int opt)
{
	die("the option -%c cannot be specified more than once", opt);
}

static void
die_opterr(int opt, int opterr)
{
	switch (opterr) {
	case ':':
		die("option '-%c' requires a value", opt);
		break;
	case '?':
	default:
		die("unrecognized option '-%c'", opt);
		break;
	}
}

/* PRINTFLIKE2 */
static void
die_dlerr(dladm_status_t err, const char *format, ...)
{
	va_list alist;
	char	errmsg[DLADM_STRSIZE];

	format = gettext(format);
	(void) fprintf(stderr, "%s: ", progname);

	va_start(alist, format);
	(void) vfprintf(stderr, format, alist);
	va_end(alist);
	(void) fprintf(stderr, ": %s\n", dladm_status2str(err, errmsg));

	/* close dladm handle if it was opened */
	if (handle != NULL)
		dladm_close(handle);

	exit(EXIT_FAILURE);
}

static void
print_flowprop(const char *flowname, show_flowprop_state_t *statep,
    const char *propname, dladm_prop_type_t type,
    const char *format, char **pptr)
{
	int		i;
	char		*ptr, *lim;
	char		buf[DLADM_STRSIZE];
	char		*unknown = "--", *notsup = "";
	char		**propvals = statep->fs_propvals;
	uint_t		valcnt = DLADM_MAX_PROP_VALCNT;
	dladm_status_t	status;

	status = dladm_get_flowprop(handle, flowname, type, propname, propvals,
	    &valcnt);
	if (status != DLADM_STATUS_OK) {
		if (status == DLADM_STATUS_TEMPONLY) {
			if (type == DLADM_PROP_VAL_MODIFIABLE &&
			    statep->fs_persist) {
				valcnt = 1;
				propvals = &unknown;
			} else {
				statep->fs_status = status;
				statep->fs_retstatus = status;
				return;
			}
		} else if (status == DLADM_STATUS_NOTSUP ||
		    statep->fs_persist) {
			valcnt = 1;
			if (type == DLADM_PROP_VAL_CURRENT)
				propvals = &unknown;
			else
				propvals = &notsup;
		} else {
			if ((statep->fs_proplist != NULL) &&
			    statep->fs_status == DLADM_STATUS_OK) {
				warn("invalid flow property '%s'", propname);
			}
			statep->fs_status = status;
			statep->fs_retstatus = status;
			return;
		}
	}

	statep->fs_status = DLADM_STATUS_OK;

	ptr = buf;
	lim = buf + DLADM_STRSIZE;
	for (i = 0; i < valcnt; i++) {
		if (propvals[i][0] == '\0' && !statep->fs_parsable)
			ptr += snprintf(ptr, lim - ptr, "--,");
		else
			ptr += snprintf(ptr, lim - ptr, "%s,", propvals[i]);
		if (ptr >= lim)
			break;
	}
	if (valcnt > 0)
		buf[strlen(buf) - 1] = '\0';

	lim = statep->fs_line + MAX_PROP_LINE;
	if (statep->fs_parsable) {
		*pptr += snprintf(*pptr, lim - *pptr,
		    "%s", buf);
	} else {
		*pptr += snprintf(*pptr, lim - *pptr, format, buf);
	}
}

static boolean_t
print_flowprop_cb(ofmt_arg_t *of_arg, char *buf, uint_t bufsize)
{
	flowprop_args_t		*arg = of_arg->ofmt_cbarg;
	char 			*propname = arg->fs_propname;
	show_flowprop_state_t	*statep = arg->fs_state;
	char			*ptr = statep->fs_line;
	char			*lim = ptr + MAX_PROP_LINE;
	char			*flowname = arg->fs_flowname;

	switch (of_arg->ofmt_id) {
	case FLOWPROP_FLOW:
		(void) snprintf(ptr, lim - ptr, "%s", statep->fs_flow);
		break;
	case FLOWPROP_PROPERTY:
		(void) snprintf(ptr, lim - ptr, "%s", propname);
		break;
	case FLOWPROP_VALUE:
		print_flowprop(flowname, statep, propname,
		    statep->fs_persist ? DLADM_PROP_VAL_PERSISTENT :
		    DLADM_PROP_VAL_CURRENT, "%s", &ptr);
		/*
		 * If we failed to query the flow property, for example, query
		 * the persistent value of a non-persistable flow property,
		 * simply skip the output.
		 */
		if (statep->fs_status != DLADM_STATUS_OK)
			goto skip;
		ptr = statep->fs_line;
		break;
	case FLOWPROP_DEFAULT:
		print_flowprop(flowname, statep, propname,
		    DLADM_PROP_VAL_DEFAULT, "%s", &ptr);
		if (statep->fs_status != DLADM_STATUS_OK)
			goto skip;
		ptr = statep->fs_line;
		break;
	case FLOWPROP_POSSIBLE:
		print_flowprop(flowname, statep, propname,
		    DLADM_PROP_VAL_MODIFIABLE, "%s ", &ptr);
		if (statep->fs_status != DLADM_STATUS_OK)
			goto skip;
		ptr = statep->fs_line;
		break;
	default:
		die("invalid input");
		break;
	}
	(void) strlcpy(buf, ptr, bufsize);
	return (B_TRUE);
skip:
	buf[0] = '\0';
	return ((statep->fs_status == DLADM_STATUS_OK) ?
	    B_TRUE : B_FALSE);
}

static int
show_one_flowprop(void *arg, const char *propname)
{
	show_flowprop_state_t	*statep = arg;
	flowprop_args_t		fs_arg;

	bzero(&fs_arg, sizeof (fs_arg));
	fs_arg.fs_state = statep;
	fs_arg.fs_propname = (char *)propname;
	fs_arg.fs_flowname = (char *)statep->fs_flow;

	ofmt_print(statep->fs_ofmt, (void *)&fs_arg);

	return (DLADM_WALK_CONTINUE);
}

/*ARGSUSED*/
/* Walker function called by dladm_walk_flow to display flow properties */
static int
show_flowprop(dladm_handle_t handle, dladm_flow_attr_t *attr, void *arg)
{
	show_flowprop_one_flow(arg, attr->fa_flowname);
	return (DLADM_WALK_CONTINUE);
}

/*
 * Wrapper of dladm_walk_flow(show_walk_fn,...) to make it
 * usable to dladm_walk_datalink_id()
 */
static int
show_flowprop_onelink(dladm_handle_t dh, datalink_id_t linkid, void *arg)
{
	char	name[MAXLINKNAMELEN];

	if (dladm_datalink_id2info(dh, linkid, NULL, NULL, NULL, name,
	    sizeof (name)) != DLADM_STATUS_OK)
		return (DLADM_WALK_TERMINATE);

	(void) dladm_walk_flow(show_flowprop, dh, linkid, arg, B_FALSE);

	return (DLADM_WALK_CONTINUE);
}

static void
do_show_flowprop(int argc, char **argv)
{
	int			option;
	dladm_arg_list_t	*proplist = NULL;
	show_flowprop_state_t	state;
	char			*fields_str = NULL;
	ofmt_handle_t		ofmt;
	ofmt_status_t		oferr;
	uint_t			ofmtflags = 0;

	opterr = 0;
	state.fs_propvals = NULL;
	state.fs_line = NULL;
	state.fs_parsable = B_FALSE;
	state.fs_persist = B_FALSE;
	state.fs_header = B_TRUE;
	state.fs_retstatus = DLADM_STATUS_OK;
	state.fs_linkid = DATALINK_INVALID_LINKID;
	state.fs_flow = NULL;

	while ((option = getopt_long(argc, argv, ":p:cPl:o:",
	    prop_longopts, NULL)) != -1) {
		switch (option) {
		case 'p':
			if (dladm_parse_flow_props(optarg, &proplist, B_TRUE)
			    != DLADM_STATUS_OK)
				die("invalid flow properties specified");
			break;
		case 'c':
			state.fs_parsable = B_TRUE;
			ofmtflags |= OFMT_PARSABLE;
			break;
		case 'P':
			state.fs_persist = B_TRUE;
			break;
		case 'l':
			if (dladm_name2info(handle, optarg, &state.fs_linkid,
			    NULL, NULL, NULL) != DLADM_STATUS_OK)
				die("invalid link '%s'", optarg);
			break;
		case 'o':
			fields_str = optarg;
			break;
		default:
			die_opterr(optopt, option);
			break;
		}
	}

	if (optind == (argc - 1)) {
		if (strlen(argv[optind]) >= MAXFLOWNAMELEN)
			die("flow name too long");
		state.fs_flow = argv[optind];
	} else if (optind != argc) {
		usage();
	}
	state.fs_proplist = proplist;
	state.fs_status = DLADM_STATUS_OK;

	oferr = ofmt_open(fields_str, flowprop_fields, ofmtflags, 0, &ofmt);
	flowadm_ofmt_check(oferr, state.fs_parsable, ofmt);
	state.fs_ofmt = ofmt;

	/* Show properties for one flow */
	if (state.fs_flow != NULL) {
		show_flowprop_one_flow(&state, state.fs_flow);

	/* Show properties for all flows on one link */
	} else if (state.fs_linkid != DATALINK_INVALID_LINKID) {
		(void) show_flowprop_onelink(handle, state.fs_linkid, &state);

	/* Show properties for all flows on all links */
	} else {
		(void) dladm_walk_datalink_id(show_flowprop_onelink, handle,
		    &state, DATALINK_CLASS_ALL, DATALINK_ANY_MEDIATYPE,
		    DLADM_OPT_ACTIVE);
	}

	dladm_free_props(proplist);
	ofmt_close(ofmt);
}

static void
show_flowprop_one_flow(void *arg, const char *flow)
{
	int			i;
	char			*buf;
	dladm_status_t		status;
	dladm_arg_list_t	*proplist = NULL;
	show_flowprop_state_t	*statep = arg;
	dladm_flow_attr_t	attr;
	const char		*savep;

	/*
	 * Do not print flow props for invalid flows.
	 */
	if ((status = dladm_flow_info(handle, flow, &attr)) !=
	    DLADM_STATUS_OK) {
		die("invalid flow: '%s'", flow);
	}

	savep = statep->fs_flow;
	statep->fs_flow = flow;

	proplist = statep->fs_proplist;

	buf = malloc((sizeof (char *) + DLADM_PROP_VAL_MAX)
	    * DLADM_MAX_PROP_VALCNT + MAX_PROP_LINE);
	if (buf == NULL)
		die("insufficient memory");

	statep->fs_propvals = (char **)(void *)buf;
	for (i = 0; i < DLADM_MAX_PROP_VALCNT; i++) {
		statep->fs_propvals[i] = buf +
		    sizeof (char *) * DLADM_MAX_PROP_VALCNT +
		    i * DLADM_PROP_VAL_MAX;
	}
	statep->fs_line = buf +
	    (sizeof (char *) + DLADM_PROP_VAL_MAX) * DLADM_MAX_PROP_VALCNT;

	/* show only specified flow properties */
	if (proplist != NULL) {
		for (i = 0; i < proplist->al_count; i++) {
			if (show_one_flowprop(statep,
			    proplist->al_info[i].ai_name) != DLADM_STATUS_OK)
				break;
		}

	/* show all flow properties */
	} else {
		status = dladm_walk_flowprop(show_one_flowprop, flow, statep);
		if (status != DLADM_STATUS_OK)
			die_dlerr(status, "show-flowprop");
	}
	free(buf);
	statep->fs_flow = savep;
}

/*
 * default output callback function that, when invoked from dladm_print_output,
 * prints string which is offset by of_arg->ofmt_id within buf.
 */
static boolean_t
print_default_cb(ofmt_arg_t *of_arg, char *buf, uint_t bufsize)
{
	char *value;

	value = (char *)of_arg->ofmt_cbarg + of_arg->ofmt_id;
	(void) strlcpy(buf, value, bufsize);
	return (B_TRUE);
}

static void
flowadm_ofmt_check(ofmt_status_t oferr, boolean_t parsable,
    ofmt_handle_t ofmt)
{
	char buf[OFMT_BUFSIZE];

	if (oferr == OFMT_SUCCESS)
		return;
	(void) ofmt_strerror(ofmt, oferr, buf, sizeof (buf));
	/*
	 * All errors are considered fatal in parsable mode.
	 * NOMEM errors are always fatal, regardless of mode.
	 * For other errors, we print diagnostics in human-readable
	 * mode and processs what we can.
	 */
	if (parsable || oferr == OFMT_ENOFIELDS) {
		ofmt_close(ofmt);
		die(buf);
	} else {
		warn(buf);
	}
}