/*
* 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 */
#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, 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);
/* 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);
}