/*
 * 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 2006 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/


#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <sys/types.h>
#include <sys/param.h>
#include <sys/sysmacros.h>
#include <sys/signal.h>
#include <sys/pcb.h>
#include <sys/user.h>
#include <sys/systm.h>
#include <sys/sysinfo.h>
#include <sys/var.h>
#include <sys/errno.h>
#include <sys/cred.h>
#include <sys/proc.h>
#include <sys/procset.h>
#include <sys/debug.h>
#include <sys/inline.h>
#include <sys/priocntl.h>
#include <sys/disp.h>
#include <sys/class.h>
#include <sys/modctl.h>
#include <sys/t_lock.h>
#include <sys/uadmin.h>
#include <sys/cmn_err.h>
#include <sys/policy.h>

/*
 * Structure used to pass arguments to the proccmp() function.
 * The arguments must be passed in a structure because proccmp()
 * is called indirectly through the dotoprocs() function which
 * will only pass through a single one word argument.
 */
struct pcmpargs {
	id_t	*pcmp_cidp;
	int	*pcmp_cntp;
	kthread_id_t	*pcmp_retthreadp;
};

/*
 * Structure used to pass arguments to the setparms() function
 * which is called indirectly through dotoprocs().
 */
struct stprmargs {
	struct pcparms	*stp_parmsp;	/* pointer to parameters */
	int		stp_error;	/* some errors returned here */
};

#if defined(_SYSCALL32_IMPL) && _LONG_LONG_ALIGNMENT_32 == 4
/*
 * A vaparm_t is an int followed by a long long -- this packs differently
 * between the 64-bit kernel ABI and the 32-bit user ABI.
 */
static int
copyin_vaparms32(caddr_t arg, pc_vaparms_t *vap, uio_seg_t seg)
{
	pc_vaparms32_t vaparms32;
	pc_vaparm32_t *src;
	pc_vaparm_t *dst;
	uint_t cnt;

	ASSERT(get_udatamodel() == DATAMODEL_ILP32);

	if ((seg == UIO_USERSPACE ? copyin : kcopy)(arg, &vaparms32,
	    sizeof (vaparms32)))
		return (EFAULT);

	vap->pc_vaparmscnt = vaparms32.pc_vaparmscnt;
	if ((cnt = vaparms32.pc_vaparmscnt) > PC_VAPARMCNT)
		cnt = PC_VAPARMCNT;
	for (src = vaparms32.pc_parms, dst = vap->pc_parms;
	    cnt--; src++, dst++) {
		dst->pc_key = src->pc_key;
		dst->pc_parm = src->pc_parm;
	}
	return (0);
}

#define	COPYIN_VAPARMS(arg, vap, size, seg)	\
	(get_udatamodel() == DATAMODEL_NATIVE ?	\
	(*copyinfn)(arg, vap, size) : copyin_vaparms32(arg, vap, seg))

#else

#define	COPYIN_VAPARMS(arg, vap, size, seg)	(*copyinfn)(arg, vap, size)

#endif

static int donice(procset_t *, pcnice_t *);
static int proccmp(proc_t *, struct pcmpargs *);
static int setparms(proc_t *, struct stprmargs *);
extern int threadcmp(struct pcmpargs *, kthread_id_t);

/*
 * The priocntl system call.
 */
long
priocntl_common(int pc_version, procset_t *psp, int cmd, caddr_t arg,
    caddr_t arg2, uio_seg_t seg)
{
	pcinfo_t		pcinfo;
	pcparms_t		pcparms;
	pcnice_t		pcnice;
	pcadmin_t		pcadmin;
	pcpri_t			pcpri;
	procset_t		procset;
	struct stprmargs	stprmargs;
	struct pcmpargs		pcmpargs;
	pc_vaparms_t		vaparms;
	char			clname[PC_CLNMSZ];
	char			*outstr;
	int			count;
	kthread_id_t		retthreadp;
	proc_t			*initpp;
	int			clnullflag;
	int			error = 0;
	int			error1 = 0;
	int			rv = 0;
	pid_t			saved_pid;
	id_t			classid;
	int			size;
	int (*copyinfn)(const void *, void *, size_t);
	int (*copyoutfn)(const void *, void *, size_t);

	/*
	 * First just check the version number. Right now there is only
	 * one version we know about and support.  If we get some other
	 * version number from the application it may be that the
	 * application was built with some future version and is trying
	 * to run on an old release of the system (that's us).  In any
	 * case if we don't recognize the version number all we can do is
	 * return error.
	 */
	if (pc_version != PC_VERSION)
		return (set_errno(EINVAL));

	if (seg == UIO_USERSPACE) {
		copyinfn = copyin;
		copyoutfn = copyout;
	} else {
		copyinfn = kcopy;
		copyoutfn = kcopy;
	}

	switch (cmd) {
	case PC_GETCID:
		/*
		 * If the arg pointer is NULL, the user just wants to
		 * know the number of classes. If non-NULL, the pointer
		 * should point to a valid user pcinfo buffer.  In the
		 * dynamic world we need to return the number of loaded
		 * classes, not the max number of available classes that
		 * can be loaded.
		 */
		if (arg == NULL) {
			rv = loaded_classes;
			break;
		} else {
			if ((*copyinfn)(arg, &pcinfo, sizeof (pcinfo)))
				return (set_errno(EFAULT));
		}

		pcinfo.pc_clname[PC_CLNMSZ-1] = '\0';

		/*
		 * Get the class ID corresponding to user supplied name.
		 */
		error = getcid(pcinfo.pc_clname, &pcinfo.pc_cid);
		if (error)
			return (set_errno(error));

		/*
		 * Can't get info about the sys class.
		 */
		if (pcinfo.pc_cid == 0)
			return (set_errno(EINVAL));

		/*
		 * Get the class specific information.
		 * we MUST make sure that the class has not already
		 * been unloaded before we try the CL_GETCLINFO.
		 * If it has then we need to load it.
		 */
		error =
		    scheduler_load(pcinfo.pc_clname, &sclass[pcinfo.pc_cid]);
		if (error)
			return (set_errno(error));
		error = CL_GETCLINFO(&sclass[pcinfo.pc_cid], pcinfo.pc_clinfo);
		if (error)
			return (set_errno(error));

		if ((*copyoutfn)(&pcinfo, arg, sizeof (pcinfo)))
			return (set_errno(EFAULT));

		rv = loaded_classes;

		break;

	case PC_GETCLINFO:
		/*
		 * If the arg pointer is NULL, the user just wants to know
		 * the number of classes. If non-NULL, the pointer should
		 * point to a valid user pcinfo buffer.
		 */
		if (arg == NULL) {
			rv = loaded_classes;
			break;
		} else {
			if ((*copyinfn)(arg, &pcinfo, sizeof (pcinfo)))
				return (set_errno(EFAULT));
		}

		if (pcinfo.pc_cid >= loaded_classes || pcinfo.pc_cid < 1)
			return (set_errno(EINVAL));

		(void) strncpy(pcinfo.pc_clname, sclass[pcinfo.pc_cid].cl_name,
		    PC_CLNMSZ);

		/*
		 * Get the class specific information.  we MUST make sure
		 * that the class has not already been unloaded before we
		 * try the CL_GETCLINFO.  If it has then we need to load
		 * it.
		 */
		error =
		    scheduler_load(pcinfo.pc_clname, &sclass[pcinfo.pc_cid]);
		if (error)
			return (set_errno(error));
		error = CL_GETCLINFO(&sclass[pcinfo.pc_cid], pcinfo.pc_clinfo);
		if (error)
			return (set_errno(error));

		if ((*copyoutfn)(&pcinfo, arg, sizeof (pcinfo)))
			return (set_errno(EFAULT));

		rv = loaded_classes;
		break;

	case PC_SETPARMS:
	case PC_SETXPARMS:
		/*
		 * First check the validity of the parameters we got from
		 * the user.  We don't do any permissions checking here
		 * because it's done on a per thread basis by parmsset().
		 */
		if (cmd == PC_SETPARMS) {
			if ((*copyinfn)(arg, &pcparms, sizeof (pcparms)))
				return (set_errno(EFAULT));

			error = parmsin(&pcparms, NULL);
		} else {
			if ((*copyinfn)(arg, clname, PC_CLNMSZ) ||
			    COPYIN_VAPARMS(arg2, &vaparms, sizeof (vaparms),
			    seg))
				return (set_errno(EFAULT));
			clname[PC_CLNMSZ-1] = '\0';

			if (getcid(clname, &pcparms.pc_cid))
				return (set_errno(EINVAL));

			error = parmsin(&pcparms, &vaparms);
		}

		if (error)
			return (set_errno(error));

		/*
		 * Get the procset from the user.
		 */
		if ((*copyinfn)(psp, &procset, sizeof (procset)))
			return (set_errno(EFAULT));

		/*
		 * For performance we do a quick check here to catch
		 * common cases where the current thread is the only one
		 * in the set.  In such cases we can call parmsset()
		 * directly, avoiding the relatively lengthy path through
		 * dotoprocs().  The underlying classes expect pidlock to
		 * be held.
		 */
		if (cur_inset_only(&procset) == B_TRUE) {
			/* do a single LWP */
			if ((procset.p_lidtype == P_LWPID) ||
			    (procset.p_ridtype == P_LWPID)) {
				mutex_enter(&pidlock);
				mutex_enter(&curproc->p_lock);
				error = parmsset(&pcparms, curthread);
				mutex_exit(&curproc->p_lock);
				mutex_exit(&pidlock);
			} else {
				/* do the entire process otherwise */
				stprmargs.stp_parmsp = &pcparms;
				stprmargs.stp_error = 0;
				mutex_enter(&pidlock);
				error = setparms(curproc, &stprmargs);
				mutex_exit(&pidlock);
				if (error == 0 && stprmargs.stp_error != 0)
					error = stprmargs.stp_error;
			}
			if (error)
				return (set_errno(error));
		} else {
			stprmargs.stp_parmsp = &pcparms;
			stprmargs.stp_error = 0;

			error1 = error = ESRCH;

			/*
			 * The dotoprocs() call below will cause
			 * setparms() to be called for each thread in the
			 * specified procset. setparms() will in turn
			 * call parmsset() (which does the real work).
			 */
			if ((procset.p_lidtype != P_LWPID) ||
				(procset.p_ridtype != P_LWPID)) {
				error1 = dotoprocs(&procset, setparms,
				    (char *)&stprmargs);
			}

			/*
			 * take care of the case when any of the
			 * operands happen to be LWP's
			 */

			if ((procset.p_lidtype == P_LWPID) ||
			    (procset.p_ridtype == P_LWPID)) {
				error = dotolwp(&procset, parmsset,
				    (char *)&pcparms);
				/*
				 * Dotolwp() returns with p_lock held.
				 * This is required for the GETPARMS case
				 * below. So, here we just release the
				 * p_lock.
				 */
				if (MUTEX_HELD(&curproc->p_lock))
					mutex_exit(&curproc->p_lock);
			}

			/*
			 * If setparms() encounters a permissions error
			 * for one or more of the threads it returns
			 * EPERM in stp_error so dotoprocs() will
			 * continue through the thread set.  If
			 * dotoprocs() returned an error above, it was
			 * more serious than permissions and dotoprocs
			 * quit when the error was encountered.  We
			 * return the more serious error if there was
			 * one, otherwise we return EPERM if we got that
			 * back.
			 */
			if (error1 != ESRCH)
				error = error1;
			if (error == 0 && stprmargs.stp_error != 0)
				error = stprmargs.stp_error;
		}
		break;

	case PC_GETPARMS:
	case PC_GETXPARMS:
		if (cmd == PC_GETPARMS) {
			if ((*copyinfn)(arg, &pcparms, sizeof (pcparms)))
				return (set_errno(EFAULT));
		} else {
			if (arg != NULL) {
				if ((*copyinfn)(arg, clname, PC_CLNMSZ))
					return (set_errno(EFAULT));

				clname[PC_CLNMSZ-1] = '\0';

				if (getcid(clname, &pcparms.pc_cid))
					return (set_errno(EINVAL));
			} else
				pcparms.pc_cid = PC_CLNULL;

			if (COPYIN_VAPARMS(arg2, &vaparms, sizeof (vaparms),
			    seg))
				return (set_errno(EFAULT));
		}

		if (pcparms.pc_cid >= loaded_classes ||
		    (pcparms.pc_cid < 1 && pcparms.pc_cid != PC_CLNULL))
			return (set_errno(EINVAL));

		if ((*copyinfn)(psp, &procset, sizeof (procset)))
			return (set_errno(EFAULT));

		/*
		 * Check to see if the current thread is the only one
		 * in the set. If not we must go through the whole set
		 * to select a thread.
		 */
		if (cur_inset_only(&procset) == B_TRUE) {
			/* do a single LWP */
			if ((procset.p_lidtype == P_LWPID) ||
			    (procset.p_ridtype == P_LWPID)) {
				if (pcparms.pc_cid != PC_CLNULL &&
				    pcparms.pc_cid != curthread->t_cid) {
					/*
					 * Specified thread not in
					 * specified class.
					 */
					return (set_errno(ESRCH));
				} else {
					mutex_enter(&curproc->p_lock);
					retthreadp = curthread;
				}
			} else {
				count = 0;
				retthreadp = NULL;
				pcmpargs.pcmp_cidp = &pcparms.pc_cid;
				pcmpargs.pcmp_cntp = &count;
				pcmpargs.pcmp_retthreadp = &retthreadp;
				/*
				 * Specified thread not in specified class.
				 */
				if (pcparms.pc_cid != PC_CLNULL &&
				    pcparms.pc_cid != curthread->t_cid)
					return (set_errno(ESRCH));
				error = proccmp(curproc, &pcmpargs);
				if (error) {
					if (retthreadp != NULL)
						mutex_exit(&(curproc->p_lock));
					return (set_errno(error));
				}
			}
		} else {
			/*
			 * get initpp early to avoid lock ordering problems
			 * (we cannot get pidlock while holding any p_lock).
			 */
			mutex_enter(&pidlock);
			initpp = prfind(P_INITPID);
			mutex_exit(&pidlock);

			/*
			 * Select the thread (from the set) whose
			 * parameters we are going to return.  First we
			 * set up some locations for return values, then
			 * we call proccmp() indirectly through
			 * dotoprocs().  proccmp() will call a class
			 * specific routine which actually does the
			 * selection.  To understand how this works take
			 * a careful look at the code below, the
			 * dotoprocs() function, the proccmp() function,
			 * and the class specific cl_proccmp() functions.
			 */
			if (pcparms.pc_cid == PC_CLNULL)
				clnullflag = 1;
			else
				clnullflag = 0;
			count = 0;
			retthreadp = NULL;
			pcmpargs.pcmp_cidp = &pcparms.pc_cid;
			pcmpargs.pcmp_cntp = &count;
			pcmpargs.pcmp_retthreadp = &retthreadp;
			error1 = error = ESRCH;

			if ((procset.p_lidtype != P_LWPID) ||
			    (procset.p_ridtype != P_LWPID)) {
				error1 = dotoprocs(&procset, proccmp,
				    (char *)&pcmpargs);
			}

			/*
			 * take care of combination of LWP and process
			 * set case in a procset
			 */
			if ((procset.p_lidtype == P_LWPID) ||
			    (procset.p_ridtype == P_LWPID)) {
				error = dotolwp(&procset, threadcmp,
				    (char *)&pcmpargs);
			}

			/*
			 * Both proccmp() and threadcmp() return with the
			 * p_lock held for the ttoproc(retthreadp). This
			 * is required to make sure that the process we
			 * chose as the winner doesn't go away
			 * i.e. retthreadp has to be a valid pointer.
			 *
			 * The case below can only happen if the thread
			 * with the highest priority was not in your
			 * process.  In that case, dotolwp will return
			 * holding p_lock for both your process as well
			 * as the process in which retthreadp is a
			 * thread.
			 */
			if ((retthreadp != NULL) &&
			    (ttoproc(retthreadp) != curproc) &&
			    MUTEX_HELD(&(curproc)->p_lock))
				mutex_exit(&(curproc)->p_lock);

			ASSERT(retthreadp == NULL ||
			    MUTEX_HELD(&(ttoproc(retthreadp)->p_lock)));
			if (error1 != ESRCH)
				error = error1;
			if (error) {
				if (retthreadp != NULL)
				    mutex_exit(&(ttoproc(retthreadp)->p_lock));
				ASSERT(MUTEX_NOT_HELD(&(curproc)->p_lock));
				return (set_errno(error));
			}
			/*
			 * dotoprocs() ignores the init process if it is
			 * in the set, unless it was the only process found.
			 * Since we are getting parameters here rather than
			 * setting them, we want to make sure init is not
			 * excluded if it is in the set.
			 */
			if (initpp != NULL &&
			    procinset(initpp, &procset) &&
			    (retthreadp != NULL) &&
			    ttoproc(retthreadp) != initpp)
				(void) proccmp(initpp, &pcmpargs);

			/*
			 * If dotoprocs returned success it found at least
			 * one thread in the set.  If proccmp() failed to
			 * select a thread it is because the user specified
			 * a class and none of the threads in the set
			 * belonged to that class, or because the process
			 * specified was in the middle of exiting and had
			 * cleared its thread list.
			 */
			if (retthreadp == NULL) {
				/*
				 * Might be here and still holding p_lock
				 * if we did a dotolwp on an lwp that
				 * existed but was in the wrong class.
				 */
				if (MUTEX_HELD(&(curproc)->p_lock))
					mutex_exit(&(curproc)->p_lock);
				return (set_errno(ESRCH));
			}

			/*
			 * User can only use PC_CLNULL with one thread in set.
			 */
			if (clnullflag && count > 1) {
				if (retthreadp != NULL)
					mutex_exit(
					    &(ttoproc(retthreadp)->p_lock));
				ASSERT(MUTEX_NOT_HELD(&(curproc)->p_lock));
				return (set_errno(EINVAL));
			}
		}

		ASSERT(retthreadp == NULL ||
		    MUTEX_HELD(&(ttoproc(retthreadp)->p_lock)));
		/*
		 * It is possible to have retthreadp == NULL. Proccmp()
		 * in the rare case (p_tlist == NULL) could return without
		 * setting a value for retthreadp.
		 */
		if (retthreadp == NULL) {
			ASSERT(MUTEX_NOT_HELD(&(curproc)->p_lock));
			return (set_errno(ESRCH));
		}
		/*
		 * We've selected a thread so now get the parameters.
		 */
		parmsget(retthreadp, &pcparms);

		/*
		 * Prepare to return parameters to the user
		 */
		error = parmsout(&pcparms,
		    (cmd == PC_GETPARMS ? NULL : &vaparms));

		/*
		 * Save pid of selected thread before dropping p_lock.
		 */
		saved_pid = ttoproc(retthreadp)->p_pid;
		mutex_exit(&(ttoproc(retthreadp)->p_lock));
		ASSERT(MUTEX_NOT_HELD(&curproc->p_lock));

		if (error)
			return (set_errno(error));

		if (cmd == PC_GETPARMS) {
			if ((*copyoutfn)(&pcparms, arg, sizeof (pcparms)))
				return (set_errno(EFAULT));
		} else if ((error = vaparmsout(arg, &pcparms, &vaparms,
		    seg)) != 0)
			return (set_errno(error));

		/*
		 * And finally, return the pid of the selected thread.
		 */
		rv = saved_pid;
		break;

	case PC_ADMIN:
		if (get_udatamodel() == DATAMODEL_NATIVE) {
			if ((*copyinfn)(arg, &pcadmin, sizeof (pcadmin_t)))
				return (set_errno(EFAULT));
#ifdef _SYSCALL32_IMPL
		} else {
			/* pcadmin struct from ILP32 callers */
			pcadmin32_t pcadmin32;

			if ((*copyinfn)(arg, &pcadmin32, sizeof (pcadmin32_t)))
				return (set_errno(EFAULT));
			pcadmin.pc_cid = pcadmin32.pc_cid;
			pcadmin.pc_cladmin = (caddr_t)(uintptr_t)
			    pcadmin32.pc_cladmin;
#endif /* _SYSCALL32_IMPL */
		}

		if (pcadmin.pc_cid >= loaded_classes ||
		    pcadmin.pc_cid < 1)
			return (set_errno(EINVAL));

		/*
		 * Have the class do whatever the user is requesting.
		 */
		mutex_enter(&ualock);
		error = CL_ADMIN(&sclass[pcadmin.pc_cid], pcadmin.pc_cladmin,
				CRED());
		mutex_exit(&ualock);
		break;

	case PC_GETPRIRANGE:
		if ((*copyinfn)(arg, &pcpri, sizeof (pcpri_t)))
			return (set_errno(EFAULT));

		if (pcpri.pc_cid >= loaded_classes || pcpri.pc_cid < 0)
			return (set_errno(EINVAL));

		error = CL_GETCLPRI(&sclass[pcpri.pc_cid], &pcpri);
		if (!error) {
			if ((*copyoutfn)(&pcpri, arg, sizeof (pcpri)))
				return (set_errno(EFAULT));
		}
		break;

	case PC_DONICE:
		/*
		 * Get pcnice and procset structures from the user.
		 */
		if ((*copyinfn)(arg, &pcnice, sizeof (pcnice)) ||
		    (*copyinfn)(psp, &procset, sizeof (procset)))
			return (set_errno(EFAULT));

		error = donice(&procset, &pcnice);

		if (!error && (pcnice.pc_op == PC_GETNICE)) {
			if ((*copyoutfn)(&pcnice, arg, sizeof (pcnice)))
				return (set_errno(EFAULT));
		}
		break;

	case PC_SETDFLCL:
		if (secpolicy_dispadm(CRED()) != 0)
			return (set_errno(EPERM));

		if (copyin(arg, (caddr_t)clname, PC_CLNMSZ) != 0)
			return (set_errno(EFAULT));
		clname[PC_CLNMSZ-1] = '\0';

		if (getcid(clname, &classid) != 0)
			return (set_errno(EINVAL));
		if (classid == syscid)
			return (set_errno(EINVAL));
		defaultcid = classid;
		ASSERT(defaultcid > 0 && defaultcid < loaded_classes);
		break;

	case PC_GETDFLCL:
		mutex_enter(&class_lock);

		if (defaultcid >= loaded_classes)
			outstr = "";
		else
			outstr = sclass[defaultcid].cl_name;
		size = strlen(outstr) + 1;
		if (arg != NULL)
			if ((*copyoutfn)(outstr, arg, size) != 0)
				error = EFAULT;

		mutex_exit(&class_lock);
		break;

	default:
		error = EINVAL;
		break;
	}
	return (error ? (set_errno(error)) : rv);
}

long
priocntlsys(int pc_version, procset_t *psp, int cmd, caddr_t arg, caddr_t arg2)
{
	return (priocntl_common(pc_version, psp, cmd, arg, arg2,
	    UIO_USERSPACE));
}

/*
 * The proccmp() function is part of the implementation of the
 * PC_GETPARMS command of the priocntl system call.  This function works
 * with the system call code and with the class specific cl_globpri()
 * function to select one thread from a specified procset based on class
 * specific criteria. proccmp() is called indirectly from the priocntl
 * code through the dotoprocs function.  Basic strategy is dotoprocs()
 * calls us once for each thread in the set.  We in turn call the class
 * specific function to compare the current thread from dotoprocs to the
 * "best" (according to the class criteria) found so far.  We keep the
 * "best" thread in *pcmp_retthreadp.
 */
static int
proccmp(proc_t *pp, struct pcmpargs *argp)
{
	kthread_id_t	tx, ty;
	int		last_pri = -1;
	int		tx_pri;
	int		found = 0;

	mutex_enter(&pp->p_lock);

	if (pp->p_tlist == NULL) {
		mutex_exit(&pp->p_lock);
		return (0);
	}
	(*argp->pcmp_cntp)++;	/* Increment count of procs in the set */

	if (*argp->pcmp_cidp == PC_CLNULL) {
		/*
		 * If no cid is specified, then lets just pick the first one.
		 * It doesn't matter because if the number of processes in the
		 * set are more than 1, then we return EINVAL in priocntlsys.
		 */
		*argp->pcmp_cidp = pp->p_tlist->t_cid;
	}
	ty = tx = pp->p_tlist;
	do {
		if (tx->t_cid == *argp->pcmp_cidp) {
			/*
			 * We found one which matches the required cid.
			 */
			found = 1;
			if ((tx_pri = CL_GLOBPRI(tx)) > last_pri) {
				last_pri = tx_pri;
				ty = tx;
			}
		}
	} while ((tx = tx->t_forw) != pp->p_tlist);
	if (found) {
		if (*argp->pcmp_retthreadp == NULL) {
			/*
			 * First time through for this set.
			 * keep the mutex held. He might be the one!
			 */
			*argp->pcmp_retthreadp = ty;
		} else {
			tx = *argp->pcmp_retthreadp;
			if (CL_GLOBPRI(ty) <= CL_GLOBPRI(tx)) {
				mutex_exit(&pp->p_lock);
			} else {
				mutex_exit(&(ttoproc(tx)->p_lock));
				*argp->pcmp_retthreadp = ty;
			}
		}
	} else {
		/*
		 * We actually didn't find anything of the same cid in
		 * this process.
		 */
		mutex_exit(&pp->p_lock);
	}
	return (0);
}


int
threadcmp(struct pcmpargs *argp, kthread_id_t tp)
{
	kthread_id_t	tx;
	proc_t		*pp;

	ASSERT(MUTEX_HELD(&(ttoproc(tp))->p_lock));

	(*argp->pcmp_cntp)++;   /* Increment count of procs in the set */
	if (*argp->pcmp_cidp == PC_CLNULL) {
		/*
		 * If no cid is specified, then lets just pick the first one.
		 * It doesn't matter because if the number of threads in the
		 * set are more than 1, then we return EINVAL in priocntlsys.
		 */
		*argp->pcmp_cidp = tp->t_cid;
	}
	if (tp->t_cid == *argp->pcmp_cidp) {
		if (*argp->pcmp_retthreadp == NULL) {
			/*
			 * First time through for this set.
			 */
			*argp->pcmp_retthreadp = tp;
		} else {
			tx = *argp->pcmp_retthreadp;
			if (CL_GLOBPRI(tp) > CL_GLOBPRI(tx)) {
				/*
				 * Unlike proccmp(), we don't release the
				 * p_lock of the ttoproc(tp) if tp's global
				 * priority is less than tx's. We need to go
				 * through the entire list before we can do
				 * that. The p_lock is released by the caller
				 * of dotolwp().
				 */
				pp = ttoproc(tx);
				ASSERT(MUTEX_HELD(&pp->p_lock));
				if (pp != curproc) {
					mutex_exit(&pp->p_lock);
				}
				*argp->pcmp_retthreadp = tp;
			}
		}
	}
	return (0);
}


/*
 * The setparms() function is called indirectly by priocntlsys()
 * through the dotoprocs() function.  setparms() acts as an
 * intermediary between dotoprocs() and the parmsset() function,
 * calling parmsset() for each thread in the set and handling
 * the error returns on their way back up to dotoprocs().
 */
static int
setparms(proc_t *targpp, struct stprmargs *stprmp)
{
	int error = 0;
	kthread_id_t t;
	int err;

	mutex_enter(&targpp->p_lock);
	if ((t = targpp->p_tlist) == NULL) {
		mutex_exit(&targpp->p_lock);
		return (0);
	}
	do {
		err = parmsset(stprmp->stp_parmsp, t);
		if (error == 0)
			error = err;
	} while ((t = t->t_forw) != targpp->p_tlist);
	mutex_exit(&targpp->p_lock);
	if (error) {
		if (error == EPERM) {
			stprmp->stp_error = EPERM;
			return (0);
		} else {
			return (error);
		}
	} else
		return (0);
}

int
setthreadnice(pcnice_t *pcnice, kthread_t *tp)
{
	int error = 0;
	int nice;
	int inc;
	id_t rtcid;

	ASSERT(MUTEX_HELD(&pidlock));
	ASSERT(MUTEX_HELD(&(ttoproc(tp)->p_lock)));

	/*
	 * The XPG5 standard requires that any realtime process or thread
	 * must be unaffected by a call to setpriority().
	 */
	error = getcidbyname("RT", &rtcid);
	if ((error == 0) && (tp->t_cid == rtcid)) {
		if (pcnice->pc_op == PC_SETNICE)
			return (error);
	}

	if ((error = CL_DONICE(tp, CRED(), 0, &nice)) != 0)
		return (error);

	if (pcnice->pc_op == PC_GETNICE) {
		/*
		 * If there is no change to priority, we should return the
		 * highest priority (lowest numerical value) pertaining to
		 * any of the specified threads.
		 */
		if (nice < pcnice->pc_val)
			pcnice->pc_val = nice;
	} else {
		ASSERT(pcnice->pc_op == PC_SETNICE);
		/*
		 * Try to change the nice value of the thread.
		 */
		inc = pcnice->pc_val - nice;

		error = CL_DONICE(tp, CRED(), inc, &inc);
	}

	return (error);
}

int
setprocnice(proc_t *pp, pcnice_t *pcnice)
{
	kthread_t *tp;
	int retval = 0;
	int error = 0;

	ASSERT(MUTEX_HELD(&pidlock));
	mutex_enter(&pp->p_lock);

	if ((tp = pp->p_tlist) == NULL) {
		mutex_exit(&pp->p_lock);
		return (ESRCH);
	}

	/*
	 * Check permissions before changing the nice value.
	 */
	if (pcnice->pc_op == PC_SETNICE) {
		if (!prochasprocperm(pp, curproc, CRED())) {
			mutex_exit(&pp->p_lock);
			return (EPERM);
		}
	}

	do {
		error = setthreadnice(pcnice, tp);
		if (error)
			retval = error;
	} while ((tp = tp->t_forw) != pp->p_tlist);

	mutex_exit(&pp->p_lock);
	return (retval);
}

/*
 * Update the nice value of the specified LWP or set of processes.
 */
static int
donice(procset_t *procset, pcnice_t *pcnice)
{
	int err_proc = 0;
	int err_thread = 0;
	int err = 0;

	/*
	 * Sanity check.
	 */
	if (pcnice->pc_op != PC_GETNICE && pcnice->pc_op != PC_SETNICE)
		return (EINVAL);

	/*
	 * If it is PC_GETNICE operation then set pc_val to the largest
	 * possible nice value to help us find the lowest nice value
	 * pertaining to any of the specified processes.
	 */
	if (pcnice->pc_op == PC_GETNICE)
		pcnice->pc_val = NZERO;

	if (procset->p_lidtype != P_LWPID ||
	    procset->p_ridtype != P_LWPID)
		err_proc = dotoprocs(procset, setprocnice, (char *)pcnice);

	if (procset->p_lidtype == P_LWPID || procset->p_ridtype == P_LWPID) {
		err_thread = dotolwp(procset, setthreadnice, (char *)pcnice);
		/*
		 * dotolwp() can return with p_lock held.  This is required
		 * for the priocntl GETPARMS case.  So, here we just release
		 * the p_lock.
		 */
		if (MUTEX_HELD(&curproc->p_lock))
			mutex_exit(&curproc->p_lock);

		/*
		 * If we were called for a single LWP, then ignore ESRCH
		 * returned by the previous dotoprocs() call.
		 */
		if (err_proc == ESRCH)
			err_proc = 0;
	}

	/*
	 * dotoprocs() ignores the init process if it is in the set, unless
	 * it was the only process found. We want to make sure init is not
	 * excluded if we're going PC_GETNICE operation.
	 */
	if (pcnice->pc_op == PC_GETNICE) {
		proc_t *initpp;

		mutex_enter(&pidlock);
		initpp = prfind(P_INITPID);
		if (initpp != NULL && procinset(initpp, procset))
			err = setprocnice(initpp, pcnice);
		mutex_exit(&pidlock);
	}

	/*
	 * We're returning the latest error here that we've got back from
	 * the setthreadnice() or setprocnice(). That is, err_thread and/or
	 * err_proc can be replaced by err.
	 */
	if (!err)
		err = err_thread ? err_thread : err_proc;

	return (err);
}