view usr/src/cmd/cmd-inet/usr.sbin/ilbadm/ilbadm_sg.c @ 10946:324bab2b3370

PSARC 2008/575 ILB: Integrated L3/L4 Load balancer 6882718 in-kernel simple L3/L4 load balancing service should be provided in Solaris 6884202 ipobs_hook() in ip_input() invalidates DB_REF assumption
author Sangeeta Misra <Sangeeta.Misra@Sun.COM>
date Tue, 03 Nov 2009 23:15:19 -0800
parents
children e5889df1eaac
line wrap: on
line source

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

/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <strings.h>
#include <stddef.h>
#include <assert.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/list.h>
#include <ofmt.h>
#include <libilb.h>
#include "ilbadm.h"

static ilbadm_key_name_t servrange_keys[] = {
	{ILB_KEY_SERVER, "server", "servers"},
	{ILB_KEY_SERVRANGE, "server", "servers"},
	{ILB_KEY_BAD, "", ""}
};

static ilbadm_key_name_t serverID_keys[] = {
	{ILB_KEY_SERVERID, "server", ""},
	{ILB_KEY_BAD, "", ""}
};

typedef struct sg_export_arg {
	FILE		*fp;
	ilbadm_sgroup_t	*sg;
} sg_export_arg_t;

typedef struct arg_struct {
	int		flags;
	char		*o_str;
	ofmt_field_t	*o_fields;
	ofmt_handle_t	oh;
} list_arg_t;

typedef struct sg_srv_o_struct {
	char		*sgname;
	ilb_server_data_t	*sd;
} sg_srv_o_arg_t;

static ofmt_cb_t of_sgname;
static ofmt_cb_t of_srvID;
static ofmt_cb_t of_port;
static ofmt_cb_t of_ip;

static ofmt_field_t sgfields_v4[] = {
	{"SGNAME", ILB_SGNAME_SZ, 0, of_sgname},
	{"SERVERID", ILB_NAMESZ, 0, of_srvID},
	{"MINPORT", 8, 0, of_port},
	{"MAXPORT", 8, 1, of_port},
	{"IP_ADDRESS", 15, 0, of_ip},
	{NULL, 0, 0, NULL}
};
static ofmt_field_t sgfields_v6[] = {
	{"SGNAME", ILB_SGNAME_SZ, 0, of_sgname},
	{"SERVERID", ILB_NAMESZ, 0, of_srvID},
	{"MINPORT", 8, 0, of_port},
	{"MAXPORT", 8, 1, of_port},
	{"IP_ADDRESS", 39, 0, of_ip},
	{NULL, 0, 0, NULL}
};

#define	MAXCOLS	80 /* make flexible? */

extern int	optind, optopt, opterr;
extern char	*optarg;

static boolean_t
of_sgname(ofmt_arg_t *of_arg, char *buf, uint_t bufsize)
{
	sg_srv_o_arg_t	*l = (sg_srv_o_arg_t *)of_arg->ofmt_cbarg;

	(void) strlcpy(buf, l->sgname, bufsize);
	return (B_TRUE);
}

static boolean_t
of_srvID(ofmt_arg_t *of_arg, char *buf, uint_t bufsize)
{
	sg_srv_o_arg_t	*l = (sg_srv_o_arg_t *)of_arg->ofmt_cbarg;

	(void) strlcpy(buf, l->sd->sd_srvID, bufsize);
	return (B_TRUE);
}

static boolean_t
of_port(ofmt_arg_t *of_arg, char *buf, uint_t bufsize)
{
	sg_srv_o_arg_t	*l = (sg_srv_o_arg_t *)of_arg->ofmt_cbarg;
	int		port;

	if (of_arg->ofmt_id == 0) {
		port = ntohs(l->sd->sd_minport);
		if (port == 0)
			*buf = '\0';
		else
			(void) snprintf(buf, bufsize, "%d", port);
	} else {
		port = ntohs(l->sd->sd_maxport);
		if (port == 0)
			*buf = '\0';
		else
			(void) snprintf(buf, bufsize, "%d", port);
	}
	return (B_TRUE);
}

static boolean_t
of_ip(ofmt_arg_t *of_arg, char *buf, uint_t bufsize)
{
	sg_srv_o_arg_t	*l = (sg_srv_o_arg_t *)of_arg->ofmt_cbarg;

	ip2str(&l->sd->sd_addr, buf, bufsize, V6_ADDRONLY);
	return (B_TRUE);
}

ilbadm_status_t
i_list_sg_srv_ofmt(char *sgname, ilb_server_data_t *sd, void *arg)
{
	list_arg_t	*larg = (list_arg_t *)arg;
	sg_srv_o_arg_t	line_arg;

	line_arg.sgname = sgname;
	line_arg.sd = sd;
	ofmt_print(larg->oh, &line_arg);
	return (ILBADM_OK);
}

/*
 * This function is always called via ilb_walk_servergroups()
 * and so must return libilb errors.
 * That's why we need to retain currently unused "h" argument
 */
/* ARGSUSED */
static ilb_status_t
ilbadm_list_sg_srv(ilb_handle_t h, ilb_server_data_t *sd, const char *sgname,
    void *arg)
{
	char		ip_str[2*INET6_ADDRSTRLEN + 3] = "";
	char		port_str[INET6_ADDRSTRLEN];
	list_arg_t	*larg = (list_arg_t *)arg;
	ofmt_status_t	oerr;
	int		oflags = 0;
	int		ocols = MAXCOLS;
	int		h_minport, h_maxport;
	static ofmt_handle_t	oh = (ofmt_handle_t)NULL;
	ofmt_field_t	*ofp;

	if (larg->o_str != NULL) {
		if (oh == NULL) {
			if (sd->sd_addr.ia_af == AF_INET)
				ofp = sgfields_v6;
			else
				ofp = sgfields_v4;

			if (larg->flags & ILBADM_LIST_PARSE)
				oflags |= OFMT_PARSABLE;

			oerr = ofmt_open(larg->o_str, ofp, oflags, ocols, &oh);
			if (oerr != OFMT_SUCCESS) {
				char	e[80];

				ilbadm_err(gettext("ofmt_open failed: %s"),
				    ofmt_strerror(oh, oerr, e, sizeof (e)));
				return (ILB_STATUS_GENERIC);
			}
			larg->oh = oh;
		}


		(void) i_list_sg_srv_ofmt((char *)sgname, sd, arg);
		return (ILB_STATUS_OK);
	}

	ip2str(&sd->sd_addr, ip_str, sizeof (ip_str), 0);

	h_minport = ntohs(sd->sd_minport);
	h_maxport = ntohs(sd->sd_maxport);
	if (h_minport == 0)
		*port_str = '\0';
	else if (h_maxport > h_minport)
		(void) sprintf(port_str, ":%d-%d", h_minport, h_maxport);
	else
		(void) sprintf(port_str, ":%d", h_minport);

	(void) printf("%s: id:%s %s%s\n", sgname,
	    sd->sd_srvID?sd->sd_srvID:"(null)", ip_str, port_str);
	return (ILB_STATUS_OK);
}

ilb_status_t
ilbadm_list_sg(ilb_handle_t h, ilb_sg_data_t *sg, void *arg)
{
	if (sg->sgd_srvcount == 0) {
		ilb_server_data_t	tmp_srv;

		bzero(&tmp_srv, sizeof (tmp_srv));
		return (ilbadm_list_sg_srv(h, &tmp_srv, sg->sgd_name, arg));
	}

	return (ilb_walk_servers(h, ilbadm_list_sg_srv, sg->sgd_name, arg));
}

static char *def_fields = "SGNAME,SERVERID,MINPORT,MAXPORT,IP_ADDRESS";

/* ARGSUSED */
ilbadm_status_t
ilbadm_show_servergroups(int argc, char *argv[])
{
	ilb_handle_t	h = ILB_INVALID_HANDLE;
	ilb_status_t	rclib = ILB_STATUS_OK;
	ilbadm_status_t	rc = ILBADM_OK;
	int		c;
	char		optstr[] = ":po:";

	boolean_t	o_opt = B_FALSE, p_opt = B_FALSE;
	list_arg_t	larg = {0, def_fields, NULL, NULL};

	while ((c = getopt(argc, argv, optstr)) != -1) {
		switch ((char)c) {
		case 'p': p_opt = B_TRUE;
			larg.flags |= ILBADM_LIST_PARSE;
			break;
		case 'o': larg.o_str = optarg;
			o_opt = B_TRUE;
			break;
		case ':': ilbadm_err(gettext("missing option argument"
			    " for %c"), (char)optopt);
			rc = ILBADM_LIBERR;
			goto out;
			/* not reached */
			break;
		default: unknown_opt(argv, optind-1);
			/* not reached */
			break;
		}
	}

	if (p_opt && !o_opt) {
		ilbadm_err(gettext("option -p requires -o"));
		exit(1);
	}

	if (p_opt && larg.o_str != NULL &&
	    (strcasecmp(larg.o_str, "all") == 0)) {
		ilbadm_err(gettext("option -p requires explicit field"
		    " names for -o"));
		exit(1);
	}

	rclib = ilb_open(&h);
	if (rclib != ILB_STATUS_OK)
		goto out;

	if (optind >= argc) {
		rclib = ilb_walk_servergroups(h, ilbadm_list_sg, NULL,
		    (void*)&larg);
		if (rclib != ILB_STATUS_OK)
			rc = ILBADM_LIBERR;
	} else {
		while (optind < argc) {
			rclib = ilb_walk_servergroups(h, ilbadm_list_sg,
			    argv[optind++], (void*)&larg);
			if (rclib != ILB_STATUS_OK) {
				rc = ILBADM_LIBERR;
				break;
			}
		}
	}

	if (larg.oh != NULL)
		ofmt_close(larg.oh);
out:
	if (h != ILB_INVALID_HANDLE)
		(void) ilb_close(h);

	if (rclib != ILB_STATUS_OK) {
		/*
		 * The show function returns ILB_STATUS_GENERIC after printing
		 * out an error message.  So we don't need to print it again.
		 */
		if (rclib != ILB_STATUS_GENERIC)
			ilbadm_err(ilb_errstr(rclib));
		rc = ILBADM_LIBERR;
	}

	return (rc);
}

ilbadm_servnode_t *
i_new_sg_elem(ilbadm_sgroup_t *sgp)
{
	ilbadm_servnode_t *s;

	s = (ilbadm_servnode_t *)calloc(sizeof (*s), 1);
	if (s != NULL) {
		list_insert_tail(&sgp->sg_serv_list, s);
		sgp->sg_count++;
	}
	return (s);
}

static ilbadm_status_t
i_parse_servrange_list(char *arg, ilbadm_sgroup_t *sgp)
{
	ilbadm_status_t	rc;
	int		count;

	rc = i_parse_optstring(arg, (void *) sgp, servrange_keys,
	    OPT_VALUE_LIST|OPT_IP_RANGE|OPT_PORTS, &count);
	return (rc);
}

static ilbadm_status_t
i_parse_serverIDs(char *arg, ilbadm_sgroup_t *sgp)
{
	ilbadm_status_t	rc;
	int		count;

	rc = i_parse_optstring(arg, (void *) sgp, serverID_keys,
	    OPT_VALUE_LIST|OPT_PORTS, &count);
	return (rc);
}

static ilbadm_status_t
i_mod_sg(ilb_handle_t h, ilbadm_sgroup_t *sgp, ilbadm_cmd_t cmd,
    int flags)
{
	ilbadm_servnode_t	*sn;
	ilb_server_data_t	*srv;
	ilb_status_t		rclib = ILB_STATUS_OK;
	ilbadm_status_t		rc = ILBADM_OK;

	if (h == ILB_INVALID_HANDLE && cmd != cmd_enable_server &&
	    cmd != cmd_disable_server)
		return (ILBADM_LIBERR);

	sn = list_head(&sgp->sg_serv_list);
	while (sn != NULL) {
		srv = &sn->s_spec;

		srv->sd_flags |= flags;
		if (cmd == cmd_create_sg || cmd == cmd_add_srv) {
			rclib = ilb_add_server_to_group(h, sgp->sg_name,
			    srv);
			if (rclib != ILB_STATUS_OK) {
				char	buf[INET6_ADDRSTRLEN + 1];

				rc = ILBADM_LIBERR;
				ip2str(&srv->sd_addr, buf, sizeof (buf),
				    V6_ADDRONLY);
				ilbadm_err(gettext("cannot add %s to %s: %s"),
				    buf, sgp->sg_name, ilb_errstr(rclib));
				/* if we created the SG, we bail out */
				if (cmd == cmd_create_sg)
					return (rc);
			}
		} else {
			assert(cmd == cmd_rem_srv);
			rclib = ilb_rem_server_from_group(h, sgp->sg_name,
			    srv);
			/* if we fail, we tell user and continue */
			if (rclib != ILB_STATUS_OK) {
				rc = ILBADM_LIBERR;
				ilbadm_err(
				    gettext("cannot remove %s from %s: %s"),
				    srv->sd_srvID, sgp->sg_name,
				    ilb_errstr(rclib));
			}
		}

		/*
		 * list_next returns NULL instead of cycling back to head
		 * so we don't have to check for list_head explicitly.
		 */
		sn = list_next(&sgp->sg_serv_list, sn);
	};

	return (rc);
}

static void
i_ilbadm_alloc_sgroup(ilbadm_sgroup_t **sgp)
{
	ilbadm_sgroup_t	*sg;

	*sgp = sg = (ilbadm_sgroup_t *)calloc(sizeof (*sg), 1);
	if (sg == NULL)
		return;
	list_create(&sg->sg_serv_list, sizeof (ilbadm_servnode_t),
	    offsetof(ilbadm_servnode_t, s_link));
}

static void
i_ilbadm_free_sgroup(ilbadm_sgroup_t *sg)
{
	ilbadm_servnode_t	*s;

	while ((s = list_remove_head(&sg->sg_serv_list)) != NULL)
		free(s);

	list_destroy(&sg->sg_serv_list);
}

ilbadm_status_t
ilbadm_create_servergroup(int argc, char *argv[])
{
	ilb_handle_t	h = ILB_INVALID_HANDLE;
	ilb_status_t	rclib = ILB_STATUS_OK;
	ilbadm_status_t	rc = ILBADM_OK;
	ilbadm_sgroup_t	*sg;
	int		c;
	int		flags = 0;

	i_ilbadm_alloc_sgroup(&sg);

	while ((c = getopt(argc, argv, ":s:")) != -1) {
		switch ((char)c) {
		case 's':
			rc = i_parse_servrange_list(optarg, sg);
			break;
		case ':':
			ilbadm_err(gettext("missing option-argument for"
			    " %c"), (char)optopt);
			rc = ILBADM_LIBERR;
			break;
		case '?':
		default:
			unknown_opt(argv, optind-1);
			/* not reached */
			break;
		}

		if (rc != ILBADM_OK)
			goto out;
	}

	if (optind >= argc) {
		ilbadm_err(gettext("missing mandatory arguments - please refer"
		    " to 'create-servergroup' subcommand"
		    "  description in ilbadm(1M)"));
		rc = ILBADM_LIBERR;
		goto out;
	}

	if (strlen(argv[optind]) > ILB_SGNAME_SZ - 1) {
		ilbadm_err(gettext("servergroup name %s is too long -"
		    " must not exceed %d chars"), argv[optind],
		    ILB_SGNAME_SZ - 1);
		rc = ILBADM_LIBERR;
		goto out;
	}

	sg->sg_name = argv[optind];

	rclib = ilb_open(&h);
	if (rclib != ILB_STATUS_OK)
		goto out;

	rclib = ilb_create_servergroup(h, sg->sg_name);
	if (rclib != ILB_STATUS_OK)
		goto out;

	/* we create a servergroup with all servers enabled */
	ILB_SET_ENABLED(flags);
	rc = i_mod_sg(h, sg, cmd_create_sg, flags);

	if (rc != ILBADM_OK)
		(void) ilb_destroy_servergroup(h, sg->sg_name);

out:
	i_ilbadm_free_sgroup(sg);
	if (h != ILB_INVALID_HANDLE)
		(void) ilb_close(h);

	if (rclib != ILB_STATUS_OK) {
		ilbadm_err(ilb_errstr(rclib));
		rc = ILBADM_LIBERR;
	}
	if ((rc != ILBADM_OK) && (rc != ILBADM_LIBERR))
		ilbadm_err(ilbadm_errstr(rc));

	return (rc);
}

ilbadm_status_t
ilbadm_add_server_to_group(int argc, char **argv)
{
	ilb_handle_t	h = ILB_INVALID_HANDLE;
	ilb_status_t	rclib = ILB_STATUS_OK;
	ilbadm_status_t	rc = ILBADM_OK;
	ilbadm_sgroup_t	*sg;
	int		c;
	int		flags = 0;

	i_ilbadm_alloc_sgroup(&sg);

	while ((c = getopt(argc, argv, ":s:")) != -1) {
		switch ((char)c) {
		case 's':
			rc = i_parse_servrange_list(optarg, sg);
			break;
		case ':':
			ilbadm_err(gettext("missing option-argument for"
			    " %c"), (char)optopt);
			rc = ILBADM_LIBERR;
			break;
		case '?':
		default: unknown_opt(argv, optind-1);
			/* not reached */
			break;
		}

		if (rc != ILBADM_OK)
			goto out;
	}

	if (optind >= argc) {
		ilbadm_err(gettext("missing mandatory arguments - please refer"
		    " to 'add-server' subcommand description in ilbadm(1M)"));
		rc = ILBADM_LIBERR;
		goto out;
	}

	sg->sg_name = argv[optind];

	rclib = ilb_open(&h);
	if (rclib != ILB_STATUS_OK)
		goto out;

	/* A server is added enabled */
	ILB_SET_ENABLED(flags);
	rc = i_mod_sg(h, sg, cmd_add_srv, flags);
out:
	i_ilbadm_free_sgroup(sg);
	if (h != ILB_INVALID_HANDLE)
		(void) ilb_close(h);

	if ((rc != ILBADM_OK) && (rc != ILBADM_LIBERR))
		ilbadm_err(ilbadm_errstr(rc));
	return (rc);
}

/* ARGSUSED */
static ilbadm_status_t
ilbadm_Xable_server(int argc, char *argv[], ilbadm_cmd_t cmd)
{
	ilb_handle_t		h = ILB_INVALID_HANDLE;
	ilbadm_status_t		rc = ILBADM_OK;
	ilb_status_t		rclib = ILB_STATUS_OK;
	int			i;

	if (argc < 2) {
		ilbadm_err(gettext("missing required argument"
		    " (server specification)"));
		rc = ILBADM_LIBERR;
		goto out;
	}

	rclib = ilb_open(&h);
	if (rclib != ILB_STATUS_OK)
		goto out;

	/* enable-server and disable-server only accepts serverids */
	for (i = 1; i < argc && rclib == ILB_STATUS_OK; i++) {
		ilb_server_data_t	srv;

		if (argv[i][0] != ILB_SRVID_PREFIX) {
			rc = ILBADM_INVAL_SRVID;
			goto out;
		}

		bzero(&srv, sizeof (srv));
		/* to do: check length */
		(void) strlcpy(srv.sd_srvID, argv[i], sizeof (srv.sd_srvID));
		switch (cmd) {
		case cmd_enable_server:
			rclib = ilb_enable_server(h, &srv, NULL);
			break;
		case cmd_disable_server:
			rclib = ilb_disable_server(h, &srv, NULL);
			break;
		}

		/* if we can't find a given server ID, just plough on */
		if (rclib == ILB_STATUS_ENOENT) {
			const char *msg = ilb_errstr(rclib);

			rc = ILBADM_LIBERR;
			ilbadm_err("%s: %s", msg, argv[i]);
			rclib = ILB_STATUS_OK;
			continue;
		}
		if (rclib != ILB_STATUS_OK)
			break;
	}
out:
	if (h != ILB_INVALID_HANDLE)
		(void) ilb_close(h);

	if (rclib != ILB_STATUS_OK) {
		ilbadm_err(ilb_errstr(rclib));
		rc = ILBADM_LIBERR;
	}

	if ((rc != ILBADM_OK) && (rc != ILBADM_LIBERR))
		ilbadm_err(ilbadm_errstr(rc));
	return (rc);
}

ilbadm_status_t
ilbadm_disable_server(int argc, char *argv[])
{
	return (ilbadm_Xable_server(argc, argv, cmd_disable_server));
}

ilbadm_status_t
ilbadm_enable_server(int argc, char *argv[])
{
	return (ilbadm_Xable_server(argc, argv, cmd_enable_server));
}

/* ARGSUSED */
ilbadm_status_t
ilbadm_rem_server_from_group(int argc, char *argv[])
{
	ilb_handle_t	h = ILB_INVALID_HANDLE;
	ilb_status_t	rclib = ILB_STATUS_OK;
	ilbadm_status_t	rc = ILBADM_OK;
	ilbadm_sgroup_t	*sg;
	int		c;

	i_ilbadm_alloc_sgroup(&sg);

	while ((c = getopt(argc, argv, ":s:")) != -1) {
		switch ((char)c) {
		case 's':
			rc = i_parse_serverIDs(optarg, sg);
			break;
		case ':':
			ilbadm_err(gettext("missing option-argument for"
			    " %c"), (char)optopt);
			rc = ILBADM_LIBERR;
			break;
		case '?':
		default: unknown_opt(argv, optind-1);
			/* not reached */
			break;
		}
		if (rc != ILBADM_OK)
			goto out;
	}

	/* we need servergroup name and at least one serverID to remove */
	if (optind >= argc || sg->sg_count == 0) {
		rc = ILBADM_ENOOPTION;
		goto out;
	}

	sg->sg_name = argv[optind];

	rclib = ilb_open(&h);
	if (rclib != ILB_STATUS_OK)
		goto out;

	rc = i_mod_sg(h, sg, cmd_rem_srv, 0);
out:
	i_ilbadm_free_sgroup(sg);

	if (h != ILB_INVALID_HANDLE)
		(void) ilb_close(h);
	if ((rc != ILBADM_OK) && (rc != ILBADM_LIBERR))
		ilbadm_err(ilbadm_errstr(rc));
	return (rc);
}

ilbadm_status_t
ilbadm_destroy_servergroup(int argc, char *argv[])
{
	ilb_handle_t	h = ILB_INVALID_HANDLE;
	ilb_status_t	rclib = ILB_STATUS_OK;
	ilbadm_status_t	rc = ILBADM_OK;
	char		*sgname;

	if (argc != 2) {
		ilbadm_err(gettext("usage:ilbadm"
		    " delete-servergroup groupname"));
		rc = ILBADM_LIBERR;
		goto out;
	}

	sgname = argv[1];

	rclib = ilb_open(&h);
	if (rclib != ILB_STATUS_OK)
		goto out;

	rclib = ilb_destroy_servergroup(h, sgname);
out:
	if (h != ILB_INVALID_HANDLE)
		(void) ilb_close(h);

	if (rclib != ILB_STATUS_OK) {
		ilbadm_err(ilb_errstr(rclib));
		rc = ILBADM_LIBERR;
	}

	return (rc);
}

#define	BUFSZ	1024

static int
export_srv_spec(ilb_server_data_t *srv, char *buf, const int bufsize)
{
	int	len = 0, bufsz = (int)bufsize;

	ip2str(&srv->sd_addr, buf, bufsz, 0);

	len += strlen(buf);
	bufsz -= len;

	if (srv->sd_minport != 0) {
		in_port_t	h_min, h_max;
		int		inc;

		h_min = ntohs(srv->sd_minport);
		h_max = ntohs(srv->sd_maxport);

		/* to do: if service name was given, print that, not number */
		if (h_max <= h_min)
			inc = snprintf(buf+len, bufsz, ":%d", h_min);
		else
			inc = snprintf(buf+len, bufsz, ":%d-%d", h_min, h_max);

		if (inc > bufsz) /* too little space */
			return (-1);
		len += inc;
	}

	return (len);
}


/*
 * this is called by ilb_walk_servers(), therefore we return ilb_status_t
 * not ilbadm_status, and retain an unused function argument
 */
/* ARGSUSED */
ilb_status_t
ilbadm_export_a_srv(ilb_handle_t h, ilb_server_data_t *srv, const char *sgname,
    void *arg)
{
	sg_export_arg_t	*larg = (sg_export_arg_t *)arg;
	FILE		*fp = larg->fp;
	char		linebuf[BUFSZ]; /* XXXms make that dynamic */
	int		sz = BUFSZ;

	if (export_srv_spec(srv, linebuf, sz) == -1)
		return (ILB_STATUS_OK);

	(void) fprintf(fp, "add-server -s server=");

	(void) fprintf(fp, "%s %s\n", linebuf, sgname);
	return (ILB_STATUS_OK);
}

ilb_status_t
ilbadm_export_sg(ilb_handle_t h, ilb_sg_data_t *sg, void *arg)
{
	ilb_status_t	rc = ILB_STATUS_OK;
	sg_export_arg_t	*larg = (sg_export_arg_t *)arg;
	FILE		*fp = larg->fp;

	(void) fprintf(fp, "create-servergroup %s\n", sg->sgd_name);
	if (sg->sgd_srvcount == 0)
		return (ILB_STATUS_OK);

	rc = ilb_walk_servers(h, ilbadm_export_a_srv, sg->sgd_name, arg);
	if (rc != ILB_STATUS_OK)
		goto out;

	if (fflush(fp) == EOF)
		rc = ILB_STATUS_WRITE;

out:
	return (rc);
}

ilbadm_status_t
ilbadm_export_servergroups(ilb_handle_t h, FILE *fp)
{
	ilb_status_t	rclib = ILB_STATUS_OK;
	ilbadm_status_t	rc = ILBADM_OK;
	sg_export_arg_t	arg;

	arg.fp = fp;
	arg.sg = NULL;

	rclib = ilb_walk_servergroups(h, ilbadm_export_sg, NULL, (void *)&arg);
	if (rclib != ILB_STATUS_OK) {
		ilbadm_err(ilb_errstr(rclib));
		rc = ILBADM_LIBERR;
	}

	return (rc);
}