client.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* 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_list_pool_t *entity_pool;
static uu_list_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_list_pool_create("repcache_entitys",
sizeof (repcache_entity_t), offsetof(repcache_entity_t, re_link),
entity_compare, UU_LIST_POOL_DEBUG);
assert_nolint(offsetof(repcache_iter_t, ri_id) == 0);
iter_pool = uu_list_pool_create("repcache_iters",
sizeof (repcache_iter_t), offsetof(repcache_iter_t, ri_link),
iter_compare, UU_LIST_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_entity_list = uu_list_create(entity_pool, cp, UU_LIST_SORTED);
if (cp->rc_entity_list == NULL)
goto fail;
cp->rc_iter_list = uu_list_create(iter_pool, cp, UU_LIST_SORTED);
if (cp->rc_iter_list == 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_iter_list != NULL)
uu_list_destroy(cp->rc_iter_list);
if (cp->rc_entity_list != NULL)
uu_list_destroy(cp->rc_entity_list);
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_list_first(cp->rc_entity_list) == NULL);
assert(uu_list_first(cp->rc_iter_list) == NULL);
uu_list_destroy(cp->rc_entity_list);
uu_list_destroy(cp->rc_iter_list);
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_list_node_init(ep, &ep->re_link, entity_pool);
}
return (ep);
}
static void
entity_add(repcache_client_t *cp, repcache_entity_t *ep)
{
uu_list_index_t idx;
(void) pthread_mutex_lock(&cp->rc_lock);
assert(cp->rc_insert_thr == pthread_self());
(void) uu_list_find(cp->rc_entity_list, ep, NULL, &idx);
uu_list_insert(cp->rc_entity_list, 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_list_find(cp->rc_entity_list, &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_list_find(cp->rc_entity_list, &id1, NULL, NULL);
e2 = uu_list_find(cp->rc_entity_list, &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_list_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_list_find(cp->rc_entity_list, &id, NULL, NULL);
if (entity != NULL)
uu_list_remove(cp->rc_entity_list, 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_list_teardown(cp->rc_entity_list, &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_list_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_list_find(cp->rc_iter_list, iter, NULL, &idx);
uu_list_insert(cp->rc_iter_list, 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_list_find(cp->rc_iter_list, &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_list_find(cp->rc_iter_list, &iter_id, NULL, NULL);
ep = uu_list_find(cp->rc_entity_list, &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_list_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_list_find(cp->rc_iter_list, &id, NULL, NULL);
if (iter != NULL)
uu_list_remove(cp->rc_iter_list, 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_list_teardown(cp->rc_iter_list, &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);
}