/*
* 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
* 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 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Contracts
* ---------
*
* Contracts are a primitive which enrich the relationships between
* processes and system resources. The primary purpose of contracts is
* to provide a means for the system to negotiate the departure from a
* binding relationship (e.g. pages locked in memory or a thread bound
* to processor), but they can also be used as a purely asynchronous
* error reporting mechanism as they are with process contracts.
*
* More information on how one interfaces with contracts and what
* contracts can do for you can be found in:
* PSARC 2003/193 Solaris Contracts
* PSARC 2004/460 Contracts addendum
*
* This file contains the core contracts framework. By itself it is
* useless: it depends the contracts filesystem (ctfs) to provide an
* interface to user processes and individual contract types to
*
* Data structure overview
* -----------------------
*
* A contract is represented by a contract_t, which itself points to an
* encapsulating contract-type specific contract object. A contract_t
* contains the contract's static identity (including its terms), its
* linkage to various bookkeeping structures, the contract-specific
* event queue, and a reference count.
*
* A contract template is represented by a ct_template_t, which, like a
* contract, points to an encapsulating contract-type specific template
* object. A ct_template_t contains the template's terms.
*
* An event queue is represented by a ct_equeue_t, and consists of a
* list of events, a list of listeners, and a list of listeners who are
* waiting for new events (affectionately referred to as "tail
* listeners"). There are three queue types, defined by ct_listnum_t
* (an enum). An event may be on one of each type of queue
* simultaneously; the list linkage used by a queue is determined by
* its type.
*
* An event is represented by a ct_kevent_t, which contains mostly
* static event data (e.g. id, payload). It also has an array of
* ct_member_t structures, each of which contains a list_node_t and
* represent the event's linkage in a specific event queue.
*
* Each open of an event endpoint results in the creation of a new
* listener, represented by a ct_listener_t. In addition to linkage
* into the aforementioned lists in the event_queue, a ct_listener_t
* contains a pointer to the ct_kevent_t it is currently positioned at
* as well as a set of status flags and other administrative data.
*
* Each process has a list of contracts it owns, p_ct_held; a pointer
* to the process contract it is a member of, p_ct_process; the linkage
* for that membership, p_ct_member; and an array of event queue
* structures representing the process bundle queues.
*
* Each LWP has an array of its active templates, lwp_ct_active; and
* the most recently created contracts, lwp_ct_latest.
*
* A process contract has a list of member processes and a list of
* inherited contracts.
*
* There is a system-wide list of all contracts, as well as per-type
* lists of contracts.
*
* Lock ordering overview
* ----------------------
*
* Locks at the top are taken first:
*
* ct_evtlock
* regent ct_lock
* member ct_lock
* pidlock
* p_lock
* contract ctq_lock contract_lock
* pbundle ctq_lock
* cte_lock
* ct_reflock
*
* same time.
*
* Reference counting and locking
* ------------------------------
*
* A contract has a reference count, protected by ct_reflock.
* (ct_reflock is also used in a couple other places where atomic
* access to a variable is needed in an innermost context). A process
* maintains a hold on each contract it owns. A process contract has a
* hold on each contract is has inherited. Each event has a hold on
* the contract which generated it. Process contract templates have
* holds on the contracts referred to by their transfer terms. CTFS
* contract directory nodes have holds on contracts. Lastly, various
* code paths may temporarily take holds on contracts to prevent them
* from disappearing while other processing is going on. It is
* important to note that the global contract lists do not hold
* references on contracts; a contract is removed from these structures
* atomically with the release of its last reference.
*
* At a given point in time, a contract can either be owned by a
* process, inherited by a regent process contract, or orphaned. A
* contract_t's owner and regent pointers, ct_owner and ct_regent, are
* protected by its ct_lock. The linkage in the holder's (holder =
* owner or regent) list of contracts, ct_ctlist, is protected by
* whatever lock protects the holder's data structure. In order for
* these two directions to remain consistent, changing the holder of a
* contract requires that both locks be held.
*
* Events also have reference counts. There is one hold on an event
* per queue it is present on, in addition to those needed for the
* usual sundry reasons. Individual listeners are associated with
* specific queues, and increase a queue-specific reference count
* stored in the ct_member_t structure.
*
* The dynamic contents of an event (reference count and flags) are
* protected by its cte_lock, while the contents of the embedded
* ct_member_t structures are protected by the locks of the queues they
* are linked into. A ct_listener_t's contents are also protected by
* its event queue's ctq_lock.
*
* Resource controls
* -----------------
*
* Control: project.max-contracts (rc_project_contract)
* Description: Maximum number of contracts allowed a project.
*
* When a contract is created, the project's allocation is tested and
* (assuming success) increased. When the last reference to a
* contract is released, the creating project's allocation is
* decreased.
*/
#include <sys/id_space.h>
#include <sys/sysmacros.h>
#include <sys/contract_impl.h>
#include <sys/dditypes.h>
extern rctl_hndl_t rc_project_contract;
int ct_debug;
static void cte_queue_destroy(ct_equeue_t *);
static void cte_queue_drain(ct_equeue_t *, int);
/*
* contract_compar
*
* A contract comparator which sorts on contract ID.
*/
int
contract_compar(const void *x, const void *y)
{
return (-1);
return (1);
return (0);
}
/*
* contract_init
*
* Initializes the contract subsystem, the specific contract types, and
* process 0.
*/
void
contract_init(void)
{
/*
* Initialize contract subsystem.
*/
/*
* Initialize contract types.
*/
/*
*/
}
/*
* contract_dtor
*
* Performs basic destruction of the common portions of a contract.
* Called from the failure path of contract_ctor and from
* contract_rele.
*/
static void
{
}
/*
* contract_ctor
*
* Called by a contract type to initialize a contract. Fails if the
* max-contract resource control would have been exceeded. After a
* successful call to contract_ctor, the contract is unlocked and
* visible in all namespaces; any type-specific initialization should
* be completed before calling contract_ctor. Returns 0 on success.
*
* Because not all callers can tolerate failure, a 0 value for canfail
* instructs contract_ctor to ignore the project.max-contracts resource
* control. Obviously, this "out" should only be employed by callers
* who are sufficiently constrained in other ways (e.g. newproc).
*/
int
{
/*
* Instance data
*/
/*
* Test project.max-contracts.
*/
return (1);
}
/*
* Insert into holder's avl of contracts.
* We use an avl not because order is important, but because
* scalar as an index into the process's list of contracts
*/
/*
* Insert into global contract AVL
*/
/*
* Insert into type AVL
*/
return (0);
}
/*
* contract_rele
*
* Releases a reference to a contract. If the caller had the last
* reference, the contract is removed from all namespaces, its
* allocation against the max-contracts resource control is released,
* and the contract type's free entry point is invoked for any
* type-specific deconstruction and to (presumably) free the object.
*/
void
{
if (nref == 0) {
/*
* ct_owner is cleared when it drops its reference.
*/
/*
* Remove from global contract AVL
*/
/*
* Remove from type AVL
*/
/*
* Release the contract's ID
*/
/*
* Release project hold
*/
/*
* Free the contract
*/
}
}
/*
* contract_hold
*
* Adds a reference to a contract
*/
void
{
}
/*
* contract_getzuniqid
*
* Get a contract's zone unique ID. Needed because 64-bit reads and
* writes aren't atomic on x86. Since there are contexts where we are
* unable to take ct_lock, we instead use ct_reflock; in actuality any
* lock would do.
*/
{
return (zuniqid);
}
/*
* contract_setzuniqid
*
* Sets a contract's zone unique ID. See contract_getzuniqid.
*/
void
{
}
/*
* contract_abandon
*
* Abandons the specified contract. If "explicit" is clear, the
* contract was implicitly abandoned (by process exit) and should be
* inherited if its terms allow it and its owner was a member of a
* regent contract. Otherwise, the contract type's abandon entry point
* is invoked to either destroy or orphan the contract.
*/
int
{
ct_equeue_t *q = NULL;
int inherit = 0;
/*
* Multiple contract locks are taken contract -> subcontract.
* Check if the contract will be inherited so we can acquire
* all the necessary locks before making sensitive changes.
*/
inherit = 1;
}
if (inherit)
return (EINVAL);
}
mutex_enter(&p->p_lock);
if (explicit)
mutex_exit(&p->p_lock);
/*
* Since we can't call cte_trim with the contract lock held,
* we grab the queue pointer here.
*/
if (p->p_ct_equeue)
/*
* contop_abandon may destroy the contract so we rely on it to
* drop ct_lock. We retain a reference on the contract so that
* the cte_trim which follows functions properly. Even though
* cte_trim doesn't dereference the contract pointer, it is
* still necessary to retain a reference to the contract so
* that we don't trim events which are sent by a subsequently
* allocated contract infortuitously located at the same address.
*/
if (inherit) {
/*
* We are handing off the process's reference to the
* parent contract. For this reason, the order in
* which we drop the contract locks is also important.
*/
} else {
}
/*
* ct_lock has been dropped; we can safely trim the event
* queue now.
*/
if (q) {
mutex_enter(&q->ctq_lock);
mutex_exit(&q->ctq_lock);
}
return (0);
}
int
{
}
/*
* contract_adopt
*
* Adopts a contract. After a successful call to this routine, the
* previously inherited contract will belong to the calling process,
* and its events will have been appended to its new owner's process
* bundle queue.
*/
int
{
ct_equeue_t *q;
/*
* Ensure the process has an event queue. Checked by ASSERTs
* below.
*/
return (EINVAL);
}
/*
* Multiple contract locks are taken contract -> subcontract.
*/
/*
* It is possible that the contract was adopted by someone else
* while its lock was dropped. It isn't possible for the
* contract to have been inherited by a different regent
* contract.
*/
return (EBUSY);
}
contract_process_adopt(ct, p);
mutex_enter(&p->p_lock);
mutex_exit(&p->p_lock);
return (0);
}
/*
* contract_ack
*
* Acknowledges receipt of a critical event.
*/
int
{
int nego = 0;
/*
* We are probably ACKing something near the head of the queue.
*/
nego = 1;
break;
error = 0;
}
break;
}
}
/*
* Not all critical events are negotiation events, however
* every negotiation event is a critical event. NEGEND events
* are critical events but are not negotiation events
*/
return (error);
else
return (error);
}
/*ARGSUSED*/
int
{
return (ENOSYS);
}
/*ARGSUSED*/
int
{
return (ENOSYS);
}
/*ARGSUSED*/
int
{
return (ERANGE);
}
/*
* contract_qack
*
* Asks that negotiations be extended by another time quantum
*/
int
{
int nego = 0;
nego = 1;
}
break;
}
}
/*
* Only a negotiated event (which is by definition also a critical
* event) which has not yet been acknowledged can provide
* time quanta to a negotiating owner process.
*/
if (!nego)
return (ESRCH);
}
/*
* contract_orphan
*
* Icky-poo. This is a process-contract special, used to ACK all
* critical messages when a contract is orphaned.
*/
void
{
}
}
}
/*
* contract_destroy
*
* Explicit contract destruction. Called when contract is empty.
* The contract will actually stick around until all of its events are
* removed from the bundle and and process bundle queues, and all fds
* which refer to it are closed. See contract_dtor if you are looking
* for what destroys the contract structure.
*/
void
{
}
/*
* contract_vnode_get
*
* Obtains the contract directory vnode for this contract, if there is
* one. The caller must VN_RELE the vnode when they are through using
* it.
*/
vnode_t *
{
break;
}
return (vp);
}
/*
* contract_vnode_set
*
* Sets the contract directory vnode for this contract. We don't hold
* a reference on the vnode because we don't want to prevent it from
* being freed. The vnode's inactive entry point will take care of
* notifying us when it should be removed.
*/
void
{
}
/*
* contract_vnode_clear
*
* Removes this vnode as the contract directory vnode for this
* contract. Called from a contract directory's inactive entry point,
* this may return 0 indicating that the vnode gained another reference
* because of a simultaneous call to contract_vnode_get.
*/
int
{
int result;
result = 1;
} else {
result = 0;
}
return (result);
}
/*
* contract_exit
*
* Abandons all contracts held by process p, and drains process p's
* bundle queues. Called on process exit.
*/
void
{
int i;
/*
* Abandon held contracts. contract_abandon knows enough not
* to remove the contract from the list a second time. We are
* exiting, so no locks are needed here. But because
* contract_abandon will take p_lock, we need to make sure we
* aren't holding it.
*/
/*
* Drain pbundles. Because a process bundle queue could have
* been passed to another process, they may not be freed right
* away.
*/
if (p->p_ct_equeue) {
for (i = 0; i < CTT_MAXTYPE; i++)
if (p->p_ct_equeue[i])
cte_queue_drain(p->p_ct_equeue[i], 0);
}
}
static int
{
int secs_elapsed;
if (t->ctm_total == -1)
return (-1);
return (secs_elapsed > 0 ? secs_elapsed : 0);
}
/*
* contract_status_common
*
* Populates a ct_status structure. Used by contract types in their
* status entry points and ctfs when only common information is
* requested.
*/
void
{
/*
* Contracts don't have holds on the zones they were
* created by. If the contract's zone no longer
* exists, we say its zoneid is -1.
*/
}
} else {
/*
* We are looking at a contract which was created by a
* process outside of our zone. We provide fake zone,
* holder, and state information.
*/
/*
* Since "zone" can't disappear until the calling ctfs
* is unmounted, zone_zsched must be valid.
*/
}
}
/*
* contract_checkcred
*
* Determines if the specified contract is owned by a process with the
* same effective uid as the specified credential. The caller must
* ensure that the uid spaces are the same. Returns 1 on success.
*/
static int
{
proc_t *p;
mutex_enter(&p->p_crlock);
mutex_exit(&p->p_crlock);
}
return (!fail);
}
/*
* contract_owned
*
* Determines if the specified credential can view an event generated
* by the specified contract. If locked is set, the contract's ct_lock
* is held and the caller will need to do additional work to determine
* if they truly can see the event. Returns 1 on success.
*/
int
{
/*
* owner: we own the contract
* cmatch: we are in the creator's (and holder's) zone and our
* uid matches the creator's or holder's
* zmatch: we are in the effective zone of a contract created
* in the global zone, and our uid matches that of the
*/
}
/*
* contract_type_init
*
* Called by contract types to register themselves with the contracts
* framework.
*/
{
result->ct_type_evid = 0;
return (result);
}
/*
* contract_type_count
*
* Obtains the number of contracts of a particular type.
*/
int
{
return (count);
}
/*
* contract_type_max
*
* Obtains the maximum contract id of of a particular type.
*/
{
return (res);
}
/*
* contract_max
*
* Obtains the maximum contract id.
*/
contract_max(void)
{
return (res);
}
/*
* contract_lookup_common
*
* Common code for contract_lookup and contract_type_lookup. Takes a
* pointer to an AVL tree to search in. Should be called with the
* appropriate tree-protecting lock held (unfortunately unassertable).
*/
static ctid_t
{
if (zuniqid != GLOBAL_ZONEUNIQID)
return (res);
}
/*
* contract_type_lookup
*
* Returns the next type contract after the specified id, visible from
* the specified zone.
*/
{
return (res);
}
/*
* contract_lookup
*
* Returns the next contract after the specified id, visible from the
* specified zone.
*/
{
return (res);
}
/*
* contract_plookup
*
* Returns the next contract held by process p after the specified id,
* visible from the specified zone. Made complicated by the fact that
* contracts visible in a zone but held by processes outside of the
* zone need to appear as being held by zsched to zone members.
*/
{
if (zuniqid != GLOBAL_ZONEUNIQID &&
/* This is inelegant. */
} else {
mutex_enter(&p->p_lock);
mutex_exit(&p->p_lock);
}
return (res);
}
/*
* contract_ptr_common
*
* Common code for contract_ptr and contract_type_ptr. Takes a pointer
* to an AVL tree to search in. Should be called with the appropriate
* tree-protecting lock held (unfortunately unassertable).
*/
static contract_t *
{
return (NULL);
}
/*
* Check to see if a thread is in the window in contract_rele
* between dropping the reference count and removing the
* contract from the type AVL.
*/
} else {
}
return (ct);
}
/*
* contract_type_ptr
*
* Returns a pointer to the contract with the specified id. The
* contract is held, so the caller needs to release the reference when
* it is through with the contract.
*/
{
return (ct);
}
/*
* contract_ptr
*
* Returns a pointer to the contract with the specified id. The
* contract is held, so the caller needs to release the reference when
* it is through with the contract.
*/
{
return (ct);
}
/*
* contract_type_time
*
* Obtains the last time a contract of a particular type was created.
*/
void
{
}
/*
* contract_type_bundle
*
* Obtains a type's bundle queue.
*/
{
return (&type->ct_type_events);
}
/*
* contract_type_pbundle
*
* Obtain's a process's bundle queue. If one doesn't exist, one is
* created. Often used simply to ensure that a bundle queue is
* allocated.
*/
{
/*
* If there isn't an array of bundle queues, allocate one.
*/
if (pp->p_ct_equeue)
else
}
/*
* If there isn't a bundle queue of the required type, allocate
* one.
*/
cte_queue_drain(q, 0);
else
}
}
/*
* ctparam_copyin
*
* copyin a ct_param_t for CT_TSET or CT_TGET commands.
* If ctparam_copyout() is not called after ctparam_copyin(), then
* the caller must kmem_free() the buffer pointed by kparam->ctpm_kbuf.
*
* because prctioctl() calls ctmpl_set() and ctmpl_get() while holding a
* process lock.
*/
int
{
void *ubuf;
return (EFAULT);
return (EINVAL);
return (EFAULT);
}
}
return (0);
}
/*
* ctparam_copyout
*
* copyout a ct_kparam_t and frees the buffer pointed by the member
* ctpm_kbuf of ct_kparam_t
*/
int
{
int r = 0;
r = EFAULT;
goto error;
}
r = EFAULT;
}
return (r);
}
/*
* ctmpl_free
*
* Frees a template.
*/
void
{
}
/*
* ctmpl_dup
*
* Creates a copy of a template.
*/
{
return (NULL);
/*
* ctmpl_lock was taken by ctop_dup's call to ctmpl_copy and
* should have remain held until now.
*/
return (new);
}
/*
* ctmpl_set
*
* Sets the requested terms of a template.
*/
int
{
int result = 0;
return (EINVAL);
} else {
}
}
case CTP_COOKIE:
break;
case CTP_EV_INFO:
else
break;
case CTP_EV_CRITICAL:
break;
/*
* Assume that a pure reduction of the critical
* set is allowed by the contract type.
*/
break;
}
/*
* There may be restrictions on what we can make
* critical, so we defer to the judgement of the
* contract type.
*/
/* FALLTHROUGH */
default:
}
return (result);
}
/*
* ctmpl_get
*
* Obtains the requested terms from a template.
*
* If the term requested is a variable-sized term and the buffer
* provided is too small for the data, we truncate the data and return
* the buffer size necessary to fit the term in kparam->ret_size. If the
* term requested is fix-sized (uint64_t) and the buffer provided is too
* small, we return EINVAL. This should never happen if you're using
* libcontract(3LIB), only if you call ioctl with a hand constructed
* ct_param_t argument.
*
* Currently, only contract specific parameters have variable-sized
* parameters.
*/
int
{
int result = 0;
return (EINVAL);
} else {
}
}
case CTP_COOKIE:
break;
case CTP_EV_INFO:
break;
case CTP_EV_CRITICAL:
break;
default:
}
return (result);
}
/*
* ctmpl_makecurrent
*
* Used by ctmpl_activate and ctmpl_clear to set the current thread's
* active template. Frees the old active template, if there was one.
*/
static void
{
mutex_enter(&p->p_lock);
mutex_exit(&p->p_lock);
if (old)
}
/*
* ctmpl_activate
*
* Copy the specified template as the current thread's activate
* template of that type.
*/
void
{
}
/*
* ctmpl_clear
*
* Clears the current thread's activate template of the same type as
* the specified template.
*/
void
{
}
/*
* ctmpl_create
*
* Creates a new contract using the specified template.
*/
int
{
}
/*
* ctmpl_init
*
* Initializes the common portion of a new contract template.
*/
void
{
new->ctmpl_cookie = 0;
}
/*
* ctmpl_copy
*
* Copies the common portions of a contract template. Intended for use
* by a contract type's ctop_dup template op. Returns with the old
* template's lock held, which will should remain held until the
* template op returns (it is dropped by ctmpl_dup).
*/
void
{
}
/*
* ctmpl_create_inval
*
* Returns EINVAL. Provided for the convenience of those contract
* types which don't support ct_tmpl_create(3contract) and would
* otherwise need to create their own stub for the ctop_create template
* op.
*/
/*ARGSUSED*/
int
{
return (EINVAL);
}
/*
* cte_queue_create
*
* Initializes a queue of a particular type. If dynamic is set, the
* queue is to be freed when its last listener is removed after being
* drained.
*/
static void
{
q->ctq_listno = list;
gethrestime(&q->ctq_atime);
q->ctq_nlisteners = 0;
q->ctq_nreliable = 0;
q->ctq_ninf = 0;
/*
* Bundle queues and contract queues are embedded in other
* structures and are implicitly referenced counted by virtue
* of their vnodes' indirect hold on their contracts. Process
* bundle queues are dynamically allocated and may persist
* after the death of the process, so they must be explicitly
* reference counted.
*/
}
/*
* cte_queue_destroy
*
* Destroys the specified queue. The queue is freed if referenced
* counted.
*/
static void
{
ASSERT(q->ctq_nlisteners == 0);
ASSERT(q->ctq_nreliable == 0);
list_destroy(&q->ctq_events);
list_destroy(&q->ctq_listeners);
list_destroy(&q->ctq_tail);
mutex_destroy(&q->ctq_lock);
if (q->ctq_flags & CTQ_REFFED)
kmem_free(q, sizeof (ct_equeue_t));
}
/*
* cte_hold
*
* Takes a hold on the specified event.
*/
static void
{
mutex_enter(&e->cte_lock);
e->cte_refs++;
mutex_exit(&e->cte_lock);
}
/*
* cte_rele
*
* Releases a hold on the specified event. If the caller had the last
* reference, frees the event and releases its hold on the contract
* that generated it.
*/
static void
{
mutex_enter(&e->cte_lock);
if (--e->cte_refs) {
mutex_exit(&e->cte_lock);
return;
}
mutex_destroy(&e->cte_lock);
nvlist_free(e->cte_data);
nvlist_free(e->cte_gdata);
kmem_free(e, sizeof (ct_kevent_t));
}
/*
* cte_qrele
*
* Remove this listener's hold on the specified event, removing and
* releasing the queue's hold on the event if appropriate.
*/
static void
{
if (l->ctl_flags & CTLF_RELIABLE)
member->ctm_nreliable--;
member->ctm_trimmed = 0;
list_remove(&q->ctq_events, e);
cte_rele(e);
}
}
/*
* cte_qmove
*
* Move this listener to the specified event in the queue.
*/
static ct_kevent_t *
{
ASSERT(l->ctl_equeue == q);
list_remove(&q->ctq_tail, l);
e = list_next(&q->ctq_events, e);
if (e != NULL) {
if (l->ctl_flags & CTLF_RELIABLE)
} else {
list_insert_tail(&q->ctq_tail, l);
}
l->ctl_position = e;
if (olde)
return (e);
}
/*
* cte_checkcred
*
* Determines if the specified event's contract is owned by a process
* with the same effective uid as the specified credential. Called
* after a failed call to contract_owned with locked set. Because it
* drops the queue lock, its caller (cte_qreadable) needs to make sure
* we're still in the same place after we return. Returns 1 on
* success.
*/
static int
{
int result;
cte_hold(e);
mutex_exit(&q->ctq_lock);
mutex_enter(&q->ctq_lock);
cte_rele(e);
return (result);
}
/*
* cte_qreadable
*
* Ensures that the listener is pointing to a valid event that the
* caller has the credentials to read. Returns 0 if we can read the
* event we're pointing to.
*/
static int
{
ASSERT(l->ctl_equeue == q);
if (l->ctl_flags & CTLF_COPYOUT)
return (1);
next = l->ctl_position;
ct = e->cte_contract;
/*
* Check obvious things first. If we are looking for a
* critical message, is this one? If we aren't in the
* global zone, is this message meant for us?
*/
/*
* Next, see if our effective uid equals that of owner
* or author of the contract. Since we are holding the
* queue lock, contract_owned can't always check if we
* have the same effective uid as the contract's
* owner. If it comes to that, it fails and we take
* the slow(er) path.
*/
/*
* At this point we either don't have any claim
* to this contract or we match the effective
* uid of the owner but couldn't tell. We
* first test for a NULL holder so that events
* from orphans and inherited contracts avoid
* the penalty phase.
*/
/*
* cte_checkcred will juggle locks to see if we
* have the same uid as the event's contract's
* current owner. If it succeeds, we have to
* make sure we are in the same point in the
* queue.
*/
else if (cte_checkcred(q, e, cr) &&
l->ctl_position == e)
break;
/*
* cte_checkcred failed; see if we're in the
* same place.
*/
else if (l->ctl_position == e)
break;
else
/*
* cte_checkcred failed, and our position was
* changed. Start from there.
*/
else
next = l->ctl_position;
} else {
break;
}
}
/*
* We check for CTLF_COPYOUT again in case we dropped the queue
* lock in cte_checkcred.
*/
}
/*
* cte_qwakeup
*
* Wakes up any waiting listeners and points them at the specified event.
*/
static void
{
ct_listener_t *l;
list_remove(&q->ctq_tail, l);
if (l->ctl_flags & CTLF_RELIABLE)
l->ctl_position = e;
}
}
/*
* cte_copy
*
* Copies events from the specified contract event queue to the
* end of the specified process bundle queue. Only called from
* contract_adopt.
*
* We copy to the end of the target queue instead of mixing the events
* in their proper order because otherwise the act of adopting a
* contract would require a process to reset all process bundle
* listeners it needed to see the new events. This would, in turn,
* require the process to keep track of which preexisting events had
* already been processed.
*/
static void
{
mutex_enter(&q->ctq_lock);
/*
* For now, only copy critical events.
*/
e = list_next(&q->ctq_events, e)) {
first = e;
/*
* It is possible for adoption to race with an owner's
* cte_publish_all(); we must only enqueue events that
* have not already been enqueued.
*/
if (!list_link_active((list_node_t *)
cte_hold(e);
}
}
}
mutex_exit(&q->ctq_lock);
if (first)
}
/*
* cte_trim
*
* Trims unneeded events from an event queue. Algorithm works as
* follows:
*
* Removes all informative and acknowledged critical events until the
* first referenced event is found.
*
* If a contract is specified, removes all events (regardless of
* acknowledgement) generated by that contract until the first event
* referenced by a reliable listener is found. Reference events are
* removed by marking them "trimmed". Such events will be removed
* when the last reference is dropped and will be skipped by future
* listeners.
*
* This is pretty basic. Ideally this should remove from the middle of
* the list (i.e. beyond the first referenced event), and even
* referenced events.
*/
static void
{
(e->cte_contract == ct)) {
/*
* Toss informative and ACKed critical messages.
*/
list_remove(&q->ctq_events, e);
cte_rele(e);
}
ASSERT(q->ctq_nlisteners != 0);
start = 0;
} else {
/*
* Don't free messages past the first reader.
*/
break;
}
}
}
/*
* cte_queue_drain
*
* Drain all events from the specified queue, and mark it dead. If
* "ack" is set, acknowledge any critical events we find along the
* way.
*/
static void
{
ct_listener_t *l;
mutex_enter(&q->ctq_lock);
/*
* Make sure critical messages are eventually
* removed from the bundle queues.
*/
mutex_enter(&e->cte_lock);
mutex_exit(&e->cte_lock);
e->cte_contract->ct_evcnt--;
}
list_remove(&q->ctq_events, e);
cte_rele(e);
}
/*
* This is necessary only because of CTEL_PBUNDLE listeners;
* the events they point to can move from one pbundle to
* another. Fortunately, this only happens if the contract is
* inherited, which (in turn) only happens if the process
* exits, which means it's an all-or-nothing deal. If this
* wasn't the case, we would instead need to keep track of
* listeners on a per-event basis, not just a per-queue basis.
* This would have the side benefit of letting us clean up
* trimmed events sooner (i.e. immediately), but would
* unfortunately make events even bigger than they already
* are.
*/
for (l = list_head(&q->ctq_listeners); l;
l = list_next(&q->ctq_listeners, l)) {
if (l->ctl_position) {
l->ctl_position = NULL;
list_insert_tail(&q->ctq_tail, l);
}
cv_broadcast(&l->ctl_cv);
}
/*
* Disallow events.
*/
/*
* If we represent the last reference to a reference counted
* process bundle queue, free it.
*/
else
mutex_exit(&q->ctq_lock);
}
/*
* cte_publish
*
* Publishes an event to a specific queue. Only called by
* cte_publish_all.
*/
static void
{
/*
* If this event may already exist on this queue, check to see if it
* is already there and return if so.
*/
q->ctq_events.list_offset))) {
mutex_exit(&q->ctq_lock);
cte_rele(e);
return;
}
/*
* Don't publish if the event is informative and there aren't
* any listeners, or if the queue has been shut down.
*/
mutex_exit(&q->ctq_lock);
cte_rele(e);
return;
}
/*
* Enqueue event
*/
list_insert_tail(&q->ctq_events, e);
/*
* Check for waiting listeners
*/
cte_qwakeup(q, e);
/*
* Trim unnecessary events from the queue.
*/
mutex_exit(&q->ctq_lock);
}
/*
* cte_publish_all
*
* Publish an event to all necessary event queues. The event, e, must
* be zallocated by the caller, and the event's flags and type must be
* set. The rest of the event's fields are initialized here.
*/
{
ct_equeue_t *q;
int negend;
e->cte_contract = ct;
e->cte_refs = 3;
/*
* For a negotiation event we set the ct->ct_nevent field of the
* contract for the duration of the negotiation
*/
negend = 0;
cte_hold(e);
} else if (e->cte_type == CT_EV_NEGEND) {
negend = 1;
}
gethrestime(&ts);
/*
* ct_evtlock simply (and only) ensures that two events sent
* from the same contract are delivered to all queues in the
* same order.
*/
/*
* CTEL_CONTRACT - First deliver to the contract queue, acking
* the event if the contract has been orphaned.
*/
else
}
/*
* CTEL_BUNDLE - Next deliver to the contract type's bundle
* queue.
*/
/*
* CTEL_PBUNDLE - Finally, if the contract has an owner,
* deliver to the owner's process bundle queue.
*/
/*
* proc_exit doesn't free event queues until it has
* abandoned all contracts.
*/
mutex_enter(&q->ctq_lock);
/*
* It is possible for this code to race with adoption; we
* publish the event indicating that the event may already
* be enqueued because adoption beat us to it (in which case
* cte_pubish() does nothing).
*/
} else {
cte_rele(e);
}
if (negend) {
}
return (evid);
}
/*
* cte_add_listener
*
* Add a new listener to an event queue.
*/
void
{
l->ctl_equeue = q;
l->ctl_position = NULL;
l->ctl_flags = 0;
mutex_enter(&q->ctq_lock);
list_insert_head(&q->ctq_tail, l);
list_insert_head(&q->ctq_listeners, l);
q->ctq_nlisteners++;
mutex_exit(&q->ctq_lock);
}
/*
* cte_remove_listener
*
* Remove a listener from an event queue. No other queue activities
* (e.g. cte_get event) may be in progress at this endpoint when this
* is called.
*/
void
{
ct_equeue_t *q = l->ctl_equeue;
ct_kevent_t *e;
mutex_enter(&q->ctq_lock);
if ((e = l->ctl_position) != NULL)
cte_qrele(q, l, e);
else
list_remove(&q->ctq_tail, l);
l->ctl_position = NULL;
q->ctq_nlisteners--;
list_remove(&q->ctq_listeners, l);
if (l->ctl_flags & CTLF_RELIABLE)
q->ctq_nreliable--;
/*
* If we are a the last listener of a dead reference counted
* queue (i.e. a process bundle) we free it. Otherwise we just
* trim any events which may have been kept around for our
* benefit.
*/
(q->ctq_nlisteners == 0)) {
} else {
mutex_exit(&q->ctq_lock);
}
}
/*
* cte_reset_listener
*
* Moves a listener's queue pointer to the beginning of the queue.
*/
void
{
ct_equeue_t *q = l->ctl_equeue;
mutex_enter(&q->ctq_lock);
/*
* We allow an asynchronous reset because it doesn't make a
* whole lot of sense to make reset block or fail. We already
* have most of the mechanism needed thanks to queue trimming,
* so implementing it isn't a big deal.
*/
if (l->ctl_flags & CTLF_COPYOUT)
l->ctl_flags |= CTLF_RESET;
/*
* Inform blocked readers.
*/
cv_broadcast(&l->ctl_cv);
mutex_exit(&q->ctq_lock);
}
/*
* cte_next_event
*
* Moves the event pointer for the specified listener to the next event
* on the queue. To avoid races, this movement only occurs if the
* specified event id matches that of the current event. This is used
* primarily to skip events that have been read but whose extended data
* haven't been copied out.
*/
int
{
ct_equeue_t *q = l->ctl_equeue;
mutex_enter(&q->ctq_lock);
if (l->ctl_flags & CTLF_COPYOUT)
l->ctl_flags |= CTLF_RESET;
mutex_exit(&q->ctq_lock);
return (0);
}
/*
* cte_get_event
*
* Reads an event from an event endpoint. If "nonblock" is clear, we
* block until a suitable event is ready. If "crit" is set, we only
* read critical events. Note that while "cr" is the caller's cred,
* "zuniqid" is the unique id of the zone the calling contract
* filesystem was mounted in.
*/
int
{
ct_equeue_t *q = l->ctl_equeue;
int result = 0;
int partial = 0;
/*
* cte_qreadable checks for CTLF_COPYOUT as well as ensures
* that there exists, and we are pointing to, an appropriate
* event. It may temporarily drop ctq_lock, but that doesn't
* really matter to us.
*/
mutex_enter(&q->ctq_lock);
if (nonblock) {
goto error;
}
goto error;
}
if (result == 0) {
goto error;
}
}
temp = l->ctl_position;
l->ctl_flags |= CTLF_COPYOUT;
mutex_exit(&q->ctq_lock);
/*
* We now have an event. Copy in the user event structure to
* see how much space we have to work with.
*/
if (result)
goto copyerr;
/*
* Determine what data we have and what the user should be
* allowed to see.
*/
NV_ENCODE_NATIVE) == 0);
}
NV_ENCODE_NATIVE) == 0);
}
/*
* If we have enough space, copy out the extended event data.
*/
if (len) {
if (size)
NV_ENCODE_NATIVE, KM_SLEEP) == 0);
if (gsize) {
}
/* This shouldn't have changed */
len);
if (result)
goto copyerr;
} else {
partial = 1;
}
}
/*
* Copy out the common event data.
*/
/*
* Only move our location in the queue if all copyouts were
* successful, the caller provided enough space for the entire
* event, and our endpoint wasn't reset or otherwise moved by
* another thread.
*/
mutex_enter(&q->ctq_lock);
if (result)
(l->ctl_position == temp))
/*
* Signal any readers blocked on our CTLF_COPYOUT.
*/
mutex_exit(&q->ctq_lock);
return (result);
}
/*
* cte_set_reliable
*
* Requests that events be reliably delivered to an event endpoint.
* Unread informative and acknowledged critical events will not be
* removed from the queue until this listener reads or skips them.
* Because a listener could maliciously request reliable delivery and
* then do nothing, this requires that PRIV_CONTRACT_EVENT be in the
* caller's effective set.
*/
int
{
ct_equeue_t *q = l->ctl_equeue;
int error;
return (error);
mutex_enter(&q->ctq_lock);
if ((l->ctl_flags & CTLF_RELIABLE) == 0) {
l->ctl_flags |= CTLF_RELIABLE;
q->ctq_nreliable++;
if (l->ctl_position != NULL)
}
mutex_exit(&q->ctq_lock);
return (0);
}