/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (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 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

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

/*
 * This is the client layer for svc.configd.  All direct protocol interactions
 * are handled here.
 *
 * Essentially, the job of this layer is to turn the idempotent protocol
 * into a series of non-idempotent calls into the object layer, while
 * also handling the necessary locking.
 */

#include <alloca.h>
#include <assert.h>
#include <door.h>
#include <errno.h>
#include <limits.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <libuutil.h>

#include "configd.h"
#include "repcache_protocol.h"

#define	INVALID_CHANGEID	(0)
#define	INVALID_DOORID		((door_id_t)-1)
#define	INVALID_RESULT		((rep_protocol_responseid_t)INT_MIN)

/*
 * lint doesn't like constant assertions
 */
#ifdef lint
#define	assert_nolint(x) (void)0
#else
#define	assert_nolint(x) assert(x)
#endif

/*
 * Protects client linkage and the freelist
 */
#define	CLIENT_HASH_SIZE	64

#pragma align 64(client_hash)
static client_bucket_t client_hash[CLIENT_HASH_SIZE];

static uu_avl_pool_t *entity_pool;
static uu_avl_pool_t *iter_pool;
static uu_list_pool_t *client_pool;

#define	CLIENT_HASH(id)		(&client_hash[((id) & (CLIENT_HASH_SIZE - 1))])

uint_t request_log_size = 1024;		/* tunable, before we start */

static pthread_mutex_t request_log_lock = PTHREAD_MUTEX_INITIALIZER;
static uint_t request_log_cur;
request_log_entry_t	*request_log;

static uint32_t		client_maxid;
static pthread_mutex_t	client_lock;	/* protects client_maxid */

static request_log_entry_t *
get_log(void)
{
	thread_info_t *ti = thread_self();
	return (&ti->ti_log);
}

void
log_enter(request_log_entry_t *rlp)
{
	if (rlp->rl_start != 0 && request_log != NULL) {
		request_log_entry_t *logrlp;

		(void) pthread_mutex_lock(&request_log_lock);
		assert(request_log_cur < request_log_size);
		logrlp = &request_log[request_log_cur++];
		if (request_log_cur == request_log_size)
			request_log_cur = 0;
		(void) memcpy(logrlp, rlp, sizeof (*rlp));
		(void) pthread_mutex_unlock(&request_log_lock);
	}
}

/*
 * Note that the svc.configd dmod will join all of the per-thread log entries
 * with the main log, so that even if the log is disabled, there is some
 * information available.
 */
static request_log_entry_t *
start_log(uint32_t clientid)
{
	request_log_entry_t *rlp = get_log();

	log_enter(rlp);

	(void) memset(rlp, 0, sizeof (*rlp));
	rlp->rl_start = gethrtime();
	rlp->rl_tid = pthread_self();
	rlp->rl_clientid = clientid;

	return (rlp);
}

void
end_log(void)
{
	request_log_entry_t *rlp = get_log();

	rlp->rl_end = gethrtime();
}

static void
add_log_ptr(request_log_entry_t *rlp, enum rc_ptr_type type, uint32_t id,
    void *ptr)
{
	request_log_ptr_t *rpp;

	if (rlp == NULL)
		return;

	if (rlp->rl_num_ptrs >= MAX_PTRS)
		return;

	rpp = &rlp->rl_ptrs[rlp->rl_num_ptrs++];
	rpp->rlp_type = type;
	rpp->rlp_id = id;
	rpp->rlp_ptr = ptr;

	/*
	 * For entities, it's useful to have the node pointer at the start
	 * of the request.
	 */
	if (type == RC_PTR_TYPE_ENTITY && ptr != NULL)
		rpp->rlp_data = ((repcache_entity_t *)ptr)->re_node.rnp_node;
}

int
client_is_privileged(void)
{
	thread_info_t *ti = thread_self();

	ucred_t *uc;

	if (ti->ti_active_client != NULL &&
	    ti->ti_active_client->rc_all_auths)
		return (1);

	if ((uc = get_ucred()) == NULL)
		return (0);

	return (ucred_is_privileged(uc));
}

/*ARGSUSED*/
static int
client_compare(const void *lc_arg, const void *rc_arg, void *private)
{
	uint32_t l_id = ((const repcache_client_t *)lc_arg)->rc_id;
	uint32_t r_id = ((const repcache_client_t *)rc_arg)->rc_id;

	if (l_id > r_id)
		return (1);
	if (l_id < r_id)
		return (-1);
	return (0);
}

/*ARGSUSED*/
static int
entity_compare(const void *lc_arg, const void *rc_arg, void *private)
{
	uint32_t l_id = ((const repcache_entity_t *)lc_arg)->re_id;
	uint32_t r_id = ((const repcache_entity_t *)rc_arg)->re_id;

	if (l_id > r_id)
		return (1);
	if (l_id < r_id)
		return (-1);
	return (0);
}

/*ARGSUSED*/
static int
iter_compare(const void *lc_arg, const void *rc_arg, void *private)
{
	uint32_t l_id = ((const repcache_iter_t *)lc_arg)->ri_id;
	uint32_t r_id = ((const repcache_iter_t *)rc_arg)->ri_id;

	if (l_id > r_id)
		return (1);
	if (l_id < r_id)
		return (-1);
	return (0);
}

static int
client_hash_init(void)
{
	int x;

	assert_nolint(offsetof(repcache_entity_t, re_id) == 0);
	entity_pool = uu_avl_pool_create("repcache_entitys",
	    sizeof (repcache_entity_t), offsetof(repcache_entity_t, re_link),
	    entity_compare, UU_AVL_POOL_DEBUG);

	assert_nolint(offsetof(repcache_iter_t, ri_id) == 0);
	iter_pool = uu_avl_pool_create("repcache_iters",
	    sizeof (repcache_iter_t), offsetof(repcache_iter_t, ri_link),
	    iter_compare, UU_AVL_POOL_DEBUG);

	assert_nolint(offsetof(repcache_client_t, rc_id) == 0);
	client_pool = uu_list_pool_create("repcache_clients",
	    sizeof (repcache_client_t), offsetof(repcache_client_t, rc_link),
	    client_compare, UU_LIST_POOL_DEBUG);

	if (entity_pool == NULL || iter_pool == NULL || client_pool == NULL)
		return (0);

	for (x = 0; x < CLIENT_HASH_SIZE; x++) {
		uu_list_t *lp = uu_list_create(client_pool, &client_hash[x],
		    UU_LIST_SORTED);
		if (lp == NULL)
			return (0);

		(void) pthread_mutex_init(&client_hash[x].cb_lock, NULL);
		client_hash[x].cb_list = lp;
	}

	return (1);
}

static repcache_client_t *
client_alloc(void)
{
	repcache_client_t *cp;
	cp = uu_zalloc(sizeof (*cp));
	if (cp == NULL)
		return (NULL);

	cp->rc_entities = uu_avl_create(entity_pool, cp, 0);
	if (cp->rc_entities == NULL)
		goto fail;

	cp->rc_iters = uu_avl_create(iter_pool, cp, 0);
	if (cp->rc_iters == NULL)
		goto fail;

	uu_list_node_init(cp, &cp->rc_link, client_pool);

	cp->rc_doorfd = -1;
	cp->rc_doorid = INVALID_DOORID;

	(void) pthread_mutex_init(&cp->rc_lock, NULL);

	rc_node_ptr_init(&cp->rc_notify_ptr);

	return (cp);

fail:
	if (cp->rc_iters != NULL)
		uu_avl_destroy(cp->rc_iters);
	if (cp->rc_entities != NULL)
		uu_avl_destroy(cp->rc_entities);
	uu_free(cp);
	return (NULL);
}

static void
client_free(repcache_client_t *cp)
{
	assert(cp->rc_insert_thr == 0);
	assert(cp->rc_refcnt == 0);
	assert(cp->rc_doorfd == -1);
	assert(cp->rc_doorid == INVALID_DOORID);
	assert(uu_avl_first(cp->rc_entities) == NULL);
	assert(uu_avl_first(cp->rc_iters) == NULL);
	uu_avl_destroy(cp->rc_entities);
	uu_avl_destroy(cp->rc_iters);
	uu_list_node_fini(cp, &cp->rc_link, client_pool);
	(void) pthread_mutex_destroy(&cp->rc_lock);
	uu_free(cp);
}

static void
client_insert(repcache_client_t *cp)
{
	client_bucket_t *bp = CLIENT_HASH(cp->rc_id);
	uu_list_index_t idx;

	assert(cp->rc_id > 0);

	(void) pthread_mutex_lock(&bp->cb_lock);
	/*
	 * We assume it does not already exist
	 */
	(void) uu_list_find(bp->cb_list, cp, NULL, &idx);
	uu_list_insert(bp->cb_list, cp, idx);

	(void) pthread_mutex_unlock(&bp->cb_lock);
}

static repcache_client_t *
client_lookup(uint32_t id)
{
	client_bucket_t *bp = CLIENT_HASH(id);
	repcache_client_t *cp;

	(void) pthread_mutex_lock(&bp->cb_lock);

	cp = uu_list_find(bp->cb_list, &id, NULL, NULL);

	/*
	 * Bump the reference count
	 */
	if (cp != NULL) {
		(void) pthread_mutex_lock(&cp->rc_lock);
		assert(!(cp->rc_flags & RC_CLIENT_DEAD));
		cp->rc_refcnt++;
		(void) pthread_mutex_unlock(&cp->rc_lock);
	}
	(void) pthread_mutex_unlock(&bp->cb_lock);

	return (cp);
}

static void
client_release(repcache_client_t *cp)
{
	(void) pthread_mutex_lock(&cp->rc_lock);
	assert(cp->rc_refcnt > 0);
	assert(cp->rc_insert_thr != pthread_self());

	--cp->rc_refcnt;
	(void) pthread_cond_broadcast(&cp->rc_cv);
	(void) pthread_mutex_unlock(&cp->rc_lock);
}

/*
 * We only allow one thread to be inserting at a time, to prevent
 * insert/insert races.
 */
static void
client_start_insert(repcache_client_t *cp)
{
	(void) pthread_mutex_lock(&cp->rc_lock);
	assert(cp->rc_refcnt > 0);

	while (cp->rc_insert_thr != 0) {
		assert(cp->rc_insert_thr != pthread_self());
		(void) pthread_cond_wait(&cp->rc_cv, &cp->rc_lock);
	}
	cp->rc_insert_thr = pthread_self();
	(void) pthread_mutex_unlock(&cp->rc_lock);
}

static void
client_end_insert(repcache_client_t *cp)
{
	(void) pthread_mutex_lock(&cp->rc_lock);
	assert(cp->rc_insert_thr == pthread_self());
	cp->rc_insert_thr = 0;
	(void) pthread_cond_broadcast(&cp->rc_cv);
	(void) pthread_mutex_unlock(&cp->rc_lock);
}

/*ARGSUSED*/
static repcache_entity_t *
entity_alloc(repcache_client_t *cp)
{
	repcache_entity_t *ep = uu_zalloc(sizeof (repcache_entity_t));
	if (ep != NULL) {
		uu_avl_node_init(ep, &ep->re_link, entity_pool);
	}
	return (ep);
}

static void
entity_add(repcache_client_t *cp, repcache_entity_t *ep)
{
	uu_avl_index_t idx;

	(void) pthread_mutex_lock(&cp->rc_lock);
	assert(cp->rc_insert_thr == pthread_self());

	(void) uu_avl_find(cp->rc_entities, ep, NULL, &idx);
	uu_avl_insert(cp->rc_entities, ep, idx);

	(void) pthread_mutex_unlock(&cp->rc_lock);
}

static repcache_entity_t *
entity_find(repcache_client_t *cp, uint32_t id)
{
	repcache_entity_t *ep;

	(void) pthread_mutex_lock(&cp->rc_lock);
	ep = uu_avl_find(cp->rc_entities, &id, NULL, NULL);
	if (ep != NULL) {
		add_log_ptr(get_log(), RC_PTR_TYPE_ENTITY, id, ep);
		(void) pthread_mutex_lock(&ep->re_lock);
	}
	(void) pthread_mutex_unlock(&cp->rc_lock);

	return (ep);
}

/*
 * Fails with
 *   _DUPLICATE_ID - the ids are equal
 *   _UNKNOWN_ID - an id does not designate an active register
 */
static int
entity_find2(repcache_client_t *cp, uint32_t id1, repcache_entity_t **out1,
    uint32_t id2, repcache_entity_t **out2)
{
	repcache_entity_t *e1, *e2;
	request_log_entry_t *rlp;

	if (id1 == id2)
		return (REP_PROTOCOL_FAIL_DUPLICATE_ID);

	(void) pthread_mutex_lock(&cp->rc_lock);
	e1 = uu_avl_find(cp->rc_entities, &id1, NULL, NULL);
	e2 = uu_avl_find(cp->rc_entities, &id2, NULL, NULL);
	if (e1 == NULL || e2 == NULL) {
		(void) pthread_mutex_unlock(&cp->rc_lock);
		return (REP_PROTOCOL_FAIL_UNKNOWN_ID);
	}

	assert(e1 != e2);

	/*
	 * locks are ordered by id number
	 */
	if (id1 < id2) {
		(void) pthread_mutex_lock(&e1->re_lock);
		(void) pthread_mutex_lock(&e2->re_lock);
	} else {
		(void) pthread_mutex_lock(&e2->re_lock);
		(void) pthread_mutex_lock(&e1->re_lock);
	}
	*out1 = e1;
	*out2 = e2;

	(void) pthread_mutex_unlock(&cp->rc_lock);

	if ((rlp = get_log()) != NULL) {
		add_log_ptr(rlp, RC_PTR_TYPE_ENTITY, id1, e1);
		add_log_ptr(rlp, RC_PTR_TYPE_ENTITY, id2, e2);
	}

	return (REP_PROTOCOL_SUCCESS);
}

static void
entity_release(repcache_entity_t *ep)
{
	assert(ep->re_node.rnp_node == NULL ||
	    !MUTEX_HELD(&ep->re_node.rnp_node->rn_lock));
	(void) pthread_mutex_unlock(&ep->re_lock);
}

static void
entity_destroy(repcache_entity_t *entity)
{
	(void) pthread_mutex_lock(&entity->re_lock);
	rc_node_clear(&entity->re_node, 0);
	(void) pthread_mutex_unlock(&entity->re_lock);

	uu_avl_node_fini(entity, &entity->re_link, entity_pool);
	(void) pthread_mutex_destroy(&entity->re_lock);
	uu_free(entity);
}

static void
entity_remove(repcache_client_t *cp, uint32_t id)
{
	repcache_entity_t *entity;

	(void) pthread_mutex_lock(&cp->rc_lock);
	entity = uu_avl_find(cp->rc_entities, &id, NULL, NULL);
	if (entity != NULL)
		uu_avl_remove(cp->rc_entities, entity);
	(void) pthread_mutex_unlock(&cp->rc_lock);

	if (entity != NULL)
		entity_destroy(entity);
}

static void
entity_cleanup(repcache_client_t *cp)
{
	repcache_entity_t *ep;
	void *cookie = NULL;

	(void) pthread_mutex_lock(&cp->rc_lock);
	while ((ep = uu_avl_teardown(cp->rc_entities, &cookie)) != NULL) {
		(void) pthread_mutex_unlock(&cp->rc_lock);
		entity_destroy(ep);
		(void) pthread_mutex_lock(&cp->rc_lock);
	}
	(void) pthread_mutex_unlock(&cp->rc_lock);
}

/*ARGSUSED*/
static repcache_iter_t *
iter_alloc(repcache_client_t *cp)
{
	repcache_iter_t *iter;
	iter = uu_zalloc(sizeof (repcache_iter_t));
	if (iter != NULL)
		uu_avl_node_init(iter, &iter->ri_link, iter_pool);
	return (iter);
}

static void
iter_add(repcache_client_t *cp, repcache_iter_t *iter)
{
	uu_list_index_t idx;

	(void) pthread_mutex_lock(&cp->rc_lock);
	assert(cp->rc_insert_thr == pthread_self());

	(void) uu_avl_find(cp->rc_iters, iter, NULL, &idx);
	uu_avl_insert(cp->rc_iters, iter, idx);

	(void) pthread_mutex_unlock(&cp->rc_lock);
}

static repcache_iter_t *
iter_find(repcache_client_t *cp, uint32_t id)
{
	repcache_iter_t *iter;

	(void) pthread_mutex_lock(&cp->rc_lock);

	iter = uu_avl_find(cp->rc_iters, &id, NULL, NULL);
	if (iter != NULL) {
		add_log_ptr(get_log(), RC_PTR_TYPE_ITER, id, iter);
		(void) pthread_mutex_lock(&iter->ri_lock);
	}
	(void) pthread_mutex_unlock(&cp->rc_lock);

	return (iter);
}

/*
 * Fails with
 *   _UNKNOWN_ID - iter_id or entity_id does not designate an active register
 */
static int
iter_find_w_entity(repcache_client_t *cp, uint32_t iter_id,
    repcache_iter_t **iterp, uint32_t entity_id, repcache_entity_t **epp)
{
	repcache_iter_t *iter;
	repcache_entity_t *ep;
	request_log_entry_t *rlp;

	(void) pthread_mutex_lock(&cp->rc_lock);
	iter = uu_avl_find(cp->rc_iters, &iter_id, NULL, NULL);
	ep = uu_avl_find(cp->rc_entities, &entity_id, NULL, NULL);

	assert(iter == NULL || !MUTEX_HELD(&iter->ri_lock));
	assert(ep == NULL || !MUTEX_HELD(&ep->re_lock));

	if (iter == NULL || ep == NULL) {
		(void) pthread_mutex_unlock(&cp->rc_lock);
		return (REP_PROTOCOL_FAIL_UNKNOWN_ID);
	}

	(void) pthread_mutex_lock(&iter->ri_lock);
	(void) pthread_mutex_lock(&ep->re_lock);

	(void) pthread_mutex_unlock(&cp->rc_lock);

	*iterp = iter;
	*epp = ep;

	if ((rlp = get_log()) != NULL) {
		add_log_ptr(rlp, RC_PTR_TYPE_ENTITY, entity_id, ep);
		add_log_ptr(rlp, RC_PTR_TYPE_ITER, iter_id, iter);
	}

	return (REP_PROTOCOL_SUCCESS);
}

static void
iter_release(repcache_iter_t *iter)
{
	(void) pthread_mutex_unlock(&iter->ri_lock);
}

static void
iter_destroy(repcache_iter_t *iter)
{
	(void) pthread_mutex_lock(&iter->ri_lock);
	rc_iter_destroy(&iter->ri_iter);
	(void) pthread_mutex_unlock(&iter->ri_lock);

	uu_avl_node_fini(iter, &iter->ri_link, iter_pool);
	(void) pthread_mutex_destroy(&iter->ri_lock);
	uu_free(iter);
}

static void
iter_remove(repcache_client_t *cp, uint32_t id)
{
	repcache_iter_t *iter;

	(void) pthread_mutex_lock(&cp->rc_lock);
	iter = uu_avl_find(cp->rc_iters, &id, NULL, NULL);
	if (iter != NULL)
		uu_avl_remove(cp->rc_iters, iter);
	(void) pthread_mutex_unlock(&cp->rc_lock);

	if (iter != NULL)
		iter_destroy(iter);
}

static void
iter_cleanup(repcache_client_t *cp)
{
	repcache_iter_t *iter;
	void *cookie = NULL;

	(void) pthread_mutex_lock(&cp->rc_lock);
	while ((iter = uu_avl_teardown(cp->rc_iters, &cookie)) != NULL) {
		(void) pthread_mutex_unlock(&cp->rc_lock);
		iter_destroy(iter);
		(void) pthread_mutex_lock(&cp->rc_lock);
	}
	(void) pthread_mutex_unlock(&cp->rc_lock);
}

/*
 * Ensure that the passed client id is no longer usable, wait for any
 * outstanding invocations to complete, then destroy the client
 * structure.
 */
static void
client_destroy(uint32_t id)
{
	client_bucket_t *bp = CLIENT_HASH(id);
	repcache_client_t *cp;

	(void) pthread_mutex_lock(&bp->cb_lock);

	cp = uu_list_find(bp->cb_list, &id, NULL, NULL);

	if (cp == NULL) {
		(void) pthread_mutex_unlock(&bp->cb_lock);
		return;
	}

	uu_list_remove(bp->cb_list, cp);

	(void) pthread_mutex_unlock(&bp->cb_lock);

	/* kick the waiters out */
	rc_notify_info_fini(&cp->rc_notify_info);

	(void) pthread_mutex_lock(&cp->rc_lock);
	assert(!(cp->rc_flags & RC_CLIENT_DEAD));
	cp->rc_flags |= RC_CLIENT_DEAD;

	if (cp->rc_doorfd != -1) {
		if (door_revoke(cp->rc_doorfd) < 0)
			perror("door_revoke");
		cp->rc_doorfd = -1;
		cp->rc_doorid = INVALID_DOORID;
	}

	while (cp->rc_refcnt > 0)
		(void) pthread_cond_wait(&cp->rc_cv, &cp->rc_lock);

	assert(cp->rc_insert_thr == 0 && cp->rc_notify_thr == 0);
	(void) pthread_mutex_unlock(&cp->rc_lock);

	/*
	 * destroy outstanding objects
	 */
	entity_cleanup(cp);
	iter_cleanup(cp);

	/*
	 * clean up notifications
	 */
	rc_pg_notify_fini(&cp->rc_pg_notify);

	client_free(cp);
}

/*
 * Fails with
 *   _TYPE_MISMATCH - the entity is already set up with a different type
 *   _NO_RESOURCES - out of memory
 */
static int
entity_setup(repcache_client_t *cp, struct rep_protocol_entity_setup *rpr)
{
	repcache_entity_t *ep;
	uint32_t type;

	client_start_insert(cp);

	if ((ep = entity_find(cp, rpr->rpr_entityid)) != NULL) {
		type = ep->re_type;
		entity_release(ep);

		client_end_insert(cp);

		if (type != rpr->rpr_entitytype)
			return (REP_PROTOCOL_FAIL_TYPE_MISMATCH);
		return (REP_PROTOCOL_SUCCESS);
	}

	switch (type = rpr->rpr_entitytype) {
	case REP_PROTOCOL_ENTITY_SCOPE:
	case REP_PROTOCOL_ENTITY_SERVICE:
	case REP_PROTOCOL_ENTITY_INSTANCE:
	case REP_PROTOCOL_ENTITY_SNAPSHOT:
	case REP_PROTOCOL_ENTITY_SNAPLEVEL:
	case REP_PROTOCOL_ENTITY_PROPERTYGRP:
	case REP_PROTOCOL_ENTITY_PROPERTY:
		break;
	default:
		return (REP_PROTOCOL_FAIL_BAD_REQUEST);
	}

	ep = entity_alloc(cp);
	if (ep == NULL) {
		client_end_insert(cp);
		return (REP_PROTOCOL_FAIL_NO_RESOURCES);
	}

	ep->re_id = rpr->rpr_entityid;
	ep->re_changeid = INVALID_CHANGEID;

	ep->re_type = type;
	rc_node_ptr_init(&ep->re_node);

	entity_add(cp, ep);
	client_end_insert(cp);
	return (REP_PROTOCOL_SUCCESS);
}

/*ARGSUSED*/
static void
entity_name(repcache_client_t *cp, const void *in, size_t insz, void *out_arg,
    size_t *outsz, void *arg)
{
	const struct rep_protocol_entity_name *rpr = in;
	struct rep_protocol_name_response *out = out_arg;
	repcache_entity_t *ep;
	size_t sz = sizeof (out->rpr_name);

	assert(*outsz == sizeof (*out));

	ep = entity_find(cp, rpr->rpr_entityid);

	if (ep == NULL) {
		out->rpr_response = REP_PROTOCOL_FAIL_UNKNOWN_ID;
		*outsz = sizeof (out->rpr_response);
		return;
	}
	out->rpr_response = rc_node_name(&ep->re_node, out->rpr_name,
	    sz, rpr->rpr_answertype, &sz);
	entity_release(ep);

	/*
	 * If we fail, we only return the response code.
	 * If we succeed, we don't return anything after the '\0' in rpr_name.
	 */
	if (out->rpr_response != REP_PROTOCOL_SUCCESS)
		*outsz = sizeof (out->rpr_response);
	else
		*outsz = offsetof(struct rep_protocol_name_response,
		    rpr_name[sz + 1]);
}

/*ARGSUSED*/
static void
entity_parent_type(repcache_client_t *cp, const void *in, size_t insz,
    void *out_arg, size_t *outsz, void *arg)
{
	const struct rep_protocol_entity_name *rpr = in;
	struct rep_protocol_integer_response *out = out_arg;
	repcache_entity_t *ep;

	assert(*outsz == sizeof (*out));

	ep = entity_find(cp, rpr->rpr_entityid);

	if (ep == NULL) {
		out->rpr_response = REP_PROTOCOL_FAIL_UNKNOWN_ID;
		*outsz = sizeof (out->rpr_response);
		return;
	}

	out->rpr_response = rc_node_parent_type(&ep->re_node, &out->rpr_value);
	entity_release(ep);

	if (out->rpr_response != REP_PROTOCOL_SUCCESS)
		*outsz = sizeof (out->rpr_response);
}

/*
 * Fails with
 *   _DUPLICATE_ID - the ids are equal
 *   _UNKNOWN_ID - an id does not designate an active register
 *   _INVALID_TYPE - type is invalid
 *   _TYPE_MISMATCH - np doesn't carry children of type type
 *   _DELETED - np has been deleted
 *   _NOT_FOUND - no child with that name/type combo found
 *   _NO_RESOURCES
 *   _BACKEND_ACCESS
 */
static int
entity_get_child(repcache_client_t *cp,
    struct rep_protocol_entity_get_child *rpr)
{
	repcache_entity_t *parent, *child;
	int result;

	uint32_t parentid = rpr->rpr_entityid;
	uint32_t childid = rpr->rpr_childid;

	result = entity_find2(cp, childid, &child, parentid, &parent);
	if (result != REP_PROTOCOL_SUCCESS)
		return (result);

	rpr->rpr_name[sizeof (rpr->rpr_name) - 1] = 0;

	result = rc_node_get_child(&parent->re_node, rpr->rpr_name,
	    child->re_type, &child->re_node);

	entity_release(child);
	entity_release(parent);

	return (result);
}

/*
 * Returns _FAIL_DUPLICATE_ID, _FAIL_UNKNOWN_ID, _FAIL_NOT_SET, _FAIL_DELETED,
 * _FAIL_TYPE_MISMATCH, _FAIL_NOT_FOUND (scope has no parent), or _SUCCESS.
 * Fails with
 *   _DUPLICATE_ID - the ids are equal
 *   _UNKNOWN_ID - an id does not designate an active register
 *   _NOT_SET - child is not set
 *   _DELETED - child has been deleted
 *   _TYPE_MISMATCH - child's parent does not match that of the parent register
 *   _NOT_FOUND - child has no parent (and is a scope)
 */
static int
entity_get_parent(repcache_client_t *cp, struct rep_protocol_entity_parent *rpr)
{
	repcache_entity_t *child, *parent;
	int result;

	uint32_t childid = rpr->rpr_entityid;
	uint32_t outid = rpr->rpr_outid;

	result = entity_find2(cp, childid, &child, outid, &parent);
	if (result != REP_PROTOCOL_SUCCESS)
		return (result);

	result = rc_node_get_parent(&child->re_node, parent->re_type,
	    &parent->re_node);

	entity_release(child);
	entity_release(parent);

	return (result);
}

static int
entity_get(repcache_client_t *cp, struct rep_protocol_entity_get *rpr)
{
	repcache_entity_t *ep;
	int result;

	ep = entity_find(cp, rpr->rpr_entityid);

	if (ep == NULL)
		return (REP_PROTOCOL_FAIL_UNKNOWN_ID);

	switch (rpr->rpr_object) {
	case RP_ENTITY_GET_INVALIDATE:
		rc_node_clear(&ep->re_node, 0);
		result = REP_PROTOCOL_SUCCESS;
		break;
	case RP_ENTITY_GET_MOST_LOCAL_SCOPE:
		result = rc_local_scope(ep->re_type, &ep->re_node);
		break;
	default:
		result = REP_PROTOCOL_FAIL_BAD_REQUEST;
		break;
	}

	entity_release(ep);

	return (result);
}

static int
entity_update(repcache_client_t *cp, struct rep_protocol_entity_update *rpr)
{
	repcache_entity_t *ep;
	int result;

	if (rpr->rpr_changeid == INVALID_CHANGEID)
		return (REP_PROTOCOL_FAIL_BAD_REQUEST);

	ep = entity_find(cp, rpr->rpr_entityid);

	if (ep == NULL)
		return (REP_PROTOCOL_FAIL_UNKNOWN_ID);

	if (ep->re_changeid == rpr->rpr_changeid) {
		result = REP_PROTOCOL_DONE;
	} else {
		result = rc_node_update(&ep->re_node);
		if (result == REP_PROTOCOL_DONE)
			ep->re_changeid = rpr->rpr_changeid;
	}

	entity_release(ep);

	return (result);
}

static int
entity_reset(repcache_client_t *cp, struct rep_protocol_entity_reset *rpr)
{
	repcache_entity_t *ep;

	ep = entity_find(cp, rpr->rpr_entityid);
	if (ep == NULL)
		return (REP_PROTOCOL_FAIL_UNKNOWN_ID);

	rc_node_clear(&ep->re_node, 0);
	ep->re_txstate = REPCACHE_TX_INIT;

	entity_release(ep);
	return (REP_PROTOCOL_SUCCESS);
}

/*
 * Fails with
 *   _BAD_REQUEST - request has invalid changeid
 *		    rpr_name is invalid
 *		    cannot create children for parent's type of node
 *   _DUPLICATE_ID - request has duplicate ids
 *   _UNKNOWN_ID - request has unknown id
 *   _DELETED - parent has been deleted
 *   _NOT_SET - parent is reset
 *   _NOT_APPLICABLE - rpr_childtype is _PROPERTYGRP
 *   _INVALID_TYPE - parent is corrupt or rpr_childtype is invalid
 *   _TYPE_MISMATCH - parent cannot have children of type rpr_childtype
 *   _NO_RESOURCES
 *   _PERMISSION_DENIED
 *   _BACKEND_ACCESS
 *   _BACKEND_READONLY
 *   _EXISTS - child already exists
 *   _NOT_FOUND - could not allocate new id
 */
static int
entity_create_child(repcache_client_t *cp,
    struct rep_protocol_entity_create_child *rpr)
{
	repcache_entity_t *parent;
	repcache_entity_t *child;

	uint32_t parentid = rpr->rpr_entityid;
	uint32_t childid = rpr->rpr_childid;

	int result;

	if (rpr->rpr_changeid == INVALID_CHANGEID)
		return (REP_PROTOCOL_FAIL_BAD_REQUEST);

	result = entity_find2(cp, parentid, &parent, childid, &child);
	if (result != REP_PROTOCOL_SUCCESS)
		return (result);

	rpr->rpr_name[sizeof (rpr->rpr_name) - 1] = 0;

	if (child->re_changeid == rpr->rpr_changeid) {
		result = REP_PROTOCOL_SUCCESS;
	} else {
		result = rc_node_create_child(&parent->re_node,
		    rpr->rpr_childtype, rpr->rpr_name, &child->re_node);
		if (result == REP_PROTOCOL_SUCCESS)
			child->re_changeid = rpr->rpr_changeid;
	}

	entity_release(parent);
	entity_release(child);

	return (result);
}

static int
entity_create_pg(repcache_client_t *cp,
    struct rep_protocol_entity_create_pg *rpr)
{
	repcache_entity_t *parent;
	repcache_entity_t *child;

	uint32_t parentid = rpr->rpr_entityid;
	uint32_t childid = rpr->rpr_childid;

	int result;

	if (rpr->rpr_changeid == INVALID_CHANGEID)
		return (REP_PROTOCOL_FAIL_BAD_REQUEST);

	result = entity_find2(cp, parentid, &parent, childid, &child);
	if (result != REP_PROTOCOL_SUCCESS)
		return (result);

	rpr->rpr_name[sizeof (rpr->rpr_name) - 1] = 0;
	rpr->rpr_type[sizeof (rpr->rpr_type) - 1] = 0;

	if (child->re_changeid == rpr->rpr_changeid) {
		result = REP_PROTOCOL_SUCCESS;
	} else {
		result = rc_node_create_child_pg(&parent->re_node,
		    child->re_type, rpr->rpr_name, rpr->rpr_type,
		    rpr->rpr_flags, &child->re_node);
		if (result == REP_PROTOCOL_SUCCESS)
			child->re_changeid = rpr->rpr_changeid;
	}

	entity_release(parent);
	entity_release(child);

	return (result);
}

static int
entity_delete(repcache_client_t *cp,
    struct rep_protocol_entity_delete *rpr)
{
	repcache_entity_t *entity;

	uint32_t entityid = rpr->rpr_entityid;

	int result;

	if (rpr->rpr_changeid == INVALID_CHANGEID)
		return (REP_PROTOCOL_FAIL_BAD_REQUEST);

	entity = entity_find(cp, entityid);

	if (entity == NULL)
		return (REP_PROTOCOL_FAIL_UNKNOWN_ID);

	if (entity->re_changeid == rpr->rpr_changeid) {
		result = REP_PROTOCOL_SUCCESS;
	} else {
		result = rc_node_delete(&entity->re_node);
		if (result == REP_PROTOCOL_SUCCESS)
			entity->re_changeid = rpr->rpr_changeid;
	}

	entity_release(entity);

	return (result);
}

static rep_protocol_responseid_t
entity_teardown(repcache_client_t *cp, struct rep_protocol_entity_teardown *rpr)
{
	entity_remove(cp, rpr->rpr_entityid);

	return (REP_PROTOCOL_SUCCESS);
}

/*
 * Fails with
 *   _MISORDERED - the iterator exists and is not reset
 *   _NO_RESOURCES - out of memory
 */
static int
iter_setup(repcache_client_t *cp, struct rep_protocol_iter_request *rpr)
{
	repcache_iter_t *iter;
	uint32_t sequence;

	client_start_insert(cp);
	/*
	 * If the iter already exists, and hasn't been read from,
	 * we assume the previous call succeeded.
	 */
	if ((iter = iter_find(cp, rpr->rpr_iterid)) != NULL) {
		sequence = iter->ri_sequence;
		iter_release(iter);

		client_end_insert(cp);

		if (sequence != 0)
			return (REP_PROTOCOL_FAIL_MISORDERED);
		return (REP_PROTOCOL_SUCCESS);
	}

	iter = iter_alloc(cp);
	if (iter == NULL) {
		client_end_insert(cp);
		return (REP_PROTOCOL_FAIL_NO_RESOURCES);
	}

	iter->ri_id = rpr->rpr_iterid;
	iter->ri_type = REP_PROTOCOL_TYPE_INVALID;
	iter->ri_sequence = 0;
	iter_add(cp, iter);

	client_end_insert(cp);
	return (REP_PROTOCOL_SUCCESS);
}

/*
 * Fails with
 *   _UNKNOWN_ID
 *   _MISORDERED - iterator has already been started
 *   _NOT_SET
 *   _DELETED
 *   _TYPE_MISMATCH - entity cannot have type children
 *   _BAD_REQUEST - rpr_flags is invalid
 *		    rpr_pattern is invalid
 *   _NO_RESOURCES
 *   _INVALID_TYPE
 *   _BACKEND_ACCESS
 */
static int
iter_start(repcache_client_t *cp, struct rep_protocol_iter_start *rpr)
{
	int result;
	repcache_iter_t *iter;
	repcache_entity_t *ep;

	result = iter_find_w_entity(cp, rpr->rpr_iterid, &iter,
	    rpr->rpr_entity, &ep);

	if (result != REP_PROTOCOL_SUCCESS)
		return (REP_PROTOCOL_FAIL_UNKNOWN_ID);

	if (iter->ri_sequence > 1) {
		result = REP_PROTOCOL_FAIL_MISORDERED;
		goto end;
	}

	if (iter->ri_sequence == 1) {
		result = REP_PROTOCOL_SUCCESS;
		goto end;
	}

	rpr->rpr_pattern[sizeof (rpr->rpr_pattern) - 1] = 0;

	result = rc_node_setup_iter(&ep->re_node, &iter->ri_iter,
	    rpr->rpr_itertype, rpr->rpr_flags, rpr->rpr_pattern);

	if (result == REP_PROTOCOL_SUCCESS)
		iter->ri_sequence++;

end:
	iter_release(iter);
	entity_release(ep);
	return (result);
}

/*
 * Returns
 *   _UNKNOWN_ID
 *   _NOT_SET - iter has not been started
 *   _MISORDERED
 *   _BAD_REQUEST - iter walks values
 *   _TYPE_MISMATCH - iter does not walk type entities
 *   _DELETED - parent was deleted
 *   _NO_RESOURCES
 *   _INVALID_TYPE - type is invalid
 *   _DONE
 *   _SUCCESS
 *
 * For composed property group iterators, can also return
 *   _TYPE_MISMATCH - parent cannot have type children
 *   _BACKEND_ACCESS
 */
static rep_protocol_responseid_t
iter_read(repcache_client_t *cp, struct rep_protocol_iter_read *rpr)
{
	rep_protocol_responseid_t result;
	repcache_iter_t *iter;
	repcache_entity_t *ep;
	uint32_t sequence;

	result = iter_find_w_entity(cp, rpr->rpr_iterid, &iter,
	    rpr->rpr_entityid, &ep);

	if (result != REP_PROTOCOL_SUCCESS)
		return (result);

	sequence = rpr->rpr_sequence;

	if (iter->ri_sequence == 0) {
		iter_release(iter);
		entity_release(ep);
		return (REP_PROTOCOL_FAIL_NOT_SET);
	}

	if (sequence == 1) {
		iter_release(iter);
		entity_release(ep);
		return (REP_PROTOCOL_FAIL_MISORDERED);
	}

	if (sequence == iter->ri_sequence) {
		iter_release(iter);
		entity_release(ep);
		return (REP_PROTOCOL_SUCCESS);
	}

	if (sequence == iter->ri_sequence + 1) {
		result = rc_iter_next(iter->ri_iter, &ep->re_node,
		    ep->re_type);

		if (result == REP_PROTOCOL_SUCCESS)
			iter->ri_sequence++;

		iter_release(iter);
		entity_release(ep);

		return (result);
	}

	iter_release(iter);
	entity_release(ep);
	return (REP_PROTOCOL_FAIL_MISORDERED);
}

/*ARGSUSED*/
static void
iter_read_value(repcache_client_t *cp, const void *in, size_t insz,
    void *out_arg, size_t *outsz, void *arg)
{
	const struct rep_protocol_iter_read_value *rpr = in;
	struct rep_protocol_value_response *out = out_arg;
	rep_protocol_responseid_t result;

	repcache_iter_t *iter;
	uint32_t sequence;
	int repeat;

	assert(*outsz == sizeof (*out));

	iter = iter_find(cp, rpr->rpr_iterid);

	if (iter == NULL) {
		result = REP_PROTOCOL_FAIL_UNKNOWN_ID;
		goto out;
	}

	sequence = rpr->rpr_sequence;

	if (iter->ri_sequence == 0) {
		iter_release(iter);
		result = REP_PROTOCOL_FAIL_NOT_SET;
		goto out;
	}

	repeat = (sequence == iter->ri_sequence);

	if (sequence == 1 || (!repeat && sequence != iter->ri_sequence + 1)) {
		iter_release(iter);
		result = REP_PROTOCOL_FAIL_MISORDERED;
		goto out;
	}

	result = rc_iter_next_value(iter->ri_iter, out, outsz, repeat);

	if (!repeat && result == REP_PROTOCOL_SUCCESS)
		iter->ri_sequence++;

	iter_release(iter);

out:
	/*
	 * If we fail, we only return the response code.
	 * If we succeed, rc_iter_next_value has shortened *outsz
	 * to only include the value bytes needed.
	 */
	if (result != REP_PROTOCOL_SUCCESS && result != REP_PROTOCOL_DONE)
		*outsz = sizeof (out->rpr_response);

	out->rpr_response = result;
}

static int
iter_reset(repcache_client_t *cp, struct rep_protocol_iter_request *rpr)
{
	repcache_iter_t *iter = iter_find(cp, rpr->rpr_iterid);

	if (iter == NULL)
		return (REP_PROTOCOL_FAIL_UNKNOWN_ID);

	if (iter->ri_sequence != 0) {
		iter->ri_sequence = 0;
		rc_iter_destroy(&iter->ri_iter);
	}
	iter_release(iter);
	return (REP_PROTOCOL_SUCCESS);
}

static rep_protocol_responseid_t
iter_teardown(repcache_client_t *cp, struct rep_protocol_iter_request *rpr)
{
	iter_remove(cp, rpr->rpr_iterid);

	return (REP_PROTOCOL_SUCCESS);
}

static rep_protocol_responseid_t
tx_start(repcache_client_t *cp, struct rep_protocol_transaction_start *rpr)
{
	repcache_entity_t *tx;
	repcache_entity_t *ep;
	rep_protocol_responseid_t result;

	uint32_t txid = rpr->rpr_entityid_tx;
	uint32_t epid = rpr->rpr_entityid;

	result = entity_find2(cp, txid, &tx, epid, &ep);
	if (result != REP_PROTOCOL_SUCCESS)
		return (result);

	if (tx->re_txstate == REPCACHE_TX_SETUP) {
		result = REP_PROTOCOL_SUCCESS;
		goto end;
	}
	if (tx->re_txstate != REPCACHE_TX_INIT) {
		result = REP_PROTOCOL_FAIL_MISORDERED;
		goto end;
	}

	result = rc_node_setup_tx(&ep->re_node, &tx->re_node);

end:
	if (result == REP_PROTOCOL_SUCCESS)
		tx->re_txstate = REPCACHE_TX_SETUP;
	else
		rc_node_clear(&tx->re_node, 0);

	entity_release(ep);
	entity_release(tx);
	return (result);
}

/*ARGSUSED*/
static void
tx_commit(repcache_client_t *cp, const void *in, size_t insz,
    void *out_arg, size_t *outsz, void *arg)
{
	struct rep_protocol_response *out = out_arg;
	const struct rep_protocol_transaction_commit *rpr = in;
	repcache_entity_t *tx;

	assert(*outsz == sizeof (*out));
	assert(insz >= REP_PROTOCOL_TRANSACTION_COMMIT_MIN_SIZE);

	if (rpr->rpr_size != insz) {
		out->rpr_response = REP_PROTOCOL_FAIL_BAD_REQUEST;
		return;
	}

	tx = entity_find(cp, rpr->rpr_entityid);

	if (tx == NULL) {
		out->rpr_response = REP_PROTOCOL_FAIL_UNKNOWN_ID;
		return;
	}

	switch (tx->re_txstate) {
	case REPCACHE_TX_INIT:
		out->rpr_response = REP_PROTOCOL_FAIL_MISORDERED;
		break;

	case REPCACHE_TX_SETUP:
		out->rpr_response = rc_tx_commit(&tx->re_node, rpr->rpr_cmd,
		    insz - REP_PROTOCOL_TRANSACTION_COMMIT_MIN_SIZE);

		if (out->rpr_response == REP_PROTOCOL_SUCCESS) {
			tx->re_txstate = REPCACHE_TX_COMMITTED;
			rc_node_clear(&tx->re_node, 0);
		}

		break;
	case REPCACHE_TX_COMMITTED:
		out->rpr_response = REP_PROTOCOL_SUCCESS;
		break;
	default:
		assert(0);	/* CAN'T HAPPEN */
		break;
	}

	entity_release(tx);
}

static rep_protocol_responseid_t
next_snaplevel(repcache_client_t *cp, struct rep_protocol_entity_pair *rpr)
{
	repcache_entity_t *src;
	repcache_entity_t *dest;

	uint32_t srcid = rpr->rpr_entity_src;
	uint32_t destid = rpr->rpr_entity_dst;

	int result;

	result = entity_find2(cp, srcid, &src, destid, &dest);
	if (result != REP_PROTOCOL_SUCCESS)
		return (result);

	result = rc_node_next_snaplevel(&src->re_node, &dest->re_node);

	entity_release(src);
	entity_release(dest);

	return (result);
}

static rep_protocol_responseid_t
snapshot_take(repcache_client_t *cp, struct rep_protocol_snapshot_take *rpr)
{
	repcache_entity_t *src;
	uint32_t srcid = rpr->rpr_entityid_src;
	repcache_entity_t *dest;
	uint32_t destid = rpr->rpr_entityid_dest;

	int result;

	result = entity_find2(cp, srcid, &src, destid, &dest);
	if (result != REP_PROTOCOL_SUCCESS)
		return (result);

	if (dest->re_type != REP_PROTOCOL_ENTITY_SNAPSHOT) {
		result = REP_PROTOCOL_FAIL_TYPE_MISMATCH;
	} else {
		rpr->rpr_name[sizeof (rpr->rpr_name) - 1] = 0;

		if (rpr->rpr_flags == REP_SNAPSHOT_NEW)
			result = rc_snapshot_take_new(&src->re_node, NULL,
			    NULL, rpr->rpr_name, &dest->re_node);
		else if (rpr->rpr_flags == REP_SNAPSHOT_ATTACH &&
		    rpr->rpr_name[0] == 0)
			result = rc_snapshot_take_attach(&src->re_node,
			    &dest->re_node);
		else
			result = REP_PROTOCOL_FAIL_BAD_REQUEST;
	}
	entity_release(src);
	entity_release(dest);

	return (result);
}

static rep_protocol_responseid_t
snapshot_take_named(repcache_client_t *cp,
    struct rep_protocol_snapshot_take_named *rpr)
{
	repcache_entity_t *src;
	uint32_t srcid = rpr->rpr_entityid_src;
	repcache_entity_t *dest;
	uint32_t destid = rpr->rpr_entityid_dest;

	int result;

	result = entity_find2(cp, srcid, &src, destid, &dest);
	if (result != REP_PROTOCOL_SUCCESS)
		return (result);

	if (dest->re_type != REP_PROTOCOL_ENTITY_SNAPSHOT) {
		result = REP_PROTOCOL_FAIL_TYPE_MISMATCH;
	} else {
		rpr->rpr_svcname[sizeof (rpr->rpr_svcname) - 1] = 0;
		rpr->rpr_instname[sizeof (rpr->rpr_instname) - 1] = 0;
		rpr->rpr_name[sizeof (rpr->rpr_name) - 1] = 0;

		result = rc_snapshot_take_new(&src->re_node, rpr->rpr_svcname,
		    rpr->rpr_instname, rpr->rpr_name, &dest->re_node);
	}
	entity_release(src);
	entity_release(dest);

	return (result);
}

static rep_protocol_responseid_t
snapshot_attach(repcache_client_t *cp, struct rep_protocol_snapshot_attach *rpr)
{
	repcache_entity_t *src;
	uint32_t srcid = rpr->rpr_entityid_src;
	repcache_entity_t *dest;
	uint32_t destid = rpr->rpr_entityid_dest;

	int result;

	result = entity_find2(cp, srcid, &src, destid, &dest);
	if (result != REP_PROTOCOL_SUCCESS)
		return (result);

	result = rc_snapshot_attach(&src->re_node, &dest->re_node);

	entity_release(src);
	entity_release(dest);

	return (result);
}

/*ARGSUSED*/
static void
property_get_type(repcache_client_t *cp, const void *in, size_t insz,
    void *out_arg, size_t *outsz, void *arg)
{
	const struct rep_protocol_property_request *rpr = in;
	struct rep_protocol_integer_response *out = out_arg;
	repcache_entity_t *ep;
	rep_protocol_value_type_t t = 0;

	assert(*outsz == sizeof (*out));

	ep = entity_find(cp, rpr->rpr_entityid);

	if (ep == NULL) {
		out->rpr_response = REP_PROTOCOL_FAIL_UNKNOWN_ID;
		*outsz = sizeof (out->rpr_response);
		return;
	}

	out->rpr_response = rc_node_get_property_type(&ep->re_node, &t);

	entity_release(ep);

	if (out->rpr_response != REP_PROTOCOL_SUCCESS)
		*outsz = sizeof (out->rpr_response);
	else
		out->rpr_value = t;
}

/*
 * Fails with:
 *	_UNKNOWN_ID - an id does not designate an active register
 *	_NOT_SET - The property is not set
 *	_DELETED - The property has been deleted
 *	_TYPE_MISMATCH - The object is not a property
 *	_NOT_FOUND - The property has no values.
 *
 * Succeeds with:
 *	_SUCCESS - The property has 1 value.
 *	_TRUNCATED - The property has >1 value.
 */
/*ARGSUSED*/
static void
property_get_value(repcache_client_t *cp, const void *in, size_t insz,
    void *out_arg, size_t *outsz, void *arg)
{
	const struct rep_protocol_property_request *rpr = in;
	struct rep_protocol_value_response *out = out_arg;
	repcache_entity_t *ep;

	assert(*outsz == sizeof (*out));

	ep = entity_find(cp, rpr->rpr_entityid);
	if (ep == NULL) {
		out->rpr_response = REP_PROTOCOL_FAIL_UNKNOWN_ID;
		*outsz = sizeof (out->rpr_response);
		return;
	}

	out->rpr_response = rc_node_get_property_value(&ep->re_node, out,
	    outsz);

	entity_release(ep);

	/*
	 * If we fail, we only return the response code.
	 * If we succeed, rc_node_get_property_value has shortened *outsz
	 * to only include the value bytes needed.
	 */
	if (out->rpr_response != REP_PROTOCOL_SUCCESS &&
	    out->rpr_response != REP_PROTOCOL_FAIL_TRUNCATED)
		*outsz = sizeof (out->rpr_response);
}

static rep_protocol_responseid_t
propertygrp_notify(repcache_client_t *cp,
    struct rep_protocol_propertygrp_request *rpr, int *out_fd)
{
	int fds[2];
	int ours, theirs;

	rep_protocol_responseid_t result;
	repcache_entity_t *ep;

	if (pipe(fds) < 0)
		return (REP_PROTOCOL_FAIL_NO_RESOURCES);

	ours = fds[0];
	theirs = fds[1];

	if ((ep = entity_find(cp, rpr->rpr_entityid)) == NULL) {
		result = REP_PROTOCOL_FAIL_UNKNOWN_ID;
		goto fail;
	}

	/*
	 * While the following can race with other threads setting up a
	 * notification, the worst that can happen is that our fd has
	 * already been closed before we return.
	 */
	result = rc_pg_notify_setup(&cp->rc_pg_notify, &ep->re_node,
	    ours);

	entity_release(ep);

	if (result != REP_PROTOCOL_SUCCESS)
		goto fail;

	*out_fd = theirs;
	return (REP_PROTOCOL_SUCCESS);

fail:
	(void) close(ours);
	(void) close(theirs);

	return (result);
}

static rep_protocol_responseid_t
client_add_notify(repcache_client_t *cp,
    struct rep_protocol_notify_request *rpr)
{
	rpr->rpr_pattern[sizeof (rpr->rpr_pattern) - 1] = 0;

	switch (rpr->rpr_type) {
	case REP_PROTOCOL_NOTIFY_PGNAME:
		return (rc_notify_info_add_name(&cp->rc_notify_info,
		    rpr->rpr_pattern));

	case REP_PROTOCOL_NOTIFY_PGTYPE:
		return (rc_notify_info_add_type(&cp->rc_notify_info,
		    rpr->rpr_pattern));

	default:
		return (REP_PROTOCOL_FAIL_BAD_REQUEST);
	}
}

/*ARGSUSED*/
static void
client_wait(repcache_client_t *cp, const void *in, size_t insz,
    void *out_arg, size_t *outsz, void *arg)
{
	int result;
	repcache_entity_t *ep;
	const struct rep_protocol_wait_request *rpr = in;
	struct rep_protocol_fmri_response *out = out_arg;

	assert(*outsz == sizeof (*out));

	(void) pthread_mutex_lock(&cp->rc_lock);
	if (cp->rc_notify_thr != 0) {
		(void) pthread_mutex_unlock(&cp->rc_lock);
		out->rpr_response = REP_PROTOCOL_FAIL_EXISTS;
		*outsz = sizeof (out->rpr_response);
		return;
	}
	cp->rc_notify_thr = pthread_self();
	(void) pthread_mutex_unlock(&cp->rc_lock);

	result = rc_notify_info_wait(&cp->rc_notify_info, &cp->rc_notify_ptr,
	    out->rpr_fmri, sizeof (out->rpr_fmri));

	if (result == REP_PROTOCOL_SUCCESS) {
		if ((ep = entity_find(cp, rpr->rpr_entityid)) != NULL) {
			if (ep->re_type == REP_PROTOCOL_ENTITY_PROPERTYGRP) {
				rc_node_ptr_assign(&ep->re_node,
				    &cp->rc_notify_ptr);
			} else {
				result = REP_PROTOCOL_FAIL_TYPE_MISMATCH;
			}
			entity_release(ep);
		} else {
			result = REP_PROTOCOL_FAIL_UNKNOWN_ID;
		}
		rc_node_clear(&cp->rc_notify_ptr, 0);
	}

	(void) pthread_mutex_lock(&cp->rc_lock);
	assert(cp->rc_notify_thr == pthread_self());
	cp->rc_notify_thr = 0;
	(void) pthread_mutex_unlock(&cp->rc_lock);

	out->rpr_response = result;
	if (result != REP_PROTOCOL_SUCCESS)
		*outsz = sizeof (out->rpr_response);
}

/*
 * Can return:
 *	_PERMISSION_DENIED	not enough privileges to do request.
 *	_BAD_REQUEST		name is not valid or reserved
 *	_TRUNCATED		name is too long for current repository path
 *	_UNKNOWN		failed for unknown reason (details written to
 *				console)
 *	_BACKEND_READONLY	backend is not writable
 *
 *	_SUCCESS		Backup completed successfully.
 */
static rep_protocol_responseid_t
backup_repository(repcache_client_t *cp,
    struct rep_protocol_backup_request *rpr)
{
	rep_protocol_responseid_t result;
	ucred_t *uc = get_ucred();

	if (!client_is_privileged() && (uc == NULL || ucred_geteuid(uc) != 0))
		return (REP_PROTOCOL_FAIL_PERMISSION_DENIED);

	rpr->rpr_name[REP_PROTOCOL_NAME_LEN - 1] = 0;
	if (strcmp(rpr->rpr_name, REPOSITORY_BOOT_BACKUP) == 0)
		return (REP_PROTOCOL_FAIL_BAD_REQUEST);

	(void) pthread_mutex_lock(&cp->rc_lock);
	if (rpr->rpr_changeid != cp->rc_changeid) {
		result = backend_create_backup(rpr->rpr_name);
		if (result == REP_PROTOCOL_SUCCESS)
			cp->rc_changeid = rpr->rpr_changeid;
	} else {
		result = REP_PROTOCOL_SUCCESS;
	}
	(void) pthread_mutex_unlock(&cp->rc_lock);

	return (result);
}


typedef rep_protocol_responseid_t protocol_simple_f(repcache_client_t *cp,
    const void *rpr);

/*ARGSUSED*/
static void
simple_handler(repcache_client_t *cp, const void *in, size_t insz,
    void *out_arg, size_t *outsz, void *arg)
{
	protocol_simple_f *f = (protocol_simple_f *)arg;
	rep_protocol_response_t *out = out_arg;

	assert(*outsz == sizeof (*out));
	assert(f != NULL);

	out->rpr_response = (*f)(cp, in);
}

typedef rep_protocol_responseid_t protocol_simple_fd_f(repcache_client_t *cp,
    const void *rpr, int *out_fd);

/*ARGSUSED*/
static void
simple_fd_handler(repcache_client_t *cp, const void *in, size_t insz,
    void *out_arg, size_t *outsz, void *arg, int *out_fd)
{
	protocol_simple_fd_f *f = (protocol_simple_fd_f *)arg;
	rep_protocol_response_t *out = out_arg;

	assert(*outsz == sizeof (*out));
	assert(f != NULL);

	out->rpr_response = (*f)(cp, in, out_fd);
}

typedef void protocol_handler_f(repcache_client_t *, const void *in,
    size_t insz, void *out, size_t *outsz, void *arg);

typedef void protocol_handler_fdret_f(repcache_client_t *, const void *in,
    size_t insz, void *out, size_t *outsz, void *arg, int *fd_out);

#define	PROTO(p, f, in) {						\
		p, #p, simple_handler, (void *)(&f), NULL,		\
		    sizeof (in), sizeof (rep_protocol_response_t), 0	\
	}

#define	PROTO_FD_OUT(p, f, in) {					\
		p, #p, NULL, (void *)(&f), simple_fd_handler,		\
		    sizeof (in),					\
		    sizeof (rep_protocol_response_t),			\
		    PROTO_FLAG_RETFD					\
	}

#define	PROTO_VARIN(p, f, insz) {					\
		p, #p, &(f), NULL, NULL,				\
		    insz, sizeof (rep_protocol_response_t),		\
		    PROTO_FLAG_VARINPUT					\
	}

#define	PROTO_UINT_OUT(p, f, in) {					\
		p, #p, &(f), NULL, NULL,				\
		    sizeof (in),					\
		    sizeof (struct rep_protocol_integer_response), 0	\
	}

#define	PROTO_NAME_OUT(p, f, in) {					\
		p, #p, &(f), NULL, NULL,				\
		    sizeof (in),					\
		    sizeof (struct rep_protocol_name_response), 0	\
	}

#define	PROTO_FMRI_OUT(p, f, in) {					\
		p, #p, &(f), NULL, NULL,				\
		    sizeof (in),					\
		    sizeof (struct rep_protocol_fmri_response), 0	\
	}

#define	PROTO_VALUE_OUT(p, f, in) {					\
		p, #p, &(f), NULL, NULL,				\
		    sizeof (in),					\
		    sizeof (struct rep_protocol_value_response), 0	\
	}

#define	PROTO_PANIC(p)	{ p, #p, NULL, NULL, NULL, 0, 0, PROTO_FLAG_PANIC }
#define	PROTO_END()	{ 0, NULL, NULL, NULL, NULL, 0, 0, PROTO_FLAG_PANIC }

#define	PROTO_FLAG_PANIC	0x00000001	/* should never be called */
#define	PROTO_FLAG_VARINPUT	0x00000004	/* in_size is minimum size */
#define	PROTO_FLAG_RETFD	0x00000008	/* can also return an FD */

#define	PROTO_ALL_FLAGS		0x0000000f	/* all flags */

static struct protocol_entry {
	enum rep_protocol_requestid	pt_request;
	const char			*pt_name;
	protocol_handler_f		*pt_handler;
	void				*pt_arg;
	protocol_handler_fdret_f	*pt_fd_handler;
	size_t				pt_in_size;
	size_t				pt_out_max;
	uint32_t			pt_flags;
} protocol_table[] = {
	PROTO_PANIC(REP_PROTOCOL_CLOSE),		/* special case */

	PROTO(REP_PROTOCOL_ENTITY_SETUP,		entity_setup,
	    struct rep_protocol_entity_setup),
	PROTO_NAME_OUT(REP_PROTOCOL_ENTITY_NAME,	entity_name,
	    struct rep_protocol_entity_name),
	PROTO_UINT_OUT(REP_PROTOCOL_ENTITY_PARENT_TYPE,	entity_parent_type,
	    struct rep_protocol_entity_parent_type),
	PROTO(REP_PROTOCOL_ENTITY_GET_CHILD,		entity_get_child,
	    struct rep_protocol_entity_get_child),
	PROTO(REP_PROTOCOL_ENTITY_GET_PARENT,		entity_get_parent,
	    struct rep_protocol_entity_parent),
	PROTO(REP_PROTOCOL_ENTITY_GET,			entity_get,
	    struct rep_protocol_entity_get),
	PROTO(REP_PROTOCOL_ENTITY_UPDATE,		entity_update,
	    struct rep_protocol_entity_update),
	PROTO(REP_PROTOCOL_ENTITY_CREATE_CHILD,		entity_create_child,
	    struct rep_protocol_entity_create_child),
	PROTO(REP_PROTOCOL_ENTITY_CREATE_PG,		entity_create_pg,
	    struct rep_protocol_entity_create_pg),
	PROTO(REP_PROTOCOL_ENTITY_DELETE,		entity_delete,
	    struct rep_protocol_entity_delete),
	PROTO(REP_PROTOCOL_ENTITY_RESET,		entity_reset,
	    struct rep_protocol_entity_reset),
	PROTO(REP_PROTOCOL_ENTITY_TEARDOWN,		entity_teardown,
	    struct rep_protocol_entity_teardown),

	PROTO(REP_PROTOCOL_ITER_SETUP,			iter_setup,
	    struct rep_protocol_iter_request),
	PROTO(REP_PROTOCOL_ITER_START,			iter_start,
	    struct rep_protocol_iter_start),
	PROTO(REP_PROTOCOL_ITER_READ,			iter_read,
	    struct rep_protocol_iter_read),
	PROTO_VALUE_OUT(REP_PROTOCOL_ITER_READ_VALUE,	iter_read_value,
	    struct rep_protocol_iter_read_value),
	PROTO(REP_PROTOCOL_ITER_RESET,			iter_reset,
	    struct rep_protocol_iter_request),
	PROTO(REP_PROTOCOL_ITER_TEARDOWN,		iter_teardown,
	    struct rep_protocol_iter_request),

	PROTO(REP_PROTOCOL_NEXT_SNAPLEVEL,		next_snaplevel,
	    struct rep_protocol_entity_pair),

	PROTO(REP_PROTOCOL_SNAPSHOT_TAKE,		snapshot_take,
	    struct rep_protocol_snapshot_take),
	PROTO(REP_PROTOCOL_SNAPSHOT_TAKE_NAMED,		snapshot_take_named,
	    struct rep_protocol_snapshot_take_named),
	PROTO(REP_PROTOCOL_SNAPSHOT_ATTACH,		snapshot_attach,
	    struct rep_protocol_snapshot_attach),

	PROTO_UINT_OUT(REP_PROTOCOL_PROPERTY_GET_TYPE,	property_get_type,
	    struct rep_protocol_property_request),
	PROTO_VALUE_OUT(REP_PROTOCOL_PROPERTY_GET_VALUE, property_get_value,
	    struct rep_protocol_property_request),

	PROTO_FD_OUT(REP_PROTOCOL_PROPERTYGRP_SETUP_WAIT, propertygrp_notify,
	    struct rep_protocol_propertygrp_request),
	PROTO(REP_PROTOCOL_PROPERTYGRP_TX_START,	tx_start,
	    struct rep_protocol_transaction_start),
	PROTO_VARIN(REP_PROTOCOL_PROPERTYGRP_TX_COMMIT,	tx_commit,
	    REP_PROTOCOL_TRANSACTION_COMMIT_MIN_SIZE),

	PROTO(REP_PROTOCOL_CLIENT_ADD_NOTIFY,		client_add_notify,
	    struct rep_protocol_notify_request),
	PROTO_FMRI_OUT(REP_PROTOCOL_CLIENT_WAIT,	client_wait,
	    struct rep_protocol_wait_request),

	PROTO(REP_PROTOCOL_BACKUP,			backup_repository,
	    struct rep_protocol_backup_request),

	PROTO_END()
};
#undef PROTO
#undef PROTO_FMRI_OUT
#undef PROTO_NAME_OUT
#undef PROTO_UINT_OUT
#undef PROTO_PANIC
#undef PROTO_END

/*
 * The number of entries, sans PROTO_END()
 */
#define	PROTOCOL_ENTRIES \
	    (sizeof (protocol_table) / sizeof (*protocol_table) - 1)

#define	PROTOCOL_PREFIX "REP_PROTOCOL_"

int
client_init(void)
{
	int i;
	struct protocol_entry *e;

	if (!client_hash_init())
		return (0);

	if (request_log_size > 0) {
		request_log = uu_zalloc(request_log_size *
		    sizeof (request_log_entry_t));
	}

	/*
	 * update the names to not include REP_PROTOCOL_
	 */
	for (i = 0; i < PROTOCOL_ENTRIES; i++) {
		e = &protocol_table[i];
		assert(strncmp(e->pt_name, PROTOCOL_PREFIX,
		    strlen(PROTOCOL_PREFIX)) == 0);
		e->pt_name += strlen(PROTOCOL_PREFIX);
	}
	/*
	 * verify the protocol table is consistent
	 */
	for (i = 0; i < PROTOCOL_ENTRIES; i++) {
		e = &protocol_table[i];
		assert(e->pt_request == (REP_PROTOCOL_BASE + i));

		assert((e->pt_flags & ~PROTO_ALL_FLAGS) == 0);

		if (e->pt_flags & PROTO_FLAG_PANIC)
			assert(e->pt_in_size == 0 && e->pt_out_max == 0 &&
			    e->pt_handler == NULL);
		else
			assert(e->pt_in_size != 0 && e->pt_out_max != 0 &&
			    (e->pt_handler != NULL ||
			    e->pt_fd_handler != NULL));
	}
	assert((REP_PROTOCOL_BASE + i) == REP_PROTOCOL_MAX_REQUEST);

	assert(protocol_table[i].pt_request == 0);

	return (1);
}

static void
client_switcher(void *cookie, char *argp, size_t arg_size, door_desc_t *desc_in,
    uint_t n_desc)
{
	thread_info_t *ti = thread_self();

	repcache_client_t *cp;
	uint32_t id = (uint32_t)cookie;
	enum rep_protocol_requestid request_code;

	rep_protocol_responseid_t result = INVALID_RESULT;

	struct protocol_entry *e;

	char *retval = NULL;
	size_t retsize = 0;

	int retfd = -1;
	door_desc_t desc;
	request_log_entry_t *rlp;

	rlp = start_log(id);

	if (n_desc != 0)
		uu_die("can't happen: %d descriptors @%p (cookie %p)",
		    n_desc, desc_in, cookie);

	if (argp == DOOR_UNREF_DATA) {
		client_destroy(id);
		goto bad_end;
	}

	thread_newstate(ti, TI_CLIENT_CALL);

	/*
	 * To simplify returning just a result code, we set up for
	 * that case here.
	 */
	retval = (char *)&result;
	retsize = sizeof (result);

	if (arg_size < sizeof (request_code)) {
		result = REP_PROTOCOL_FAIL_BAD_REQUEST;
		goto end_unheld;
	}

	ti->ti_client_request = (void *)argp;

	/* LINTED alignment */
	request_code = *(uint32_t *)argp;

	if (rlp != NULL) {
		rlp->rl_request = request_code;
	}
	/*
	 * In order to avoid locking problems on removal, we handle the
	 * "close" case before doing a lookup.
	 */
	if (request_code == REP_PROTOCOL_CLOSE) {
		client_destroy(id);
		result = REP_PROTOCOL_SUCCESS;
		goto end_unheld;
	}

	cp = client_lookup(id);
	/*
	 * cp is held
	 */

	if (cp == NULL)
		goto bad_end;

	if (rlp != NULL)
		rlp->rl_client = cp;

	ti->ti_active_client = cp;

	if (request_code < REP_PROTOCOL_BASE ||
	    request_code >= REP_PROTOCOL_BASE + PROTOCOL_ENTRIES) {
		result = REP_PROTOCOL_FAIL_BAD_REQUEST;
		goto end;
	}

	e = &protocol_table[request_code - REP_PROTOCOL_BASE];

	assert(!(e->pt_flags & PROTO_FLAG_PANIC));

	if (e->pt_flags & PROTO_FLAG_VARINPUT) {
		if (arg_size < e->pt_in_size) {
			result = REP_PROTOCOL_FAIL_BAD_REQUEST;
			goto end;
		}
	} else if (arg_size != e->pt_in_size) {
		result = REP_PROTOCOL_FAIL_BAD_REQUEST;
		goto end;
	}

	if (retsize != e->pt_out_max) {
		retsize = e->pt_out_max;
		retval = alloca(retsize);
	}

	if (e->pt_flags & PROTO_FLAG_RETFD)
		e->pt_fd_handler(cp, argp, arg_size, retval, &retsize,
		    e->pt_arg, &retfd);
	else
		e->pt_handler(cp, argp, arg_size, retval, &retsize, e->pt_arg);

end:
	ti->ti_active_client = NULL;
	client_release(cp);

end_unheld:
	if (rlp != NULL) {
		/* LINTED alignment */
		rlp->rl_response = *(uint32_t *)retval;
		end_log();
		rlp = NULL;
	}
	ti->ti_client_request = NULL;
	thread_newstate(ti, TI_DOOR_RETURN);

	if (retval == (char *)&result) {
		assert(result != INVALID_RESULT && retsize == sizeof (result));
	} else {
		/* LINTED alignment */
		result = *(uint32_t *)retval;
	}
	if (retfd != -1) {
		desc.d_attributes = DOOR_DESCRIPTOR | DOOR_RELEASE;
		desc.d_data.d_desc.d_descriptor = retfd;
		(void) door_return(retval, retsize, &desc, 1);
	} else {
		(void) door_return(retval, retsize, NULL, 0);
	}
bad_end:
	if (rlp != NULL) {
		rlp->rl_response = -1;
		end_log();
		rlp = NULL;
	}
	(void) door_return(NULL, 0, NULL, 0);
}

int
create_client(pid_t pid, uint32_t debugflags, int privileged, int *out_fd)
{
	int fd;

	repcache_client_t *cp;

	struct door_info info;

	int door_flags = DOOR_UNREF | DOOR_REFUSE_DESC;
#ifdef DOOR_NO_CANCEL
	door_flags |= DOOR_NO_CANCEL;
#endif

	cp = client_alloc();
	if (cp == NULL)
		return (REPOSITORY_DOOR_FAIL_NO_RESOURCES);

	(void) pthread_mutex_lock(&client_lock);
	cp->rc_id = ++client_maxid;
	(void) pthread_mutex_unlock(&client_lock);

	cp->rc_all_auths = privileged;
	cp->rc_pid = pid;
	cp->rc_debug = debugflags;

	cp->rc_doorfd = door_create(client_switcher, (void *)cp->rc_id,
	    door_flags);

	if (cp->rc_doorfd < 0) {
		client_free(cp);
		return (REPOSITORY_DOOR_FAIL_NO_RESOURCES);
	}
#ifdef DOOR_PARAM_DATA_MIN
	(void) door_setparam(cp->rc_doorfd, DOOR_PARAM_DATA_MIN,
	    sizeof (enum rep_protocol_requestid));
#endif

	if ((fd = dup(cp->rc_doorfd)) < 0 ||
	    door_info(cp->rc_doorfd, &info) < 0) {
		if (fd >= 0)
			(void) close(fd);
		(void) door_revoke(cp->rc_doorfd);
		cp->rc_doorfd = -1;
		client_free(cp);
		return (REPOSITORY_DOOR_FAIL_NO_RESOURCES);
	}

	rc_pg_notify_init(&cp->rc_pg_notify);
	rc_notify_info_init(&cp->rc_notify_info);

	client_insert(cp);

	cp->rc_doorid = info.di_uniquifier;
	*out_fd = fd;

	return (REPOSITORY_DOOR_SUCCESS);
}