/*
 * 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 <stdlib.h>
#include <stdio.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/list.h>
#include <libilb.h>
#include <assert.h>
#include <libscf.h>
#include "libilb_impl.h"
#include "ilbd.h"

#define	ILBD_PG_NAME_RULE "rule_"
#define	ILBD_PG_NAME_SG "sg_"
#define	ILBD_PG_NAME_HC "hc_"
#define	ILBD_SVC_FMRI "svc:/network/loadbalancer/ilb"
#define	ILBD_INST_NAME "default"

typedef enum {
	ILBD_RULE_STATUS,
	ILBD_RULE_VIP,
	ILBD_RULE_PROTO,
	ILBD_RULE_PORT,
	ILBD_RULE_ALGO,
	ILBD_RULE_TOPO,
	ILBD_RULE_NAT_STR,
	ILBD_RULE_NAT_END,
	ILBD_RULE_STI_MASK,
	ILBD_RULE_SGNAME,
	ILBD_RULE_HCNAME,
	ILBD_RULE_HCPORT,
	ILBD_RULE_HCPFLAG,
	ILBD_RULE_DRAINTIME,
	ILBD_RULE_NAT_TO,
	ILBD_RULE_PERS_TO,

	ILBD_SG_SERVER,

	ILBD_HC_TEST,
	ILBD_HC_TIMEOUT,
	ILBD_HC_INTERVAL,
	ILBD_HC_DEF_PING,
	ILBD_HC_COUNT,

	ILBD_VAR_INVALID
} ilbd_var_type_t;

typedef struct prop_tbl_entry {
	ilbd_var_type_t val_type;
	const char *scf_propname;
	scf_type_t scf_proptype;
} prop_tbl_entry_t;

/*
 * this table contains a map of all SCF properties, including rules,
 * servergroups and health checks. The place to add new property needs to be
 * watched carefully. When new properties are added, corresponding *VAR_NUM
 * needs to be adjusted to reflect the correct index of the table
 */
prop_tbl_entry_t prop_tbl[] = {
	/* entried for rule */
	{ILBD_RULE_STATUS, "status", SCF_TYPE_BOOLEAN},
	/* SCF_TYPE_NET_ADDR_V4 or SCF_TYPE_NET_ADDR_V6 */
	{ILBD_RULE_VIP, "vip", SCF_TYPE_INVALID},
	{ILBD_RULE_PROTO, "protocol", SCF_TYPE_ASTRING},
	{ILBD_RULE_PORT, "port", SCF_TYPE_ASTRING},
	{ILBD_RULE_ALGO, "ilb-algo", SCF_TYPE_ASTRING},
	{ILBD_RULE_TOPO, "ilb-type", SCF_TYPE_ASTRING},
	{ILBD_RULE_NAT_STR, "ilb-nat-start", SCF_TYPE_INVALID},
	{ILBD_RULE_NAT_END, "ilb-nat-end", SCF_TYPE_INVALID},
	{ILBD_RULE_STI_MASK, "ilb-sti-mask", SCF_TYPE_INVALID},
	{ILBD_RULE_SGNAME, "servergroup", SCF_TYPE_ASTRING},
	{ILBD_RULE_HCNAME, "healthcheck", SCF_TYPE_ASTRING},
	{ILBD_RULE_HCPORT, "hc-port", SCF_TYPE_INTEGER},
	{ILBD_RULE_HCPFLAG, "hcp-flag", SCF_TYPE_INTEGER},
	{ILBD_RULE_DRAINTIME, "drain-time", SCF_TYPE_INTEGER},
	{ILBD_RULE_NAT_TO, "nat-timeout", SCF_TYPE_INTEGER},
	{ILBD_RULE_PERS_TO, "pers-timeout", SCF_TYPE_INTEGER},
	/* add new rule related prop here */
	/* entries for sg */
	{ILBD_SG_SERVER, "server", SCF_TYPE_ASTRING},
	/* add new sg related prop here */
	/* entries for hc */
	{ILBD_HC_TEST, "test", SCF_TYPE_ASTRING},
	{ILBD_HC_TIMEOUT, "timeout", SCF_TYPE_INTEGER},
	{ILBD_HC_INTERVAL, "interval", SCF_TYPE_INTEGER},
	{ILBD_HC_DEF_PING, "ping", SCF_TYPE_BOOLEAN},
	/* add new hc related prop here */
	{ILBD_HC_COUNT, "count", SCF_TYPE_INTEGER}
};

#define	ILBD_PROP_VAR_NUM (ILBD_HC_COUNT + 1)
#define	ILBD_RULE_VAR_NUM (ILBD_SG_SERVER)
#define	ILBD_SG_VAR_NUM (ILBD_HC_TEST - ILBD_SG_SERVER)
#define	ILBD_HC_VAR_NUM (ILBD_PROP_VAR_NUM - ILBD_HC_TEST)

static ilb_status_t ilbd_scf_set_prop(scf_propertygroup_t *, const char *,
    scf_type_t, scf_value_t *);
static ilb_status_t ilbd_scf_retrieve_pg(const char *, scf_propertygroup_t **,
    boolean_t);
static ilb_status_t ilbd_scf_delete_pg(scf_propertygroup_t *);
static ilb_status_t ilbd_scf_get_prop_val(scf_propertygroup_t *, const char *,
    scf_value_t **);

#define	MIN(a, b)	((a) < (b) ? (a) : (b))

int
ilbd_scf_limit(int type)
{
	return (MIN(scf_limit(type), 120));
}

/*
 * Translate libscf error to libilb status
 */
ilb_status_t
ilbd_scf_err_to_ilb_err()
{
	switch (scf_error()) {
	case SCF_ERROR_NONE:
		return (ILB_STATUS_OK);
	case SCF_ERROR_HANDLE_MISMATCH:
	case SCF_ERROR_HANDLE_DESTROYED:
	case SCF_ERROR_VERSION_MISMATCH:
	case SCF_ERROR_NOT_BOUND:
	case SCF_ERROR_CONSTRAINT_VIOLATED:
	case SCF_ERROR_NOT_SET:
	case SCF_ERROR_TYPE_MISMATCH:
	case SCF_ERROR_INVALID_ARGUMENT:
		return (ILB_STATUS_EINVAL);
	case SCF_ERROR_NO_MEMORY:
	case SCF_ERROR_NO_RESOURCES:
		return (ILB_STATUS_ENOMEM);
	case SCF_ERROR_NOT_FOUND:
	case SCF_ERROR_DELETED:
		return (ILB_STATUS_ENOENT);
	case SCF_ERROR_EXISTS:
		return (ILB_STATUS_EEXIST);
	case SCF_ERROR_PERMISSION_DENIED:
		return (ILB_STATUS_PERMIT);
	case SCF_ERROR_CALLBACK_FAILED:
		return (ILB_STATUS_CALLBACK);
	case SCF_ERROR_IN_USE:
		return (ILB_STATUS_INUSE);
	default:
		return (ILB_STATUS_INTERNAL);
	}
}

static void
ilbd_name_to_scfpgname(ilbd_scf_pg_type_t pg_type, const char *pgname,
    char *scf_pgname)
{
	switch (pg_type) {
	case ILBD_SCF_RULE:
		(void) snprintf(scf_pgname, ILBD_MAX_NAME_LEN,
		    ILBD_PG_NAME_RULE "%s", pgname);
		return;
	case ILBD_SCF_SG:
		(void) snprintf(scf_pgname, ILBD_MAX_NAME_LEN,
		    ILBD_PG_NAME_SG "%s", pgname);
		return;
	case ILBD_SCF_HC:
		(void) snprintf(scf_pgname, ILBD_MAX_NAME_LEN,
		    ILBD_PG_NAME_HC "%s", pgname);
		return;
	/* Should not happen.  Log it and put ILB service in maintenance. */
	default:
		logerr("ilbd_name_to_scfpgname: invalid pg type %d for pg %s",
		    pg_type, pgname);
		(void) smf_maintain_instance(ILB_FMRI, SMF_IMMEDIATE);
		exit(EXIT_FAILURE);
		return;
	}
}

static void
ilbd_scf_destroy(scf_handle_t *h, scf_service_t *s, scf_instance_t *inst,
    scf_propertygroup_t *pg)
{
	if (pg != NULL)
		scf_pg_destroy(pg);
	if (inst != NULL)
		scf_instance_destroy(inst);
	if (s != NULL)
		scf_service_destroy(s);
	if (h != NULL)
		scf_handle_destroy(h);
}


static ilb_status_t
ilbd_scf_get_inst(scf_handle_t **h, scf_service_t **svc, scf_instance_t **inst)
{
	if ((*h = scf_handle_create(SCF_VERSION)) == NULL)
		return (ILB_STATUS_INTERNAL);

	if (scf_handle_bind(*h) != 0) {
		ilbd_scf_destroy(*h, NULL, NULL, NULL);
		return (ilbd_scf_err_to_ilb_err());
	}

	if ((*svc = scf_service_create(*h)) == NULL) {
		ilbd_scf_destroy(*h, NULL, NULL, NULL);
		return (ilbd_scf_err_to_ilb_err());
	}

	if (scf_handle_decode_fmri(*h, ILBD_SVC_FMRI, NULL, *svc, NULL, NULL,
	    NULL, SCF_DECODE_FMRI_EXACT) != 0) {
		ilbd_scf_destroy(*h, *svc, NULL, NULL);
		return (ilbd_scf_err_to_ilb_err());
	}

	if ((*inst = scf_instance_create(*h)) == NULL) {
		ilbd_scf_destroy(*h, *svc, NULL, NULL);
		return (ilbd_scf_err_to_ilb_err());
	}

	if (scf_service_get_instance(*svc, ILBD_INST_NAME, *inst) != 0) {
		ilbd_scf_destroy(*h, *svc, *inst, NULL);
		return (ilbd_scf_err_to_ilb_err());
	}
	return (ILB_STATUS_OK);
}

/*
 * If create is set, create a new prop group, destroy the old one if exists.
 * If create not set, try to find the prop group with given name.
 * The created or found entry is returned as *pg.
 * Caller frees *pg and its handle scf_pg_handle(pg)
 */
static ilb_status_t
ilbd_scf_retrieve_pg(const char *pgname, scf_propertygroup_t **pg,
    boolean_t create)
{
	scf_instance_t *inst;
	scf_handle_t *h;
	scf_service_t *svc;
	ilb_status_t ret;

	ret = ilbd_scf_get_inst(&h, &svc, &inst);
	if (ret != ILB_STATUS_OK)
		return (ret);

	*pg = scf_pg_create(h);
	if (*pg == NULL)
		return (ILB_STATUS_INTERNAL);

	if (scf_instance_get_pg(inst, pgname, *pg) != 0) {
		if (scf_error() != SCF_ERROR_NOT_FOUND ||
		    (scf_error() == SCF_ERROR_NOT_FOUND && (!create))) {
			ilbd_scf_destroy(h, svc, inst, *pg);
			*pg = NULL;
			return (ilbd_scf_err_to_ilb_err());
		}
	} else {
		/*
		 * Found pg, don't want to create, return EEXIST.  Note that
		 * h cannot be destroyed here since the caller needs to use it.
		 * The caller gets it by calling scf_pg_handle().
		 */
		if (!create) {
			ilbd_scf_destroy(NULL, svc, inst, NULL);
			return (ILB_STATUS_EEXIST);
		}
		/* found pg, need to create, destroy the existing one */
		else
			(void) ilbd_scf_delete_pg(*pg);
	}

	if (create) {
		if (scf_instance_add_pg(inst, pgname,
		    SCF_GROUP_APPLICATION, 0, *pg) != 0) {
			ilbd_scf_destroy(h, svc, inst, *pg);
			*pg = NULL;
			return (ilbd_scf_err_to_ilb_err());
		}
	}

	/*
	 * Note that handle cannot be destroyed here, caller sometimes needs
	 * to use it.  It gets the handle by calling scf_pg_handle().
	 */
	ilbd_scf_destroy(NULL, svc, inst, NULL);
	return (ILB_STATUS_OK);
}

struct algo_tbl_entry {
	ilb_algo_t algo_type;
	const char *algo_str;
} algo_tbl[] = {
	{ILB_ALG_ROUNDROBIN, "ROUNDROBIN"},
	{ILB_ALG_HASH_IP, "HASH-IP"},
	{ILB_ALG_HASH_IP_SPORT, "HASH-IP-PORT"},
	{ILB_ALG_HASH_IP_VIP, "HASH-IP-VIP"}
};

#define	ILBD_ALGO_TBL_SIZE (sizeof (algo_tbl) / \
	sizeof (*algo_tbl))

void
ilbd_algo_to_str(ilb_algo_t algo_type, char *valstr)
{
	int i;

	for (i = 0; i < ILBD_ALGO_TBL_SIZE; i++) {
		if (algo_type == algo_tbl[i].algo_type) {
			(void) strlcpy(valstr, algo_tbl[i].algo_str,
			    ILBD_MAX_VALUE_LEN);
			return;
		}
	}
	logerr("ilbd_algo_to_str: algo not found");
}

static void
ilbd_scf_str_to_algo(ilb_algo_t *algo_type, char *valstr)
{
	int i;

	for (i = 0; i < ILBD_ALGO_TBL_SIZE; i++) {
		if (strcmp(valstr, algo_tbl[i].algo_str) == 0) {
			*algo_type = algo_tbl[i].algo_type;
			return;
		}
	}
	logerr("ilbd_scf_str_to_algo: algo not found");
}

struct topo_tbl_entry {
	ilb_topo_t topo_type;
	const char *topo_str;
} topo_tbl[] = {
	{ILB_TOPO_DSR, "DSR"},
	{ILB_TOPO_NAT, "NAT"},
	{ILB_TOPO_HALF_NAT, "HALF-NAT"}
};

#define	ILBD_TOPO_TBL_SIZE (sizeof (topo_tbl) / \
	sizeof (*topo_tbl))

void
ilbd_topo_to_str(ilb_topo_t topo_type, char *valstr)
{
	int i;

	for (i = 0; i < ILBD_TOPO_TBL_SIZE; i++) {
		if (topo_type == topo_tbl[i].topo_type) {
			(void) strlcpy(valstr, topo_tbl[i].topo_str,
			    ILBD_MAX_VALUE_LEN);
			return;
		}
	}
	logerr("ilbd_scf_topo_to_str: topo not found");
}

static void
ilbd_scf_str_to_topo(ilb_topo_t *topo_type, char *valstr)
{
	int i;

	for (i = 0; i < ILBD_TOPO_TBL_SIZE; i++) {
		if (strcmp(valstr, topo_tbl[i].topo_str) == 0) {
			*topo_type = topo_tbl[i].topo_type;
			return;
		}
	}
	logerr("ilbd_scf_str_to_topo: topo not found");
}

static void
ilbd_get_svr_field(char *valstr, struct in6_addr *sgs_addr,
    int32_t *min_port, int32_t *max_port, int32_t *sgs_flags)
{
	char *ipaddr, *ipverstr, *portstr, *flagstr;
	int ip_ver;
	ilb_ip_addr_t temp_ip;
	void *addrptr;
	char *max_portstr;

	ipaddr = strtok(valstr, ";");
	ipverstr = strtok(NULL, ";");
	portstr = strtok(NULL, ";");
	flagstr = strtok(NULL, ";");

	if (ipaddr == NULL || ipverstr == NULL || portstr == NULL ||
	    flagstr == NULL) {
		logerr("%s: invalid server fields", __func__);
		(void) smf_maintain_instance(ILB_FMRI, SMF_IMMEDIATE);
		exit(EXIT_FAILURE);
	}
	ip_ver = atoi(ipverstr);
	addrptr = (ip_ver == AF_INET) ? (void *)&temp_ip.ia_v4 :
	    (void *)&temp_ip.ia_v6;
	if (inet_pton(ip_ver, ipaddr, addrptr) == NULL) {
		logerr("ilbd_get_svr_field: inet_pton failed");
		return;
	}

	if (ip_ver == AF_INET) {
		IN6_INADDR_TO_V4MAPPED(&(temp_ip.ia_v4), sgs_addr);
	} else {
		(void) memcpy(sgs_addr, &(temp_ip.ia_v6),
		    sizeof (struct in6_addr));
	}

	*sgs_flags = atoi(flagstr);
	*min_port = atoi(strtok(portstr, "-"));
	*min_port = ntohs(*min_port);
	max_portstr = strtok(NULL, "-");
	if (max_portstr != NULL) {
		*max_port = atoi(max_portstr);
		*max_port = ntohs(*max_port);
	}
}

/*
 * Convert the info of a server to its SCF string value representation.
 * Argument value is assumed to be of size ILBD_MAX_VALUE_LEN.
 */
static void
ilbd_srv_scf_val(ilbd_srv_t *srv, char *value)
{
	char ipstr[INET6_ADDRSTRLEN];
	int ipver;

	if (GET_AF(&srv->isv_addr) == AF_INET) {
		struct in_addr v4_addr;

		IN6_V4MAPPED_TO_INADDR(&srv->isv_addr, &v4_addr);
		(void) inet_ntop(AF_INET, &v4_addr, ipstr, sizeof (ipstr));
		ipver = AF_INET;
	} else {
		(void) inet_ntop(AF_INET6, &srv->isv_addr, ipstr,
		    sizeof (ipstr));
		ipver = AF_INET6;
	}
	(void) snprintf(value, ILBD_MAX_VALUE_LEN, "%s;%d;%d-%d;%d",
	    ipstr, ipver, ntohs(srv->isv_minport), ntohs(srv->isv_maxport),
	    srv->isv_flags);
}

/* get the "ip:port:status" str of the #num server in the servergroup */
ilb_status_t
ilbd_get_svr_info(ilbd_sg_t *sg, int num, char *valstr, char *svrname)
{
	int i;
	ilbd_srv_t *tmp_srv = NULL;

	tmp_srv = list_head(&sg->isg_srvlist);
	if (tmp_srv == NULL)
		return (ILB_STATUS_ENOENT);

	for (i = 0; i < num; i++)
		tmp_srv = list_next(&sg->isg_srvlist, tmp_srv);

	assert(tmp_srv != NULL);
	if (valstr != NULL)
		ilbd_srv_scf_val(tmp_srv, valstr);

	if (svrname != NULL) {
		(void) snprintf(svrname, ILBD_MAX_NAME_LEN, "server%d",
		    tmp_srv->isv_id);
	}

	return (ILB_STATUS_OK);
}

/* convert a struct in6_addr to valstr */
ilb_status_t
ilbd_scf_ip_to_str(uint16_t ipversion, struct in6_addr *addr,
    scf_type_t *scftype, char *valstr)
{
	size_t vallen;
	ilb_ip_addr_t ipaddr;
	void *addrptr;

	vallen = (ipversion == AF_INET) ? INET_ADDRSTRLEN :
	    INET6_ADDRSTRLEN;
	if (scftype != NULL)
		*scftype = (ipversion == AF_INET) ? SCF_TYPE_NET_ADDR_V4 :
		    SCF_TYPE_NET_ADDR_V6;

	IP_COPY_IMPL_2_CLI(addr, &ipaddr);
	addrptr = (ipversion == AF_INET) ?
	    (void *)&ipaddr.ia_v4 : (void *)&ipaddr.ia_v6;
	(void) inet_ntop(ipversion, (void *)addrptr, valstr, vallen);
	return (ILB_STATUS_OK);
}

/*
 * This function takes a ilbd internal data struct and translate its value to
 * scf value. The data struct is passed in within "data".
 * Upon successful return, the scf val will be stored in "val" and the scf type
 * will be returned in "scftype" if scftype != NULL, the number of values
 * translated will be in "numval"
 * If it failed, no data will be written to SCF
 */
static ilb_status_t
ilbd_data_to_scfval(ilbd_scf_pg_type_t pg_type, ilbd_var_type_t type,
    scf_handle_t *h, void *data, scf_value_t ***val, scf_type_t *scftype,
    int *numval)
{
	scf_value_t *v, **varray = NULL;
	int ret = ILB_STATUS_OK;
	int i;
	int scf_val_len = ILBD_MAX_VALUE_LEN;
	char valstr[scf_val_len];
	int valint;
	uint8_t valbool = 0;
	ilbd_rule_t *r_ent = NULL;
	ilbd_sg_t *s_ent = NULL;
	ilbd_hc_t *h_ent = NULL;

	switch (pg_type) {
	case ILBD_SCF_RULE:
		r_ent = (ilbd_rule_t *)data;
		break;
	case ILBD_SCF_SG:
		s_ent = (ilbd_sg_t *)data;
		break;
	case ILBD_SCF_HC:
		h_ent = (ilbd_hc_t *)data;
		break;
	}

	v = scf_value_create(h);
	if (v == NULL)
		return (ILB_STATUS_INTERNAL);

	switch (type) {
	case ILBD_RULE_STATUS:
		valbool = r_ent->irl_flags & ILB_FLAGS_RULE_ENABLED;
		break;
	case ILBD_RULE_VIP:
		ret = ilbd_scf_ip_to_str(r_ent->irl_ipversion, &r_ent->irl_vip,
		    scftype, valstr);
		if (ret != ILB_STATUS_OK) {
			scf_value_destroy(v);
			return (ret);
		}
		break;
	case ILBD_RULE_PROTO: {
		struct protoent *protoent;

		protoent = getprotobynumber(r_ent->irl_proto);
		(void) strlcpy(valstr, protoent->p_name, sizeof (valstr));
		break;
	}
	case ILBD_RULE_PORT:
		(void) snprintf(valstr, sizeof (valstr), "%d-%d",
		    r_ent->irl_minport, r_ent->irl_maxport);
		break;
	case ILBD_RULE_ALGO:
		ilbd_algo_to_str(r_ent->irl_algo, valstr);
		break;
	case ILBD_RULE_TOPO:
		ilbd_topo_to_str(r_ent->irl_topo, valstr);
		break;
	case ILBD_RULE_NAT_STR:
		ret = ilbd_scf_ip_to_str(r_ent->irl_ipversion,
		    &r_ent->irl_nat_src_start, scftype, valstr);
		if (ret != ILB_STATUS_OK) {
			scf_value_destroy(v);
			return (ret);
		}
		break;
	case ILBD_RULE_NAT_END:
		ret = ilbd_scf_ip_to_str(r_ent->irl_ipversion,
		    &r_ent->irl_nat_src_end, scftype, valstr);
		if (ret != ILB_STATUS_OK) {
			scf_value_destroy(v);
			return (ret);
		}
		break;
	case ILBD_RULE_STI_MASK:
		ret = ilbd_scf_ip_to_str(r_ent->irl_ipversion,
		    &r_ent->irl_stickymask, scftype, valstr);
		if (ret != ILB_STATUS_OK) {
			scf_value_destroy(v);
			return (ret);
		}
		break;
	case ILBD_RULE_SGNAME:
		(void) strlcpy(valstr, r_ent->irl_sgname, sizeof (valstr));
		break;
	case ILBD_RULE_HCNAME:
		if (r_ent->irl_hcname[0] != '\0')
			(void) strlcpy(valstr, r_ent->irl_hcname,
			    sizeof (valstr));
		else
			bzero(valstr, ILBD_MAX_VALUE_LEN);
		break;
	case ILBD_RULE_HCPORT:
		valint = r_ent->irl_hcport;
		break;
	case ILBD_RULE_HCPFLAG:
		valint = r_ent->irl_hcpflag;
		break;
	case ILBD_RULE_DRAINTIME:
		valint = r_ent->irl_conndrain;
		break;
	case ILBD_RULE_NAT_TO:
		valint = r_ent->irl_nat_timeout;
		break;
	case ILBD_RULE_PERS_TO:
		valint = r_ent->irl_sticky_timeout;
		break;

	case ILBD_SG_SERVER:
		if (s_ent->isg_srvcount == 0) {
			(void) strlcpy(valstr, "EMPTY_SERVERGROUP",
			    sizeof (valstr));
			break;
		}

		varray = calloc(sizeof (*varray), s_ent->isg_srvcount);
		if (varray == NULL) {
			scf_value_destroy(v);
			return (ILB_STATUS_ENOMEM);
		}

		for (i = 0; i < s_ent->isg_srvcount; i++) {
			if (v == NULL) {
				for (i--; i >= 0; i--)
					scf_value_destroy(varray[i]);
				free(varray);
				return (ILB_STATUS_ENOMEM);
			}

			ret = ilbd_get_svr_info(s_ent, i, valstr, NULL);
			if (ret != ILB_STATUS_OK) {
				scf_value_destroy(v);
				for (i--; i >= 0; i--)
					scf_value_destroy(varray[i]);
				free(varray);
				return (ret);
			}
			(void) scf_value_set_astring(v, valstr);
			varray[i] = v;
			v = scf_value_create(h);
		}
		/* the last 'v' we created will go unused, so drop it */
		scf_value_destroy(v);
		*numval = s_ent->isg_srvcount;
		*val = varray;
		return (ret);
	case ILBD_HC_TEST:
		(void) strlcpy(valstr, h_ent->ihc_test, sizeof (valstr));
		break;
	case ILBD_HC_TIMEOUT:
		valint = h_ent->ihc_timeout;
		break;
	case ILBD_HC_INTERVAL:
		valint = h_ent->ihc_interval;
		break;
	case ILBD_HC_DEF_PING:
		valbool = h_ent->ihc_def_ping;
		break;
	case ILBD_HC_COUNT:
		valint = h_ent->ihc_count;
		break;
	}

	switch (*scftype) {
	case SCF_TYPE_BOOLEAN:
		scf_value_set_boolean(v, valbool);
		break;
	case SCF_TYPE_ASTRING:
		(void) scf_value_set_astring(v, valstr);
		break;
	case SCF_TYPE_INTEGER:
		scf_value_set_integer(v, valint);
		break;
	case SCF_TYPE_NET_ADDR_V4:
		(void) scf_value_set_from_string(v, SCF_TYPE_NET_ADDR_V4,
		    valstr);
		break;
	case SCF_TYPE_NET_ADDR_V6:
		(void) scf_value_set_from_string(v, SCF_TYPE_NET_ADDR_V6,
		    valstr);
		break;
	}

	varray = calloc(1, sizeof (*varray));
	if (varray == NULL) {
		scf_value_destroy(v);
		return (ILB_STATUS_ENOMEM);
	}
	varray[0] = v;
	*val = varray;
	*numval = 1;

	return (ret);
}

/*
 * create a scf property group
 */
ilb_status_t
ilbd_create_pg(ilbd_scf_pg_type_t pg_type, void *data)
{
	ilb_status_t ret;
	char *pgname;
	scf_propertygroup_t *pg = NULL;
	scf_value_t **val;
	scf_handle_t *h;
	int scf_name_len = ILBD_MAX_NAME_LEN;
	char scfpgname[scf_name_len];
	int i, i_st, i_end;

	switch (pg_type) {
	case ILBD_SCF_RULE: {
		ilbd_rule_t *r_ent = (ilbd_rule_t *)data;

		pgname = r_ent->irl_name;
		i_st = 0;
		i_end = ILBD_RULE_VAR_NUM;
		break;
	}
	case ILBD_SCF_SG: {
		ilbd_sg_t *s_ent = (ilbd_sg_t *)data;

		pgname = s_ent->isg_name;
		i_st = ILBD_RULE_VAR_NUM;
		i_end = ILBD_RULE_VAR_NUM + ILBD_SG_VAR_NUM;
		break;
	}
	case ILBD_SCF_HC: {
		ilbd_hc_t *h_ent = (ilbd_hc_t *)data;

		pgname = h_ent->ihc_name;
		i_st = ILBD_RULE_VAR_NUM + ILBD_SG_VAR_NUM;
		i_end = ILBD_PROP_VAR_NUM;
		break;
	}
	default:
		logdebug("ilbd_create_pg: invalid pg type %d for pg %s",
		    pg_type, pgname);
		return (ILB_STATUS_EINVAL);
	}

	ilbd_name_to_scfpgname(pg_type, pgname, scfpgname);

	ret = ilbd_scf_retrieve_pg(scfpgname, &pg, B_TRUE);
	if (ret != ILB_STATUS_OK)
		return (ret);
	h = scf_pg_handle(pg);

	/* fill in props */
	for (i = i_st; i < i_end; i++) {
		int num, j;
		int scf_name_len = ILBD_MAX_NAME_LEN;
		char propname[scf_name_len];
		scf_type_t scftype = prop_tbl[i].scf_proptype;

		ret = ilbd_data_to_scfval(pg_type, prop_tbl[i].val_type, h,
		    data, &val, &scftype, &num);
		if (ret != ILB_STATUS_OK)
			goto done;

		for (j = 0; j < num; j++) {
			if (pg_type == ILBD_SCF_SG) {
				ret = ilbd_get_svr_info(data, j, NULL,
				    propname);
				if (ret == ILB_STATUS_ENOENT) {
					(void) strlcpy(propname, "EMPTY_SERVER",
					    ILBD_MAX_NAME_LEN);
				}
				ret = ilbd_scf_set_prop(pg, propname,
				    scftype, val[j]);
			} else {
				ret = ilbd_scf_set_prop(pg,
				    prop_tbl[i].scf_propname, scftype, val[j]);
			}
			scf_value_destroy(val[j]);
		}
		free(val);
	}

done:
	ilbd_scf_destroy(h, NULL, NULL, pg);
	return (ret);
}

/*
 * destroy a scf property group
 */
static ilb_status_t
ilbd_scf_delete_pg(scf_propertygroup_t *pg)
{
	if (scf_pg_delete(pg) != 0)
		return (ilbd_scf_err_to_ilb_err());
	return (ILB_STATUS_OK);
}

/* sg can have same name as rule */
ilb_status_t
ilbd_destroy_pg(ilbd_scf_pg_type_t pg_t, const char *pgname)
{
	ilb_status_t ret;
	scf_propertygroup_t *pg;
	int scf_name_len = ILBD_MAX_NAME_LEN;
	char scfname[scf_name_len];

	ilbd_name_to_scfpgname(pg_t, pgname, scfname);

	ret = ilbd_scf_retrieve_pg(scfname, &pg, B_FALSE);
	if (ret != ILB_STATUS_EEXIST)
		return (ret);

	ret = ilbd_scf_delete_pg(pg);
	ilbd_scf_destroy(scf_pg_handle(pg), NULL, NULL, pg);
	return (ret);
}

/*
 * Set named property to scf value specified.  If property is new,
 * create it.
 */
static ilb_status_t
ilbd_scf_set_prop(scf_propertygroup_t *pg, const char *propname,
    scf_type_t proptype, scf_value_t *val)
{
	scf_handle_t *h = NULL;
	scf_property_t *prop = NULL;
	scf_value_t *oldval = NULL;
	scf_transaction_t *tx = NULL;
	scf_transaction_entry_t *ent = NULL;
	boolean_t new = B_FALSE;
	ilb_status_t ret = ILB_STATUS_OK;
	int commit_ret;

	h = scf_pg_handle(pg);
	if (h == NULL || propname == NULL)
		return (ILB_STATUS_EINVAL);

	ret = ilbd_scf_get_prop_val(pg, propname, &oldval);
	if (oldval != NULL)
		scf_value_destroy(oldval);
	if (ret == ILB_STATUS_ENOENT)
		new = B_TRUE;
	else if (ret != ILB_STATUS_OK)
		return (ret);

	if ((prop = scf_property_create(h)) == NULL)
		return (ilbd_scf_err_to_ilb_err());
	if ((tx = scf_transaction_create(h)) == NULL ||
	    (ent = scf_entry_create(h)) == NULL) {
		ret = ilbd_scf_err_to_ilb_err();
		logdebug("ilbd_scf_set_prop: create scf transaction failed\n");
		goto out;
	}

	if (scf_transaction_start(tx, pg) == -1) {
		ret = ilbd_scf_err_to_ilb_err();
		logdebug("ilbd_scf_set_prop: start scf transaction failed\n");
		goto out;
	}

	if (new) {
		if (scf_transaction_property_new(tx, ent, propname,
		    proptype) == -1) {
			ret = ilbd_scf_err_to_ilb_err();
			logdebug("ilbd_scf_set_prop: create scf prop failed\n");
			goto out;
		}
	} else {
		if (scf_transaction_property_change(tx, ent, propname, proptype)
		    == -1) {
			ret = ilbd_scf_err_to_ilb_err();
			logdebug("ilbd_scf_set_prop: change scf prop failed\n");
			goto out;
		}
	}

	if (scf_entry_add_value(ent, val) != 0) {
		logdebug("ilbd_scf_set_prop: add scf entry failed\n");
		ret = ilbd_scf_err_to_ilb_err();
		goto out;
	}

	commit_ret = scf_transaction_commit(tx);
	switch (commit_ret) {
	case 1:
		ret = ILB_STATUS_OK;
		/* update pg here, so subsequent property setting  succeeds */
		(void) scf_pg_update(pg);
		break;
	case 0:
		/* transaction failed due to not having most recent pg */
		ret = ILB_STATUS_INUSE;
		break;
	default:
		ret = ilbd_scf_err_to_ilb_err();
		break;
	}
out:
	if (tx != NULL)
		scf_transaction_destroy(tx);
	if (ent != NULL)
		scf_entry_destroy(ent);
	if (prop != NULL)
		scf_property_destroy(prop);

	return (ret);
}

/*
 * get a prop's scf val
 */
static ilb_status_t
ilbd_scf_get_prop_val(scf_propertygroup_t *pg, const char *propname,
    scf_value_t **val)
{
	scf_handle_t *h = NULL;
	scf_property_t *prop = NULL;
	scf_value_t *value = NULL;
	ilb_status_t ret = ILB_STATUS_OK;

	h = scf_pg_handle(pg);
	if (h == NULL || propname == NULL)
		return (ILB_STATUS_EINVAL);

	if ((prop = scf_property_create(h)) == NULL)
		return (ilbd_scf_err_to_ilb_err());

	if (scf_pg_get_property(pg, propname, prop) != 0) {
		ret = ilbd_scf_err_to_ilb_err();
		goto out;
	}

	if ((value = scf_value_create(h)) == NULL) {
		ret = ilbd_scf_err_to_ilb_err();
		goto out;
	}

	if (scf_property_get_value(prop, value) != 0) {
		scf_value_destroy(value);
		ret = ilbd_scf_err_to_ilb_err();
		goto out;
	}

	*val = value;
out:
	if (prop != NULL)
		scf_property_destroy(prop);

	return (ret);
}

typedef struct ilbd_data
{
	union {
		ilb_sg_info_t *sg_info;
		ilb_hc_info_t *hc_info;
		ilb_rule_info_t *rule_info;
	} data;
	ilbd_scf_pg_type_t pg_type;	/* type of data */
#define	sg_data data.sg_info
#define	hc_data data.hc_info
#define	rule_data data.rule_info
} ilbd_data_t;

void
ilbd_scf_str_to_ip(int ipversion, char *ipstr, struct in6_addr *addr)
{
	ilb_ip_addr_t ipaddr;
	void *addrptr;

	addrptr = (ipversion == AF_INET) ?
	    (void *)&ipaddr.ia_v4 : (void *)&ipaddr.ia_v6;
	(void) inet_pton(ipversion, ipstr, addrptr);
	if (ipversion == AF_INET) {
		IN6_INADDR_TO_V4MAPPED(&(ipaddr.ia_v4), addr);
	} else {
		(void) memcpy(addr, &(ipaddr.ia_v6),
		    sizeof (struct in6_addr));
	}
}

/*
 * This function takes a scf value and writes it to the correct field of the
 * corresponding data struct.
 */
static ilb_status_t
ilbd_scfval_to_data(const char *propname, ilbd_var_type_t ilb_type,
    scf_value_t *val, ilbd_data_t *ilb_data)
{

	scf_type_t scf_type = scf_value_type(val);
	ilbd_scf_pg_type_t pg_type = ilb_data->pg_type;
	int ret = 0;
	ilb_rule_info_t *r_ent = NULL;
	ilb_sg_info_t *s_ent = NULL;
	ilb_hc_info_t *h_ent = NULL;
	char ipstr[INET6_ADDRSTRLEN];
	int scf_val_len = ILBD_MAX_VALUE_LEN;
	char valstr[scf_val_len];
	int64_t valint;
	uint8_t valbool;
	int ipversion;

	switch (pg_type) {
	case ILBD_SCF_RULE:
		r_ent = ilb_data->rule_data;
		break;
	case ILBD_SCF_HC:
		h_ent = ilb_data->hc_data;
		break;
	case ILBD_SCF_SG:
		s_ent = ilb_data->sg_data;
		break;
	}

	/* get scf value out */
	switch (scf_type) {
		case SCF_TYPE_NET_ADDR_V4:
			if (scf_value_get_as_string_typed(val,
			    SCF_TYPE_NET_ADDR_V4, ipstr, INET_ADDRSTRLEN) < 0)
				return (ILB_STATUS_INTERNAL);
			ipversion = AF_INET;
			break;
		case SCF_TYPE_NET_ADDR_V6:
			if (scf_value_get_as_string_typed(val,
			    SCF_TYPE_NET_ADDR_V6, ipstr, INET6_ADDRSTRLEN) < 0)
				return (ILB_STATUS_INTERNAL);
			ipversion = AF_INET6;
			break;
		case SCF_TYPE_BOOLEAN:
			if (scf_value_get_boolean(val, &valbool) < 0)
				return (ILB_STATUS_INTERNAL);
			break;
		case SCF_TYPE_ASTRING:
			if (scf_value_get_astring(val, valstr, sizeof (valstr))
			    < 0)
				return (ILB_STATUS_INTERNAL);
			break;
		case SCF_TYPE_INTEGER:
			if (scf_value_get_integer(val, &valint) < 0)
				return (ILB_STATUS_INTERNAL);
			break;
		default:
			return (ILB_STATUS_INTERNAL);
	}

	ret = ILB_STATUS_OK;
	switch (ilb_type) {
	case ILBD_RULE_STATUS:
		if (valbool)
			r_ent->rl_flags |= ILB_FLAGS_RULE_ENABLED;
		break;
	case ILBD_RULE_VIP:
		r_ent->rl_ipversion = ipversion;
		ilbd_scf_str_to_ip(ipversion, ipstr, &r_ent->rl_vip);
		break;
	case ILBD_RULE_PROTO: {
		struct protoent *protoent;

		protoent = getprotobyname(valstr);
		r_ent->rl_proto = protoent->p_proto;
		break;
	}
	case ILBD_RULE_PORT: {
		char *token1, *token2;

		token1 = strtok(valstr, "-");
		token2 = strtok(NULL, "-");
		r_ent->rl_minport = atoi(token1);
		r_ent->rl_maxport = atoi(token2);
		break;
	}
	case ILBD_RULE_ALGO:
		ilbd_scf_str_to_algo(&(r_ent->rl_algo), valstr);
		break;
	case ILBD_RULE_TOPO:
		ilbd_scf_str_to_topo(&(r_ent->rl_topo), valstr);
		break;
	case ILBD_RULE_NAT_STR:
		ilbd_scf_str_to_ip(ipversion, ipstr, &r_ent->rl_nat_src_start);
		break;
	case ILBD_RULE_NAT_END:
		ilbd_scf_str_to_ip(ipversion, ipstr, &r_ent->rl_nat_src_end);
		break;
	case ILBD_RULE_STI_MASK:
		ilbd_scf_str_to_ip(ipversion, ipstr, &r_ent->rl_stickymask);
		if (ipversion == AF_INET) {
			if (!IN6_IS_ADDR_V4MAPPED_ANY(&r_ent->rl_stickymask))
				r_ent->rl_flags |= ILB_FLAGS_RULE_STICKY;
		} else {
			if (!IN6_IS_ADDR_UNSPECIFIED(&r_ent->rl_stickymask))
				r_ent->rl_flags |= ILB_FLAGS_RULE_STICKY;
		}
		break;
	case ILBD_RULE_SGNAME:
		(void) strlcpy(r_ent->rl_sgname, valstr,
		    sizeof (r_ent->rl_sgname));
		break;
	case ILBD_RULE_HCNAME:
		(void) strlcpy(r_ent->rl_hcname, valstr,
		    sizeof (r_ent->rl_hcname));
		break;
	case ILBD_RULE_HCPORT:
		r_ent->rl_hcport = valint;
		break;
	case ILBD_RULE_HCPFLAG:
		r_ent->rl_hcpflag = valint;
		break;
	case ILBD_RULE_DRAINTIME:
		r_ent->rl_conndrain = valint;
		break;
	case ILBD_RULE_NAT_TO:
		r_ent->rl_nat_timeout = valint;
		break;
	case ILBD_RULE_PERS_TO:
		r_ent->rl_sticky_timeout = valint;
		break;

	case ILBD_SG_SERVER: {
		int svr_cnt = s_ent->sg_srvcount;

		/* found a new server, increase the svr count of this sg */
		s_ent->sg_srvcount++;

		/*
		 * valstr contains information of one server in the servergroup
		 * valstr is in the format of "ip:minport-maxport:enable"
		 */
		s_ent = realloc(s_ent, sizeof (ilb_sg_info_t) +
		    s_ent->sg_srvcount * sizeof (ilb_sg_srv_t));

		/* sgs_srvID is the sg name, leave it blank */
		/*
		 * sgs_id is the digit in propname, propname is in a format of
		 * "server" + the digital serverID. We get the serverID by
		 * reading from the 7th char of propname.
		 */
		s_ent->sg_servers[svr_cnt].sgs_id = atoi(&propname[6]);

		ilbd_get_svr_field(valstr,
		    &s_ent->sg_servers[svr_cnt].sgs_addr,
		    &s_ent->sg_servers[svr_cnt].sgs_minport,
		    &s_ent->sg_servers[svr_cnt].sgs_maxport,
		    &s_ent->sg_servers[svr_cnt].sgs_flags);
		ilb_data->sg_data = s_ent;

		break;
	}
	case ILBD_HC_TEST:
		(void) strlcpy(h_ent->hci_test, valstr,
		    sizeof (h_ent->hci_test));
		break;
	case ILBD_HC_TIMEOUT:
		h_ent->hci_timeout = valint;
		break;
	case ILBD_HC_INTERVAL:
		h_ent->hci_interval = valint;
		break;
	case ILBD_HC_DEF_PING:
		h_ent->hci_def_ping = valbool;
		break;
	case ILBD_HC_COUNT:
		h_ent->hci_count = valint;
		break;
	case ILBD_VAR_INVALID:
		/*
		 * An empty server group is represented by an invalid
		 * SCF property.  So when loading a server group, this
		 * case can be hit.  But it should happen only for this
		 * single case.  So if it happens in another case, move
		 * the service into maintenance mode.
		 */
		if (pg_type != ILBD_SCF_SG || scf_type != SCF_TYPE_ASTRING) {
			logerr("%s: invalid ilb type", __func__);
			(void) smf_maintain_instance(ILB_FMRI, SMF_IMMEDIATE);
		} else {
			logdebug("%s: invalid ilb type", __func__);
		}
		break;
	}

	return (ret);
}

static ilbd_var_type_t
ilbd_name_to_valtype(const char *prop_name)
{
	int i;

	for (i = 0; i < ILBD_PROP_VAR_NUM; i++)
		if (strncmp(prop_name, prop_tbl[i].scf_propname,
		    strlen(prop_tbl[i].scf_propname)) == 0)
			return (prop_tbl[i].val_type);

	logdebug("ilbd_name_to_valtype: couldn't find prop %s", prop_name);
	return (ILBD_VAR_INVALID);
}

/* callback for pg_walk_prop, arg is ilbd_data_t */
static ilb_status_t
ilbd_scf_load_prop(scf_propertygroup_t *pg, const char *prop_name, void *arg)
{
	scf_handle_t *h;
	scf_value_t *val;
	ilb_status_t ret;
	ilbd_data_t *ilb_data = (ilbd_data_t *)arg;
	ilbd_var_type_t val_type = ilbd_name_to_valtype(prop_name);

	h = scf_pg_handle(pg);
	if (h == NULL)
		return (ILB_STATUS_EINVAL);

	ret = ilbd_scf_get_prop_val(pg, prop_name, &val);
	if (ret == ILB_STATUS_ENOENT)
		return (ILB_STATUS_OK);
	else if (ret != ILB_STATUS_OK)
		return (ret);

	/*
	 * Load value to ilb_data.
	 */
	ret = ilbd_scfval_to_data(prop_name, val_type, val, ilb_data);

out:
	if (val != NULL)
		scf_value_destroy(val);

	return (ret);
}

/*
 * walk properties in one prop group, arg is ilbd_data
 * cb is ilbd_scf_load_prop()
 */
static ilb_status_t
ilbd_scf_pg_walk_props(scf_propertygroup_t *pg,
    ilb_status_t (*cb)(scf_propertygroup_t *, const char *, void *),
    void *arg)
{
	scf_handle_t *h;
	scf_iter_t *propiter;
	scf_property_t *prop;
	int scf_name_len = ILBD_MAX_NAME_LEN;
	char prop_name[scf_name_len];
	ilb_status_t ret = ILB_STATUS_OK;
	int scf_ret = -1;

	h = scf_pg_handle(pg);
	if (h == NULL)
		return (ILB_STATUS_EINVAL);

	prop = scf_property_create(h);
	propiter = scf_iter_create(h);
	if (prop == NULL || propiter == NULL)
		goto out;

	if (scf_iter_pg_properties(propiter, pg) != 0)
		goto out;

	while ((scf_ret = scf_iter_next_property(propiter, prop)) == 1) {
		if (scf_property_get_name(prop, prop_name, sizeof (prop_name))
		    < 0) {
			ret = ilbd_scf_err_to_ilb_err();
			goto out;
		}
		ret = cb(pg, prop_name, arg);
		if (ret != ILB_STATUS_OK)
			break;
	}
out:
	if (scf_ret == -1)
		ret = ilbd_scf_err_to_ilb_err();
	if (prop != NULL)
		scf_property_destroy(prop);
	if (propiter != NULL)
		scf_iter_destroy(propiter);

	return (ret);
}

/* cbs are libd_create_X */
static ilb_status_t
ilbd_scf_instance_walk_pg(scf_instance_t *inst,
    ilbd_scf_pg_type_t pg_type,
    ilb_status_t (*cb)(void *, int, struct passwd *, ucred_t *),
    void *arg1, void *arg2)
{
	int			scf_ret;
	ilb_status_t		ret;
	scf_handle_t		*h;
	scf_iter_t		*pgiter;
	scf_propertygroup_t	*newpg;
	int			port = *((int *)arg1);

	if (inst == NULL)
		return (ILB_STATUS_EINVAL);

	h = scf_instance_handle(inst);
	if (h == NULL)
		return (ILB_STATUS_EINVAL);

	if ((newpg = scf_pg_create(h)) == NULL)
		return (ilbd_scf_err_to_ilb_err());

	if ((pgiter = scf_iter_create(h)) == NULL) {
		scf_pg_destroy(newpg);
		return (ilbd_scf_err_to_ilb_err());
	}

	if ((scf_ret = scf_iter_instance_pgs(pgiter, inst)) < 0)
		goto out;

	while ((scf_ret = scf_iter_next_pg(pgiter, newpg)) > 0) {
		ilbd_data_t data;
		int scf_name_len = ILBD_MAX_NAME_LEN;
		char pg_name[scf_name_len];

		if (scf_pg_get_name(newpg, pg_name, sizeof (pg_name)) < 0) {
			ret = ilbd_scf_err_to_ilb_err();
			goto out;
		}

		/*
		 * if pg name indicates it's a ilb configuration, walk its prop
		 */
		data.pg_type = pg_type;
		data.hc_data = NULL;
		data.sg_data = NULL;
		data.rule_data = NULL;

		switch (pg_type) {
		case ILBD_SCF_RULE:
			if (strncmp(ILBD_PG_NAME_RULE, pg_name,
			    strlen(ILBD_PG_NAME_RULE)) == 0) {
				data.rule_data = calloc(1,
				    sizeof (ilb_rule_info_t));
				if (data.rule_data == NULL) {
					ret = ILB_STATUS_ENOMEM;
					goto out;
				}
				ret = ilbd_scf_pg_walk_props(newpg,
				    ilbd_scf_load_prop, &data);
				if (ret != ILB_STATUS_OK)
					goto out;
				assert(data.rule_data != NULL);
				/* set rule name */
				(void) strlcpy(data.rule_data->rl_name,
				    &pg_name[strlen(ILBD_PG_NAME_RULE)],
				    sizeof (data.rule_data->rl_name));

				ret = cb(data.rule_data, port, arg2, NULL);
				free(data.rule_data);
				if (ret != ILB_STATUS_OK)
					goto out;
			}
			break;
		case ILBD_SCF_SG:
			if (strncmp(ILBD_PG_NAME_SG, pg_name,
			    strlen(ILBD_PG_NAME_SG)) == 0) {
				data.sg_data = calloc(1,
				    sizeof (ilb_sg_info_t));
				if (data.sg_data == NULL)
					return (ILB_STATUS_ENOMEM);
				ret = ilbd_scf_pg_walk_props(newpg,
				    ilbd_scf_load_prop, &data);
				if (ret != ILB_STATUS_OK) {
					free(data.sg_data);
					goto out;
				}
				assert(data.sg_data != NULL);
				/* set sg name */
				(void) strlcpy(data.sg_data->sg_name,
				    &pg_name[strlen(ILBD_PG_NAME_SG)],
				    sizeof (data.sg_data->sg_name));
				ret = cb(data.sg_data, port, arg2, NULL);
				if (ret != ILB_STATUS_OK) {
					free(data.sg_data);
					goto out;
				}
				/*
				 * create a servergroup is two-step operation.
				 * 1. create an empty servergroup.
				 * 2. add server(s) to the group.
				 *
				 * since we are here from:
				 * main_loop()->ilbd_read_config()->
				 * ilbd_walk_sg_pgs()
				 * there is no cli to send. So in this
				 * path auditing will skip the
				 * adt_set_from_ucred() check
				 */
				if (data.sg_data->sg_srvcount > 0) {
					ret = ilbd_add_server_to_group(
					    data.sg_data, port, NULL, NULL);
					if (ret != ILB_STATUS_OK) {
						free(data.sg_data);
						goto out;
					}
					free(data.sg_data);
				}
			}
			break;
		case ILBD_SCF_HC:
			if (strncmp(ILBD_PG_NAME_HC, pg_name,
			    strlen(ILBD_PG_NAME_HC)) == 0) {
				data.hc_data = calloc(1,
				    sizeof (ilb_hc_info_t));
				if (data.hc_data == NULL)
					return (ILB_STATUS_ENOMEM);
				ret = ilbd_scf_pg_walk_props(newpg,
				    ilbd_scf_load_prop, &data);
				if (ret != ILB_STATUS_OK)
					goto out;
				assert(data.hc_data != NULL);
				/* set hc name */
				(void) strlcpy(data.hc_data->hci_name,
				    &pg_name[strlen(ILBD_PG_NAME_HC)],
				    sizeof (data.hc_data->hci_name));
				ret = cb(data.hc_data, port, arg2, NULL);
				free(data.hc_data);
				if (ret != ILB_STATUS_OK)
					goto out;
			}
			break;
		}
	}

out:
	if (scf_ret < 0)
		ret = ilbd_scf_err_to_ilb_err();
	scf_pg_destroy(newpg);
	scf_iter_destroy(pgiter);
	return (ret);
}

typedef ilb_status_t (*ilbd_scf_walker_fn)(void *, int, struct passwd *,
    ucred_t *);

ilb_status_t
ilbd_walk_rule_pgs(ilb_status_t (*func)(ilb_rule_info_t *, int,
    const struct passwd *, ucred_t *), void *arg1, void *arg2)
{
	scf_instance_t *inst;
	scf_handle_t *h;
	scf_service_t *svc;
	ilb_status_t ret;

	ret = ilbd_scf_get_inst(&h, &svc, &inst);
	if (ret != ILB_STATUS_OK)
		return (ret);

	/* get rule prop group, transfer it to ilb_lrule_info_t */
	ret = ilbd_scf_instance_walk_pg(inst, ILBD_SCF_RULE,
	    (ilbd_scf_walker_fn)func, arg1, arg2);
	ilbd_scf_destroy(h, svc, inst, NULL);
	return (ret);
}

ilb_status_t
ilbd_walk_sg_pgs(ilb_status_t (*func)(ilb_sg_info_t *, int,
    const struct passwd *, ucred_t *), void *arg1, void *arg2)
{
	scf_instance_t *inst;
	scf_handle_t *h;
	scf_service_t *svc;
	ilb_status_t ret;

	ret = ilbd_scf_get_inst(&h, &svc, &inst);
	if (ret != ILB_STATUS_OK)
		return (ret);

	ret = ilbd_scf_instance_walk_pg(inst, ILBD_SCF_SG,
	    (ilbd_scf_walker_fn)func, arg1, arg2);
	ilbd_scf_destroy(h, svc, inst, NULL);
	return (ret);
}

ilb_status_t
ilbd_walk_hc_pgs(ilb_status_t (*func)(const ilb_hc_info_t *, int,
    const struct passwd *, ucred_t *), void *arg1, void *arg2)
{
	scf_instance_t *inst;
	scf_handle_t *h;
	scf_service_t *svc;
	ilb_status_t ret;

	ret = ilbd_scf_get_inst(&h, &svc, &inst);
	if (ret != ILB_STATUS_OK)
		return (ret);

	ret = ilbd_scf_instance_walk_pg(inst, ILBD_SCF_HC,
	    (ilbd_scf_walker_fn)func, arg1, arg2);
	ilbd_scf_destroy(h, svc, inst, NULL);
	return (ret);
}

ilb_status_t
ilbd_change_prop(ilbd_scf_pg_type_t pg_type, const char *pg_name,
    const char *prop_name, void *new_val)
{
	int ret;
	scf_propertygroup_t *scfpg = NULL;
	int scf_name_len = ILBD_MAX_NAME_LEN;
	char scf_pgname[scf_name_len];
	scf_type_t scftype;
	scf_value_t *scfval;
	scf_handle_t *h;

	ilbd_name_to_scfpgname(pg_type, pg_name, scf_pgname);
	ret = ilbd_scf_retrieve_pg(scf_pgname, &scfpg, B_FALSE);
	if (ret != ILB_STATUS_EEXIST)
		return (ret);

	assert(scfpg != NULL);

	h = scf_pg_handle(scfpg);
	if (h == NULL) {
		ret = ILB_STATUS_EINVAL;
		goto done;
	}

	if ((scfval = scf_value_create(h)) == NULL) {
		ret = ILB_STATUS_ENOMEM;
		goto done;
	}

	if (pg_type == ILBD_SCF_RULE) {
		scftype = SCF_TYPE_BOOLEAN;
		scf_value_set_boolean(scfval, *(boolean_t *)new_val);
	} else if (pg_type == ILBD_SCF_SG) {
		scftype = SCF_TYPE_ASTRING;
		(void) scf_value_set_astring(scfval, (char *)new_val);
	}
	ret = ilbd_scf_set_prop(scfpg, prop_name, scftype, scfval);

done:
	if (scf_pg_handle(scfpg) != NULL)
		scf_handle_destroy(scf_pg_handle(scfpg));
	if (scfpg != NULL)
		scf_pg_destroy(scfpg);
	if (scfval != NULL)
		scf_value_destroy(scfval);
	return (ret);
}

/*
 * Update the persistent configuration with a new server, srv, added to a
 * server group, sg.
 */
ilb_status_t
ilbd_scf_add_srv(ilbd_sg_t *sg, ilbd_srv_t *srv)
{
	scf_propertygroup_t *pg;
	scf_handle_t *h;
	scf_value_t *val;
	ilb_status_t ret;
	int scf_name_len = ILBD_MAX_NAME_LEN;
	char buf[scf_name_len];
	char propname[scf_name_len];

	ilbd_name_to_scfpgname(ILBD_SCF_SG, sg->isg_name, buf);
	ret = ilbd_scf_retrieve_pg(buf, &pg, B_FALSE);
	/*
	 * The server group does not exist in persistent storage.  This
	 * cannot happen.  Should probably transition the service to
	 * maintenance since it should be there.
	 */
	if (ret != ILB_STATUS_EEXIST) {
		logerr("ilbd_scf_add_srv: SCF update failed - entering"
		    " maintenance mode");
		(void) smf_maintain_instance(ILB_FMRI, SMF_IMMEDIATE);
		return (ILB_STATUS_INTERNAL);
	}

	if ((h = scf_pg_handle(pg)) == NULL) {
		ilbd_scf_destroy(NULL, NULL, NULL, pg);
		return (ilbd_scf_err_to_ilb_err());
	}

	if ((val = scf_value_create(h)) == NULL) {
		ilbd_scf_destroy(h, NULL, NULL, pg);
		return (ILB_STATUS_ENOMEM);
	}
	ilbd_srv_scf_val(srv, buf);
	(void) scf_value_set_astring(val, buf);
	(void) snprintf(propname, sizeof (propname), "server%d", srv->isv_id);
	ret = ilbd_scf_set_prop(pg, propname, SCF_TYPE_ASTRING, val);

	ilbd_scf_destroy(h, NULL, NULL, pg);
	scf_value_destroy(val);

	return (ret);
}

/*
 * Delete a server, srv, of a server group, sg, from the persistent
 * configuration.
 */
ilb_status_t
ilbd_scf_del_srv(ilbd_sg_t *sg, ilbd_srv_t *srv)
{
	ilb_status_t ret;
	scf_propertygroup_t *pg;
	scf_handle_t *h;
	int scf_name_len = ILBD_MAX_NAME_LEN;
	char buf[scf_name_len];
	scf_transaction_t *tx = NULL;
	scf_transaction_entry_t *entry = NULL;

	ilbd_name_to_scfpgname(ILBD_SCF_SG, sg->isg_name, buf);
	ret = ilbd_scf_retrieve_pg(buf, &pg, B_FALSE);
	/*
	 * The server group does not exist in persistent storage.  This
	 * cannot happen. THe caller of this function puts service in
	 * maintenance mode.
	 */
	if (ret != ILB_STATUS_EEXIST)
		return (ILB_STATUS_INTERNAL);
	ret = ILB_STATUS_OK;

	if ((h = scf_pg_handle(pg)) == NULL) {
		logdebug("ilbd_scf_del_srv: scf_pg_handle: %s\n",
		    scf_strerror(scf_error()));
		ilbd_scf_destroy(NULL, NULL, NULL, pg);
		return (ilbd_scf_err_to_ilb_err());
	}

	if ((tx = scf_transaction_create(h)) == NULL ||
	    (entry = scf_entry_create(h)) == NULL) {
		logdebug("ilbd_scf_del_srv: create scf transaction failed: "
		    "%s\n", scf_strerror(scf_error()));
		ret = ilbd_scf_err_to_ilb_err();
		goto out;
	}

	(void) snprintf(buf, sizeof (buf), "server%d", srv->isv_id);

	if (scf_transaction_start(tx, pg) == -1) {
		logdebug("ilbd_scf_set_prop: start scf transaction failed: "
		    "%s\n", scf_strerror(scf_error()));
		ret = ilbd_scf_err_to_ilb_err();
		goto out;
	}
	if (scf_transaction_property_delete(tx, entry, buf) == -1) {
		logdebug("ilbd_scf_set_prop: delete property failed: %s\n",
		    scf_strerror(scf_error()));
		ret = ilbd_scf_err_to_ilb_err();
		goto out;
	}
	if (scf_transaction_commit(tx) != 1) {
		logdebug("ilbd_scf_set_prop: commit transaction failed: %s\n",
		    scf_strerror(scf_error()));
		ret = ilbd_scf_err_to_ilb_err();
	}

out:
	if (entry != NULL)
		scf_entry_destroy(entry);
	if (tx != NULL)
		scf_transaction_destroy(tx);
	ilbd_scf_destroy(h, NULL, NULL, pg);

	return (ret);
}