/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (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 2004 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/param.h>
#include <sys/types.h>
#include <sys/inttypes.h>
#include <sys/sysmacros.h>
#include <sys/systm.h>
#include <sys/tuneable.h>
#include <sys/user.h>
#include <sys/errno.h>
#include <sys/vnode.h>
#include <sys/file.h>
#include <sys/proc.h>
#include <sys/resource.h>
#include <sys/ulimit.h>
#include <sys/debug.h>
#include <sys/rctl.h>

#include <vm/as.h>

/*
 * Perhaps ulimit could be moved into a user library, as calls to
 * getrlimit and setrlimit, were it not for binary compatibility
 * restrictions.
 */
long
ulimit(int cmd, long arg)
{
	proc_t *p = curproc;
	long	retval;

	switch (cmd) {

	case UL_GFILLIM: /* Return current file size limit. */
	{
		rlim64_t filesize;

		mutex_enter(&p->p_lock);
		filesize = rctl_enforced_value(rctlproc_legacy[RLIMIT_FSIZE],
		    p->p_rctls, p);
		mutex_exit(&p->p_lock);

		if (get_udatamodel() == DATAMODEL_ILP32) {
			/*
			 * File size is returned in blocks for ulimit.
			 * This function is deprecated and therefore LFS API
			 * didn't define the behaviour of ulimit.
			 * Here we return maximum value of file size possible
			 * so that applications that do not check errors
			 * continue to work.
			 */
			if (filesize > MAXOFF32_T)
				filesize = MAXOFF32_T;
			retval = ((int)filesize >> SCTRSHFT);
		} else
			retval = filesize >> SCTRSHFT;
		break;
	}

	case UL_SFILLIM: /* Set new file size limit. */
	{
		int error = 0;
		rlim64_t lim = (rlim64_t)arg;
		struct rlimit64 rl64;
		rctl_alloc_gp_t *gp = rctl_rlimit_set_prealloc(1);

		if (lim >= (((rlim64_t)MAXOFFSET_T) >> SCTRSHFT))
			lim = (rlim64_t)RLIM64_INFINITY;
		else
			lim <<= SCTRSHFT;

		rl64.rlim_max = rl64.rlim_cur = lim;
		mutex_enter(&p->p_lock);
		if (error = rctl_rlimit_set(rctlproc_legacy[RLIMIT_FSIZE], p,
		    &rl64, gp, RCTL_LOCAL_DENY | RCTL_LOCAL_SIGNAL, SIGXFSZ,
		    CRED())) {
			mutex_exit(&p->p_lock);
			rctl_prealloc_destroy(gp);
			return (set_errno(error));
		}
		mutex_exit(&p->p_lock);
		rctl_prealloc_destroy(gp);
		retval = arg;
		break;
	}

	case UL_GMEMLIM: /* Return maximum possible break value. */
	{
		struct seg *seg;
		struct seg *nextseg;
		struct as *as = p->p_as;
		caddr_t brkend;
		caddr_t brkbase;
		size_t size;
		rlim64_t size_ctl;
		rlim64_t vmem_ctl;

		/*
		 * Find the segment with a virtual address
		 * greater than the end of the current break.
		 */
		nextseg = NULL;
		mutex_enter(&p->p_lock);
		brkbase = (caddr_t)p->p_brkbase;
		brkend = (caddr_t)p->p_brkbase + p->p_brksize;
		mutex_exit(&p->p_lock);

		/*
		 * Since we can't return less than the current break,
		 * initialize the return value to the current break
		 */
		retval = (long)brkend;

		AS_LOCK_ENTER(as, &as->a_lock, RW_READER);
		for (seg = as_findseg(as, brkend, 0); seg != NULL;
		    seg = AS_SEGNEXT(as, seg)) {
			if (seg->s_base >= brkend) {
				nextseg = seg;
				break;
			}
		}

		mutex_enter(&p->p_lock);
		size_ctl = rctl_enforced_value(rctlproc_legacy[RLIMIT_DATA],
		    p->p_rctls, p);
		vmem_ctl = rctl_enforced_value(rctlproc_legacy[RLIMIT_VMEM],
		    p->p_rctls, p);
		mutex_exit(&p->p_lock);

		/*
		 * First, calculate the maximum break value based on
		 * the user's RLIMIT_DATA, but also taking into account
		 * that this value cannot be greater than as->a_userlimit.
		 * We also take care to make sure that we don't overflow
		 * in the calculation.
		 */
		/*
		 * Since we are casting the RLIMIT_DATA value to a
		 * ulong (a 32-bit value in the 32-bit kernel) we have
		 * to pass this assertion.
		 */
		ASSERT32((size_t)size_ctl <= UINT32_MAX);

		size = (size_t)size_ctl;
		if (as->a_userlimit - brkbase > size)
			retval = MAX((size_t)retval, (size_t)(brkbase + size));
					/* don't return less than current */
		else
			retval = (long)as->a_userlimit;

		/*
		 * The max break cannot extend into the next segment
		 */
		if (nextseg != NULL)
			retval = MIN((uintptr_t)retval,
			    (uintptr_t)nextseg->s_base);

		/*
		 * Handle the case where there is an limit on RLIMIT_VMEM
		 */
		if (vmem_ctl < UINT64_MAX) {
			/* calculate brkend based on the end of page */
			caddr_t brkendpg = (caddr_t)roundup((uintptr_t)brkend,
			    PAGESIZE);
			/*
			 * Large Files: The following assertion has to pass
			 * through to ensure the correctness of the cast.
			 */
			ASSERT32(vmem_ctl <= UINT32_MAX);

			size = (size_t)(vmem_ctl & PAGEMASK);

			if (as->a_size < size)
				size -= as->a_size;
			else
				size = 0;
			/*
			 * Take care to not overflow the calculation
			 */
			if (as->a_userlimit - brkendpg > size)
				retval = MIN((size_t)retval,
				    (size_t)(brkendpg + size));
		}

		AS_LOCK_EXIT(as, &as->a_lock);

		/* truncate to same boundary as sbrk */

		switch (get_udatamodel()) {
		default:
		case DATAMODEL_ILP32:
			retval = retval & ~(8-1);
			break;
		case DATAMODEL_LP64:
			retval = retval & ~(16-1);
			break;
		}
		break;
	}

	case UL_GDESLIM: /* Return approximate number of open files */
	{
		rlim64_t fdno_ctl;

		mutex_enter(&curproc->p_lock);
		fdno_ctl = rctl_enforced_value(rctlproc_legacy[RLIMIT_NOFILE],
		    curproc->p_rctls, curproc);
		ASSERT(fdno_ctl <= INT_MAX);
		retval = (rlim_t)fdno_ctl;
		mutex_exit(&curproc->p_lock);
		break;
	}

	default:
		return (set_errno(EINVAL));

	}
	return (retval);
}

#ifdef _SYSCALL32_IMPL

int
ulimit32(int cmd, int arg)
{
	return ((int)ulimit(cmd, (long)arg));
}

#endif	/* _SYSCALL32_IMPL */

#if defined(_ILP32) || defined(_SYSCALL32_IMPL)

/*
 * Large Files: getrlimit returns RLIM_SAVED_CUR or RLIM_SAVED_MAX when
 * rlim_cur or rlim_max is not representable in 32-bit rlim_t. These
 * values are just tokens which will be used in setrlimit to set the
 * correct limits. The current limits are saved in the saved_rlimit members
 * in user structures when the token is returned. setrlimit restores
 * the limit values to these saved values when the token is passed.
 * Consider the following common scenario of the apps:
 *
 * 		limit = getrlimit();
 *		savedlimit = limit;
 * 		limit = limit1;
 *		setrlimit(limit)
 *		// execute all processes in the new rlimit state.
 *		setrlimit(savedlimit) // restore the old values.
 *
 * Most apps don't check error returns from getrlimit or setrlimit
 * and this is why we return tokens when the correct value
 * cannot be represented in rlim_t. For more discussion refer to
 * the LFS API document.
 *
 * In the 64-bit kernel, all existing resource limits are treated in this
 * manner.  In the 32-bit kernel, CPU time is treated equivalently to the
 * file size limit above; the VM-related limits are not.  The macro,
 * RLIM_SAVED(x), returns true if the resource limit should be handled in
 * this way on the current kernel.
 */
int
getrlimit32(int resource, struct rlimit32 *rlp)
{
	struct rlimit32 rlim32;
	struct rlimit64 rlim64;
	struct proc *p = curproc;
	struct user *up = PTOU(p);
	int savecur = 0;
	int savemax = 0;

	if (resource < 0 || resource >= RLIM_NLIMITS)
		return (set_errno(EINVAL));

	mutex_enter(&p->p_lock);
	(void) rctl_rlimit_get(rctlproc_legacy[resource], p, &rlim64);
	mutex_exit(&p->p_lock);

	if (rlim64.rlim_max > (rlim64_t)UINT32_MAX) {

		if (rlim64.rlim_max == RLIM64_INFINITY)
			rlim32.rlim_max = RLIM32_INFINITY;
		else {
			savemax = 1;
			rlim32.rlim_max = RLIM32_SAVED_MAX;
			/*CONSTCOND*/
			ASSERT(RLIM_SAVED(resource));
		}

		if (rlim64.rlim_cur == RLIM64_INFINITY)
			rlim32.rlim_cur = RLIM32_INFINITY;
		else if (rlim64.rlim_cur == rlim64.rlim_max) {
			savecur = 1;
			rlim32.rlim_cur = RLIM32_SAVED_MAX;
			/*CONSTCOND*/
			ASSERT(RLIM_SAVED(resource));
		} else if (rlim64.rlim_cur > (rlim64_t)UINT32_MAX) {
			savecur = 1;
			rlim32.rlim_cur = RLIM32_SAVED_CUR;
			/*CONSTCOND*/
			ASSERT(RLIM_SAVED(resource));
		} else
			rlim32.rlim_cur = rlim64.rlim_cur;

		/*
		 * save the current limits in user structure.
		 */
		/*CONSTCOND*/
		if (RLIM_SAVED(resource)) {
			mutex_enter(&p->p_lock);
			if (savemax)
				up->u_saved_rlimit[resource].rlim_max =
				    rlim64.rlim_max;
			if (savecur)
				up->u_saved_rlimit[resource].rlim_cur =
				    rlim64.rlim_cur;
			mutex_exit(&p->p_lock);
		}
	} else {
		ASSERT(rlim64.rlim_cur <= (rlim64_t)UINT32_MAX);
		rlim32.rlim_max = rlim64.rlim_max;
		rlim32.rlim_cur = rlim64.rlim_cur;
	}

	if (copyout(&rlim32, rlp, sizeof (rlim32)))
		return (set_errno(EFAULT));

	return (0);
}

/*
 * See comments above getrlimit32(). When the tokens are passed in the
 * rlimit structure the values are considered equal to the values
 * stored in saved_rlimit members of user structure.
 * When the user passes RLIM_INFINITY to set the resource limit to
 * unlimited internally understand this value as RLIM64_INFINITY and
 * let rlimit() do the job.
 */
int
setrlimit32(int resource, struct rlimit32 *rlp)
{
	struct rlimit32 rlim32;
	struct rlimit64 rlim64;
	struct rlimit64 saved_rlim;
	int	error;
	struct proc *p = ttoproc(curthread);
	struct user *up = PTOU(p);
	rctl_alloc_gp_t *gp;

	if (resource < 0 || resource >= RLIM_NLIMITS)
		return (set_errno(EINVAL));
	if (copyin(rlp, &rlim32, sizeof (rlim32)))
		return (set_errno(EFAULT));

	gp = rctl_rlimit_set_prealloc(1);

	/*
	 * Disallow resource limit tunnelling
	 */
	/*CONSTCOND*/
	if (RLIM_SAVED(resource)) {
		mutex_enter(&p->p_lock);
		saved_rlim = up->u_saved_rlimit[resource];
		mutex_exit(&p->p_lock);
	} else {
		saved_rlim.rlim_max = (rlim64_t)rlim32.rlim_max;
		saved_rlim.rlim_cur = (rlim64_t)rlim32.rlim_cur;
	}

	switch (rlim32.rlim_cur) {
	case RLIM32_INFINITY:
		rlim64.rlim_cur = RLIM64_INFINITY;
		break;
	case RLIM32_SAVED_CUR:
		rlim64.rlim_cur = saved_rlim.rlim_cur;
		break;
	case RLIM32_SAVED_MAX:
		rlim64.rlim_cur = saved_rlim.rlim_max;
		break;
	default:
		rlim64.rlim_cur = (rlim64_t)rlim32.rlim_cur;
		break;
	}

	switch (rlim32.rlim_max) {
	case RLIM32_INFINITY:
		rlim64.rlim_max = RLIM64_INFINITY;
		break;
	case RLIM32_SAVED_MAX:
		rlim64.rlim_max = saved_rlim.rlim_max;
		break;
	case RLIM32_SAVED_CUR:
		rlim64.rlim_max = saved_rlim.rlim_cur;
		break;
	default:
		rlim64.rlim_max = (rlim64_t)rlim32.rlim_max;
		break;
	}

	mutex_enter(&p->p_lock);
	if (error = rctl_rlimit_set(rctlproc_legacy[resource], p, &rlim64, gp,
	    rctlproc_flags[resource], rctlproc_signals[resource], CRED())) {
		mutex_exit(&p->p_lock);
		rctl_prealloc_destroy(gp);
		return (set_errno(error));
	}
	mutex_exit(&p->p_lock);
	rctl_prealloc_destroy(gp);

	return (0);
}

#endif	/* _ILP32 && _SYSCALL32_IMPL */

int
getrlimit64(int resource, struct rlimit64 *rlp)
{
	struct rlimit64 rlim64;
	struct proc *p = ttoproc(curthread);

	if (resource < 0 || resource >= RLIM_NLIMITS)
		return (set_errno(EINVAL));

	mutex_enter(&p->p_lock);
	(void) rctl_rlimit_get(rctlproc_legacy[resource], p, &rlim64);
	mutex_exit(&p->p_lock);

	if (copyout(&rlim64, rlp, sizeof (rlim64)))
		return (set_errno(EFAULT));
	return (0);
}

int
setrlimit64(int resource, struct rlimit64 *rlp)
{
	struct rlimit64 rlim64;
	struct proc *p = ttoproc(curthread);
	int	error;
	rctl_alloc_gp_t *gp;

	if (resource < 0 || resource >= RLIM_NLIMITS)
		return (set_errno(EINVAL));
	if (copyin(rlp, &rlim64, sizeof (rlim64)))
		return (set_errno(EFAULT));

	gp = rctl_rlimit_set_prealloc(1);

	mutex_enter(&p->p_lock);
	if (error = rctl_rlimit_set(rctlproc_legacy[resource], p, &rlim64, gp,
	    rctlproc_flags[resource], rctlproc_signals[resource], CRED())) {
		mutex_exit(&p->p_lock);
		rctl_prealloc_destroy(gp);
		return (set_errno(error));
	}
	mutex_exit(&p->p_lock);
	rctl_prealloc_destroy(gp);
	return (0);

}