1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 #include <stdio.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <string.h>
32 #include <fcntl.h>
33 #include <unistd.h>
34 #include <stropts.h>
35 #include <stdlib.h>
36 #include <errno.h>
37 #include <strings.h>
38 #include <libintl.h>
39 #include <net/if_types.h>
40 #include <net/if_dl.h>
41 #include <libdlaggr.h>
42 #include <libdladm_impl.h>
43 
44 /*
45  * Link Aggregation Administration Library.
46  *
47  * This library is used by administration tools such as dladm(1M) to
48  * configure link aggregations.
49  *
50  * Link aggregation configuration information is saved in a text
51  * file of the following format:
52  *
53  * <db-file>	::= <groups>*
54  * <group>	::= <key> <sep> <policy> <sep> <nports> <sep> <ports> <sep>
55  *		      <mac> <sep> <lacp-mode> <sep> <lacp-timer>
56  * <sep>	::= ' ' | '\t'
57  * <key>	::= <number>
58  * <nports>	::= <number>
59  * <ports>	::= <port> <m-port>*
60  * <m-port>	::= ',' <port>
61  * <port>	::= <devname>
62  * <devname>	::= <string>
63  * <port-num>	::= <number>
64  * <policy>	::= <pol-level> <m-pol>*
65  * <m-pol>	::= ',' <pol-level>
66  * <pol-level>	::= 'L2' | 'L3' | 'L4'
67  * <mac>	::= 'auto' | <mac-addr>
68  * <mac-addr>	::= <hex> ':' <hex> ':' <hex> ':' <hex> ':' <hex> ':' <hex>
69  * <lacp-mode>	::= 'off' | 'active' | 'passive'
70  * <lacp-timer>	::= 'short' | 'long'
71  */
72 
73 #define	DLADM_AGGR_DEV		"/devices/pseudo/aggr@0:" AGGR_DEVNAME_CTL
74 #define	DLADM_AGGR_DB		"/etc/dladm/aggregation.conf"
75 #define	DLADM_AGGR_DB_TMP	"/etc/dladm/aggregation.conf.new"
76 #define	DLADM_AGGR_DB_LOCK	"/tmp/aggregation.conf.lock"
77 
78 #define	DLADM_AGGR_DB_PERMS	S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH
79 #define	DLADM_AGGR_DB_OWNER	15	/* "dladm" UID */
80 #define	DLADM_AGGR_DB_GROUP	3	/* "sys" GID */
81 
82 /*
83  * The largest configurable aggregation key.  Because by default the key is
84  * used as the DLPI device PPA and default VLAN PPA's are calculated as
85  * ((1000 * vid) + PPA), the largest key can't be > 999.
86  */
87 #define	DLADM_AGGR_MAX_KEY	999
88 
89 #define	BLANK_LINE(s)	((s[0] == '\0') || (s[0] == '#') || (s[0] == '\n'))
90 
91 /* Limits on buffer size for LAIOC_INFO request */
92 #define	MIN_INFO_SIZE (4*1024)
93 #define	MAX_INFO_SIZE (128*1024)
94 
95 #define	MAXPATHLEN	1024
96 
97 static uchar_t	zero_mac[] = {0, 0, 0, 0, 0, 0};
98 
99 /* configuration database entry */
100 typedef struct dladm_aggr_grp_attr_db {
101 	uint32_t	lt_key;
102 	uint32_t	lt_policy;
103 	uint32_t	lt_nports;
104 	dladm_aggr_port_attr_db_t *lt_ports;
105 	boolean_t	lt_mac_fixed;
106 	uchar_t		lt_mac[ETHERADDRL];
107 	aggr_lacp_mode_t lt_lacp_mode;
108 	aggr_lacp_timer_t lt_lacp_timer;
109 } dladm_aggr_grp_attr_db_t;
110 
111 typedef struct dladm_aggr_up {
112 	uint32_t	lu_key;
113 	boolean_t	lu_found;
114 	int		lu_fd;
115 } dladm_aggr_up_t;
116 
117 typedef struct dladm_aggr_down {
118 	uint32_t	ld_key;
119 	boolean_t	ld_found;
120 } dladm_aggr_down_t;
121 
122 typedef struct dladm_aggr_modify_attr {
123 	uint32_t	ld_policy;
124 	boolean_t	ld_mac_fixed;
125 	uchar_t		ld_mac[ETHERADDRL];
126 	aggr_lacp_mode_t ld_lacp_mode;
127 	aggr_lacp_timer_t ld_lacp_timer;
128 } dladm_aggr_modify_attr_t;
129 
130 typedef struct policy_s {
131 	char		*pol_name;
132 	uint32_t	policy;
133 } policy_t;
134 
135 static policy_t policies[] = {
136 	{"L2",		AGGR_POLICY_L2},
137 	{"L3",		AGGR_POLICY_L3},
138 	{"L4",		AGGR_POLICY_L4}};
139 
140 #define	NPOLICIES	(sizeof (policies) / sizeof (policy_t))
141 
142 typedef struct dladm_aggr_lacpmode_s {
143 	char		*mode_str;
144 	aggr_lacp_mode_t mode_id;
145 } dladm_aggr_lacpmode_t;
146 
147 static dladm_aggr_lacpmode_t lacp_modes[] = {
148 	{"off", AGGR_LACP_OFF},
149 	{"active", AGGR_LACP_ACTIVE},
150 	{"passive", AGGR_LACP_PASSIVE}};
151 
152 #define	NLACP_MODES	(sizeof (lacp_modes) / sizeof (dladm_aggr_lacpmode_t))
153 
154 typedef struct dladm_aggr_lacptimer_s {
155 	char		*lt_str;
156 	aggr_lacp_timer_t lt_id;
157 } dladm_aggr_lacptimer_t;
158 
159 static dladm_aggr_lacptimer_t lacp_timers[] = {
160 	{"short", AGGR_LACP_TIMER_SHORT},
161 	{"long", AGGR_LACP_TIMER_LONG}};
162 
163 #define	NLACP_TIMERS	(sizeof (lacp_timers) / sizeof (dladm_aggr_lacptimer_t))
164 
165 typedef struct dladm_aggr_port_state {
166 	char			*state_str;
167 	aggr_port_state_t	state_id;
168 } dladm_aggr_port_state_t;
169 
170 static dladm_aggr_port_state_t port_states[] = {
171 	{"standby", AGGR_PORT_STATE_STANDBY },
172 	{"attached", AGGR_PORT_STATE_ATTACHED }
173 };
174 
175 #define	NPORT_STATES	\
176 	(sizeof (port_states) / sizeof (dladm_aggr_port_state_t))
177 
178 typedef struct delete_db_state {
179 	uint32_t	ds_key;
180 	boolean_t	ds_found;
181 } delete_db_state_t;
182 
183 typedef struct modify_db_state {
184 	uint32_t		us_key;
185 	uint32_t		us_mask;
186 	dladm_aggr_modify_attr_t *us_attr_new;
187 	dladm_aggr_modify_attr_t *us_attr_old;
188 	boolean_t		us_found;
189 } modify_db_state_t;
190 
191 typedef struct add_db_state {
192 	dladm_aggr_grp_attr_db_t *as_attr;
193 	boolean_t	as_found;
194 } add_db_state_t;
195 
196 static int i_dladm_aggr_fput_grp(FILE *, dladm_aggr_grp_attr_db_t *);
197 
198 static int
199 i_dladm_aggr_strioctl(int fd, int cmd, void *ptr, int ilen)
200 {
201 	struct strioctl str;
202 
203 	str.ic_cmd = cmd;
204 	str.ic_timout = 0;
205 	str.ic_len = ilen;
206 	str.ic_dp = ptr;
207 
208 	return (ioctl(fd, I_STR, &str));
209 }
210 
211 /*
212  * Open and lock the aggregation configuration file lock. The lock is
213  * acquired as a reader (F_RDLCK) or writer (F_WRLCK).
214  */
215 static int
216 i_dladm_aggr_lock_db(short type)
217 {
218 	int lock_fd;
219 	struct flock lock;
220 	int errno_save;
221 
222 	if ((lock_fd = open(DLADM_AGGR_DB_LOCK, O_RDWR | O_CREAT | O_TRUNC,
223 	    DLADM_AGGR_DB_PERMS)) < 0)
224 		return (-1);
225 
226 	lock.l_type = type;
227 	lock.l_whence = SEEK_SET;
228 	lock.l_start = 0;
229 	lock.l_len = 0;
230 
231 	if (fcntl(lock_fd, F_SETLKW, &lock) < 0) {
232 		errno_save = errno;
233 		(void) close(lock_fd);
234 		(void) unlink(DLADM_AGGR_DB_LOCK);
235 		errno = errno_save;
236 		return (-1);
237 	}
238 	return (lock_fd);
239 }
240 
241 /*
242  * Unlock and close the specified file.
243  */
244 static void
245 i_dladm_aggr_unlock_db(int fd)
246 {
247 	struct flock lock;
248 
249 	if (fd < 0)
250 		return;
251 
252 	lock.l_type = F_UNLCK;
253 	lock.l_whence = SEEK_SET;
254 	lock.l_start = 0;
255 	lock.l_len = 0;
256 
257 	(void) fcntl(fd, F_SETLKW, &lock);
258 	(void) close(fd);
259 	(void) unlink(DLADM_AGGR_DB_LOCK);
260 }
261 
262 /*
263  * Walk through the groups defined on the system and for each group <grp>,
264  * invoke <fn>(<arg>, <grp>);
265  * Terminate the walk if at any time <fn> returns non-NULL value
266  */
267 int
268 dladm_aggr_walk(int (*fn)(void *, dladm_aggr_grp_attr_t *), void *arg)
269 {
270 	laioc_info_t *ioc;
271 	laioc_info_group_t *grp;
272 	laioc_info_port_t *port;
273 	dladm_aggr_grp_attr_t attr;
274 	int rc, i, j, bufsize, fd;
275 	char *where;
276 
277 	if ((fd = open(DLADM_AGGR_DEV, O_RDWR)) == -1)
278 		return (-1);
279 
280 	bufsize = MIN_INFO_SIZE;
281 	ioc = (laioc_info_t *)calloc(1, bufsize);
282 	if (ioc == NULL) {
283 		(void) close(fd);
284 		errno = ENOMEM;
285 		return (-1);
286 	}
287 
288 tryagain:
289 	rc = i_dladm_aggr_strioctl(fd, LAIOC_INFO, ioc, bufsize);
290 
291 	if (rc != 0) {
292 		if (errno == ENOSPC) {
293 			/*
294 			 * The LAIOC_INFO call failed due to a short
295 			 * buffer. Reallocate the buffer and try again.
296 			 */
297 			bufsize *= 2;
298 			if (bufsize <= MAX_INFO_SIZE) {
299 				ioc = (laioc_info_t *)realloc(ioc, bufsize);
300 				if (ioc != NULL) {
301 					bzero(ioc, sizeof (bufsize));
302 					goto tryagain;
303 				}
304 			}
305 		}
306 		goto bail;
307 	}
308 
309 	/*
310 	 * Go through each group returned by the aggregation driver.
311 	 */
312 	where = (char *)(ioc + 1);
313 	for (i = 0; i < ioc->li_ngroups; i++) {
314 		/* LINTED E_BAD_PTR_CAST_ALIGN */
315 		grp = (laioc_info_group_t *)where;
316 
317 		attr.lg_key = grp->lg_key;
318 		attr.lg_nports = grp->lg_nports;
319 		attr.lg_policy = grp->lg_policy;
320 		attr.lg_lacp_mode = grp->lg_lacp_mode;
321 		attr.lg_lacp_timer = grp->lg_lacp_timer;
322 
323 		bcopy(grp->lg_mac, attr.lg_mac, ETHERADDRL);
324 		attr.lg_mac_fixed = grp->lg_mac_fixed;
325 
326 		attr.lg_ports = malloc(grp->lg_nports *
327 		    sizeof (dladm_aggr_port_attr_t));
328 		if (attr.lg_ports == NULL) {
329 			errno = ENOMEM;
330 			goto bail;
331 		}
332 
333 		where = (char *)(grp + 1);
334 
335 		/*
336 		 * Go through each port that is part of the group.
337 		 */
338 		for (j = 0; j < grp->lg_nports; j++) {
339 			/* LINTED E_BAD_PTR_CAST_ALIGN */
340 			port = (laioc_info_port_t *)where;
341 
342 			bcopy(port->lp_devname, attr.lg_ports[j].lp_devname,
343 			    MAXNAMELEN + 1);
344 			bcopy(port->lp_mac, attr.lg_ports[j].lp_mac,
345 			    ETHERADDRL);
346 			attr.lg_ports[j].lp_state = port->lp_state;
347 			attr.lg_ports[j].lp_lacp_state = port->lp_lacp_state;
348 
349 			where = (char *)(port + 1);
350 		}
351 
352 		rc = fn(arg, &attr);
353 		free(attr.lg_ports);
354 		if (rc != 0)
355 			goto bail;
356 	}
357 
358 bail:
359 	free(ioc);
360 	(void) close(fd);
361 	return (rc);
362 }
363 
364 /*
365  * Parse one line of the link aggregation DB, and return the corresponding
366  * group. Memory for the ports associated with the aggregation may be
367  * allocated. It is the responsibility of the caller to free the lt_ports
368  * aggregation group attribute.
369  *
370  * Returns -1 on parsing failure, or 0 on success.
371  */
372 static int
373 i_dladm_aggr_parse_db(char *line, dladm_aggr_grp_attr_db_t *attr)
374 {
375 	char	*token;
376 	int	i;
377 	int	value;
378 	char	*endp = NULL;
379 	char	*lasts = NULL;
380 
381 	bzero(attr, sizeof (*attr));
382 
383 	/* key */
384 	if ((token = strtok_r(line, " \t", &lasts)) == NULL)
385 		goto failed;
386 
387 	errno = 0;
388 	value = (int)strtol(token, &endp, 10);
389 	if (errno != 0 || *endp != '\0')
390 		goto failed;
391 
392 	attr->lt_key = value;
393 
394 	/* policy */
395 	if ((token = strtok_r(NULL, " \t", &lasts)) == NULL ||
396 	    !dladm_aggr_str2policy(token, &attr->lt_policy))
397 		goto failed;
398 
399 	/* number of ports */
400 	if ((token = strtok_r(NULL, " \t", &lasts)) == NULL)
401 		return (-1);
402 
403 	errno = 0;
404 	value = (int)strtol(token, &endp, 10);
405 	if (errno != 0 || *endp != '\0')
406 		goto failed;
407 
408 	attr->lt_nports = value;
409 
410 	/* ports */
411 	if ((attr->lt_ports = malloc(attr->lt_nports *
412 	    sizeof (dladm_aggr_port_attr_db_t))) == NULL)
413 		goto failed;
414 
415 	for (i = 0; i < attr->lt_nports; i++) {
416 		char *where, *devname;
417 
418 		/* port */
419 		if ((token = strtok_r(NULL, ", \t\n", &lasts)) == NULL)
420 			goto failed;
421 
422 		/*
423 		 * device name: In a previous version of this file, a port
424 		 * number could be specified using <devname>/<portnum>.
425 		 * This syntax is unecessary and obsolete.
426 		 */
427 		if ((devname = strtok_r(token, "/", &where)) == NULL)
428 			goto failed;
429 		if (strlcpy(attr->lt_ports[i].lp_devname, devname,
430 		    MAXNAMELEN) >= MAXNAMELEN)
431 			goto failed;
432 	}
433 
434 	/* unicast MAC address */
435 	if ((token = strtok_r(NULL, " \t\n", &lasts)) == NULL ||
436 	    !dladm_aggr_str2macaddr(token, &attr->lt_mac_fixed,
437 	    attr->lt_mac))
438 		goto failed;
439 
440 	/* LACP mode */
441 	if ((token = strtok_r(NULL, " \t\n", &lasts)) == NULL ||
442 	    !dladm_aggr_str2lacpmode(token, &attr->lt_lacp_mode))
443 		attr->lt_lacp_mode = AGGR_LACP_OFF;
444 
445 	/* LACP timer */
446 	if ((token = strtok_r(NULL, " \t\n", &lasts)) == NULL ||
447 	    !dladm_aggr_str2lacptimer(token, &attr->lt_lacp_timer))
448 		attr->lt_lacp_timer = AGGR_LACP_TIMER_SHORT;
449 
450 	return (0);
451 
452 failed:
453 	free(attr->lt_ports);
454 	attr->lt_ports = NULL;
455 	return (-1);
456 }
457 
458 /*
459  * Walk through the groups defined in the DB and for each group <grp>,
460  * invoke <fn>(<arg>, <grp>);
461  */
462 static dladm_status_t
463 i_dladm_aggr_walk_db(dladm_status_t (*fn)(void *, dladm_aggr_grp_attr_db_t *),
464     void *arg, const char *root)
465 {
466 	FILE *fp;
467 	char line[MAXLINELEN];
468 	dladm_aggr_grp_attr_db_t attr;
469 	char *db_file;
470 	char db_file_buf[MAXPATHLEN];
471 	int lock_fd;
472 	dladm_status_t status = DLADM_STATUS_OK;
473 
474 	if (root == NULL) {
475 		db_file = DLADM_AGGR_DB;
476 	} else {
477 		(void) snprintf(db_file_buf, MAXPATHLEN, "%s%s", root,
478 		    DLADM_AGGR_DB);
479 		db_file = db_file_buf;
480 	}
481 
482 	lock_fd = i_dladm_aggr_lock_db(F_RDLCK);
483 
484 	if ((fp = fopen(db_file, "r")) == NULL) {
485 		status = dladm_errno2status(errno);
486 		i_dladm_aggr_unlock_db(lock_fd);
487 		return (status);
488 	}
489 
490 	bzero(&attr, sizeof (attr));
491 
492 	while (fgets(line, MAXLINELEN, fp) != NULL) {
493 		/* skip comments */
494 		if (BLANK_LINE(line))
495 			continue;
496 
497 		if (i_dladm_aggr_parse_db(line, &attr) != 0) {
498 			status = DLADM_STATUS_REPOSITORYINVAL;
499 			goto done;
500 		}
501 
502 		if ((status = fn(arg, &attr)) != DLADM_STATUS_OK)
503 			goto done;
504 
505 		free(attr.lt_ports);
506 		attr.lt_ports = NULL;
507 	}
508 
509 done:
510 	free(attr.lt_ports);
511 	(void) fclose(fp);
512 	i_dladm_aggr_unlock_db(lock_fd);
513 	return (status);
514 }
515 
516 /*
517  * Send an add or remove command to the link aggregation driver.
518  */
519 static dladm_status_t
520 i_dladm_aggr_add_rem_sys(dladm_aggr_grp_attr_db_t *attr, int cmd)
521 {
522 	int i, rc, fd, len;
523 	laioc_add_rem_t *iocp;
524 	laioc_port_t *ports;
525 	dladm_status_t status = DLADM_STATUS_OK;
526 
527 	len = sizeof (*iocp) + attr->lt_nports * sizeof (laioc_port_t);
528 	iocp = malloc(len);
529 	if (iocp == NULL) {
530 		status = DLADM_STATUS_NOMEM;
531 		goto done;
532 	}
533 
534 	iocp->la_key = attr->lt_key;
535 	iocp->la_nports = attr->lt_nports;
536 	ports = (laioc_port_t *)(iocp + 1);
537 
538 	for (i = 0; i < attr->lt_nports; i++) {
539 		if (strlcpy(ports[i].lp_devname,
540 		    attr->lt_ports[i].lp_devname,
541 		    MAXNAMELEN) >= MAXNAMELEN) {
542 			status = DLADM_STATUS_BADARG;
543 			goto done;
544 		}
545 	}
546 
547 	if ((fd = open(DLADM_AGGR_DEV, O_RDWR)) < 0) {
548 		status = dladm_errno2status(errno);
549 		goto done;
550 	}
551 
552 	rc = i_dladm_aggr_strioctl(fd, cmd, iocp, len);
553 	if (rc < 0) {
554 		if (errno == EINVAL)
555 			status = DLADM_STATUS_LINKINVAL;
556 		else
557 			status = dladm_errno2status(errno);
558 	}
559 
560 	(void) close(fd);
561 
562 done:
563 	free(iocp);
564 	return (status);
565 }
566 
567 /*
568  * Send a modify command to the link aggregation driver.
569  */
570 static dladm_status_t
571 i_dladm_aggr_modify_sys(uint32_t key, uint32_t mask,
572     dladm_aggr_modify_attr_t *attr)
573 {
574 	int rc, fd;
575 	laioc_modify_t ioc;
576 	dladm_status_t status = DLADM_STATUS_OK;
577 
578 	ioc.lu_key = key;
579 
580 	ioc.lu_modify_mask = 0;
581 	if (mask & DLADM_AGGR_MODIFY_POLICY)
582 		ioc.lu_modify_mask |= LAIOC_MODIFY_POLICY;
583 	if (mask & DLADM_AGGR_MODIFY_MAC)
584 		ioc.lu_modify_mask |= LAIOC_MODIFY_MAC;
585 	if (mask & DLADM_AGGR_MODIFY_LACP_MODE)
586 		ioc.lu_modify_mask |= LAIOC_MODIFY_LACP_MODE;
587 	if (mask & DLADM_AGGR_MODIFY_LACP_TIMER)
588 		ioc.lu_modify_mask |= LAIOC_MODIFY_LACP_TIMER;
589 
590 	ioc.lu_policy = attr->ld_policy;
591 	ioc.lu_mac_fixed = attr->ld_mac_fixed;
592 	bcopy(attr->ld_mac, ioc.lu_mac, ETHERADDRL);
593 	ioc.lu_lacp_mode = attr->ld_lacp_mode;
594 	ioc.lu_lacp_timer = attr->ld_lacp_timer;
595 
596 	if ((fd = open(DLADM_AGGR_DEV, O_RDWR)) < 0)
597 		return (dladm_errno2status(errno));
598 
599 	rc = i_dladm_aggr_strioctl(fd, LAIOC_MODIFY, &ioc, sizeof (ioc));
600 	if (rc < 0) {
601 		if (errno == EINVAL)
602 			status = DLADM_STATUS_MACADDRINVAL;
603 		else
604 			status = dladm_errno2status(errno);
605 	}
606 
607 	(void) close(fd);
608 	return (status);
609 }
610 
611 /*
612  * Send a create command to the link aggregation driver.
613  */
614 static dladm_status_t
615 i_dladm_aggr_create_sys(int fd, dladm_aggr_grp_attr_db_t *attr)
616 {
617 	int i, rc, len;
618 	laioc_create_t *iocp;
619 	laioc_port_t *ports;
620 	dladm_status_t status = DLADM_STATUS_OK;
621 
622 	len = sizeof (*iocp) + attr->lt_nports * sizeof (laioc_port_t);
623 	iocp = malloc(len);
624 	if (iocp == NULL)
625 		return (DLADM_STATUS_NOMEM);
626 
627 	iocp->lc_key = attr->lt_key;
628 	iocp->lc_nports = attr->lt_nports;
629 	iocp->lc_policy = attr->lt_policy;
630 	iocp->lc_lacp_mode = attr->lt_lacp_mode;
631 	iocp->lc_lacp_timer = attr->lt_lacp_timer;
632 
633 	ports = (laioc_port_t *)(iocp + 1);
634 
635 	for (i = 0; i < attr->lt_nports; i++) {
636 		if (strlcpy(ports[i].lp_devname,
637 		    attr->lt_ports[i].lp_devname,
638 		    MAXNAMELEN) >= MAXNAMELEN) {
639 			free(iocp);
640 			return (DLADM_STATUS_BADARG);
641 		}
642 	}
643 
644 	if (attr->lt_mac_fixed &&
645 	    ((bcmp(zero_mac, attr->lt_mac, ETHERADDRL) == 0) ||
646 	    (attr->lt_mac[0] & 0x01))) {
647 		free(iocp);
648 		return (DLADM_STATUS_MACADDRINVAL);
649 	}
650 
651 	bcopy(attr->lt_mac, iocp->lc_mac, ETHERADDRL);
652 	iocp->lc_mac_fixed = attr->lt_mac_fixed;
653 
654 	rc = i_dladm_aggr_strioctl(fd, LAIOC_CREATE, iocp, len);
655 	if (rc < 0)
656 		status = DLADM_STATUS_LINKINVAL;
657 
658 	free(iocp);
659 	return (status);
660 }
661 
662 /*
663  * Invoked to bring up a link aggregation group.
664  */
665 static dladm_status_t
666 i_dladm_aggr_up(void *arg, dladm_aggr_grp_attr_db_t *attr)
667 {
668 	dladm_aggr_up_t	*up = (dladm_aggr_up_t *)arg;
669 	dladm_status_t	status;
670 
671 	if (up->lu_key != 0 && up->lu_key != attr->lt_key)
672 		return (DLADM_STATUS_OK);
673 
674 	up->lu_found = B_TRUE;
675 
676 	status = i_dladm_aggr_create_sys(up->lu_fd, attr);
677 	if (status != DLADM_STATUS_OK && up->lu_key != 0)
678 		return (status);
679 
680 	return (DLADM_STATUS_OK);
681 }
682 
683 /*
684  * Bring up a link aggregation group or all of them if the key is zero.
685  * If key is 0, walk may terminate early if any of the links fail
686  */
687 dladm_status_t
688 dladm_aggr_up(uint32_t key, const char *root)
689 {
690 	dladm_aggr_up_t up;
691 	dladm_status_t status;
692 
693 	if ((up.lu_fd = open(DLADM_AGGR_DEV, O_RDWR)) < 0)
694 		return (dladm_errno2status(errno));
695 
696 	up.lu_key = key;
697 	up.lu_found = B_FALSE;
698 
699 	status = i_dladm_aggr_walk_db(i_dladm_aggr_up, &up, root);
700 	if (status != DLADM_STATUS_OK) {
701 		(void) close(up.lu_fd);
702 		return (status);
703 	}
704 	(void) close(up.lu_fd);
705 
706 	/*
707 	 * only return error if user specified key and key was
708 	 * not found
709 	 */
710 	if (!up.lu_found && key != 0)
711 		return (DLADM_STATUS_NOTFOUND);
712 
713 	return (DLADM_STATUS_OK);
714 }
715 /*
716  * Send a delete command to the link aggregation driver.
717  */
718 static int
719 i_dladm_aggr_delete_sys(int fd, dladm_aggr_grp_attr_t *attr)
720 {
721 	laioc_delete_t ioc;
722 
723 	ioc.ld_key = attr->lg_key;
724 
725 	return (i_dladm_aggr_strioctl(fd, LAIOC_DELETE, &ioc, sizeof (ioc)));
726 }
727 
728 /*
729  * Invoked to bring down a link aggregation group.
730  */
731 static int
732 i_dladm_aggr_down(void *arg, dladm_aggr_grp_attr_t *attr)
733 {
734 	dladm_aggr_down_t *down = (dladm_aggr_down_t *)arg;
735 	int fd, errno_save;
736 
737 	if (down->ld_key != 0 && down->ld_key != attr->lg_key)
738 		return (0);
739 
740 	down->ld_found = B_TRUE;
741 
742 	if ((fd = open(DLADM_AGGR_DEV, O_RDWR)) < 0)
743 		return (-1);
744 
745 	if (i_dladm_aggr_delete_sys(fd, attr) < 0 && down->ld_key != 0) {
746 		errno_save = errno;
747 		(void) close(fd);
748 		errno = errno_save;
749 		return (-1);
750 	}
751 
752 	(void) close(fd);
753 	return (0);
754 }
755 
756 /*
757  * Bring down a link aggregation group or all of them if the key is zero.
758  * If key is 0, walk may terminate early if any of the links fail
759  */
760 dladm_status_t
761 dladm_aggr_down(uint32_t key)
762 {
763 	dladm_aggr_down_t down;
764 
765 	down.ld_key = key;
766 	down.ld_found = B_FALSE;
767 
768 	if (dladm_aggr_walk(i_dladm_aggr_down, &down) < 0)
769 		return (dladm_errno2status(errno));
770 
771 	/*
772 	 * only return error if user specified key and key was
773 	 * not found
774 	 */
775 	if (!down.ld_found && key != 0)
776 		return (DLADM_STATUS_NOTFOUND);
777 
778 	return (DLADM_STATUS_OK);
779 }
780 
781 /*
782  * For each group <grp> found in the DB, invokes <fn>(<grp>, <arg>).
783  *
784  * The following values can be returned by <fn>():
785  *
786  * -1: an error occured. This will cause the walk to be terminated,
787  *     and the original DB file to be preserved.
788  *
789  *  0: success and write. The walker will write the contents of
790  *     the attribute passed as argument to <fn>(), and continue walking
791  *     the entries found in the DB.
792  *
793  *  1: skip. The walker should not write the contents of the current
794  *     group attributes to the new DB, but should continue walking
795  *     the entries found in the DB.
796  */
797 static dladm_status_t
798 i_dladm_aggr_walk_rw_db(int (*fn)(void *, dladm_aggr_grp_attr_db_t *),
799     void *arg, const char *root)
800 {
801 	FILE *fp, *nfp;
802 	int nfd, fn_rc, lock_fd;
803 	char line[MAXLINELEN];
804 	dladm_aggr_grp_attr_db_t attr;
805 	char *db_file, *tmp_db_file;
806 	char db_file_buf[MAXPATHLEN];
807 	char tmp_db_file_buf[MAXPATHLEN];
808 	dladm_status_t status;
809 
810 	if (root == NULL) {
811 		db_file = DLADM_AGGR_DB;
812 		tmp_db_file = DLADM_AGGR_DB_TMP;
813 	} else {
814 		(void) snprintf(db_file_buf, MAXPATHLEN, "%s%s", root,
815 		    DLADM_AGGR_DB);
816 		(void) snprintf(tmp_db_file_buf, MAXPATHLEN, "%s%s", root,
817 		    DLADM_AGGR_DB_TMP);
818 		db_file = db_file_buf;
819 		tmp_db_file = tmp_db_file_buf;
820 	}
821 
822 	if ((lock_fd = i_dladm_aggr_lock_db(F_WRLCK)) < 0)
823 		return (dladm_errno2status(errno));
824 
825 	if ((fp = fopen(db_file, "r")) == NULL) {
826 		status = dladm_errno2status(errno);
827 		i_dladm_aggr_unlock_db(lock_fd);
828 		return (status);
829 	}
830 
831 	if ((nfd = open(tmp_db_file, O_WRONLY|O_CREAT|O_TRUNC,
832 	    DLADM_AGGR_DB_PERMS)) == -1) {
833 		status = dladm_errno2status(errno);
834 		(void) fclose(fp);
835 		i_dladm_aggr_unlock_db(lock_fd);
836 		return (status);
837 	}
838 
839 	if ((nfp = fdopen(nfd, "w")) == NULL) {
840 		status = dladm_errno2status(errno);
841 		(void) close(nfd);
842 		(void) fclose(fp);
843 		(void) unlink(tmp_db_file);
844 		i_dladm_aggr_unlock_db(lock_fd);
845 		return (status);
846 	}
847 
848 	attr.lt_ports = NULL;
849 
850 	while (fgets(line, MAXLINELEN, fp) != NULL) {
851 
852 		/* skip comments */
853 		if (BLANK_LINE(line)) {
854 			if (fputs(line, nfp) == EOF) {
855 				status = dladm_errno2status(errno);
856 				goto failed;
857 			}
858 			continue;
859 		}
860 
861 		if (i_dladm_aggr_parse_db(line, &attr) != 0) {
862 			status = DLADM_STATUS_REPOSITORYINVAL;
863 			goto failed;
864 		}
865 
866 		fn_rc = fn(arg, &attr);
867 
868 		switch (fn_rc) {
869 		case -1:
870 			/* failure, stop walking */
871 			status = dladm_errno2status(errno);
872 			goto failed;
873 		case 0:
874 			/*
875 			 * Success, write group attributes, which could
876 			 * have been modified by fn().
877 			 */
878 			if (i_dladm_aggr_fput_grp(nfp, &attr) != 0) {
879 				status = dladm_errno2status(errno);
880 				goto failed;
881 			}
882 			break;
883 		case 1:
884 			/* skip current group */
885 			break;
886 		}
887 
888 		free(attr.lt_ports);
889 		attr.lt_ports = NULL;
890 	}
891 
892 	if (getuid() == 0 || geteuid() == 0) {
893 		if (fchmod(nfd, DLADM_AGGR_DB_PERMS) == -1) {
894 			status = dladm_errno2status(errno);
895 			goto failed;
896 		}
897 
898 		if (fchown(nfd, DLADM_AGGR_DB_OWNER,
899 		    DLADM_AGGR_DB_GROUP) == -1) {
900 			status = dladm_errno2status(errno);
901 			goto failed;
902 		}
903 	}
904 
905 	if (fflush(nfp) == EOF) {
906 		status = dladm_errno2status(errno);
907 		goto failed;
908 	}
909 
910 	(void) fclose(fp);
911 	(void) fclose(nfp);
912 
913 	if (rename(tmp_db_file, db_file) == -1) {
914 		status = dladm_errno2status(errno);
915 		(void) unlink(tmp_db_file);
916 		i_dladm_aggr_unlock_db(lock_fd);
917 		return (status);
918 	}
919 
920 	i_dladm_aggr_unlock_db(lock_fd);
921 	return (DLADM_STATUS_OK);
922 
923 failed:
924 	free(attr.lt_ports);
925 	(void) fclose(fp);
926 	(void) fclose(nfp);
927 	(void) unlink(tmp_db_file);
928 	i_dladm_aggr_unlock_db(lock_fd);
929 
930 	return (status);
931 }
932 
933 /*
934  * Remove an entry from the DB.
935  */
936 static int
937 i_dladm_aggr_del_db_fn(void *arg, dladm_aggr_grp_attr_db_t *grp)
938 {
939 	delete_db_state_t *state = arg;
940 
941 	if (grp->lt_key != state->ds_key)
942 		return (0);
943 
944 	state->ds_found = B_TRUE;
945 
946 	/* don't save matching group */
947 	return (1);
948 }
949 
950 static dladm_status_t
951 i_dladm_aggr_del_db(dladm_aggr_grp_attr_db_t *attr, const char *root)
952 {
953 	delete_db_state_t state;
954 	dladm_status_t status;
955 
956 	state.ds_key = attr->lt_key;
957 	state.ds_found = B_FALSE;
958 
959 	status = i_dladm_aggr_walk_rw_db(i_dladm_aggr_del_db_fn, &state, root);
960 	if (status != DLADM_STATUS_OK)
961 		return (status);
962 
963 	if (!state.ds_found)
964 		return (DLADM_STATUS_NOTFOUND);
965 
966 	return (DLADM_STATUS_OK);
967 }
968 
969 /*
970  * Modify the properties of an existing group in the DB.
971  */
972 static int
973 i_dladm_aggr_modify_db_fn(void *arg, dladm_aggr_grp_attr_db_t *grp)
974 {
975 	modify_db_state_t *state = arg;
976 	dladm_aggr_modify_attr_t *new_attr = state->us_attr_new;
977 	dladm_aggr_modify_attr_t *old_attr = state->us_attr_old;
978 
979 	if (grp->lt_key != state->us_key)
980 		return (0);
981 
982 	state->us_found = B_TRUE;
983 
984 	if (state->us_mask & DLADM_AGGR_MODIFY_POLICY) {
985 		if (old_attr != NULL)
986 			old_attr->ld_policy = grp->lt_policy;
987 		grp->lt_policy = new_attr->ld_policy;
988 	}
989 
990 	if (state->us_mask & DLADM_AGGR_MODIFY_MAC) {
991 		if (old_attr != NULL) {
992 			old_attr->ld_mac_fixed = grp->lt_mac_fixed;
993 			bcopy(grp->lt_mac, old_attr->ld_mac, ETHERADDRL);
994 		}
995 		grp->lt_mac_fixed = new_attr->ld_mac_fixed;
996 		bcopy(new_attr->ld_mac, grp->lt_mac, ETHERADDRL);
997 	}
998 
999 	if (state->us_mask & DLADM_AGGR_MODIFY_LACP_MODE) {
1000 		if (old_attr != NULL)
1001 			old_attr->ld_lacp_mode = grp->lt_lacp_mode;
1002 		grp->lt_lacp_mode = new_attr->ld_lacp_mode;
1003 	}
1004 
1005 	if (state->us_mask & DLADM_AGGR_MODIFY_LACP_TIMER) {
1006 		if (old_attr != NULL)
1007 			old_attr->ld_lacp_timer = grp->lt_lacp_timer;
1008 		grp->lt_lacp_timer = new_attr->ld_lacp_timer;
1009 	}
1010 
1011 	/* save modified group */
1012 	return (0);
1013 }
1014 
1015 static dladm_status_t
1016 i_dladm_aggr_modify_db(uint32_t key, uint32_t mask,
1017     dladm_aggr_modify_attr_t *new, dladm_aggr_modify_attr_t *old,
1018     const char *root)
1019 {
1020 	modify_db_state_t state;
1021 	dladm_status_t status;
1022 
1023 	state.us_key = key;
1024 	state.us_mask = mask;
1025 	state.us_attr_new = new;
1026 	state.us_attr_old = old;
1027 	state.us_found = B_FALSE;
1028 
1029 	if ((status = i_dladm_aggr_walk_rw_db(i_dladm_aggr_modify_db_fn,
1030 	    &state, root)) != DLADM_STATUS_OK) {
1031 		return (status);
1032 	}
1033 
1034 	if (!state.us_found)
1035 		return (DLADM_STATUS_NOTFOUND);
1036 
1037 	return (DLADM_STATUS_OK);
1038 }
1039 
1040 /*
1041  * Add ports to an existing group in the DB.
1042  */
1043 static int
1044 i_dladm_aggr_add_db_fn(void *arg, dladm_aggr_grp_attr_db_t *grp)
1045 {
1046 	add_db_state_t *state = arg;
1047 	dladm_aggr_grp_attr_db_t *attr = state->as_attr;
1048 	void *ports;
1049 	int i, j;
1050 
1051 	if (grp->lt_key != attr->lt_key)
1052 		return (0);
1053 
1054 	state->as_found = B_TRUE;
1055 
1056 	/* are any of the ports to be added already members of the group? */
1057 	for (i = 0; i < grp->lt_nports; i++) {
1058 		for (j = 0; j < attr->lt_nports; j++) {
1059 			if (strcmp(grp->lt_ports[i].lp_devname,
1060 			    attr->lt_ports[j].lp_devname) == 0) {
1061 				errno = EEXIST;
1062 				return (-1);
1063 			}
1064 		}
1065 	}
1066 
1067 	/* add groups specified by attr to grp */
1068 	ports = realloc(grp->lt_ports, (grp->lt_nports +
1069 	    attr->lt_nports) * sizeof (dladm_aggr_port_attr_db_t));
1070 	if (ports == NULL)
1071 		return (-1);
1072 	grp->lt_ports = ports;
1073 
1074 	for (i = 0; i < attr->lt_nports; i++) {
1075 		if (strlcpy(grp->lt_ports[grp->lt_nports + i].lp_devname,
1076 		    attr->lt_ports[i].lp_devname, MAXNAMELEN + 1) >=
1077 		    MAXNAMELEN + 1)
1078 			return (-1);
1079 	}
1080 
1081 	grp->lt_nports += attr->lt_nports;
1082 
1083 	/* save modified group */
1084 	return (0);
1085 }
1086 
1087 static dladm_status_t
1088 i_dladm_aggr_add_db(dladm_aggr_grp_attr_db_t *attr, const char *root)
1089 {
1090 	add_db_state_t state;
1091 	dladm_status_t status;
1092 
1093 	state.as_attr = attr;
1094 	state.as_found = B_FALSE;
1095 
1096 	status = i_dladm_aggr_walk_rw_db(i_dladm_aggr_add_db_fn, &state, root);
1097 	if (status != DLADM_STATUS_OK)
1098 		return (status);
1099 
1100 	if (!state.as_found)
1101 		return (DLADM_STATUS_NOTFOUND);
1102 
1103 	return (DLADM_STATUS_OK);
1104 }
1105 
1106 /*
1107  * Remove ports from an existing group in the DB.
1108  */
1109 
1110 typedef struct remove_db_state {
1111 	dladm_aggr_grp_attr_db_t *rs_attr;
1112 	boolean_t	rs_found;
1113 } remove_db_state_t;
1114 
1115 static int
1116 i_dladm_aggr_remove_db_fn(void *arg, dladm_aggr_grp_attr_db_t *grp)
1117 {
1118 	remove_db_state_t *state = (remove_db_state_t *)arg;
1119 	dladm_aggr_grp_attr_db_t *attr = state->rs_attr;
1120 	int i, j, k, nremoved;
1121 	boolean_t match;
1122 
1123 	if (grp->lt_key != attr->lt_key)
1124 		return (0);
1125 
1126 	state->rs_found = B_TRUE;
1127 
1128 	/* remove the ports specified by attr from the group */
1129 	nremoved = 0;
1130 	k = 0;
1131 	for (i = 0; i < grp->lt_nports; i++) {
1132 		match = B_FALSE;
1133 		for (j = 0; j < attr->lt_nports && !match; j++) {
1134 			match = (strcmp(grp->lt_ports[i].lp_devname,
1135 			    attr->lt_ports[j].lp_devname) == 0);
1136 		}
1137 		if (match)
1138 			nremoved++;
1139 		else
1140 			grp->lt_ports[k++] = grp->lt_ports[i];
1141 	}
1142 
1143 	if (nremoved != attr->lt_nports) {
1144 		errno = ENOENT;
1145 		return (-1);
1146 	}
1147 
1148 	grp->lt_nports -= nremoved;
1149 
1150 	/* save modified group */
1151 	return (0);
1152 }
1153 
1154 static dladm_status_t
1155 i_dladm_aggr_remove_db(dladm_aggr_grp_attr_db_t *attr, const char *root)
1156 {
1157 	remove_db_state_t state;
1158 	dladm_status_t status;
1159 
1160 	state.rs_attr = attr;
1161 	state.rs_found = B_FALSE;
1162 
1163 	status = i_dladm_aggr_walk_rw_db(i_dladm_aggr_remove_db_fn,
1164 	    &state, root);
1165 	if (status != DLADM_STATUS_OK)
1166 		return (status);
1167 
1168 	if (!state.rs_found)
1169 		return (DLADM_STATUS_NOTFOUND);
1170 
1171 	return (DLADM_STATUS_OK);
1172 }
1173 
1174 /*
1175  * Given a policy string, return a policy mask. Returns B_TRUE on
1176  * success, or B_FALSE if an error occured during parsing.
1177  */
1178 boolean_t
1179 dladm_aggr_str2policy(const char *str, uint32_t *policy)
1180 {
1181 	int i;
1182 	policy_t *pol;
1183 	char *token = NULL;
1184 	char *lasts;
1185 
1186 	*policy = 0;
1187 
1188 	while ((token = strtok_r((token == NULL) ? (char *)str : NULL, ",",
1189 	    &lasts)) != NULL) {
1190 		for (i = 0; i < NPOLICIES; i++) {
1191 			pol = &policies[i];
1192 			if (strcasecmp(token, pol->pol_name) == 0) {
1193 				*policy |= pol->policy;
1194 				break;
1195 			}
1196 		}
1197 		if (i == NPOLICIES)
1198 			return (B_FALSE);
1199 	}
1200 
1201 	return (B_TRUE);
1202 }
1203 
1204 /*
1205  * Given a policy mask, returns a printable string, or NULL if the
1206  * policy mask is invalid. It is the responsibility of the caller to
1207  * free the returned string after use.
1208  */
1209 char *
1210 dladm_aggr_policy2str(uint32_t policy, char *str)
1211 {
1212 	int i, npolicies = 0;
1213 	policy_t *pol;
1214 
1215 	str[0] = '\0';
1216 
1217 	for (i = 0; i < NPOLICIES; i++) {
1218 		pol = &policies[i];
1219 		if ((policy & pol->policy) != 0) {
1220 			npolicies++;
1221 			if (npolicies > 1)
1222 				(void) strcat(str, ",");
1223 			(void) strcat(str, pol->pol_name);
1224 		}
1225 	}
1226 
1227 	return (str);
1228 }
1229 
1230 /*
1231  * Given a MAC address string, return the MAC address in the mac_addr
1232  * array. If the MAC address was not explicitly specified, i.e. is
1233  * equal to 'auto', zero out mac-addr and set mac_fixed to B_TRUE.
1234  * Return B_FALSE if a syntax error was encountered, B_FALSE otherwise.
1235  */
1236 boolean_t
1237 dladm_aggr_str2macaddr(const char *str, boolean_t *mac_fixed, uchar_t *mac_addr)
1238 {
1239 	uchar_t *conv_str;
1240 	int mac_len;
1241 
1242 	*mac_fixed = (strcmp(str, "auto") != 0);
1243 	if (!*mac_fixed) {
1244 		bzero(mac_addr, ETHERADDRL);
1245 		return (B_TRUE);
1246 	}
1247 
1248 	conv_str = _link_aton(str, &mac_len);
1249 	if (conv_str == NULL)
1250 		return (B_FALSE);
1251 
1252 	if (mac_len != ETHERADDRL) {
1253 		free(conv_str);
1254 		return (B_FALSE);
1255 	}
1256 
1257 	if ((bcmp(zero_mac, conv_str, ETHERADDRL) == 0) ||
1258 	    (conv_str[0] & 0x01)) {
1259 		free(conv_str);
1260 		return (B_FALSE);
1261 	}
1262 
1263 	bcopy(conv_str, mac_addr, ETHERADDRL);
1264 	free(conv_str);
1265 
1266 	return (B_TRUE);
1267 }
1268 
1269 /*
1270  * Returns a string containing a printable representation of a MAC address.
1271  */
1272 const char *
1273 dladm_aggr_macaddr2str(unsigned char *mac, char *buf)
1274 {
1275 	static char unknown_mac[] = {0, 0, 0, 0, 0, 0};
1276 
1277 	if (buf == NULL)
1278 		return (NULL);
1279 
1280 	if (bcmp(unknown_mac, mac, ETHERADDRL) == 0)
1281 		return (gettext("<unknown>"));
1282 	else
1283 		return (_link_ntoa(mac, buf, ETHERADDRL, IFT_OTHER));
1284 }
1285 
1286 /*
1287  * Given a LACP mode string, find the corresponding LACP mode number. Returns
1288  * B_TRUE if a match was found, B_FALSE otherwise.
1289  */
1290 boolean_t
1291 dladm_aggr_str2lacpmode(const char *str, aggr_lacp_mode_t *lacp_mode)
1292 {
1293 	int i;
1294 	dladm_aggr_lacpmode_t *mode;
1295 
1296 	for (i = 0; i < NLACP_MODES; i++) {
1297 		mode = &lacp_modes[i];
1298 		if (strncasecmp(str, mode->mode_str,
1299 		    strlen(mode->mode_str)) == 0) {
1300 			*lacp_mode = mode->mode_id;
1301 			return (B_TRUE);
1302 		}
1303 	}
1304 
1305 	return (B_FALSE);
1306 }
1307 
1308 /*
1309  * Given a LACP mode number, returns a printable string, or NULL if the
1310  * LACP mode number is invalid.
1311  */
1312 const char *
1313 dladm_aggr_lacpmode2str(aggr_lacp_mode_t mode_id, char *buf)
1314 {
1315 	int i;
1316 	dladm_aggr_lacpmode_t *mode;
1317 
1318 	for (i = 0; i < NLACP_MODES; i++) {
1319 		mode = &lacp_modes[i];
1320 		if (mode->mode_id == mode_id) {
1321 			(void) snprintf(buf, DLADM_STRSIZE, "%s",
1322 			    mode->mode_str);
1323 			return (buf);
1324 		}
1325 	}
1326 
1327 	(void) strlcpy(buf, "unknown", DLADM_STRSIZE);
1328 	return (buf);
1329 }
1330 
1331 /*
1332  * Given a LACP timer string, find the corresponding LACP timer number. Returns
1333  * B_TRUE if a match was found, B_FALSE otherwise.
1334  */
1335 boolean_t
1336 dladm_aggr_str2lacptimer(const char *str, aggr_lacp_timer_t *lacp_timer)
1337 {
1338 	int i;
1339 	dladm_aggr_lacptimer_t *timer;
1340 
1341 	for (i = 0; i < NLACP_TIMERS; i++) {
1342 		timer = &lacp_timers[i];
1343 		if (strncasecmp(str, timer->lt_str,
1344 		    strlen(timer->lt_str)) == 0) {
1345 			*lacp_timer = timer->lt_id;
1346 			return (B_TRUE);
1347 		}
1348 	}
1349 
1350 	return (B_FALSE);
1351 }
1352 
1353 /*
1354  * Given a LACP timer, returns a printable string, or NULL if the
1355  * LACP timer number is invalid.
1356  */
1357 const char *
1358 dladm_aggr_lacptimer2str(aggr_lacp_timer_t timer_id, char *buf)
1359 {
1360 	int i;
1361 	dladm_aggr_lacptimer_t *timer;
1362 
1363 	for (i = 0; i < NLACP_TIMERS; i++) {
1364 		timer = &lacp_timers[i];
1365 		if (timer->lt_id == timer_id) {
1366 			(void) snprintf(buf, DLADM_STRSIZE, "%s",
1367 			    timer->lt_str);
1368 			return (buf);
1369 		}
1370 	}
1371 
1372 	(void) strlcpy(buf, "unknown", DLADM_STRSIZE);
1373 	return (buf);
1374 }
1375 
1376 const char *
1377 dladm_aggr_portstate2str(aggr_port_state_t state_id, char *buf)
1378 {
1379 	int			i;
1380 	dladm_aggr_port_state_t	*state;
1381 
1382 	for (i = 0; i < NPORT_STATES; i++) {
1383 		state = &port_states[i];
1384 		if (state->state_id == state_id) {
1385 			(void) snprintf(buf, DLADM_STRSIZE, "%s",
1386 			    state->state_str);
1387 			return (buf);
1388 		}
1389 	}
1390 
1391 	(void) strlcpy(buf, "unknown", DLADM_STRSIZE);
1392 	return (buf);
1393 }
1394 
1395 #define	FPRINTF_ERR(fcall) if ((fcall) < 0) return (-1);
1396 
1397 /*
1398  * Write the attribute of a group to the specified file. Returns 0 on
1399  * success, -1 on failure.
1400  */
1401 static int
1402 i_dladm_aggr_fput_grp(FILE *fp, dladm_aggr_grp_attr_db_t *attr)
1403 {
1404 	int i;
1405 	char addr_str[ETHERADDRL * 3];
1406 	char buf[DLADM_STRSIZE];
1407 
1408 	/* key, policy */
1409 	FPRINTF_ERR(fprintf(fp, "%d\t%s\t", attr->lt_key,
1410 	    dladm_aggr_policy2str(attr->lt_policy, buf)));
1411 
1412 	/* number of ports, ports */
1413 	FPRINTF_ERR(fprintf(fp, "%d\t", attr->lt_nports));
1414 	for (i = 0; i < attr->lt_nports; i++) {
1415 		if (i > 0)
1416 			FPRINTF_ERR(fprintf(fp, ","));
1417 		FPRINTF_ERR(fprintf(fp, "%s", attr->lt_ports[i].lp_devname));
1418 	}
1419 	FPRINTF_ERR(fprintf(fp, "\t"));
1420 
1421 	/* MAC address */
1422 	if (!attr->lt_mac_fixed) {
1423 		FPRINTF_ERR(fprintf(fp, "auto"));
1424 	} else {
1425 		FPRINTF_ERR(fprintf(fp, "%s",
1426 		    dladm_aggr_macaddr2str(attr->lt_mac, addr_str)));
1427 	}
1428 	FPRINTF_ERR(fprintf(fp, "\t"));
1429 
1430 	FPRINTF_ERR(fprintf(fp, "%s\t",
1431 	    dladm_aggr_lacpmode2str(attr->lt_lacp_mode, buf)));
1432 
1433 	FPRINTF_ERR(fprintf(fp, "%s\n",
1434 	    dladm_aggr_lacptimer2str(attr->lt_lacp_timer, buf)));
1435 
1436 	return (0);
1437 }
1438 
1439 static dladm_status_t
1440 i_dladm_aggr_create_db(dladm_aggr_grp_attr_db_t *attr, const char *root)
1441 {
1442 	FILE		*fp;
1443 	char		line[MAXLINELEN];
1444 	uint32_t	key;
1445 	int 		lock_fd;
1446 	char 		*db_file;
1447 	char 		db_file_buf[MAXPATHLEN];
1448 	char 		*endp = NULL;
1449 	dladm_status_t	status;
1450 
1451 	if (root == NULL) {
1452 		db_file = DLADM_AGGR_DB;
1453 	} else {
1454 		(void) snprintf(db_file_buf, MAXPATHLEN, "%s%s", root,
1455 		    DLADM_AGGR_DB);
1456 		db_file = db_file_buf;
1457 	}
1458 
1459 	if ((lock_fd = i_dladm_aggr_lock_db(F_WRLCK)) < 0)
1460 		return (dladm_errno2status(errno));
1461 
1462 	if ((fp = fopen(db_file, "r+")) == NULL &&
1463 	    (fp = fopen(db_file, "w")) == NULL) {
1464 		status = dladm_errno2status(errno);
1465 		i_dladm_aggr_unlock_db(lock_fd);
1466 		return (status);
1467 	}
1468 
1469 	/* look for existing group with same key */
1470 	while (fgets(line, MAXLINELEN, fp) != NULL) {
1471 		char *holder, *lasts;
1472 
1473 		/* skip comments */
1474 		if (BLANK_LINE(line))
1475 			continue;
1476 
1477 		/* ignore corrupted lines */
1478 		holder = strtok_r(line, " \t", &lasts);
1479 		if (holder == NULL)
1480 			continue;
1481 
1482 		/* port number */
1483 		errno = 0;
1484 		key = (int)strtol(holder, &endp, 10);
1485 		if (errno != 0 || *endp != '\0') {
1486 			status = DLADM_STATUS_REPOSITORYINVAL;
1487 			goto done;
1488 		}
1489 
1490 		if (key == attr->lt_key) {
1491 			/* group with key already exists */
1492 			status = DLADM_STATUS_EXIST;
1493 			goto done;
1494 		}
1495 	}
1496 
1497 	/*
1498 	 * If we get here, we've verified that no existing group with
1499 	 * the same key already exists. It's now time to add the
1500 	 * new group to the DB.
1501 	 */
1502 	if (i_dladm_aggr_fput_grp(fp, attr) != 0) {
1503 		status = dladm_errno2status(errno);
1504 		goto done;
1505 	}
1506 
1507 	status = DLADM_STATUS_OK;
1508 
1509 done:
1510 	(void) fclose(fp);
1511 	i_dladm_aggr_unlock_db(lock_fd);
1512 	return (status);
1513 }
1514 
1515 /*
1516  * Create a new link aggregation group. Update the configuration
1517  * file and bring it up.
1518  */
1519 dladm_status_t
1520 dladm_aggr_create(uint32_t key, uint32_t nports,
1521     dladm_aggr_port_attr_db_t *ports, uint32_t policy, boolean_t mac_addr_fixed,
1522     uchar_t *mac_addr, aggr_lacp_mode_t lacp_mode, aggr_lacp_timer_t lacp_timer,
1523     boolean_t tempop, const char *root)
1524 {
1525 	dladm_aggr_grp_attr_db_t attr;
1526 	dladm_status_t status;
1527 
1528 	if (key == 0 || key > DLADM_AGGR_MAX_KEY)
1529 		return (DLADM_STATUS_KEYINVAL);
1530 
1531 	attr.lt_key = key;
1532 	attr.lt_nports = nports;
1533 	attr.lt_ports = ports;
1534 	attr.lt_policy = policy;
1535 	attr.lt_mac_fixed = mac_addr_fixed;
1536 	if (attr.lt_mac_fixed)
1537 		bcopy(mac_addr, attr.lt_mac, ETHERADDRL);
1538 	else
1539 		bzero(attr.lt_mac, ETHERADDRL);
1540 	attr.lt_lacp_mode = lacp_mode;
1541 	attr.lt_lacp_timer = lacp_timer;
1542 
1543 	/* add the link aggregation group to the DB */
1544 	if (!tempop) {
1545 		status = i_dladm_aggr_create_db(&attr, root);
1546 		if (status != DLADM_STATUS_OK)
1547 			return (status);
1548 	} else {
1549 		dladm_aggr_up_t up;
1550 
1551 		up.lu_key = key;
1552 		up.lu_found = B_FALSE;
1553 		up.lu_fd = open(DLADM_AGGR_DEV, O_RDWR);
1554 		if (up.lu_fd < 0)
1555 			return (dladm_errno2status(errno));
1556 
1557 		status = i_dladm_aggr_up((void *)&up, &attr);
1558 		(void) close(up.lu_fd);
1559 		return (status);
1560 	}
1561 
1562 	/* bring up the link aggregation group */
1563 	status = dladm_aggr_up(key, root);
1564 	/*
1565 	 * If the operation fails because the aggregation already exists,
1566 	 * then only update the persistent configuration repository and
1567 	 * return success.
1568 	 */
1569 	if (status == DLADM_STATUS_EXIST)
1570 		status = DLADM_STATUS_OK;
1571 
1572 	if (status != DLADM_STATUS_OK && !tempop)
1573 		(void) i_dladm_aggr_del_db(&attr, root);
1574 
1575 	return (status);
1576 }
1577 
1578 /*
1579  * Modify the parameters of an existing link aggregation group. Update
1580  * the configuration file and pass the changes to the kernel.
1581  */
1582 dladm_status_t
1583 dladm_aggr_modify(uint32_t key, uint32_t modify_mask, uint32_t policy,
1584     boolean_t mac_fixed, uchar_t *mac_addr, aggr_lacp_mode_t lacp_mode,
1585     aggr_lacp_timer_t lacp_timer, boolean_t tempop, const char *root)
1586 {
1587 	dladm_aggr_modify_attr_t new_attr, old_attr;
1588 	dladm_status_t status;
1589 
1590 	if (key == 0)
1591 		return (DLADM_STATUS_KEYINVAL);
1592 
1593 	if (modify_mask & DLADM_AGGR_MODIFY_POLICY)
1594 		new_attr.ld_policy = policy;
1595 
1596 	if (modify_mask & DLADM_AGGR_MODIFY_MAC) {
1597 		new_attr.ld_mac_fixed = mac_fixed;
1598 		bcopy(mac_addr, new_attr.ld_mac, ETHERADDRL);
1599 	}
1600 
1601 	if (modify_mask & DLADM_AGGR_MODIFY_LACP_MODE)
1602 		new_attr.ld_lacp_mode = lacp_mode;
1603 
1604 	if (modify_mask & DLADM_AGGR_MODIFY_LACP_TIMER)
1605 		new_attr.ld_lacp_timer = lacp_timer;
1606 
1607 	/* update the DB */
1608 	if (!tempop && ((status = i_dladm_aggr_modify_db(key, modify_mask,
1609 	    &new_attr, &old_attr, root)) != DLADM_STATUS_OK)) {
1610 		return (status);
1611 	}
1612 
1613 	status = i_dladm_aggr_modify_sys(key, modify_mask, &new_attr);
1614 	if (status != DLADM_STATUS_OK && !tempop) {
1615 		(void) i_dladm_aggr_modify_db(key, modify_mask, &old_attr,
1616 		    NULL, root);
1617 	}
1618 
1619 	return (status);
1620 }
1621 
1622 /*
1623  * Delete a previously created link aggregation group.
1624  */
1625 dladm_status_t
1626 dladm_aggr_delete(uint32_t key, boolean_t tempop, const char *root)
1627 {
1628 	dladm_aggr_grp_attr_db_t db_attr;
1629 	dladm_status_t status;
1630 
1631 	if (key == 0)
1632 		return (DLADM_STATUS_KEYINVAL);
1633 
1634 	if (tempop) {
1635 		dladm_aggr_down_t down;
1636 		dladm_aggr_grp_attr_t sys_attr;
1637 
1638 		down.ld_key = key;
1639 		down.ld_found = B_FALSE;
1640 		sys_attr.lg_key = key;
1641 		if (i_dladm_aggr_down((void *)&down, &sys_attr) < 0)
1642 			return (dladm_errno2status(errno));
1643 		else
1644 			return (DLADM_STATUS_OK);
1645 	} else {
1646 		status = dladm_aggr_down(key);
1647 
1648 		/*
1649 		 * Only continue to delete the configuration repository
1650 		 * either if we successfully delete the active aggregation
1651 		 * or if the aggregation is not found.
1652 		 */
1653 		if (status != DLADM_STATUS_OK &&
1654 		    status != DLADM_STATUS_NOTFOUND) {
1655 			return (status);
1656 		}
1657 	}
1658 
1659 	if (tempop)
1660 		return (DLADM_STATUS_OK);
1661 
1662 	db_attr.lt_key = key;
1663 	return (i_dladm_aggr_del_db(&db_attr, root));
1664 }
1665 
1666 /*
1667  * Add one or more ports to an existing link aggregation.
1668  */
1669 dladm_status_t
1670 dladm_aggr_add(uint32_t key, uint32_t nports, dladm_aggr_port_attr_db_t *ports,
1671     boolean_t tempop, const char *root)
1672 {
1673 	dladm_aggr_grp_attr_db_t attr;
1674 	dladm_status_t status;
1675 
1676 	if (key == 0)
1677 		return (DLADM_STATUS_KEYINVAL);
1678 
1679 	bzero(&attr, sizeof (attr));
1680 	attr.lt_key = key;
1681 	attr.lt_nports = nports;
1682 	attr.lt_ports = ports;
1683 
1684 	if (!tempop &&
1685 	    ((status = i_dladm_aggr_add_db(&attr, root)) != DLADM_STATUS_OK)) {
1686 		return (status);
1687 	}
1688 
1689 	status = i_dladm_aggr_add_rem_sys(&attr, LAIOC_ADD);
1690 	if (status != DLADM_STATUS_OK && !tempop)
1691 		(void) i_dladm_aggr_remove_db(&attr, root);
1692 
1693 	return (status);
1694 }
1695 
1696 /*
1697  * Remove one or more ports from an existing link aggregation.
1698  */
1699 dladm_status_t
1700 dladm_aggr_remove(uint32_t key, uint32_t nports,
1701     dladm_aggr_port_attr_db_t *ports, boolean_t tempop, const char *root)
1702 {
1703 	dladm_aggr_grp_attr_db_t attr;
1704 	dladm_status_t status;
1705 
1706 	if (key == 0)
1707 		return (DLADM_STATUS_KEYINVAL);
1708 
1709 	bzero(&attr, sizeof (attr));
1710 	attr.lt_key = key;
1711 	attr.lt_nports = nports;
1712 	attr.lt_ports = ports;
1713 
1714 	if (!tempop &&
1715 	    ((status = i_dladm_aggr_remove_db(&attr, root)) !=
1716 	    DLADM_STATUS_OK)) {
1717 		return (status);
1718 	}
1719 
1720 	status = i_dladm_aggr_add_rem_sys(&attr, LAIOC_REMOVE);
1721 	if (status != DLADM_STATUS_OK && !tempop)
1722 		(void) i_dladm_aggr_add_db(&attr, root);
1723 
1724 	return (status);
1725 }
1726