/*
 * 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 (c) 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2018, Joyent, Inc.
 * Copyright 2017 Gary Mills
 * Copyright (c) 2016, Chris Fraire <cfraire@me.com>.
 * Copyright 2021 Tintri by DDN, Inc. All rights reserved.
 * Copyright 2021 OmniOS Community Edition (OmniOSce) Association.
 * Copyright 2024 Oxide Computer Company
 */

#include <arpa/inet.h>
#include <errno.h>
#include <getopt.h>
#include <inet/ip.h>
#include <inet/iptun.h>
#include <inet/tunables.h>
#include <libdladm.h>
#include <libdliptun.h>
#include <libdllink.h>
#include <libinetutil.h>
#include <libipadm.h>
#include <ipmp.h>
#include <ipmp_admin.h>
#include <locale.h>
#include <netdb.h>
#include <netinet/in.h>
#include <ofmt.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <zone.h>
#include <sys/list.h>
#include <stddef.h>

#define	STR_UNKNOWN_VAL	"?"
#define	LIFC_DEFAULT	(LIFC_NOXMIT | LIFC_TEMPORARY | LIFC_ALLZONES |\
			LIFC_UNDER_IPMP)

static void do_create_ip_common(int, char **, const char *, uint32_t);

typedef void cmdfunc_t(int, char **, const char *);
static cmdfunc_t do_help;
static cmdfunc_t do_create_ip, do_delete_ip;
static cmdfunc_t do_create_ipmp, do_add_ipmp, do_remove_ipmp;
static cmdfunc_t do_disable_if, do_enable_if, do_show_if;
static cmdfunc_t do_set_ifprop, do_reset_ifprop, do_show_ifprop;
static cmdfunc_t do_create_addr, do_delete_addr, do_show_addr, do_refresh_addr;
static cmdfunc_t do_disable_addr, do_enable_addr, do_down_addr, do_up_addr;
static cmdfunc_t do_set_addrprop, do_reset_addrprop, do_show_addrprop;
static cmdfunc_t do_set_prop, do_reset_prop, do_show_prop;

typedef struct	cmd {
	char		*c_name;
	cmdfunc_t	*c_fn;
	const char	*c_usage;
} cmd_t;

static cmd_t	cmds[] = {
	{ "help",	do_help,	NULL },
	/* interface management related sub-commands */
	{ "create-if", do_create_ip, "\tcreate-if\t[-t] <interface>"	},
	{ "create-ip", do_create_ip, "\tcreate-ip\t[-t] <interface>"	},
	{ "delete-if", do_delete_ip, "\tdelete-if\t<interface>\n"	},
	{ "delete-ip", do_delete_ip, "\tdelete-ip\t<interface>\n"	},

	{ "create-ipmp", do_create_ipmp,
	    "\tcreate-ipmp\t[-t] [-i <interface>[,<interface>]...] "
	    "<ipmp-interface>"						},
	{ "delete-ipmp", do_delete_ip,
	    "\tdelete-ipmp\t<ipmp-interface>"				},
	{ "add-ipmp", do_add_ipmp,
	    "\tadd-ipmp\t[-t] -i <interface>[,<interface>]... "
	    "<ipmp-interface>"						},
	{ "remove-ipmp", do_remove_ipmp,
	    "\tremove-ipmp\t[-t] -i <interface>[,<interface>]... "
	    "<ipmp-interface>\n"					},

	{ "disable-if", do_disable_if, "\tdisable-if\t-t <interface>"	},
	{ "enable-if", do_enable_if, "\tenable-if\t-t <interface>"	},
	{ "show-if", do_show_if,
	    "\tshow-if\t\t[[-p] -o <field>,...] [<interface>]\n"	},

	{ "set-ifprop",	do_set_ifprop,
	    "\tset-ifprop\t[-t] -p <prop>=<value[,...]> -m <protocol> "
	    "<interface>"						},
	{ "reset-ifprop", do_reset_ifprop,
	    "\treset-ifprop\t[-t] -p <prop> -m <protocol> <interface>"	},
	{ "show-ifprop", do_show_ifprop,
	    "\tshow-ifprop\t[[-c] -o <field>,...] [-p <prop>,...]\n"
	    "\t\t\t[-m <protocol>] [interface]\n"			},

	/* address management related sub-commands */
	{ "create-addr", do_create_addr,
	    "\tcreate-addr\t[-t] -T static [-d] "
	    "-a{local|remote}=addr[/prefixlen]\n\t\t\t<addrobj>\n"
	    "\tcreate-addr\t[-t] -T dhcp [-w <seconds> | forever]\n"
	    "\t\t\t[-1] [-h <hostname>] <addrobj>\n"
	    "\tcreate-addr\t[-t] -T addrconf [-i interface-id]\n"
	    "\t\t\t[-p {stateful|stateless}={yes|no}] <addrobj>" },
	{ "delete-addr", do_delete_addr, "\tdelete-addr\t[-r] <addrobj>" },
	{ "show-addr", do_show_addr,
	    "\tshow-addr\t[[-p] -o <field>,...] [<addrobj>]"		},
	{ "refresh-addr", do_refresh_addr, "\trefresh-addr\t[-i] <addrobj>" },
	{ "down-addr", do_down_addr, "\tdown-addr\t[-t] <addrobj>"	},
	{ "up-addr", do_up_addr, "\tup-addr\t\t[-t] <addrobj>"	},
	{ "disable-addr", do_disable_addr, "\tdisable-addr\t-t <addrobj>" },
	{ "enable-addr", do_enable_addr, "\tenable-addr\t-t <addrobj>\n" },

	{ "set-addrprop", do_set_addrprop,
	    "\tset-addrprop\t[-t] -p <prop>=<value[,...]> <addrobj>"	},
	{ "reset-addrprop", do_reset_addrprop,
	    "\treset-addrprop\t[-t] -p <prop> <addrobj>"		},
	{ "show-addrprop", do_show_addrprop,
	    "\tshow-addrprop\t[[-c] -o <field>,...] [-p <prop>,...] "
	    "<addrobj>\n"						},

	/* protocol properties related sub-commands */
	{ "set-prop", do_set_prop,
	    "\tset-prop\t[-t] -p <prop>[+|-]=<value[,...]> <protocol>"	},
	{ "reset-prop", do_reset_prop,
	    "\treset-prop\t[-t] -p <prop> <protocol>"			},
	{ "show-prop", do_show_prop,
	    "\tshow-prop\t[[-c] -o <field>,...] [-p <prop>,...]"
	    " [protocol]"						}
};

static const struct option if_longopts[] = {
	{"temporary",	no_argument,		0, 't'	},
	{ 0, 0, 0, 0 }
};

static const struct option show_prop_longopts[] = {
	{"parsable",	no_argument,		0, 'c'	},
	{"prop",	required_argument,	0, 'p'	},
	{"output",	required_argument,	0, 'o'	},
	{ 0, 0, 0, 0 }
};

static const struct option show_ifprop_longopts[] = {
	{"module",	required_argument,	0, 'm'	},
	{"parsable",	no_argument,		0, 'c'	},
	{"prop",	required_argument,	0, 'p'	},
	{"output",	required_argument,	0, 'o'	},
	{ 0, 0, 0, 0 }
};

static const struct option set_prop_longopts[] = {
	{"prop",	required_argument,	0, 'p'	},
	{"temporary",	no_argument,		0, 't'	},
	{ 0, 0, 0, 0 }
};

static const struct option set_ifprop_longopts[] = {
	{"module",	required_argument,	0, 'm'	},
	{"prop",	required_argument,	0, 'p'	},
	{"temporary",	no_argument,		0, 't'	},
	{ 0, 0, 0, 0 }
};

static const struct option addr_misc_longopts[] = {
	{"inform",	no_argument,		0, 'i'	},
	{"release",	no_argument,		0, 'r'	},
	{"temporary",	no_argument,		0, 't'	},
	{ 0, 0, 0, 0 }
};

static const struct option addr_longopts[] = {
	{"address",	required_argument,	0, 'a'	},
	{"down",	no_argument,		0, 'd'	},
	{"interface-id", required_argument,	0, 'i'	},
	{"primary",	no_argument,		0, '1'	},
	{"prop",	required_argument,	0, 'p'	},
	{"reqhost", required_argument,	0, 'h'	},
	{"temporary",	no_argument,		0, 't'	},
	{"type",	required_argument,	0, 'T'	},
	{"wait",	required_argument,	0, 'w'	},
	{ 0, 0, 0, 0 }
};

static const struct option show_addr_longopts[] = {
	{"parsable",	no_argument,		0, 'p'	},
	{"output",	required_argument,	0, 'o'	},
	{ 0, 0, 0, 0 }
};

static const struct option show_if_longopts[] = {
	{"parsable",	no_argument,		0, 'p'	},
	{"output",	required_argument,	0, 'o'	},
	{ 0, 0, 0, 0 }
};

/* callback functions to print show-* subcommands output */
static ofmt_cb_t print_prop_cb;
static ofmt_cb_t print_sa_cb;
static ofmt_cb_t print_si_cb;

/* structures for 'ipadm show-*' subcommands */
typedef enum {
	IPADM_PROPFIELD_IFNAME,
	IPADM_PROPFIELD_PROTO,
	IPADM_PROPFIELD_ADDROBJ,
	IPADM_PROPFIELD_PROPERTY,
	IPADM_PROPFIELD_PERM,
	IPADM_PROPFIELD_CURRENT,
	IPADM_PROPFIELD_PERSISTENT,
	IPADM_PROPFIELD_DEFAULT,
	IPADM_PROPFIELD_POSSIBLE
} ipadm_propfield_index_t;

static ofmt_field_t intfprop_fields[] = {
/* name,	field width,	index,			callback */
{ "IFNAME",	12,	IPADM_PROPFIELD_IFNAME,		print_prop_cb},
{ "PROPERTY",	16,	IPADM_PROPFIELD_PROPERTY,	print_prop_cb},
{ "PROTO",	6,	IPADM_PROPFIELD_PROTO,		print_prop_cb},
{ "PERM",	5,	IPADM_PROPFIELD_PERM,		print_prop_cb},
{ "CURRENT",	11,	IPADM_PROPFIELD_CURRENT,	print_prop_cb},
{ "PERSISTENT",	11,	IPADM_PROPFIELD_PERSISTENT,	print_prop_cb},
{ "DEFAULT",	11,	IPADM_PROPFIELD_DEFAULT,	print_prop_cb},
{ "POSSIBLE",	16,	IPADM_PROPFIELD_POSSIBLE,	print_prop_cb},
{ NULL,		0,	0,				NULL}
};


static ofmt_field_t modprop_fields[] = {
/* name,	field width,	index,			callback */
{ "PROTO",	6,	IPADM_PROPFIELD_PROTO,		print_prop_cb},
{ "PROPERTY",	22,	IPADM_PROPFIELD_PROPERTY,	print_prop_cb},
{ "PERM",	5,	IPADM_PROPFIELD_PERM,		print_prop_cb},
{ "CURRENT",	13,	IPADM_PROPFIELD_CURRENT,	print_prop_cb},
{ "PERSISTENT",	13,	IPADM_PROPFIELD_PERSISTENT,	print_prop_cb},
{ "DEFAULT",	13,	IPADM_PROPFIELD_DEFAULT,	print_prop_cb},
{ "POSSIBLE",	15,	IPADM_PROPFIELD_POSSIBLE,	print_prop_cb},
{ NULL,		0,	0,				NULL}
};

static ofmt_field_t addrprop_fields[] = {
/* name,	field width,	index,			callback */
{ "ADDROBJ",	18,	IPADM_PROPFIELD_ADDROBJ,	print_prop_cb},
{ "PROPERTY",	11,	IPADM_PROPFIELD_PROPERTY,	print_prop_cb},
{ "PERM",	5,	IPADM_PROPFIELD_PERM,		print_prop_cb},
{ "CURRENT",	16,	IPADM_PROPFIELD_CURRENT,	print_prop_cb},
{ "PERSISTENT",	16,	IPADM_PROPFIELD_PERSISTENT,	print_prop_cb},
{ "DEFAULT",	16,	IPADM_PROPFIELD_DEFAULT,	print_prop_cb},
{ "POSSIBLE",	15,	IPADM_PROPFIELD_POSSIBLE,	print_prop_cb},
{ NULL,		0,	0,				NULL}
};

typedef struct show_prop_state {
	char		sps_ifname[LIFNAMSIZ];
	char		sps_aobjname[IPADM_AOBJSIZ];
	const char	*sps_pname;
	uint_t		sps_proto;
	char		*sps_propval;
	nvlist_t	*sps_proplist;
	boolean_t	sps_parsable;
	boolean_t	sps_addrprop;
	boolean_t	sps_ifprop;
	boolean_t	sps_modprop;
	ipadm_status_t	sps_status;
	ipadm_status_t	sps_retstatus;
	ofmt_handle_t	sps_ofmt;
} show_prop_state_t;

typedef struct show_addr_state {
	boolean_t	sa_parsable;
	boolean_t	sa_persist;
	ofmt_handle_t	sa_ofmt;
} show_addr_state_t;

typedef struct show_if_state {
	boolean_t	si_parsable;
	ofmt_handle_t	si_ofmt;
} show_if_state_t;

typedef struct show_addr_args_s {
	show_addr_state_t	*sa_state;
	ipadm_addr_info_t	*sa_info;
} show_addr_args_t;

typedef struct show_if_args_s {
	show_if_state_t *si_state;
	ipadm_if_info_t *si_info;
} show_if_args_t;

typedef enum {
	SA_ADDROBJ,
	SA_TYPE,
	SA_STATE,
	SA_CURRENT,
	SA_PERSISTENT,
	SA_ADDR
} sa_field_index_t;

typedef enum {
	SI_IFNAME,
	SI_IFCLASS,
	SI_STATE,
	SI_CURRENT,
	SI_PERSISTENT
} si_field_index_t;

static ofmt_field_t show_addr_fields[] = {
/* name,	field width,	id,		callback */
{ "ADDROBJ",	18,		SA_ADDROBJ,	print_sa_cb},
{ "TYPE",	9,		SA_TYPE,	print_sa_cb},
{ "STATE",	13,		SA_STATE,	print_sa_cb},
{ "CURRENT",	8,		SA_CURRENT,	print_sa_cb},
{ "PERSISTENT",	11,		SA_PERSISTENT,	print_sa_cb},
{ "ADDR",	46,		SA_ADDR,	print_sa_cb},
{ NULL,		0,		0,		NULL}
};

static ofmt_field_t show_if_fields[] = {
/* name,	field width,	id,		callback */
{ "IFNAME",	11,		SI_IFNAME,	print_si_cb},
{ "CLASS",	10,		SI_IFCLASS,	print_si_cb},
{ "STATE",	9,		SI_STATE,	print_si_cb},
{ "CURRENT",	13,		SI_CURRENT,	print_si_cb},
{ "PERSISTENT",	11,		SI_PERSISTENT,	print_si_cb},
{ NULL,		0,		0,		NULL}
};

#define	IPADM_ALL_BITS	((uint_t)-1)
typedef struct intf_mask {
	char		*name;
	uint64_t	bits;
	uint64_t	mask;
} fmask_t;

typedef enum {
    IPMP_ADD_MEMBER,
    IPMP_REMOVE_MEMBER
} ipmp_action_t;

/*
 * Handle to libipadm. Opened in main() before the sub-command specific
 * function is called and is closed before the program exits.
 */
ipadm_handle_t	iph = NULL;

/*
 * Opaque ipadm address object. Used by all the address management subcommands.
 */
ipadm_addrobj_t	ipaddr = NULL;

static char *progname;


static void	warn(const char *, ...);
static void	die(const char *, ...) __NORETURN;
static void	die_opterr(int, int, const char *) __NORETURN;
static void	warn_ipadmerr(ipadm_status_t, const char *, ...);
static void	ipadm_check_propstr(const char *, boolean_t, const char *);
static void	process_misc_addrargs(int, char **, const char *, int *,
		    uint32_t *);
static void	do_action_ipmp(char *, ipadm_ipmp_members_t *, ipmp_action_t,
    uint32_t);


static void
usage(int ret)
{
	int	i;
	cmd_t	*cmdp;

	(void) fprintf(stderr,
	    gettext("usage:  ipadm <subcommand> <args> ...\n"));
	for (i = 0; i < sizeof (cmds) / sizeof (cmds[0]); i++) {
		cmdp = &cmds[i];
		if (cmdp->c_usage != NULL)
			(void) fprintf(stderr, "%s\n", gettext(cmdp->c_usage));
	}

	ipadm_destroy_addrobj(ipaddr);
	ipadm_close(iph);
	exit(ret);
}

static void
do_help(int argc __unused, char **argv __unused, const char *use __unused)
{
	usage(0);
}

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

	(void) setlocale(LC_ALL, "");
	(void) textdomain(TEXT_DOMAIN);

	if ((progname = strrchr(argv[0], '/')) == NULL)
		progname = argv[0];
	else
		progname++;

	if (argc < 2) {
		argv[1] = "show-addr";
		argc = 2;
	}

	status = ipadm_open(&iph, 0);
	if (status != IPADM_SUCCESS) {
		die("Could not open handle to library - %s",
		    ipadm_status2str(status));
	}

	for (i = 0; i < sizeof (cmds) / sizeof (cmds[0]); i++) {
		cmdp = &cmds[i];
		if (strcmp(argv[1], cmdp->c_name) == 0) {
			cmdp->c_fn(argc - 1, &argv[1], gettext(cmdp->c_usage));
			ipadm_destroy_addrobj(ipaddr);
			ipadm_close(iph);
			exit(0);
		}
	}

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

	return (0);
}

/*
 * Create regular IP interface or IPMP group interface
 */
static void
do_create_ip_common(int argc, char *argv[], const char *use, uint32_t flags)
{
	ipadm_status_t	status;
	int		option;

	opterr = 0;
	while ((option = getopt_long(argc, argv,
	    ":t", if_longopts, NULL)) != -1) {
		switch (option) {
		case 't':
			/*
			 * "ifconfig" mode - plumb interface, but do not
			 * restore settings that may exist in db.
			 */
			flags &= ~IPADM_OPT_PERSIST;
			break;
		default:
			die_opterr(optopt, option, use);
		}
	}
	if (optind != (argc - 1)) {
		if (use != NULL)
			die("usage: %s", use);
		else
			die(NULL);
	}
	status = ipadm_create_if(iph, argv[optind], AF_UNSPEC, flags);
	if (status != IPADM_SUCCESS) {
		die("Could not create %s : %s",
		    argv[optind], ipadm_status2str(status));
	}
}

/*
 * Helpers to parse options into ipadm_ipmp_members_t list, and to free it.
 */
static ipadm_ipmp_members_t *
get_ipmp_members(int argc, char *argv[], const char *use, uint32_t *flags)
{
	ipadm_ipmp_members_t *members = NULL;
	ipadm_ipmp_member_t *member;
	char *ifname;
	int option;


	opterr = 0;
	while ((option = getopt_long(argc, argv, ":i:", if_longopts, NULL)) !=
	    -1) {
		switch (option) {
		case 't':
			*flags &= ~IPADM_OPT_PERSIST;
			break;
		case 'i':
			if (members == NULL) {
				members = calloc(1,
				    sizeof (ipadm_ipmp_members_t));
				if (members == NULL)
					die("insufficient memory");
				list_create(members,
				    sizeof (ipadm_ipmp_member_t),
				    offsetof(ipadm_ipmp_member_t, node));
			}

			for (ifname = strtok(optarg, ",");
			    ifname != NULL;
			    ifname = strtok(NULL, ",")) {
				if ((member = calloc(1,
				    sizeof (ipadm_ipmp_member_t))) == NULL)
					die("insufficient memory");

				if (strlcpy(member->if_name, ifname,
				    sizeof (member->if_name)) >= LIFNAMSIZ)
					die("Incorrect length of interface "
					    "name: %s", ifname);

				list_insert_tail(members, member);
			}
			break;
		default:
			die_opterr(optopt, option, use);
		}
	}

	if (optind != (argc - 1))
		die("Usage: %s", use);

	if (members != NULL && list_is_empty(members)) {
		free(members);
		members = NULL;
	}

	return (members);
}

static void
free_ipmp_members(ipadm_ipmp_members_t *members)
{
	ipadm_ipmp_member_t *member;

	while ((member = list_remove_head(members)) != NULL)
		free(member);

	list_destroy(members);

	free(members);
}

/*
 * Create an IPMP group interface for which no saved configuration
 * exists in the persistent store.
 */
static void
do_create_ipmp(int argc, char *argv[], const char *use)
{
	uint32_t flags = IPADM_OPT_PERSIST | IPADM_OPT_ACTIVE | IPADM_OPT_IPMP;
	ipadm_ipmp_members_t *members = NULL;
	ipmp_handle_t ipmp_handle;
	int retval;

	retval = ipmp_open(&ipmp_handle);
	if (retval != IPMP_SUCCESS) {
		die("Could not create IPMP handle: %s",
		    ipadm_status2str(retval));
	}

	retval = ipmp_ping_daemon(ipmp_handle);
	ipmp_close(ipmp_handle);

	if (retval != IPMP_SUCCESS) {
		die("Cannot ping in.mpathd: %s", ipmp_errmsg(retval));
	}

	members = get_ipmp_members(argc, argv, use, &flags);

	do_create_ip_common(argc, argv, use, flags);

	if (members != NULL) {
		do_action_ipmp(argv[optind], members, IPMP_ADD_MEMBER, flags);
		free_ipmp_members(members);
	}
}

static void
do_add_ipmp(int argc, char *argv[], const char *use)
{
	uint32_t flags = IPADM_OPT_PERSIST | IPADM_OPT_ACTIVE;
	ipadm_ipmp_members_t *members;

	members = get_ipmp_members(argc, argv, use, &flags);

	if (members == NULL)
		die_opterr(optopt, ':', use);

	do_action_ipmp(argv[optind], members, IPMP_ADD_MEMBER, flags);
	free_ipmp_members(members);
}

static void
do_remove_ipmp(int argc, char *argv[], const char *use)
{
	uint32_t flags = IPADM_OPT_PERSIST | IPADM_OPT_ACTIVE;
	ipadm_ipmp_members_t *members;

	members = get_ipmp_members(argc, argv, use, &flags);

	if (members == NULL)
		die_opterr(optopt, ':', use);

	do_action_ipmp(argv[optind], members, IPMP_REMOVE_MEMBER, flags);
	free_ipmp_members(members);
}

static void
do_action_ipmp(char *ipmp, ipadm_ipmp_members_t *members, ipmp_action_t action,
    uint32_t flags)
{
	ipadm_status_t (*func)(ipadm_handle_t, const char *, const char *,
	    uint32_t);
	ipadm_status_t  status;
	ipadm_ipmp_member_t *member;
	char *ifname;
	const char *msg;

	if (action == IPMP_ADD_MEMBER) {
		func = ipadm_add_ipmp_member;
		msg = "Cannot add interface '%s' to IPMP interface '%s': %s";
	} else {
		func = ipadm_remove_ipmp_member;
		msg = "Cannot remove interface '%s' from IPMP interface '%s': "
		    "%s";
	}

	while ((member = list_remove_head(members)) != NULL) {
		ifname = member->if_name;

		status = func(iph, ipmp, ifname, flags);
		if (status != IPADM_SUCCESS)
			die(msg, ifname, ipmp, ipadm_status2str(status));
	}
}

/*
 * Create an IP interface for which no saved configuration exists in the
 * persistent store.
 */
static void
do_create_ip(int argc, char *argv[], const char *use)
{
	do_create_ip_common(argc, argv, use,
	    IPADM_OPT_PERSIST | IPADM_OPT_ACTIVE);
}

/*
 * Enable an IP interface based on the persistent configuration for
 * that interface.
 */
static void
do_enable_if(int argc, char *argv[], const char *use)
{
	ipadm_status_t	status;
	int		index;
	uint32_t	flags = IPADM_OPT_ACTIVE|IPADM_OPT_PERSIST;

	process_misc_addrargs(argc, argv, use, &index, &flags);
	if (flags & IPADM_OPT_PERSIST)
		die("persistent operation not supported for enable-if");
	status = ipadm_enable_if(iph, argv[index], flags);
	if (status == IPADM_ALL_ADDRS_NOT_ENABLED) {
		warn_ipadmerr(status, "");
	} else if (status != IPADM_SUCCESS) {
		die("Could not enable %s : %s",
		    argv[optind], ipadm_status2str(status));
	}
}

/*
 * Remove an IP interface from both active and persistent configuration.
 */
static void
do_delete_ip(int argc, char *argv[], const char *use)
{
	ipadm_status_t	status;
	uint32_t	flags = IPADM_OPT_ACTIVE|IPADM_OPT_PERSIST;

	if (argc != 2)
		die("Usage: %s", use);

	status = ipadm_delete_if(iph, argv[1], AF_UNSPEC, flags);
	if (status != IPADM_SUCCESS) {
		die("Could not delete %s: %s",
		    argv[optind], ipadm_status2str(status));
	}
}

/*
 * Disable an IP interface by removing it from active configuration.
 */
static void
do_disable_if(int argc, char *argv[], const char *use)
{
	ipadm_status_t	status;
	int		index;
	uint32_t	flags = IPADM_OPT_ACTIVE|IPADM_OPT_PERSIST;

	process_misc_addrargs(argc, argv, use, &index, &flags);
	if (flags & IPADM_OPT_PERSIST)
		die("persistent operation not supported for disable-if");
	status = ipadm_disable_if(iph, argv[index], flags);
	if (status != IPADM_SUCCESS) {
		die("Could not disable %s: %s",
		    argv[optind], ipadm_status2str(status));
	}
}

/*
 * Print individual columns for the show-*prop subcommands.
 */
static void
print_prop(show_prop_state_t *statep, uint_t flags, char *buf, size_t bufsize)
{
	const char		*prop_name = statep->sps_pname;
	char			*ifname = statep->sps_ifname;
	char			*propval = statep->sps_propval;
	uint_t			proto = statep->sps_proto;
	size_t			propsize = MAXPROPVALLEN;
	ipadm_status_t		status;

	if (statep->sps_ifprop) {
		status = ipadm_get_ifprop(iph, ifname, prop_name, propval,
		    &propsize, proto, flags);
	} else if (statep->sps_modprop) {
		status = ipadm_get_prop(iph, prop_name, propval, &propsize,
		    proto, flags);
	} else {
		status = ipadm_get_addrprop(iph, prop_name, propval, &propsize,
		    statep->sps_aobjname, flags);
	}

	if (status != IPADM_SUCCESS) {
		if ((status == IPADM_NOTFOUND && (flags & IPADM_OPT_PERSIST)) ||
		    status == IPADM_ENXIO) {
			propval[0] = '\0';
			goto cont;
		}
		statep->sps_status = status;
		statep->sps_retstatus = status;
		return;
	}
cont:
	statep->sps_status = IPADM_SUCCESS;
	(void) snprintf(buf, bufsize, "%s", propval);
}

/*
 * Callback function for show-*prop subcommands.
 */
/* Hammerhead: Use uint_t to match ofmt_cb_t typedef in libofmt */
static boolean_t
print_prop_cb(ofmt_arg_t *ofarg, char *buf, uint_t bufsize)
{
	show_prop_state_t	*statep = ofarg->ofmt_cbarg;
	const char		*propname = statep->sps_pname;
	uint_t			proto = statep->sps_proto;
	boolean_t		cont = _B_TRUE;

	/*
	 * Fail retrieving remaining fields, if you fail
	 * to retrieve a field.
	 */
	if (statep->sps_status != IPADM_SUCCESS)
		return (_B_FALSE);

	switch (ofarg->ofmt_id) {
	case IPADM_PROPFIELD_IFNAME:
		(void) snprintf(buf, bufsize, "%s", statep->sps_ifname);
		break;
	case IPADM_PROPFIELD_PROTO:
		(void) snprintf(buf, bufsize, "%s", ipadm_proto2str(proto));
		break;
	case IPADM_PROPFIELD_ADDROBJ:
		(void) snprintf(buf, bufsize, "%s", statep->sps_aobjname);
		break;
	case IPADM_PROPFIELD_PROPERTY:
		(void) snprintf(buf, bufsize, "%s", propname);
		break;
	case IPADM_PROPFIELD_PERM:
		print_prop(statep, IPADM_OPT_PERM, buf, bufsize);
		break;
	case IPADM_PROPFIELD_CURRENT:
		print_prop(statep, IPADM_OPT_ACTIVE, buf, bufsize);
		break;
	case IPADM_PROPFIELD_PERSISTENT:
		print_prop(statep, IPADM_OPT_PERSIST, buf, bufsize);
		break;
	case IPADM_PROPFIELD_DEFAULT:
		print_prop(statep, IPADM_OPT_DEFAULT, buf, bufsize);
		break;
	case IPADM_PROPFIELD_POSSIBLE:
		print_prop(statep, IPADM_OPT_POSSIBLE, buf, bufsize);
		break;
	}
	if (statep->sps_status != IPADM_SUCCESS)
		cont = _B_FALSE;
	return (cont);
}

/*
 * Callback function called by the property walker (ipadm_walk_prop() or
 * ipadm_walk_proptbl()), for every matched property. This function in turn
 * calls ofmt_print() to print property information.
 */
boolean_t
show_property(void *arg, const char *pname, uint_t proto)
{
	show_prop_state_t	*statep = arg;

	statep->sps_pname = pname;
	statep->sps_proto = proto;
	statep->sps_status = IPADM_SUCCESS;
	ofmt_print(statep->sps_ofmt, arg);

	/*
	 * if an object is not found or operation is not supported then
	 * stop the walker.
	 */
	if (statep->sps_status == IPADM_NOTFOUND ||
	    statep->sps_status == IPADM_NOTSUP)
		return (_B_FALSE);
	return (_B_TRUE);
}

/*
 * Properties to be displayed is in `statep->sps_proplist'. If it is NULL,
 * for all the properties for the specified object, display relevant
 * information. Otherwise, for the selected property set, display relevant
 * information
 */
static void
show_properties(void *arg, int prop_class)
{
	show_prop_state_t	*statep = arg;
	nvlist_t		*nvl = statep->sps_proplist;
	uint_t			proto = statep->sps_proto;
	nvpair_t		*curr_nvp;
	char			*buf, *name;
	ipadm_status_t		status;

	/* allocate sufficient buffer to hold a property value */
	if ((buf = malloc(MAXPROPVALLEN)) == NULL)
		die("insufficient memory");
	statep->sps_propval = buf;

	/* if no properties were specified, display all the properties */
	if (nvl == NULL) {
		(void) ipadm_walk_proptbl(proto, prop_class, show_property,
		    statep);
	} else {
		for (curr_nvp = nvlist_next_nvpair(nvl, NULL); curr_nvp;
		    curr_nvp = nvlist_next_nvpair(nvl, curr_nvp)) {
			name = nvpair_name(curr_nvp);
			status = ipadm_walk_prop(name, proto, prop_class,
			    show_property, statep);
			if (status == IPADM_PROP_UNKNOWN)
				(void) show_property(statep, name, proto);
		}
	}

	free(buf);
}

/*
 * Display information for all or specific interface properties, either for a
 * given interface or for all the interfaces in the system.
 */
static void
do_show_ifprop(int argc, char **argv, const char *use)
{
	int		option;
	nvlist_t	*proplist = NULL;
	char		*fields_str = NULL;
	char		*ifname;
	ofmt_handle_t	ofmt;
	ofmt_status_t	oferr;
	uint_t		ofmtflags = 0;
	uint_t		proto;
	boolean_t	m_arg = _B_FALSE;
	char		*protostr;
	ipadm_if_info_t	*ifinfo, *ifp;
	ipadm_status_t	status;
	show_prop_state_t state;

	protostr = "ip";
	opterr = 0;
	bzero(&state, sizeof (state));
	state.sps_propval = NULL;
	state.sps_parsable = _B_FALSE;
	state.sps_ifprop = _B_TRUE;
	state.sps_status = state.sps_retstatus = IPADM_SUCCESS;
	while ((option = getopt_long(argc, argv, ":p:m:co:",
	    show_ifprop_longopts, NULL)) != -1) {
		switch (option) {
		case 'p':
			if (ipadm_str2nvlist(optarg, &proplist,
			    IPADM_NORVAL) != 0)
				die("invalid interface properties specified");
			break;
		case 'c':
			state.sps_parsable = _B_TRUE;
			break;
		case 'o':
			fields_str = optarg;
			break;
		case 'm':
			if (m_arg)
				die("cannot specify more than one -m");
			m_arg = _B_TRUE;
			protostr = optarg;
			break;
		default:
			die_opterr(optopt, option, use);
			break;
		}
	}

	if (optind == argc - 1)
		ifname = argv[optind];
	else if (optind != argc)
		die("Usage: %s", use);
	else
		ifname = NULL;

	if ((proto = ipadm_str2proto(protostr)) == MOD_PROTO_NONE)
		die("invalid protocol '%s' specified", protostr);

	state.sps_proto = proto;
	state.sps_proplist = proplist;

	if (state.sps_parsable)
		ofmtflags |= OFMT_PARSABLE;
	oferr = ofmt_open(fields_str, intfprop_fields, ofmtflags, 0, &ofmt);
	ofmt_check(oferr, state.sps_parsable, ofmt, die, warn);
	state.sps_ofmt = ofmt;

	/* retrieve interface(s) and print the properties */
	status = ipadm_if_info(iph, ifname, &ifinfo, 0, LIFC_DEFAULT);
	if (ifname != NULL && status == IPADM_ENXIO)
		die("no such object '%s': %s", ifname,
		    ipadm_status2str(status));
	if (status != IPADM_SUCCESS)
		die("Error retrieving interface(s): %s",
		    ipadm_status2str(status));
	for (ifp = ifinfo; ifp != NULL; ifp = ifp->ifi_next) {
		(void) strlcpy(state.sps_ifname, ifp->ifi_name, LIFNAMSIZ);
		state.sps_proto = proto;
		show_properties(&state, IPADMPROP_CLASS_IF);
	}
	if (ifinfo)
		ipadm_free_if_info(ifinfo);

	nvlist_free(proplist);
	ofmt_close(ofmt);

	if (state.sps_retstatus != IPADM_SUCCESS) {
		ipadm_close(iph);
		exit(EXIT_FAILURE);
	}
}

/*
 * set/reset the interface property for a given interface.
 */
static void
set_ifprop(int argc, char **argv, boolean_t reset, const char *use)
{
	int			option;
	ipadm_status_t		status = IPADM_SUCCESS;
	boolean_t		p_arg = _B_FALSE;
	boolean_t		m_arg = _B_FALSE;
	char			*ifname, *nv, *protostr;
	char			*prop_name, *prop_val;
	uint_t			flags = IPADM_OPT_PERSIST;
	uint_t			proto;

	nv = NULL;
	protostr = NULL;
	opterr = 0;
	while ((option = getopt_long(argc, argv, ":m:p:t",
	    set_ifprop_longopts, NULL)) != -1) {
		switch (option) {
		case 'p':
			if (p_arg)
				die("-p must be specified once only");
			p_arg = _B_TRUE;

			ipadm_check_propstr(optarg, reset, use);
			nv = optarg;
			break;
		case 'm':
			if (m_arg)
				die("-m must be specified once only");
			m_arg = _B_TRUE;
			protostr = optarg;
			break;
		case 't':
			flags &= ~IPADM_OPT_PERSIST;
			break;
		default:
			die_opterr(optopt, option, use);
		}
	}

	if (!m_arg || !p_arg || optind != argc - 1)
		die("Usage: %s", use);

	ifname = argv[optind];

	prop_name = nv;
	prop_val = strchr(nv, '=');
	if (prop_val != NULL)
		*prop_val++ = '\0';

	if ((proto = ipadm_str2proto(protostr)) == MOD_PROTO_NONE)
		die("invalid protocol '%s' specified", protostr);

	if (reset)
		flags |= IPADM_OPT_DEFAULT;
	else
		flags |= IPADM_OPT_ACTIVE;
	status = ipadm_set_ifprop(iph, ifname, prop_name, prop_val, proto,
	    flags);

	if (status != IPADM_SUCCESS) {
		if (reset)
			die("reset-ifprop: %s: %s",
			    prop_name, ipadm_status2str(status));
		else
			die("set-ifprop: %s: %s",
			    prop_name, ipadm_status2str(status));
	}
}

static void
do_set_ifprop(int argc, char **argv, const char *use)
{
	set_ifprop(argc, argv, _B_FALSE, use);
}

static void
do_reset_ifprop(int argc, char **argv, const char *use)
{
	set_ifprop(argc, argv, _B_TRUE, use);
}

/*
 * Display information for all or specific protocol properties, either for a
 * given protocol or for supported protocols (IP/IPv4/IPv6/TCP/UDP/SCTP)
 */
static void
do_show_prop(int argc, char **argv, const char *use)
{
	int			option;
	nvlist_t		*proplist = NULL;
	char			*fields_str = NULL;
	char			*protostr;
	show_prop_state_t	state;
	ofmt_handle_t		ofmt;
	ofmt_status_t		oferr;
	uint_t			ofmtflags = 0;
	uint_t			proto;
	boolean_t		p_arg = _B_FALSE;

	opterr = 0;
	bzero(&state, sizeof (state));
	state.sps_propval = NULL;
	state.sps_parsable = _B_FALSE;
	state.sps_modprop = _B_TRUE;
	state.sps_status = state.sps_retstatus = IPADM_SUCCESS;
	while ((option = getopt_long(argc, argv, ":p:co:", show_prop_longopts,
	    NULL)) != -1) {
		switch (option) {
		case 'p':
			if (p_arg)
				die("-p must be specified once only");
			p_arg = _B_TRUE;
			if (ipadm_str2nvlist(optarg, &proplist,
			    IPADM_NORVAL) != 0)
				die("invalid protocol properties specified");
			break;
		case 'c':
			state.sps_parsable = _B_TRUE;
			break;
		case 'o':
			fields_str = optarg;
			break;
		default:
			die_opterr(optopt, option, use);
			break;
		}
	}
	if (optind == argc - 1) {
		protostr =  argv[optind];
		if ((proto = ipadm_str2proto(protostr)) == MOD_PROTO_NONE)
			die("invalid protocol '%s' specified", protostr);
		state.sps_proto = proto;
	} else if (optind != argc) {
		die("Usage: %s", use);
	} else {
		if (p_arg)
			die("protocol must be specified when "
			    "property name is used");
		state.sps_proto = MOD_PROTO_NONE;
	}

	state.sps_proplist = proplist;

	if (state.sps_parsable)
		ofmtflags |= OFMT_PARSABLE;
	else
		ofmtflags |= OFMT_WRAP;
	oferr = ofmt_open(fields_str, modprop_fields, ofmtflags, 0, &ofmt);
	ofmt_check(oferr, state.sps_parsable, ofmt, die, warn);
	state.sps_ofmt = ofmt;

	/* handles all the errors */
	show_properties(&state, IPADMPROP_CLASS_MODULE);

	nvlist_free(proplist);
	ofmt_close(ofmt);

	if (state.sps_retstatus != IPADM_SUCCESS) {
		ipadm_close(iph);
		exit(EXIT_FAILURE);
	}
}

/*
 * Checks to see if there are any modifiers, + or -. If there are modifiers
 * then sets IPADM_OPT_APPEND or IPADM_OPT_REMOVE, accordingly.
 */
static void
parse_modifiers(const char *pstr, uint_t *flags, const char *use)
{
	char *p;

	if ((p = strchr(pstr, '=')) == NULL)
		return;

	if (p == pstr)
		die("Invalid prop=val specified\n%s", use);

	--p;
	if (*p == '+')
		*flags |= IPADM_OPT_APPEND;
	else if (*p == '-')
		*flags |= IPADM_OPT_REMOVE;
}

/*
 * set/reset the protocol property for a given protocol.
 */
static void
set_prop(int argc, char **argv, boolean_t reset, const char *use)
{
	int			option;
	ipadm_status_t		status = IPADM_SUCCESS;
	char			*protostr, *nv, *prop_name, *prop_val;
	boolean_t		p_arg = _B_FALSE;
	uint_t			proto;
	uint_t			flags = IPADM_OPT_PERSIST;

	nv = NULL;
	opterr = 0;
	while ((option = getopt_long(argc, argv, ":p:t", set_prop_longopts,
	    NULL)) != -1) {
		switch (option) {
		case 'p':
			if (p_arg)
				die("-p must be specified once only");
			p_arg = _B_TRUE;

			ipadm_check_propstr(optarg, reset, use);
			nv = optarg;
			break;
		case 't':
			flags &= ~IPADM_OPT_PERSIST;
			break;
		default:
			die_opterr(optopt, option, use);
		}
	}

	if (!p_arg || optind != argc - 1)
		die("Usage: %s", use);

	parse_modifiers(nv, &flags, use);
	prop_name = nv;
	prop_val = strchr(nv, '=');
	if (prop_val != NULL) {
		if (flags & (IPADM_OPT_APPEND|IPADM_OPT_REMOVE))
			*(prop_val - 1) = '\0';
		*prop_val++ = '\0';
	}
	protostr = argv[optind];
	if ((proto = ipadm_str2proto(protostr)) == MOD_PROTO_NONE)
		die("invalid protocol '%s' specified", protostr);

	if (reset)
		flags |= IPADM_OPT_DEFAULT;
	else
		flags |= IPADM_OPT_ACTIVE;
	status = ipadm_set_prop(iph, prop_name, prop_val, proto, flags);

	if (status != IPADM_SUCCESS) {
		if (reset)
			die("reset-prop: %s: %s",
			    prop_name, ipadm_status2str(status));
		else
			die("set-prop: %s: %s",
			    prop_name, ipadm_status2str(status));
	}
}

static void
do_set_prop(int argc, char **argv, const char *use)
{
	set_prop(argc, argv, _B_FALSE, use);
}

static void
do_reset_prop(int argc, char **argv, const char *use)
{
	set_prop(argc, argv,  _B_TRUE, use);
}

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

	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, "\n");
}

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

	if (format != NULL) {
		format = gettext(format);
		(void) fprintf(stderr, "%s: ", progname);

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

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

	ipadm_destroy_addrobj(ipaddr);
	ipadm_close(iph);
	exit(EXIT_FAILURE);
}

static void
die_opterr(int opt, int opterr, const char *usage)
{
	switch (opterr) {
	case ':':
		die("option '-%c' requires a value\nusage: %s", opt,
		    gettext(usage));
		break;
	case '?':
	default:
		die("unrecognized option '-%c'\nusage: %s", opt,
		    gettext(usage));
		break;
	}
}

/* PRINTFLIKE2 */
static void
warn_ipadmerr(ipadm_status_t err, const char *format, ...)
{
	va_list alist;

	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", ipadm_status2str(err));
}

static void
process_static_addrargs(const char *use, char *addrarg, const char *aobjname)
{
	int		option;
	char		*val;
	char		*laddr = NULL;
	char		*raddr = NULL;
	char		*save_input_arg = addrarg;
	boolean_t	found_mismatch = _B_FALSE;
	ipadm_status_t	status;
	enum		{ A_LOCAL, A_REMOTE };
	static char	*addr_optstr[] = {
		"local",
		"remote",
		NULL,
	};

	while (*addrarg != '\0') {
		option = getsubopt(&addrarg, addr_optstr, &val);
		switch (option) {
		case A_LOCAL:
			if (laddr != NULL)
				die("Multiple local addresses provided");
			laddr = val;
			break;
		case A_REMOTE:
			if (raddr != NULL)
				die("Multiple remote addresses provided");
			raddr = val;
			break;
		default:
			if (found_mismatch)
				die("Invalid address provided\nusage: %s", use);
			found_mismatch = _B_TRUE;
			break;
		}
	}
	if (raddr != NULL && laddr == NULL)
		die("Missing local address\nusage: %s", use);

	/* If only one address is provided, it is assumed a local address. */
	if (laddr == NULL) {
		if (found_mismatch)
			laddr = save_input_arg;
		else
			die("Missing local address\nusage: %s", use);
	}

	/* Initialize the addrobj for static addresses. */
	status = ipadm_create_addrobj(IPADM_ADDR_STATIC, aobjname, &ipaddr);
	if (status != IPADM_SUCCESS) {
		die("Error in creating address object: %s",
		    ipadm_status2str(status));
	}

	/* Set the local and remote addresses */
	status = ipadm_set_addr(ipaddr, laddr, AF_UNSPEC);
	if (status != IPADM_SUCCESS) {
		die("Error in setting local address: %s",
		    ipadm_status2str(status));
	}
	if (raddr != NULL) {
		status = ipadm_set_dst_addr(ipaddr, raddr, AF_UNSPEC);
		if (status != IPADM_SUCCESS) {
			die("Error in setting remote address: %s",
			    ipadm_status2str(status));
		}
	}
}

static void
process_addrconf_addrargs(const char *use, char *addrarg)
{
	int		option;
	char		*val;
	enum		{ P_STATELESS, P_STATEFUL };
	static char	*addr_optstr[] = {
		"stateless",
		"stateful",
		NULL,
	};
	boolean_t	stateless = _B_FALSE;
	boolean_t	stateless_arg = _B_FALSE;
	boolean_t	stateful = _B_FALSE;
	boolean_t	stateful_arg = _B_FALSE;
	ipadm_status_t	status;

	while (*addrarg != '\0') {
		option = getsubopt(&addrarg, addr_optstr, &val);
		switch (option) {
		case P_STATELESS:
			if (stateless_arg)
				die("Duplicate option");
			if (val == NULL)
				die("Invalid argument");
			if (strcmp(val, "yes") == 0)
				stateless = _B_TRUE;
			else if (strcmp(val, "no") == 0)
				stateless = _B_FALSE;
			else
				die("Invalid argument");
			stateless_arg = _B_TRUE;
			break;
		case P_STATEFUL:
			if (stateful_arg)
				die("Duplicate option");
			if (val == NULL)
				die("Invalid argument");
			if (strcmp(val, "yes") == 0)
				stateful = _B_TRUE;
			else if (strcmp(val, "no") == 0)
				stateful = _B_FALSE;
			else
				die("Invalid argument");
			stateful_arg = _B_TRUE;
			break;
		default:
			die_opterr(optopt, option, use);
		}
	}

	if (!stateless_arg && !stateful_arg)
		die("Invalid arguments for option -p");

	/* Set the addrobj fields for addrconf */
	if (stateless_arg) {
		status = ipadm_set_stateless(ipaddr, stateless);
		if (status != IPADM_SUCCESS) {
			die("Error in setting stateless option: %s",
			    ipadm_status2str(status));
		}
	}
	if (stateful_arg) {
		status = ipadm_set_stateful(ipaddr, stateful);
		if (status != IPADM_SUCCESS) {
			die("Error in setting stateful option: %s",
			    ipadm_status2str(status));
		}
	}
}

/*
 * Creates static, dhcp or addrconf addresses and associates the created
 * addresses with the specified address object name.
 */
static void
do_create_addr(int argc, char *argv[], const char *use)
{
	ipadm_status_t	status;
	int		option;
	uint32_t	flags =
	    IPADM_OPT_PERSIST|IPADM_OPT_ACTIVE|IPADM_OPT_UP|IPADM_OPT_V46;
	char		*cp;
	char		*atype = NULL;
	char		*static_arg = NULL;
	char		*addrconf_arg = NULL;
	char		*interface_id = NULL;
	char		*wait = NULL;
	char		*reqhost = NULL;
	boolean_t	s_opt = _B_FALSE;	/* static addr options */
	boolean_t	auto_opt = _B_FALSE;	/* Addrconf options */
	boolean_t	dhcp_opt = _B_FALSE;	/* dhcp options */
	boolean_t	primary_opt = _B_FALSE;	/* dhcp primary option */

	opterr = 0;
	while ((option = getopt_long(argc, argv, ":1T:a:dh:i:p:w:t",
	    addr_longopts, NULL)) != -1) {
		switch (option) {
		case '1':
			primary_opt = _B_TRUE;
			break;
		case 'T':
			atype = optarg;
			break;
		case 'a':
			static_arg = optarg;
			s_opt = _B_TRUE;
			break;
		case 'd':
			flags &= ~IPADM_OPT_UP;
			s_opt = _B_TRUE;
			break;
		case 'h':
			reqhost = optarg;
			break;
		case 'i':
			interface_id = optarg;
			auto_opt = _B_TRUE;
			break;
		case 'p':
			addrconf_arg = optarg;
			auto_opt = _B_TRUE;
			break;
		case 'w':
			wait = optarg;
			dhcp_opt = _B_TRUE;
			break;
		case 't':
			flags &= ~IPADM_OPT_PERSIST;
			break;
		default:
			die_opterr(optopt, option, use);
		}
	}
	if (atype == NULL || optind != (argc - 1)) {
		die("Invalid arguments\nusage: %s", use);
	} else if ((cp = strchr(argv[optind], '/')) == NULL ||
	    strlen(++cp) == 0) {
		die("invalid address object name: %s\nusage: %s",
		    argv[optind], use);
	}

	/*
	 * Allocate and initialize the addrobj based on the address type.
	 */
	if (strcmp(atype, "static") == 0) {
		if (static_arg == NULL || auto_opt || dhcp_opt ||
		    reqhost != NULL || primary_opt) {
			die("Invalid arguments for type %s\nusage: %s",
			    atype, use);
		}
		process_static_addrargs(use, static_arg, argv[optind]);
	} else if (strcmp(atype, "dhcp") == 0) {
		if (auto_opt || s_opt) {
			die("Invalid arguments for type %s\nusage: %s",
			    atype, use);
		}

		/* Initialize the addrobj for dhcp addresses. */
		status = ipadm_create_addrobj(IPADM_ADDR_DHCP, argv[optind],
		    &ipaddr);
		if (status != IPADM_SUCCESS) {
			die("Error in creating address object: %s",
			    ipadm_status2str(status));
		}
		if (wait != NULL) {
			int32_t ipadm_wait;

			if (strcmp(wait, "forever") == 0) {
				ipadm_wait = IPADM_DHCP_WAIT_FOREVER;
			} else {
				char *end;
				long timeout = strtol(wait, &end, 10);

				if (*end != '\0' || timeout < 0)
					die("Invalid argument");
				ipadm_wait = (int32_t)timeout;
			}
			status = ipadm_set_wait_time(ipaddr, ipadm_wait);
			if (status != IPADM_SUCCESS) {
				die("Error in setting wait time: %s",
				    ipadm_status2str(status));
			}
		}
		if (primary_opt) {
			status = ipadm_set_primary(ipaddr, _B_TRUE);
			if (status != IPADM_SUCCESS) {
				die("Error in setting primary flag: %s",
				    ipadm_status2str(status));
			}
		}
		if (reqhost != NULL) {
			status = ipadm_set_reqhost(ipaddr, reqhost);
			if (status != IPADM_SUCCESS) {
				die("Error in setting reqhost: %s",
				    ipadm_status2str(status));
			}
		}
	} else if (strcmp(atype, "addrconf") == 0) {
		if (dhcp_opt || s_opt || reqhost != NULL || primary_opt) {
			die("Invalid arguments for type %s\nusage: %s",
			    atype, use);
		}

		/* Initialize the addrobj for ipv6-addrconf addresses. */
		status = ipadm_create_addrobj(IPADM_ADDR_IPV6_ADDRCONF,
		    argv[optind], &ipaddr);
		if (status != IPADM_SUCCESS) {
			die("Error in creating address object: %s",
			    ipadm_status2str(status));
		}
		if (interface_id != NULL) {
			status = ipadm_set_interface_id(ipaddr, interface_id);
			if (status != IPADM_SUCCESS) {
				die("Error in setting interface ID: %s",
				    ipadm_status2str(status));
			}
		}
		if (addrconf_arg)
			process_addrconf_addrargs(use, addrconf_arg);
	} else {
		die("Invalid address type %s", atype);
	}

	status = ipadm_create_addr(iph, ipaddr, flags);
	if (status == IPADM_DHCP_IPC_TIMEOUT)
		warn_ipadmerr(status, "");
	else if (status != IPADM_SUCCESS)
		die("Could not create address: %s", ipadm_status2str(status));
}

/*
 * Used by some address management functions to parse the command line
 * arguments and create `ipaddr' address object.
 */
static void
process_misc_addrargs(int argc, char *argv[], const char *use, int *index,
    uint32_t *flags)
{
	int		option;

	opterr = 0;
	while ((option = getopt_long(argc, argv, ":t", addr_misc_longopts,
	    NULL)) != -1) {
		switch (option) {
		case 't':
			*flags &= ~IPADM_OPT_PERSIST;
			break;
		default:
			die_opterr(optopt, option, use);
		}
	}
	if (optind != (argc - 1))
		die("Usage: %s", use);

	*index = optind;
}

/*
 * Remove an addrobj from both active and persistent configuration.
 */
static void
do_delete_addr(int argc, char *argv[], const char *use)
{
	ipadm_status_t	status;
	uint32_t	flags = IPADM_OPT_ACTIVE|IPADM_OPT_PERSIST;
	int		option;

	opterr = 0;
	while ((option = getopt_long(argc, argv, ":r", addr_misc_longopts,
	    NULL)) != -1) {
		switch (option) {
		case 'r':
			flags |= IPADM_OPT_RELEASE;
			break;
		default:
			die_opterr(optopt, option, use);
		}
	}
	if (optind != (argc - 1))
		die("Usage: %s", use);

	status = ipadm_delete_addr(iph, argv[optind], flags);
	if (status != IPADM_SUCCESS) {
		die("could not delete address: %s",
		    ipadm_status2str(status));
	}
}

/*
 * Enable an IP address based on the persistent configuration for that
 * IP address
 */
static void
do_enable_addr(int argc, char *argv[], const char *use)
{
	ipadm_status_t	status;
	int		index;
	uint32_t	flags = IPADM_OPT_ACTIVE|IPADM_OPT_PERSIST;

	process_misc_addrargs(argc, argv, use, &index, &flags);
	if (flags & IPADM_OPT_PERSIST)
		die("persistent operation not supported for enable-addr");

	status = ipadm_enable_addr(iph, argv[index], flags);
	if (status != IPADM_SUCCESS)
		die("could not enable address: %s", ipadm_status2str(status));
}

/*
 * Mark the address identified by addrobj 'up'
 */
static void
do_up_addr(int argc, char *argv[], const char *use)
{
	ipadm_status_t	status;
	int		index;
	uint32_t	flags = IPADM_OPT_ACTIVE|IPADM_OPT_PERSIST;

	process_misc_addrargs(argc, argv, use, &index, &flags);
	status = ipadm_up_addr(iph, argv[index], flags);
	if (status != IPADM_SUCCESS) {
		die("Could not mark the address up: %s",
		    ipadm_status2str(status));
	}
}

/*
 * Disable the specified addrobj by removing it from active cofiguration
 */
static void
do_disable_addr(int argc, char *argv[], const char *use)
{
	ipadm_status_t	status;
	int		index;
	uint32_t	flags = IPADM_OPT_ACTIVE|IPADM_OPT_PERSIST;

	process_misc_addrargs(argc, argv, use, &index, &flags);
	if (flags & IPADM_OPT_PERSIST)
		die("persistent operation not supported for disable-addr");

	status = ipadm_disable_addr(iph, argv[index], flags);
	if (status != IPADM_SUCCESS) {
		die("could not disable address: %s",
		    ipadm_status2str(status));
	}
}

/*
 * Mark the address identified by addrobj 'down'
 */
static void
do_down_addr(int argc, char *argv[], const char *use)
{
	ipadm_status_t	status;
	int		index;
	uint32_t	flags = IPADM_OPT_ACTIVE|IPADM_OPT_PERSIST;

	process_misc_addrargs(argc, argv, use, &index, &flags);
	status = ipadm_down_addr(iph, argv[index], flags);
	if (status != IPADM_SUCCESS)
		die("Could not mark the address down: %s",
		    ipadm_status2str(status));
}

/*
 * Restart DAD for static address. Extend lease duration for DHCP addresses
 */
static void
do_refresh_addr(int argc, char *argv[], const char *use)
{
	ipadm_status_t	status;
	int		option;
	uint32_t	flags = 0;

	opterr = 0;
	while ((option = getopt_long(argc, argv, ":i", addr_misc_longopts,
	    NULL)) != -1) {
		switch (option) {
		case 'i':
			flags |= IPADM_OPT_INFORM;
			break;
		default:
			die_opterr(optopt, option, use);
		}
	}
	if (optind != (argc - 1))
		die("Usage: %s", use);

	status = ipadm_refresh_addr(iph, argv[optind], flags);
	if (status == IPADM_DHCP_IPC_TIMEOUT)
		warn_ipadmerr(status, "");
	else if (status != IPADM_SUCCESS)
		die("could not refresh address %s", ipadm_status2str(status));
}

static void
sockaddr2str(const struct sockaddr_storage *ssp, char *buf, uint_t bufsize)
{
	socklen_t socklen;
	struct sockaddr *sp = (struct sockaddr *)ssp;

	switch (ssp->ss_family) {
	case AF_INET:
		socklen = sizeof (struct sockaddr_in);
		break;
	case AF_INET6:
		socklen = sizeof (struct sockaddr_in6);
		break;
	default:
		(void) strlcpy(buf, STR_UNKNOWN_VAL, bufsize);
		return;
	}

	(void) getnameinfo(sp, socklen, buf, bufsize, NULL, 0,
	    (NI_NOFQDN | NI_NUMERICHOST));
}

static void
flags2str(uint64_t flags, fmask_t *tbl, boolean_t is_bits,
    char *buf, uint_t bufsize)
{
	int		i;
	boolean_t	first = _B_TRUE;

	if (is_bits) {
		for (i = 0;  tbl[i].name; i++) {
			if ((flags & tbl[i].mask) == tbl[i].bits)
				(void) strlcat(buf, tbl[i].name, bufsize);
			else
				(void) strlcat(buf, "-", bufsize);
		}
	} else {
		for (i = 0; tbl[i].name; i++) {
			if ((flags & tbl[i].mask) == tbl[i].bits) {
				if (!first)
					(void) strlcat(buf, ",", bufsize);
				(void) strlcat(buf, tbl[i].name, bufsize);
				first = _B_FALSE;
			}
		}
	}
}

/*
 * return true if the address for lifname comes to us from the global zone
 * with 'allowed-ips' constraints.
 */
static boolean_t
is_from_gz(const char *lifname)
{
	ipadm_if_info_t		*if_info;
	char			phyname[LIFNAMSIZ], *cp;
	boolean_t		ret = _B_FALSE;
	ipadm_status_t		status;
	zoneid_t		zoneid;
	ushort_t		zflags;

	if ((zoneid = getzoneid()) == GLOBAL_ZONEID)
		return (_B_FALSE); /* from-gz only  makes sense in a NGZ */

	if (zone_getattr(zoneid, ZONE_ATTR_FLAGS, &zflags, sizeof (zflags)) < 0)
		return (_B_FALSE);

	if (!(zflags & ZF_NET_EXCL))
		return (_B_TRUE);  /* everything is from the GZ for shared-ip */

	(void) strncpy(phyname, lifname, sizeof (phyname));
	if ((cp = strchr(phyname, ':')) != NULL)
		*cp = '\0';
	status = ipadm_if_info(iph, phyname, &if_info, 0, LIFC_DEFAULT);
	if (status != IPADM_SUCCESS)
		return (ret);

	if (if_info->ifi_cflags & IFIF_L3PROTECT)
		ret = _B_TRUE;
	ipadm_free_if_info(if_info);
	return (ret);
}

static boolean_t
print_sa_cb(ofmt_arg_t *ofarg, char *buf, uint_t bufsize)
{
	show_addr_args_t	*arg = ofarg->ofmt_cbarg;
	ipadm_addr_info_t	*ainfo = arg->sa_info;
	char			interface[LIFNAMSIZ];
	char			addrbuf[MAXPROPVALLEN];
	char			dstbuf[MAXPROPVALLEN];
	char			prefixlenstr[MAXPROPVALLEN];
	int			prefixlen;
	struct sockaddr_in	*sin;
	struct sockaddr_in6	*sin6;
	sa_family_t		af;
	char			*phyname = NULL;
	struct ifaddrs		*ifa = &ainfo->ia_ifa;
	fmask_t cflags_mask[] = {
		{ "U",	IA_UP,			IA_UP		},
		{ "u",	IA_UNNUMBERED,		IA_UNNUMBERED	},
		{ "p",	IA_PRIVATE,		IA_PRIVATE	},
		{ "t",	IA_TEMPORARY,		IA_TEMPORARY	},
		{ "d",	IA_DEPRECATED,		IA_DEPRECATED	},
		{ NULL,		0,			0	}
	};
	fmask_t pflags_mask[] = {
		{ "U",	IA_UP,			IA_UP		},
		{ "p",	IA_PRIVATE,		IA_PRIVATE	},
		{ "d",	IA_DEPRECATED,		IA_DEPRECATED	},
		{ NULL,		0,			0	}
	};
	fmask_t type[] = {
		{ "static",	IPADM_ADDR_STATIC,	IPADM_ALL_BITS},
		{ "addrconf",	IPADM_ADDR_IPV6_ADDRCONF, IPADM_ALL_BITS},
		{ "dhcp",	IPADM_ADDR_DHCP,	IPADM_ALL_BITS},
		{ NULL,		0,			0	}
	};
	fmask_t addr_state[] = {
		{ "disabled",	IFA_DISABLED,	IPADM_ALL_BITS},
		{ "duplicate",	IFA_DUPLICATE,	IPADM_ALL_BITS},
		{ "down",	IFA_DOWN,	IPADM_ALL_BITS},
		{ "tentative",	IFA_TENTATIVE,	IPADM_ALL_BITS},
		{ "ok",		IFA_OK,		IPADM_ALL_BITS},
		{ "inaccessible", IFA_INACCESSIBLE, IPADM_ALL_BITS},
		{ NULL,		0,		0	}
	};

	buf[0] = '\0';
	switch (ofarg->ofmt_id) {
	case SA_ADDROBJ:
		if (ainfo->ia_aobjname[0] == '\0') {
			(void) strncpy(interface, ifa->ifa_name, LIFNAMSIZ);
			phyname = strrchr(interface, ':');
			if (phyname)
				*phyname = '\0';
			(void) snprintf(buf, bufsize, "%s/%s", interface,
			    STR_UNKNOWN_VAL);
		} else {
			(void) snprintf(buf, bufsize, "%s", ainfo->ia_aobjname);
		}
		break;
	case SA_STATE:
		flags2str(ainfo->ia_state, addr_state, _B_FALSE,
		    buf, bufsize);
		break;
	case SA_TYPE:
		if (is_from_gz(ifa->ifa_name))
			(void) snprintf(buf, bufsize, "from-gz");
		else
			flags2str(ainfo->ia_atype, type, _B_FALSE, buf,
			    bufsize);
		break;
	case SA_CURRENT:
		flags2str(ainfo->ia_cflags, cflags_mask, _B_TRUE, buf, bufsize);
		break;
	case SA_PERSISTENT:
		flags2str(ainfo->ia_pflags, pflags_mask, _B_TRUE, buf, bufsize);
		break;
	case SA_ADDR:
		af = ifa->ifa_addr->sa_family;
		/*
		 * If the address is 0.0.0.0 or :: and the origin is DHCP,
		 * print STR_UNKNOWN_VAL.
		 */
		if (ainfo->ia_atype == IPADM_ADDR_DHCP) {
			sin = (struct sockaddr_in *)ifa->ifa_addr;
			sin6 = (struct sockaddr_in6 *)ifa->ifa_addr;
			if ((af == AF_INET &&
			    sin->sin_addr.s_addr == INADDR_ANY) ||
			    (af == AF_INET6 &&
			    IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr))) {
				(void) snprintf(buf, bufsize, STR_UNKNOWN_VAL);
				break;
			}
		}
		if (ifa->ifa_netmask == NULL)
			prefixlen = 0;
		else
			prefixlen = mask2plen(ifa->ifa_netmask);
		bzero(prefixlenstr, sizeof (prefixlenstr));
		if (prefixlen > 0) {
			(void) snprintf(prefixlenstr, sizeof (prefixlenstr),
			    "/%d", prefixlen);
		}
		bzero(addrbuf, sizeof (addrbuf));
		bzero(dstbuf, sizeof (dstbuf));
		if (ainfo->ia_atype == IPADM_ADDR_STATIC) {
			/*
			 * Print the hostname fields if the address is not
			 * in active configuration.
			 */
			if (ainfo->ia_state == IFA_DISABLED) {
				(void) snprintf(buf, bufsize, "%s",
				    ainfo->ia_sname);
				if (ainfo->ia_dname[0] != '\0') {
					(void) snprintf(dstbuf, sizeof (dstbuf),
					    "->%s", ainfo->ia_dname);
					(void) strlcat(buf, dstbuf, bufsize);
				} else {
					(void) strlcat(buf, prefixlenstr,
					    bufsize);
				}
				break;
			}
		}
		/*
		 * For the non-persistent case, we need to show the
		 * currently configured addresses for source and
		 * destination.
		 */
		sockaddr2str((struct sockaddr_storage *)ifa->ifa_addr,
		    addrbuf, sizeof (addrbuf));
		if (ifa->ifa_flags & IFF_POINTOPOINT) {
			sockaddr2str(
			    (struct sockaddr_storage *)ifa->ifa_dstaddr,
			    dstbuf, sizeof (dstbuf));
			(void) snprintf(buf, bufsize, "%s->%s", addrbuf,
			    dstbuf);
		} else {
			(void) snprintf(buf, bufsize, "%s%s", addrbuf,
			    prefixlenstr);
		}
		break;
	default:
		die("invalid input");
		break;
	}

	return (_B_TRUE);
}

/*
 * Display address information, either for the given address or
 * for all the addresses managed by ipadm.
 */
static void
do_show_addr(int argc, char *argv[], const char *use)
{
	ipadm_status_t		status;
	show_addr_state_t	state;
	char			*def_fields_str = "addrobj,type,state,addr";
	char			*fields_str = NULL;
	ipadm_addr_info_t	*ainfo;
	ipadm_addr_info_t	*ptr;
	show_addr_args_t	sargs;
	int			option;
	ofmt_handle_t		ofmt;
	ofmt_status_t		oferr;
	uint_t			ofmtflags = 0;
	char			*aname = NULL;
	char			*ifname = NULL;
	char			*cp;
	boolean_t		found = _B_FALSE;

	opterr = 0;
	state.sa_parsable = _B_FALSE;
	state.sa_persist = _B_FALSE;
	while ((option = getopt_long(argc, argv, "po:", show_addr_longopts,
	    NULL)) != -1) {
		switch (option) {
		case 'p':
			state.sa_parsable = _B_TRUE;
			break;
		case 'o':
			fields_str = optarg;
			break;
		default:
			die_opterr(optopt, option, use);
			break;
		}
	}
	if (state.sa_parsable && fields_str == NULL)
		die("-p requires -o");

	if (optind == argc - 1) {
		aname = argv[optind];
		if ((cp = strchr(aname, '/')) == NULL)
			die("Invalid address object name provided");
		if (*(cp + 1) == '\0') {
			ifname = aname;
			*cp = '\0';
			aname = NULL;
		}
	} else if (optind == argc) {
		ifname = aname = NULL;
	} else {
		die("Usage: %s", use);
	}

	if (state.sa_parsable)
		ofmtflags |= OFMT_PARSABLE;
	if (fields_str == NULL)
		fields_str = def_fields_str;
	oferr = ofmt_open(fields_str, show_addr_fields, ofmtflags, 0, &ofmt);

	ofmt_check(oferr, state.sa_parsable, ofmt, die, warn);
	state.sa_ofmt = ofmt;

	status = ipadm_addr_info(iph, ifname, &ainfo, 0, LIFC_DEFAULT);
	if (status != IPADM_SUCCESS)
		die("Could not get address: %s", ipadm_status2str(status));

	/*
	 * There is a bit of subtlety to this. If we're here then either:
	 *
	 * - no address object parameter was provided, aname == ifname == NULL;
	 * - an address object was provided and is in aname; ifname == NULL;
	 * - an interface was provided and is in ifname; aname == NULL.
	 *
	 * We can just loop through the returned addresses and see if we found
	 * anything we care about, and the only case in which we need to report
	 * an error is if some parameter was provided - that is if `ifname` or
	 * `aname` are not NULL.
	 *
	 * In fact, if we were provided with an argument of the type
	 * `<interface>/` then we will have passed that <interface> to
	 * ipadm_addr_info() and if no addresses were found that will have
	 * already returned an error such as IPADM_NOTFOUND if was unable to
	 * find any addresses matching the interface.
	 */

	bzero(&sargs, sizeof (sargs));
	sargs.sa_state = &state;
	for (ptr = ainfo; ptr != NULL; ptr = IA_NEXT(ptr)) {
		sargs.sa_info = ptr;
		if (aname != NULL) {
			if (strcmp(sargs.sa_info->ia_aobjname, aname) != 0)
				continue;
		}
		found = _B_TRUE;
		ofmt_print(state.sa_ofmt, &sargs);
	}
	if (ainfo != NULL)
		ipadm_free_addr_info(ainfo);
	if ((ifname != NULL || aname != NULL) && !found) {
		die("Could not get address: %s",
		    ipadm_status2str(IPADM_NOTFOUND));
	}
	ofmt_close(ofmt);
}

static boolean_t
print_si_cb(ofmt_arg_t *ofarg, char *buf, uint_t bufsize)
{
	show_if_args_t		*arg = ofarg->ofmt_cbarg;
	ipadm_if_info_t		*ifinfo = arg->si_info;
	char			*ifname = ifinfo->ifi_name;
	fmask_t intf_state[] = {
		{ "ok",		IFIS_OK,	IPADM_ALL_BITS},
		{ "down",	IFIS_DOWN,	IPADM_ALL_BITS},
		{ "disabled",	IFIS_DISABLED,	IPADM_ALL_BITS},
		{ "failed",	IFIS_FAILED,	IPADM_ALL_BITS},
		{ "offline",	IFIS_OFFLINE,	IPADM_ALL_BITS},
		{ NULL,		0,		0	}
	};
	fmask_t intf_pflags[] = {
		{ "s",	IFIF_STANDBY,		IFIF_STANDBY	},
		{ "4",	IFIF_IPV4,		IFIF_IPV4	},
		{ "6",	IFIF_IPV6,		IFIF_IPV6	},
		{ NULL,	0,			0		}
	};
	fmask_t intf_cflags[] = {
		{ "b",	IFIF_BROADCAST,		IFIF_BROADCAST	},
		{ "m",	IFIF_MULTICAST,		IFIF_MULTICAST	},
		{ "p",	IFIF_POINTOPOINT,	IFIF_POINTOPOINT},
		{ "v",	IFIF_VIRTUAL,		IFIF_VIRTUAL	},
		{ "I",	IFIF_IPMP,		IFIF_IPMP	},
		{ "s",	IFIF_STANDBY,		IFIF_STANDBY	},
		{ "i",	IFIF_INACTIVE,		IFIF_INACTIVE	},
		{ "V",	IFIF_VRRP,		IFIF_VRRP	},
		{ "a",	IFIF_NOACCEPT,		IFIF_NOACCEPT	},
		{ "Z",	IFIF_L3PROTECT,		IFIF_L3PROTECT	},
		{ "4",	IFIF_IPV4,		IFIF_IPV4	},
		{ "6",	IFIF_IPV6,		IFIF_IPV6	},
		{ NULL,	0,			0		}
	};
	fmask_t intf_class[] = {
		{ "IP",		IPADM_IF_CLASS_REGULAR, IPADM_ALL_BITS},
		{ "IPMP",	IPADM_IF_CLASS_IPMP,    IPADM_ALL_BITS},
		{ "VIRTUAL",	IPADM_IF_CLASS_VIRTUAL, IPADM_ALL_BITS},
		{ "UNKNOWN",	IPADM_IF_CLASS_UNKNOWN, IPADM_ALL_BITS},
		{ NULL,	0,	0}
	};

	buf[0] = '\0';
	switch (ofarg->ofmt_id) {
	case SI_IFNAME:
		(void) snprintf(buf, bufsize, "%s", ifname);
		break;
	case SI_IFCLASS:
		flags2str(ifinfo->ifi_class, intf_class, _B_FALSE,
		    buf, bufsize);
		break;
	case SI_STATE:
		flags2str(ifinfo->ifi_state, intf_state, _B_FALSE,
		    buf, bufsize);
		break;
	case SI_CURRENT:
		flags2str(ifinfo->ifi_cflags, intf_cflags, _B_TRUE,
		    buf, bufsize);
		break;
	case SI_PERSISTENT:
		flags2str(ifinfo->ifi_pflags, intf_pflags, _B_TRUE,
		    buf, bufsize);
		break;
	default:
		die("invalid input");
		break;
	}

	return (_B_TRUE);
}

/*
 * Display interface information, either for the given interface or
 * for all the interfaces in the system.
 */
static void
do_show_if(int argc, char *argv[], const char *use)
{
	ipadm_status_t		status;
	show_if_state_t		state;
	char			*fields_str = NULL;
	ipadm_if_info_t		*if_info, *ptr;
	show_if_args_t		sargs;
	int			option;
	ofmt_handle_t		ofmt;
	ofmt_status_t		oferr;
	uint_t			ofmtflags = 0;
	char			*ifname = NULL;

	opterr = 0;
	state.si_parsable = _B_FALSE;

	while ((option = getopt_long(argc, argv, "po:", show_if_longopts,
	    NULL)) != -1) {
		switch (option) {
		case 'p':
			state.si_parsable = _B_TRUE;
			break;
		case 'o':
			fields_str = optarg;
			break;
		default:
			die_opterr(optopt, option, use);
			break;
		}
	}
	if (optind == argc - 1)
		ifname = argv[optind];
	else if (optind != argc)
		die("Usage: %s", use);
	if (state.si_parsable)
		ofmtflags |= OFMT_PARSABLE;
	oferr = ofmt_open(fields_str, show_if_fields, ofmtflags, 0, &ofmt);
	ofmt_check(oferr, state.si_parsable, ofmt, die, warn);
	state.si_ofmt = ofmt;
	bzero(&sargs, sizeof (sargs));
	sargs.si_state = &state;
	status = ipadm_if_info(iph, ifname, &if_info, 0, LIFC_DEFAULT);
	/*
	 * Return without printing any error, if no addresses were found.
	 */
	if (status != IPADM_SUCCESS) {
		die("Could not get interface(s): %s",
		    ipadm_status2str(status));
	}

	for (ptr = if_info; ptr != NULL; ptr = ptr->ifi_next) {
		sargs.si_info = ptr;
		ofmt_print(state.si_ofmt, &sargs);
	}
	if (if_info)
		ipadm_free_if_info(if_info);
}

/*
 * set/reset the address property for a given address
 */
static void
set_addrprop(int argc, char **argv, boolean_t reset, const char *use)
{
	int			option;
	ipadm_status_t		status = IPADM_SUCCESS;
	boolean_t		p_arg = _B_FALSE;
	char			*nv, *aobjname;
	char			*prop_name, *prop_val;
	uint_t			flags = IPADM_OPT_ACTIVE|IPADM_OPT_PERSIST;

	nv = NULL;
	opterr = 0;
	while ((option = getopt_long(argc, argv, ":i:p:t", set_ifprop_longopts,
	    NULL)) != -1) {
		switch (option) {
		case 'p':
			if (p_arg)
				die("-p must be specified once only");
			p_arg = _B_TRUE;

			ipadm_check_propstr(optarg, reset, use);
			nv = optarg;
			break;
		case 't':
			flags &= ~IPADM_OPT_PERSIST;
			break;
		default:
			die_opterr(optopt, option, use);
		}
	}

	if (!p_arg || optind != (argc - 1))
		die("Usage: %s", use);

	prop_name = nv;
	prop_val = strchr(nv, '=');
	if (prop_val != NULL)
		*prop_val++ = '\0';
	aobjname = argv[optind];
	if (reset)
		flags |= IPADM_OPT_DEFAULT;
	status = ipadm_set_addrprop(iph, prop_name, prop_val, aobjname, flags);
	if (status != IPADM_SUCCESS) {
		if (reset)
			die("reset-addrprop: %s: %s", prop_name,
			    ipadm_status2str(status));
		else
			die("set-addrprop: %s: %s", prop_name,
			    ipadm_status2str(status));
	}
}

/*
 * Sets a property on an address object.
 */
static void
do_set_addrprop(int argc, char **argv, const char *use)
{
	set_addrprop(argc, argv, _B_FALSE, use);
}

/*
 * Resets a property to its default value on an address object.
 */
static void
do_reset_addrprop(int argc, char **argv, const char *use)
{
	set_addrprop(argc, argv,  _B_TRUE, use);
}

/*
 * Display information for all or specific address properties, either for a
 * given address or for all the addresses in the system.
 */
static void
do_show_addrprop(int argc, char *argv[], const char *use)
{
	int			option;
	nvlist_t		*proplist = NULL;
	char			*fields_str = NULL;
	show_prop_state_t	state;
	ofmt_handle_t		ofmt;
	ofmt_status_t		oferr;
	uint_t			ofmtflags = 0;
	char			*aobjname = NULL;
	char			*ifname = NULL;
	char			*cp;
	ipadm_addr_info_t	*ainfop = NULL;
	ipadm_addr_info_t	*ptr;
	ipadm_status_t		status;
	boolean_t		found = _B_FALSE;

	opterr = 0;
	bzero(&state, sizeof (state));
	state.sps_propval = NULL;
	state.sps_parsable = _B_FALSE;
	state.sps_addrprop = _B_TRUE;
	state.sps_proto = MOD_PROTO_NONE;
	state.sps_status = state.sps_retstatus = IPADM_SUCCESS;
	while ((option = getopt_long(argc, argv, ":p:i:cPo:",
	    show_prop_longopts, NULL)) != -1) {
		switch (option) {
		case 'p':
			if (ipadm_str2nvlist(optarg, &proplist,
			    IPADM_NORVAL) != 0)
				die("invalid addrobj properties specified");
			break;
		case 'c':
			state.sps_parsable = _B_TRUE;
			break;
		case 'o':
			fields_str = optarg;
			break;
		default:
			die_opterr(optopt, option, use);
			break;
		}
	}
	if (optind == argc - 1) {
		aobjname = argv[optind];
		cp = strchr(aobjname, '/');
		if (cp == NULL)
			die("invalid addrobj name provided");
		if (*(cp + 1) == '\0') {
			ifname = aobjname;
			*cp = '\0';
			aobjname = NULL;
		}
	} else if (optind != argc) {
		die("Usage: %s", use);
	}
	state.sps_proplist = proplist;
	if (state.sps_parsable)
		ofmtflags |= OFMT_PARSABLE;
	oferr = ofmt_open(fields_str, addrprop_fields, ofmtflags, 0, &ofmt);
	ofmt_check(oferr, state.sps_parsable, ofmt, die, warn);
	state.sps_ofmt = ofmt;

	status = ipadm_addr_info(iph, ifname, &ainfop, 0, LIFC_DEFAULT);
	/* Return without printing any error, if no addresses were found */
	if (status == IPADM_NOTFOUND)
		return;
	if (status != IPADM_SUCCESS)
		die("error retrieving address: %s", ipadm_status2str(status));

	for (ptr = ainfop; ptr != NULL; ptr = IA_NEXT(ptr)) {
		char	*taobjname = ptr->ia_aobjname;

		if (taobjname[0] == '\0')
			continue;
		if (aobjname != NULL) {
			if (strcmp(aobjname, taobjname) == 0)
				found = _B_TRUE;
			else
				continue;
		}
		if (ptr->ia_atype == IPADM_ADDR_IPV6_ADDRCONF) {
			if (found)
				break;
			else
				continue;
		}
		(void) strlcpy(state.sps_aobjname, taobjname,
		    sizeof (state.sps_aobjname));
		show_properties(&state, IPADMPROP_CLASS_ADDR);
		if (found)
			break;
	}
	ipadm_free_addr_info(ainfop);

	if (aobjname != NULL && !found)
		die("addrobj not found: %s", aobjname);

	nvlist_free(proplist);
	ofmt_close(ofmt);
	if (state.sps_retstatus != IPADM_SUCCESS) {
		ipadm_close(iph);
		exit(EXIT_FAILURE);
	}
}

/*
 * check if the `pstr' adheres to following syntax
 *	- prop=<value[,...]>	(for set)
 *	- prop			(for reset)
 */
static void
ipadm_check_propstr(const char *pstr, boolean_t reset, const char *use)
{
	char	*nv;

	nv = strchr(pstr, '=');
	if (reset) {
		if (nv != NULL)
			die("incorrect syntax used for -p.\n%s", use);
	} else {
		if (nv == NULL || *++nv == '\0')
			die("please specify the value to be set.\n%s", use);
		nv = strchr(nv, '=');
		/* cannot have multiple 'prop=val' for single -p */
		if (nv != NULL)
			die("cannot specify more than one prop=val at "
			    "a time.\n%s", use);
	}
}
