/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2015 RackTop Systems. * Copyright 2019 Joyent, Inc. */ /* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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); (void) pthread_mutex_init(&cp->rc_annotate_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); (void) pthread_mutex_destroy(&cp->rc_annotate_lock); rc_node_ptr_free_mem(&cp->rc_notify_ptr); 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); rc_node_ptr_free_mem(&entity->re_node); 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) { add_log_ptr(get_log(), RC_PTR_TYPE_ENTITY, id, entity); 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); /* * clean up annotations */ if (cp->rc_operation != NULL) free((void *)cp->rc_operation); if (cp->rc_file != NULL) free((void *)cp->rc_file); /* * End audit session. */ #ifndef NATIVE_BUILD (void) adt_end_session(cp->rc_adt_session); #endif 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 */ 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 * _NO_RESOURCES out of memory * _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); } /* * This function captures the information that will be used for an * annotation audit event. Specifically, it captures the operation to be * performed and the name of the file that is being used. These values are * copied from the rep_protocol_annotation request at rpr to the client * structure. If both these values are null, the client is turning * annotation off. * * Fails with * _NO_RESOURCES - unable to allocate memory */ static rep_protocol_responseid_t set_annotation(repcache_client_t *cp, struct rep_protocol_annotation *rpr) { au_id_t audit_uid; const char *file = NULL; const char *old_ptrs[2]; const char *operation = NULL; rep_protocol_responseid_t rc = REP_PROTOCOL_FAIL_NO_RESOURCES; au_asid_t sessionid; (void) memset((void *)old_ptrs, 0, sizeof (old_ptrs)); /* Copy rpr_operation and rpr_file if they are not empty strings. */ if (rpr->rpr_operation[0] != 0) { /* * Make sure that client did not send us an unterminated buffer. */ rpr->rpr_operation[sizeof (rpr->rpr_operation) - 1] = 0; if ((operation = strdup(rpr->rpr_operation)) == NULL) goto out; } if (rpr->rpr_file[0] != 0) { /* * Make sure that client did not send us an unterminated buffer. */ rpr->rpr_file[sizeof (rpr->rpr_file) - 1] = 0; if ((file = strdup(rpr->rpr_file)) == NULL) goto out; } (void) pthread_mutex_lock(&cp->rc_annotate_lock); /* Save addresses of memory to free when not locked */ old_ptrs[0] = cp->rc_operation; old_ptrs[1] = cp->rc_file; /* Save pointers to annotation strings. */ cp->rc_operation = operation; cp->rc_file = file; /* * Set annotation flag. Annotations should be turned on if either * operation or file are not NULL. */ cp->rc_annotate = (operation != NULL) || (file != NULL); (void) pthread_mutex_unlock(&cp->rc_annotate_lock); /* * operation and file pointers are saved in cp, so don't free them * during cleanup. */ operation = NULL; file = NULL; rc = REP_PROTOCOL_SUCCESS; /* * Native builds are done to create svc.configd-native. This * program runs only on the Open Solaris build machines to create * the seed repository. Until the SMF auditing code is distributed * to the Open Solaris build machines, adt_get_unique_id() in the * following code is not a global function in libbsm. Hence the * following conditional compilation. */ #ifndef NATIVE_BUILD /* * Set the appropriate audit session id. */ if (cp->rc_annotate) { /* * We're starting a group of annotated audit events, so * create and set an audit session ID for this annotation. */ adt_get_auid(cp->rc_adt_session, &audit_uid); sessionid = adt_get_unique_id(audit_uid); } else { /* * Annotation is done so restore our client audit session * id. */ sessionid = cp->rc_adt_sessionid; } adt_set_asid(cp->rc_adt_session, sessionid); #endif /* NATIVE_BUILD */ out: if (operation != NULL) free((void *)operation); if (file != NULL) free((void *)file); free((void *)old_ptrs[0]); free((void *)old_ptrs[1]); return (rc); } /* * Determine if an annotation event needs to be generated. If it does * provide the operation and file name that should be used in the event. * * Can return: * 0 No annotation event needed or buffers are not large * enough. Either way an event should not be * generated. * 1 Generate annotation event. */ int client_annotation_needed(char *operation, size_t oper_sz, char *file, size_t file_sz) { thread_info_t *ti = thread_self(); repcache_client_t *cp = ti->ti_active_client; int rc = 0; (void) pthread_mutex_lock(&cp->rc_annotate_lock); if (cp->rc_annotate) { rc = 1; if (cp->rc_operation == NULL) { if (oper_sz > 0) operation[0] = 0; } else { if (strlcpy(operation, cp->rc_operation, oper_sz) >= oper_sz) { /* Buffer overflow, so do not generate event */ rc = 0; } } if (cp->rc_file == NULL) { if (file_sz > 0) file[0] = 0; } else if (rc == 1) { if (strlcpy(file, cp->rc_file, file_sz) >= file_sz) { /* Buffer overflow, so do not generate event */ rc = 0; } } } (void) pthread_mutex_unlock(&cp->rc_annotate_lock); return (rc); } void client_annotation_finished() { thread_info_t *ti = thread_self(); repcache_client_t *cp = ti->ti_active_client; (void) pthread_mutex_lock(&cp->rc_annotate_lock); cp->rc_annotate = 0; (void) pthread_mutex_unlock(&cp->rc_annotate_lock); } #ifndef NATIVE_BUILD static void start_audit_session(repcache_client_t *cp) { ucred_t *cred = NULL; adt_session_data_t *session; /* * A NULL session pointer value can legally be used in all * subsequent calls to adt_* functions. */ cp->rc_adt_session = NULL; if (!adt_audit_state(AUC_AUDITING)) return; if (door_ucred(&cred) != 0) { switch (errno) { case EAGAIN: case ENOMEM: syslog(LOG_ERR, gettext("start_audit_session(): cannot " "get ucred. %m\n")); return; case EINVAL: /* * Door client went away. This is a normal, * although infrequent event, so there is no need * to create a syslog message. */ return; case EFAULT: default: bad_error("door_ucred", errno); } } if (adt_start_session(&session, NULL, 0) != 0) { syslog(LOG_ERR, gettext("start_audit_session(): could not " "start audit session.\n")); ucred_free(cred); return; } if (adt_set_from_ucred(session, cred, ADT_NEW) != 0) { syslog(LOG_ERR, gettext("start_audit_session(): cannot set " "audit session data from ucred\n")); /* Something went wrong. End the session. */ (void) adt_end_session(session); ucred_free(cred); return; } /* All went well. Save the session data and session ID */ cp->rc_adt_session = session; adt_get_asid(session, &cp->rc_adt_sessionid); ucred_free(cred); } #endif /* * Handle switch client request * * This routine can return: * * _PERMISSION_DENIED not enough privileges to do request. * _UNKNOWN file operation error (details written to * the console). * _SUCCESS switch operation is completed. * _BACKEND_ACCESS backend access fails. * _NO_RESOURCES out of memory. * _BACKEND_READONLY backend is not writable. */ static rep_protocol_responseid_t repository_switch(repcache_client_t *cp, struct rep_protocol_switch_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); } (void) pthread_mutex_lock(&cp->rc_lock); if (rpr->rpr_changeid != cp->rc_changeid) { if ((result = backend_switch(rpr->rpr_flag)) == 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(REP_PROTOCOL_SET_AUDIT_ANNOTATION, set_annotation, struct rep_protocol_annotation), PROTO(REP_PROTOCOL_SWITCH, repository_switch, struct rep_protocol_switch_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; #ifndef NATIVE_BUILD start_audit_session(cp); #endif 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); }