/* * 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 (c) 2013, Joyent, Inc. All rights reserved. * Copyright (c) 2016 by Delphix. All rights reserved. */ /* * rc_node.c - In-memory SCF object management * * This layer manages the in-memory cache (the Repository Cache) of SCF * data. Read requests are usually satisfied from here, but may require * load calls to the "object" layer. Modify requests always write-through * to the object layer. * * SCF data comprises scopes, services, instances, snapshots, snaplevels, * property groups, properties, and property values. All but the last are * known here as "entities" and are represented by rc_node_t data * structures. (Property values are kept in the rn_values member of the * respective property, not as separate objects.) All entities besides * the "localhost" scope have some entity as a parent, and therefore form * a tree. * * The entity tree is rooted at rc_scope, which rc_node_init() initializes to * the "localhost" scope. The tree is filled in from the database on-demand * by rc_node_fill_children(). * * rc_node_t's are also placed in the cache_hash[] hash table, for rapid * lookup. * * Multiple threads may service client requests, so access to each * rc_node_t is synchronized by its rn_lock member. Some fields are * protected by bits in the rn_flags field instead, to support operations * which need to drop rn_lock, for example to respect locking order. Such * flags should be manipulated with the rc_node_{hold,rele}_flag() * functions. * * We track references to nodes to tell when they can be free()d. rn_refs * should be incremented with rc_node_hold() on the creation of client * references (rc_node_ptr_t's and rc_iter_t's). rn_erefs ("ephemeral * references") should be incremented when a pointer is read into a local * variable of a thread, with rc_node_hold_ephemeral_locked(). This * hasn't been fully implemented, however, so rc_node_rele() tolerates * rn_erefs being 0. Some code which predates rn_erefs counts ephemeral * references in rn_refs. Other references are tracked by the * rn_other_refs field and the RC_NODE_DEAD, RC_NODE_IN_PARENT, * RC_NODE_OLD, and RC_NODE_ON_FORMER flags. * * Locking rules: To dereference an rc_node_t * (usually to lock it), you must * have a hold (rc_node_hold()) on it or otherwise be sure that it hasn't been * rc_node_destroy()ed (hold a lock on its parent or child, hold a flag, * etc.). Once you have locked an rc_node_t you must check its rn_flags for * RC_NODE_DEAD before you can use it. This is usually done with the * rc_node_{wait,hold}_flag() functions (often via the rc_node_check_*() * functions & RC_NODE_*() macros), which fail if the object has died. * * When a transactional node (property group or snapshot) is updated, * a new node takes the place of the old node in the global hash and the * old node is hung off of the rn_former list of the new node. At the * same time, all of its children have their rn_parent_ref pointer set, * and any holds they have are reflected in the old node's rn_other_refs * count. This is automatically kept up to date until the final reference * to the subgraph is dropped, at which point the node is unrefed and * destroyed, along with all of its children. * * Because name service lookups may take a long time and, more importantly * may trigger additional accesses to the repository, perm_granted() must be * called without holding any locks. * * An ITER_START for a non-ENTITY_VALUE induces an rc_node_fill_children() * call via rc_node_setup_iter() to populate the rn_children uu_list of the * rc_node_t * in question and a call to uu_list_walk_start() on that list. For * ITER_READ, rc_iter_next() uses uu_list_walk_next() to find the next * apropriate child. * * An ITER_START for an ENTITY_VALUE makes sure the node has its values * filled, and sets up the iterator. An ITER_READ_VALUE just copies out * the proper values and updates the offset information. * * To allow aliases, snapshots are implemented with a level of indirection. * A snapshot rc_node_t has a snapid which refers to an rc_snapshot_t in * snapshot.c which contains the authoritative snaplevel information. The * snapid is "assigned" by rc_attach_snapshot(). * * We provide the client layer with rc_node_ptr_t's to reference objects. * Objects referred to by them are automatically held & released by * rc_node_assign() & rc_node_clear(). The RC_NODE_PTR_*() macros are used at * client.c entry points to read the pointers. They fetch the pointer to the * object, return (from the function) if it is dead, and lock, hold, or hold * a flag of the object. */ /* * Permission checking is authorization-based: some operations may only * proceed if the user has been assigned at least one of a set of * authorization strings. The set of enabling authorizations depends on the * operation and the target object. The set of authorizations assigned to * a user is determined by an algorithm defined in libsecdb. * * The fastest way to decide whether the two sets intersect is by entering the * strings into a hash table and detecting collisions, which takes linear time * in the total size of the sets. Except for the authorization patterns which * may be assigned to users, which without advanced pattern-matching * algorithms will take O(n) in the number of enabling authorizations, per * pattern. * * We can achieve some practical speed-ups by noting that if we enter all of * the authorizations from one of the sets into the hash table we can merely * check the elements of the second set for existence without adding them. * This reduces memory requirements and hash table clutter. The enabling set * is well suited for this because it is internal to configd (for now, at * least). Combine this with short-circuiting and we can even minimize the * number of queries to the security databases (user_attr & prof_attr). * * To force this usage onto clients we provide functions for adding * authorizations to the enabling set of a permission context structure * (perm_add_*()) and one to decide whether the the user associated with the * current door call client possesses any of them (perm_granted()). * * At some point, a generic version of this should move to libsecdb. * * While entering the enabling strings into the hash table, we keep track * of which is the most specific for use in generating auditing events. * See the "Collecting the Authorization String" section of the "SMF Audit * Events" block comment below. */ /* * Composition is the combination of sets of properties. The sets are ordered * and properties in higher sets obscure properties of the same name in lower * sets. Here we present a composed view of an instance's properties as the * union of its properties and its service's properties. Similarly the * properties of snaplevels are combined to form a composed view of the * properties of a snapshot (which should match the composed view of the * properties of the instance when the snapshot was taken). * * In terms of the client interface, the client may request that a property * group iterator for an instance or snapshot be composed. Property groups * traversed by such an iterator may not have the target entity as a parent. * Similarly, the properties traversed by a property iterator for those * property groups may not have the property groups iterated as parents. * * Implementation requires that iterators for instances and snapshots be * composition-savvy, and that we have a "composed property group" entity * which represents the composition of a number of property groups. Iteration * over "composed property groups" yields properties which may have different * parents, but for all other operations a composed property group behaves * like the top-most property group it represents. * * The implementation is based on the rn_cchain[] array of rc_node_t pointers * in rc_node_t. For instances, the pointers point to the instance and its * parent service. For snapshots they point to the child snaplevels, and for * composed property groups they point to property groups. A composed * iterator carries an index into rn_cchain[]. Thus most of the magic ends up * int the rc_iter_*() code. */ /* * SMF Audit Events: * ================ * * To maintain security, SMF generates audit events whenever * privileged operations are attempted. See the System Administration * Guide:Security Services answerbook for a discussion of the Solaris * audit system. * * The SMF audit event codes are defined in adt_event.h by symbols * starting with ADT_smf_ and are described in audit_event.txt. The * audit record structures are defined in the SMF section of adt.xml. * adt.xml is used to automatically generate adt_event.h which * contains the definitions that we code to in this file. For the * most part the audit events map closely to actions that you would * perform with svcadm or svccfg, but there are some special cases * which we'll discuss later. * * The software associated with SMF audit events falls into three * categories: * - collecting information to be written to the audit * records * - using the adt_* functions in * usr/src/lib/libbsm/common/adt.c to generate the audit * records. * - handling special cases * * Collecting Information: * ---------------------- * * Most all of the audit events require the FMRI of the affected * object and the authorization string that was used. The one * exception is ADT_smf_annotation which we'll talk about later. * * Collecting the FMRI: * * The rc_node structure has a member called rn_fmri which points to * its FMRI. This is initialized by a call to rc_node_build_fmri() * when the node's parent is established. The reason for doing it * at this time is that a node's FMRI is basically the concatenation * of the parent's FMRI and the node's name with the appropriate * decoration. rc_node_build_fmri() does this concatenation and * decorating. It is called from rc_node_link_child() and * rc_node_relink_child() where a node is linked to its parent. * * rc_node_get_fmri_or_fragment() is called to retrieve a node's FMRI * when it is needed. It returns rn_fmri if it is set. If the node * is at the top level, however, rn_fmri won't be set because it was * never linked to a parent. In this case, * rc_node_get_fmri_or_fragment() constructs an FMRI fragment based on * its node type and its name, rn_name. * * Collecting the Authorization String: * * Naturally, the authorization string is captured during the * authorization checking process. Acceptable authorization strings * are added to a permcheck_t hash table as noted in the section on * permission checking above. Once all entries have been added to the * hash table, perm_granted() is called. If the client is authorized, * perm_granted() returns with pc_auth_string of the permcheck_t * structure pointing to the authorization string. * * This works fine if the client is authorized, but what happens if * the client is not authorized? We need to report the required * authorization string. This is the authorization that would have * been used if permission had been granted. perm_granted() will * find no match, so it needs to decide which string in the hash * table to use as the required authorization string. It needs to do * this, because configd is still going to generate an event. A * design decision was made to use the most specific authorization * in the hash table. The pc_auth_type enum designates the * specificity of an authorization string. For example, an * authorization string that is declared in an instance PG is more * specific than one that is declared in a service PG. * * The pc_add() function keeps track of the most specific * authorization in the hash table. It does this using the * pc_specific and pc_specific_type members of the permcheck * structure. pc_add() updates these members whenever a more * specific authorization string is added to the hash table. Thus, if * an authorization match is not found, perm_granted() will return * with pc_auth_string in the permcheck_t pointing to the string that * is referenced by pc_specific. * * Generating the Audit Events: * =========================== * * As the functions in this file process requests for clients of * configd, they gather the information that is required for an audit * event. Eventually, the request processing gets to the point where * the authorization is rejected or to the point where the requested * action was attempted. At these two points smf_audit_event() is * called. * * smf_audit_event() takes 4 parameters: * - the event ID which is one of the ADT_smf_* symbols from * adt_event.h. * - status to pass to adt_put_event() * - return value to pass to adt_put_event() * - the event data (see audit_event_data structure) * * All interactions with the auditing software require an audit * session. We use one audit session per configd client. We keep * track of the audit session in the repcache_client structure. * smf_audit_event() calls get_audit_session() to get the session * pointer. * * smf_audit_event() then calls adt_alloc_event() to allocate an * adt_event_data union which is defined in adt_event.h, copies the * data into the appropriate members of the union and calls * adt_put_event() to generate the event. * * Special Cases: * ============= * * There are three major types of special cases: * * - gathering event information for each action in a * transaction * - Higher level events represented by special property * group/property name combinations. Many of these are * restarter actions. * - ADT_smf_annotation event * * Processing Transaction Actions: * ------------------------------ * * A transaction can contain multiple actions to modify, create or * delete one or more properties. We need to capture information so * that we can generate an event for each property action. The * transaction information is stored in a tx_commmit_data_t, and * object.c provides accessor functions to retrieve data from this * structure. rc_tx_commit() obtains a tx_commit_data_t by calling * tx_commit_data_new() and passes this to object_tx_commit() to * commit the transaction. Then we call generate_property_events() to * generate an audit event for each property action. * * Special Properties: * ------------------ * * There are combinations of property group/property name that are special. * They are special because they have specific meaning to startd. startd * interprets them in a service-independent fashion. * restarter_actions/refresh and general/enabled are two examples of these. * A special event is generated for these properties in addition to the * regular property event described in the previous section. The special * properties are declared as an array of audit_special_prop_item * structures at special_props_list in rc_node.c. * * In the previous section, we mentioned the * generate_property_event() function that generates an event for * every property action. Before generating the event, * generate_property_event() calls special_property_event(). * special_property_event() checks to see if the action involves a * special property. If it does, it generates a special audit * event. * * ADT_smf_annotation event: * ------------------------ * * This is a special event unlike any other. It allows the svccfg * program to store an annotation in the event log before a series * of transactions is processed. It is used with the import and * apply svccfg commands. svccfg uses the rep_protocol_annotation * message to pass the operation (import or apply) and the file name * to configd. The set_annotation() function in client.c stores * these away in the a repcache_client structure. The address of * this structure is saved in the thread_info structure. * * Before it generates any events, smf_audit_event() calls * smf_annotation_event(). smf_annotation_event() calls * client_annotation_needed() which is defined in client.c. If an * annotation is needed client_annotation_needed() returns the * operation and filename strings that were saved from the * rep_protocol_annotation message. smf_annotation_event() then * generates the ADT_smf_annotation event. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "configd.h" #define AUTH_PREFIX "solaris.smf." #define AUTH_MANAGE AUTH_PREFIX "manage" #define AUTH_MODIFY AUTH_PREFIX "modify" #define AUTH_MODIFY_PREFIX AUTH_MODIFY "." #define AUTH_PG_ACTIONS SCF_PG_RESTARTER_ACTIONS #define AUTH_PG_ACTIONS_TYPE SCF_PG_RESTARTER_ACTIONS_TYPE #define AUTH_PG_GENERAL SCF_PG_GENERAL #define AUTH_PG_GENERAL_TYPE SCF_PG_GENERAL_TYPE #define AUTH_PG_GENERAL_OVR SCF_PG_GENERAL_OVR #define AUTH_PG_GENERAL_OVR_TYPE SCF_PG_GENERAL_OVR_TYPE #define AUTH_PROP_ACTION "action_authorization" #define AUTH_PROP_ENABLED "enabled" #define AUTH_PROP_MODIFY "modify_authorization" #define AUTH_PROP_VALUE "value_authorization" #define AUTH_PROP_READ "read_authorization" #define MAX_VALID_CHILDREN 3 typedef struct rc_type_info { uint32_t rt_type; /* matches array index */ uint32_t rt_num_ids; uint32_t rt_name_flags; uint32_t rt_valid_children[MAX_VALID_CHILDREN]; } rc_type_info_t; #define RT_NO_NAME -1U static rc_type_info_t rc_types[] = { {REP_PROTOCOL_ENTITY_NONE, 0, RT_NO_NAME}, {REP_PROTOCOL_ENTITY_SCOPE, 0, 0, {REP_PROTOCOL_ENTITY_SERVICE, REP_PROTOCOL_ENTITY_SCOPE}}, {REP_PROTOCOL_ENTITY_SERVICE, 0, UU_NAME_DOMAIN | UU_NAME_PATH, {REP_PROTOCOL_ENTITY_INSTANCE, REP_PROTOCOL_ENTITY_PROPERTYGRP}}, {REP_PROTOCOL_ENTITY_INSTANCE, 1, UU_NAME_DOMAIN, {REP_PROTOCOL_ENTITY_SNAPSHOT, REP_PROTOCOL_ENTITY_PROPERTYGRP}}, {REP_PROTOCOL_ENTITY_SNAPSHOT, 2, UU_NAME_DOMAIN, {REP_PROTOCOL_ENTITY_SNAPLEVEL, REP_PROTOCOL_ENTITY_PROPERTYGRP}}, {REP_PROTOCOL_ENTITY_SNAPLEVEL, 4, RT_NO_NAME, {REP_PROTOCOL_ENTITY_PROPERTYGRP}}, {REP_PROTOCOL_ENTITY_PROPERTYGRP, 5, UU_NAME_DOMAIN, {REP_PROTOCOL_ENTITY_PROPERTY}}, {REP_PROTOCOL_ENTITY_CPROPERTYGRP, 0, UU_NAME_DOMAIN, {REP_PROTOCOL_ENTITY_PROPERTY}}, {REP_PROTOCOL_ENTITY_PROPERTY, 7, UU_NAME_DOMAIN}, {-1UL} }; #define NUM_TYPES ((sizeof (rc_types) / sizeof (*rc_types))) /* Element of a permcheck_t hash table. */ struct pc_elt { struct pc_elt *pce_next; char pce_auth[1]; }; /* * If an authorization fails, we must decide which of the elements in the * permcheck hash table to use in the audit event. That is to say of all * the strings in the hash table, we must choose one and use it in the audit * event. It is desirable to use the most specific string in the audit * event. * * The pc_auth_type specifies the types (sources) of authorization * strings. The enum is ordered in increasing specificity. */ typedef enum pc_auth_type { PC_AUTH_NONE = 0, /* no auth string available. */ PC_AUTH_SMF, /* strings coded into SMF. */ PC_AUTH_SVC, /* strings specified in PG of a service. */ PC_AUTH_INST /* strings specified in PG of an instance. */ } pc_auth_type_t; /* * The following enum is used to represent the results of the checks to see * if the client has the appropriate permissions to perform an action. */ typedef enum perm_status { PERM_DENIED = 0, /* Permission denied. */ PERM_GRANTED, /* Client has authorizations. */ PERM_GONE, /* Door client went away. */ PERM_FAIL /* Generic failure. e.g. resources */ } perm_status_t; /* An authorization set hash table. */ typedef struct { struct pc_elt **pc_buckets; uint_t pc_bnum; /* number of buckets */ uint_t pc_enum; /* number of elements */ struct pc_elt *pc_specific; /* most specific element */ pc_auth_type_t pc_specific_type; /* type of pc_specific */ char *pc_auth_string; /* authorization string */ /* for audit events */ } permcheck_t; /* * Structure for holding audit event data. Not all events use all members * of the structure. */ typedef struct audit_event_data { char *ed_auth; /* authorization string. */ char *ed_fmri; /* affected FMRI. */ char *ed_snapname; /* name of snapshot. */ char *ed_old_fmri; /* old fmri in attach case. */ char *ed_old_name; /* old snapshot in attach case. */ char *ed_type; /* prop. group or prop. type. */ char *ed_prop_value; /* property value. */ } audit_event_data_t; /* * Pointer to function to do special processing to get audit event ID. * Audit event IDs are defined in /usr/include/bsm/adt_event.h. Function * returns 0 if ID successfully retrieved. Otherwise it returns -1. */ typedef int (*spc_getid_fn_t)(tx_commit_data_t *, size_t, const char *, au_event_t *); static int general_enable_id(tx_commit_data_t *, size_t, const char *, au_event_t *); static uu_list_pool_t *rc_children_pool; static uu_list_pool_t *rc_pg_notify_pool; static uu_list_pool_t *rc_notify_pool; static uu_list_pool_t *rc_notify_info_pool; static rc_node_t *rc_scope; static pthread_mutex_t rc_pg_notify_lock = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t rc_pg_notify_cv = PTHREAD_COND_INITIALIZER; static uint_t rc_notify_in_use; /* blocks removals */ /* * Some combinations of property group/property name require a special * audit event to be generated when there is a change. * audit_special_prop_item_t is used to specify these special cases. The * special_props_list array defines a list of these special properties. */ typedef struct audit_special_prop_item { const char *api_pg_name; /* property group name. */ const char *api_prop_name; /* property name. */ au_event_t api_event_id; /* event id or 0. */ spc_getid_fn_t api_event_func; /* function to get event id. */ } audit_special_prop_item_t; /* * Native builds are done using the build machine's standard include * files. These files may not yet have the definitions for the ADT_smf_* * symbols. Thus, we do not compile this table when doing native builds. */ #ifndef NATIVE_BUILD /* * The following special_props_list array specifies property group/property * name combinations that have specific meaning to startd. A special event * is generated for these combinations in addition to the regular property * event. * * At run time this array gets sorted. See the call to qsort(3C) in * rc_node_init(). The array is sorted, so that bsearch(3C) can be used * to do lookups. */ static audit_special_prop_item_t special_props_list[] = { {SCF_PG_RESTARTER_ACTIONS, SCF_PROPERTY_DEGRADED, ADT_smf_degrade, NULL}, {SCF_PG_RESTARTER_ACTIONS, SCF_PROPERTY_DEGRADE_IMMEDIATE, ADT_smf_immediate_degrade, NULL}, {SCF_PG_RESTARTER_ACTIONS, SCF_PROPERTY_MAINT_OFF, ADT_smf_clear, NULL}, {SCF_PG_RESTARTER_ACTIONS, SCF_PROPERTY_MAINT_ON, ADT_smf_maintenance, NULL}, {SCF_PG_RESTARTER_ACTIONS, SCF_PROPERTY_MAINT_ON_IMMEDIATE, ADT_smf_immediate_maintenance, NULL}, {SCF_PG_RESTARTER_ACTIONS, SCF_PROPERTY_MAINT_ON_IMMTEMP, ADT_smf_immtmp_maintenance, NULL}, {SCF_PG_RESTARTER_ACTIONS, SCF_PROPERTY_MAINT_ON_TEMPORARY, ADT_smf_tmp_maintenance, NULL}, {SCF_PG_RESTARTER_ACTIONS, SCF_PROPERTY_REFRESH, ADT_smf_refresh, NULL}, {SCF_PG_RESTARTER_ACTIONS, SCF_PROPERTY_RESTART, ADT_smf_restart, NULL}, {SCF_PG_RESTARTER_ACTIONS, SCF_PROPERTY_RESTORE, ADT_smf_clear, NULL}, {SCF_PG_OPTIONS, SCF_PROPERTY_MILESTONE, ADT_smf_milestone, NULL}, {SCF_PG_OPTIONS_OVR, SCF_PROPERTY_MILESTONE, ADT_smf_milestone, NULL}, {SCF_PG_GENERAL, SCF_PROPERTY_ENABLED, 0, general_enable_id}, {SCF_PG_GENERAL_OVR, SCF_PROPERTY_ENABLED, 0, general_enable_id} }; #define SPECIAL_PROP_COUNT (sizeof (special_props_list) /\ sizeof (audit_special_prop_item_t)) #endif /* NATIVE_BUILD */ /* * We support an arbitrary number of clients interested in events for certain * types of changes. Each client is represented by an rc_notify_info_t, and * all clients are chained onto the rc_notify_info_list. * * The rc_notify_list is the global notification list. Each entry is of * type rc_notify_t, which is embedded in one of three other structures: * * rc_node_t property group update notification * rc_notify_delete_t object deletion notification * rc_notify_info_t notification clients * * Which type of object is determined by which pointer in the rc_notify_t is * non-NULL. * * New notifications and clients are added to the end of the list. * Notifications no-one is interested in are never added to the list. * * Clients use their position in the list to track which notifications they * have not yet reported. As they process notifications, they move forward * in the list past them. There is always a client at the beginning of the * list -- as it moves past notifications, it removes them from the list and * cleans them up. * * The rc_pg_notify_lock protects all notification state. The rc_pg_notify_cv * is used for global signalling, and each client has a cv which it waits for * events of interest on. * * rc_notify_in_use is used to protect rc_notify_list from deletions when * the rc_pg_notify_lock is dropped. Specifically, rc_notify_info_wait() * must drop the lock to call rc_node_assign(), and then it reacquires the * lock. Deletions from rc_notify_list during this period are not * allowed. Insertions do not matter, because they are always done at the * end of the list. */ static uu_list_t *rc_notify_info_list; static uu_list_t *rc_notify_list; #define HASH_SIZE 512 #define HASH_MASK (HASH_SIZE - 1) #pragma align 64(cache_hash) static cache_bucket_t cache_hash[HASH_SIZE]; #define CACHE_BUCKET(h) (&cache_hash[(h) & HASH_MASK]) static void rc_node_no_client_refs(rc_node_t *np); static uint32_t rc_node_hash(rc_node_lookup_t *lp) { uint32_t type = lp->rl_type; uint32_t backend = lp->rl_backend; uint32_t mainid = lp->rl_main_id; uint32_t *ids = lp->rl_ids; rc_type_info_t *tp = &rc_types[type]; uint32_t num_ids; uint32_t left; uint32_t hash; assert(backend == BACKEND_TYPE_NORMAL || backend == BACKEND_TYPE_NONPERSIST); assert(type > 0 && type < NUM_TYPES); num_ids = tp->rt_num_ids; left = MAX_IDS - num_ids; assert(num_ids <= MAX_IDS); hash = type * 7 + mainid * 5 + backend; while (num_ids-- > 0) hash = hash * 11 + *ids++ * 7; /* * the rest should be zeroed */ while (left-- > 0) assert(*ids++ == 0); return (hash); } static int rc_node_match(rc_node_t *np, rc_node_lookup_t *l) { rc_node_lookup_t *r = &np->rn_id; rc_type_info_t *tp; uint32_t type; uint32_t num_ids; if (r->rl_main_id != l->rl_main_id) return (0); type = r->rl_type; if (type != l->rl_type) return (0); assert(type > 0 && type < NUM_TYPES); tp = &rc_types[r->rl_type]; num_ids = tp->rt_num_ids; assert(num_ids <= MAX_IDS); while (num_ids-- > 0) if (r->rl_ids[num_ids] != l->rl_ids[num_ids]) return (0); return (1); } /* * Register an ephemeral reference to np. This should be done while both * the persistent reference from which the np pointer was read is locked * and np itself is locked. This guarantees that another thread which * thinks it has the last reference will yield without destroying the * node. */ static void rc_node_hold_ephemeral_locked(rc_node_t *np) { assert(MUTEX_HELD(&np->rn_lock)); ++np->rn_erefs; } /* * the "other" references on a node are maintained in an atomically * updated refcount, rn_other_refs. This can be bumped from arbitrary * context, and tracks references to a possibly out-of-date node's children. * * To prevent the node from disappearing between the final drop of * rn_other_refs and the unref handling, rn_other_refs_held is bumped on * 0->1 transitions and decremented (with the node lock held) on 1->0 * transitions. */ static void rc_node_hold_other(rc_node_t *np) { if (atomic_add_32_nv(&np->rn_other_refs, 1) == 1) { atomic_add_32(&np->rn_other_refs_held, 1); assert(np->rn_other_refs_held > 0); } assert(np->rn_other_refs > 0); } /* * No node locks may be held */ static void rc_node_rele_other(rc_node_t *np) { assert(np->rn_other_refs > 0); if (atomic_add_32_nv(&np->rn_other_refs, -1) == 0) { (void) pthread_mutex_lock(&np->rn_lock); assert(np->rn_other_refs_held > 0); if (atomic_add_32_nv(&np->rn_other_refs_held, -1) == 0 && np->rn_refs == 0 && (np->rn_flags & RC_NODE_OLD)) { /* * This was the last client reference. Destroy * any other references and free() the node. */ rc_node_no_client_refs(np); } else { (void) pthread_mutex_unlock(&np->rn_lock); } } } static void rc_node_hold_locked(rc_node_t *np) { assert(MUTEX_HELD(&np->rn_lock)); if (np->rn_refs == 0 && (np->rn_flags & RC_NODE_PARENT_REF)) rc_node_hold_other(np->rn_parent_ref); np->rn_refs++; assert(np->rn_refs > 0); } static void rc_node_hold(rc_node_t *np) { (void) pthread_mutex_lock(&np->rn_lock); rc_node_hold_locked(np); (void) pthread_mutex_unlock(&np->rn_lock); } static void rc_node_rele_locked(rc_node_t *np) { int unref = 0; rc_node_t *par_ref = NULL; assert(MUTEX_HELD(&np->rn_lock)); assert(np->rn_refs > 0); if (--np->rn_refs == 0) { if (np->rn_flags & RC_NODE_PARENT_REF) par_ref = np->rn_parent_ref; /* * Composed property groups are only as good as their * references. */ if (np->rn_id.rl_type == REP_PROTOCOL_ENTITY_CPROPERTYGRP) np->rn_flags |= RC_NODE_DEAD; if ((np->rn_flags & (RC_NODE_DEAD|RC_NODE_OLD)) && np->rn_other_refs == 0 && np->rn_other_refs_held == 0) unref = 1; } if (unref) { /* * This was the last client reference. Destroy any other * references and free() the node. */ rc_node_no_client_refs(np); } else { /* * rn_erefs can be 0 if we acquired the reference in * a path which hasn't been updated to increment rn_erefs. * When all paths which end here are updated, we should * assert rn_erefs > 0 and always decrement it. */ if (np->rn_erefs > 0) --np->rn_erefs; (void) pthread_mutex_unlock(&np->rn_lock); } if (par_ref != NULL) rc_node_rele_other(par_ref); } void rc_node_rele(rc_node_t *np) { (void) pthread_mutex_lock(&np->rn_lock); rc_node_rele_locked(np); } static cache_bucket_t * cache_hold(uint32_t h) { cache_bucket_t *bp = CACHE_BUCKET(h); (void) pthread_mutex_lock(&bp->cb_lock); return (bp); } static void cache_release(cache_bucket_t *bp) { (void) pthread_mutex_unlock(&bp->cb_lock); } static rc_node_t * cache_lookup_unlocked(cache_bucket_t *bp, rc_node_lookup_t *lp) { uint32_t h = rc_node_hash(lp); rc_node_t *np; assert(MUTEX_HELD(&bp->cb_lock)); assert(bp == CACHE_BUCKET(h)); for (np = bp->cb_head; np != NULL; np = np->rn_hash_next) { if (np->rn_hash == h && rc_node_match(np, lp)) { rc_node_hold(np); return (np); } } return (NULL); } static rc_node_t * cache_lookup(rc_node_lookup_t *lp) { uint32_t h; cache_bucket_t *bp; rc_node_t *np; h = rc_node_hash(lp); bp = cache_hold(h); np = cache_lookup_unlocked(bp, lp); cache_release(bp); return (np); } static void cache_insert_unlocked(cache_bucket_t *bp, rc_node_t *np) { assert(MUTEX_HELD(&bp->cb_lock)); assert(np->rn_hash == rc_node_hash(&np->rn_id)); assert(bp == CACHE_BUCKET(np->rn_hash)); assert(np->rn_hash_next == NULL); np->rn_hash_next = bp->cb_head; bp->cb_head = np; } static void cache_remove_unlocked(cache_bucket_t *bp, rc_node_t *np) { rc_node_t **npp; assert(MUTEX_HELD(&bp->cb_lock)); assert(np->rn_hash == rc_node_hash(&np->rn_id)); assert(bp == CACHE_BUCKET(np->rn_hash)); for (npp = &bp->cb_head; *npp != NULL; npp = &(*npp)->rn_hash_next) if (*npp == np) break; assert(*npp == np); *npp = np->rn_hash_next; np->rn_hash_next = NULL; } /* * verify that the 'parent' type can have a child typed 'child' * Fails with * _INVALID_TYPE - argument is invalid * _TYPE_MISMATCH - parent type cannot have children of type child */ static int rc_check_parent_child(uint32_t parent, uint32_t child) { int idx; uint32_t type; if (parent == 0 || parent >= NUM_TYPES || child == 0 || child >= NUM_TYPES) return (REP_PROTOCOL_FAIL_INVALID_TYPE); /* invalid types */ for (idx = 0; idx < MAX_VALID_CHILDREN; idx++) { type = rc_types[parent].rt_valid_children[idx]; if (type == child) return (REP_PROTOCOL_SUCCESS); } return (REP_PROTOCOL_FAIL_TYPE_MISMATCH); } /* * Fails with * _INVALID_TYPE - type is invalid * _BAD_REQUEST - name is an invalid name for a node of type type */ int rc_check_type_name(uint32_t type, const char *name) { if (type == 0 || type >= NUM_TYPES) return (REP_PROTOCOL_FAIL_INVALID_TYPE); /* invalid types */ if (uu_check_name(name, rc_types[type].rt_name_flags) == -1) return (REP_PROTOCOL_FAIL_BAD_REQUEST); return (REP_PROTOCOL_SUCCESS); } static int rc_check_pgtype_name(const char *name) { if (uu_check_name(name, UU_NAME_DOMAIN) == -1) return (REP_PROTOCOL_FAIL_BAD_REQUEST); return (REP_PROTOCOL_SUCCESS); } /* * rc_node_free_fmri should be called whenever a node loses its parent. * The reason is that the node's fmri string is built up by concatenating * its name to the parent's fmri. Thus, when the node no longer has a * parent, its fmri is no longer valid. */ static void rc_node_free_fmri(rc_node_t *np) { if (np->rn_fmri != NULL) { free((void *)np->rn_fmri); np->rn_fmri = NULL; } } /* * Concatenate the appropriate separator and the FMRI element to the base * FMRI string at fmri. * * Fails with * _TRUNCATED Not enough room in buffer at fmri. */ static int rc_concat_fmri_element( char *fmri, /* base fmri */ size_t bufsize, /* size of buf at fmri */ size_t *sz_out, /* receives result size. */ const char *element, /* element name to concat */ rep_protocol_entity_t type) /* type of element */ { size_t actual; const char *name = element; int rc; const char *separator; if (bufsize > 0) *sz_out = strlen(fmri); else *sz_out = 0; switch (type) { case REP_PROTOCOL_ENTITY_SCOPE: if (strcmp(element, SCF_FMRI_LOCAL_SCOPE) == 0) { /* * No need to display scope information if we are * in the local scope. */ separator = SCF_FMRI_SVC_PREFIX; name = NULL; } else { /* * Need to display scope information, because it is * not the local scope. */ separator = SCF_FMRI_SVC_PREFIX SCF_FMRI_SCOPE_PREFIX; } break; case REP_PROTOCOL_ENTITY_SERVICE: separator = SCF_FMRI_SERVICE_PREFIX; break; case REP_PROTOCOL_ENTITY_INSTANCE: separator = SCF_FMRI_INSTANCE_PREFIX; break; case REP_PROTOCOL_ENTITY_PROPERTYGRP: case REP_PROTOCOL_ENTITY_CPROPERTYGRP: separator = SCF_FMRI_PROPERTYGRP_PREFIX; break; case REP_PROTOCOL_ENTITY_PROPERTY: separator = SCF_FMRI_PROPERTY_PREFIX; break; case REP_PROTOCOL_ENTITY_VALUE: /* * A value does not have a separate FMRI from its property, * so there is nothing to concat. */ return (REP_PROTOCOL_SUCCESS); case REP_PROTOCOL_ENTITY_SNAPSHOT: case REP_PROTOCOL_ENTITY_SNAPLEVEL: /* Snapshots do not have FMRIs, so there is nothing to do. */ return (REP_PROTOCOL_SUCCESS); default: (void) fprintf(stderr, "%s:%d: Unknown protocol type %d.\n", __FILE__, __LINE__, type); abort(); /* Missing a case in switch if we get here. */ } /* Concatenate separator and element to the fmri buffer. */ actual = strlcat(fmri, separator, bufsize); if (name != NULL) { if (actual < bufsize) { actual = strlcat(fmri, name, bufsize); } else { actual += strlen(name); } } if (actual < bufsize) { rc = REP_PROTOCOL_SUCCESS; } else { rc = REP_PROTOCOL_FAIL_TRUNCATED; } *sz_out = actual; return (rc); } /* * Get the FMRI for the node at np. The fmri will be placed in buf. On * success sz_out will be set to the size of the fmri in buf. If * REP_PROTOCOL_FAIL_TRUNCATED is returned, sz_out will be set to the size * of the buffer that would be required to avoid truncation. * * Fails with * _TRUNCATED not enough room in buf for the FMRI. */ static int rc_node_get_fmri_or_fragment(rc_node_t *np, char *buf, size_t bufsize, size_t *sz_out) { size_t fmri_len = 0; int r; if (bufsize > 0) *buf = 0; *sz_out = 0; if (np->rn_fmri == NULL) { /* * A NULL rn_fmri implies that this is a top level scope. * Child nodes will always have an rn_fmri established * because both rc_node_link_child() and * rc_node_relink_child() call rc_node_build_fmri(). In * this case, we'll just return our name preceded by the * appropriate FMRI decorations. */ assert(np->rn_parent == NULL); r = rc_concat_fmri_element(buf, bufsize, &fmri_len, np->rn_name, np->rn_id.rl_type); if (r != REP_PROTOCOL_SUCCESS) return (r); } else { /* We have an fmri, so return it. */ fmri_len = strlcpy(buf, np->rn_fmri, bufsize); } *sz_out = fmri_len; if (fmri_len >= bufsize) return (REP_PROTOCOL_FAIL_TRUNCATED); return (REP_PROTOCOL_SUCCESS); } /* * Build an FMRI string for this node and save it in rn_fmri. * * The basic strategy here is to get the fmri of our parent and then * concatenate the appropriate separator followed by our name. If our name * is null, the resulting fmri will just be a copy of the parent fmri. * rc_node_build_fmri() should be called with the RC_NODE_USING_PARENT flag * set. Also the rn_lock for this node should be held. * * Fails with * _NO_RESOURCES Could not allocate memory. */ static int rc_node_build_fmri(rc_node_t *np) { size_t actual; char fmri[REP_PROTOCOL_FMRI_LEN]; int rc; size_t sz = REP_PROTOCOL_FMRI_LEN; assert(MUTEX_HELD(&np->rn_lock)); assert(np->rn_flags & RC_NODE_USING_PARENT); rc_node_free_fmri(np); rc = rc_node_get_fmri_or_fragment(np->rn_parent, fmri, sz, &actual); assert(rc == REP_PROTOCOL_SUCCESS); if (np->rn_name != NULL) { rc = rc_concat_fmri_element(fmri, sz, &actual, np->rn_name, np->rn_id.rl_type); assert(rc == REP_PROTOCOL_SUCCESS); np->rn_fmri = strdup(fmri); } else { np->rn_fmri = strdup(fmri); } if (np->rn_fmri == NULL) { rc = REP_PROTOCOL_FAIL_NO_RESOURCES; } else { rc = REP_PROTOCOL_SUCCESS; } return (rc); } /* * Get the FMRI of the node at np placing the result in fmri. Then * concatenate the additional element to fmri. The type variable indicates * the type of element, so that the appropriate separator can be * generated. size is the number of bytes in the buffer at fmri, and * sz_out receives the size of the generated string. If the result is * truncated, sz_out will receive the size of the buffer that would be * required to avoid truncation. * * Fails with * _TRUNCATED Not enough room in buffer at fmri. */ static int rc_get_fmri_and_concat(rc_node_t *np, char *fmri, size_t size, size_t *sz_out, const char *element, rep_protocol_entity_t type) { int rc; if ((rc = rc_node_get_fmri_or_fragment(np, fmri, size, sz_out)) != REP_PROTOCOL_SUCCESS) { return (rc); } if ((rc = rc_concat_fmri_element(fmri, size, sz_out, element, type)) != REP_PROTOCOL_SUCCESS) { return (rc); } return (REP_PROTOCOL_SUCCESS); } static int rc_notify_info_interested(rc_notify_info_t *rnip, rc_notify_t *np) { rc_node_t *nnp = np->rcn_node; int i; assert(MUTEX_HELD(&rc_pg_notify_lock)); if (np->rcn_delete != NULL) { assert(np->rcn_info == NULL && np->rcn_node == NULL); return (1); /* everyone likes deletes */ } if (np->rcn_node == NULL) { assert(np->rcn_info != NULL || np->rcn_delete != NULL); return (0); } assert(np->rcn_info == NULL); for (i = 0; i < RC_NOTIFY_MAX_NAMES; i++) { if (rnip->rni_namelist[i] != NULL) { if (strcmp(nnp->rn_name, rnip->rni_namelist[i]) == 0) return (1); } if (rnip->rni_typelist[i] != NULL) { if (strcmp(nnp->rn_type, rnip->rni_typelist[i]) == 0) return (1); } } return (0); } static void rc_notify_insert_node(rc_node_t *nnp) { rc_notify_t *np = &nnp->rn_notify; rc_notify_info_t *nip; int found = 0; assert(np->rcn_info == NULL); if (nnp->rn_id.rl_type != REP_PROTOCOL_ENTITY_PROPERTYGRP) return; (void) pthread_mutex_lock(&rc_pg_notify_lock); np->rcn_node = nnp; for (nip = uu_list_first(rc_notify_info_list); nip != NULL; nip = uu_list_next(rc_notify_info_list, nip)) { if (rc_notify_info_interested(nip, np)) { (void) pthread_cond_broadcast(&nip->rni_cv); found++; } } if (found) (void) uu_list_insert_before(rc_notify_list, NULL, np); else np->rcn_node = NULL; (void) pthread_mutex_unlock(&rc_pg_notify_lock); } static void rc_notify_deletion(rc_notify_delete_t *ndp, const char *service, const char *instance, const char *pg) { rc_notify_info_t *nip; uu_list_node_init(&ndp->rnd_notify, &ndp->rnd_notify.rcn_list_node, rc_notify_pool); ndp->rnd_notify.rcn_delete = ndp; (void) snprintf(ndp->rnd_fmri, sizeof (ndp->rnd_fmri), "svc:/%s%s%s%s%s", service, (instance != NULL)? ":" : "", (instance != NULL)? instance : "", (pg != NULL)? "/:properties/" : "", (pg != NULL)? pg : ""); /* * add to notification list, notify watchers */ (void) pthread_mutex_lock(&rc_pg_notify_lock); for (nip = uu_list_first(rc_notify_info_list); nip != NULL; nip = uu_list_next(rc_notify_info_list, nip)) (void) pthread_cond_broadcast(&nip->rni_cv); (void) uu_list_insert_before(rc_notify_list, NULL, ndp); (void) pthread_mutex_unlock(&rc_pg_notify_lock); } static void rc_notify_remove_node(rc_node_t *nnp) { rc_notify_t *np = &nnp->rn_notify; assert(np->rcn_info == NULL); assert(!MUTEX_HELD(&nnp->rn_lock)); (void) pthread_mutex_lock(&rc_pg_notify_lock); while (np->rcn_node != NULL) { if (rc_notify_in_use) { (void) pthread_cond_wait(&rc_pg_notify_cv, &rc_pg_notify_lock); continue; } (void) uu_list_remove(rc_notify_list, np); np->rcn_node = NULL; break; } (void) pthread_mutex_unlock(&rc_pg_notify_lock); } static void rc_notify_remove_locked(rc_notify_t *np) { assert(MUTEX_HELD(&rc_pg_notify_lock)); assert(rc_notify_in_use == 0); (void) uu_list_remove(rc_notify_list, np); if (np->rcn_node) { np->rcn_node = NULL; } else if (np->rcn_delete) { uu_free(np->rcn_delete); } else { assert(0); /* CAN'T HAPPEN */ } } /* * Permission checking functions. See comment atop this file. */ #ifndef NATIVE_BUILD static permcheck_t * pc_create() { permcheck_t *p; p = uu_zalloc(sizeof (*p)); if (p == NULL) return (NULL); p->pc_bnum = 8; /* Normal case will only have 2 elts. */ p->pc_buckets = uu_zalloc(sizeof (*p->pc_buckets) * p->pc_bnum); if (p->pc_buckets == NULL) { uu_free(p); return (NULL); } p->pc_enum = 0; return (p); } static void pc_free(permcheck_t *pcp) { uint_t i; struct pc_elt *ep, *next; for (i = 0; i < pcp->pc_bnum; ++i) { for (ep = pcp->pc_buckets[i]; ep != NULL; ep = next) { next = ep->pce_next; free(ep); } } free(pcp->pc_buckets); free(pcp); } static uint32_t pc_hash(const char *auth) { uint32_t h = 0, g; const char *p; /* * Generic hash function from uts/common/os/modhash.c. */ for (p = auth; *p != '\0'; ++p) { h = (h << 4) + *p; g = (h & 0xf0000000); if (g != 0) { h ^= (g >> 24); h ^= g; } } return (h); } static perm_status_t pc_exists(permcheck_t *pcp, const char *auth) { uint32_t h; struct pc_elt *ep; h = pc_hash(auth); for (ep = pcp->pc_buckets[h & (pcp->pc_bnum - 1)]; ep != NULL; ep = ep->pce_next) { if (strcmp(auth, ep->pce_auth) == 0) { pcp->pc_auth_string = ep->pce_auth; return (PERM_GRANTED); } } return (PERM_DENIED); } static perm_status_t pc_match(permcheck_t *pcp, const char *pattern) { uint_t i; struct pc_elt *ep; for (i = 0; i < pcp->pc_bnum; ++i) { for (ep = pcp->pc_buckets[i]; ep != NULL; ep = ep->pce_next) { if (_auth_match(pattern, ep->pce_auth)) { pcp->pc_auth_string = ep->pce_auth; return (PERM_GRANTED); } } } return (PERM_DENIED); } static int pc_grow(permcheck_t *pcp) { uint_t new_bnum, i, j; struct pc_elt **new_buckets; struct pc_elt *ep, *next; new_bnum = pcp->pc_bnum * 2; if (new_bnum < pcp->pc_bnum) /* Homey don't play that. */ return (-1); new_buckets = uu_zalloc(sizeof (*new_buckets) * new_bnum); if (new_buckets == NULL) return (-1); for (i = 0; i < pcp->pc_bnum; ++i) { for (ep = pcp->pc_buckets[i]; ep != NULL; ep = next) { next = ep->pce_next; j = pc_hash(ep->pce_auth) & (new_bnum - 1); ep->pce_next = new_buckets[j]; new_buckets[j] = ep; } } uu_free(pcp->pc_buckets); pcp->pc_buckets = new_buckets; pcp->pc_bnum = new_bnum; return (0); } static int pc_add(permcheck_t *pcp, const char *auth, pc_auth_type_t auth_type) { struct pc_elt *ep; uint_t i; ep = uu_zalloc(offsetof(struct pc_elt, pce_auth) + strlen(auth) + 1); if (ep == NULL) return (-1); /* Grow if pc_enum / pc_bnum > 3/4. */ if (pcp->pc_enum * 4 > 3 * pcp->pc_bnum) /* Failure is not a stopper; we'll try again next time. */ (void) pc_grow(pcp); (void) strcpy(ep->pce_auth, auth); i = pc_hash(auth) & (pcp->pc_bnum - 1); ep->pce_next = pcp->pc_buckets[i]; pcp->pc_buckets[i] = ep; if (auth_type > pcp->pc_specific_type) { pcp->pc_specific_type = auth_type; pcp->pc_specific = ep; } ++pcp->pc_enum; return (0); } /* * For the type of a property group, return the authorization which may be * used to modify it. */ static const char * perm_auth_for_pgtype(const char *pgtype) { if (strcmp(pgtype, SCF_GROUP_METHOD) == 0) return (AUTH_MODIFY_PREFIX "method"); else if (strcmp(pgtype, SCF_GROUP_DEPENDENCY) == 0) return (AUTH_MODIFY_PREFIX "dependency"); else if (strcmp(pgtype, SCF_GROUP_APPLICATION) == 0) return (AUTH_MODIFY_PREFIX "application"); else if (strcmp(pgtype, SCF_GROUP_FRAMEWORK) == 0) return (AUTH_MODIFY_PREFIX "framework"); else return (NULL); } /* * Fails with * _NO_RESOURCES - out of memory */ static int perm_add_enabling_type(permcheck_t *pcp, const char *auth, pc_auth_type_t auth_type) { return (pc_add(pcp, auth, auth_type) == 0 ? REP_PROTOCOL_SUCCESS : REP_PROTOCOL_FAIL_NO_RESOURCES); } /* * Fails with * _NO_RESOURCES - out of memory */ static int perm_add_enabling(permcheck_t *pcp, const char *auth) { return (perm_add_enabling_type(pcp, auth, PC_AUTH_SMF)); } /* Note that perm_add_enabling_values() is defined below. */ /* * perm_granted() returns PERM_GRANTED if the current door caller has one of * the enabling authorizations in pcp, PERM_DENIED if it doesn't, PERM_GONE if * the door client went away and PERM_FAIL if an error (usually lack of * memory) occurs. auth_cb() checks each and every authorizations as * enumerated by _enum_auths. When we find a result other than PERM_DENIED, * we short-cut the enumeration and return non-zero. */ static int auth_cb(const char *auth, void *ctxt, void *vres) { permcheck_t *pcp = ctxt; int *pret = vres; if (strchr(auth, KV_WILDCHAR) == NULL) *pret = pc_exists(pcp, auth); else *pret = pc_match(pcp, auth); if (*pret != PERM_DENIED) return (1); /* * If we failed, choose the most specific auth string for use in * the audit event. */ assert(pcp->pc_specific != NULL); pcp->pc_auth_string = pcp->pc_specific->pce_auth; return (0); /* Tells that we need to continue */ } static perm_status_t perm_granted(permcheck_t *pcp) { ucred_t *uc; perm_status_t ret = PERM_DENIED; uid_t uid; struct passwd pw; char pwbuf[1024]; /* XXX should be NSS_BUFLEN_PASSWD */ /* Get the uid */ if ((uc = get_ucred()) == NULL) { if (errno == EINVAL) { /* * Client is no longer waiting for our response (e.g., * it received a signal & resumed with EINTR). * Punting with door_return() would be nice but we * need to release all of the locks & references we * hold. And we must report failure to the client * layer to keep it from ignoring retries as * already-done (idempotency & all that). None of the * error codes fit very well, so we might as well * force the return of _PERMISSION_DENIED since we * couldn't determine the user. */ return (PERM_GONE); } assert(0); abort(); } uid = ucred_geteuid(uc); assert(uid != (uid_t)-1); if (getpwuid_r(uid, &pw, pwbuf, sizeof (pwbuf)) == NULL) { return (PERM_FAIL); } /* * Enumerate all the auths defined for the user and return the * result in ret. */ if (_enum_auths(pw.pw_name, auth_cb, pcp, &ret) < 0) return (PERM_FAIL); return (ret); } static int map_granted_status(perm_status_t status, permcheck_t *pcp, char **match_auth) { int rc; *match_auth = NULL; switch (status) { case PERM_DENIED: *match_auth = strdup(pcp->pc_auth_string); if (*match_auth == NULL) rc = REP_PROTOCOL_FAIL_NO_RESOURCES; else rc = REP_PROTOCOL_FAIL_PERMISSION_DENIED; break; case PERM_GRANTED: *match_auth = strdup(pcp->pc_auth_string); if (*match_auth == NULL) rc = REP_PROTOCOL_FAIL_NO_RESOURCES; else rc = REP_PROTOCOL_SUCCESS; break; case PERM_GONE: rc = REP_PROTOCOL_FAIL_PERMISSION_DENIED; break; case PERM_FAIL: rc = REP_PROTOCOL_FAIL_NO_RESOURCES; break; } return (rc); } #endif /* NATIVE_BUILD */ /* * flags in RC_NODE_WAITING_FLAGS are broadcast when unset, and are used to * serialize certain actions, and to wait for certain operations to complete * * The waiting flags are: * RC_NODE_CHILDREN_CHANGING * The child list is being built or changed (due to creation * or deletion). All iterators pause. * * RC_NODE_USING_PARENT * Someone is actively using the parent pointer, so we can't * be removed from the parent list. * * RC_NODE_CREATING_CHILD * A child is being created -- locks out other creations, to * prevent insert-insert races. * * RC_NODE_IN_TX * This object is running a transaction. * * RC_NODE_DYING * This node might be dying. Always set as a set, using * RC_NODE_DYING_FLAGS (which is everything but * RC_NODE_USING_PARENT) */ static int rc_node_hold_flag(rc_node_t *np, uint32_t flag) { assert(MUTEX_HELD(&np->rn_lock)); assert((flag & ~RC_NODE_WAITING_FLAGS) == 0); while (!(np->rn_flags & RC_NODE_DEAD) && (np->rn_flags & flag)) { (void) pthread_cond_wait(&np->rn_cv, &np->rn_lock); } if (np->rn_flags & RC_NODE_DEAD) return (0); np->rn_flags |= flag; return (1); } static void rc_node_rele_flag(rc_node_t *np, uint32_t flag) { assert((flag & ~RC_NODE_WAITING_FLAGS) == 0); assert(MUTEX_HELD(&np->rn_lock)); assert((np->rn_flags & flag) == flag); np->rn_flags &= ~flag; (void) pthread_cond_broadcast(&np->rn_cv); } /* * wait until a particular flag has cleared. Fails if the object dies. */ static int rc_node_wait_flag(rc_node_t *np, uint32_t flag) { assert(MUTEX_HELD(&np->rn_lock)); while (!(np->rn_flags & RC_NODE_DEAD) && (np->rn_flags & flag)) (void) pthread_cond_wait(&np->rn_cv, &np->rn_lock); return (!(np->rn_flags & RC_NODE_DEAD)); } /* * On entry, np's lock must be held, and this thread must be holding * RC_NODE_USING_PARENT. On return, both of them are released. * * If the return value is NULL, np either does not have a parent, or * the parent has been marked DEAD. * * If the return value is non-NULL, it is the parent of np, and both * its lock and the requested flags are held. */ static rc_node_t * rc_node_hold_parent_flag(rc_node_t *np, uint32_t flag) { rc_node_t *pp; assert(MUTEX_HELD(&np->rn_lock)); assert(np->rn_flags & RC_NODE_USING_PARENT); if ((pp = np->rn_parent) == NULL) { rc_node_rele_flag(np, RC_NODE_USING_PARENT); (void) pthread_mutex_unlock(&np->rn_lock); return (NULL); } (void) pthread_mutex_unlock(&np->rn_lock); (void) pthread_mutex_lock(&pp->rn_lock); (void) pthread_mutex_lock(&np->rn_lock); rc_node_rele_flag(np, RC_NODE_USING_PARENT); (void) pthread_mutex_unlock(&np->rn_lock); if (!rc_node_hold_flag(pp, flag)) { (void) pthread_mutex_unlock(&pp->rn_lock); return (NULL); } return (pp); } rc_node_t * rc_node_alloc(void) { rc_node_t *np = uu_zalloc(sizeof (*np)); if (np == NULL) return (NULL); (void) pthread_mutex_init(&np->rn_lock, NULL); (void) pthread_cond_init(&np->rn_cv, NULL); np->rn_children = uu_list_create(rc_children_pool, np, 0); np->rn_pg_notify_list = uu_list_create(rc_pg_notify_pool, np, 0); uu_list_node_init(np, &np->rn_sibling_node, rc_children_pool); uu_list_node_init(&np->rn_notify, &np->rn_notify.rcn_list_node, rc_notify_pool); return (np); } void rc_node_destroy(rc_node_t *np) { int i; if (np->rn_flags & RC_NODE_UNREFED) return; /* being handled elsewhere */ assert(np->rn_refs == 0 && np->rn_other_refs == 0); assert(np->rn_former == NULL); if (np->rn_id.rl_type == REP_PROTOCOL_ENTITY_CPROPERTYGRP) { /* Release the holds from rc_iter_next(). */ for (i = 0; i < COMPOSITION_DEPTH; ++i) { /* rn_cchain[i] may be NULL for empty snapshots. */ if (np->rn_cchain[i] != NULL) rc_node_rele(np->rn_cchain[i]); } } if (np->rn_name != NULL) free((void *)np->rn_name); np->rn_name = NULL; if (np->rn_type != NULL) free((void *)np->rn_type); np->rn_type = NULL; if (np->rn_values != NULL) object_free_values(np->rn_values, np->rn_valtype, np->rn_values_count, np->rn_values_size); np->rn_values = NULL; rc_node_free_fmri(np); if (np->rn_snaplevel != NULL) rc_snaplevel_rele(np->rn_snaplevel); np->rn_snaplevel = NULL; uu_list_node_fini(np, &np->rn_sibling_node, rc_children_pool); uu_list_node_fini(&np->rn_notify, &np->rn_notify.rcn_list_node, rc_notify_pool); assert(uu_list_first(np->rn_children) == NULL); uu_list_destroy(np->rn_children); uu_list_destroy(np->rn_pg_notify_list); (void) pthread_mutex_destroy(&np->rn_lock); (void) pthread_cond_destroy(&np->rn_cv); uu_free(np); } /* * Link in a child node. * * Because of the lock ordering, cp has to already be in the hash table with * its lock dropped before we get it. To prevent anyone from noticing that * it is parentless, the creation code sets the RC_NODE_USING_PARENT. Once * we've linked it in, we release the flag. */ static void rc_node_link_child(rc_node_t *np, rc_node_t *cp) { assert(!MUTEX_HELD(&np->rn_lock)); assert(!MUTEX_HELD(&cp->rn_lock)); (void) pthread_mutex_lock(&np->rn_lock); (void) pthread_mutex_lock(&cp->rn_lock); assert(!(cp->rn_flags & RC_NODE_IN_PARENT) && (cp->rn_flags & RC_NODE_USING_PARENT)); assert(rc_check_parent_child(np->rn_id.rl_type, cp->rn_id.rl_type) == REP_PROTOCOL_SUCCESS); cp->rn_parent = np; cp->rn_flags |= RC_NODE_IN_PARENT; (void) uu_list_insert_before(np->rn_children, NULL, cp); (void) rc_node_build_fmri(cp); (void) pthread_mutex_unlock(&np->rn_lock); rc_node_rele_flag(cp, RC_NODE_USING_PARENT); (void) pthread_mutex_unlock(&cp->rn_lock); } /* * Sets the rn_parent_ref field of all the children of np to pp -- always * initially invoked as rc_node_setup_parent_ref(np, np), we then recurse. * * This is used when we mark a node RC_NODE_OLD, so that when the object and * its children are no longer referenced, they will all be deleted as a unit. */ static void rc_node_setup_parent_ref(rc_node_t *np, rc_node_t *pp) { rc_node_t *cp; assert(MUTEX_HELD(&np->rn_lock)); for (cp = uu_list_first(np->rn_children); cp != NULL; cp = uu_list_next(np->rn_children, cp)) { (void) pthread_mutex_lock(&cp->rn_lock); if (cp->rn_flags & RC_NODE_PARENT_REF) { assert(cp->rn_parent_ref == pp); } else { assert(cp->rn_parent_ref == NULL); cp->rn_flags |= RC_NODE_PARENT_REF; cp->rn_parent_ref = pp; if (cp->rn_refs != 0) rc_node_hold_other(pp); } rc_node_setup_parent_ref(cp, pp); /* recurse */ (void) pthread_mutex_unlock(&cp->rn_lock); } } /* * Atomically replace 'np' with 'newp', with a parent of 'pp'. * * Requirements: * *no* node locks may be held. * pp must be held with RC_NODE_CHILDREN_CHANGING * newp and np must be held with RC_NODE_IN_TX * np must be marked RC_NODE_IN_PARENT, newp must not be * np must be marked RC_NODE_OLD * * Afterwards: * pp's RC_NODE_CHILDREN_CHANGING is dropped * newp and np's RC_NODE_IN_TX is dropped * newp->rn_former = np; * newp is RC_NODE_IN_PARENT, np is not. * interested notify subscribers have been notified of newp's new status. */ static void rc_node_relink_child(rc_node_t *pp, rc_node_t *np, rc_node_t *newp) { cache_bucket_t *bp; /* * First, swap np and nnp in the cache. newp's RC_NODE_IN_TX flag * keeps rc_node_update() from seeing it until we are done. */ bp = cache_hold(newp->rn_hash); cache_remove_unlocked(bp, np); cache_insert_unlocked(bp, newp); cache_release(bp); /* * replace np with newp in pp's list, and attach it to newp's rn_former * link. */ (void) pthread_mutex_lock(&pp->rn_lock); assert(pp->rn_flags & RC_NODE_CHILDREN_CHANGING); (void) pthread_mutex_lock(&newp->rn_lock); assert(!(newp->rn_flags & RC_NODE_IN_PARENT)); assert(newp->rn_flags & RC_NODE_IN_TX); (void) pthread_mutex_lock(&np->rn_lock); assert(np->rn_flags & RC_NODE_IN_PARENT); assert(np->rn_flags & RC_NODE_OLD); assert(np->rn_flags & RC_NODE_IN_TX); newp->rn_parent = pp; newp->rn_flags |= RC_NODE_IN_PARENT; /* * Note that we carefully add newp before removing np -- this * keeps iterators on the list from missing us. */ (void) uu_list_insert_after(pp->rn_children, np, newp); (void) rc_node_build_fmri(newp); (void) uu_list_remove(pp->rn_children, np); /* * re-set np */ newp->rn_former = np; np->rn_parent = NULL; np->rn_flags &= ~RC_NODE_IN_PARENT; np->rn_flags |= RC_NODE_ON_FORMER; rc_notify_insert_node(newp); rc_node_rele_flag(pp, RC_NODE_CHILDREN_CHANGING); (void) pthread_mutex_unlock(&pp->rn_lock); rc_node_rele_flag(newp, RC_NODE_USING_PARENT | RC_NODE_IN_TX); (void) pthread_mutex_unlock(&newp->rn_lock); rc_node_setup_parent_ref(np, np); rc_node_rele_flag(np, RC_NODE_IN_TX); (void) pthread_mutex_unlock(&np->rn_lock); } /* * makes sure a node with lookup 'nip', name 'name', and parent 'pp' exists. * 'cp' is used (and returned) if the node does not yet exist. If it does * exist, 'cp' is freed, and the existent node is returned instead. */ rc_node_t * rc_node_setup(rc_node_t *cp, rc_node_lookup_t *nip, const char *name, rc_node_t *pp) { rc_node_t *np; cache_bucket_t *bp; uint32_t h = rc_node_hash(nip); assert(cp->rn_refs == 0); bp = cache_hold(h); if ((np = cache_lookup_unlocked(bp, nip)) != NULL) { cache_release(bp); /* * make sure it matches our expectations */ (void) pthread_mutex_lock(&np->rn_lock); if (rc_node_hold_flag(np, RC_NODE_USING_PARENT)) { assert(np->rn_parent == pp); assert(memcmp(&np->rn_id, nip, sizeof (*nip)) == 0); assert(strcmp(np->rn_name, name) == 0); assert(np->rn_type == NULL); assert(np->rn_flags & RC_NODE_IN_PARENT); rc_node_rele_flag(np, RC_NODE_USING_PARENT); } (void) pthread_mutex_unlock(&np->rn_lock); rc_node_destroy(cp); return (np); } /* * No one is there -- setup & install the new node. */ np = cp; rc_node_hold(np); np->rn_id = *nip; np->rn_hash = h; np->rn_name = strdup(name); np->rn_flags |= RC_NODE_USING_PARENT; if (np->rn_id.rl_type == REP_PROTOCOL_ENTITY_INSTANCE) { #if COMPOSITION_DEPTH == 2 np->rn_cchain[0] = np; np->rn_cchain[1] = pp; #else #error This code must be updated. #endif } cache_insert_unlocked(bp, np); cache_release(bp); /* we are now visible */ rc_node_link_child(pp, np); return (np); } /* * makes sure a snapshot with lookup 'nip', name 'name', and parent 'pp' exists. * 'cp' is used (and returned) if the node does not yet exist. If it does * exist, 'cp' is freed, and the existent node is returned instead. */ rc_node_t * rc_node_setup_snapshot(rc_node_t *cp, rc_node_lookup_t *nip, const char *name, uint32_t snap_id, rc_node_t *pp) { rc_node_t *np; cache_bucket_t *bp; uint32_t h = rc_node_hash(nip); assert(cp->rn_refs == 0); bp = cache_hold(h); if ((np = cache_lookup_unlocked(bp, nip)) != NULL) { cache_release(bp); /* * make sure it matches our expectations */ (void) pthread_mutex_lock(&np->rn_lock); if (rc_node_hold_flag(np, RC_NODE_USING_PARENT)) { assert(np->rn_parent == pp); assert(memcmp(&np->rn_id, nip, sizeof (*nip)) == 0); assert(strcmp(np->rn_name, name) == 0); assert(np->rn_type == NULL); assert(np->rn_flags & RC_NODE_IN_PARENT); rc_node_rele_flag(np, RC_NODE_USING_PARENT); } (void) pthread_mutex_unlock(&np->rn_lock); rc_node_destroy(cp); return (np); } /* * No one is there -- create a new node. */ np = cp; rc_node_hold(np); np->rn_id = *nip; np->rn_hash = h; np->rn_name = strdup(name); np->rn_snapshot_id = snap_id; np->rn_flags |= RC_NODE_USING_PARENT; cache_insert_unlocked(bp, np); cache_release(bp); /* we are now visible */ rc_node_link_child(pp, np); return (np); } /* * makes sure a snaplevel with lookup 'nip' and parent 'pp' exists. 'cp' is * used (and returned) if the node does not yet exist. If it does exist, 'cp' * is freed, and the existent node is returned instead. */ rc_node_t * rc_node_setup_snaplevel(rc_node_t *cp, rc_node_lookup_t *nip, rc_snaplevel_t *lvl, rc_node_t *pp) { rc_node_t *np; cache_bucket_t *bp; uint32_t h = rc_node_hash(nip); assert(cp->rn_refs == 0); bp = cache_hold(h); if ((np = cache_lookup_unlocked(bp, nip)) != NULL) { cache_release(bp); /* * make sure it matches our expectations */ (void) pthread_mutex_lock(&np->rn_lock); if (rc_node_hold_flag(np, RC_NODE_USING_PARENT)) { assert(np->rn_parent == pp); assert(memcmp(&np->rn_id, nip, sizeof (*nip)) == 0); assert(np->rn_name == NULL); assert(np->rn_type == NULL); assert(np->rn_flags & RC_NODE_IN_PARENT); rc_node_rele_flag(np, RC_NODE_USING_PARENT); } (void) pthread_mutex_unlock(&np->rn_lock); rc_node_destroy(cp); return (np); } /* * No one is there -- create a new node. */ np = cp; rc_node_hold(np); /* released in snapshot_fill_children() */ np->rn_id = *nip; np->rn_hash = h; rc_snaplevel_hold(lvl); np->rn_snaplevel = lvl; np->rn_flags |= RC_NODE_USING_PARENT; cache_insert_unlocked(bp, np); cache_release(bp); /* we are now visible */ /* Add this snaplevel to the snapshot's composition chain. */ assert(pp->rn_cchain[lvl->rsl_level_num - 1] == NULL); pp->rn_cchain[lvl->rsl_level_num - 1] = np; rc_node_link_child(pp, np); return (np); } /* * Returns NULL if strdup() fails. */ rc_node_t * rc_node_setup_pg(rc_node_t *cp, rc_node_lookup_t *nip, const char *name, const char *type, uint32_t flags, uint32_t gen_id, rc_node_t *pp) { rc_node_t *np; cache_bucket_t *bp; uint32_t h = rc_node_hash(nip); bp = cache_hold(h); if ((np = cache_lookup_unlocked(bp, nip)) != NULL) { cache_release(bp); /* * make sure it matches our expectations (don't check * the generation number or parent, since someone could * have gotten a transaction through while we weren't * looking) */ (void) pthread_mutex_lock(&np->rn_lock); if (rc_node_hold_flag(np, RC_NODE_USING_PARENT)) { assert(memcmp(&np->rn_id, nip, sizeof (*nip)) == 0); assert(strcmp(np->rn_name, name) == 0); assert(strcmp(np->rn_type, type) == 0); assert(np->rn_pgflags == flags); assert(np->rn_flags & RC_NODE_IN_PARENT); rc_node_rele_flag(np, RC_NODE_USING_PARENT); } (void) pthread_mutex_unlock(&np->rn_lock); rc_node_destroy(cp); return (np); } np = cp; rc_node_hold(np); /* released in fill_pg_callback() */ np->rn_id = *nip; np->rn_hash = h; np->rn_name = strdup(name); if (np->rn_name == NULL) { rc_node_rele(np); return (NULL); } np->rn_type = strdup(type); if (np->rn_type == NULL) { free((void *)np->rn_name); rc_node_rele(np); return (NULL); } np->rn_pgflags = flags; np->rn_gen_id = gen_id; np->rn_flags |= RC_NODE_USING_PARENT; cache_insert_unlocked(bp, np); cache_release(bp); /* we are now visible */ rc_node_link_child(pp, np); return (np); } #if COMPOSITION_DEPTH == 2 /* * Initialize a "composed property group" which represents the composition of * property groups pg1 & pg2. It is ephemeral: once created & returned for an * ITER_READ request, keeping it out of cache_hash and any child lists * prevents it from being looked up. Operations besides iteration are passed * through to pg1. * * pg1 & pg2 should be held before entering this function. They will be * released in rc_node_destroy(). */ static int rc_node_setup_cpg(rc_node_t *cpg, rc_node_t *pg1, rc_node_t *pg2) { if (strcmp(pg1->rn_type, pg2->rn_type) != 0) return (REP_PROTOCOL_FAIL_TYPE_MISMATCH); cpg->rn_id.rl_type = REP_PROTOCOL_ENTITY_CPROPERTYGRP; cpg->rn_name = strdup(pg1->rn_name); if (cpg->rn_name == NULL) return (REP_PROTOCOL_FAIL_NO_RESOURCES); cpg->rn_cchain[0] = pg1; cpg->rn_cchain[1] = pg2; return (REP_PROTOCOL_SUCCESS); } #else #error This code must be updated. #endif /* * Fails with _NO_RESOURCES. */ int rc_node_create_property(rc_node_t *pp, rc_node_lookup_t *nip, const char *name, rep_protocol_value_type_t type, const char *vals, size_t count, size_t size) { rc_node_t *np; cache_bucket_t *bp; uint32_t h = rc_node_hash(nip); bp = cache_hold(h); if ((np = cache_lookup_unlocked(bp, nip)) != NULL) { cache_release(bp); /* * make sure it matches our expectations */ (void) pthread_mutex_lock(&np->rn_lock); if (rc_node_hold_flag(np, RC_NODE_USING_PARENT)) { assert(np->rn_parent == pp); assert(memcmp(&np->rn_id, nip, sizeof (*nip)) == 0); assert(strcmp(np->rn_name, name) == 0); assert(np->rn_valtype == type); assert(np->rn_values_count == count); assert(np->rn_values_size == size); assert(vals == NULL || memcmp(np->rn_values, vals, size) == 0); assert(np->rn_flags & RC_NODE_IN_PARENT); rc_node_rele_flag(np, RC_NODE_USING_PARENT); } rc_node_rele_locked(np); object_free_values(vals, type, count, size); return (REP_PROTOCOL_SUCCESS); } /* * No one is there -- create a new node. */ np = rc_node_alloc(); if (np == NULL) { cache_release(bp); object_free_values(vals, type, count, size); return (REP_PROTOCOL_FAIL_NO_RESOURCES); } np->rn_id = *nip; np->rn_hash = h; np->rn_name = strdup(name); if (np->rn_name == NULL) { cache_release(bp); object_free_values(vals, type, count, size); return (REP_PROTOCOL_FAIL_NO_RESOURCES); } np->rn_valtype = type; np->rn_values = vals; np->rn_values_count = count; np->rn_values_size = size; np->rn_flags |= RC_NODE_USING_PARENT; cache_insert_unlocked(bp, np); cache_release(bp); /* we are now visible */ rc_node_link_child(pp, np); return (REP_PROTOCOL_SUCCESS); } /* * This function implements a decision table to determine the event ID for * changes to the enabled (SCF_PROPERTY_ENABLED) property. The event ID is * determined by the value of the first property in the command specified * by cmd_no and the name of the property group. Here is the decision * table: * * Property Group Name * Property ------------------------------------------ * Value SCF_PG_GENERAL SCF_PG_GENERAL_OVR * -------- -------------- ------------------ * "0" ADT_smf_disable ADT_smf_tmp_disable * "1" ADT_smf_enable ADT_smf_tmp_enable * * This function is called by special_property_event through a function * pointer in the special_props_list array. * * Since the ADT_smf_* symbols may not be defined in the build machine's * include files, this function is not compiled when doing native builds. */ #ifndef NATIVE_BUILD static int general_enable_id(tx_commit_data_t *tx_data, size_t cmd_no, const char *pg, au_event_t *event_id) { const char *value; uint32_t nvalues; int enable; /* * First, check property value. */ if (tx_cmd_nvalues(tx_data, cmd_no, &nvalues) != REP_PROTOCOL_SUCCESS) return (-1); if (nvalues == 0) return (-1); if (tx_cmd_value(tx_data, cmd_no, 0, &value) != REP_PROTOCOL_SUCCESS) return (-1); if (strcmp(value, "0") == 0) { enable = 0; } else if (strcmp(value, "1") == 0) { enable = 1; } else { return (-1); } /* * Now check property group name. */ if (strcmp(pg, SCF_PG_GENERAL) == 0) { *event_id = enable ? ADT_smf_enable : ADT_smf_disable; return (0); } else if (strcmp(pg, SCF_PG_GENERAL_OVR) == 0) { *event_id = enable ? ADT_smf_tmp_enable : ADT_smf_tmp_disable; return (0); } return (-1); } #endif /* NATIVE_BUILD */ /* * This function compares two audit_special_prop_item_t structures * represented by item1 and item2. It returns an integer greater than 0 if * item1 is greater than item2. It returns 0 if they are equal and an * integer less than 0 if item1 is less than item2. api_prop_name and * api_pg_name are the key fields for sorting. * * This function is suitable for calls to bsearch(3C) and qsort(3C). */ static int special_prop_compare(const void *item1, const void *item2) { const audit_special_prop_item_t *a = (audit_special_prop_item_t *)item1; const audit_special_prop_item_t *b = (audit_special_prop_item_t *)item2; int r; r = strcmp(a->api_prop_name, b->api_prop_name); if (r == 0) { /* * Primary keys are the same, so check the secondary key. */ r = strcmp(a->api_pg_name, b->api_pg_name); } return (r); } int rc_node_init(void) { rc_node_t *np; cache_bucket_t *bp; rc_children_pool = uu_list_pool_create("rc_children_pool", sizeof (rc_node_t), offsetof(rc_node_t, rn_sibling_node), NULL, UU_LIST_POOL_DEBUG); rc_pg_notify_pool = uu_list_pool_create("rc_pg_notify_pool", sizeof (rc_node_pg_notify_t), offsetof(rc_node_pg_notify_t, rnpn_node), NULL, UU_LIST_POOL_DEBUG); rc_notify_pool = uu_list_pool_create("rc_notify_pool", sizeof (rc_notify_t), offsetof(rc_notify_t, rcn_list_node), NULL, UU_LIST_POOL_DEBUG); rc_notify_info_pool = uu_list_pool_create("rc_notify_info_pool", sizeof (rc_notify_info_t), offsetof(rc_notify_info_t, rni_list_node), NULL, UU_LIST_POOL_DEBUG); if (rc_children_pool == NULL || rc_pg_notify_pool == NULL || rc_notify_pool == NULL || rc_notify_info_pool == NULL) uu_die("out of memory"); rc_notify_list = uu_list_create(rc_notify_pool, &rc_notify_list, 0); rc_notify_info_list = uu_list_create(rc_notify_info_pool, &rc_notify_info_list, 0); if (rc_notify_list == NULL || rc_notify_info_list == NULL) uu_die("out of memory"); /* * Sort the special_props_list array so that it can be searched * with bsearch(3C). * * The special_props_list array is not compiled into the native * build code, so there is no need to call qsort if NATIVE_BUILD is * defined. */ #ifndef NATIVE_BUILD qsort(special_props_list, SPECIAL_PROP_COUNT, sizeof (special_props_list[0]), special_prop_compare); #endif /* NATIVE_BUILD */ if ((np = rc_node_alloc()) == NULL) uu_die("out of memory"); rc_node_hold(np); np->rn_id.rl_type = REP_PROTOCOL_ENTITY_SCOPE; np->rn_id.rl_backend = BACKEND_TYPE_NORMAL; np->rn_hash = rc_node_hash(&np->rn_id); np->rn_name = "localhost"; bp = cache_hold(np->rn_hash); cache_insert_unlocked(bp, np); cache_release(bp); rc_scope = np; return (1); } /* * Fails with * _INVALID_TYPE - type is invalid * _TYPE_MISMATCH - np doesn't carry children of type type * _DELETED - np has been deleted * _NO_RESOURCES */ static int rc_node_fill_children(rc_node_t *np, uint32_t type) { int rc; assert(MUTEX_HELD(&np->rn_lock)); if ((rc = rc_check_parent_child(np->rn_id.rl_type, type)) != REP_PROTOCOL_SUCCESS) return (rc); if (!rc_node_hold_flag(np, RC_NODE_CHILDREN_CHANGING)) return (REP_PROTOCOL_FAIL_DELETED); if (np->rn_flags & RC_NODE_HAS_CHILDREN) { rc_node_rele_flag(np, RC_NODE_CHILDREN_CHANGING); return (REP_PROTOCOL_SUCCESS); } (void) pthread_mutex_unlock(&np->rn_lock); rc = object_fill_children(np); (void) pthread_mutex_lock(&np->rn_lock); if (rc == REP_PROTOCOL_SUCCESS) { np->rn_flags |= RC_NODE_HAS_CHILDREN; } rc_node_rele_flag(np, RC_NODE_CHILDREN_CHANGING); return (rc); } /* * Returns * _INVALID_TYPE - type is invalid * _TYPE_MISMATCH - np doesn't carry children of type type * _DELETED - np has been deleted * _NO_RESOURCES * _SUCCESS - if *cpp is not NULL, it is held */ static int rc_node_find_named_child(rc_node_t *np, const char *name, uint32_t type, rc_node_t **cpp) { int ret; rc_node_t *cp; assert(MUTEX_HELD(&np->rn_lock)); assert(np->rn_id.rl_type != REP_PROTOCOL_ENTITY_CPROPERTYGRP); ret = rc_node_fill_children(np, type); if (ret != REP_PROTOCOL_SUCCESS) return (ret); for (cp = uu_list_first(np->rn_children); cp != NULL; cp = uu_list_next(np->rn_children, cp)) { if (cp->rn_id.rl_type == type && strcmp(cp->rn_name, name) == 0) break; } if (cp != NULL) rc_node_hold(cp); *cpp = cp; return (REP_PROTOCOL_SUCCESS); } static int rc_node_parent(rc_node_t *, rc_node_t **); /* * Returns * _INVALID_TYPE - type is invalid * _DELETED - np or an ancestor has been deleted * _NOT_FOUND - no ancestor of specified type exists * _SUCCESS - *app is held */ static int rc_node_find_ancestor(rc_node_t *np, uint32_t type, rc_node_t **app) { int ret; rc_node_t *parent, *np_orig; if (type >= REP_PROTOCOL_ENTITY_MAX) return (REP_PROTOCOL_FAIL_INVALID_TYPE); np_orig = np; while (np->rn_id.rl_type > type) { ret = rc_node_parent(np, &parent); if (np != np_orig) rc_node_rele(np); if (ret != REP_PROTOCOL_SUCCESS) return (ret); np = parent; } if (np->rn_id.rl_type == type) { *app = parent; return (REP_PROTOCOL_SUCCESS); } return (REP_PROTOCOL_FAIL_NOT_FOUND); } #ifndef NATIVE_BUILD /* * If the propname property exists in pg, and it is of type string, add its * values as authorizations to pcp. pg must not be locked on entry, and it is * returned unlocked. Returns * _DELETED - pg was deleted * _NO_RESOURCES * _NOT_FOUND - pg has no property named propname * _SUCCESS */ static int perm_add_pg_prop_values(permcheck_t *pcp, rc_node_t *pg, const char *propname) { rc_node_t *prop; int result; uint_t count; const char *cp; assert(!MUTEX_HELD(&pg->rn_lock)); assert(pg->rn_id.rl_type == REP_PROTOCOL_ENTITY_PROPERTYGRP); (void) pthread_mutex_lock(&pg->rn_lock); result = rc_node_find_named_child(pg, propname, REP_PROTOCOL_ENTITY_PROPERTY, &prop); (void) pthread_mutex_unlock(&pg->rn_lock); if (result != REP_PROTOCOL_SUCCESS) { switch (result) { case REP_PROTOCOL_FAIL_DELETED: case REP_PROTOCOL_FAIL_NO_RESOURCES: return (result); case REP_PROTOCOL_FAIL_INVALID_TYPE: case REP_PROTOCOL_FAIL_TYPE_MISMATCH: default: bad_error("rc_node_find_named_child", result); } } if (prop == NULL) return (REP_PROTOCOL_FAIL_NOT_FOUND); /* rn_valtype is immutable, so no locking. */ if (prop->rn_valtype != REP_PROTOCOL_TYPE_STRING) { rc_node_rele(prop); return (REP_PROTOCOL_SUCCESS); } (void) pthread_mutex_lock(&prop->rn_lock); for (count = prop->rn_values_count, cp = prop->rn_values; count > 0; --count) { result = perm_add_enabling_type(pcp, cp, (pg->rn_id.rl_ids[ID_INSTANCE]) ? PC_AUTH_INST : PC_AUTH_SVC); if (result != REP_PROTOCOL_SUCCESS) break; cp = strchr(cp, '\0') + 1; } rc_node_rele_locked(prop); return (result); } /* * Assuming that ent is a service or instance node, if the pgname property * group has type pgtype, and it has a propname property with string type, add * its values as authorizations to pcp. If pgtype is NULL, it is not checked. * Returns * _SUCCESS * _DELETED - ent was deleted * _NO_RESOURCES - no resources * _NOT_FOUND - ent does not have pgname pg or propname property */ static int perm_add_ent_prop_values(permcheck_t *pcp, rc_node_t *ent, const char *pgname, const char *pgtype, const char *propname) { int r; rc_node_t *pg; assert(!MUTEX_HELD(&ent->rn_lock)); (void) pthread_mutex_lock(&ent->rn_lock); r = rc_node_find_named_child(ent, pgname, REP_PROTOCOL_ENTITY_PROPERTYGRP, &pg); (void) pthread_mutex_unlock(&ent->rn_lock); switch (r) { case REP_PROTOCOL_SUCCESS: break; case REP_PROTOCOL_FAIL_DELETED: case REP_PROTOCOL_FAIL_NO_RESOURCES: return (r); default: bad_error("rc_node_find_named_child", r); } if (pg == NULL) return (REP_PROTOCOL_FAIL_NOT_FOUND); if (pgtype == NULL || strcmp(pg->rn_type, pgtype) == 0) { r = perm_add_pg_prop_values(pcp, pg, propname); switch (r) { case REP_PROTOCOL_FAIL_DELETED: r = REP_PROTOCOL_FAIL_NOT_FOUND; break; case REP_PROTOCOL_FAIL_NO_RESOURCES: case REP_PROTOCOL_SUCCESS: case REP_PROTOCOL_FAIL_NOT_FOUND: break; default: bad_error("perm_add_pg_prop_values", r); } } rc_node_rele(pg); return (r); } /* * If pg has a property named propname, and is string typed, add its values as * authorizations to pcp. If pg has no such property, and its parent is an * instance, walk up to the service and try doing the same with the property * of the same name from the property group of the same name. Returns * _SUCCESS * _NO_RESOURCES * _DELETED - pg (or an ancestor) was deleted */ static int perm_add_enabling_values(permcheck_t *pcp, rc_node_t *pg, const char *propname) { int r; char pgname[REP_PROTOCOL_NAME_LEN + 1]; rc_node_t *svc; size_t sz; r = perm_add_pg_prop_values(pcp, pg, propname); if (r != REP_PROTOCOL_FAIL_NOT_FOUND) return (r); assert(!MUTEX_HELD(&pg->rn_lock)); if (pg->rn_id.rl_ids[ID_INSTANCE] == 0) return (REP_PROTOCOL_SUCCESS); sz = strlcpy(pgname, pg->rn_name, sizeof (pgname)); assert(sz < sizeof (pgname)); /* * If pg is a child of an instance or snapshot, we want to compose the * authorization property with the service's (if it exists). The * snapshot case applies only to read_authorization. In all other * cases, the pg's parent will be the instance. */ r = rc_node_find_ancestor(pg, REP_PROTOCOL_ENTITY_SERVICE, &svc); if (r != REP_PROTOCOL_SUCCESS) { assert(r == REP_PROTOCOL_FAIL_DELETED); return (r); } assert(svc->rn_id.rl_type == REP_PROTOCOL_ENTITY_SERVICE); r = perm_add_ent_prop_values(pcp, svc, pgname, NULL, propname); rc_node_rele(svc); if (r == REP_PROTOCOL_FAIL_NOT_FOUND) r = REP_PROTOCOL_SUCCESS; return (r); } /* * Call perm_add_enabling_values() for the "action_authorization" property of * the "general" property group of inst. Returns * _DELETED - inst (or an ancestor) was deleted * _NO_RESOURCES * _SUCCESS */ static int perm_add_inst_action_auth(permcheck_t *pcp, rc_node_t *inst) { int r; rc_node_t *svc; assert(inst->rn_id.rl_type == REP_PROTOCOL_ENTITY_INSTANCE); r = perm_add_ent_prop_values(pcp, inst, AUTH_PG_GENERAL, AUTH_PG_GENERAL_TYPE, AUTH_PROP_ACTION); if (r != REP_PROTOCOL_FAIL_NOT_FOUND) return (r); r = rc_node_parent(inst, &svc); if (r != REP_PROTOCOL_SUCCESS) { assert(r == REP_PROTOCOL_FAIL_DELETED); return (r); } r = perm_add_ent_prop_values(pcp, svc, AUTH_PG_GENERAL, AUTH_PG_GENERAL_TYPE, AUTH_PROP_ACTION); return (r == REP_PROTOCOL_FAIL_NOT_FOUND ? REP_PROTOCOL_SUCCESS : r); } #endif /* NATIVE_BUILD */ void rc_node_ptr_init(rc_node_ptr_t *out) { out->rnp_node = NULL; out->rnp_auth_string = NULL; out->rnp_authorized = RC_AUTH_UNKNOWN; out->rnp_deleted = 0; } void rc_node_ptr_free_mem(rc_node_ptr_t *npp) { if (npp->rnp_auth_string != NULL) { free((void *)npp->rnp_auth_string); npp->rnp_auth_string = NULL; } } static void rc_node_assign(rc_node_ptr_t *out, rc_node_t *val) { rc_node_t *cur = out->rnp_node; if (val != NULL) rc_node_hold(val); out->rnp_node = val; if (cur != NULL) { NODE_LOCK(cur); /* * Register the ephemeral reference created by reading * out->rnp_node into cur. Note that the persistent * reference we're destroying is locked by the client * layer. */ rc_node_hold_ephemeral_locked(cur); rc_node_rele_locked(cur); } out->rnp_authorized = RC_AUTH_UNKNOWN; rc_node_ptr_free_mem(out); out->rnp_deleted = 0; } void rc_node_clear(rc_node_ptr_t *out, int deleted) { rc_node_assign(out, NULL); out->rnp_deleted = deleted; } void rc_node_ptr_assign(rc_node_ptr_t *out, const rc_node_ptr_t *val) { rc_node_assign(out, val->rnp_node); } /* * rc_node_check()/RC_NODE_CHECK() * generic "entry" checks, run before the use of an rc_node pointer. * * Fails with * _NOT_SET * _DELETED */ static int rc_node_check_and_lock(rc_node_t *np) { int result = REP_PROTOCOL_SUCCESS; if (np == NULL) return (REP_PROTOCOL_FAIL_NOT_SET); (void) pthread_mutex_lock(&np->rn_lock); if (!rc_node_wait_flag(np, RC_NODE_DYING)) { result = REP_PROTOCOL_FAIL_DELETED; (void) pthread_mutex_unlock(&np->rn_lock); } return (result); } /* * Fails with * _NOT_SET - ptr is reset * _DELETED - node has been deleted */ static rc_node_t * rc_node_ptr_check_and_lock(rc_node_ptr_t *npp, int *res) { rc_node_t *np = npp->rnp_node; if (np == NULL) { if (npp->rnp_deleted) *res = REP_PROTOCOL_FAIL_DELETED; else *res = REP_PROTOCOL_FAIL_NOT_SET; return (NULL); } (void) pthread_mutex_lock(&np->rn_lock); if (!rc_node_wait_flag(np, RC_NODE_DYING)) { (void) pthread_mutex_unlock(&np->rn_lock); rc_node_clear(npp, 1); *res = REP_PROTOCOL_FAIL_DELETED; return (NULL); } return (np); } #define RC_NODE_CHECK_AND_LOCK(n) { \ int rc__res; \ if ((rc__res = rc_node_check_and_lock(n)) != REP_PROTOCOL_SUCCESS) \ return (rc__res); \ } #define RC_NODE_CHECK(n) { \ RC_NODE_CHECK_AND_LOCK(n); \ (void) pthread_mutex_unlock(&(n)->rn_lock); \ } #define RC_NODE_CHECK_AND_HOLD(n) { \ RC_NODE_CHECK_AND_LOCK(n); \ rc_node_hold_locked(n); \ (void) pthread_mutex_unlock(&(n)->rn_lock); \ } #define RC_NODE_PTR_GET_CHECK_AND_LOCK(np, npp) { \ int rc__res; \ if (((np) = rc_node_ptr_check_and_lock(npp, &rc__res)) == NULL) \ return (rc__res); \ } #define RC_NODE_PTR_CHECK_LOCK_OR_FREE_RETURN(np, npp, mem) { \ int rc__res; \ if (((np) = rc_node_ptr_check_and_lock(npp, &rc__res)) == \ NULL) { \ if ((mem) != NULL) \ free((mem)); \ return (rc__res); \ } \ } #define RC_NODE_PTR_GET_CHECK(np, npp) { \ RC_NODE_PTR_GET_CHECK_AND_LOCK(np, npp); \ (void) pthread_mutex_unlock(&(np)->rn_lock); \ } #define RC_NODE_PTR_GET_CHECK_AND_HOLD(np, npp) { \ RC_NODE_PTR_GET_CHECK_AND_LOCK(np, npp); \ rc_node_hold_locked(np); \ (void) pthread_mutex_unlock(&(np)->rn_lock); \ } #define HOLD_FLAG_OR_RETURN(np, flag) { \ assert(MUTEX_HELD(&(np)->rn_lock)); \ assert(!((np)->rn_flags & RC_NODE_DEAD)); \ if (!rc_node_hold_flag((np), flag)) { \ (void) pthread_mutex_unlock(&(np)->rn_lock); \ return (REP_PROTOCOL_FAIL_DELETED); \ } \ } #define HOLD_PTR_FLAG_OR_FREE_AND_RETURN(np, npp, flag, mem) { \ assert(MUTEX_HELD(&(np)->rn_lock)); \ if (!rc_node_hold_flag((np), flag)) { \ (void) pthread_mutex_unlock(&(np)->rn_lock); \ assert((np) == (npp)->rnp_node); \ rc_node_clear(npp, 1); \ if ((mem) != NULL) \ free((mem)); \ return (REP_PROTOCOL_FAIL_DELETED); \ } \ } int rc_local_scope(uint32_t type, rc_node_ptr_t *out) { if (type != REP_PROTOCOL_ENTITY_SCOPE) { rc_node_clear(out, 0); return (REP_PROTOCOL_FAIL_TYPE_MISMATCH); } /* * the main scope never gets destroyed */ rc_node_assign(out, rc_scope); return (REP_PROTOCOL_SUCCESS); } /* * Fails with * _NOT_SET - npp is not set * _DELETED - the node npp pointed at has been deleted * _TYPE_MISMATCH - type is not _SCOPE * _NOT_FOUND - scope has no parent */ static int rc_scope_parent_scope(rc_node_ptr_t *npp, uint32_t type, rc_node_ptr_t *out) { rc_node_t *np; rc_node_clear(out, 0); RC_NODE_PTR_GET_CHECK(np, npp); if (type != REP_PROTOCOL_ENTITY_SCOPE) return (REP_PROTOCOL_FAIL_TYPE_MISMATCH); return (REP_PROTOCOL_FAIL_NOT_FOUND); } static int rc_node_pg_check_read_protect(rc_node_t *); /* * Fails with * _NOT_SET * _DELETED * _NOT_APPLICABLE * _NOT_FOUND * _BAD_REQUEST * _TRUNCATED * _NO_RESOURCES */ int rc_node_name(rc_node_ptr_t *npp, char *buf, size_t sz, uint32_t answertype, size_t *sz_out) { size_t actual; rc_node_t *np; assert(sz == *sz_out); RC_NODE_PTR_GET_CHECK(np, npp); if (np->rn_id.rl_type == REP_PROTOCOL_ENTITY_CPROPERTYGRP) { np = np->rn_cchain[0]; RC_NODE_CHECK(np); } switch (answertype) { case RP_ENTITY_NAME_NAME: if (np->rn_name == NULL) return (REP_PROTOCOL_FAIL_NOT_APPLICABLE); actual = strlcpy(buf, np->rn_name, sz); break; case RP_ENTITY_NAME_PGTYPE: if (np->rn_id.rl_type != REP_PROTOCOL_ENTITY_PROPERTYGRP) return (REP_PROTOCOL_FAIL_NOT_APPLICABLE); actual = strlcpy(buf, np->rn_type, sz); break; case RP_ENTITY_NAME_PGFLAGS: if (np->rn_id.rl_type != REP_PROTOCOL_ENTITY_PROPERTYGRP) return (REP_PROTOCOL_FAIL_NOT_APPLICABLE); actual = snprintf(buf, sz, "%d", np->rn_pgflags); break; case RP_ENTITY_NAME_SNAPLEVEL_SCOPE: if (np->rn_id.rl_type != REP_PROTOCOL_ENTITY_SNAPLEVEL) return (REP_PROTOCOL_FAIL_NOT_APPLICABLE); actual = strlcpy(buf, np->rn_snaplevel->rsl_scope, sz); break; case RP_ENTITY_NAME_SNAPLEVEL_SERVICE: if (np->rn_id.rl_type != REP_PROTOCOL_ENTITY_SNAPLEVEL) return (REP_PROTOCOL_FAIL_NOT_APPLICABLE); actual = strlcpy(buf, np->rn_snaplevel->rsl_service, sz); break; case RP_ENTITY_NAME_SNAPLEVEL_INSTANCE: if (np->rn_id.rl_type != REP_PROTOCOL_ENTITY_SNAPLEVEL) return (REP_PROTOCOL_FAIL_NOT_APPLICABLE); if (np->rn_snaplevel->rsl_instance == NULL) return (REP_PROTOCOL_FAIL_NOT_FOUND); actual = strlcpy(buf, np->rn_snaplevel->rsl_instance, sz); break; case RP_ENTITY_NAME_PGREADPROT: { int ret; if (np->rn_id.rl_type != REP_PROTOCOL_ENTITY_PROPERTYGRP) return (REP_PROTOCOL_FAIL_NOT_APPLICABLE); ret = rc_node_pg_check_read_protect(np); assert(ret != REP_PROTOCOL_FAIL_TYPE_MISMATCH); switch (ret) { case REP_PROTOCOL_FAIL_PERMISSION_DENIED: actual = snprintf(buf, sz, "1"); break; case REP_PROTOCOL_SUCCESS: actual = snprintf(buf, sz, "0"); break; default: return (ret); } break; } default: return (REP_PROTOCOL_FAIL_BAD_REQUEST); } if (actual >= sz) return (REP_PROTOCOL_FAIL_TRUNCATED); *sz_out = actual; return (REP_PROTOCOL_SUCCESS); } int rc_node_get_property_type(rc_node_ptr_t *npp, rep_protocol_value_type_t *out) { rc_node_t *np; RC_NODE_PTR_GET_CHECK(np, npp); if (np->rn_id.rl_type != REP_PROTOCOL_ENTITY_PROPERTY) return (REP_PROTOCOL_FAIL_TYPE_MISMATCH); *out = np->rn_valtype; return (REP_PROTOCOL_SUCCESS); } /* * Get np's parent. If np is deleted, returns _DELETED. Otherwise puts a hold * on the parent, returns a pointer to it in *out, and returns _SUCCESS. */ static int rc_node_parent(rc_node_t *np, rc_node_t **out) { rc_node_t *pnp; rc_node_t *np_orig; if (np->rn_id.rl_type != REP_PROTOCOL_ENTITY_CPROPERTYGRP) { RC_NODE_CHECK_AND_LOCK(np); } else { np = np->rn_cchain[0]; RC_NODE_CHECK_AND_LOCK(np); } np_orig = np; rc_node_hold_locked(np); /* simplifies the remainder */ for (;;) { if (!rc_node_wait_flag(np, RC_NODE_IN_TX | RC_NODE_USING_PARENT)) { rc_node_rele_locked(np); return (REP_PROTOCOL_FAIL_DELETED); } if (!(np->rn_flags & RC_NODE_OLD)) break; rc_node_rele_locked(np); np = cache_lookup(&np_orig->rn_id); assert(np != np_orig); if (np == NULL) goto deleted; (void) pthread_mutex_lock(&np->rn_lock); } /* guaranteed to succeed without dropping the lock */ if (!rc_node_hold_flag(np, RC_NODE_USING_PARENT)) { (void) pthread_mutex_unlock(&np->rn_lock); *out = NULL; rc_node_rele(np); return (REP_PROTOCOL_FAIL_DELETED); } assert(np->rn_parent != NULL); pnp = np->rn_parent; (void) pthread_mutex_unlock(&np->rn_lock); (void) pthread_mutex_lock(&pnp->rn_lock); (void) pthread_mutex_lock(&np->rn_lock); rc_node_rele_flag(np, RC_NODE_USING_PARENT); (void) pthread_mutex_unlock(&np->rn_lock); rc_node_hold_locked(pnp); (void) pthread_mutex_unlock(&pnp->rn_lock); rc_node_rele(np); *out = pnp; return (REP_PROTOCOL_SUCCESS); deleted: rc_node_rele(np); return (REP_PROTOCOL_FAIL_DELETED); } /* * Fails with * _NOT_SET * _DELETED */ static int rc_node_ptr_parent(rc_node_ptr_t *npp, rc_node_t **out) { rc_node_t *np; RC_NODE_PTR_GET_CHECK(np, npp); return (rc_node_parent(np, out)); } /* * Fails with * _NOT_SET - npp is not set * _DELETED - the node npp pointed at has been deleted * _TYPE_MISMATCH - npp's node's parent is not of type type * * If npp points to a scope, can also fail with * _NOT_FOUND - scope has no parent */ int rc_node_get_parent(rc_node_ptr_t *npp, uint32_t type, rc_node_ptr_t *out) { rc_node_t *pnp; int rc; if (npp->rnp_node != NULL && npp->rnp_node->rn_id.rl_type == REP_PROTOCOL_ENTITY_SCOPE) return (rc_scope_parent_scope(npp, type, out)); if ((rc = rc_node_ptr_parent(npp, &pnp)) != REP_PROTOCOL_SUCCESS) { rc_node_clear(out, 0); return (rc); } if (type != pnp->rn_id.rl_type) { rc_node_rele(pnp); return (REP_PROTOCOL_FAIL_TYPE_MISMATCH); } rc_node_assign(out, pnp); rc_node_rele(pnp); return (REP_PROTOCOL_SUCCESS); } int rc_node_parent_type(rc_node_ptr_t *npp, uint32_t *type_out) { rc_node_t *pnp; int rc; if (npp->rnp_node != NULL && npp->rnp_node->rn_id.rl_type == REP_PROTOCOL_ENTITY_SCOPE) { *type_out = REP_PROTOCOL_ENTITY_SCOPE; return (REP_PROTOCOL_SUCCESS); } if ((rc = rc_node_ptr_parent(npp, &pnp)) != REP_PROTOCOL_SUCCESS) return (rc); *type_out = pnp->rn_id.rl_type; rc_node_rele(pnp); return (REP_PROTOCOL_SUCCESS); } /* * Fails with * _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 */ int rc_node_get_child(rc_node_ptr_t *npp, const char *name, uint32_t type, rc_node_ptr_t *outp) { rc_node_t *np, *cp; rc_node_t *child = NULL; int ret, idx; RC_NODE_PTR_GET_CHECK_AND_LOCK(np, npp); if ((ret = rc_check_type_name(type, name)) == REP_PROTOCOL_SUCCESS) { if (np->rn_id.rl_type != REP_PROTOCOL_ENTITY_CPROPERTYGRP) { ret = rc_node_find_named_child(np, name, type, &child); } else { (void) pthread_mutex_unlock(&np->rn_lock); ret = REP_PROTOCOL_SUCCESS; for (idx = 0; idx < COMPOSITION_DEPTH; idx++) { cp = np->rn_cchain[idx]; if (cp == NULL) break; RC_NODE_CHECK_AND_LOCK(cp); ret = rc_node_find_named_child(cp, name, type, &child); (void) pthread_mutex_unlock(&cp->rn_lock); /* * loop only if we succeeded, but no child of * the correct name was found. */ if (ret != REP_PROTOCOL_SUCCESS || child != NULL) break; } (void) pthread_mutex_lock(&np->rn_lock); } } (void) pthread_mutex_unlock(&np->rn_lock); if (ret == REP_PROTOCOL_SUCCESS) { rc_node_assign(outp, child); if (child != NULL) rc_node_rele(child); else ret = REP_PROTOCOL_FAIL_NOT_FOUND; } else { rc_node_assign(outp, NULL); } return (ret); } int rc_node_update(rc_node_ptr_t *npp) { cache_bucket_t *bp; rc_node_t *np = npp->rnp_node; rc_node_t *nnp; rc_node_t *cpg = NULL; if (np != NULL && np->rn_id.rl_type == REP_PROTOCOL_ENTITY_CPROPERTYGRP) { /* * If we're updating a composed property group, actually * update the top-level property group & return the * appropriate value. But leave *nnp pointing at us. */ cpg = np; np = np->rn_cchain[0]; } RC_NODE_CHECK(np); if (np->rn_id.rl_type != REP_PROTOCOL_ENTITY_PROPERTYGRP && np->rn_id.rl_type != REP_PROTOCOL_ENTITY_SNAPSHOT) return (REP_PROTOCOL_FAIL_BAD_REQUEST); for (;;) { bp = cache_hold(np->rn_hash); nnp = cache_lookup_unlocked(bp, &np->rn_id); if (nnp == NULL) { cache_release(bp); rc_node_clear(npp, 1); return (REP_PROTOCOL_FAIL_DELETED); } /* * grab the lock before dropping the cache bucket, so * that no one else can sneak in */ (void) pthread_mutex_lock(&nnp->rn_lock); cache_release(bp); if (!(nnp->rn_flags & RC_NODE_IN_TX) || !rc_node_wait_flag(nnp, RC_NODE_IN_TX)) break; rc_node_rele_locked(nnp); } /* * If it is dead, we want to update it so that it will continue to * report being dead. */ if (nnp->rn_flags & RC_NODE_DEAD) { (void) pthread_mutex_unlock(&nnp->rn_lock); if (nnp != np && cpg == NULL) rc_node_assign(npp, nnp); /* updated */ rc_node_rele(nnp); return (REP_PROTOCOL_FAIL_DELETED); } assert(!(nnp->rn_flags & RC_NODE_OLD)); (void) pthread_mutex_unlock(&nnp->rn_lock); if (nnp != np && cpg == NULL) rc_node_assign(npp, nnp); /* updated */ rc_node_rele(nnp); return ((nnp == np)? REP_PROTOCOL_SUCCESS : REP_PROTOCOL_DONE); } /* * does a generic modification check, for creation, deletion, and snapshot * management only. Property group transactions have different checks. * * The string returned to *match_auth must be freed. */ static perm_status_t rc_node_modify_permission_check(char **match_auth) { permcheck_t *pcp; perm_status_t granted = PERM_GRANTED; int rc; *match_auth = NULL; #ifdef NATIVE_BUILD if (!client_is_privileged()) { granted = PERM_DENIED; } return (granted); #else if (is_main_repository == 0) return (PERM_GRANTED); pcp = pc_create(); if (pcp != NULL) { rc = perm_add_enabling(pcp, AUTH_MODIFY); if (rc == REP_PROTOCOL_SUCCESS) { granted = perm_granted(pcp); if ((granted == PERM_GRANTED) || (granted == PERM_DENIED)) { /* * Copy off the authorization * string before freeing pcp. */ *match_auth = strdup(pcp->pc_auth_string); if (*match_auth == NULL) granted = PERM_FAIL; } } else { granted = PERM_FAIL; } pc_free(pcp); } else { granted = PERM_FAIL; } return (granted); #endif /* NATIVE_BUILD */ } /* * Native builds are done to create svc.configd-native. This program runs * only on the Solaris build machines to create the seed repository, and it * is compiled against the build machine's header files. The ADT_smf_* * symbols may not be defined in these header files. For this reason * smf_annotation_event(), smf_audit_event() and special_property_event() * are not compiled for native builds. */ #ifndef NATIVE_BUILD /* * This function generates an annotation audit event if one has been setup. * Annotation events should only be generated immediately before the audit * record from the first attempt to modify the repository from a client * which has requested an annotation. */ static void smf_annotation_event(int status, int return_val) { adt_session_data_t *session; adt_event_data_t *event = NULL; char file[MAXPATHLEN]; char operation[REP_PROTOCOL_NAME_LEN]; /* Don't audit if we're using an alternate repository. */ if (is_main_repository == 0) return; if (client_annotation_needed(operation, sizeof (operation), file, sizeof (file)) == 0) { return; } if (file[0] == 0) { (void) strlcpy(file, "NO FILE", sizeof (file)); } if (operation[0] == 0) { (void) strlcpy(operation, "NO OPERATION", sizeof (operation)); } if ((session = get_audit_session()) == NULL) return; if ((event = adt_alloc_event(session, ADT_smf_annotation)) == NULL) { uu_warn("smf_annotation_event cannot allocate event " "data. %s\n", strerror(errno)); return; } event->adt_smf_annotation.operation = operation; event->adt_smf_annotation.file = file; if (adt_put_event(event, status, return_val) == 0) { client_annotation_finished(); } else { uu_warn("smf_annotation_event failed to put event. " "%s\n", strerror(errno)); } adt_free_event(event); } #endif /* * smf_audit_event interacts with the security auditing system to generate * an audit event structure. It establishes an audit session and allocates * an audit event. The event is filled in from the audit data, and * adt_put_event is called to generate the event. */ static void smf_audit_event(au_event_t event_id, int status, int return_val, audit_event_data_t *data) { #ifndef NATIVE_BUILD char *auth_used; char *fmri; char *prop_value; adt_session_data_t *session; adt_event_data_t *event = NULL; /* Don't audit if we're using an alternate repository */ if (is_main_repository == 0) return; smf_annotation_event(status, return_val); if ((session = get_audit_session()) == NULL) return; if ((event = adt_alloc_event(session, event_id)) == NULL) { uu_warn("smf_audit_event cannot allocate event " "data. %s\n", strerror(errno)); return; } /* * Handle possibility of NULL authorization strings, FMRIs and * property values. */ if (data->ed_auth == NULL) { auth_used = "PRIVILEGED"; } else { auth_used = data->ed_auth; } if (data->ed_fmri == NULL) { syslog(LOG_WARNING, "smf_audit_event called with " "empty FMRI string"); fmri = "UNKNOWN FMRI"; } else { fmri = data->ed_fmri; } if (data->ed_prop_value == NULL) { prop_value = ""; } else { prop_value = data->ed_prop_value; } /* Fill in the event data. */ switch (event_id) { case ADT_smf_attach_snap: event->adt_smf_attach_snap.auth_used = auth_used; event->adt_smf_attach_snap.old_fmri = data->ed_old_fmri; event->adt_smf_attach_snap.old_name = data->ed_old_name; event->adt_smf_attach_snap.new_fmri = fmri; event->adt_smf_attach_snap.new_name = data->ed_snapname; break; case ADT_smf_change_prop: event->adt_smf_change_prop.auth_used = auth_used; event->adt_smf_change_prop.fmri = fmri; event->adt_smf_change_prop.type = data->ed_type; event->adt_smf_change_prop.value = prop_value; break; case ADT_smf_clear: event->adt_smf_clear.auth_used = auth_used; event->adt_smf_clear.fmri = fmri; break; case ADT_smf_create: event->adt_smf_create.fmri = fmri; event->adt_smf_create.auth_used = auth_used; break; case ADT_smf_create_npg: event->adt_smf_create_npg.auth_used = auth_used; event->adt_smf_create_npg.fmri = fmri; event->adt_smf_create_npg.type = data->ed_type; break; case ADT_smf_create_pg: event->adt_smf_create_pg.auth_used = auth_used; event->adt_smf_create_pg.fmri = fmri; event->adt_smf_create_pg.type = data->ed_type; break; case ADT_smf_create_prop: event->adt_smf_create_prop.auth_used = auth_used; event->adt_smf_create_prop.fmri = fmri; event->adt_smf_create_prop.type = data->ed_type; event->adt_smf_create_prop.value = prop_value; break; case ADT_smf_create_snap: event->adt_smf_create_snap.auth_used = auth_used; event->adt_smf_create_snap.fmri = fmri; event->adt_smf_create_snap.name = data->ed_snapname; break; case ADT_smf_degrade: event->adt_smf_degrade.auth_used = auth_used; event->adt_smf_degrade.fmri = fmri; break; case ADT_smf_delete: event->adt_smf_delete.fmri = fmri; event->adt_smf_delete.auth_used = auth_used; break; case ADT_smf_delete_npg: event->adt_smf_delete_npg.auth_used = auth_used; event->adt_smf_delete_npg.fmri = fmri; event->adt_smf_delete_npg.type = data->ed_type; break; case ADT_smf_delete_pg: event->adt_smf_delete_pg.auth_used = auth_used; event->adt_smf_delete_pg.fmri = fmri; event->adt_smf_delete_pg.type = data->ed_type; break; case ADT_smf_delete_prop: event->adt_smf_delete_prop.auth_used = auth_used; event->adt_smf_delete_prop.fmri = fmri; break; case ADT_smf_delete_snap: event->adt_smf_delete_snap.auth_used = auth_used; event->adt_smf_delete_snap.fmri = fmri; event->adt_smf_delete_snap.name = data->ed_snapname; break; case ADT_smf_disable: event->adt_smf_disable.auth_used = auth_used; event->adt_smf_disable.fmri = fmri; break; case ADT_smf_enable: event->adt_smf_enable.auth_used = auth_used; event->adt_smf_enable.fmri = fmri; break; case ADT_smf_immediate_degrade: event->adt_smf_immediate_degrade.auth_used = auth_used; event->adt_smf_immediate_degrade.fmri = fmri; break; case ADT_smf_immediate_maintenance: event->adt_smf_immediate_maintenance.auth_used = auth_used; event->adt_smf_immediate_maintenance.fmri = fmri; break; case ADT_smf_immtmp_maintenance: event->adt_smf_immtmp_maintenance.auth_used = auth_used; event->adt_smf_immtmp_maintenance.fmri = fmri; break; case ADT_smf_maintenance: event->adt_smf_maintenance.auth_used = auth_used; event->adt_smf_maintenance.fmri = fmri; break; case ADT_smf_milestone: event->adt_smf_milestone.auth_used = auth_used; event->adt_smf_milestone.fmri = fmri; break; case ADT_smf_read_prop: event->adt_smf_read_prop.auth_used = auth_used; event->adt_smf_read_prop.fmri = fmri; break; case ADT_smf_refresh: event->adt_smf_refresh.auth_used = auth_used; event->adt_smf_refresh.fmri = fmri; break; case ADT_smf_restart: event->adt_smf_restart.auth_used = auth_used; event->adt_smf_restart.fmri = fmri; break; case ADT_smf_tmp_disable: event->adt_smf_tmp_disable.auth_used = auth_used; event->adt_smf_tmp_disable.fmri = fmri; break; case ADT_smf_tmp_enable: event->adt_smf_tmp_enable.auth_used = auth_used; event->adt_smf_tmp_enable.fmri = fmri; break; case ADT_smf_tmp_maintenance: event->adt_smf_tmp_maintenance.auth_used = auth_used; event->adt_smf_tmp_maintenance.fmri = fmri; break; default: abort(); /* Need to cover all SMF event IDs */ } if (adt_put_event(event, status, return_val) != 0) { uu_warn("smf_audit_event failed to put event. %s\n", strerror(errno)); } adt_free_event(event); #endif } #ifndef NATIVE_BUILD /* * Determine if the combination of the property group at pg_name and the * property at prop_name are in the set of special startd properties. If * they are, a special audit event will be generated. */ static void special_property_event(audit_event_data_t *evdp, const char *prop_name, char *pg_name, int status, int return_val, tx_commit_data_t *tx_data, size_t cmd_no) { au_event_t event_id; audit_special_prop_item_t search_key; audit_special_prop_item_t *found; /* Use bsearch to find the special property information. */ search_key.api_prop_name = prop_name; search_key.api_pg_name = pg_name; found = (audit_special_prop_item_t *)bsearch(&search_key, special_props_list, SPECIAL_PROP_COUNT, sizeof (special_props_list[0]), special_prop_compare); if (found == NULL) { /* Not a special property. */ return; } /* Get the event id */ if (found->api_event_func == NULL) { event_id = found->api_event_id; } else { if ((*found->api_event_func)(tx_data, cmd_no, found->api_pg_name, &event_id) < 0) return; } /* Generate the event. */ smf_audit_event(event_id, status, return_val, evdp); } #endif /* NATIVE_BUILD */ /* * Return a pointer to a string containing all the values of the command * specified by cmd_no with each value enclosed in quotes. It is up to the * caller to free the memory at the returned pointer. */ static char * generate_value_list(tx_commit_data_t *tx_data, size_t cmd_no) { const char *cp; const char *cur_value; size_t byte_count = 0; uint32_t i; uint32_t nvalues; size_t str_size = 0; char *values = NULL; char *vp; if (tx_cmd_nvalues(tx_data, cmd_no, &nvalues) != REP_PROTOCOL_SUCCESS) return (NULL); /* * First determine the size of the buffer that we will need. We * will represent each property value surrounded by quotes with a * space separating the values. Thus, we need to find the total * size of all the value strings and add 3 for each value. * * There is one catch, though. We need to escape any internal * quote marks in the values. So for each quote in the value we * need to add another byte to the buffer size. */ for (i = 0; i < nvalues; i++) { if (tx_cmd_value(tx_data, cmd_no, i, &cur_value) != REP_PROTOCOL_SUCCESS) return (NULL); for (cp = cur_value; *cp != 0; cp++) { byte_count += (*cp == '"') ? 2 : 1; } byte_count += 3; /* surrounding quotes & space */ } byte_count++; /* nul terminator */ values = malloc(byte_count); if (values == NULL) return (NULL); *values = 0; /* Now build up the string of values. */ for (i = 0; i < nvalues; i++) { if (tx_cmd_value(tx_data, cmd_no, i, &cur_value) != REP_PROTOCOL_SUCCESS) { free(values); return (NULL); } (void) strlcat(values, "\"", byte_count); for (cp = cur_value, vp = values + strlen(values); *cp != 0; cp++) { if (*cp == '"') { *vp++ = '\\'; *vp++ = '"'; } else { *vp++ = *cp; } } *vp = 0; str_size = strlcat(values, "\" ", byte_count); assert(str_size < byte_count); } if (str_size > 0) values[str_size - 1] = 0; /* get rid of trailing space */ return (values); } /* * generate_property_events takes the transaction commit data at tx_data * and generates an audit event for each command. * * Native builds are done to create svc.configd-native. This program runs * only on the Solaris build machines to create the seed repository. Thus, * no audit events should be generated when running svc.configd-native. */ static void generate_property_events( tx_commit_data_t *tx_data, char *pg_fmri, /* FMRI of property group */ char *auth_string, int auth_status, int auth_ret_value) { #ifndef NATIVE_BUILD enum rep_protocol_transaction_action action; audit_event_data_t audit_data; size_t count; size_t cmd_no; char *cp; au_event_t event_id; char fmri[REP_PROTOCOL_FMRI_LEN]; char pg_name[REP_PROTOCOL_NAME_LEN]; char *pg_end; /* End of prop. group fmri */ const char *prop_name; uint32_t ptype; char prop_type[3]; enum rep_protocol_responseid rc; size_t sz_out; /* Make sure we have something to do. */ if (tx_data == NULL) return; if ((count = tx_cmd_count(tx_data)) == 0) return; /* Copy the property group fmri */ pg_end = fmri; pg_end += strlcpy(fmri, pg_fmri, sizeof (fmri)); /* * Get the property group name. It is the first component after * the last occurance of SCF_FMRI_PROPERTYGRP_PREFIX in the fmri. */ cp = strstr(pg_fmri, SCF_FMRI_PROPERTYGRP_PREFIX); if (cp == NULL) { pg_name[0] = 0; } else { cp += strlen(SCF_FMRI_PROPERTYGRP_PREFIX); (void) strlcpy(pg_name, cp, sizeof (pg_name)); } audit_data.ed_auth = auth_string; audit_data.ed_fmri = fmri; audit_data.ed_type = prop_type; /* * Property type is two characters (see * rep_protocol_value_type_t), so terminate the string. */ prop_type[2] = 0; for (cmd_no = 0; cmd_no < count; cmd_no++) { /* Construct FMRI of the property */ *pg_end = 0; if (tx_cmd_prop(tx_data, cmd_no, &prop_name) != REP_PROTOCOL_SUCCESS) { continue; } rc = rc_concat_fmri_element(fmri, sizeof (fmri), &sz_out, prop_name, REP_PROTOCOL_ENTITY_PROPERTY); if (rc != REP_PROTOCOL_SUCCESS) { /* * If we can't get the FMRI, we'll abandon this * command */ continue; } /* Generate special property event if necessary. */ special_property_event(&audit_data, prop_name, pg_name, auth_status, auth_ret_value, tx_data, cmd_no); /* Capture rest of audit data. */ if (tx_cmd_prop_type(tx_data, cmd_no, &ptype) != REP_PROTOCOL_SUCCESS) { continue; } prop_type[0] = REP_PROTOCOL_BASE_TYPE(ptype); prop_type[1] = REP_PROTOCOL_SUBTYPE(ptype); audit_data.ed_prop_value = generate_value_list(tx_data, cmd_no); /* Determine the event type. */ if (tx_cmd_action(tx_data, cmd_no, &action) != REP_PROTOCOL_SUCCESS) { free(audit_data.ed_prop_value); continue; } switch (action) { case REP_PROTOCOL_TX_ENTRY_NEW: event_id = ADT_smf_create_prop; break; case REP_PROTOCOL_TX_ENTRY_CLEAR: event_id = ADT_smf_change_prop; break; case REP_PROTOCOL_TX_ENTRY_REPLACE: event_id = ADT_smf_change_prop; break; case REP_PROTOCOL_TX_ENTRY_DELETE: event_id = ADT_smf_delete_prop; break; default: assert(0); /* Missing a case */ free(audit_data.ed_prop_value); continue; } /* Generate the event. */ smf_audit_event(event_id, auth_status, auth_ret_value, &audit_data); free(audit_data.ed_prop_value); } #endif /* NATIVE_BUILD */ } /* * Fails with * _DELETED - node has been deleted * _NOT_SET - npp is reset * _NOT_APPLICABLE - type is _PROPERTYGRP * _INVALID_TYPE - node is corrupt or type is invalid * _TYPE_MISMATCH - node cannot have children of type type * _BAD_REQUEST - name is invalid * cannot create children for this type of node * _NO_RESOURCES - out of memory, or could not allocate new id * _PERMISSION_DENIED * _BACKEND_ACCESS * _BACKEND_READONLY * _EXISTS - child already exists * _TRUNCATED - truncated FMRI for the audit record */ int rc_node_create_child(rc_node_ptr_t *npp, uint32_t type, const char *name, rc_node_ptr_t *cpp) { rc_node_t *np; rc_node_t *cp = NULL; int rc; perm_status_t perm_rc; size_t sz_out; char fmri[REP_PROTOCOL_FMRI_LEN]; audit_event_data_t audit_data; rc_node_clear(cpp, 0); /* * rc_node_modify_permission_check() must be called before the node * is locked. This is because the library functions that check * authorizations can trigger calls back into configd. */ perm_rc = rc_node_modify_permission_check(&audit_data.ed_auth); switch (perm_rc) { case PERM_DENIED: /* * We continue in this case, so that an audit event can be * generated later in the function. */ break; case PERM_GRANTED: break; case PERM_GONE: return (REP_PROTOCOL_FAIL_PERMISSION_DENIED); case PERM_FAIL: return (REP_PROTOCOL_FAIL_NO_RESOURCES); default: bad_error(rc_node_modify_permission_check, perm_rc); } RC_NODE_PTR_CHECK_LOCK_OR_FREE_RETURN(np, npp, audit_data.ed_auth); audit_data.ed_fmri = fmri; /* * there is a separate interface for creating property groups */ if (type == REP_PROTOCOL_ENTITY_PROPERTYGRP) { (void) pthread_mutex_unlock(&np->rn_lock); free(audit_data.ed_auth); return (REP_PROTOCOL_FAIL_NOT_APPLICABLE); } if (np->rn_id.rl_type == REP_PROTOCOL_ENTITY_CPROPERTYGRP) { (void) pthread_mutex_unlock(&np->rn_lock); np = np->rn_cchain[0]; if ((rc = rc_node_check_and_lock(np)) != REP_PROTOCOL_SUCCESS) { free(audit_data.ed_auth); return (rc); } } if ((rc = rc_check_parent_child(np->rn_id.rl_type, type)) != REP_PROTOCOL_SUCCESS) { (void) pthread_mutex_unlock(&np->rn_lock); free(audit_data.ed_auth); return (rc); } if ((rc = rc_check_type_name(type, name)) != REP_PROTOCOL_SUCCESS) { (void) pthread_mutex_unlock(&np->rn_lock); free(audit_data.ed_auth); return (rc); } if ((rc = rc_get_fmri_and_concat(np, fmri, sizeof (fmri), &sz_out, name, type)) != REP_PROTOCOL_SUCCESS) { (void) pthread_mutex_unlock(&np->rn_lock); free(audit_data.ed_auth); return (rc); } if (perm_rc == PERM_DENIED) { (void) pthread_mutex_unlock(&np->rn_lock); smf_audit_event(ADT_smf_create, ADT_FAILURE, ADT_FAIL_VALUE_AUTH, &audit_data); free(audit_data.ed_auth); return (REP_PROTOCOL_FAIL_PERMISSION_DENIED); } HOLD_PTR_FLAG_OR_FREE_AND_RETURN(np, npp, RC_NODE_CREATING_CHILD, audit_data.ed_auth); (void) pthread_mutex_unlock(&np->rn_lock); rc = object_create(np, type, name, &cp); assert(rc != REP_PROTOCOL_FAIL_NOT_APPLICABLE); if (rc == REP_PROTOCOL_SUCCESS) { rc_node_assign(cpp, cp); rc_node_rele(cp); } (void) pthread_mutex_lock(&np->rn_lock); rc_node_rele_flag(np, RC_NODE_CREATING_CHILD); (void) pthread_mutex_unlock(&np->rn_lock); if (rc == REP_PROTOCOL_SUCCESS) { smf_audit_event(ADT_smf_create, ADT_SUCCESS, ADT_SUCCESS, &audit_data); } free(audit_data.ed_auth); return (rc); } int rc_node_create_child_pg(rc_node_ptr_t *npp, uint32_t type, const char *name, const char *pgtype, uint32_t flags, rc_node_ptr_t *cpp) { rc_node_t *np; rc_node_t *cp; int rc; permcheck_t *pcp; perm_status_t granted; char fmri[REP_PROTOCOL_FMRI_LEN]; audit_event_data_t audit_data; au_event_t event_id; size_t sz_out; audit_data.ed_auth = NULL; audit_data.ed_fmri = fmri; audit_data.ed_type = (char *)pgtype; rc_node_clear(cpp, 0); /* verify flags is valid */ if (flags & ~SCF_PG_FLAG_NONPERSISTENT) return (REP_PROTOCOL_FAIL_BAD_REQUEST); RC_NODE_PTR_GET_CHECK_AND_HOLD(np, npp); if (type != REP_PROTOCOL_ENTITY_PROPERTYGRP) { rc_node_rele(np); return (REP_PROTOCOL_FAIL_NOT_APPLICABLE); } if ((rc = rc_check_parent_child(np->rn_id.rl_type, type)) != REP_PROTOCOL_SUCCESS) { rc_node_rele(np); return (rc); } if ((rc = rc_check_type_name(type, name)) != REP_PROTOCOL_SUCCESS || (rc = rc_check_pgtype_name(pgtype)) != REP_PROTOCOL_SUCCESS) { rc_node_rele(np); return (rc); } #ifdef NATIVE_BUILD if (!client_is_privileged()) { rc = REP_PROTOCOL_FAIL_PERMISSION_DENIED; } #else if (flags & SCF_PG_FLAG_NONPERSISTENT) { event_id = ADT_smf_create_npg; } else { event_id = ADT_smf_create_pg; } if ((rc = rc_get_fmri_and_concat(np, fmri, sizeof (fmri), &sz_out, name, REP_PROTOCOL_ENTITY_PROPERTYGRP)) != REP_PROTOCOL_SUCCESS) { rc_node_rele(np); return (rc); } if (is_main_repository) { /* Must have .smf.modify or smf.modify. authorization */ pcp = pc_create(); if (pcp != NULL) { rc = perm_add_enabling(pcp, AUTH_MODIFY); if (rc == REP_PROTOCOL_SUCCESS) { const char * const auth = perm_auth_for_pgtype(pgtype); if (auth != NULL) rc = perm_add_enabling(pcp, auth); } /* * .manage or $action_authorization can be used to * create the actions pg and the general_ovr pg. */ if (rc == REP_PROTOCOL_SUCCESS && (flags & SCF_PG_FLAG_NONPERSISTENT) != 0 && np->rn_id.rl_type == REP_PROTOCOL_ENTITY_INSTANCE && ((strcmp(name, AUTH_PG_ACTIONS) == 0 && strcmp(pgtype, AUTH_PG_ACTIONS_TYPE) == 0) || (strcmp(name, AUTH_PG_GENERAL_OVR) == 0 && strcmp(pgtype, AUTH_PG_GENERAL_OVR_TYPE) == 0))) { rc = perm_add_enabling(pcp, AUTH_MANAGE); if (rc == REP_PROTOCOL_SUCCESS) rc = perm_add_inst_action_auth(pcp, np); } if (rc == REP_PROTOCOL_SUCCESS) { granted = perm_granted(pcp); rc = map_granted_status(granted, pcp, &audit_data.ed_auth); if (granted == PERM_GONE) { /* No auditing if client gone. */ pc_free(pcp); rc_node_rele(np); return (rc); } } pc_free(pcp); } else { rc = REP_PROTOCOL_FAIL_NO_RESOURCES; } } else { rc = REP_PROTOCOL_SUCCESS; } #endif /* NATIVE_BUILD */ if (rc != REP_PROTOCOL_SUCCESS) { rc_node_rele(np); if (rc != REP_PROTOCOL_FAIL_NO_RESOURCES) { smf_audit_event(event_id, ADT_FAILURE, ADT_FAIL_VALUE_AUTH, &audit_data); } if (audit_data.ed_auth != NULL) free(audit_data.ed_auth); return (rc); } (void) pthread_mutex_lock(&np->rn_lock); HOLD_PTR_FLAG_OR_FREE_AND_RETURN(np, npp, RC_NODE_CREATING_CHILD, audit_data.ed_auth); (void) pthread_mutex_unlock(&np->rn_lock); rc = object_create_pg(np, type, name, pgtype, flags, &cp); if (rc == REP_PROTOCOL_SUCCESS) { rc_node_assign(cpp, cp); rc_node_rele(cp); } (void) pthread_mutex_lock(&np->rn_lock); rc_node_rele_flag(np, RC_NODE_CREATING_CHILD); (void) pthread_mutex_unlock(&np->rn_lock); if (rc == REP_PROTOCOL_SUCCESS) { smf_audit_event(event_id, ADT_SUCCESS, ADT_SUCCESS, &audit_data); } if (audit_data.ed_auth != NULL) free(audit_data.ed_auth); return (rc); } static void rc_pg_notify_fire(rc_node_pg_notify_t *pnp) { assert(MUTEX_HELD(&rc_pg_notify_lock)); if (pnp->rnpn_pg != NULL) { uu_list_remove(pnp->rnpn_pg->rn_pg_notify_list, pnp); (void) close(pnp->rnpn_fd); pnp->rnpn_pg = NULL; pnp->rnpn_fd = -1; } else { assert(pnp->rnpn_fd == -1); } } static void rc_notify_node_delete(rc_notify_delete_t *ndp, rc_node_t *np_arg) { rc_node_t *svc = NULL; rc_node_t *inst = NULL; rc_node_t *pg = NULL; rc_node_t *np = np_arg; rc_node_t *nnp; while (svc == NULL) { (void) pthread_mutex_lock(&np->rn_lock); if (!rc_node_hold_flag(np, RC_NODE_USING_PARENT)) { (void) pthread_mutex_unlock(&np->rn_lock); goto cleanup; } nnp = np->rn_parent; rc_node_hold_locked(np); /* hold it in place */ switch (np->rn_id.rl_type) { case REP_PROTOCOL_ENTITY_PROPERTYGRP: assert(pg == NULL); pg = np; break; case REP_PROTOCOL_ENTITY_INSTANCE: assert(inst == NULL); inst = np; break; case REP_PROTOCOL_ENTITY_SERVICE: assert(svc == NULL); svc = np; break; default: rc_node_rele_flag(np, RC_NODE_USING_PARENT); rc_node_rele_locked(np); goto cleanup; } (void) pthread_mutex_unlock(&np->rn_lock); np = nnp; if (np == NULL) goto cleanup; } rc_notify_deletion(ndp, svc->rn_name, inst != NULL ? inst->rn_name : NULL, pg != NULL ? pg->rn_name : NULL); ndp = NULL; cleanup: if (ndp != NULL) uu_free(ndp); for (;;) { if (svc != NULL) { np = svc; svc = NULL; } else if (inst != NULL) { np = inst; inst = NULL; } else if (pg != NULL) { np = pg; pg = NULL; } else break; (void) pthread_mutex_lock(&np->rn_lock); rc_node_rele_flag(np, RC_NODE_USING_PARENT); rc_node_rele_locked(np); } } /* * Hold RC_NODE_DYING_FLAGS on np's descendents. If andformer is true, do * the same down the rn_former chain. */ static void rc_node_delete_hold(rc_node_t *np, int andformer) { rc_node_t *cp; again: assert(MUTEX_HELD(&np->rn_lock)); assert((np->rn_flags & RC_NODE_DYING_FLAGS) == RC_NODE_DYING_FLAGS); for (cp = uu_list_first(np->rn_children); cp != NULL; cp = uu_list_next(np->rn_children, cp)) { (void) pthread_mutex_lock(&cp->rn_lock); (void) pthread_mutex_unlock(&np->rn_lock); if (!rc_node_hold_flag(cp, RC_NODE_DYING_FLAGS)) { /* * already marked as dead -- can't happen, since that * would require setting RC_NODE_CHILDREN_CHANGING * in np, and we're holding that... */ abort(); } rc_node_delete_hold(cp, andformer); /* recurse, drop lock */ (void) pthread_mutex_lock(&np->rn_lock); } if (andformer && (cp = np->rn_former) != NULL) { (void) pthread_mutex_lock(&cp->rn_lock); (void) pthread_mutex_unlock(&np->rn_lock); if (!rc_node_hold_flag(cp, RC_NODE_DYING_FLAGS)) abort(); /* can't happen, see above */ np = cp; goto again; /* tail-recurse down rn_former */ } (void) pthread_mutex_unlock(&np->rn_lock); } /* * N.B.: this function drops np->rn_lock on the way out. */ static void rc_node_delete_rele(rc_node_t *np, int andformer) { rc_node_t *cp; again: assert(MUTEX_HELD(&np->rn_lock)); assert((np->rn_flags & RC_NODE_DYING_FLAGS) == RC_NODE_DYING_FLAGS); for (cp = uu_list_first(np->rn_children); cp != NULL; cp = uu_list_next(np->rn_children, cp)) { (void) pthread_mutex_lock(&cp->rn_lock); (void) pthread_mutex_unlock(&np->rn_lock); rc_node_delete_rele(cp, andformer); /* recurse, drop lock */ (void) pthread_mutex_lock(&np->rn_lock); } if (andformer && (cp = np->rn_former) != NULL) { (void) pthread_mutex_lock(&cp->rn_lock); rc_node_rele_flag(np, RC_NODE_DYING_FLAGS); (void) pthread_mutex_unlock(&np->rn_lock); np = cp; goto again; /* tail-recurse down rn_former */ } rc_node_rele_flag(np, RC_NODE_DYING_FLAGS); (void) pthread_mutex_unlock(&np->rn_lock); } static void rc_node_finish_delete(rc_node_t *cp) { cache_bucket_t *bp; rc_node_pg_notify_t *pnp; assert(MUTEX_HELD(&cp->rn_lock)); if (!(cp->rn_flags & RC_NODE_OLD)) { assert(cp->rn_flags & RC_NODE_IN_PARENT); if (!rc_node_wait_flag(cp, RC_NODE_USING_PARENT)) { abort(); /* can't happen, see above */ } cp->rn_flags &= ~RC_NODE_IN_PARENT; cp->rn_parent = NULL; rc_node_free_fmri(cp); } cp->rn_flags |= RC_NODE_DEAD; /* * If this node is not out-dated, we need to remove it from * the notify list and cache hash table. */ if (!(cp->rn_flags & RC_NODE_OLD)) { assert(cp->rn_refs > 0); /* can't go away yet */ (void) pthread_mutex_unlock(&cp->rn_lock); (void) pthread_mutex_lock(&rc_pg_notify_lock); while ((pnp = uu_list_first(cp->rn_pg_notify_list)) != NULL) rc_pg_notify_fire(pnp); (void) pthread_mutex_unlock(&rc_pg_notify_lock); rc_notify_remove_node(cp); bp = cache_hold(cp->rn_hash); (void) pthread_mutex_lock(&cp->rn_lock); cache_remove_unlocked(bp, cp); cache_release(bp); } } /* * For each child, call rc_node_finish_delete() and recurse. If andformer * is set, also recurse down rn_former. Finally release np, which might * free it. */ static void rc_node_delete_children(rc_node_t *np, int andformer) { rc_node_t *cp; again: assert(np->rn_refs > 0); assert(MUTEX_HELD(&np->rn_lock)); assert(np->rn_flags & RC_NODE_DEAD); while ((cp = uu_list_first(np->rn_children)) != NULL) { uu_list_remove(np->rn_children, cp); (void) pthread_mutex_lock(&cp->rn_lock); (void) pthread_mutex_unlock(&np->rn_lock); rc_node_hold_locked(cp); /* hold while we recurse */ rc_node_finish_delete(cp); rc_node_delete_children(cp, andformer); /* drops lock + ref */ (void) pthread_mutex_lock(&np->rn_lock); } /* * When we drop cp's lock, all the children will be gone, so we * can release DYING_FLAGS. */ rc_node_rele_flag(np, RC_NODE_DYING_FLAGS); if (andformer && (cp = np->rn_former) != NULL) { np->rn_former = NULL; /* unlink */ (void) pthread_mutex_lock(&cp->rn_lock); /* * Register the ephemeral reference created by reading * np->rn_former into cp. Note that the persistent * reference (np->rn_former) is locked because we haven't * dropped np's lock since we dropped its RC_NODE_IN_TX * (via RC_NODE_DYING_FLAGS). */ rc_node_hold_ephemeral_locked(cp); (void) pthread_mutex_unlock(&np->rn_lock); cp->rn_flags &= ~RC_NODE_ON_FORMER; rc_node_hold_locked(cp); /* hold while we loop */ rc_node_finish_delete(cp); rc_node_rele(np); /* drop the old reference */ np = cp; goto again; /* tail-recurse down rn_former */ } rc_node_rele_locked(np); } /* * The last client or child reference to np, which must be either * RC_NODE_OLD or RC_NODE_DEAD, has been destroyed. We'll destroy any * remaining references (e.g., rn_former) and call rc_node_destroy() to * free np. */ static void rc_node_no_client_refs(rc_node_t *np) { int unrefed; rc_node_t *current, *cur; assert(MUTEX_HELD(&np->rn_lock)); assert(np->rn_refs == 0); assert(np->rn_other_refs == 0); assert(np->rn_other_refs_held == 0); if (np->rn_flags & RC_NODE_DEAD) { /* * The node is DEAD, so the deletion code should have * destroyed all rn_children or rn_former references. * Since the last client or child reference has been * destroyed, we're free to destroy np. Unless another * thread has an ephemeral reference, in which case we'll * pass the buck. */ if (np->rn_erefs > 1) { --np->rn_erefs; NODE_UNLOCK(np); return; } (void) pthread_mutex_unlock(&np->rn_lock); rc_node_destroy(np); return; } /* We only collect DEAD and OLD nodes, thank you. */ assert(np->rn_flags & RC_NODE_OLD); /* * RC_NODE_UNREFED keeps multiple threads from processing OLD * nodes. But it's vulnerable to unfriendly scheduling, so full * use of rn_erefs should supersede it someday. */ if (np->rn_flags & RC_NODE_UNREFED) { (void) pthread_mutex_unlock(&np->rn_lock); return; } np->rn_flags |= RC_NODE_UNREFED; /* * Now we'll remove the node from the rn_former chain and take its * DYING_FLAGS. */ /* * Since this node is OLD, it should be on an rn_former chain. To * remove it, we must find the current in-hash object and grab its * RC_NODE_IN_TX flag to protect the entire rn_former chain. */ (void) pthread_mutex_unlock(&np->rn_lock); for (;;) { current = cache_lookup(&np->rn_id); if (current == NULL) { (void) pthread_mutex_lock(&np->rn_lock); if (np->rn_flags & RC_NODE_DEAD) goto died; /* * We are trying to unreference this node, but the * owner of the former list does not exist. It must * be the case that another thread is deleting this * entire sub-branch, but has not yet reached us. * We will in short order be deleted. */ np->rn_flags &= ~RC_NODE_UNREFED; (void) pthread_mutex_unlock(&np->rn_lock); return; } if (current == np) { /* * no longer unreferenced */ (void) pthread_mutex_lock(&np->rn_lock); np->rn_flags &= ~RC_NODE_UNREFED; /* held in cache_lookup() */ rc_node_rele_locked(np); return; } (void) pthread_mutex_lock(¤t->rn_lock); if (current->rn_flags & RC_NODE_OLD) { /* * current has been replaced since we looked it * up. Try again. */ /* held in cache_lookup() */ rc_node_rele_locked(current); continue; } if (!rc_node_hold_flag(current, RC_NODE_IN_TX)) { /* * current has been deleted since we looked it up. Try * again. */ /* held in cache_lookup() */ rc_node_rele_locked(current); continue; } /* * rc_node_hold_flag() might have dropped current's lock, so * check OLD again. */ if (!(current->rn_flags & RC_NODE_OLD)) { /* Not old. Stop looping. */ (void) pthread_mutex_unlock(¤t->rn_lock); break; } rc_node_rele_flag(current, RC_NODE_IN_TX); rc_node_rele_locked(current); } /* To take np's RC_NODE_DYING_FLAGS, we need its lock. */ (void) pthread_mutex_lock(&np->rn_lock); /* * While we didn't have the lock, a thread may have added * a reference or changed the flags. */ if (!(np->rn_flags & (RC_NODE_OLD | RC_NODE_DEAD)) || np->rn_refs != 0 || np->rn_other_refs != 0 || np->rn_other_refs_held != 0) { np->rn_flags &= ~RC_NODE_UNREFED; (void) pthread_mutex_lock(¤t->rn_lock); rc_node_rele_flag(current, RC_NODE_IN_TX); /* held by cache_lookup() */ rc_node_rele_locked(current); return; } if (!rc_node_hold_flag(np, RC_NODE_DYING_FLAGS)) { /* * Someone deleted the node while we were waiting for * DYING_FLAGS. Undo the modifications to current. */ (void) pthread_mutex_unlock(&np->rn_lock); rc_node_rele_flag(current, RC_NODE_IN_TX); /* held by cache_lookup() */ rc_node_rele_locked(current); (void) pthread_mutex_lock(&np->rn_lock); goto died; } /* Take RC_NODE_DYING_FLAGS on np's descendents. */ rc_node_delete_hold(np, 0); /* drops np->rn_lock */ /* Mark np DEAD. This requires the lock. */ (void) pthread_mutex_lock(&np->rn_lock); /* Recheck for new references. */ if (!(np->rn_flags & RC_NODE_OLD) || np->rn_refs != 0 || np->rn_other_refs != 0 || np->rn_other_refs_held != 0) { np->rn_flags &= ~RC_NODE_UNREFED; rc_node_delete_rele(np, 0); /* drops np's lock */ (void) pthread_mutex_lock(¤t->rn_lock); rc_node_rele_flag(current, RC_NODE_IN_TX); /* held by cache_lookup() */ rc_node_rele_locked(current); return; } np->rn_flags |= RC_NODE_DEAD; /* * Delete the children. This calls rc_node_rele_locked() on np at * the end, so add a reference to keep the count from going * negative. It will recurse with RC_NODE_DEAD set, so we'll call * rc_node_destroy() above, but RC_NODE_UNREFED is also set, so it * shouldn't actually free() np. */ rc_node_hold_locked(np); rc_node_delete_children(np, 0); /* unlocks np */ /* Remove np from current's rn_former chain. */ (void) pthread_mutex_lock(¤t->rn_lock); for (cur = current; cur != NULL && cur->rn_former != np; cur = cur->rn_former) ; assert(cur != NULL && cur != np); cur->rn_former = np->rn_former; np->rn_former = NULL; rc_node_rele_flag(current, RC_NODE_IN_TX); /* held by cache_lookup() */ rc_node_rele_locked(current); /* Clear ON_FORMER and UNREFED, and destroy. */ (void) pthread_mutex_lock(&np->rn_lock); assert(np->rn_flags & RC_NODE_ON_FORMER); np->rn_flags &= ~(RC_NODE_UNREFED | RC_NODE_ON_FORMER); if (np->rn_erefs > 1) { /* Still referenced. Stay execution. */ --np->rn_erefs; NODE_UNLOCK(np); return; } (void) pthread_mutex_unlock(&np->rn_lock); rc_node_destroy(np); return; died: /* * Another thread marked np DEAD. If there still aren't any * persistent references, destroy the node. */ np->rn_flags &= ~RC_NODE_UNREFED; unrefed = (np->rn_refs == 0 && np->rn_other_refs == 0 && np->rn_other_refs_held == 0); if (np->rn_erefs > 0) --np->rn_erefs; if (unrefed && np->rn_erefs > 0) { NODE_UNLOCK(np); return; } (void) pthread_mutex_unlock(&np->rn_lock); if (unrefed) rc_node_destroy(np); } static au_event_t get_delete_event_id(rep_protocol_entity_t entity, uint32_t pgflags) { au_event_t id = 0; #ifndef NATIVE_BUILD switch (entity) { case REP_PROTOCOL_ENTITY_SERVICE: case REP_PROTOCOL_ENTITY_INSTANCE: id = ADT_smf_delete; break; case REP_PROTOCOL_ENTITY_SNAPSHOT: id = ADT_smf_delete_snap; break; case REP_PROTOCOL_ENTITY_PROPERTYGRP: case REP_PROTOCOL_ENTITY_CPROPERTYGRP: if (pgflags & SCF_PG_FLAG_NONPERSISTENT) { id = ADT_smf_delete_npg; } else { id = ADT_smf_delete_pg; } break; default: abort(); } #endif /* NATIVE_BUILD */ return (id); } /* * Fails with * _NOT_SET * _DELETED * _BAD_REQUEST * _PERMISSION_DENIED * _NO_RESOURCES * _TRUNCATED * and whatever object_delete() fails with. */ int rc_node_delete(rc_node_ptr_t *npp) { rc_node_t *np, *np_orig; rc_node_t *pp = NULL; int rc; rc_node_pg_notify_t *pnp; cache_bucket_t *bp; rc_notify_delete_t *ndp; permcheck_t *pcp; int granted; au_event_t event_id = 0; size_t sz_out; audit_event_data_t audit_data; int audit_failure = 0; RC_NODE_PTR_GET_CHECK_AND_LOCK(np, npp); audit_data.ed_fmri = NULL; audit_data.ed_auth = NULL; audit_data.ed_snapname = NULL; audit_data.ed_type = NULL; switch (np->rn_id.rl_type) { case REP_PROTOCOL_ENTITY_SERVICE: event_id = get_delete_event_id(REP_PROTOCOL_ENTITY_SERVICE, np->rn_pgflags); break; case REP_PROTOCOL_ENTITY_INSTANCE: event_id = get_delete_event_id(REP_PROTOCOL_ENTITY_INSTANCE, np->rn_pgflags); break; case REP_PROTOCOL_ENTITY_SNAPSHOT: event_id = get_delete_event_id(REP_PROTOCOL_ENTITY_SNAPSHOT, np->rn_pgflags); audit_data.ed_snapname = strdup(np->rn_name); if (audit_data.ed_snapname == NULL) { (void) pthread_mutex_unlock(&np->rn_lock); return (REP_PROTOCOL_FAIL_NO_RESOURCES); } break; /* deletable */ case REP_PROTOCOL_ENTITY_SCOPE: case REP_PROTOCOL_ENTITY_SNAPLEVEL: /* Scopes and snaplevels are indelible. */ (void) pthread_mutex_unlock(&np->rn_lock); return (REP_PROTOCOL_FAIL_BAD_REQUEST); case REP_PROTOCOL_ENTITY_CPROPERTYGRP: (void) pthread_mutex_unlock(&np->rn_lock); np = np->rn_cchain[0]; RC_NODE_CHECK_AND_LOCK(np); event_id = get_delete_event_id(REP_PROTOCOL_ENTITY_CPROPERTYGRP, np->rn_pgflags); break; case REP_PROTOCOL_ENTITY_PROPERTYGRP: if (np->rn_id.rl_ids[ID_SNAPSHOT] == 0) { event_id = get_delete_event_id(REP_PROTOCOL_ENTITY_PROPERTYGRP, np->rn_pgflags); audit_data.ed_type = strdup(np->rn_type); if (audit_data.ed_type == NULL) { (void) pthread_mutex_unlock(&np->rn_lock); return (REP_PROTOCOL_FAIL_NO_RESOURCES); } break; } /* Snapshot property groups are indelible. */ (void) pthread_mutex_unlock(&np->rn_lock); return (REP_PROTOCOL_FAIL_PERMISSION_DENIED); case REP_PROTOCOL_ENTITY_PROPERTY: (void) pthread_mutex_unlock(&np->rn_lock); return (REP_PROTOCOL_FAIL_BAD_REQUEST); default: assert(0); abort(); break; } audit_data.ed_fmri = malloc(REP_PROTOCOL_FMRI_LEN); if (audit_data.ed_fmri == NULL) { rc = REP_PROTOCOL_FAIL_NO_RESOURCES; goto cleanout; } np_orig = np; rc_node_hold_locked(np); /* simplifies rest of the code */ again: /* * The following loop is to deal with the fact that snapshots and * property groups are moving targets -- changes to them result * in a new "child" node. Since we can only delete from the top node, * we have to loop until we have a non-RC_NODE_OLD version. */ for (;;) { if (!rc_node_wait_flag(np, RC_NODE_IN_TX | RC_NODE_USING_PARENT)) { rc_node_rele_locked(np); rc = REP_PROTOCOL_FAIL_DELETED; goto cleanout; } if (np->rn_flags & RC_NODE_OLD) { rc_node_rele_locked(np); np = cache_lookup(&np_orig->rn_id); assert(np != np_orig); if (np == NULL) { rc = REP_PROTOCOL_FAIL_DELETED; goto fail; } (void) pthread_mutex_lock(&np->rn_lock); continue; } if (!rc_node_hold_flag(np, RC_NODE_USING_PARENT)) { rc_node_rele_locked(np); rc_node_clear(npp, 1); rc = REP_PROTOCOL_FAIL_DELETED; } /* * Mark our parent as children changing. this call drops our * lock and the RC_NODE_USING_PARENT flag, and returns with * pp's lock held */ pp = rc_node_hold_parent_flag(np, RC_NODE_CHILDREN_CHANGING); if (pp == NULL) { /* our parent is gone, we're going next... */ rc_node_rele(np); rc_node_clear(npp, 1); rc = REP_PROTOCOL_FAIL_DELETED; goto cleanout; } rc_node_hold_locked(pp); /* hold for later */ (void) pthread_mutex_unlock(&pp->rn_lock); (void) pthread_mutex_lock(&np->rn_lock); if (!(np->rn_flags & RC_NODE_OLD)) break; /* not old -- we're done */ (void) pthread_mutex_unlock(&np->rn_lock); (void) pthread_mutex_lock(&pp->rn_lock); rc_node_rele_flag(pp, RC_NODE_CHILDREN_CHANGING); rc_node_rele_locked(pp); (void) pthread_mutex_lock(&np->rn_lock); continue; /* loop around and try again */ } /* * Everyone out of the pool -- we grab everything but * RC_NODE_USING_PARENT (including RC_NODE_DYING) to keep * any changes from occurring while we are attempting to * delete the node. */ if (!rc_node_hold_flag(np, RC_NODE_DYING_FLAGS)) { (void) pthread_mutex_unlock(&np->rn_lock); rc = REP_PROTOCOL_FAIL_DELETED; goto fail; } assert(!(np->rn_flags & RC_NODE_OLD)); if ((rc = rc_node_get_fmri_or_fragment(np, audit_data.ed_fmri, REP_PROTOCOL_FMRI_LEN, &sz_out)) != REP_PROTOCOL_SUCCESS) { rc_node_rele_flag(np, RC_NODE_DYING_FLAGS); (void) pthread_mutex_unlock(&np->rn_lock); goto fail; } #ifdef NATIVE_BUILD if (!client_is_privileged()) { rc = REP_PROTOCOL_FAIL_PERMISSION_DENIED; } #else if (is_main_repository) { /* permission check */ (void) pthread_mutex_unlock(&np->rn_lock); pcp = pc_create(); if (pcp != NULL) { rc = perm_add_enabling(pcp, AUTH_MODIFY); /* add .smf.modify. for pgs. */ if (rc == REP_PROTOCOL_SUCCESS && np->rn_id.rl_type == REP_PROTOCOL_ENTITY_PROPERTYGRP) { const char * const auth = perm_auth_for_pgtype(np->rn_type); if (auth != NULL) rc = perm_add_enabling(pcp, auth); } if (rc == REP_PROTOCOL_SUCCESS) { granted = perm_granted(pcp); rc = map_granted_status(granted, pcp, &audit_data.ed_auth); if (granted == PERM_GONE) { /* No need to audit if client gone. */ pc_free(pcp); rc_node_rele_flag(np, RC_NODE_DYING_FLAGS); return (rc); } if (granted == PERM_DENIED) audit_failure = 1; } pc_free(pcp); } else { rc = REP_PROTOCOL_FAIL_NO_RESOURCES; } (void) pthread_mutex_lock(&np->rn_lock); } else { rc = REP_PROTOCOL_SUCCESS; } #endif /* NATIVE_BUILD */ if (rc != REP_PROTOCOL_SUCCESS) { rc_node_rele_flag(np, RC_NODE_DYING_FLAGS); (void) pthread_mutex_unlock(&np->rn_lock); goto fail; } ndp = uu_zalloc(sizeof (*ndp)); if (ndp == NULL) { rc_node_rele_flag(np, RC_NODE_DYING_FLAGS); (void) pthread_mutex_unlock(&np->rn_lock); rc = REP_PROTOCOL_FAIL_NO_RESOURCES; goto fail; } rc_node_delete_hold(np, 1); /* hold entire subgraph, drop lock */ rc = object_delete(np); if (rc != REP_PROTOCOL_SUCCESS) { (void) pthread_mutex_lock(&np->rn_lock); rc_node_delete_rele(np, 1); /* drops lock */ uu_free(ndp); goto fail; } /* * Now, delicately unlink and delete the object. * * Create the delete notification, atomically remove * from the hash table and set the NODE_DEAD flag, and * remove from the parent's children list. */ rc_notify_node_delete(ndp, np); /* frees or uses ndp */ bp = cache_hold(np->rn_hash); (void) pthread_mutex_lock(&np->rn_lock); cache_remove_unlocked(bp, np); cache_release(bp); np->rn_flags |= RC_NODE_DEAD; if (pp != NULL) { /* * Remove from pp's rn_children. This requires pp's lock, * so we must drop np's lock to respect lock order. */ (void) pthread_mutex_unlock(&np->rn_lock); (void) pthread_mutex_lock(&pp->rn_lock); (void) pthread_mutex_lock(&np->rn_lock); uu_list_remove(pp->rn_children, np); rc_node_rele_flag(pp, RC_NODE_CHILDREN_CHANGING); (void) pthread_mutex_unlock(&pp->rn_lock); np->rn_flags &= ~RC_NODE_IN_PARENT; } /* * finally, propagate death to our children (including marking * them DEAD), handle notifications, and release our hold. */ rc_node_hold_locked(np); /* hold for delete */ rc_node_delete_children(np, 1); /* drops DYING_FLAGS, lock, ref */ rc_node_clear(npp, 1); (void) pthread_mutex_lock(&rc_pg_notify_lock); while ((pnp = uu_list_first(np->rn_pg_notify_list)) != NULL) rc_pg_notify_fire(pnp); (void) pthread_mutex_unlock(&rc_pg_notify_lock); rc_notify_remove_node(np); rc_node_rele(np); smf_audit_event(event_id, ADT_SUCCESS, ADT_SUCCESS, &audit_data); free(audit_data.ed_auth); free(audit_data.ed_snapname); free(audit_data.ed_type); free(audit_data.ed_fmri); return (rc); fail: rc_node_rele(np); if (rc == REP_PROTOCOL_FAIL_DELETED) rc_node_clear(npp, 1); if (pp != NULL) { (void) pthread_mutex_lock(&pp->rn_lock); rc_node_rele_flag(pp, RC_NODE_CHILDREN_CHANGING); rc_node_rele_locked(pp); /* drop ref and lock */ } if (audit_failure) { smf_audit_event(event_id, ADT_FAILURE, ADT_FAIL_VALUE_AUTH, &audit_data); } cleanout: free(audit_data.ed_auth); free(audit_data.ed_snapname); free(audit_data.ed_type); free(audit_data.ed_fmri); return (rc); } int rc_node_next_snaplevel(rc_node_ptr_t *npp, rc_node_ptr_t *cpp) { rc_node_t *np; rc_node_t *cp, *pp; int res; rc_node_clear(cpp, 0); RC_NODE_PTR_GET_CHECK_AND_LOCK(np, npp); if (np->rn_id.rl_type != REP_PROTOCOL_ENTITY_SNAPSHOT && np->rn_id.rl_type != REP_PROTOCOL_ENTITY_SNAPLEVEL) { (void) pthread_mutex_unlock(&np->rn_lock); return (REP_PROTOCOL_FAIL_NOT_APPLICABLE); } if (np->rn_id.rl_type == REP_PROTOCOL_ENTITY_SNAPSHOT) { if ((res = rc_node_fill_children(np, REP_PROTOCOL_ENTITY_SNAPLEVEL)) != REP_PROTOCOL_SUCCESS) { (void) pthread_mutex_unlock(&np->rn_lock); return (res); } for (cp = uu_list_first(np->rn_children); cp != NULL; cp = uu_list_next(np->rn_children, cp)) { if (cp->rn_id.rl_type != REP_PROTOCOL_ENTITY_SNAPLEVEL) continue; rc_node_hold(cp); break; } (void) pthread_mutex_unlock(&np->rn_lock); } else { if (!rc_node_hold_flag(np, RC_NODE_USING_PARENT)) { (void) pthread_mutex_unlock(&np->rn_lock); rc_node_clear(npp, 1); return (REP_PROTOCOL_FAIL_DELETED); } /* * mark our parent as children changing. This call drops our * lock and the RC_NODE_USING_PARENT flag, and returns with * pp's lock held */ pp = rc_node_hold_parent_flag(np, RC_NODE_CHILDREN_CHANGING); if (pp == NULL) { /* our parent is gone, we're going next... */ rc_node_clear(npp, 1); return (REP_PROTOCOL_FAIL_DELETED); } /* * find the next snaplevel */ cp = np; while ((cp = uu_list_next(pp->rn_children, cp)) != NULL && cp->rn_id.rl_type != REP_PROTOCOL_ENTITY_SNAPLEVEL) ; /* it must match the snaplevel list */ assert((cp == NULL && np->rn_snaplevel->rsl_next == NULL) || (cp != NULL && np->rn_snaplevel->rsl_next == cp->rn_snaplevel)); if (cp != NULL) rc_node_hold(cp); rc_node_rele_flag(pp, RC_NODE_CHILDREN_CHANGING); (void) pthread_mutex_unlock(&pp->rn_lock); } rc_node_assign(cpp, cp); if (cp != NULL) { rc_node_rele(cp); return (REP_PROTOCOL_SUCCESS); } return (REP_PROTOCOL_FAIL_NOT_FOUND); } /* * This call takes a snapshot (np) and either: * an existing snapid (to be associated with np), or * a non-NULL parentp (from which a new snapshot is taken, and associated * with np) * * To do the association, np is duplicated, the duplicate is made to * represent the new snapid, and np is replaced with the new rc_node_t on * np's parent's child list. np is placed on the new node's rn_former list, * and replaces np in cache_hash (so rc_node_update() will find the new one). * * old_fmri and old_name point to the original snap shot's FMRI and name. * These values are used when generating audit events. * * Fails with * _BAD_REQUEST * _BACKEND_READONLY * _DELETED * _NO_RESOURCES * _TRUNCATED * _TYPE_MISMATCH */ static int rc_attach_snapshot( rc_node_t *np, uint32_t snapid, rc_node_t *parentp, char *old_fmri, char *old_name) { rc_node_t *np_orig; rc_node_t *nnp, *prev; rc_node_t *pp; int rc; size_t sz_out; perm_status_t granted; au_event_t event_id; audit_event_data_t audit_data; if (parentp == NULL) { assert(old_fmri != NULL); } else { assert(snapid == 0); } assert(MUTEX_HELD(&np->rn_lock)); /* Gather the audit data. */ /* * ADT_smf_* symbols may not be defined in the /usr/include header * files on the build machine. Thus, the following if-else will * not be compiled when doing native builds. */ #ifndef NATIVE_BUILD if (parentp == NULL) { event_id = ADT_smf_attach_snap; } else { event_id = ADT_smf_create_snap; } #endif /* NATIVE_BUILD */ audit_data.ed_fmri = malloc(REP_PROTOCOL_FMRI_LEN); audit_data.ed_snapname = malloc(REP_PROTOCOL_NAME_LEN); if ((audit_data.ed_fmri == NULL) || (audit_data.ed_snapname == NULL)) { (void) pthread_mutex_unlock(&np->rn_lock); free(audit_data.ed_fmri); free(audit_data.ed_snapname); return (REP_PROTOCOL_FAIL_NO_RESOURCES); } audit_data.ed_auth = NULL; if (strlcpy(audit_data.ed_snapname, np->rn_name, REP_PROTOCOL_NAME_LEN) >= REP_PROTOCOL_NAME_LEN) { abort(); } audit_data.ed_old_fmri = old_fmri; audit_data.ed_old_name = old_name ? old_name : "NO NAME"; if (parentp == NULL) { /* * In the attach case, get the instance FMRIs of the * snapshots. */ if ((rc = rc_node_get_fmri_or_fragment(np, audit_data.ed_fmri, REP_PROTOCOL_FMRI_LEN, &sz_out)) != REP_PROTOCOL_SUCCESS) { (void) pthread_mutex_unlock(&np->rn_lock); free(audit_data.ed_fmri); free(audit_data.ed_snapname); return (rc); } } else { /* * Capture the FMRI of the parent if we're actually going * to take the snapshot. */ if ((rc = rc_node_get_fmri_or_fragment(parentp, audit_data.ed_fmri, REP_PROTOCOL_FMRI_LEN, &sz_out)) != REP_PROTOCOL_SUCCESS) { (void) pthread_mutex_unlock(&np->rn_lock); free(audit_data.ed_fmri); free(audit_data.ed_snapname); return (rc); } } np_orig = np; rc_node_hold_locked(np); /* simplifies the remainder */ (void) pthread_mutex_unlock(&np->rn_lock); granted = rc_node_modify_permission_check(&audit_data.ed_auth); switch (granted) { case PERM_DENIED: smf_audit_event(event_id, ADT_FAILURE, ADT_FAIL_VALUE_AUTH, &audit_data); rc = REP_PROTOCOL_FAIL_PERMISSION_DENIED; rc_node_rele(np); goto cleanout; case PERM_GRANTED: break; case PERM_GONE: rc = REP_PROTOCOL_FAIL_PERMISSION_DENIED; rc_node_rele(np); goto cleanout; case PERM_FAIL: rc = REP_PROTOCOL_FAIL_NO_RESOURCES; rc_node_rele(np); goto cleanout; default: bad_error(rc_node_modify_permission_check, granted); } (void) pthread_mutex_lock(&np->rn_lock); /* * get the latest node, holding RC_NODE_IN_TX to keep the rn_former * list from changing. */ for (;;) { if (!(np->rn_flags & RC_NODE_OLD)) { if (!rc_node_hold_flag(np, RC_NODE_USING_PARENT)) { goto again; } pp = rc_node_hold_parent_flag(np, RC_NODE_CHILDREN_CHANGING); (void) pthread_mutex_lock(&np->rn_lock); if (pp == NULL) { goto again; } if (np->rn_flags & RC_NODE_OLD) { rc_node_rele_flag(pp, RC_NODE_CHILDREN_CHANGING); (void) pthread_mutex_unlock(&pp->rn_lock); goto again; } (void) pthread_mutex_unlock(&pp->rn_lock); if (!rc_node_hold_flag(np, RC_NODE_IN_TX)) { /* * Can't happen, since we're holding our * parent's CHILDREN_CHANGING flag... */ abort(); } break; /* everything's ready */ } again: rc_node_rele_locked(np); np = cache_lookup(&np_orig->rn_id); if (np == NULL) { rc = REP_PROTOCOL_FAIL_DELETED; goto cleanout; } (void) pthread_mutex_lock(&np->rn_lock); } if (parentp != NULL) { if (pp != parentp) { rc = REP_PROTOCOL_FAIL_BAD_REQUEST; goto fail; } nnp = NULL; } else { /* * look for a former node with the snapid we need. */ if (np->rn_snapshot_id == snapid) { rc_node_rele_flag(np, RC_NODE_IN_TX); rc_node_rele_locked(np); (void) pthread_mutex_lock(&pp->rn_lock); rc_node_rele_flag(pp, RC_NODE_CHILDREN_CHANGING); (void) pthread_mutex_unlock(&pp->rn_lock); rc = REP_PROTOCOL_SUCCESS; /* nothing to do */ goto cleanout; } prev = np; while ((nnp = prev->rn_former) != NULL) { if (nnp->rn_snapshot_id == snapid) { rc_node_hold(nnp); break; /* existing node with that id */ } prev = nnp; } } if (nnp == NULL) { prev = NULL; nnp = rc_node_alloc(); if (nnp == NULL) { rc = REP_PROTOCOL_FAIL_NO_RESOURCES; goto fail; } nnp->rn_id = np->rn_id; /* structure assignment */ nnp->rn_hash = np->rn_hash; nnp->rn_name = strdup(np->rn_name); nnp->rn_snapshot_id = snapid; nnp->rn_flags = RC_NODE_IN_TX | RC_NODE_USING_PARENT; if (nnp->rn_name == NULL) { rc = REP_PROTOCOL_FAIL_NO_RESOURCES; goto fail; } } (void) pthread_mutex_unlock(&np->rn_lock); rc = object_snapshot_attach(&np->rn_id, &snapid, (parentp != NULL)); if (parentp != NULL) nnp->rn_snapshot_id = snapid; /* fill in new snapid */ else assert(nnp->rn_snapshot_id == snapid); (void) pthread_mutex_lock(&np->rn_lock); if (rc != REP_PROTOCOL_SUCCESS) goto fail; /* * fix up the former chain */ if (prev != NULL) { prev->rn_former = nnp->rn_former; (void) pthread_mutex_lock(&nnp->rn_lock); nnp->rn_flags &= ~RC_NODE_ON_FORMER; nnp->rn_former = NULL; (void) pthread_mutex_unlock(&nnp->rn_lock); } np->rn_flags |= RC_NODE_OLD; (void) pthread_mutex_unlock(&np->rn_lock); /* * replace np with nnp */ rc_node_relink_child(pp, np, nnp); rc_node_rele(np); smf_audit_event(event_id, ADT_SUCCESS, ADT_SUCCESS, &audit_data); rc = REP_PROTOCOL_SUCCESS; cleanout: free(audit_data.ed_auth); free(audit_data.ed_fmri); free(audit_data.ed_snapname); return (rc); fail: rc_node_rele_flag(np, RC_NODE_IN_TX); rc_node_rele_locked(np); (void) pthread_mutex_lock(&pp->rn_lock); rc_node_rele_flag(pp, RC_NODE_CHILDREN_CHANGING); (void) pthread_mutex_unlock(&pp->rn_lock); if (nnp != NULL) { if (prev == NULL) rc_node_destroy(nnp); else rc_node_rele(nnp); } free(audit_data.ed_auth); free(audit_data.ed_fmri); free(audit_data.ed_snapname); return (rc); } int rc_snapshot_take_new(rc_node_ptr_t *npp, const char *svcname, const char *instname, const char *name, rc_node_ptr_t *outpp) { perm_status_t granted; rc_node_t *np; rc_node_t *outp = NULL; int rc, perm_rc; char fmri[REP_PROTOCOL_FMRI_LEN]; audit_event_data_t audit_data; size_t sz_out; rc_node_clear(outpp, 0); /* * rc_node_modify_permission_check() must be called before the node * is locked. This is because the library functions that check * authorizations can trigger calls back into configd. */ granted = rc_node_modify_permission_check(&audit_data.ed_auth); switch (granted) { case PERM_DENIED: /* * We continue in this case, so that we can generate an * audit event later in this function. */ perm_rc = REP_PROTOCOL_FAIL_PERMISSION_DENIED; break; case PERM_GRANTED: perm_rc = REP_PROTOCOL_SUCCESS; break; case PERM_GONE: /* No need to produce audit event if client is gone. */ return (REP_PROTOCOL_FAIL_PERMISSION_DENIED); case PERM_FAIL: return (REP_PROTOCOL_FAIL_NO_RESOURCES); default: bad_error("rc_node_modify_permission_check", granted); break; } RC_NODE_PTR_CHECK_LOCK_OR_FREE_RETURN(np, npp, audit_data.ed_auth); if (np->rn_id.rl_type != REP_PROTOCOL_ENTITY_INSTANCE) { (void) pthread_mutex_unlock(&np->rn_lock); free(audit_data.ed_auth); return (REP_PROTOCOL_FAIL_TYPE_MISMATCH); } rc = rc_check_type_name(REP_PROTOCOL_ENTITY_SNAPSHOT, name); if (rc != REP_PROTOCOL_SUCCESS) { (void) pthread_mutex_unlock(&np->rn_lock); free(audit_data.ed_auth); return (rc); } if (svcname != NULL && (rc = rc_check_type_name(REP_PROTOCOL_ENTITY_SERVICE, svcname)) != REP_PROTOCOL_SUCCESS) { (void) pthread_mutex_unlock(&np->rn_lock); free(audit_data.ed_auth); return (rc); } if (instname != NULL && (rc = rc_check_type_name(REP_PROTOCOL_ENTITY_INSTANCE, instname)) != REP_PROTOCOL_SUCCESS) { (void) pthread_mutex_unlock(&np->rn_lock); free(audit_data.ed_auth); return (rc); } audit_data.ed_fmri = fmri; audit_data.ed_snapname = (char *)name; if ((rc = rc_node_get_fmri_or_fragment(np, fmri, sizeof (fmri), &sz_out)) != REP_PROTOCOL_SUCCESS) { (void) pthread_mutex_unlock(&np->rn_lock); free(audit_data.ed_auth); return (rc); } if (perm_rc != REP_PROTOCOL_SUCCESS) { (void) pthread_mutex_unlock(&np->rn_lock); smf_audit_event(ADT_smf_create_snap, ADT_FAILURE, ADT_FAIL_VALUE_AUTH, &audit_data); free(audit_data.ed_auth); return (perm_rc); } HOLD_PTR_FLAG_OR_FREE_AND_RETURN(np, npp, RC_NODE_CREATING_CHILD, audit_data.ed_auth); (void) pthread_mutex_unlock(&np->rn_lock); rc = object_snapshot_take_new(np, svcname, instname, name, &outp); if (rc == REP_PROTOCOL_SUCCESS) { rc_node_assign(outpp, outp); rc_node_rele(outp); } (void) pthread_mutex_lock(&np->rn_lock); rc_node_rele_flag(np, RC_NODE_CREATING_CHILD); (void) pthread_mutex_unlock(&np->rn_lock); if (rc == REP_PROTOCOL_SUCCESS) { smf_audit_event(ADT_smf_create_snap, ADT_SUCCESS, ADT_SUCCESS, &audit_data); } if (audit_data.ed_auth != NULL) free(audit_data.ed_auth); return (rc); } int rc_snapshot_take_attach(rc_node_ptr_t *npp, rc_node_ptr_t *outpp) { rc_node_t *np, *outp; RC_NODE_PTR_GET_CHECK(np, npp); if (np->rn_id.rl_type != REP_PROTOCOL_ENTITY_INSTANCE) { return (REP_PROTOCOL_FAIL_TYPE_MISMATCH); } RC_NODE_PTR_GET_CHECK_AND_LOCK(outp, outpp); if (outp->rn_id.rl_type != REP_PROTOCOL_ENTITY_SNAPSHOT) { (void) pthread_mutex_unlock(&outp->rn_lock); return (REP_PROTOCOL_FAIL_BAD_REQUEST); } return (rc_attach_snapshot(outp, 0, np, NULL, NULL)); /* drops outp's lock */ } int rc_snapshot_attach(rc_node_ptr_t *npp, rc_node_ptr_t *cpp) { rc_node_t *np; rc_node_t *cp; uint32_t snapid; char old_name[REP_PROTOCOL_NAME_LEN]; int rc; size_t sz_out; char old_fmri[REP_PROTOCOL_FMRI_LEN]; RC_NODE_PTR_GET_CHECK_AND_LOCK(np, npp); if (np->rn_id.rl_type != REP_PROTOCOL_ENTITY_SNAPSHOT) { (void) pthread_mutex_unlock(&np->rn_lock); return (REP_PROTOCOL_FAIL_BAD_REQUEST); } snapid = np->rn_snapshot_id; rc = rc_node_get_fmri_or_fragment(np, old_fmri, sizeof (old_fmri), &sz_out); (void) pthread_mutex_unlock(&np->rn_lock); if (rc != REP_PROTOCOL_SUCCESS) return (rc); if (np->rn_name != NULL) { if (strlcpy(old_name, np->rn_name, sizeof (old_name)) >= sizeof (old_name)) { return (REP_PROTOCOL_FAIL_TRUNCATED); } } RC_NODE_PTR_GET_CHECK_AND_LOCK(cp, cpp); if (cp->rn_id.rl_type != REP_PROTOCOL_ENTITY_SNAPSHOT) { (void) pthread_mutex_unlock(&cp->rn_lock); return (REP_PROTOCOL_FAIL_BAD_REQUEST); } rc = rc_attach_snapshot(cp, snapid, NULL, old_fmri, old_name); /* drops cp's lock */ return (rc); } /* * If the pgname property group under ent has type pgtype, and it has a * propname property with type ptype, return _SUCCESS. If pgtype is NULL, * it is not checked. If ent is not a service node, we will return _SUCCESS if * a property meeting the requirements exists in either the instance or its * parent. * * Returns * _SUCCESS - see above * _DELETED - ent or one of its ancestors was deleted * _NO_RESOURCES - no resources * _NOT_FOUND - no matching property was found */ static int rc_svc_prop_exists(rc_node_t *ent, const char *pgname, const char *pgtype, const char *propname, rep_protocol_value_type_t ptype) { int ret; rc_node_t *pg = NULL, *spg = NULL, *svc, *prop; assert(!MUTEX_HELD(&ent->rn_lock)); (void) pthread_mutex_lock(&ent->rn_lock); ret = rc_node_find_named_child(ent, pgname, REP_PROTOCOL_ENTITY_PROPERTYGRP, &pg); (void) pthread_mutex_unlock(&ent->rn_lock); switch (ret) { case REP_PROTOCOL_SUCCESS: break; case REP_PROTOCOL_FAIL_DELETED: case REP_PROTOCOL_FAIL_NO_RESOURCES: return (ret); default: bad_error("rc_node_find_named_child", ret); } if (ent->rn_id.rl_type != REP_PROTOCOL_ENTITY_SERVICE) { ret = rc_node_find_ancestor(ent, REP_PROTOCOL_ENTITY_SERVICE, &svc); if (ret != REP_PROTOCOL_SUCCESS) { assert(ret == REP_PROTOCOL_FAIL_DELETED); if (pg != NULL) rc_node_rele(pg); return (ret); } assert(svc->rn_id.rl_type == REP_PROTOCOL_ENTITY_SERVICE); (void) pthread_mutex_lock(&svc->rn_lock); ret = rc_node_find_named_child(svc, pgname, REP_PROTOCOL_ENTITY_PROPERTYGRP, &spg); (void) pthread_mutex_unlock(&svc->rn_lock); rc_node_rele(svc); switch (ret) { case REP_PROTOCOL_SUCCESS: break; case REP_PROTOCOL_FAIL_DELETED: case REP_PROTOCOL_FAIL_NO_RESOURCES: if (pg != NULL) rc_node_rele(pg); return (ret); default: bad_error("rc_node_find_named_child", ret); } } if (pg != NULL && pgtype != NULL && strcmp(pg->rn_type, pgtype) != 0) { rc_node_rele(pg); pg = NULL; } if (spg != NULL && pgtype != NULL && strcmp(spg->rn_type, pgtype) != 0) { rc_node_rele(spg); spg = NULL; } if (pg == NULL) { if (spg == NULL) return (REP_PROTOCOL_FAIL_NOT_FOUND); pg = spg; spg = NULL; } /* * At this point, pg is non-NULL, and is a property group node of the * correct type. spg, if non-NULL, is also a property group node of * the correct type. Check for the property in pg first, then spg * (if applicable). */ (void) pthread_mutex_lock(&pg->rn_lock); ret = rc_node_find_named_child(pg, propname, REP_PROTOCOL_ENTITY_PROPERTY, &prop); (void) pthread_mutex_unlock(&pg->rn_lock); rc_node_rele(pg); switch (ret) { case REP_PROTOCOL_SUCCESS: if (prop != NULL) { if (prop->rn_valtype == ptype) { rc_node_rele(prop); if (spg != NULL) rc_node_rele(spg); return (REP_PROTOCOL_SUCCESS); } rc_node_rele(prop); } break; case REP_PROTOCOL_FAIL_NO_RESOURCES: if (spg != NULL) rc_node_rele(spg); return (ret); case REP_PROTOCOL_FAIL_DELETED: break; default: bad_error("rc_node_find_named_child", ret); } if (spg == NULL) return (REP_PROTOCOL_FAIL_NOT_FOUND); pg = spg; (void) pthread_mutex_lock(&pg->rn_lock); ret = rc_node_find_named_child(pg, propname, REP_PROTOCOL_ENTITY_PROPERTY, &prop); (void) pthread_mutex_unlock(&pg->rn_lock); rc_node_rele(pg); switch (ret) { case REP_PROTOCOL_SUCCESS: if (prop != NULL) { if (prop->rn_valtype == ptype) { rc_node_rele(prop); return (REP_PROTOCOL_SUCCESS); } rc_node_rele(prop); } return (REP_PROTOCOL_FAIL_NOT_FOUND); case REP_PROTOCOL_FAIL_NO_RESOURCES: return (ret); case REP_PROTOCOL_FAIL_DELETED: return (REP_PROTOCOL_FAIL_NOT_FOUND); default: bad_error("rc_node_find_named_child", ret); } return (REP_PROTOCOL_SUCCESS); } /* * Given a property group node, returns _SUCCESS if the property group may * be read without any special authorization. * * Fails with: * _DELETED - np or an ancestor node was deleted * _TYPE_MISMATCH - np does not refer to a property group * _NO_RESOURCES - no resources * _PERMISSION_DENIED - authorization is required */ static int rc_node_pg_check_read_protect(rc_node_t *np) { int ret; rc_node_t *ent; assert(!MUTEX_HELD(&np->rn_lock)); if (np->rn_id.rl_type != REP_PROTOCOL_ENTITY_PROPERTYGRP) return (REP_PROTOCOL_FAIL_TYPE_MISMATCH); if (strcmp(np->rn_type, SCF_GROUP_FRAMEWORK) == 0 || strcmp(np->rn_type, SCF_GROUP_DEPENDENCY) == 0 || strcmp(np->rn_type, SCF_GROUP_METHOD) == 0) return (REP_PROTOCOL_SUCCESS); ret = rc_node_parent(np, &ent); if (ret != REP_PROTOCOL_SUCCESS) return (ret); ret = rc_svc_prop_exists(ent, np->rn_name, np->rn_type, AUTH_PROP_READ, REP_PROTOCOL_TYPE_STRING); rc_node_rele(ent); switch (ret) { case REP_PROTOCOL_FAIL_NOT_FOUND: return (REP_PROTOCOL_SUCCESS); case REP_PROTOCOL_SUCCESS: return (REP_PROTOCOL_FAIL_PERMISSION_DENIED); case REP_PROTOCOL_FAIL_DELETED: case REP_PROTOCOL_FAIL_NO_RESOURCES: return (ret); default: bad_error("rc_svc_prop_exists", ret); } return (REP_PROTOCOL_SUCCESS); } /* * Fails with * _DELETED - np's node or parent has been deleted * _TYPE_MISMATCH - np's node is not a property * _NO_RESOURCES - out of memory * _PERMISSION_DENIED - no authorization to read this property's value(s) * _BAD_REQUEST - np's parent is not a property group */ static int rc_node_property_may_read(rc_node_t *np) { int ret; perm_status_t granted = PERM_DENIED; rc_node_t *pgp; permcheck_t *pcp; audit_event_data_t audit_data; size_t sz_out; if (np->rn_id.rl_type != REP_PROTOCOL_ENTITY_PROPERTY) return (REP_PROTOCOL_FAIL_TYPE_MISMATCH); if (client_is_privileged()) return (REP_PROTOCOL_SUCCESS); #ifdef NATIVE_BUILD return (REP_PROTOCOL_FAIL_PERMISSION_DENIED); #else ret = rc_node_parent(np, &pgp); if (ret != REP_PROTOCOL_SUCCESS) return (ret); if (pgp->rn_id.rl_type != REP_PROTOCOL_ENTITY_PROPERTYGRP) { rc_node_rele(pgp); return (REP_PROTOCOL_FAIL_BAD_REQUEST); } ret = rc_node_pg_check_read_protect(pgp); if (ret != REP_PROTOCOL_FAIL_PERMISSION_DENIED) { rc_node_rele(pgp); return (ret); } pcp = pc_create(); if (pcp == NULL) { rc_node_rele(pgp); return (REP_PROTOCOL_FAIL_NO_RESOURCES); } ret = perm_add_enabling(pcp, AUTH_MODIFY); if (ret == REP_PROTOCOL_SUCCESS) { const char * const auth = perm_auth_for_pgtype(pgp->rn_type); if (auth != NULL) ret = perm_add_enabling(pcp, auth); } /* * If you are permitted to modify the value, you may also * read it. This means that both the MODIFY and VALUE * authorizations are acceptable. We don't allow requests * for AUTH_PROP_MODIFY if all you have is $AUTH_PROP_VALUE, * however, to avoid leaking possibly valuable information * since such a user can't change the property anyway. */ if (ret == REP_PROTOCOL_SUCCESS) ret = perm_add_enabling_values(pcp, pgp, AUTH_PROP_MODIFY); if (ret == REP_PROTOCOL_SUCCESS && strcmp(np->rn_name, AUTH_PROP_MODIFY) != 0) ret = perm_add_enabling_values(pcp, pgp, AUTH_PROP_VALUE); if (ret == REP_PROTOCOL_SUCCESS) ret = perm_add_enabling_values(pcp, pgp, AUTH_PROP_READ); rc_node_rele(pgp); if (ret == REP_PROTOCOL_SUCCESS) { granted = perm_granted(pcp); if (granted == PERM_FAIL) ret = REP_PROTOCOL_FAIL_NO_RESOURCES; if (granted == PERM_GONE) ret = REP_PROTOCOL_FAIL_PERMISSION_DENIED; } if (ret == REP_PROTOCOL_SUCCESS) { /* Generate a read_prop audit event. */ audit_data.ed_fmri = malloc(REP_PROTOCOL_FMRI_LEN); if (audit_data.ed_fmri == NULL) ret = REP_PROTOCOL_FAIL_NO_RESOURCES; } if (ret == REP_PROTOCOL_SUCCESS) { ret = rc_node_get_fmri_or_fragment(np, audit_data.ed_fmri, REP_PROTOCOL_FMRI_LEN, &sz_out); } if (ret == REP_PROTOCOL_SUCCESS) { int status; int ret_value; if (granted == PERM_DENIED) { status = ADT_FAILURE; ret_value = ADT_FAIL_VALUE_AUTH; } else { status = ADT_SUCCESS; ret_value = ADT_SUCCESS; } audit_data.ed_auth = pcp->pc_auth_string; smf_audit_event(ADT_smf_read_prop, status, ret_value, &audit_data); } free(audit_data.ed_fmri); pc_free(pcp); if ((ret == REP_PROTOCOL_SUCCESS) && (granted == PERM_DENIED)) ret = REP_PROTOCOL_FAIL_PERMISSION_DENIED; return (ret); #endif /* NATIVE_BUILD */ } /* * Iteration */ static int rc_iter_filter_name(rc_node_t *np, void *s) { const char *name = s; return (strcmp(np->rn_name, name) == 0); } static int rc_iter_filter_type(rc_node_t *np, void *s) { const char *type = s; return (np->rn_type != NULL && strcmp(np->rn_type, type) == 0); } /*ARGSUSED*/ static int rc_iter_null_filter(rc_node_t *np, void *s) { return (1); } /* * Allocate & initialize an rc_node_iter_t structure. Essentially, ensure * np->rn_children is populated and call uu_list_walk_start(np->rn_children). * If successful, leaves a hold on np & increments np->rn_other_refs * * If composed is true, then set up for iteration across the top level of np's * composition chain. If successful, leaves a hold on np and increments * rn_other_refs for the top level of np's composition chain. * * Fails with * _NO_RESOURCES * _INVALID_TYPE * _TYPE_MISMATCH - np cannot carry type children * _DELETED */ static int rc_iter_create(rc_node_iter_t **resp, rc_node_t *np, uint32_t type, rc_iter_filter_func *filter, void *arg, boolean_t composed) { rc_node_iter_t *nip; int res; assert(*resp == NULL); nip = uu_zalloc(sizeof (*nip)); if (nip == NULL) return (REP_PROTOCOL_FAIL_NO_RESOURCES); /* np is held by the client's rc_node_ptr_t */ if (np->rn_id.rl_type == REP_PROTOCOL_ENTITY_CPROPERTYGRP) composed = 1; if (!composed) { (void) pthread_mutex_lock(&np->rn_lock); if ((res = rc_node_fill_children(np, type)) != REP_PROTOCOL_SUCCESS) { (void) pthread_mutex_unlock(&np->rn_lock); uu_free(nip); return (res); } nip->rni_clevel = -1; nip->rni_iter = uu_list_walk_start(np->rn_children, UU_WALK_ROBUST); if (nip->rni_iter != NULL) { nip->rni_iter_node = np; rc_node_hold_other(np); } else { (void) pthread_mutex_unlock(&np->rn_lock); uu_free(nip); return (REP_PROTOCOL_FAIL_NO_RESOURCES); } (void) pthread_mutex_unlock(&np->rn_lock); } else { rc_node_t *ent; if (np->rn_id.rl_type == REP_PROTOCOL_ENTITY_SNAPSHOT) { /* rn_cchain isn't valid until children are loaded. */ (void) pthread_mutex_lock(&np->rn_lock); res = rc_node_fill_children(np, REP_PROTOCOL_ENTITY_SNAPLEVEL); (void) pthread_mutex_unlock(&np->rn_lock); if (res != REP_PROTOCOL_SUCCESS) { uu_free(nip); return (res); } /* Check for an empty snapshot. */ if (np->rn_cchain[0] == NULL) goto empty; } /* Start at the top of the composition chain. */ for (nip->rni_clevel = 0; ; ++nip->rni_clevel) { if (nip->rni_clevel >= COMPOSITION_DEPTH) { /* Empty composition chain. */ empty: nip->rni_clevel = -1; nip->rni_iter = NULL; /* It's ok, iter_next() will return _DONE. */ goto out; } ent = np->rn_cchain[nip->rni_clevel]; assert(ent != NULL); if (rc_node_check_and_lock(ent) == REP_PROTOCOL_SUCCESS) break; /* Someone deleted it, so try the next one. */ } res = rc_node_fill_children(ent, type); if (res == REP_PROTOCOL_SUCCESS) { nip->rni_iter = uu_list_walk_start(ent->rn_children, UU_WALK_ROBUST); if (nip->rni_iter == NULL) res = REP_PROTOCOL_FAIL_NO_RESOURCES; else { nip->rni_iter_node = ent; rc_node_hold_other(ent); } } if (res != REP_PROTOCOL_SUCCESS) { (void) pthread_mutex_unlock(&ent->rn_lock); uu_free(nip); return (res); } (void) pthread_mutex_unlock(&ent->rn_lock); } out: rc_node_hold(np); /* released by rc_iter_end() */ nip->rni_parent = np; nip->rni_type = type; nip->rni_filter = (filter != NULL)? filter : rc_iter_null_filter; nip->rni_filter_arg = arg; *resp = nip; return (REP_PROTOCOL_SUCCESS); } static void rc_iter_end(rc_node_iter_t *iter) { rc_node_t *np = iter->rni_parent; if (iter->rni_clevel >= 0) np = np->rn_cchain[iter->rni_clevel]; assert(MUTEX_HELD(&np->rn_lock)); if (iter->rni_iter != NULL) uu_list_walk_end(iter->rni_iter); iter->rni_iter = NULL; (void) pthread_mutex_unlock(&np->rn_lock); rc_node_rele(iter->rni_parent); if (iter->rni_iter_node != NULL) rc_node_rele_other(iter->rni_iter_node); } /* * Fails with * _NOT_SET - npp is reset * _DELETED - npp's node has been deleted * _NOT_APPLICABLE - npp's node is not a property * _NO_RESOURCES - out of memory */ static int rc_node_setup_value_iter(rc_node_ptr_t *npp, rc_node_iter_t **iterp) { rc_node_t *np; rc_node_iter_t *nip; assert(*iterp == NULL); RC_NODE_PTR_GET_CHECK_AND_LOCK(np, npp); if (np->rn_id.rl_type != REP_PROTOCOL_ENTITY_PROPERTY) { (void) pthread_mutex_unlock(&np->rn_lock); return (REP_PROTOCOL_FAIL_NOT_APPLICABLE); } nip = uu_zalloc(sizeof (*nip)); if (nip == NULL) { (void) pthread_mutex_unlock(&np->rn_lock); return (REP_PROTOCOL_FAIL_NO_RESOURCES); } nip->rni_parent = np; nip->rni_iter = NULL; nip->rni_clevel = -1; nip->rni_type = REP_PROTOCOL_ENTITY_VALUE; nip->rni_offset = 0; nip->rni_last_offset = 0; rc_node_hold_locked(np); *iterp = nip; (void) pthread_mutex_unlock(&np->rn_lock); return (REP_PROTOCOL_SUCCESS); } /* * Returns: * _NO_RESOURCES - out of memory * _NOT_SET - npp is reset * _DELETED - npp's node has been deleted * _TYPE_MISMATCH - npp's node is not a property * _NOT_FOUND - property has no values * _TRUNCATED - property has >1 values (first is written into out) * _SUCCESS - property has 1 value (which is written into out) * _PERMISSION_DENIED - no authorization to read property value(s) * * We shorten *sz_out to not include anything after the final '\0'. */ int rc_node_get_property_value(rc_node_ptr_t *npp, struct rep_protocol_value_response *out, size_t *sz_out) { rc_node_t *np; size_t w; int ret; assert(*sz_out == sizeof (*out)); RC_NODE_PTR_GET_CHECK_AND_HOLD(np, npp); ret = rc_node_property_may_read(np); rc_node_rele(np); if (ret != REP_PROTOCOL_SUCCESS) return (ret); RC_NODE_PTR_GET_CHECK_AND_LOCK(np, npp); if (np->rn_id.rl_type != REP_PROTOCOL_ENTITY_PROPERTY) { (void) pthread_mutex_unlock(&np->rn_lock); return (REP_PROTOCOL_FAIL_TYPE_MISMATCH); } if (np->rn_values_size == 0) { (void) pthread_mutex_unlock(&np->rn_lock); return (REP_PROTOCOL_FAIL_NOT_FOUND); } out->rpr_type = np->rn_valtype; w = strlcpy(out->rpr_value, &np->rn_values[0], sizeof (out->rpr_value)); if (w >= sizeof (out->rpr_value)) backend_panic("value too large"); *sz_out = offsetof(struct rep_protocol_value_response, rpr_value[w + 1]); ret = (np->rn_values_count != 1)? REP_PROTOCOL_FAIL_TRUNCATED : REP_PROTOCOL_SUCCESS; (void) pthread_mutex_unlock(&np->rn_lock); return (ret); } int rc_iter_next_value(rc_node_iter_t *iter, struct rep_protocol_value_response *out, size_t *sz_out, int repeat) { rc_node_t *np = iter->rni_parent; const char *vals; size_t len; size_t start; size_t w; int ret; rep_protocol_responseid_t result; assert(*sz_out == sizeof (*out)); (void) memset(out, '\0', *sz_out); if (iter->rni_type != REP_PROTOCOL_ENTITY_VALUE) return (REP_PROTOCOL_FAIL_BAD_REQUEST); RC_NODE_CHECK(np); ret = rc_node_property_may_read(np); if (ret != REP_PROTOCOL_SUCCESS) return (ret); RC_NODE_CHECK_AND_LOCK(np); vals = np->rn_values; len = np->rn_values_size; out->rpr_type = np->rn_valtype; start = (repeat)? iter->rni_last_offset : iter->rni_offset; if (len == 0 || start >= len) { result = REP_PROTOCOL_DONE; *sz_out -= sizeof (out->rpr_value); } else { w = strlcpy(out->rpr_value, &vals[start], sizeof (out->rpr_value)); if (w >= sizeof (out->rpr_value)) backend_panic("value too large"); *sz_out = offsetof(struct rep_protocol_value_response, rpr_value[w + 1]); /* * update the offsets if we're not repeating */ if (!repeat) { iter->rni_last_offset = iter->rni_offset; iter->rni_offset += (w + 1); } result = REP_PROTOCOL_SUCCESS; } (void) pthread_mutex_unlock(&np->rn_lock); return (result); } /* * Entry point for ITER_START from client.c. Validate the arguments & call * rc_iter_create(). * * Fails with * _NOT_SET * _DELETED * _TYPE_MISMATCH - np cannot carry type children * _BAD_REQUEST - flags is invalid * pattern is invalid * _NO_RESOURCES * _INVALID_TYPE * _TYPE_MISMATCH - *npp cannot have children of type * _BACKEND_ACCESS */ int rc_node_setup_iter(rc_node_ptr_t *npp, rc_node_iter_t **iterp, uint32_t type, uint32_t flags, const char *pattern) { rc_node_t *np; rc_iter_filter_func *f = NULL; int rc; RC_NODE_PTR_GET_CHECK(np, npp); if (pattern != NULL && pattern[0] == '\0') pattern = NULL; if (type == REP_PROTOCOL_ENTITY_VALUE) { if (np->rn_id.rl_type != REP_PROTOCOL_ENTITY_PROPERTY) return (REP_PROTOCOL_FAIL_TYPE_MISMATCH); if (flags != RP_ITER_START_ALL || pattern != NULL) return (REP_PROTOCOL_FAIL_BAD_REQUEST); rc = rc_node_setup_value_iter(npp, iterp); assert(rc != REP_PROTOCOL_FAIL_NOT_APPLICABLE); return (rc); } if ((rc = rc_check_parent_child(np->rn_id.rl_type, type)) != REP_PROTOCOL_SUCCESS) return (rc); if (((flags & RP_ITER_START_FILT_MASK) == RP_ITER_START_ALL) ^ (pattern == NULL)) return (REP_PROTOCOL_FAIL_BAD_REQUEST); /* Composition only works for instances & snapshots. */ if ((flags & RP_ITER_START_COMPOSED) && (np->rn_id.rl_type != REP_PROTOCOL_ENTITY_INSTANCE && np->rn_id.rl_type != REP_PROTOCOL_ENTITY_SNAPSHOT)) return (REP_PROTOCOL_FAIL_BAD_REQUEST); if (pattern != NULL) { if ((rc = rc_check_type_name(type, pattern)) != REP_PROTOCOL_SUCCESS) return (rc); pattern = strdup(pattern); if (pattern == NULL) return (REP_PROTOCOL_FAIL_NO_RESOURCES); } switch (flags & RP_ITER_START_FILT_MASK) { case RP_ITER_START_ALL: f = NULL; break; case RP_ITER_START_EXACT: f = rc_iter_filter_name; break; case RP_ITER_START_PGTYPE: if (type != REP_PROTOCOL_ENTITY_PROPERTYGRP) { free((void *)pattern); return (REP_PROTOCOL_FAIL_BAD_REQUEST); } f = rc_iter_filter_type; break; default: free((void *)pattern); return (REP_PROTOCOL_FAIL_BAD_REQUEST); } rc = rc_iter_create(iterp, np, type, f, (void *)pattern, flags & RP_ITER_START_COMPOSED); if (rc != REP_PROTOCOL_SUCCESS && pattern != NULL) free((void *)pattern); return (rc); } /* * Do uu_list_walk_next(iter->rni_iter) until we find a child which matches * the filter. * For composed iterators, then check to see if there's an overlapping entity * (see embedded comments). If we reach the end of the list, start over at * the next level. * * Returns * _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 */ int rc_iter_next(rc_node_iter_t *iter, rc_node_ptr_t *out, uint32_t type) { rc_node_t *np = iter->rni_parent; rc_node_t *res; int rc; if (iter->rni_type == REP_PROTOCOL_ENTITY_VALUE) return (REP_PROTOCOL_FAIL_BAD_REQUEST); if (iter->rni_iter == NULL) { rc_node_clear(out, 0); return (REP_PROTOCOL_DONE); } if (iter->rni_type != type) { rc_node_clear(out, 0); return (REP_PROTOCOL_FAIL_TYPE_MISMATCH); } (void) pthread_mutex_lock(&np->rn_lock); /* held by _iter_create() */ if (!rc_node_wait_flag(np, RC_NODE_CHILDREN_CHANGING)) { (void) pthread_mutex_unlock(&np->rn_lock); rc_node_clear(out, 1); return (REP_PROTOCOL_FAIL_DELETED); } if (iter->rni_clevel >= 0) { /* Composed iterator. Iterate over appropriate level. */ (void) pthread_mutex_unlock(&np->rn_lock); np = np->rn_cchain[iter->rni_clevel]; /* * If iter->rni_parent is an instance or a snapshot, np must * be valid since iter holds iter->rni_parent & possible * levels (service, instance, snaplevel) cannot be destroyed * while rni_parent is held. If iter->rni_parent is * a composed property group then rc_node_setup_cpg() put * a hold on np. */ (void) pthread_mutex_lock(&np->rn_lock); if (!rc_node_wait_flag(np, RC_NODE_CHILDREN_CHANGING)) { (void) pthread_mutex_unlock(&np->rn_lock); rc_node_clear(out, 1); return (REP_PROTOCOL_FAIL_DELETED); } } assert(np->rn_flags & RC_NODE_HAS_CHILDREN); for (;;) { res = uu_list_walk_next(iter->rni_iter); if (res == NULL) { rc_node_t *parent = iter->rni_parent; #if COMPOSITION_DEPTH == 2 if (iter->rni_clevel < 0 || iter->rni_clevel == 1) { /* release walker and lock */ rc_iter_end(iter); break; } /* Stop walking current level. */ uu_list_walk_end(iter->rni_iter); iter->rni_iter = NULL; (void) pthread_mutex_unlock(&np->rn_lock); rc_node_rele_other(iter->rni_iter_node); iter->rni_iter_node = NULL; /* Start walking next level. */ ++iter->rni_clevel; np = parent->rn_cchain[iter->rni_clevel]; assert(np != NULL); #else #error This code must be updated. #endif (void) pthread_mutex_lock(&np->rn_lock); rc = rc_node_fill_children(np, iter->rni_type); if (rc == REP_PROTOCOL_SUCCESS) { iter->rni_iter = uu_list_walk_start(np->rn_children, UU_WALK_ROBUST); if (iter->rni_iter == NULL) rc = REP_PROTOCOL_FAIL_NO_RESOURCES; else { iter->rni_iter_node = np; rc_node_hold_other(np); } } if (rc != REP_PROTOCOL_SUCCESS) { (void) pthread_mutex_unlock(&np->rn_lock); rc_node_clear(out, 0); return (rc); } continue; } if (res->rn_id.rl_type != type || !iter->rni_filter(res, iter->rni_filter_arg)) continue; /* * If we're composed and not at the top level, check to see if * there's an entity at a higher level with the same name. If * so, skip this one. */ if (iter->rni_clevel > 0) { rc_node_t *ent = iter->rni_parent->rn_cchain[0]; rc_node_t *pg; #if COMPOSITION_DEPTH == 2 assert(iter->rni_clevel == 1); (void) pthread_mutex_unlock(&np->rn_lock); (void) pthread_mutex_lock(&ent->rn_lock); rc = rc_node_find_named_child(ent, res->rn_name, type, &pg); if (rc == REP_PROTOCOL_SUCCESS && pg != NULL) rc_node_rele(pg); (void) pthread_mutex_unlock(&ent->rn_lock); if (rc != REP_PROTOCOL_SUCCESS) { rc_node_clear(out, 0); return (rc); } (void) pthread_mutex_lock(&np->rn_lock); /* Make sure np isn't being deleted all of a sudden. */ if (!rc_node_wait_flag(np, RC_NODE_DYING)) { (void) pthread_mutex_unlock(&np->rn_lock); rc_node_clear(out, 1); return (REP_PROTOCOL_FAIL_DELETED); } if (pg != NULL) /* Keep going. */ continue; #else #error This code must be updated. #endif } /* * If we're composed, iterating over property groups, and not * at the bottom level, check to see if there's a pg at lower * level with the same name. If so, return a cpg. */ if (iter->rni_clevel >= 0 && type == REP_PROTOCOL_ENTITY_PROPERTYGRP && iter->rni_clevel < COMPOSITION_DEPTH - 1) { #if COMPOSITION_DEPTH == 2 rc_node_t *pg; rc_node_t *ent = iter->rni_parent->rn_cchain[1]; rc_node_hold(res); /* While we drop np->rn_lock */ (void) pthread_mutex_unlock(&np->rn_lock); (void) pthread_mutex_lock(&ent->rn_lock); rc = rc_node_find_named_child(ent, res->rn_name, type, &pg); /* holds pg if not NULL */ (void) pthread_mutex_unlock(&ent->rn_lock); if (rc != REP_PROTOCOL_SUCCESS) { rc_node_rele(res); rc_node_clear(out, 0); return (rc); } (void) pthread_mutex_lock(&np->rn_lock); if (!rc_node_wait_flag(np, RC_NODE_DYING)) { (void) pthread_mutex_unlock(&np->rn_lock); rc_node_rele(res); if (pg != NULL) rc_node_rele(pg); rc_node_clear(out, 1); return (REP_PROTOCOL_FAIL_DELETED); } if (pg == NULL) { (void) pthread_mutex_unlock(&np->rn_lock); rc_node_rele(res); (void) pthread_mutex_lock(&np->rn_lock); if (!rc_node_wait_flag(np, RC_NODE_DYING)) { (void) pthread_mutex_unlock(&np-> rn_lock); rc_node_clear(out, 1); return (REP_PROTOCOL_FAIL_DELETED); } } else { rc_node_t *cpg; /* Keep res held for rc_node_setup_cpg(). */ cpg = rc_node_alloc(); if (cpg == NULL) { (void) pthread_mutex_unlock( &np->rn_lock); rc_node_rele(res); rc_node_rele(pg); rc_node_clear(out, 0); return (REP_PROTOCOL_FAIL_NO_RESOURCES); } switch (rc_node_setup_cpg(cpg, res, pg)) { case REP_PROTOCOL_SUCCESS: res = cpg; break; case REP_PROTOCOL_FAIL_TYPE_MISMATCH: /* Nevermind. */ (void) pthread_mutex_unlock(&np-> rn_lock); rc_node_destroy(cpg); rc_node_rele(pg); rc_node_rele(res); (void) pthread_mutex_lock(&np-> rn_lock); if (!rc_node_wait_flag(np, RC_NODE_DYING)) { (void) pthread_mutex_unlock(& np->rn_lock); rc_node_clear(out, 1); return (REP_PROTOCOL_FAIL_DELETED); } break; case REP_PROTOCOL_FAIL_NO_RESOURCES: rc_node_destroy(cpg); (void) pthread_mutex_unlock( &np->rn_lock); rc_node_rele(res); rc_node_rele(pg); rc_node_clear(out, 0); return (REP_PROTOCOL_FAIL_NO_RESOURCES); default: assert(0); abort(); } } #else #error This code must be updated. #endif } rc_node_hold(res); (void) pthread_mutex_unlock(&np->rn_lock); break; } rc_node_assign(out, res); if (res == NULL) return (REP_PROTOCOL_DONE); rc_node_rele(res); return (REP_PROTOCOL_SUCCESS); } void rc_iter_destroy(rc_node_iter_t **nipp) { rc_node_iter_t *nip = *nipp; rc_node_t *np; if (nip == NULL) return; /* already freed */ np = nip->rni_parent; if (nip->rni_filter_arg != NULL) free(nip->rni_filter_arg); nip->rni_filter_arg = NULL; if (nip->rni_type == REP_PROTOCOL_ENTITY_VALUE || nip->rni_iter != NULL) { if (nip->rni_clevel < 0) (void) pthread_mutex_lock(&np->rn_lock); else (void) pthread_mutex_lock( &np->rn_cchain[nip->rni_clevel]->rn_lock); rc_iter_end(nip); /* release walker and lock */ } nip->rni_parent = NULL; uu_free(nip); *nipp = NULL; } int rc_node_setup_tx(rc_node_ptr_t *npp, rc_node_ptr_t *txp) { rc_node_t *np; permcheck_t *pcp; int ret; perm_status_t granted; rc_auth_state_t authorized = RC_AUTH_UNKNOWN; char *auth_string = NULL; RC_NODE_PTR_GET_CHECK_AND_HOLD(np, npp); if (np->rn_id.rl_type == REP_PROTOCOL_ENTITY_CPROPERTYGRP) { rc_node_rele(np); np = np->rn_cchain[0]; RC_NODE_CHECK_AND_HOLD(np); } if (np->rn_id.rl_type != REP_PROTOCOL_ENTITY_PROPERTYGRP) { rc_node_rele(np); return (REP_PROTOCOL_FAIL_TYPE_MISMATCH); } if (np->rn_id.rl_ids[ID_SNAPSHOT] != 0) { rc_node_rele(np); return (REP_PROTOCOL_FAIL_PERMISSION_DENIED); } #ifdef NATIVE_BUILD if (client_is_privileged()) goto skip_checks; rc_node_rele(np); return (REP_PROTOCOL_FAIL_PERMISSION_DENIED); #else if (is_main_repository == 0) goto skip_checks; /* permission check */ pcp = pc_create(); if (pcp == NULL) { rc_node_rele(np); return (REP_PROTOCOL_FAIL_NO_RESOURCES); } if (np->rn_id.rl_ids[ID_INSTANCE] != 0 && /* instance pg */ ((strcmp(np->rn_name, AUTH_PG_ACTIONS) == 0 && strcmp(np->rn_type, AUTH_PG_ACTIONS_TYPE) == 0) || (strcmp(np->rn_name, AUTH_PG_GENERAL_OVR) == 0 && strcmp(np->rn_type, AUTH_PG_GENERAL_OVR_TYPE) == 0))) { rc_node_t *instn; /* solaris.smf.modify can be used */ ret = perm_add_enabling(pcp, AUTH_MODIFY); if (ret != REP_PROTOCOL_SUCCESS) { pc_free(pcp); rc_node_rele(np); return (ret); } /* solaris.smf.manage can be used. */ ret = perm_add_enabling(pcp, AUTH_MANAGE); if (ret != REP_PROTOCOL_SUCCESS) { pc_free(pcp); rc_node_rele(np); return (ret); } /* general/action_authorization values can be used. */ ret = rc_node_parent(np, &instn); if (ret != REP_PROTOCOL_SUCCESS) { assert(ret == REP_PROTOCOL_FAIL_DELETED); rc_node_rele(np); pc_free(pcp); return (REP_PROTOCOL_FAIL_DELETED); } assert(instn->rn_id.rl_type == REP_PROTOCOL_ENTITY_INSTANCE); ret = perm_add_inst_action_auth(pcp, instn); rc_node_rele(instn); switch (ret) { case REP_PROTOCOL_SUCCESS: break; case REP_PROTOCOL_FAIL_DELETED: case REP_PROTOCOL_FAIL_NO_RESOURCES: rc_node_rele(np); pc_free(pcp); return (ret); default: bad_error("perm_add_inst_action_auth", ret); } if (strcmp(np->rn_name, AUTH_PG_ACTIONS) == 0) authorized = RC_AUTH_PASSED; /* No check on commit. */ } else { ret = perm_add_enabling(pcp, AUTH_MODIFY); if (ret == REP_PROTOCOL_SUCCESS) { /* propertygroup-type-specific authorization */ /* no locking because rn_type won't change anyway */ const char * const auth = perm_auth_for_pgtype(np->rn_type); if (auth != NULL) ret = perm_add_enabling(pcp, auth); } if (ret == REP_PROTOCOL_SUCCESS) /* propertygroup/transaction-type-specific auths */ ret = perm_add_enabling_values(pcp, np, AUTH_PROP_VALUE); if (ret == REP_PROTOCOL_SUCCESS) ret = perm_add_enabling_values(pcp, np, AUTH_PROP_MODIFY); /* AUTH_MANAGE can manipulate general/AUTH_PROP_ACTION */ if (ret == REP_PROTOCOL_SUCCESS && strcmp(np->rn_name, AUTH_PG_GENERAL) == 0 && strcmp(np->rn_type, AUTH_PG_GENERAL_TYPE) == 0) ret = perm_add_enabling(pcp, AUTH_MANAGE); if (ret != REP_PROTOCOL_SUCCESS) { pc_free(pcp); rc_node_rele(np); return (ret); } } granted = perm_granted(pcp); ret = map_granted_status(granted, pcp, &auth_string); pc_free(pcp); if ((granted == PERM_GONE) || (granted == PERM_FAIL) || (ret == REP_PROTOCOL_FAIL_NO_RESOURCES)) { free(auth_string); rc_node_rele(np); return (ret); } if (granted == PERM_DENIED) { /* * If we get here, the authorization failed. * Unfortunately, we don't have enough information at this * point to generate the security audit events. We'll only * get that information when the client tries to commit the * event. Thus, we'll remember the failed authorization, * so that we can generate the audit events later. */ authorized = RC_AUTH_FAILED; } #endif /* NATIVE_BUILD */ skip_checks: rc_node_assign(txp, np); txp->rnp_authorized = authorized; if (authorized != RC_AUTH_UNKNOWN) { /* Save the authorization string. */ if (txp->rnp_auth_string != NULL) free((void *)txp->rnp_auth_string); txp->rnp_auth_string = auth_string; auth_string = NULL; /* Don't free until done with txp. */ } rc_node_rele(np); if (auth_string != NULL) free(auth_string); return (REP_PROTOCOL_SUCCESS); } /* * Return 1 if the given transaction commands only modify the values of * properties other than "modify_authorization". Return -1 if any of the * commands are invalid, and 0 otherwise. */ static int tx_allow_value(const void *cmds_arg, size_t cmds_sz, rc_node_t *pg) { const struct rep_protocol_transaction_cmd *cmds; uintptr_t loc; uint32_t sz; rc_node_t *prop; boolean_t ok; assert(!MUTEX_HELD(&pg->rn_lock)); loc = (uintptr_t)cmds_arg; while (cmds_sz > 0) { cmds = (struct rep_protocol_transaction_cmd *)loc; if (cmds_sz <= REP_PROTOCOL_TRANSACTION_CMD_MIN_SIZE) return (-1); sz = cmds->rptc_size; if (sz <= REP_PROTOCOL_TRANSACTION_CMD_MIN_SIZE) return (-1); sz = TX_SIZE(sz); if (sz > cmds_sz) return (-1); switch (cmds[0].rptc_action) { case REP_PROTOCOL_TX_ENTRY_CLEAR: break; case REP_PROTOCOL_TX_ENTRY_REPLACE: /* Check type */ (void) pthread_mutex_lock(&pg->rn_lock); ok = B_FALSE; if (rc_node_find_named_child(pg, (const char *)cmds[0].rptc_data, REP_PROTOCOL_ENTITY_PROPERTY, &prop) == REP_PROTOCOL_SUCCESS) { if (prop != NULL) { ok = prop->rn_valtype == cmds[0].rptc_type; /* * rc_node_find_named_child() * places a hold on prop which we * do not need to hang on to. */ rc_node_rele(prop); } } (void) pthread_mutex_unlock(&pg->rn_lock); if (ok) break; return (0); default: return (0); } if (strcmp((const char *)cmds[0].rptc_data, AUTH_PROP_MODIFY) == 0) return (0); loc += sz; cmds_sz -= sz; } return (1); } /* * Return 1 if any of the given transaction commands affect * "action_authorization". Return -1 if any of the commands are invalid and * 0 in all other cases. */ static int tx_modifies_action(const void *cmds_arg, size_t cmds_sz) { const struct rep_protocol_transaction_cmd *cmds; uintptr_t loc; uint32_t sz; loc = (uintptr_t)cmds_arg; while (cmds_sz > 0) { cmds = (struct rep_protocol_transaction_cmd *)loc; if (cmds_sz <= REP_PROTOCOL_TRANSACTION_CMD_MIN_SIZE) return (-1); sz = cmds->rptc_size; if (sz <= REP_PROTOCOL_TRANSACTION_CMD_MIN_SIZE) return (-1); sz = TX_SIZE(sz); if (sz > cmds_sz) return (-1); if (strcmp((const char *)cmds[0].rptc_data, AUTH_PROP_ACTION) == 0) return (1); loc += sz; cmds_sz -= sz; } return (0); } /* * Returns 1 if the transaction commands only modify properties named * 'enabled'. */ static int tx_only_enabled(const void *cmds_arg, size_t cmds_sz) { const struct rep_protocol_transaction_cmd *cmd; uintptr_t loc; uint32_t sz; loc = (uintptr_t)cmds_arg; while (cmds_sz > 0) { cmd = (struct rep_protocol_transaction_cmd *)loc; if (cmds_sz <= REP_PROTOCOL_TRANSACTION_CMD_MIN_SIZE) return (-1); sz = cmd->rptc_size; if (sz <= REP_PROTOCOL_TRANSACTION_CMD_MIN_SIZE) return (-1); sz = TX_SIZE(sz); if (sz > cmds_sz) return (-1); if (strcmp((const char *)cmd->rptc_data, AUTH_PROP_ENABLED) != 0) return (0); loc += sz; cmds_sz -= sz; } return (1); } int rc_tx_commit(rc_node_ptr_t *txp, const void *cmds, size_t cmds_sz) { rc_node_t *np = txp->rnp_node; rc_node_t *pp; rc_node_t *nnp; rc_node_pg_notify_t *pnp; int rc; permcheck_t *pcp; perm_status_t granted; int normal; char *pg_fmri = NULL; char *auth_string = NULL; int auth_status = ADT_SUCCESS; int auth_ret_value = ADT_SUCCESS; size_t sz_out; int tx_flag = 1; tx_commit_data_t *tx_data = NULL; RC_NODE_CHECK(np); if ((txp->rnp_authorized != RC_AUTH_UNKNOWN) && (txp->rnp_auth_string != NULL)) { auth_string = strdup(txp->rnp_auth_string); if (auth_string == NULL) return (REP_PROTOCOL_FAIL_NO_RESOURCES); } if ((txp->rnp_authorized == RC_AUTH_UNKNOWN) && is_main_repository) { #ifdef NATIVE_BUILD if (!client_is_privileged()) { return (REP_PROTOCOL_FAIL_PERMISSION_DENIED); } #else /* permission check: depends on contents of transaction */ pcp = pc_create(); if (pcp == NULL) return (REP_PROTOCOL_FAIL_NO_RESOURCES); /* If normal is cleared, we won't do the normal checks. */ normal = 1; rc = REP_PROTOCOL_SUCCESS; if (strcmp(np->rn_name, AUTH_PG_GENERAL) == 0 && strcmp(np->rn_type, AUTH_PG_GENERAL_TYPE) == 0) { /* Touching general[framework]/action_authorization? */ rc = tx_modifies_action(cmds, cmds_sz); if (rc == -1) { pc_free(pcp); return (REP_PROTOCOL_FAIL_BAD_REQUEST); } if (rc) { /* * Yes: only AUTH_MODIFY and AUTH_MANAGE * can be used. */ rc = perm_add_enabling(pcp, AUTH_MODIFY); if (rc == REP_PROTOCOL_SUCCESS) rc = perm_add_enabling(pcp, AUTH_MANAGE); normal = 0; } else { rc = REP_PROTOCOL_SUCCESS; } } else if (np->rn_id.rl_ids[ID_INSTANCE] != 0 && strcmp(np->rn_name, AUTH_PG_GENERAL_OVR) == 0 && strcmp(np->rn_type, AUTH_PG_GENERAL_OVR_TYPE) == 0) { rc_node_t *instn; rc = tx_only_enabled(cmds, cmds_sz); if (rc == -1) { pc_free(pcp); return (REP_PROTOCOL_FAIL_BAD_REQUEST); } if (rc) { rc = rc_node_parent(np, &instn); if (rc != REP_PROTOCOL_SUCCESS) { assert(rc == REP_PROTOCOL_FAIL_DELETED); pc_free(pcp); return (rc); } assert(instn->rn_id.rl_type == REP_PROTOCOL_ENTITY_INSTANCE); rc = perm_add_inst_action_auth(pcp, instn); rc_node_rele(instn); switch (rc) { case REP_PROTOCOL_SUCCESS: break; case REP_PROTOCOL_FAIL_DELETED: case REP_PROTOCOL_FAIL_NO_RESOURCES: pc_free(pcp); return (rc); default: bad_error("perm_add_inst_action_auth", rc); } } else { rc = REP_PROTOCOL_SUCCESS; } } if (rc == REP_PROTOCOL_SUCCESS && normal) { rc = perm_add_enabling(pcp, AUTH_MODIFY); if (rc == REP_PROTOCOL_SUCCESS) { /* Add pgtype-specific authorization. */ const char * const auth = perm_auth_for_pgtype(np->rn_type); if (auth != NULL) rc = perm_add_enabling(pcp, auth); } /* Add pg-specific modify_authorization auths. */ if (rc == REP_PROTOCOL_SUCCESS) rc = perm_add_enabling_values(pcp, np, AUTH_PROP_MODIFY); /* If value_authorization values are ok, add them. */ if (rc == REP_PROTOCOL_SUCCESS) { rc = tx_allow_value(cmds, cmds_sz, np); if (rc == -1) rc = REP_PROTOCOL_FAIL_BAD_REQUEST; else if (rc) rc = perm_add_enabling_values(pcp, np, AUTH_PROP_VALUE); } } if (rc == REP_PROTOCOL_SUCCESS) { granted = perm_granted(pcp); rc = map_granted_status(granted, pcp, &auth_string); if ((granted == PERM_DENIED) && auth_string) { /* * _PERMISSION_DENIED should not cause us * to exit at this point, because we still * want to generate an audit event. */ rc = REP_PROTOCOL_SUCCESS; } } pc_free(pcp); if (rc != REP_PROTOCOL_SUCCESS) goto cleanout; if (granted == PERM_DENIED) { auth_status = ADT_FAILURE; auth_ret_value = ADT_FAIL_VALUE_AUTH; tx_flag = 0; } #endif /* NATIVE_BUILD */ } else if (txp->rnp_authorized == RC_AUTH_FAILED) { auth_status = ADT_FAILURE; auth_ret_value = ADT_FAIL_VALUE_AUTH; tx_flag = 0; } pg_fmri = malloc(REP_PROTOCOL_FMRI_LEN); if (pg_fmri == NULL) { rc = REP_PROTOCOL_FAIL_NO_RESOURCES; goto cleanout; } if ((rc = rc_node_get_fmri_or_fragment(np, pg_fmri, REP_PROTOCOL_FMRI_LEN, &sz_out)) != REP_PROTOCOL_SUCCESS) { goto cleanout; } /* * Parse the transaction commands into a useful form. */ if ((rc = tx_commit_data_new(cmds, cmds_sz, &tx_data)) != REP_PROTOCOL_SUCCESS) { goto cleanout; } if (tx_flag == 0) { /* Authorization failed. Generate audit events. */ generate_property_events(tx_data, pg_fmri, auth_string, auth_status, auth_ret_value); rc = REP_PROTOCOL_FAIL_PERMISSION_DENIED; goto cleanout; } nnp = rc_node_alloc(); if (nnp == NULL) { rc = REP_PROTOCOL_FAIL_NO_RESOURCES; goto cleanout; } nnp->rn_id = np->rn_id; /* structure assignment */ nnp->rn_hash = np->rn_hash; nnp->rn_name = strdup(np->rn_name); nnp->rn_type = strdup(np->rn_type); nnp->rn_pgflags = np->rn_pgflags; nnp->rn_flags = RC_NODE_IN_TX | RC_NODE_USING_PARENT; if (nnp->rn_name == NULL || nnp->rn_type == NULL) { rc_node_destroy(nnp); rc = REP_PROTOCOL_FAIL_NO_RESOURCES; goto cleanout; } (void) pthread_mutex_lock(&np->rn_lock); /* * We must have all of the old properties in the cache, or the * database deletions could cause inconsistencies. */ if ((rc = rc_node_fill_children(np, REP_PROTOCOL_ENTITY_PROPERTY)) != REP_PROTOCOL_SUCCESS) { (void) pthread_mutex_unlock(&np->rn_lock); rc_node_destroy(nnp); goto cleanout; } if (!rc_node_hold_flag(np, RC_NODE_USING_PARENT)) { (void) pthread_mutex_unlock(&np->rn_lock); rc_node_destroy(nnp); rc = REP_PROTOCOL_FAIL_DELETED; goto cleanout; } if (np->rn_flags & RC_NODE_OLD) { rc_node_rele_flag(np, RC_NODE_USING_PARENT); (void) pthread_mutex_unlock(&np->rn_lock); rc_node_destroy(nnp); rc = REP_PROTOCOL_FAIL_NOT_LATEST; goto cleanout; } pp = rc_node_hold_parent_flag(np, RC_NODE_CHILDREN_CHANGING); if (pp == NULL) { /* our parent is gone, we're going next... */ rc_node_destroy(nnp); (void) pthread_mutex_lock(&np->rn_lock); if (np->rn_flags & RC_NODE_OLD) { (void) pthread_mutex_unlock(&np->rn_lock); rc = REP_PROTOCOL_FAIL_NOT_LATEST; goto cleanout; } (void) pthread_mutex_unlock(&np->rn_lock); rc = REP_PROTOCOL_FAIL_DELETED; goto cleanout; } (void) pthread_mutex_unlock(&pp->rn_lock); /* * prepare for the transaction */ (void) pthread_mutex_lock(&np->rn_lock); if (!rc_node_hold_flag(np, RC_NODE_IN_TX)) { (void) pthread_mutex_unlock(&np->rn_lock); (void) pthread_mutex_lock(&pp->rn_lock); rc_node_rele_flag(pp, RC_NODE_CHILDREN_CHANGING); (void) pthread_mutex_unlock(&pp->rn_lock); rc_node_destroy(nnp); rc = REP_PROTOCOL_FAIL_DELETED; goto cleanout; } nnp->rn_gen_id = np->rn_gen_id; (void) pthread_mutex_unlock(&np->rn_lock); /* Sets nnp->rn_gen_id on success. */ rc = object_tx_commit(&np->rn_id, tx_data, &nnp->rn_gen_id); (void) pthread_mutex_lock(&np->rn_lock); if (rc != REP_PROTOCOL_SUCCESS) { rc_node_rele_flag(np, RC_NODE_IN_TX); (void) pthread_mutex_unlock(&np->rn_lock); (void) pthread_mutex_lock(&pp->rn_lock); rc_node_rele_flag(pp, RC_NODE_CHILDREN_CHANGING); (void) pthread_mutex_unlock(&pp->rn_lock); rc_node_destroy(nnp); rc_node_clear(txp, 0); if (rc == REP_PROTOCOL_DONE) rc = REP_PROTOCOL_SUCCESS; /* successful empty tx */ goto cleanout; } /* * Notify waiters */ (void) pthread_mutex_lock(&rc_pg_notify_lock); while ((pnp = uu_list_first(np->rn_pg_notify_list)) != NULL) rc_pg_notify_fire(pnp); (void) pthread_mutex_unlock(&rc_pg_notify_lock); np->rn_flags |= RC_NODE_OLD; (void) pthread_mutex_unlock(&np->rn_lock); rc_notify_remove_node(np); /* * replace np with nnp */ rc_node_relink_child(pp, np, nnp); /* * all done -- clear the transaction. */ rc_node_clear(txp, 0); generate_property_events(tx_data, pg_fmri, auth_string, auth_status, auth_ret_value); rc = REP_PROTOCOL_SUCCESS; cleanout: free(auth_string); free(pg_fmri); tx_commit_data_free(tx_data); return (rc); } void rc_pg_notify_init(rc_node_pg_notify_t *pnp) { uu_list_node_init(pnp, &pnp->rnpn_node, rc_pg_notify_pool); pnp->rnpn_pg = NULL; pnp->rnpn_fd = -1; } int rc_pg_notify_setup(rc_node_pg_notify_t *pnp, rc_node_ptr_t *npp, int fd) { rc_node_t *np; RC_NODE_PTR_GET_CHECK_AND_LOCK(np, npp); if (np->rn_id.rl_type != REP_PROTOCOL_ENTITY_PROPERTYGRP) { (void) pthread_mutex_unlock(&np->rn_lock); return (REP_PROTOCOL_FAIL_BAD_REQUEST); } /* * wait for any transaction in progress to complete */ if (!rc_node_wait_flag(np, RC_NODE_IN_TX)) { (void) pthread_mutex_unlock(&np->rn_lock); return (REP_PROTOCOL_FAIL_DELETED); } if (np->rn_flags & RC_NODE_OLD) { (void) pthread_mutex_unlock(&np->rn_lock); return (REP_PROTOCOL_FAIL_NOT_LATEST); } (void) pthread_mutex_lock(&rc_pg_notify_lock); rc_pg_notify_fire(pnp); pnp->rnpn_pg = np; pnp->rnpn_fd = fd; (void) uu_list_insert_after(np->rn_pg_notify_list, NULL, pnp); (void) pthread_mutex_unlock(&rc_pg_notify_lock); (void) pthread_mutex_unlock(&np->rn_lock); return (REP_PROTOCOL_SUCCESS); } void rc_pg_notify_fini(rc_node_pg_notify_t *pnp) { (void) pthread_mutex_lock(&rc_pg_notify_lock); rc_pg_notify_fire(pnp); (void) pthread_mutex_unlock(&rc_pg_notify_lock); uu_list_node_fini(pnp, &pnp->rnpn_node, rc_pg_notify_pool); } void rc_notify_info_init(rc_notify_info_t *rnip) { int i; uu_list_node_init(rnip, &rnip->rni_list_node, rc_notify_info_pool); uu_list_node_init(&rnip->rni_notify, &rnip->rni_notify.rcn_list_node, rc_notify_pool); rnip->rni_notify.rcn_node = NULL; rnip->rni_notify.rcn_info = rnip; bzero(rnip->rni_namelist, sizeof (rnip->rni_namelist)); bzero(rnip->rni_typelist, sizeof (rnip->rni_typelist)); (void) pthread_cond_init(&rnip->rni_cv, NULL); for (i = 0; i < RC_NOTIFY_MAX_NAMES; i++) { rnip->rni_namelist[i] = NULL; rnip->rni_typelist[i] = NULL; } } static void rc_notify_info_insert_locked(rc_notify_info_t *rnip) { assert(MUTEX_HELD(&rc_pg_notify_lock)); assert(!(rnip->rni_flags & RC_NOTIFY_ACTIVE)); rnip->rni_flags |= RC_NOTIFY_ACTIVE; (void) uu_list_insert_after(rc_notify_info_list, NULL, rnip); (void) uu_list_insert_before(rc_notify_list, NULL, &rnip->rni_notify); } static void rc_notify_info_remove_locked(rc_notify_info_t *rnip) { rc_notify_t *me = &rnip->rni_notify; rc_notify_t *np; assert(MUTEX_HELD(&rc_pg_notify_lock)); assert(rnip->rni_flags & RC_NOTIFY_ACTIVE); assert(!(rnip->rni_flags & RC_NOTIFY_DRAIN)); rnip->rni_flags |= RC_NOTIFY_DRAIN; (void) pthread_cond_broadcast(&rnip->rni_cv); (void) uu_list_remove(rc_notify_info_list, rnip); /* * clean up any notifications at the beginning of the list */ if (uu_list_first(rc_notify_list) == me) { /* * We can't call rc_notify_remove_locked() unless * rc_notify_in_use is 0. */ while (rc_notify_in_use) { (void) pthread_cond_wait(&rc_pg_notify_cv, &rc_pg_notify_lock); } while ((np = uu_list_next(rc_notify_list, me)) != NULL && np->rcn_info == NULL) rc_notify_remove_locked(np); } (void) uu_list_remove(rc_notify_list, me); while (rnip->rni_waiters) { (void) pthread_cond_broadcast(&rc_pg_notify_cv); (void) pthread_cond_broadcast(&rnip->rni_cv); (void) pthread_cond_wait(&rnip->rni_cv, &rc_pg_notify_lock); } rnip->rni_flags &= ~(RC_NOTIFY_DRAIN | RC_NOTIFY_ACTIVE); } static int rc_notify_info_add_watch(rc_notify_info_t *rnip, const char **arr, const char *name) { int i; int rc; char *f; rc = rc_check_type_name(REP_PROTOCOL_ENTITY_PROPERTYGRP, name); if (rc != REP_PROTOCOL_SUCCESS) return (rc); f = strdup(name); if (f == NULL) return (REP_PROTOCOL_FAIL_NO_RESOURCES); (void) pthread_mutex_lock(&rc_pg_notify_lock); while (rnip->rni_flags & RC_NOTIFY_EMPTYING) (void) pthread_cond_wait(&rnip->rni_cv, &rc_pg_notify_lock); for (i = 0; i < RC_NOTIFY_MAX_NAMES; i++) { if (arr[i] == NULL) break; /* * Don't add name if it's already being tracked. */ if (strcmp(arr[i], f) == 0) { free(f); goto out; } } if (i == RC_NOTIFY_MAX_NAMES) { (void) pthread_mutex_unlock(&rc_pg_notify_lock); free(f); return (REP_PROTOCOL_FAIL_NO_RESOURCES); } arr[i] = f; out: if (!(rnip->rni_flags & RC_NOTIFY_ACTIVE)) rc_notify_info_insert_locked(rnip); (void) pthread_mutex_unlock(&rc_pg_notify_lock); return (REP_PROTOCOL_SUCCESS); } int rc_notify_info_add_name(rc_notify_info_t *rnip, const char *name) { return (rc_notify_info_add_watch(rnip, rnip->rni_namelist, name)); } int rc_notify_info_add_type(rc_notify_info_t *rnip, const char *type) { return (rc_notify_info_add_watch(rnip, rnip->rni_typelist, type)); } /* * Wait for and report an event of interest to rnip, a notification client */ int rc_notify_info_wait(rc_notify_info_t *rnip, rc_node_ptr_t *out, char *outp, size_t sz) { rc_notify_t *np; rc_notify_t *me = &rnip->rni_notify; rc_node_t *nnp; rc_notify_delete_t *ndp; int am_first_info; if (sz > 0) outp[0] = 0; (void) pthread_mutex_lock(&rc_pg_notify_lock); while ((rnip->rni_flags & (RC_NOTIFY_ACTIVE | RC_NOTIFY_DRAIN)) == RC_NOTIFY_ACTIVE) { /* * If I'm first on the notify list, it is my job to * clean up any notifications I pass by. I can't do that * if someone is blocking the list from removals, so I * have to wait until they have all drained. */ am_first_info = (uu_list_first(rc_notify_list) == me); if (am_first_info && rc_notify_in_use) { rnip->rni_waiters++; (void) pthread_cond_wait(&rc_pg_notify_cv, &rc_pg_notify_lock); rnip->rni_waiters--; continue; } /* * Search the list for a node of interest. */ np = uu_list_next(rc_notify_list, me); while (np != NULL && !rc_notify_info_interested(rnip, np)) { rc_notify_t *next = uu_list_next(rc_notify_list, np); if (am_first_info) { if (np->rcn_info) { /* * Passing another client -- stop * cleaning up notifications */ am_first_info = 0; } else { rc_notify_remove_locked(np); } } np = next; } /* * Nothing of interest -- wait for notification */ if (np == NULL) { rnip->rni_waiters++; (void) pthread_cond_wait(&rnip->rni_cv, &rc_pg_notify_lock); rnip->rni_waiters--; continue; } /* * found something to report -- move myself after the * notification and process it. */ (void) uu_list_remove(rc_notify_list, me); (void) uu_list_insert_after(rc_notify_list, np, me); if ((ndp = np->rcn_delete) != NULL) { (void) strlcpy(outp, ndp->rnd_fmri, sz); if (am_first_info) rc_notify_remove_locked(np); (void) pthread_mutex_unlock(&rc_pg_notify_lock); rc_node_clear(out, 0); return (REP_PROTOCOL_SUCCESS); } nnp = np->rcn_node; assert(nnp != NULL); /* * We can't bump nnp's reference count without grabbing its * lock, and rc_pg_notify_lock is a leaf lock. So we * temporarily block all removals to keep nnp from * disappearing. */ rc_notify_in_use++; assert(rc_notify_in_use > 0); (void) pthread_mutex_unlock(&rc_pg_notify_lock); rc_node_assign(out, nnp); (void) pthread_mutex_lock(&rc_pg_notify_lock); assert(rc_notify_in_use > 0); rc_notify_in_use--; if (am_first_info) { /* * While we had the lock dropped, another thread * may have also incremented rc_notify_in_use. We * need to make sure that we're back to 0 before * removing the node. */ while (rc_notify_in_use) { (void) pthread_cond_wait(&rc_pg_notify_cv, &rc_pg_notify_lock); } rc_notify_remove_locked(np); } if (rc_notify_in_use == 0) (void) pthread_cond_broadcast(&rc_pg_notify_cv); (void) pthread_mutex_unlock(&rc_pg_notify_lock); return (REP_PROTOCOL_SUCCESS); } /* * If we're the last one out, let people know it's clear. */ if (rnip->rni_waiters == 0) (void) pthread_cond_broadcast(&rnip->rni_cv); (void) pthread_mutex_unlock(&rc_pg_notify_lock); return (REP_PROTOCOL_DONE); } static void rc_notify_info_reset(rc_notify_info_t *rnip) { int i; (void) pthread_mutex_lock(&rc_pg_notify_lock); if (rnip->rni_flags & RC_NOTIFY_ACTIVE) rc_notify_info_remove_locked(rnip); assert(!(rnip->rni_flags & (RC_NOTIFY_DRAIN | RC_NOTIFY_EMPTYING))); rnip->rni_flags |= RC_NOTIFY_EMPTYING; (void) pthread_mutex_unlock(&rc_pg_notify_lock); for (i = 0; i < RC_NOTIFY_MAX_NAMES; i++) { if (rnip->rni_namelist[i] != NULL) { free((void *)rnip->rni_namelist[i]); rnip->rni_namelist[i] = NULL; } if (rnip->rni_typelist[i] != NULL) { free((void *)rnip->rni_typelist[i]); rnip->rni_typelist[i] = NULL; } } (void) pthread_mutex_lock(&rc_pg_notify_lock); rnip->rni_flags &= ~RC_NOTIFY_EMPTYING; (void) pthread_mutex_unlock(&rc_pg_notify_lock); } void rc_notify_info_fini(rc_notify_info_t *rnip) { rc_notify_info_reset(rnip); uu_list_node_fini(rnip, &rnip->rni_list_node, rc_notify_info_pool); uu_list_node_fini(&rnip->rni_notify, &rnip->rni_notify.rcn_list_node, rc_notify_pool); }