#include <sys/resource.h>
#include <sys/spinlock.h>
#include <sys/proc.h>
#include <sys/caps.h>
#include <sys/file.h>
#include <sys/lockf.h>
#include <sys/kern_syscall.h>
#include <sys/malloc.h>
#include <sys/sysmsg.h>
#include <vm/vm_param.h>
#include <vm/vm.h>
#include <vm/vm_map.h>
#include <machine/pmap.h>
#include <sys/spinlock2.h>
static MALLOC_DEFINE(M_PLIMIT, "plimit", "resource limits");
static void plimit_copy(struct plimit *olimit, struct plimit *nlimit);
static __inline
struct plimit *
readplimits(struct proc *p)
{
thread_t td = curthread;
struct plimit *limit;
limit = td->td_limit;
if (limit != p->p_limit) {
spin_lock_shared(&p->p_spin);
limit = p->p_limit;
atomic_add_int(&limit->p_refcnt, 1);
spin_unlock_shared(&p->p_spin);
if (td->td_limit)
plimit_free(td->td_limit);
td->td_limit = limit;
}
return limit;
}
void
plimit_init0(struct plimit *limit)
{
int i;
rlim_t lim;
for (i = 0; i < RLIM_NLIMITS; ++i) {
limit->pl_rlimit[i].rlim_cur = RLIM_INFINITY;
limit->pl_rlimit[i].rlim_max = RLIM_INFINITY;
}
limit->pl_rlimit[RLIMIT_NOFILE].rlim_cur = maxfiles;
limit->pl_rlimit[RLIMIT_NOFILE].rlim_max = maxfiles;
limit->pl_rlimit[RLIMIT_NPROC].rlim_cur = maxproc;
limit->pl_rlimit[RLIMIT_NPROC].rlim_max = maxproc;
lim = ptoa((rlim_t)vmstats.v_free_count);
limit->pl_rlimit[RLIMIT_RSS].rlim_max = lim;
limit->pl_rlimit[RLIMIT_MEMLOCK].rlim_max = lim;
limit->pl_rlimit[RLIMIT_MEMLOCK].rlim_cur = lim / 3;
limit->p_cpulimit = RLIM_INFINITY;
limit->p_refcnt = 1;
spin_init(&limit->p_spin, "plimitinit");
}
struct plimit *
plimit_fork(struct proc *p1)
{
struct plimit *olimit = p1->p_limit;
struct plimit *nlimit;
uint32_t count;
count = atomic_fetchadd_int(&olimit->p_refcnt, 1);
cpu_ccfence();
if (count & PLIMITF_EXCLUSIVE) {
if ((count & PLIMITF_MASK) == 1 && p1->p_nthreads == 1) {
atomic_clear_int(&olimit->p_refcnt, PLIMITF_EXCLUSIVE);
} else {
nlimit = kmalloc(sizeof(*nlimit), M_PLIMIT, M_WAITOK);
plimit_copy(olimit, nlimit);
plimit_free(olimit);
olimit = nlimit;
}
}
return olimit;
}
void
plimit_lwp_fork(struct proc *p)
{
struct plimit *olimit = p->p_limit;
struct plimit *nlimit;
uint32_t count;
count = olimit->p_refcnt;
cpu_ccfence();
if ((count & PLIMITF_EXCLUSIVE) == 0) {
if (count != 1) {
nlimit = kmalloc(sizeof(*nlimit), M_PLIMIT, M_WAITOK);
plimit_copy(olimit, nlimit);
p->p_limit = nlimit;
plimit_free(olimit);
olimit = nlimit;
}
atomic_set_int(&olimit->p_refcnt, PLIMITF_EXCLUSIVE);
}
}
void
plimit_modify(struct proc *p, int index, struct rlimit *rlim)
{
struct plimit *olimit;
struct plimit *nlimit;
uint32_t count;
olimit = p->p_limit;
count = olimit->p_refcnt;
cpu_ccfence();
if ((count & PLIMITF_EXCLUSIVE) == 0) {
if (count != 1) {
nlimit = kmalloc(sizeof(*nlimit), M_PLIMIT, M_WAITOK);
plimit_copy(olimit, nlimit);
p->p_limit = nlimit;
plimit_free(olimit);
olimit = nlimit;
}
atomic_set_int(&olimit->p_refcnt, PLIMITF_EXCLUSIVE);
}
if (index >= 0) {
if (p->p_nthreads == 1) {
p->p_limit->pl_rlimit[index] = *rlim;
} else {
spin_lock(&olimit->p_spin);
p->p_limit->pl_rlimit[index].rlim_cur = rlim->rlim_cur;
p->p_limit->pl_rlimit[index].rlim_max = rlim->rlim_max;
spin_unlock(&olimit->p_spin);
}
}
}
void
plimit_free(struct plimit *limit)
{
uint32_t count;
count = atomic_fetchadd_int(&limit->p_refcnt, -1);
if ((count & ~PLIMITF_EXCLUSIVE) == 1) {
limit->p_refcnt = -999;
kfree(limit, M_PLIMIT);
}
}
int
kern_setrlimit(u_int which, struct rlimit *limp)
{
struct proc *p = curproc;
struct plimit *limit;
struct rlimit *alimp;
int error;
if (which >= RLIM_NLIMITS)
return (EINVAL);
plimit_modify(p, -1, NULL);
limit = p->p_limit;
alimp = &limit->pl_rlimit[which];
if (limp->rlim_cur < 0)
limp->rlim_cur = RLIM_INFINITY;
if (limp->rlim_max < 0)
limp->rlim_max = RLIM_INFINITY;
spin_lock(&limit->p_spin);
if (limp->rlim_cur > alimp->rlim_max ||
limp->rlim_max > alimp->rlim_max)
{
spin_unlock(&limit->p_spin);
error = caps_priv_check(p->p_ucred, SYSCAP_NOPROC_SETRLIMIT);
if (error)
return (error);
} else {
spin_unlock(&limit->p_spin);
}
if (limp->rlim_cur > limp->rlim_max)
limp->rlim_cur = limp->rlim_max;
switch (which) {
case RLIMIT_CPU:
spin_lock(&limit->p_spin);
if (limp->rlim_cur > RLIM_INFINITY / (rlim_t)1000000)
limit->p_cpulimit = RLIM_INFINITY;
else
limit->p_cpulimit = (rlim_t)1000000 * limp->rlim_cur;
spin_unlock(&limit->p_spin);
break;
case RLIMIT_DATA:
if (limp->rlim_cur > maxdsiz)
limp->rlim_cur = maxdsiz;
if (limp->rlim_max > maxdsiz)
limp->rlim_max = maxdsiz;
break;
case RLIMIT_STACK:
if (limp->rlim_cur > maxssiz)
limp->rlim_cur = maxssiz;
if (limp->rlim_max > maxssiz)
limp->rlim_max = maxssiz;
spin_lock(&limit->p_spin);
if (limp->rlim_cur != alimp->rlim_cur) {
vm_offset_t addr;
vm_size_t size;
vm_prot_t prot;
if (limp->rlim_cur > alimp->rlim_cur) {
prot = VM_PROT_ALL;
size = limp->rlim_cur - alimp->rlim_cur;
addr = USRSTACK - limp->rlim_cur;
} else {
prot = VM_PROT_NONE;
size = alimp->rlim_cur - limp->rlim_cur;
addr = USRSTACK - alimp->rlim_cur;
}
spin_unlock(&limit->p_spin);
addr = trunc_page(addr);
size = round_page(size);
vm_map_protect(&p->p_vmspace->vm_map,
addr, addr+size, prot, FALSE);
} else {
spin_unlock(&limit->p_spin);
}
break;
case RLIMIT_NOFILE:
if (limp->rlim_cur > maxfilesperproc)
limp->rlim_cur = maxfilesperproc;
if (limp->rlim_max > maxfilesperproc)
limp->rlim_max = maxfilesperproc;
break;
case RLIMIT_NPROC:
if (limp->rlim_cur > maxprocperuid)
limp->rlim_cur = maxprocperuid;
if (limp->rlim_max > maxprocperuid)
limp->rlim_max = maxprocperuid;
if (limp->rlim_cur < 1)
limp->rlim_cur = 1;
if (limp->rlim_max < 1)
limp->rlim_max = 1;
break;
case RLIMIT_POSIXLOCKS:
if (limp->rlim_cur > maxposixlocksperuid)
limp->rlim_cur = maxposixlocksperuid;
if (limp->rlim_max > maxposixlocksperuid)
limp->rlim_max = maxposixlocksperuid;
break;
}
spin_lock(&limit->p_spin);
*alimp = *limp;
spin_unlock(&limit->p_spin);
return (0);
}
int
sys_setrlimit(struct sysmsg *sysmsg, const struct __setrlimit_args *uap)
{
struct rlimit alim;
int error;
error = copyin(uap->rlp, &alim, sizeof(alim));
if (error)
return (error);
error = kern_setrlimit(uap->which, &alim);
return (error);
}
int
kern_getrlimit(u_int which, struct rlimit *limp)
{
struct proc *p = curproc;
struct plimit *limit;
if (p == NULL)
return 0;
if (which >= RLIM_NLIMITS)
return (EINVAL);
limit = readplimits(p);
*limp = limit->pl_rlimit[which];
return (0);
}
int
sys_getrlimit(struct sysmsg *sysmsg, const struct __getrlimit_args *uap)
{
struct rlimit lim;
int error;
error = kern_getrlimit(uap->which, &lim);
if (error == 0)
error = copyout(&lim, uap->rlp, sizeof(*uap->rlp));
return error;
}
int
plimit_testcpulimit(struct proc *p, u_int64_t ttime)
{
struct plimit *limit;
struct rlimit *rlim;
int mode;
limit = readplimits(p);
if (limit->p_cpulimit == RLIM_INFINITY)
return(PLIMIT_TESTCPU_OK);
if (ttime <= limit->p_cpulimit)
return(PLIMIT_TESTCPU_OK);
if (ttime > limit->p_cpulimit) {
rlim = &limit->pl_rlimit[RLIMIT_CPU];
if (ttime / (rlim_t)1000000 >= rlim->rlim_max + 5)
mode = PLIMIT_TESTCPU_KILL;
else
mode = PLIMIT_TESTCPU_XCPU;
} else {
mode = PLIMIT_TESTCPU_OK;
}
return(mode);
}
static
void
plimit_copy(struct plimit *olimit, struct plimit *nlimit)
{
*nlimit = *olimit;
spin_init(&nlimit->p_spin, "plimitcopy");
nlimit->p_refcnt = 1;
}
u_int64_t
plimit_getadjvalue(int i)
{
struct proc *p = curproc;
struct plimit *limit;
uint64_t v;
uint32_t depth;
limit = p->p_limit;
v = limit->pl_rlimit[i].rlim_cur;
if (i == RLIMIT_NPROC) {
depth = p->p_depth;
if (depth > 32 * 5)
depth = 32 * 5;
v -= v * depth / 320;
}
return v;
}