#include <sys/param.h>
#include <sys/systm.h>
#include <sys/sysmsg.h>
#include <sys/file.h>
#include <sys/kernel.h>
#include <sys/resourcevar.h>
#include <sys/malloc.h>
#include <sys/proc.h>
#include <sys/caps.h>
#include <sys/time.h>
#include <sys/lockf.h>
#include <vm/vm.h>
#include <vm/vm_param.h>
#include <sys/lock.h>
#include <vm/pmap.h>
#include <vm/vm_map.h>
#include <sys/thread2.h>
#include <sys/spinlock2.h>
static int donice (struct proc *chgp, int n);
static int doionice (struct proc *chgp, int n);
static MALLOC_DEFINE(M_UIDINFO, "uidinfo", "uidinfo structures");
#define UIHASH(uid) (&uihashtbl[(uid) & uihash])
static struct spinlock uihash_lock;
static LIST_HEAD(uihashhead, uidinfo) *uihashtbl;
static u_long uihash;
static struct uidinfo *uilookup (uid_t uid);
struct getpriority_info {
int low;
int who;
};
static int getpriority_callback(struct proc *p, void *data);
int
sys_getpriority(struct sysmsg *sysmsg, const struct getpriority_args *uap)
{
struct getpriority_info info;
thread_t curtd = curthread;
struct proc *curp = curproc;
struct proc *p;
struct pgrp *pg;
int low = PRIO_MAX + 1;
int who = uap->who;
int error;
switch (uap->which) {
case PRIO_PROCESS:
if (who == 0) {
low = curp->p_nice;
} else {
p = pfind(who);
if (p) {
lwkt_gettoken_shared(&p->p_token);
if (PRISON_CHECK(curtd->td_ucred, p->p_ucred))
low = p->p_nice;
lwkt_reltoken(&p->p_token);
PRELE(p);
}
}
break;
case PRIO_PGRP:
if (who == 0) {
lwkt_gettoken_shared(&curp->p_token);
pg = curp->p_pgrp;
pgref(pg);
lwkt_reltoken(&curp->p_token);
} else if ((pg = pgfind(who)) == NULL) {
break;
}
lwkt_gettoken_shared(&pg->pg_token);
LIST_FOREACH(p, &pg->pg_members, p_pglist) {
if (PRISON_CHECK(curtd->td_ucred, p->p_ucred) &&
p->p_nice < low) {
low = p->p_nice;
}
}
lwkt_reltoken(&pg->pg_token);
pgrel(pg);
break;
case PRIO_USER:
if (who == 0)
who = curtd->td_ucred->cr_uid;
info.low = low;
info.who = who;
allproc_scan(getpriority_callback, &info, 0);
low = info.low;
break;
default:
error = EINVAL;
goto done;
}
if (low == PRIO_MAX + 1) {
error = ESRCH;
goto done;
}
sysmsg->sysmsg_result = low;
error = 0;
done:
return (error);
}
static
int
getpriority_callback(struct proc *p, void *data)
{
struct getpriority_info *info = data;
lwkt_gettoken_shared(&p->p_token);
if (PRISON_CHECK(curthread->td_ucred, p->p_ucred) &&
p->p_ucred->cr_uid == info->who &&
p->p_nice < info->low) {
info->low = p->p_nice;
}
lwkt_reltoken(&p->p_token);
return(0);
}
struct setpriority_info {
int prio;
int who;
int error;
int found;
};
static int setpriority_callback(struct proc *p, void *data);
int
sys_setpriority(struct sysmsg *sysmsg, const struct setpriority_args *uap)
{
struct setpriority_info info;
thread_t curtd = curthread;
struct proc *curp = curproc;
struct proc *p;
struct pgrp *pg;
int found = 0, error = 0;
int who = uap->who;
switch (uap->which) {
case PRIO_PROCESS:
if (who == 0) {
lwkt_gettoken(&curp->p_token);
error = donice(curp, uap->prio);
found++;
lwkt_reltoken(&curp->p_token);
} else {
p = pfind(who);
if (p) {
lwkt_gettoken(&p->p_token);
if (PRISON_CHECK(curtd->td_ucred, p->p_ucred)) {
error = donice(p, uap->prio);
found++;
}
lwkt_reltoken(&p->p_token);
PRELE(p);
}
}
break;
case PRIO_PGRP:
if (who == 0) {
lwkt_gettoken_shared(&curp->p_token);
pg = curp->p_pgrp;
pgref(pg);
lwkt_reltoken(&curp->p_token);
} else if ((pg = pgfind(who)) == NULL) {
break;
}
lwkt_gettoken(&pg->pg_token);
restart:
LIST_FOREACH(p, &pg->pg_members, p_pglist) {
PHOLD(p);
lwkt_gettoken(&p->p_token);
if (p->p_pgrp == pg &&
PRISON_CHECK(curtd->td_ucred, p->p_ucred)) {
error = donice(p, uap->prio);
found++;
}
lwkt_reltoken(&p->p_token);
if (p->p_pgrp != pg) {
PRELE(p);
goto restart;
}
PRELE(p);
}
lwkt_reltoken(&pg->pg_token);
pgrel(pg);
break;
case PRIO_USER:
if (who == 0)
who = curtd->td_ucred->cr_uid;
info.prio = uap->prio;
info.who = who;
info.error = 0;
info.found = 0;
allproc_scan(setpriority_callback, &info, 0);
error = info.error;
found = info.found;
break;
default:
error = EINVAL;
found = 1;
break;
}
if (found == 0)
error = ESRCH;
return (error);
}
static
int
setpriority_callback(struct proc *p, void *data)
{
struct setpriority_info *info = data;
int error;
lwkt_gettoken(&p->p_token);
if (p->p_ucred->cr_uid == info->who &&
PRISON_CHECK(curthread->td_ucred, p->p_ucred)) {
error = donice(p, info->prio);
if (error)
info->error = error;
++info->found;
}
lwkt_reltoken(&p->p_token);
return(0);
}
static int
donice(struct proc *chgp, int n)
{
struct ucred *cr = curthread->td_ucred;
struct lwp *lp;
if (cr->cr_uid && cr->cr_ruid &&
cr->cr_uid != chgp->p_ucred->cr_uid &&
cr->cr_ruid != chgp->p_ucred->cr_uid)
return (EPERM);
if (n > PRIO_MAX)
n = PRIO_MAX;
if (n < PRIO_MIN)
n = PRIO_MIN;
if (n < chgp->p_nice && caps_priv_check(cr, SYSCAP_NOSCHED))
return (EACCES);
chgp->p_nice = n;
FOREACH_LWP_IN_PROC(lp, chgp) {
LWPHOLD(lp);
chgp->p_usched->resetpriority(lp);
LWPRELE(lp);
}
return (0);
}
struct ioprio_get_info {
int high;
int who;
};
static int ioprio_get_callback(struct proc *p, void *data);
int
sys_ioprio_get(struct sysmsg *sysmsg, const struct ioprio_get_args *uap)
{
struct ioprio_get_info info;
thread_t curtd = curthread;
struct proc *curp = curproc;
struct proc *p;
struct pgrp *pg;
int high = IOPRIO_MIN-2;
int who = uap->who;
int error;
switch (uap->which) {
case PRIO_PROCESS:
if (who == 0) {
high = curp->p_ionice;
} else {
p = pfind(who);
if (p) {
lwkt_gettoken_shared(&p->p_token);
if (PRISON_CHECK(curtd->td_ucred, p->p_ucred))
high = p->p_ionice;
lwkt_reltoken(&p->p_token);
PRELE(p);
}
}
break;
case PRIO_PGRP:
if (who == 0) {
lwkt_gettoken_shared(&curp->p_token);
pg = curp->p_pgrp;
pgref(pg);
lwkt_reltoken(&curp->p_token);
} else if ((pg = pgfind(who)) == NULL) {
break;
}
lwkt_gettoken_shared(&pg->pg_token);
LIST_FOREACH(p, &pg->pg_members, p_pglist) {
if (PRISON_CHECK(curtd->td_ucred, p->p_ucred) &&
p->p_nice > high)
high = p->p_ionice;
}
lwkt_reltoken(&pg->pg_token);
pgrel(pg);
break;
case PRIO_USER:
if (who == 0)
who = curtd->td_ucred->cr_uid;
info.high = high;
info.who = who;
allproc_scan(ioprio_get_callback, &info, 0);
high = info.high;
break;
default:
error = EINVAL;
goto done;
}
if (high == IOPRIO_MIN-2) {
error = ESRCH;
goto done;
}
sysmsg->sysmsg_result = high;
error = 0;
done:
return (error);
}
static
int
ioprio_get_callback(struct proc *p, void *data)
{
struct ioprio_get_info *info = data;
lwkt_gettoken_shared(&p->p_token);
if (PRISON_CHECK(curthread->td_ucred, p->p_ucred) &&
p->p_ucred->cr_uid == info->who &&
p->p_ionice > info->high) {
info->high = p->p_ionice;
}
lwkt_reltoken(&p->p_token);
return(0);
}
struct ioprio_set_info {
int prio;
int who;
int error;
int found;
};
static int ioprio_set_callback(struct proc *p, void *data);
int
sys_ioprio_set(struct sysmsg *sysmsg, const struct ioprio_set_args *uap)
{
struct ioprio_set_info info;
thread_t curtd = curthread;
struct proc *curp = curproc;
struct proc *p;
struct pgrp *pg;
int found = 0, error = 0;
int who = uap->who;
switch (uap->which) {
case PRIO_PROCESS:
if (who == 0) {
lwkt_gettoken(&curp->p_token);
error = doionice(curp, uap->prio);
lwkt_reltoken(&curp->p_token);
found++;
} else {
p = pfind(who);
if (p) {
lwkt_gettoken(&p->p_token);
if (PRISON_CHECK(curtd->td_ucred, p->p_ucred)) {
error = doionice(p, uap->prio);
found++;
}
lwkt_reltoken(&p->p_token);
PRELE(p);
}
}
break;
case PRIO_PGRP:
if (who == 0) {
lwkt_gettoken_shared(&curp->p_token);
pg = curp->p_pgrp;
pgref(pg);
lwkt_reltoken(&curp->p_token);
} else if ((pg = pgfind(who)) == NULL) {
break;
}
lwkt_gettoken(&pg->pg_token);
restart:
LIST_FOREACH(p, &pg->pg_members, p_pglist) {
PHOLD(p);
lwkt_gettoken(&p->p_token);
if (p->p_pgrp == pg &&
PRISON_CHECK(curtd->td_ucred, p->p_ucred)) {
error = doionice(p, uap->prio);
found++;
}
lwkt_reltoken(&p->p_token);
if (p->p_pgrp != pg) {
PRELE(p);
goto restart;
}
PRELE(p);
}
lwkt_reltoken(&pg->pg_token);
pgrel(pg);
break;
case PRIO_USER:
if (who == 0)
who = curtd->td_ucred->cr_uid;
info.prio = uap->prio;
info.who = who;
info.error = 0;
info.found = 0;
allproc_scan(ioprio_set_callback, &info, 0);
error = info.error;
found = info.found;
break;
default:
error = EINVAL;
found = 1;
break;
}
if (found == 0)
error = ESRCH;
return (error);
}
static
int
ioprio_set_callback(struct proc *p, void *data)
{
struct ioprio_set_info *info = data;
int error;
lwkt_gettoken(&p->p_token);
if (p->p_ucred->cr_uid == info->who &&
PRISON_CHECK(curthread->td_ucred, p->p_ucred)) {
error = doionice(p, info->prio);
if (error)
info->error = error;
++info->found;
}
lwkt_reltoken(&p->p_token);
return(0);
}
static int
doionice(struct proc *chgp, int n)
{
struct ucred *cr = curthread->td_ucred;
if (cr->cr_uid && cr->cr_ruid &&
cr->cr_uid != chgp->p_ucred->cr_uid &&
cr->cr_ruid != chgp->p_ucred->cr_uid)
return (EPERM);
if (n > IOPRIO_MAX)
n = IOPRIO_MAX;
if (n < IOPRIO_MIN)
n = IOPRIO_MIN;
if (n < chgp->p_ionice &&
caps_priv_check(cr, SYSCAP_NOSCHED))
{
return (EACCES);
}
chgp->p_ionice = n;
return (0);
}
int
sys_lwp_rtprio(struct sysmsg *sysmsg, const struct lwp_rtprio_args *uap)
{
struct ucred *cr = curthread->td_ucred;
struct proc *p;
struct lwp *lp;
struct rtprio rtp;
int error;
error = copyin(uap->rtp, &rtp, sizeof(struct rtprio));
if (error)
return error;
if (uap->pid < 0)
return EINVAL;
if (uap->pid == 0) {
p = curproc;
PHOLD(p);
} else {
p = pfind(uap->pid);
}
if (p == NULL) {
error = ESRCH;
goto done;
}
lwkt_gettoken(&p->p_token);
if (uap->tid < -1) {
error = EINVAL;
goto done;
}
if (uap->tid == -1) {
lp = curthread->td_lwp;
} else {
lp = lwp_rb_tree_RB_LOOKUP(&p->p_lwp_tree, uap->tid);
if (lp == NULL) {
error = ESRCH;
goto done;
}
}
LWPHOLD(lp);
switch (uap->function) {
case RTP_LOOKUP:
error = copyout(&lp->lwp_rtprio, uap->rtp,
sizeof(struct rtprio));
break;
case RTP_SET:
if (cr->cr_uid && cr->cr_ruid &&
cr->cr_uid != p->p_ucred->cr_uid &&
cr->cr_ruid != p->p_ucred->cr_uid) {
error = EPERM;
break;
}
if (caps_priv_check(cr, SYSCAP_NOSCHED)) {
if (uap->pid) {
error = EPERM;
break;
}
if (RTP_PRIO_IS_REALTIME(rtp.type)) {
error = EPERM;
break;
}
}
switch (rtp.type) {
#ifdef RTP_PRIO_FIFO
case RTP_PRIO_FIFO:
#endif
case RTP_PRIO_REALTIME:
case RTP_PRIO_NORMAL:
case RTP_PRIO_IDLE:
if (rtp.prio > RTP_PRIO_MAX) {
error = EINVAL;
} else {
lp->lwp_rtprio = rtp;
error = 0;
}
break;
default:
error = EINVAL;
break;
}
break;
default:
error = EINVAL;
break;
}
LWPRELE(lp);
done:
if (p) {
lwkt_reltoken(&p->p_token);
PRELE(p);
}
return (error);
}
int
sys_rtprio(struct sysmsg *sysmsg, const struct rtprio_args *uap)
{
struct ucred *cr = curthread->td_ucred;
struct proc *p;
struct lwp *lp;
struct rtprio rtp;
int error;
error = copyin(uap->rtp, &rtp, sizeof(struct rtprio));
if (error)
return (error);
if (uap->pid == 0) {
p = curproc;
PHOLD(p);
} else {
p = pfind(uap->pid);
}
if (p == NULL) {
error = ESRCH;
goto done;
}
lwkt_gettoken(&p->p_token);
lp = FIRST_LWP_IN_PROC(p);
switch (uap->function) {
case RTP_LOOKUP:
error = copyout(&lp->lwp_rtprio, uap->rtp,
sizeof(struct rtprio));
break;
case RTP_SET:
if (cr->cr_uid && cr->cr_ruid &&
cr->cr_uid != p->p_ucred->cr_uid &&
cr->cr_ruid != p->p_ucred->cr_uid) {
error = EPERM;
break;
}
if (caps_priv_check(cr, SYSCAP_NOSCHED)) {
if (uap->pid) {
error = EPERM;
break;
}
if (RTP_PRIO_IS_REALTIME(rtp.type)) {
error = EPERM;
break;
}
}
switch (rtp.type) {
#ifdef RTP_PRIO_FIFO
case RTP_PRIO_FIFO:
#endif
case RTP_PRIO_REALTIME:
case RTP_PRIO_NORMAL:
case RTP_PRIO_IDLE:
if (rtp.prio > RTP_PRIO_MAX) {
error = EINVAL;
break;
}
lp->lwp_rtprio = rtp;
error = 0;
break;
default:
error = EINVAL;
break;
}
break;
default:
error = EINVAL;
break;
}
done:
if (p) {
lwkt_reltoken(&p->p_token);
PRELE(p);
}
return (error);
}
void
calcru(struct lwp *lp, struct timeval *up, struct timeval *sp)
{
struct thread *td;
if ((td = lp->lwp_thread) != NULL) {
crit_enter();
up->tv_sec = td->td_uticks / 1000000;
up->tv_usec = td->td_uticks % 1000000;
sp->tv_sec = td->td_sticks / 1000000;
sp->tv_usec = td->td_sticks % 1000000;
crit_exit();
}
}
void
calcru_proc(struct proc *p, struct rusage *ru)
{
struct timeval upt, spt;
long *rip1, *rip2;
struct lwp *lp;
*ru = p->p_ru;
FOREACH_LWP_IN_PROC(lp, p) {
calcru(lp, &upt, &spt);
timevaladd(&ru->ru_utime, &upt);
timevaladd(&ru->ru_stime, &spt);
for (rip1 = &ru->ru_first, rip2 = &lp->lwp_ru.ru_first;
rip1 <= &ru->ru_last;
rip1++, rip2++)
*rip1 += *rip2;
}
}
int
sys_getrusage(struct sysmsg *sysmsg, const struct getrusage_args *uap)
{
struct proc *p = curproc;
struct rusage ru;
struct rusage *rup;
int error;
lwkt_gettoken(&p->p_token);
switch (uap->who) {
case RUSAGE_SELF:
rup = &ru;
calcru_proc(p, rup);
error = 0;
break;
case RUSAGE_CHILDREN:
rup = &p->p_cru;
error = 0;
break;
default:
error = EINVAL;
break;
}
lwkt_reltoken(&p->p_token);
if (error == 0)
error = copyout(rup, uap->rusage, sizeof(struct rusage));
return (error);
}
void
ruadd(struct rusage *ru, struct rusage *ru2)
{
long *ip, *ip2;
int i;
timevaladd(&ru->ru_utime, &ru2->ru_utime);
timevaladd(&ru->ru_stime, &ru2->ru_stime);
if (ru->ru_maxrss < ru2->ru_maxrss)
ru->ru_maxrss = ru2->ru_maxrss;
ip = &ru->ru_first; ip2 = &ru2->ru_first;
for (i = &ru->ru_last - &ru->ru_first; i >= 0; i--)
*ip++ += *ip2++;
}
void
uihashinit(void)
{
spin_init(&uihash_lock, "uihashinit");
uihashtbl = hashinit(maxproc / 16, M_UIDINFO, &uihash);
}
static struct uidinfo *
uilookup(uid_t uid)
{
struct uihashhead *uipp;
struct uidinfo *uip;
uipp = UIHASH(uid);
LIST_FOREACH(uip, uipp, ui_hash) {
if (uip->ui_uid == uid)
break;
}
return (uip);
}
struct uidinfo *
uicreate(uid_t uid)
{
struct uidinfo *uip, *tmp;
uip = kmalloc(sizeof(*uip), M_UIDINFO, M_WAITOK | M_ZERO);
spin_init(&uip->ui_lock, "uicreate");
uip->ui_uid = uid;
uip->ui_ref = 1;
varsymset_init(&uip->ui_varsymset, NULL);
uip->ui_pcpu = kmalloc(sizeof(*uip->ui_pcpu) * ncpus,
M_UIDINFO, M_WAITOK | M_ZERO);
spin_lock(&uihash_lock);
tmp = uilookup(uid);
if (tmp != NULL) {
uihold(tmp);
spin_unlock(&uihash_lock);
spin_uninit(&uip->ui_lock);
varsymset_clean(&uip->ui_varsymset);
kfree(uip->ui_pcpu, M_UIDINFO);
kfree(uip, M_UIDINFO);
uip = tmp;
} else {
LIST_INSERT_HEAD(UIHASH(uid), uip, ui_hash);
spin_unlock(&uihash_lock);
}
return (uip);
}
struct uidinfo *
uifind(uid_t uid)
{
struct uidinfo *uip;
thread_t td = curthread;
if (td->td_ucred) {
uip = td->td_ucred->cr_uidinfo;
if (uip->ui_uid == uid) {
uihold(uip);
return uip;
}
uip = td->td_ucred->cr_ruidinfo;
if (uip->ui_uid == uid) {
uihold(uip);
return uip;
}
}
spin_lock_shared(&uihash_lock);
uip = uilookup(uid);
if (uip == NULL) {
spin_unlock_shared(&uihash_lock);
uip = uicreate(uid);
} else {
uihold(uip);
spin_unlock_shared(&uihash_lock);
}
return (uip);
}
static __inline void
uifree(uid_t uid)
{
struct uidinfo *uip;
spin_lock(&uihash_lock);
uip = uilookup(uid);
if (uip && uip->ui_ref == 0) {
LIST_REMOVE(uip, ui_hash);
spin_unlock(&uihash_lock);
if (uip->ui_sbsize != 0)
kprintf("freeing uidinfo: uid = %d, sbsize = %jd\n",
uip->ui_uid, (intmax_t)uip->ui_sbsize);
if (uip->ui_proccnt != 0)
kprintf("freeing uidinfo: uid = %d, proccnt = %ld\n",
uip->ui_uid, uip->ui_proccnt);
varsymset_clean(&uip->ui_varsymset);
lockuninit(&uip->ui_varsymset.vx_lock);
spin_uninit(&uip->ui_lock);
kfree(uip->ui_pcpu, M_UIDINFO);
kfree(uip, M_UIDINFO);
} else {
spin_unlock(&uihash_lock);
}
}
void
uihold(struct uidinfo *uip)
{
KKASSERT(uip->ui_ref >= 0);
atomic_add_int(&uip->ui_ref, 1);
}
void
uidrop(struct uidinfo *uip)
{
uid_t uid;
KKASSERT(uip->ui_ref > 0);
uid = uip->ui_uid;
cpu_ccfence();
if (atomic_fetchadd_int(&uip->ui_ref, -1) == 1) {
uifree(uid);
}
}
void
uireplace(struct uidinfo **puip, struct uidinfo *nuip)
{
uidrop(*puip);
*puip = nuip;
}
int
chgproccnt(struct uidinfo *uip, int diff, int max)
{
int ret;
if (diff > 0 && uip->ui_proccnt + diff > max && max != 0) {
ret = 0;
} else {
atomic_add_long(&uip->ui_proccnt, diff);
if (uip->ui_proccnt < 0)
kprintf("negative proccnt for uid = %d\n", uip->ui_uid);
ret = 1;
}
return ret;
}
int
chgsbsize(struct uidinfo *uip, u_long *hiwat, u_long to, rlim_t max)
{
rlim_t new;
rlim_t sbsize;
sbsize = atomic_fetchadd_long(&uip->ui_sbsize, to - *hiwat);
new = sbsize + to - *hiwat;
KKASSERT(new >= 0);
if (to > *hiwat && to > MCLBYTES && new > max) {
to = to * max / new;
if (to < MCLBYTES)
to = MCLBYTES;
}
*hiwat = to;
return (1);
}