/*
 * 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 2007 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <stropts.h>
#include <stdlib.h>
#include <errno.h>
#include <strings.h>
#include <libintl.h>
#include <net/if_types.h>
#include <net/if_dl.h>
#include <libdlaggr.h>
#include <libdladm_impl.h>

/*
 * Link Aggregation Administration Library.
 *
 * This library is used by administration tools such as dladm(1M) to
 * configure link aggregations.
 *
 * Link aggregation configuration information is saved in a text
 * file of the following format:
 *
 * <db-file>	::= <groups>*
 * <group>	::= <key> <sep> <policy> <sep> <nports> <sep> <ports> <sep>
 *		      <mac> <sep> <lacp-mode> <sep> <lacp-timer>
 * <sep>	::= ' ' | '\t'
 * <key>	::= <number>
 * <nports>	::= <number>
 * <ports>	::= <port> <m-port>*
 * <m-port>	::= ',' <port>
 * <port>	::= <devname>
 * <devname>	::= <string>
 * <port-num>	::= <number>
 * <policy>	::= <pol-level> <m-pol>*
 * <m-pol>	::= ',' <pol-level>
 * <pol-level>	::= 'L2' | 'L3' | 'L4'
 * <mac>	::= 'auto' | <mac-addr>
 * <mac-addr>	::= <hex> ':' <hex> ':' <hex> ':' <hex> ':' <hex> ':' <hex>
 * <lacp-mode>	::= 'off' | 'active' | 'passive'
 * <lacp-timer>	::= 'short' | 'long'
 */

#define	DLADM_AGGR_DEV		"/devices/pseudo/aggr@0:" AGGR_DEVNAME_CTL
#define	DLADM_AGGR_DB		"/etc/dladm/aggregation.conf"
#define	DLADM_AGGR_DB_TMP	"/etc/dladm/aggregation.conf.new"
#define	DLADM_AGGR_DB_LOCK	"/tmp/aggregation.conf.lock"

#define	DLADM_AGGR_DB_PERMS	S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH
#define	DLADM_AGGR_DB_OWNER	15	/* "dladm" UID */
#define	DLADM_AGGR_DB_GROUP	3	/* "sys" GID */

/*
 * The largest configurable aggregation key.  Because by default the key is
 * used as the DLPI device PPA and default VLAN PPA's are calculated as
 * ((1000 * vid) + PPA), the largest key can't be > 999.
 */
#define	DLADM_AGGR_MAX_KEY	999

#define	BLANK_LINE(s)	((s[0] == '\0') || (s[0] == '#') || (s[0] == '\n'))

/* Limits on buffer size for LAIOC_INFO request */
#define	MIN_INFO_SIZE (4*1024)
#define	MAX_INFO_SIZE (128*1024)

#define	MAXPATHLEN	1024

static uchar_t	zero_mac[] = {0, 0, 0, 0, 0, 0};

/* configuration database entry */
typedef struct dladm_aggr_grp_attr_db {
	uint32_t	lt_key;
	uint32_t	lt_policy;
	uint32_t	lt_nports;
	dladm_aggr_port_attr_db_t *lt_ports;
	boolean_t	lt_mac_fixed;
	uchar_t		lt_mac[ETHERADDRL];
	aggr_lacp_mode_t lt_lacp_mode;
	aggr_lacp_timer_t lt_lacp_timer;
} dladm_aggr_grp_attr_db_t;

typedef struct dladm_aggr_up {
	uint32_t	lu_key;
	boolean_t	lu_found;
	int		lu_fd;
} dladm_aggr_up_t;

typedef struct dladm_aggr_down {
	uint32_t	ld_key;
	boolean_t	ld_found;
} dladm_aggr_down_t;

typedef struct dladm_aggr_modify_attr {
	uint32_t	ld_policy;
	boolean_t	ld_mac_fixed;
	uchar_t		ld_mac[ETHERADDRL];
	aggr_lacp_mode_t ld_lacp_mode;
	aggr_lacp_timer_t ld_lacp_timer;
} dladm_aggr_modify_attr_t;

typedef struct policy_s {
	char		*pol_name;
	uint32_t	policy;
} policy_t;

static policy_t policies[] = {
	{"L2",		AGGR_POLICY_L2},
	{"L3",		AGGR_POLICY_L3},
	{"L4",		AGGR_POLICY_L4}};

#define	NPOLICIES	(sizeof (policies) / sizeof (policy_t))

typedef struct dladm_aggr_lacpmode_s {
	char		*mode_str;
	aggr_lacp_mode_t mode_id;
} dladm_aggr_lacpmode_t;

static dladm_aggr_lacpmode_t lacp_modes[] = {
	{"off", AGGR_LACP_OFF},
	{"active", AGGR_LACP_ACTIVE},
	{"passive", AGGR_LACP_PASSIVE}};

#define	NLACP_MODES	(sizeof (lacp_modes) / sizeof (dladm_aggr_lacpmode_t))

typedef struct dladm_aggr_lacptimer_s {
	char		*lt_str;
	aggr_lacp_timer_t lt_id;
} dladm_aggr_lacptimer_t;

static dladm_aggr_lacptimer_t lacp_timers[] = {
	{"short", AGGR_LACP_TIMER_SHORT},
	{"long", AGGR_LACP_TIMER_LONG}};

#define	NLACP_TIMERS	(sizeof (lacp_timers) / sizeof (dladm_aggr_lacptimer_t))

typedef struct dladm_aggr_port_state {
	char			*state_str;
	aggr_port_state_t	state_id;
} dladm_aggr_port_state_t;

static dladm_aggr_port_state_t port_states[] = {
	{"standby", AGGR_PORT_STATE_STANDBY },
	{"attached", AGGR_PORT_STATE_ATTACHED }
};

#define	NPORT_STATES	\
	(sizeof (port_states) / sizeof (dladm_aggr_port_state_t))

typedef struct delete_db_state {
	uint32_t	ds_key;
	boolean_t	ds_found;
} delete_db_state_t;

typedef struct modify_db_state {
	uint32_t		us_key;
	uint32_t		us_mask;
	dladm_aggr_modify_attr_t *us_attr_new;
	dladm_aggr_modify_attr_t *us_attr_old;
	boolean_t		us_found;
} modify_db_state_t;

typedef struct add_db_state {
	dladm_aggr_grp_attr_db_t *as_attr;
	boolean_t	as_found;
} add_db_state_t;

static int i_dladm_aggr_fput_grp(FILE *, dladm_aggr_grp_attr_db_t *);

static int
i_dladm_aggr_strioctl(int fd, int cmd, void *ptr, int ilen)
{
	struct strioctl str;

	str.ic_cmd = cmd;
	str.ic_timout = 0;
	str.ic_len = ilen;
	str.ic_dp = ptr;

	return (ioctl(fd, I_STR, &str));
}

/*
 * Open and lock the aggregation configuration file lock. The lock is
 * acquired as a reader (F_RDLCK) or writer (F_WRLCK).
 */
static int
i_dladm_aggr_lock_db(short type)
{
	int lock_fd;
	struct flock lock;
	int errno_save;

	if ((lock_fd = open(DLADM_AGGR_DB_LOCK, O_RDWR | O_CREAT | O_TRUNC,
	    DLADM_AGGR_DB_PERMS)) < 0)
		return (-1);

	lock.l_type = type;
	lock.l_whence = SEEK_SET;
	lock.l_start = 0;
	lock.l_len = 0;

	if (fcntl(lock_fd, F_SETLKW, &lock) < 0) {
		errno_save = errno;
		(void) close(lock_fd);
		(void) unlink(DLADM_AGGR_DB_LOCK);
		errno = errno_save;
		return (-1);
	}
	return (lock_fd);
}

/*
 * Unlock and close the specified file.
 */
static void
i_dladm_aggr_unlock_db(int fd)
{
	struct flock lock;

	if (fd < 0)
		return;

	lock.l_type = F_UNLCK;
	lock.l_whence = SEEK_SET;
	lock.l_start = 0;
	lock.l_len = 0;

	(void) fcntl(fd, F_SETLKW, &lock);
	(void) close(fd);
	(void) unlink(DLADM_AGGR_DB_LOCK);
}

/*
 * Walk through the groups defined on the system and for each group <grp>,
 * invoke <fn>(<arg>, <grp>);
 * Terminate the walk if at any time <fn> returns non-NULL value
 */
int
dladm_aggr_walk(int (*fn)(void *, dladm_aggr_grp_attr_t *), void *arg)
{
	laioc_info_t *ioc;
	laioc_info_group_t *grp;
	laioc_info_port_t *port;
	dladm_aggr_grp_attr_t attr;
	int rc, i, j, bufsize, fd;
	char *where;

	if ((fd = open(DLADM_AGGR_DEV, O_RDWR)) == -1)
		return (-1);

	bufsize = MIN_INFO_SIZE;
	ioc = (laioc_info_t *)calloc(1, bufsize);
	if (ioc == NULL) {
		(void) close(fd);
		errno = ENOMEM;
		return (-1);
	}

tryagain:
	rc = i_dladm_aggr_strioctl(fd, LAIOC_INFO, ioc, bufsize);

	if (rc != 0) {
		if (errno == ENOSPC) {
			/*
			 * The LAIOC_INFO call failed due to a short
			 * buffer. Reallocate the buffer and try again.
			 */
			bufsize *= 2;
			if (bufsize <= MAX_INFO_SIZE) {
				ioc = (laioc_info_t *)realloc(ioc, bufsize);
				if (ioc != NULL) {
					bzero(ioc, sizeof (bufsize));
					goto tryagain;
				}
			}
		}
		goto bail;
	}

	/*
	 * Go through each group returned by the aggregation driver.
	 */
	where = (char *)(ioc + 1);
	for (i = 0; i < ioc->li_ngroups; i++) {
		/* LINTED E_BAD_PTR_CAST_ALIGN */
		grp = (laioc_info_group_t *)where;

		attr.lg_key = grp->lg_key;
		attr.lg_nports = grp->lg_nports;
		attr.lg_policy = grp->lg_policy;
		attr.lg_lacp_mode = grp->lg_lacp_mode;
		attr.lg_lacp_timer = grp->lg_lacp_timer;

		bcopy(grp->lg_mac, attr.lg_mac, ETHERADDRL);
		attr.lg_mac_fixed = grp->lg_mac_fixed;

		attr.lg_ports = malloc(grp->lg_nports *
		    sizeof (dladm_aggr_port_attr_t));
		if (attr.lg_ports == NULL) {
			errno = ENOMEM;
			goto bail;
		}

		where = (char *)(grp + 1);

		/*
		 * Go through each port that is part of the group.
		 */
		for (j = 0; j < grp->lg_nports; j++) {
			/* LINTED E_BAD_PTR_CAST_ALIGN */
			port = (laioc_info_port_t *)where;

			bcopy(port->lp_devname, attr.lg_ports[j].lp_devname,
			    MAXNAMELEN + 1);
			bcopy(port->lp_mac, attr.lg_ports[j].lp_mac,
			    ETHERADDRL);
			attr.lg_ports[j].lp_state = port->lp_state;
			attr.lg_ports[j].lp_lacp_state = port->lp_lacp_state;

			where = (char *)(port + 1);
		}

		rc = fn(arg, &attr);
		free(attr.lg_ports);
		if (rc != 0)
			goto bail;
	}

bail:
	free(ioc);
	(void) close(fd);
	return (rc);
}

/*
 * Parse one line of the link aggregation DB, and return the corresponding
 * group. Memory for the ports associated with the aggregation may be
 * allocated. It is the responsibility of the caller to free the lt_ports
 * aggregation group attribute.
 *
 * Returns -1 on parsing failure, or 0 on success.
 */
static int
i_dladm_aggr_parse_db(char *line, dladm_aggr_grp_attr_db_t *attr)
{
	char	*token;
	int	i;
	int	value;
	char	*endp = NULL;
	char	*lasts = NULL;

	bzero(attr, sizeof (*attr));

	/* key */
	if ((token = strtok_r(line, " \t", &lasts)) == NULL)
		goto failed;

	errno = 0;
	value = (int)strtol(token, &endp, 10);
	if (errno != 0 || *endp != '\0')
		goto failed;

	attr->lt_key = value;

	/* policy */
	if ((token = strtok_r(NULL, " \t", &lasts)) == NULL ||
	    !dladm_aggr_str2policy(token, &attr->lt_policy))
		goto failed;

	/* number of ports */
	if ((token = strtok_r(NULL, " \t", &lasts)) == NULL)
		return (-1);

	errno = 0;
	value = (int)strtol(token, &endp, 10);
	if (errno != 0 || *endp != '\0')
		goto failed;

	attr->lt_nports = value;

	/* ports */
	if ((attr->lt_ports = malloc(attr->lt_nports *
	    sizeof (dladm_aggr_port_attr_db_t))) == NULL)
		goto failed;

	for (i = 0; i < attr->lt_nports; i++) {
		char *where, *devname;

		/* port */
		if ((token = strtok_r(NULL, ", \t\n", &lasts)) == NULL)
			goto failed;

		/*
		 * device name: In a previous version of this file, a port
		 * number could be specified using <devname>/<portnum>.
		 * This syntax is unecessary and obsolete.
		 */
		if ((devname = strtok_r(token, "/", &where)) == NULL)
			goto failed;
		if (strlcpy(attr->lt_ports[i].lp_devname, devname,
		    MAXNAMELEN) >= MAXNAMELEN)
			goto failed;
	}

	/* unicast MAC address */
	if ((token = strtok_r(NULL, " \t\n", &lasts)) == NULL ||
	    !dladm_aggr_str2macaddr(token, &attr->lt_mac_fixed,
	    attr->lt_mac))
		goto failed;

	/* LACP mode */
	if ((token = strtok_r(NULL, " \t\n", &lasts)) == NULL ||
	    !dladm_aggr_str2lacpmode(token, &attr->lt_lacp_mode))
		attr->lt_lacp_mode = AGGR_LACP_OFF;

	/* LACP timer */
	if ((token = strtok_r(NULL, " \t\n", &lasts)) == NULL ||
	    !dladm_aggr_str2lacptimer(token, &attr->lt_lacp_timer))
		attr->lt_lacp_timer = AGGR_LACP_TIMER_SHORT;

	return (0);

failed:
	free(attr->lt_ports);
	attr->lt_ports = NULL;
	return (-1);
}

/*
 * Walk through the groups defined in the DB and for each group <grp>,
 * invoke <fn>(<arg>, <grp>);
 */
static dladm_status_t
i_dladm_aggr_walk_db(dladm_status_t (*fn)(void *, dladm_aggr_grp_attr_db_t *),
    void *arg, const char *root)
{
	FILE *fp;
	char line[MAXLINELEN];
	dladm_aggr_grp_attr_db_t attr;
	char *db_file;
	char db_file_buf[MAXPATHLEN];
	int lock_fd;
	dladm_status_t status = DLADM_STATUS_OK;

	if (root == NULL) {
		db_file = DLADM_AGGR_DB;
	} else {
		(void) snprintf(db_file_buf, MAXPATHLEN, "%s%s", root,
		    DLADM_AGGR_DB);
		db_file = db_file_buf;
	}

	lock_fd = i_dladm_aggr_lock_db(F_RDLCK);

	if ((fp = fopen(db_file, "r")) == NULL) {
		status = dladm_errno2status(errno);
		i_dladm_aggr_unlock_db(lock_fd);
		return (status);
	}

	bzero(&attr, sizeof (attr));

	while (fgets(line, MAXLINELEN, fp) != NULL) {
		/* skip comments */
		if (BLANK_LINE(line))
			continue;

		if (i_dladm_aggr_parse_db(line, &attr) != 0) {
			status = DLADM_STATUS_REPOSITORYINVAL;
			goto done;
		}

		if ((status = fn(arg, &attr)) != DLADM_STATUS_OK)
			goto done;

		free(attr.lt_ports);
		attr.lt_ports = NULL;
	}

done:
	free(attr.lt_ports);
	(void) fclose(fp);
	i_dladm_aggr_unlock_db(lock_fd);
	return (status);
}

/*
 * Send an add or remove command to the link aggregation driver.
 */
static dladm_status_t
i_dladm_aggr_add_rem_sys(dladm_aggr_grp_attr_db_t *attr, int cmd)
{
	int i, rc, fd, len;
	laioc_add_rem_t *iocp;
	laioc_port_t *ports;
	dladm_status_t status = DLADM_STATUS_OK;

	len = sizeof (*iocp) + attr->lt_nports * sizeof (laioc_port_t);
	iocp = malloc(len);
	if (iocp == NULL) {
		status = DLADM_STATUS_NOMEM;
		goto done;
	}

	iocp->la_key = attr->lt_key;
	iocp->la_nports = attr->lt_nports;
	ports = (laioc_port_t *)(iocp + 1);

	for (i = 0; i < attr->lt_nports; i++) {
		if (strlcpy(ports[i].lp_devname,
		    attr->lt_ports[i].lp_devname,
		    MAXNAMELEN) >= MAXNAMELEN) {
			status = DLADM_STATUS_BADARG;
			goto done;
		}
	}

	if ((fd = open(DLADM_AGGR_DEV, O_RDWR)) < 0) {
		status = dladm_errno2status(errno);
		goto done;
	}

	rc = i_dladm_aggr_strioctl(fd, cmd, iocp, len);
	if (rc < 0) {
		if (errno == EINVAL)
			status = DLADM_STATUS_LINKINVAL;
		else
			status = dladm_errno2status(errno);
	}

	(void) close(fd);

done:
	free(iocp);
	return (status);
}

/*
 * Send a modify command to the link aggregation driver.
 */
static dladm_status_t
i_dladm_aggr_modify_sys(uint32_t key, uint32_t mask,
    dladm_aggr_modify_attr_t *attr)
{
	int rc, fd;
	laioc_modify_t ioc;
	dladm_status_t status = DLADM_STATUS_OK;

	ioc.lu_key = key;

	ioc.lu_modify_mask = 0;
	if (mask & DLADM_AGGR_MODIFY_POLICY)
		ioc.lu_modify_mask |= LAIOC_MODIFY_POLICY;
	if (mask & DLADM_AGGR_MODIFY_MAC)
		ioc.lu_modify_mask |= LAIOC_MODIFY_MAC;
	if (mask & DLADM_AGGR_MODIFY_LACP_MODE)
		ioc.lu_modify_mask |= LAIOC_MODIFY_LACP_MODE;
	if (mask & DLADM_AGGR_MODIFY_LACP_TIMER)
		ioc.lu_modify_mask |= LAIOC_MODIFY_LACP_TIMER;

	ioc.lu_policy = attr->ld_policy;
	ioc.lu_mac_fixed = attr->ld_mac_fixed;
	bcopy(attr->ld_mac, ioc.lu_mac, ETHERADDRL);
	ioc.lu_lacp_mode = attr->ld_lacp_mode;
	ioc.lu_lacp_timer = attr->ld_lacp_timer;

	if ((fd = open(DLADM_AGGR_DEV, O_RDWR)) < 0)
		return (dladm_errno2status(errno));

	rc = i_dladm_aggr_strioctl(fd, LAIOC_MODIFY, &ioc, sizeof (ioc));
	if (rc < 0) {
		if (errno == EINVAL)
			status = DLADM_STATUS_MACADDRINVAL;
		else
			status = dladm_errno2status(errno);
	}

	(void) close(fd);
	return (status);
}

/*
 * Send a create command to the link aggregation driver.
 */
static dladm_status_t
i_dladm_aggr_create_sys(int fd, dladm_aggr_grp_attr_db_t *attr)
{
	int i, rc, len;
	laioc_create_t *iocp;
	laioc_port_t *ports;
	dladm_status_t status = DLADM_STATUS_OK;

	len = sizeof (*iocp) + attr->lt_nports * sizeof (laioc_port_t);
	iocp = malloc(len);
	if (iocp == NULL)
		return (DLADM_STATUS_NOMEM);

	iocp->lc_key = attr->lt_key;
	iocp->lc_nports = attr->lt_nports;
	iocp->lc_policy = attr->lt_policy;
	iocp->lc_lacp_mode = attr->lt_lacp_mode;
	iocp->lc_lacp_timer = attr->lt_lacp_timer;

	ports = (laioc_port_t *)(iocp + 1);

	for (i = 0; i < attr->lt_nports; i++) {
		if (strlcpy(ports[i].lp_devname,
		    attr->lt_ports[i].lp_devname,
		    MAXNAMELEN) >= MAXNAMELEN) {
			free(iocp);
			return (DLADM_STATUS_BADARG);
		}
	}

	if (attr->lt_mac_fixed &&
	    ((bcmp(zero_mac, attr->lt_mac, ETHERADDRL) == 0) ||
	    (attr->lt_mac[0] & 0x01))) {
		free(iocp);
		return (DLADM_STATUS_MACADDRINVAL);
	}

	bcopy(attr->lt_mac, iocp->lc_mac, ETHERADDRL);
	iocp->lc_mac_fixed = attr->lt_mac_fixed;

	rc = i_dladm_aggr_strioctl(fd, LAIOC_CREATE, iocp, len);
	if (rc < 0)
		status = DLADM_STATUS_LINKINVAL;

	free(iocp);
	return (status);
}

/*
 * Invoked to bring up a link aggregation group.
 */
static dladm_status_t
i_dladm_aggr_up(void *arg, dladm_aggr_grp_attr_db_t *attr)
{
	dladm_aggr_up_t	*up = (dladm_aggr_up_t *)arg;
	dladm_status_t	status;

	if (up->lu_key != 0 && up->lu_key != attr->lt_key)
		return (DLADM_STATUS_OK);

	up->lu_found = B_TRUE;

	status = i_dladm_aggr_create_sys(up->lu_fd, attr);
	if (status != DLADM_STATUS_OK && up->lu_key != 0)
		return (status);

	return (DLADM_STATUS_OK);
}

/*
 * Bring up a link aggregation group or all of them if the key is zero.
 * If key is 0, walk may terminate early if any of the links fail
 */
dladm_status_t
dladm_aggr_up(uint32_t key, const char *root)
{
	dladm_aggr_up_t up;
	dladm_status_t status;

	if ((up.lu_fd = open(DLADM_AGGR_DEV, O_RDWR)) < 0)
		return (dladm_errno2status(errno));

	up.lu_key = key;
	up.lu_found = B_FALSE;

	status = i_dladm_aggr_walk_db(i_dladm_aggr_up, &up, root);
	if (status != DLADM_STATUS_OK) {
		(void) close(up.lu_fd);
		return (status);
	}
	(void) close(up.lu_fd);

	/*
	 * only return error if user specified key and key was
	 * not found
	 */
	if (!up.lu_found && key != 0)
		return (DLADM_STATUS_NOTFOUND);

	return (DLADM_STATUS_OK);
}
/*
 * Send a delete command to the link aggregation driver.
 */
static int
i_dladm_aggr_delete_sys(int fd, dladm_aggr_grp_attr_t *attr)
{
	laioc_delete_t ioc;

	ioc.ld_key = attr->lg_key;

	return (i_dladm_aggr_strioctl(fd, LAIOC_DELETE, &ioc, sizeof (ioc)));
}

/*
 * Invoked to bring down a link aggregation group.
 */
static int
i_dladm_aggr_down(void *arg, dladm_aggr_grp_attr_t *attr)
{
	dladm_aggr_down_t *down = (dladm_aggr_down_t *)arg;
	int fd, errno_save;

	if (down->ld_key != 0 && down->ld_key != attr->lg_key)
		return (0);

	down->ld_found = B_TRUE;

	if ((fd = open(DLADM_AGGR_DEV, O_RDWR)) < 0)
		return (-1);

	if (i_dladm_aggr_delete_sys(fd, attr) < 0 && down->ld_key != 0) {
		errno_save = errno;
		(void) close(fd);
		errno = errno_save;
		return (-1);
	}

	(void) close(fd);
	return (0);
}

/*
 * Bring down a link aggregation group or all of them if the key is zero.
 * If key is 0, walk may terminate early if any of the links fail
 */
dladm_status_t
dladm_aggr_down(uint32_t key)
{
	dladm_aggr_down_t down;

	down.ld_key = key;
	down.ld_found = B_FALSE;

	if (dladm_aggr_walk(i_dladm_aggr_down, &down) < 0)
		return (dladm_errno2status(errno));

	/*
	 * only return error if user specified key and key was
	 * not found
	 */
	if (!down.ld_found && key != 0)
		return (DLADM_STATUS_NOTFOUND);

	return (DLADM_STATUS_OK);
}

/*
 * For each group <grp> found in the DB, invokes <fn>(<grp>, <arg>).
 *
 * The following values can be returned by <fn>():
 *
 * -1: an error occured. This will cause the walk to be terminated,
 *     and the original DB file to be preserved.
 *
 *  0: success and write. The walker will write the contents of
 *     the attribute passed as argument to <fn>(), and continue walking
 *     the entries found in the DB.
 *
 *  1: skip. The walker should not write the contents of the current
 *     group attributes to the new DB, but should continue walking
 *     the entries found in the DB.
 */
static dladm_status_t
i_dladm_aggr_walk_rw_db(int (*fn)(void *, dladm_aggr_grp_attr_db_t *),
    void *arg, const char *root)
{
	FILE *fp, *nfp;
	int nfd, fn_rc, lock_fd;
	char line[MAXLINELEN];
	dladm_aggr_grp_attr_db_t attr;
	char *db_file, *tmp_db_file;
	char db_file_buf[MAXPATHLEN];
	char tmp_db_file_buf[MAXPATHLEN];
	dladm_status_t status;

	if (root == NULL) {
		db_file = DLADM_AGGR_DB;
		tmp_db_file = DLADM_AGGR_DB_TMP;
	} else {
		(void) snprintf(db_file_buf, MAXPATHLEN, "%s%s", root,
		    DLADM_AGGR_DB);
		(void) snprintf(tmp_db_file_buf, MAXPATHLEN, "%s%s", root,
		    DLADM_AGGR_DB_TMP);
		db_file = db_file_buf;
		tmp_db_file = tmp_db_file_buf;
	}

	if ((lock_fd = i_dladm_aggr_lock_db(F_WRLCK)) < 0)
		return (dladm_errno2status(errno));

	if ((fp = fopen(db_file, "r")) == NULL) {
		status = dladm_errno2status(errno);
		i_dladm_aggr_unlock_db(lock_fd);
		return (status);
	}

	if ((nfd = open(tmp_db_file, O_WRONLY|O_CREAT|O_TRUNC,
	    DLADM_AGGR_DB_PERMS)) == -1) {
		status = dladm_errno2status(errno);
		(void) fclose(fp);
		i_dladm_aggr_unlock_db(lock_fd);
		return (status);
	}

	if ((nfp = fdopen(nfd, "w")) == NULL) {
		status = dladm_errno2status(errno);
		(void) close(nfd);
		(void) fclose(fp);
		(void) unlink(tmp_db_file);
		i_dladm_aggr_unlock_db(lock_fd);
		return (status);
	}

	attr.lt_ports = NULL;

	while (fgets(line, MAXLINELEN, fp) != NULL) {

		/* skip comments */
		if (BLANK_LINE(line)) {
			if (fputs(line, nfp) == EOF) {
				status = dladm_errno2status(errno);
				goto failed;
			}
			continue;
		}

		if (i_dladm_aggr_parse_db(line, &attr) != 0) {
			status = DLADM_STATUS_REPOSITORYINVAL;
			goto failed;
		}

		fn_rc = fn(arg, &attr);

		switch (fn_rc) {
		case -1:
			/* failure, stop walking */
			status = dladm_errno2status(errno);
			goto failed;
		case 0:
			/*
			 * Success, write group attributes, which could
			 * have been modified by fn().
			 */
			if (i_dladm_aggr_fput_grp(nfp, &attr) != 0) {
				status = dladm_errno2status(errno);
				goto failed;
			}
			break;
		case 1:
			/* skip current group */
			break;
		}

		free(attr.lt_ports);
		attr.lt_ports = NULL;
	}

	if (getuid() == 0 || geteuid() == 0) {
		if (fchmod(nfd, DLADM_AGGR_DB_PERMS) == -1) {
			status = dladm_errno2status(errno);
			goto failed;
		}

		if (fchown(nfd, DLADM_AGGR_DB_OWNER,
		    DLADM_AGGR_DB_GROUP) == -1) {
			status = dladm_errno2status(errno);
			goto failed;
		}
	}

	if (fflush(nfp) == EOF) {
		status = dladm_errno2status(errno);
		goto failed;
	}

	(void) fclose(fp);
	(void) fclose(nfp);

	if (rename(tmp_db_file, db_file) == -1) {
		status = dladm_errno2status(errno);
		(void) unlink(tmp_db_file);
		i_dladm_aggr_unlock_db(lock_fd);
		return (status);
	}

	i_dladm_aggr_unlock_db(lock_fd);
	return (DLADM_STATUS_OK);

failed:
	free(attr.lt_ports);
	(void) fclose(fp);
	(void) fclose(nfp);
	(void) unlink(tmp_db_file);
	i_dladm_aggr_unlock_db(lock_fd);

	return (status);
}

/*
 * Remove an entry from the DB.
 */
static int
i_dladm_aggr_del_db_fn(void *arg, dladm_aggr_grp_attr_db_t *grp)
{
	delete_db_state_t *state = arg;

	if (grp->lt_key != state->ds_key)
		return (0);

	state->ds_found = B_TRUE;

	/* don't save matching group */
	return (1);
}

static dladm_status_t
i_dladm_aggr_del_db(dladm_aggr_grp_attr_db_t *attr, const char *root)
{
	delete_db_state_t state;
	dladm_status_t status;

	state.ds_key = attr->lt_key;
	state.ds_found = B_FALSE;

	status = i_dladm_aggr_walk_rw_db(i_dladm_aggr_del_db_fn, &state, root);
	if (status != DLADM_STATUS_OK)
		return (status);

	if (!state.ds_found)
		return (DLADM_STATUS_NOTFOUND);

	return (DLADM_STATUS_OK);
}

/*
 * Modify the properties of an existing group in the DB.
 */
static int
i_dladm_aggr_modify_db_fn(void *arg, dladm_aggr_grp_attr_db_t *grp)
{
	modify_db_state_t *state = arg;
	dladm_aggr_modify_attr_t *new_attr = state->us_attr_new;
	dladm_aggr_modify_attr_t *old_attr = state->us_attr_old;

	if (grp->lt_key != state->us_key)
		return (0);

	state->us_found = B_TRUE;

	if (state->us_mask & DLADM_AGGR_MODIFY_POLICY) {
		if (old_attr != NULL)
			old_attr->ld_policy = grp->lt_policy;
		grp->lt_policy = new_attr->ld_policy;
	}

	if (state->us_mask & DLADM_AGGR_MODIFY_MAC) {
		if (old_attr != NULL) {
			old_attr->ld_mac_fixed = grp->lt_mac_fixed;
			bcopy(grp->lt_mac, old_attr->ld_mac, ETHERADDRL);
		}
		grp->lt_mac_fixed = new_attr->ld_mac_fixed;
		bcopy(new_attr->ld_mac, grp->lt_mac, ETHERADDRL);
	}

	if (state->us_mask & DLADM_AGGR_MODIFY_LACP_MODE) {
		if (old_attr != NULL)
			old_attr->ld_lacp_mode = grp->lt_lacp_mode;
		grp->lt_lacp_mode = new_attr->ld_lacp_mode;
	}

	if (state->us_mask & DLADM_AGGR_MODIFY_LACP_TIMER) {
		if (old_attr != NULL)
			old_attr->ld_lacp_timer = grp->lt_lacp_timer;
		grp->lt_lacp_timer = new_attr->ld_lacp_timer;
	}

	/* save modified group */
	return (0);
}

static dladm_status_t
i_dladm_aggr_modify_db(uint32_t key, uint32_t mask,
    dladm_aggr_modify_attr_t *new, dladm_aggr_modify_attr_t *old,
    const char *root)
{
	modify_db_state_t state;
	dladm_status_t status;

	state.us_key = key;
	state.us_mask = mask;
	state.us_attr_new = new;
	state.us_attr_old = old;
	state.us_found = B_FALSE;

	if ((status = i_dladm_aggr_walk_rw_db(i_dladm_aggr_modify_db_fn,
	    &state, root)) != DLADM_STATUS_OK) {
		return (status);
	}

	if (!state.us_found)
		return (DLADM_STATUS_NOTFOUND);

	return (DLADM_STATUS_OK);
}

/*
 * Add ports to an existing group in the DB.
 */
static int
i_dladm_aggr_add_db_fn(void *arg, dladm_aggr_grp_attr_db_t *grp)
{
	add_db_state_t *state = arg;
	dladm_aggr_grp_attr_db_t *attr = state->as_attr;
	void *ports;
	int i, j;

	if (grp->lt_key != attr->lt_key)
		return (0);

	state->as_found = B_TRUE;

	/* are any of the ports to be added already members of the group? */
	for (i = 0; i < grp->lt_nports; i++) {
		for (j = 0; j < attr->lt_nports; j++) {
			if (strcmp(grp->lt_ports[i].lp_devname,
			    attr->lt_ports[j].lp_devname) == 0) {
				errno = EEXIST;
				return (-1);
			}
		}
	}

	/* add groups specified by attr to grp */
	ports = realloc(grp->lt_ports, (grp->lt_nports +
	    attr->lt_nports) * sizeof (dladm_aggr_port_attr_db_t));
	if (ports == NULL)
		return (-1);
	grp->lt_ports = ports;

	for (i = 0; i < attr->lt_nports; i++) {
		if (strlcpy(grp->lt_ports[grp->lt_nports + i].lp_devname,
		    attr->lt_ports[i].lp_devname, MAXNAMELEN + 1) >=
		    MAXNAMELEN + 1)
			return (-1);
	}

	grp->lt_nports += attr->lt_nports;

	/* save modified group */
	return (0);
}

static dladm_status_t
i_dladm_aggr_add_db(dladm_aggr_grp_attr_db_t *attr, const char *root)
{
	add_db_state_t state;
	dladm_status_t status;

	state.as_attr = attr;
	state.as_found = B_FALSE;

	status = i_dladm_aggr_walk_rw_db(i_dladm_aggr_add_db_fn, &state, root);
	if (status != DLADM_STATUS_OK)
		return (status);

	if (!state.as_found)
		return (DLADM_STATUS_NOTFOUND);

	return (DLADM_STATUS_OK);
}

/*
 * Remove ports from an existing group in the DB.
 */

typedef struct remove_db_state {
	dladm_aggr_grp_attr_db_t *rs_attr;
	boolean_t	rs_found;
} remove_db_state_t;

static int
i_dladm_aggr_remove_db_fn(void *arg, dladm_aggr_grp_attr_db_t *grp)
{
	remove_db_state_t *state = (remove_db_state_t *)arg;
	dladm_aggr_grp_attr_db_t *attr = state->rs_attr;
	int i, j, k, nremoved;
	boolean_t match;

	if (grp->lt_key != attr->lt_key)
		return (0);

	state->rs_found = B_TRUE;

	/* remove the ports specified by attr from the group */
	nremoved = 0;
	k = 0;
	for (i = 0; i < grp->lt_nports; i++) {
		match = B_FALSE;
		for (j = 0; j < attr->lt_nports && !match; j++) {
			match = (strcmp(grp->lt_ports[i].lp_devname,
			    attr->lt_ports[j].lp_devname) == 0);
		}
		if (match)
			nremoved++;
		else
			grp->lt_ports[k++] = grp->lt_ports[i];
	}

	if (nremoved != attr->lt_nports) {
		errno = ENOENT;
		return (-1);
	}

	grp->lt_nports -= nremoved;

	/* save modified group */
	return (0);
}

static dladm_status_t
i_dladm_aggr_remove_db(dladm_aggr_grp_attr_db_t *attr, const char *root)
{
	remove_db_state_t state;
	dladm_status_t status;

	state.rs_attr = attr;
	state.rs_found = B_FALSE;

	status = i_dladm_aggr_walk_rw_db(i_dladm_aggr_remove_db_fn,
	    &state, root);
	if (status != DLADM_STATUS_OK)
		return (status);

	if (!state.rs_found)
		return (DLADM_STATUS_NOTFOUND);

	return (DLADM_STATUS_OK);
}

/*
 * Given a policy string, return a policy mask. Returns B_TRUE on
 * success, or B_FALSE if an error occured during parsing.
 */
boolean_t
dladm_aggr_str2policy(const char *str, uint32_t *policy)
{
	int i;
	policy_t *pol;
	char *token = NULL;
	char *lasts;

	*policy = 0;

	while ((token = strtok_r((token == NULL) ? (char *)str : NULL, ",",
	    &lasts)) != NULL) {
		for (i = 0; i < NPOLICIES; i++) {
			pol = &policies[i];
			if (strcasecmp(token, pol->pol_name) == 0) {
				*policy |= pol->policy;
				break;
			}
		}
		if (i == NPOLICIES)
			return (B_FALSE);
	}

	return (B_TRUE);
}

/*
 * Given a policy mask, returns a printable string, or NULL if the
 * policy mask is invalid. It is the responsibility of the caller to
 * free the returned string after use.
 */
char *
dladm_aggr_policy2str(uint32_t policy, char *str)
{
	int i, npolicies = 0;
	policy_t *pol;

	str[0] = '\0';

	for (i = 0; i < NPOLICIES; i++) {
		pol = &policies[i];
		if ((policy & pol->policy) != 0) {
			npolicies++;
			if (npolicies > 1)
				(void) strcat(str, ",");
			(void) strcat(str, pol->pol_name);
		}
	}

	return (str);
}

/*
 * Given a MAC address string, return the MAC address in the mac_addr
 * array. If the MAC address was not explicitly specified, i.e. is
 * equal to 'auto', zero out mac-addr and set mac_fixed to B_TRUE.
 * Return B_FALSE if a syntax error was encountered, B_FALSE otherwise.
 */
boolean_t
dladm_aggr_str2macaddr(const char *str, boolean_t *mac_fixed, uchar_t *mac_addr)
{
	uchar_t *conv_str;
	int mac_len;

	*mac_fixed = (strcmp(str, "auto") != 0);
	if (!*mac_fixed) {
		bzero(mac_addr, ETHERADDRL);
		return (B_TRUE);
	}

	conv_str = _link_aton(str, &mac_len);
	if (conv_str == NULL)
		return (B_FALSE);

	if (mac_len != ETHERADDRL) {
		free(conv_str);
		return (B_FALSE);
	}

	if ((bcmp(zero_mac, conv_str, ETHERADDRL) == 0) ||
	    (conv_str[0] & 0x01)) {
		free(conv_str);
		return (B_FALSE);
	}

	bcopy(conv_str, mac_addr, ETHERADDRL);
	free(conv_str);

	return (B_TRUE);
}

/*
 * Returns a string containing a printable representation of a MAC address.
 */
const char *
dladm_aggr_macaddr2str(unsigned char *mac, char *buf)
{
	static char unknown_mac[] = {0, 0, 0, 0, 0, 0};

	if (buf == NULL)
		return (NULL);

	if (bcmp(unknown_mac, mac, ETHERADDRL) == 0)
		return (gettext("<unknown>"));
	else
		return (_link_ntoa(mac, buf, ETHERADDRL, IFT_OTHER));
}

/*
 * Given a LACP mode string, find the corresponding LACP mode number. Returns
 * B_TRUE if a match was found, B_FALSE otherwise.
 */
boolean_t
dladm_aggr_str2lacpmode(const char *str, aggr_lacp_mode_t *lacp_mode)
{
	int i;
	dladm_aggr_lacpmode_t *mode;

	for (i = 0; i < NLACP_MODES; i++) {
		mode = &lacp_modes[i];
		if (strncasecmp(str, mode->mode_str,
		    strlen(mode->mode_str)) == 0) {
			*lacp_mode = mode->mode_id;
			return (B_TRUE);
		}
	}

	return (B_FALSE);
}

/*
 * Given a LACP mode number, returns a printable string, or NULL if the
 * LACP mode number is invalid.
 */
const char *
dladm_aggr_lacpmode2str(aggr_lacp_mode_t mode_id, char *buf)
{
	int i;
	dladm_aggr_lacpmode_t *mode;

	for (i = 0; i < NLACP_MODES; i++) {
		mode = &lacp_modes[i];
		if (mode->mode_id == mode_id) {
			(void) snprintf(buf, DLADM_STRSIZE, "%s",
			    mode->mode_str);
			return (buf);
		}
	}

	(void) strlcpy(buf, "unknown", DLADM_STRSIZE);
	return (buf);
}

/*
 * Given a LACP timer string, find the corresponding LACP timer number. Returns
 * B_TRUE if a match was found, B_FALSE otherwise.
 */
boolean_t
dladm_aggr_str2lacptimer(const char *str, aggr_lacp_timer_t *lacp_timer)
{
	int i;
	dladm_aggr_lacptimer_t *timer;

	for (i = 0; i < NLACP_TIMERS; i++) {
		timer = &lacp_timers[i];
		if (strncasecmp(str, timer->lt_str,
		    strlen(timer->lt_str)) == 0) {
			*lacp_timer = timer->lt_id;
			return (B_TRUE);
		}
	}

	return (B_FALSE);
}

/*
 * Given a LACP timer, returns a printable string, or NULL if the
 * LACP timer number is invalid.
 */
const char *
dladm_aggr_lacptimer2str(aggr_lacp_timer_t timer_id, char *buf)
{
	int i;
	dladm_aggr_lacptimer_t *timer;

	for (i = 0; i < NLACP_TIMERS; i++) {
		timer = &lacp_timers[i];
		if (timer->lt_id == timer_id) {
			(void) snprintf(buf, DLADM_STRSIZE, "%s",
			    timer->lt_str);
			return (buf);
		}
	}

	(void) strlcpy(buf, "unknown", DLADM_STRSIZE);
	return (buf);
}

const char *
dladm_aggr_portstate2str(aggr_port_state_t state_id, char *buf)
{
	int			i;
	dladm_aggr_port_state_t	*state;

	for (i = 0; i < NPORT_STATES; i++) {
		state = &port_states[i];
		if (state->state_id == state_id) {
			(void) snprintf(buf, DLADM_STRSIZE, "%s",
			    state->state_str);
			return (buf);
		}
	}

	(void) strlcpy(buf, "unknown", DLADM_STRSIZE);
	return (buf);
}

#define	FPRINTF_ERR(fcall) if ((fcall) < 0) return (-1);

/*
 * Write the attribute of a group to the specified file. Returns 0 on
 * success, -1 on failure.
 */
static int
i_dladm_aggr_fput_grp(FILE *fp, dladm_aggr_grp_attr_db_t *attr)
{
	int i;
	char addr_str[ETHERADDRL * 3];
	char buf[DLADM_STRSIZE];

	/* key, policy */
	FPRINTF_ERR(fprintf(fp, "%d\t%s\t", attr->lt_key,
	    dladm_aggr_policy2str(attr->lt_policy, buf)));

	/* number of ports, ports */
	FPRINTF_ERR(fprintf(fp, "%d\t", attr->lt_nports));
	for (i = 0; i < attr->lt_nports; i++) {
		if (i > 0)
			FPRINTF_ERR(fprintf(fp, ","));
		FPRINTF_ERR(fprintf(fp, "%s", attr->lt_ports[i].lp_devname));
	}
	FPRINTF_ERR(fprintf(fp, "\t"));

	/* MAC address */
	if (!attr->lt_mac_fixed) {
		FPRINTF_ERR(fprintf(fp, "auto"));
	} else {
		FPRINTF_ERR(fprintf(fp, "%s",
		    dladm_aggr_macaddr2str(attr->lt_mac, addr_str)));
	}
	FPRINTF_ERR(fprintf(fp, "\t"));

	FPRINTF_ERR(fprintf(fp, "%s\t",
	    dladm_aggr_lacpmode2str(attr->lt_lacp_mode, buf)));

	FPRINTF_ERR(fprintf(fp, "%s\n",
	    dladm_aggr_lacptimer2str(attr->lt_lacp_timer, buf)));

	return (0);
}

static dladm_status_t
i_dladm_aggr_create_db(dladm_aggr_grp_attr_db_t *attr, const char *root)
{
	FILE		*fp;
	char		line[MAXLINELEN];
	uint32_t	key;
	int 		lock_fd;
	char 		*db_file;
	char 		db_file_buf[MAXPATHLEN];
	char 		*endp = NULL;
	dladm_status_t	status;

	if (root == NULL) {
		db_file = DLADM_AGGR_DB;
	} else {
		(void) snprintf(db_file_buf, MAXPATHLEN, "%s%s", root,
		    DLADM_AGGR_DB);
		db_file = db_file_buf;
	}

	if ((lock_fd = i_dladm_aggr_lock_db(F_WRLCK)) < 0)
		return (dladm_errno2status(errno));

	if ((fp = fopen(db_file, "r+")) == NULL &&
	    (fp = fopen(db_file, "w")) == NULL) {
		status = dladm_errno2status(errno);
		i_dladm_aggr_unlock_db(lock_fd);
		return (status);
	}

	/* look for existing group with same key */
	while (fgets(line, MAXLINELEN, fp) != NULL) {
		char *holder, *lasts;

		/* skip comments */
		if (BLANK_LINE(line))
			continue;

		/* ignore corrupted lines */
		holder = strtok_r(line, " \t", &lasts);
		if (holder == NULL)
			continue;

		/* port number */
		errno = 0;
		key = (int)strtol(holder, &endp, 10);
		if (errno != 0 || *endp != '\0') {
			status = DLADM_STATUS_REPOSITORYINVAL;
			goto done;
		}

		if (key == attr->lt_key) {
			/* group with key already exists */
			status = DLADM_STATUS_EXIST;
			goto done;
		}
	}

	/*
	 * If we get here, we've verified that no existing group with
	 * the same key already exists. It's now time to add the
	 * new group to the DB.
	 */
	if (i_dladm_aggr_fput_grp(fp, attr) != 0) {
		status = dladm_errno2status(errno);
		goto done;
	}

	status = DLADM_STATUS_OK;

done:
	(void) fclose(fp);
	i_dladm_aggr_unlock_db(lock_fd);
	return (status);
}

/*
 * Create a new link aggregation group. Update the configuration
 * file and bring it up.
 */
dladm_status_t
dladm_aggr_create(uint32_t key, uint32_t nports,
    dladm_aggr_port_attr_db_t *ports, uint32_t policy, boolean_t mac_addr_fixed,
    uchar_t *mac_addr, aggr_lacp_mode_t lacp_mode, aggr_lacp_timer_t lacp_timer,
    boolean_t tempop, const char *root)
{
	dladm_aggr_grp_attr_db_t attr;
	dladm_status_t status;

	if (key == 0 || key > DLADM_AGGR_MAX_KEY)
		return (DLADM_STATUS_KEYINVAL);

	attr.lt_key = key;
	attr.lt_nports = nports;
	attr.lt_ports = ports;
	attr.lt_policy = policy;
	attr.lt_mac_fixed = mac_addr_fixed;
	if (attr.lt_mac_fixed)
		bcopy(mac_addr, attr.lt_mac, ETHERADDRL);
	else
		bzero(attr.lt_mac, ETHERADDRL);
	attr.lt_lacp_mode = lacp_mode;
	attr.lt_lacp_timer = lacp_timer;

	/* add the link aggregation group to the DB */
	if (!tempop) {
		status = i_dladm_aggr_create_db(&attr, root);
		if (status != DLADM_STATUS_OK)
			return (status);
	} else {
		dladm_aggr_up_t up;

		up.lu_key = key;
		up.lu_found = B_FALSE;
		up.lu_fd = open(DLADM_AGGR_DEV, O_RDWR);
		if (up.lu_fd < 0)
			return (dladm_errno2status(errno));

		status = i_dladm_aggr_up((void *)&up, &attr);
		(void) close(up.lu_fd);
		return (status);
	}

	/* bring up the link aggregation group */
	status = dladm_aggr_up(key, root);
	/*
	 * If the operation fails because the aggregation already exists,
	 * then only update the persistent configuration repository and
	 * return success.
	 */
	if (status == DLADM_STATUS_EXIST)
		status = DLADM_STATUS_OK;

	if (status != DLADM_STATUS_OK && !tempop)
		(void) i_dladm_aggr_del_db(&attr, root);

	return (status);
}

/*
 * Modify the parameters of an existing link aggregation group. Update
 * the configuration file and pass the changes to the kernel.
 */
dladm_status_t
dladm_aggr_modify(uint32_t key, uint32_t modify_mask, uint32_t policy,
    boolean_t mac_fixed, uchar_t *mac_addr, aggr_lacp_mode_t lacp_mode,
    aggr_lacp_timer_t lacp_timer, boolean_t tempop, const char *root)
{
	dladm_aggr_modify_attr_t new_attr, old_attr;
	dladm_status_t status;

	if (key == 0)
		return (DLADM_STATUS_KEYINVAL);

	if (modify_mask & DLADM_AGGR_MODIFY_POLICY)
		new_attr.ld_policy = policy;

	if (modify_mask & DLADM_AGGR_MODIFY_MAC) {
		new_attr.ld_mac_fixed = mac_fixed;
		bcopy(mac_addr, new_attr.ld_mac, ETHERADDRL);
	}

	if (modify_mask & DLADM_AGGR_MODIFY_LACP_MODE)
		new_attr.ld_lacp_mode = lacp_mode;

	if (modify_mask & DLADM_AGGR_MODIFY_LACP_TIMER)
		new_attr.ld_lacp_timer = lacp_timer;

	/* update the DB */
	if (!tempop && ((status = i_dladm_aggr_modify_db(key, modify_mask,
	    &new_attr, &old_attr, root)) != DLADM_STATUS_OK)) {
		return (status);
	}

	status = i_dladm_aggr_modify_sys(key, modify_mask, &new_attr);
	if (status != DLADM_STATUS_OK && !tempop) {
		(void) i_dladm_aggr_modify_db(key, modify_mask, &old_attr,
		    NULL, root);
	}

	return (status);
}

/*
 * Delete a previously created link aggregation group.
 */
dladm_status_t
dladm_aggr_delete(uint32_t key, boolean_t tempop, const char *root)
{
	dladm_aggr_grp_attr_db_t db_attr;
	dladm_status_t status;

	if (key == 0)
		return (DLADM_STATUS_KEYINVAL);

	if (tempop) {
		dladm_aggr_down_t down;
		dladm_aggr_grp_attr_t sys_attr;

		down.ld_key = key;
		down.ld_found = B_FALSE;
		sys_attr.lg_key = key;
		if (i_dladm_aggr_down((void *)&down, &sys_attr) < 0)
			return (dladm_errno2status(errno));
		else
			return (DLADM_STATUS_OK);
	} else {
		status = dladm_aggr_down(key);

		/*
		 * Only continue to delete the configuration repository
		 * either if we successfully delete the active aggregation
		 * or if the aggregation is not found.
		 */
		if (status != DLADM_STATUS_OK &&
		    status != DLADM_STATUS_NOTFOUND) {
			return (status);
		}
	}

	if (tempop)
		return (DLADM_STATUS_OK);

	db_attr.lt_key = key;
	return (i_dladm_aggr_del_db(&db_attr, root));
}

/*
 * Add one or more ports to an existing link aggregation.
 */
dladm_status_t
dladm_aggr_add(uint32_t key, uint32_t nports, dladm_aggr_port_attr_db_t *ports,
    boolean_t tempop, const char *root)
{
	dladm_aggr_grp_attr_db_t attr;
	dladm_status_t status;

	if (key == 0)
		return (DLADM_STATUS_KEYINVAL);

	bzero(&attr, sizeof (attr));
	attr.lt_key = key;
	attr.lt_nports = nports;
	attr.lt_ports = ports;

	if (!tempop &&
	    ((status = i_dladm_aggr_add_db(&attr, root)) != DLADM_STATUS_OK)) {
		return (status);
	}

	status = i_dladm_aggr_add_rem_sys(&attr, LAIOC_ADD);
	if (status != DLADM_STATUS_OK && !tempop)
		(void) i_dladm_aggr_remove_db(&attr, root);

	return (status);
}

/*
 * Remove one or more ports from an existing link aggregation.
 */
dladm_status_t
dladm_aggr_remove(uint32_t key, uint32_t nports,
    dladm_aggr_port_attr_db_t *ports, boolean_t tempop, const char *root)
{
	dladm_aggr_grp_attr_db_t attr;
	dladm_status_t status;

	if (key == 0)
		return (DLADM_STATUS_KEYINVAL);

	bzero(&attr, sizeof (attr));
	attr.lt_key = key;
	attr.lt_nports = nports;
	attr.lt_ports = ports;

	if (!tempop &&
	    ((status = i_dladm_aggr_remove_db(&attr, root)) !=
	    DLADM_STATUS_OK)) {
		return (status);
	}

	status = i_dladm_aggr_add_rem_sys(&attr, LAIOC_REMOVE);
	if (status != DLADM_STATUS_OK && !tempop)
		(void) i_dladm_aggr_add_db(&attr, root);

	return (status);
}