usr/src/uts/common/os/project.c
author Casper H.S. Dik <Casper.Dik@Sun.COM>
Wed, 28 Apr 2010 10:01:37 +0200
changeset 12273 63678502e95e
parent 6134 27ee74117a16
child 12725 334fd88ae67c
permissions -rw-r--r--
PSARC 2009/377 In-kernel pfexec implementation. PSARC 2009/378 Basic File Privileges PSARC 2010/072 RBAC update: user attrs from profiles 4912090 pfzsh(1) should exist 4912093 pfbash(1) should exist 4912096 pftcsh(1) should exist 6440298 Expand the basic privilege set in order to restrict file access 6859862 Move pfexec into the kernel 6919171 cred_t sidesteps kmem_debug; we need to be able to detect bad hold/free when they occur 6923721 The new SYS_SMB privilege is not backward compatible 6937562 autofs doesn't remove its door when the zone shuts down 6937727 Zones stuck on deathrow; netstack_zone keeps a credential reference to the zone 6940159 Implement PSARC 2010/072

/*
 * 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) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
 */

#include <sys/project.h>
#include <sys/modhash.h>
#include <sys/modctl.h>
#include <sys/kmem.h>
#include <sys/kstat.h>
#include <sys/atomic.h>
#include <sys/cmn_err.h>
#include <sys/proc.h>
#include <sys/rctl.h>
#include <sys/sunddi.h>
#include <sys/fss.h>
#include <sys/systm.h>
#include <sys/ipc_impl.h>
#include <sys/port_kernel.h>
#include <sys/task.h>
#include <sys/zone.h>
#include <sys/cpucaps.h>
#include <sys/klpd.h>

int project_hash_size = 64;
static kmutex_t project_hash_lock;
static kmutex_t projects_list_lock;
static mod_hash_t *projects_hash;
static kproject_t *projects_list;

rctl_hndl_t rc_project_cpu_shares;
rctl_hndl_t rc_project_cpu_cap;
rctl_hndl_t rc_project_nlwps;
rctl_hndl_t rc_project_ntasks;
rctl_hndl_t rc_project_msgmni;
rctl_hndl_t rc_project_semmni;
rctl_hndl_t rc_project_shmmax;
rctl_hndl_t rc_project_shmmni;
rctl_hndl_t rc_project_portids;
rctl_hndl_t rc_project_locked_mem;
rctl_hndl_t rc_project_contract;
rctl_hndl_t rc_project_crypto_mem;

/*
 * Dummy structure used when comparing projects.  This structure must be kept
 * identical to the first two fields of kproject_t.
 */
struct project_zone {
	projid_t	kpj_id;
	zoneid_t	kpj_zoneid;
};

/*
 * Projects
 *
 *   A dictionary of all active projects is maintained by the kernel so that we
 *   may track project usage and limits.  (By an active project, we mean a
 *   project associated with one or more task, and therefore with one or more
 *   processes.) We build the dictionary on top of the mod_hash facility, since
 *   project additions and deletions are relatively rare events.  An
 *   integer-to-pointer mapping is maintained within the hash, representing the
 *   map from project id to project structure.  All projects, including the
 *   primordial "project 0", are allocated via the project_hold_by_id()
 *   interface.
 *
 *   Currently, the project contains a reference count; the project ID, which is
 *   examined by the extended accounting subsystem as well as /proc; a resource
 *   control set, which contains the allowable values (and actions on exceeding
 *   those values) for controlled project-level resources on the system; and a
 *   number of CPU shares, which is used by the fair share scheduling class
 *   (FSS) to support its proportion-based scheduling algorithm.
 *
 * Reference counting convention
 *   The dictionary entry does not itself count as a reference--only references
 *   outside of the subsystem are tallied.  At the drop of the final external
 *   reference, the project entry is removed.  The reference counter keeps
 *   track of the number of threads *and* tasks within a project.
 *
 * Locking
 *   Walking the doubly-linked project list must be done while holding
 *   projects_list_lock.  Thus, any dereference of kpj_next or kpj_prev must be
 *   under projects_list_lock.
 *
 *   If both the hash lock, project_hash_lock, and the list lock are to be
 *   acquired, the hash lock is to be acquired first.
 */

static kstat_t *project_kstat_create(kproject_t *pj, zone_t *zone);
static void project_kstat_delete(kproject_t *pj);

static void
project_data_init(kproject_data_t *data)
{
	/*
	 * Initialize subsystem-specific data
	 */
	data->kpd_shmmax = 0;
	data->kpd_ipc.ipcq_shmmni = 0;
	data->kpd_ipc.ipcq_semmni = 0;
	data->kpd_ipc.ipcq_msgmni = 0;
	data->kpd_locked_mem = 0;
	data->kpd_locked_mem_ctl = UINT64_MAX;
	data->kpd_contract = 0;
	data->kpd_crypto_mem = 0;
	data->kpd_crypto_mem_ctl = UINT64_MAX;
	data->kpd_lockedmem_kstat = NULL;
}

/*ARGSUSED*/
static uint_t
project_hash_by_id(void *hash_data, mod_hash_key_t key)
{
	struct project_zone *pz = key;
	uint_t mykey;

	/*
	 * Merge the zoneid and projectid together to a 32-bit quantity, and
	 * then pass that in to the existing idhash.
	 */
	mykey = (pz->kpj_zoneid << 16) | pz->kpj_id;
	return (mod_hash_byid(hash_data, (mod_hash_key_t)(uintptr_t)mykey));
}

static int
project_hash_key_cmp(mod_hash_key_t key1, mod_hash_key_t key2)
{
	struct project_zone *pz1 = key1, *pz2 = key2;
	int retval;

	return ((int)((retval = pz1->kpj_id - pz2->kpj_id) != 0 ? retval :
	    pz1->kpj_zoneid - pz2->kpj_zoneid));
}

static void
project_hash_val_dtor(mod_hash_val_t val)
{
	kproject_t *kp = (kproject_t *)val;

	ASSERT(kp->kpj_count == 0);
	ASSERT(kp->kpj_cpucap == NULL);
	kmem_free(kp, sizeof (kproject_t));
}

/*
 * kproject_t *project_hold(kproject_t *)
 *
 * Overview
 *   Record that an additional reference on the indicated project has been
 *   taken.
 *
 * Return values
 *   A pointer to the indicated project.
 *
 * Caller's context
 *   project_hash_lock must not be held across the project_hold() call.
 */
kproject_t *
project_hold(kproject_t *p)
{
	mutex_enter(&project_hash_lock);
	ASSERT(p != NULL);
	p->kpj_count++;
	ASSERT(p->kpj_count != 0);
	mutex_exit(&project_hash_lock);
	return (p);
}

/*
 * kproject_t *project_hold_by_id(projid_t, zone_t *, int)
 *
 * Overview
 *   project_hold_by_id() performs a look-up in the dictionary of projects
 *   active on the system by specified project ID + zone and puts a hold on
 *   it.  The third argument defines the desired behavior in the case when
 *   project with given project ID cannot be found:
 *
 *   PROJECT_HOLD_INSERT	New entry is made in dictionary and the project
 *   				is added to the global list.
 *
 *   PROJECT_HOLD_FIND		Return NULL.
 *
 *   The project is returned with its reference count incremented by one.
 *   A new project derives its resource controls from those of project 0.
 *
 * Return values
 *   A pointer to the held project.
 *
 * Caller's context
 *   Caller must be in a context suitable for KM_SLEEP allocations.
 */
kproject_t *
project_hold_by_id(projid_t id, zone_t *zone, int flag)
{
	kproject_t *spare_p;
	kproject_t *p;
	mod_hash_hndl_t hndl;
	rctl_set_t *set;
	rctl_alloc_gp_t *gp;
	rctl_entity_p_t e;
	struct project_zone pz;
	boolean_t create = B_FALSE;
	kstat_t *ksp;

	pz.kpj_id = id;
	pz.kpj_zoneid = zone->zone_id;

	if (flag == PROJECT_HOLD_FIND) {
		mutex_enter(&project_hash_lock);

		if (mod_hash_find(projects_hash, (mod_hash_key_t)&pz,
		    (mod_hash_val_t)&p) == MH_ERR_NOTFOUND)
			p = NULL;
		else
			p->kpj_count++;

		mutex_exit(&project_hash_lock);
		return (p);
	}

	ASSERT(flag == PROJECT_HOLD_INSERT);

	spare_p = kmem_zalloc(sizeof (kproject_t), KM_SLEEP);
	set = rctl_set_create();

	gp = rctl_set_init_prealloc(RCENTITY_PROJECT);

	(void) mod_hash_reserve(projects_hash, &hndl);

	mutex_enter(&curproc->p_lock);
	mutex_enter(&project_hash_lock);
	if (mod_hash_find(projects_hash, (mod_hash_key_t)&pz,
	    (mod_hash_val_t *)&p) == MH_ERR_NOTFOUND) {

		p = spare_p;
		p->kpj_id = id;
		p->kpj_zone = zone;
		p->kpj_zoneid = zone->zone_id;
		p->kpj_count = 0;
		p->kpj_shares = 1;
		p->kpj_nlwps = 0;
		p->kpj_ntasks = 0;
		p->kpj_nlwps_ctl = INT_MAX;
		p->kpj_ntasks_ctl = INT_MAX;
		project_data_init(&p->kpj_data);
		e.rcep_p.proj = p;
		e.rcep_t = RCENTITY_PROJECT;
		p->kpj_rctls = rctl_set_init(RCENTITY_PROJECT, curproc, &e,
		    set, gp);
		mutex_exit(&curproc->p_lock);

		if (mod_hash_insert_reserve(projects_hash, (mod_hash_key_t)p,
		    (mod_hash_val_t)p, hndl))
			panic("unable to insert project %d(%p)", id, (void *)p);

		/*
		 * Insert project into global project list.
		 */
		mutex_enter(&projects_list_lock);
		if (id != 0 || zone != &zone0) {
			p->kpj_next = projects_list;
			p->kpj_prev = projects_list->kpj_prev;
			p->kpj_prev->kpj_next = p;
			projects_list->kpj_prev = p;
		} else {
			/*
			 * Special case: primordial hold on project 0.
			 */
			p->kpj_next = p;
			p->kpj_prev = p;
			projects_list = p;
		}
		mutex_exit(&projects_list_lock);
		create = B_TRUE;
	} else {
		mutex_exit(&curproc->p_lock);
		mod_hash_cancel(projects_hash, &hndl);
		kmem_free(spare_p, sizeof (kproject_t));
		rctl_set_free(set);
	}

	rctl_prealloc_destroy(gp);
	p->kpj_count++;
	mutex_exit(&project_hash_lock);

	/*
	 * The kstat stores the project's zone name, as zoneid's may change
	 * across reboots.
	 */
	if (create == B_TRUE) {
		/*
		 * Inform CPU caps framework of the new project
		 */
		cpucaps_project_add(p);
		/*
		 * Set up project kstats
		 */
		ksp = project_kstat_create(p, zone);
		mutex_enter(&project_hash_lock);
		ASSERT(p->kpj_data.kpd_lockedmem_kstat == NULL);
		p->kpj_data.kpd_lockedmem_kstat = ksp;
		mutex_exit(&project_hash_lock);
	}
	return (p);
}

/*
 * void project_rele(kproject_t *)
 *
 * Overview
 *   Advertise that one external reference to this project is no longer needed.
 *
 * Return values
 *   None.
 *
 * Caller's context
 *   No restriction on context.
 */
void
project_rele(kproject_t *p)
{
	mutex_enter(&project_hash_lock);
	ASSERT(p->kpj_count != 0);
	p->kpj_count--;
	if (p->kpj_count == 0) {

		/*
		 * Remove project from global list.
		 */
		mutex_enter(&projects_list_lock);
		p->kpj_next->kpj_prev = p->kpj_prev;
		p->kpj_prev->kpj_next = p->kpj_next;
		if (projects_list == p)
			projects_list = p->kpj_next;
		mutex_exit(&projects_list_lock);

		cpucaps_project_remove(p);

		rctl_set_free(p->kpj_rctls);
		project_kstat_delete(p);

		if (p->kpj_klpd != NULL)
			klpd_freelist(&p->kpj_klpd);

		if (mod_hash_destroy(projects_hash, (mod_hash_key_t)p))
			panic("unable to delete project %d zone %d", p->kpj_id,
			    p->kpj_zoneid);

	}
	mutex_exit(&project_hash_lock);
}

/*
 * int project_walk_all(zoneid_t, int (*)(kproject_t *, void *), void *)
 *
 * Overview
 *   Walk the project list for the given zoneid with a callback.
 *
 * Return values
 *   -1 for an invalid walk, number of projects visited otherwise.
 *
 * Caller's context
 *   projects_list_lock must not be held, as it is acquired by
 *   project_walk_all().  Accordingly, callbacks may not perform KM_SLEEP
 *   allocations.
 */
int
project_walk_all(zoneid_t zoneid, int (*cb)(kproject_t *, void *),
    void *walk_data)
{
	int cnt = 0;
	kproject_t *kp = proj0p;

	mutex_enter(&projects_list_lock);
	do {
		if (zoneid != ALL_ZONES && kp->kpj_zoneid != zoneid)
			continue;
		if (cb(kp, walk_data) == -1) {
			cnt = -1;
			break;
		} else {
			cnt++;
		}
	} while ((kp = kp->kpj_next) != proj0p);
	mutex_exit(&projects_list_lock);
	return (cnt);
}

/*
 * projid_t curprojid(void)
 *
 * Overview
 *   Return project ID of the current thread
 *
 * Caller's context
 *   No restrictions.
 */
projid_t
curprojid()
{
	return (ttoproj(curthread)->kpj_id);
}

/*
 * project.cpu-shares resource control support.
 */
/*ARGSUSED*/
static rctl_qty_t
project_cpu_shares_usage(rctl_t *rctl, struct proc *p)
{
	ASSERT(MUTEX_HELD(&p->p_lock));
	return (p->p_task->tk_proj->kpj_shares);
}

/*ARGSUSED*/
static int
project_cpu_shares_set(rctl_t *rctl, struct proc *p, rctl_entity_p_t *e,
    rctl_qty_t nv)
{
	ASSERT(MUTEX_HELD(&p->p_lock));
	ASSERT(e->rcep_t == RCENTITY_PROJECT);
	if (e->rcep_p.proj == NULL)
		return (0);

	e->rcep_p.proj->kpj_shares = nv;

	return (0);
}

static rctl_ops_t project_cpu_shares_ops = {
	rcop_no_action,
	project_cpu_shares_usage,
	project_cpu_shares_set,
	rcop_no_test
};


/*
 * project.cpu-cap resource control support.
 */
/*ARGSUSED*/
static rctl_qty_t
project_cpu_cap_get(rctl_t *rctl, struct proc *p)
{
	ASSERT(MUTEX_HELD(&p->p_lock));
	return (cpucaps_project_get(p->p_task->tk_proj));
}

/*ARGSUSED*/
static int
project_cpu_cap_set(rctl_t *rctl, struct proc *p, rctl_entity_p_t *e,
    rctl_qty_t nv)
{
	kproject_t *kpj = e->rcep_p.proj;

	ASSERT(MUTEX_HELD(&p->p_lock));
	ASSERT(e->rcep_t == RCENTITY_PROJECT);
	if (kpj == NULL)
		return (0);

	/*
	 * set cap to the new value.
	 */
	return (cpucaps_project_set(kpj,  nv));
}

static rctl_ops_t project_cpu_cap_ops = {
	rcop_no_action,
	project_cpu_cap_get,
	project_cpu_cap_set,
	rcop_no_test
};

/*ARGSUSED*/
static rctl_qty_t
project_lwps_usage(rctl_t *r, proc_t *p)
{
	kproject_t *pj;
	rctl_qty_t nlwps;

	ASSERT(MUTEX_HELD(&p->p_lock));
	pj = p->p_task->tk_proj;
	mutex_enter(&p->p_zone->zone_nlwps_lock);
	nlwps = pj->kpj_nlwps;
	mutex_exit(&p->p_zone->zone_nlwps_lock);

	return (nlwps);
}

/*ARGSUSED*/
static int
project_lwps_test(rctl_t *r, proc_t *p, rctl_entity_p_t *e, rctl_val_t *rcntl,
    rctl_qty_t incr, uint_t flags)
{
	rctl_qty_t nlwps;

	ASSERT(MUTEX_HELD(&p->p_lock));
	ASSERT(MUTEX_HELD(&p->p_zone->zone_nlwps_lock));
	ASSERT(e->rcep_t == RCENTITY_PROJECT);
	if (e->rcep_p.proj == NULL)
		return (0);

	nlwps = e->rcep_p.proj->kpj_nlwps;
	if (nlwps + incr > rcntl->rcv_value)
		return (1);

	return (0);
}

/*ARGSUSED*/
static int
project_lwps_set(rctl_t *rctl, struct proc *p, rctl_entity_p_t *e,
    rctl_qty_t nv) {

	ASSERT(MUTEX_HELD(&p->p_lock));
	ASSERT(e->rcep_t == RCENTITY_PROJECT);
	if (e->rcep_p.proj == NULL)
		return (0);

	e->rcep_p.proj->kpj_nlwps_ctl = nv;
	return (0);
}

static rctl_ops_t project_lwps_ops = {
	rcop_no_action,
	project_lwps_usage,
	project_lwps_set,
	project_lwps_test,
};

/*ARGSUSED*/
static rctl_qty_t
project_ntasks_usage(rctl_t *r, proc_t *p)
{
	kproject_t *pj;
	rctl_qty_t ntasks;

	ASSERT(MUTEX_HELD(&p->p_lock));
	pj = p->p_task->tk_proj;
	mutex_enter(&p->p_zone->zone_nlwps_lock);
	ntasks = pj->kpj_ntasks;
	mutex_exit(&p->p_zone->zone_nlwps_lock);

	return (ntasks);
}

/*ARGSUSED*/
static int
project_ntasks_test(rctl_t *r, proc_t *p, rctl_entity_p_t *e, rctl_val_t *rcntl,
    rctl_qty_t incr, uint_t flags)
{
	rctl_qty_t ntasks;

	ASSERT(MUTEX_HELD(&p->p_lock));
	ASSERT(e->rcep_t == RCENTITY_PROJECT);
	ntasks = e->rcep_p.proj->kpj_ntasks;
	if (ntasks + incr > rcntl->rcv_value)
		return (1);

	return (0);
}

/*ARGSUSED*/
static int
project_ntasks_set(rctl_t *rctl, struct proc *p, rctl_entity_p_t *e,
    rctl_qty_t nv) {

	ASSERT(MUTEX_HELD(&p->p_lock));
	ASSERT(e->rcep_t == RCENTITY_PROJECT);
	e->rcep_p.proj->kpj_ntasks_ctl = nv;
	return (0);
}

static rctl_ops_t project_tasks_ops = {
	rcop_no_action,
	project_ntasks_usage,
	project_ntasks_set,
	project_ntasks_test,
};

/*
 * project.max-shm-memory resource control support.
 */

/*ARGSUSED*/
static int
project_shmmax_test(struct rctl *rctl, struct proc *p, rctl_entity_p_t *e,
    rctl_val_t *rval, rctl_qty_t inc, uint_t flags)
{
	rctl_qty_t v;
	ASSERT(MUTEX_HELD(&p->p_lock));
	ASSERT(e->rcep_t == RCENTITY_PROJECT);
	v = e->rcep_p.proj->kpj_data.kpd_shmmax + inc;
	if (v > rval->rcv_value)
		return (1);

	return (0);
}

static rctl_ops_t project_shmmax_ops = {
	rcop_no_action,
	rcop_no_usage,
	rcop_no_set,
	project_shmmax_test
};

/*
 * project.max-shm-ids resource control support.
 */

/*ARGSUSED*/
static int
project_shmmni_test(struct rctl *rctl, struct proc *p, rctl_entity_p_t *e,
    rctl_val_t *rval, rctl_qty_t inc, uint_t flags)
{
	rctl_qty_t v;
	ASSERT(MUTEX_HELD(&p->p_lock));
	ASSERT(e->rcep_t == RCENTITY_PROJECT);
	v = e->rcep_p.proj->kpj_data.kpd_ipc.ipcq_shmmni + inc;
	if (v > rval->rcv_value)
		return (1);

	return (0);
}

static rctl_ops_t project_shmmni_ops = {
	rcop_no_action,
	rcop_no_usage,
	rcop_no_set,
	project_shmmni_test
};

/*
 * project.max-sem-ids resource control support.
 */

/*ARGSUSED*/
static int
project_semmni_test(struct rctl *rctl, struct proc *p, rctl_entity_p_t *e,
    rctl_val_t *rval, rctl_qty_t inc, uint_t flags)
{
	rctl_qty_t v;
	ASSERT(MUTEX_HELD(&p->p_lock));
	ASSERT(e->rcep_t == RCENTITY_PROJECT);
	v = e->rcep_p.proj->kpj_data.kpd_ipc.ipcq_semmni + inc;
	if (v > rval->rcv_value)
		return (1);

	return (0);
}

static rctl_ops_t project_semmni_ops = {
	rcop_no_action,
	rcop_no_usage,
	rcop_no_set,
	project_semmni_test
};

/*
 * project.max-msg-ids resource control support.
 */

/*ARGSUSED*/
static int
project_msgmni_test(struct rctl *rctl, struct proc *p, rctl_entity_p_t *e,
    rctl_val_t *rval, rctl_qty_t inc, uint_t flags)
{
	rctl_qty_t v;
	ASSERT(MUTEX_HELD(&p->p_lock));
	ASSERT(e->rcep_t == RCENTITY_PROJECT);
	v = e->rcep_p.proj->kpj_data.kpd_ipc.ipcq_msgmni + inc;
	if (v > rval->rcv_value)
		return (1);

	return (0);
}

static rctl_ops_t project_msgmni_ops = {
	rcop_no_action,
	rcop_no_usage,
	rcop_no_set,
	project_msgmni_test
};

/*ARGSUSED*/
static rctl_qty_t
project_locked_mem_usage(rctl_t *rctl, struct proc *p)
{
	rctl_qty_t q;
	ASSERT(MUTEX_HELD(&p->p_lock));
	mutex_enter(&p->p_zone->zone_mem_lock);
	q = p->p_task->tk_proj->kpj_data.kpd_locked_mem;
	mutex_exit(&p->p_zone->zone_mem_lock);
	return (q);
}

/*ARGSUSED*/
static int
project_locked_mem_test(struct rctl *rctl, struct proc *p, rctl_entity_p_t *e,
    rctl_val_t *rval, rctl_qty_t inc, uint_t flags)
{
	rctl_qty_t q;
	ASSERT(MUTEX_HELD(&p->p_lock));
	ASSERT(MUTEX_HELD(&p->p_zone->zone_mem_lock));
	q = p->p_task->tk_proj->kpj_data.kpd_locked_mem;
	if (q + inc > rval->rcv_value)
		return (1);
	return (0);
}

/*ARGSUSED*/
static int
project_locked_mem_set(rctl_t *rctl, struct proc *p, rctl_entity_p_t *e,
    rctl_qty_t nv) {

	ASSERT(MUTEX_HELD(&p->p_lock));
	ASSERT(e->rcep_t == RCENTITY_PROJECT);
	if (e->rcep_p.proj == NULL)
		return (0);

	e->rcep_p.proj->kpj_data.kpd_locked_mem_ctl = nv;
	return (0);
}

static rctl_ops_t project_locked_mem_ops = {
	rcop_no_action,
	project_locked_mem_usage,
	project_locked_mem_set,
	project_locked_mem_test
};

/*
 * project.max-contracts resource control support.
 */

/*ARGSUSED*/
static int
project_contract_test(struct rctl *rctl, struct proc *p, rctl_entity_p_t *e,
    rctl_val_t *rval, rctl_qty_t inc, uint_t flags)
{
	rctl_qty_t v;

	ASSERT(MUTEX_HELD(&p->p_lock));
	ASSERT(e->rcep_t == RCENTITY_PROJECT);

	v = e->rcep_p.proj->kpj_data.kpd_contract + inc;

	if ((p->p_task != NULL) && (p->p_task->tk_proj) != NULL &&
	    (v > rval->rcv_value))
		return (1);

	return (0);
}

static rctl_ops_t project_contract_ops = {
	rcop_no_action,
	rcop_no_usage,
	rcop_no_set,
	project_contract_test
};

/*ARGSUSED*/
static rctl_qty_t
project_crypto_usage(rctl_t *r, proc_t *p)
{
	ASSERT(MUTEX_HELD(&p->p_lock));
	return (p->p_task->tk_proj->kpj_data.kpd_crypto_mem);
}

/*ARGSUSED*/
static int
project_crypto_set(rctl_t *r, proc_t *p, rctl_entity_p_t *e,
    rctl_qty_t nv)
{
	ASSERT(MUTEX_HELD(&p->p_lock));
	ASSERT(e->rcep_t == RCENTITY_PROJECT);
	if (e->rcep_p.proj == NULL)
		return (0);

	e->rcep_p.proj->kpj_data.kpd_crypto_mem_ctl = nv;
	return (0);
}

/*ARGSUSED*/
static int
project_crypto_test(rctl_t *r, proc_t *p, rctl_entity_p_t *e,
    rctl_val_t *rval, rctl_qty_t incr, uint_t flags)
{
	rctl_qty_t v;
	ASSERT(MUTEX_HELD(&p->p_lock));
	ASSERT(e->rcep_t == RCENTITY_PROJECT);
	v = e->rcep_p.proj->kpj_data.kpd_crypto_mem + incr;
	if (v > rval->rcv_value)
		return (1);
	return (0);
}

static rctl_ops_t project_crypto_mem_ops = {
	rcop_no_action,
	project_crypto_usage,
	project_crypto_set,
	project_crypto_test
};

/*
 * void project_init(void)
 *
 * Overview
 *   Initialize the project subsystem, including the primordial project 0 entry.
 *   Register generic project resource controls, if any.
 *
 * Return values
 *   None.
 *
 * Caller's context
 *   Safe for KM_SLEEP allocations.
 */
void
project_init(void)
{
	rctl_qty_t shmmni, shmmax, qty;
	boolean_t check;

	projects_hash = mod_hash_create_extended("projects_hash",
	    project_hash_size, mod_hash_null_keydtor, project_hash_val_dtor,
	    project_hash_by_id,
	    (void *)(uintptr_t)mod_hash_iddata_gen(project_hash_size),
	    project_hash_key_cmp, KM_SLEEP);

	rc_project_cpu_shares = rctl_register("project.cpu-shares",
	    RCENTITY_PROJECT, RCTL_GLOBAL_SIGNAL_NEVER |
	    RCTL_GLOBAL_DENY_NEVER | RCTL_GLOBAL_NOBASIC |
	    RCTL_GLOBAL_COUNT | RCTL_GLOBAL_SYSLOG_NEVER,
	    FSS_MAXSHARES, FSS_MAXSHARES,
	    &project_cpu_shares_ops);
	rctl_add_default_limit("project.cpu-shares", 1, RCPRIV_PRIVILEGED,
	    RCTL_LOCAL_NOACTION);

	rc_project_cpu_cap = rctl_register("project.cpu-cap",
	    RCENTITY_PROJECT, RCTL_GLOBAL_SIGNAL_NEVER |
	    RCTL_GLOBAL_DENY_ALWAYS | RCTL_GLOBAL_NOBASIC |
	    RCTL_GLOBAL_COUNT | RCTL_GLOBAL_SYSLOG_NEVER |
	    RCTL_GLOBAL_INFINITE,
	    MAXCAP, MAXCAP, &project_cpu_cap_ops);

	rc_project_nlwps = rctl_register("project.max-lwps", RCENTITY_PROJECT,
	    RCTL_GLOBAL_NOACTION | RCTL_GLOBAL_NOBASIC | RCTL_GLOBAL_COUNT,
	    INT_MAX, INT_MAX, &project_lwps_ops);

	rc_project_ntasks = rctl_register("project.max-tasks", RCENTITY_PROJECT,
	    RCTL_GLOBAL_NOACTION | RCTL_GLOBAL_NOBASIC | RCTL_GLOBAL_COUNT,
	    INT_MAX, INT_MAX, &project_tasks_ops);

	/*
	 * This rctl handle is used by /dev/crypto. It is here rather than
	 * in misc/kcf or the drv/crypto module because resource controls
	 * currently don't allow modules to be unloaded, and the control
	 * must be registered before init starts.
	 */
	rc_project_crypto_mem = rctl_register("project.max-crypto-memory",
	    RCENTITY_PROJECT, RCTL_GLOBAL_DENY_ALWAYS | RCTL_GLOBAL_NOBASIC |
	    RCTL_GLOBAL_BYTES, UINT64_MAX, UINT64_MAX,
	    &project_crypto_mem_ops);

	/*
	 * Default to a quarter of the machine's memory
	 */
	qty = availrmem_initial << (PAGESHIFT - 2);
	rctl_add_default_limit("project.max-crypto-memory", qty,
	    RCPRIV_PRIVILEGED, RCTL_LOCAL_DENY);

	/*
	 * System V IPC resource controls
	 */
	rc_project_semmni = rctl_register("project.max-sem-ids",
	    RCENTITY_PROJECT, RCTL_GLOBAL_DENY_ALWAYS | RCTL_GLOBAL_NOBASIC |
	    RCTL_GLOBAL_COUNT, IPC_IDS_MAX, IPC_IDS_MAX, &project_semmni_ops);
	rctl_add_legacy_limit("project.max-sem-ids", "semsys",
	    "seminfo_semmni", 128, IPC_IDS_MAX);

	rc_project_msgmni = rctl_register("project.max-msg-ids",
	    RCENTITY_PROJECT, RCTL_GLOBAL_DENY_ALWAYS | RCTL_GLOBAL_NOBASIC |
	    RCTL_GLOBAL_COUNT, IPC_IDS_MAX, IPC_IDS_MAX, &project_msgmni_ops);
	rctl_add_legacy_limit("project.max-msg-ids", "msgsys",
	    "msginfo_msgmni", 128, IPC_IDS_MAX);

	rc_project_shmmni = rctl_register("project.max-shm-ids",
	    RCENTITY_PROJECT, RCTL_GLOBAL_DENY_ALWAYS | RCTL_GLOBAL_NOBASIC |
	    RCTL_GLOBAL_COUNT, IPC_IDS_MAX, IPC_IDS_MAX, &project_shmmni_ops);
	rctl_add_legacy_limit("project.max-shm-ids", "shmsys",
	    "shminfo_shmmni", 128, IPC_IDS_MAX);

	rc_project_shmmax = rctl_register("project.max-shm-memory",
	    RCENTITY_PROJECT, RCTL_GLOBAL_DENY_ALWAYS | RCTL_GLOBAL_NOBASIC |
	    RCTL_GLOBAL_BYTES, UINT64_MAX, UINT64_MAX, &project_shmmax_ops);

	check = B_FALSE;
	if (!mod_sysvar("shmsys", "shminfo_shmmni", &shmmni))
		shmmni = 100;
	else
		check = B_TRUE;
	if (!mod_sysvar("shmsys", "shminfo_shmmax", &shmmax))
		shmmax = 0x800000;
	else
		check = B_TRUE;

	/*
	 * Default to a quarter of the machine's memory
	 */
	qty = availrmem_initial << (PAGESHIFT - 2);
	if (check) {
		if ((shmmax > 0) && (UINT64_MAX / shmmax <= shmmni))
			qty = UINT64_MAX;
		else if (shmmni * shmmax > qty)
			qty = shmmni * shmmax;
	}
	rctl_add_default_limit("project.max-shm-memory", qty,
	    RCPRIV_PRIVILEGED, RCTL_LOCAL_DENY);

	/*
	 * Event Ports resource controls
	 */

	rc_project_portids = rctl_register("project.max-port-ids",
	    RCENTITY_PROJECT, RCTL_GLOBAL_DENY_ALWAYS | RCTL_GLOBAL_NOBASIC |
	    RCTL_GLOBAL_COUNT, PORT_MAX_PORTS, PORT_MAX_PORTS,
	    &rctl_absolute_ops);
	rctl_add_default_limit("project.max-port-ids", PORT_DEFAULT_PORTS,
	    RCPRIV_PRIVILEGED, RCTL_LOCAL_DENY);

	/*
	 * Resource control for locked memory
	 */
	rc_project_locked_mem = rctl_register(
	    "project.max-locked-memory", RCENTITY_PROJECT,
	    RCTL_GLOBAL_DENY_ALWAYS | RCTL_GLOBAL_NOBASIC | RCTL_GLOBAL_BYTES,
	    UINT64_MAX, UINT64_MAX, &project_locked_mem_ops);

	/*
	 * Per project limit on contracts.
	 */
	rc_project_contract = rctl_register("project.max-contracts",
	    RCENTITY_PROJECT, RCTL_GLOBAL_DENY_ALWAYS | RCTL_GLOBAL_NOBASIC |
	    RCTL_GLOBAL_COUNT, INT_MAX, INT_MAX, &project_contract_ops);
	rctl_add_default_limit("project.max-contracts", 10000,
	    RCPRIV_PRIVILEGED, RCTL_LOCAL_DENY);

	t0.t_proj = proj0p = project_hold_by_id(0, &zone0,
	    PROJECT_HOLD_INSERT);

	mutex_enter(&p0.p_lock);
	proj0p->kpj_nlwps = p0.p_lwpcnt;
	mutex_exit(&p0.p_lock);
	proj0p->kpj_ntasks = 1;
}

static int
project_lockedmem_kstat_update(kstat_t *ksp, int rw)
{
	kproject_t *pj = ksp->ks_private;
	kproject_kstat_t *kpk = ksp->ks_data;

	if (rw == KSTAT_WRITE)
		return (EACCES);

	kpk->kpk_usage.value.ui64 = pj->kpj_data.kpd_locked_mem;
	kpk->kpk_value.value.ui64 = pj->kpj_data.kpd_locked_mem_ctl;
	return (0);
}

static kstat_t *
project_kstat_create(kproject_t *pj, zone_t *zone)
{
	kstat_t *ksp;
	kproject_kstat_t *kpk;
	char *zonename = zone->zone_name;

	ksp = rctl_kstat_create_project(pj, "lockedmem", KSTAT_TYPE_NAMED,
	    sizeof (kproject_kstat_t) / sizeof (kstat_named_t),
	    KSTAT_FLAG_VIRTUAL);

	if (ksp == NULL)
		return (NULL);

	kpk = ksp->ks_data = kmem_alloc(sizeof (kproject_kstat_t), KM_SLEEP);
	ksp->ks_data_size += strlen(zonename) + 1;
	kstat_named_init(&kpk->kpk_zonename, "zonename", KSTAT_DATA_STRING);
	kstat_named_setstr(&kpk->kpk_zonename, zonename);
	kstat_named_init(&kpk->kpk_usage, "usage", KSTAT_DATA_UINT64);
	kstat_named_init(&kpk->kpk_value, "value", KSTAT_DATA_UINT64);
	ksp->ks_update = project_lockedmem_kstat_update;
	ksp->ks_private = pj;
	kstat_install(ksp);

	return (ksp);
}

static void
project_kstat_delete(kproject_t *pj)
{
	void *data;

	if (pj->kpj_data.kpd_lockedmem_kstat != NULL) {
		data = pj->kpj_data.kpd_lockedmem_kstat->ks_data;
		kstat_delete(pj->kpj_data.kpd_lockedmem_kstat);
		kmem_free(data, sizeof (kproject_kstat_t));
	}
	pj->kpj_data.kpd_lockedmem_kstat = NULL;
}