/*
* 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 2015, Joyent, Inc.
*/
#define __EXTENSIONS__ /* For strtok_r */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#include <string.h>
#include <signal.h>
#include <limits.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/mkdev.h>
#include <libproc.h>
#include <priv.h>
#define TRUE 1
#define FALSE 0
static int interrupt;
static char *command;
static int Fflag;
static int kbytes = FALSE;
static int mbytes = FALSE;
static char set_current[RLIM_NLIMITS];
static char set_maximum[RLIM_NLIMITS];
static struct rlimit64 rlimit[RLIM_NLIMITS];
static void intr(int);
static int parse_limits(int, char *);
static void show_limits(struct ps_prochandle *);
static int set_limits(struct ps_prochandle *);
static void
usage()
{
(void) fprintf(stderr,
"usage:\n"
" For each process, report all resource limits:\n"
"\t%s [-km] pid ...\n"
"\t-k\treport file sizes in kilobytes\n"
"\t-m\treport file/memory sizes in megabytes\n"
" For each process, set specified resource limits:\n"
"\t%s -{cdfnstv} soft,hard ... pid ...\n"
"\t-c soft,hard\tset core file size limits\n"
"\t-d soft,hard\tset data segment (heap) size limits\n"
"\t-f soft,hard\tset file size limits\n"
"\t-n soft,hard\tset file descriptor limits\n"
"\t-s soft,hard\tset stack segment size limits\n"
"\t-t soft,hard\tset CPU time limits\n"
"\t-v soft,hard\tset virtual memory size limits\n"
"\t(default units are as shown by the output of '%s pid')\n",
command, command, command);
exit(2);
}
int
main(int argc, char **argv)
{
int retc = 0;
int opt;
int errflg = 0;
int set = FALSE;
struct ps_prochandle *Pr;
if ((command = strrchr(argv[0], '/')) != NULL)
command++;
else
command = argv[0];
while ((opt = getopt(argc, argv, "Fkmc:d:f:n:s:t:v:")) != EOF) {
switch (opt) {
case 'F': /* force grabbing (no O_EXCL) */
Fflag = PGRAB_FORCE;
break;
case 'k':
kbytes = TRUE;
mbytes = FALSE;
break;
case 'm':
kbytes = FALSE;
mbytes = TRUE;
break;
case 'c': /* core file size */
set = TRUE;
errflg += parse_limits(RLIMIT_CORE, optarg);
break;
case 'd': /* data segment size */
set = TRUE;
errflg += parse_limits(RLIMIT_DATA, optarg);
break;
case 'f': /* file size */
set = TRUE;
errflg += parse_limits(RLIMIT_FSIZE, optarg);
break;
case 'n': /* file descriptors */
set = TRUE;
errflg += parse_limits(RLIMIT_NOFILE, optarg);
break;
case 's': /* stack segment size */
set = TRUE;
errflg += parse_limits(RLIMIT_STACK, optarg);
break;
case 't': /* CPU time */
set = TRUE;
errflg += parse_limits(RLIMIT_CPU, optarg);
break;
case 'v': /* virtual memory size */
set = TRUE;
errflg += parse_limits(RLIMIT_VMEM, optarg);
break;
default:
errflg = 1;
break;
}
}
argc -= optind;
argv += optind;
if (errflg || argc <= 0)
usage();
/* catch signals from terminal */
if (sigset(SIGHUP, SIG_IGN) == SIG_DFL)
(void) sigset(SIGHUP, intr);
if (sigset(SIGINT, SIG_IGN) == SIG_DFL)
(void) sigset(SIGINT, intr);
if (sigset(SIGQUIT, SIG_IGN) == SIG_DFL)
(void) sigset(SIGQUIT, intr);
(void) sigset(SIGPIPE, intr);
(void) sigset(SIGTERM, intr);
while (--argc >= 0 && !interrupt) {
psinfo_t psinfo;
char *arg;
pid_t pid;
int gret;
(void) fflush(stdout); /* process-at-a-time */
/* get the specified pid and the psinfo struct */
if ((pid = proc_arg_psinfo(arg = *argv++, PR_ARG_PIDS,
&psinfo, &gret)) == -1) {
(void) fprintf(stderr, "%s: cannot examine %s: %s\n",
command, arg, Pgrab_error(gret));
retc = 1;
} else if ((Pr = Pgrab(pid, Fflag, &gret)) != NULL) {
if (Pcreate_agent(Pr) == 0) {
if (set) {
if (set_limits(Pr) != 0)
retc = 1;
} else {
proc_unctrl_psinfo(&psinfo);
(void) printf("%d:\t%.70s\n",
(int)pid, psinfo.pr_psargs);
show_limits(Pr);
}
Pdestroy_agent(Pr);
} else {
(void) fprintf(stderr,
"%s: cannot control process %d\n",
command, (int)pid);
retc = 1;
}
Prelease(Pr, 0);
} else {
if ((gret == G_SYS || gret == G_SELF) && !set) {
proc_unctrl_psinfo(&psinfo);
(void) printf("%d:\t%.70s\n", (int)pid,
psinfo.pr_psargs);
if (gret == G_SYS)
(void) printf(" [system process]\n");
else
show_limits(NULL);
} else {
(void) fprintf(stderr,
"%s: %s: %d\n",
command, Pgrab_error(gret), (int)pid);
retc = 1;
}
}
}
if (interrupt)
retc = 1;
return (retc);
}
static void
intr(int sig)
{
interrupt = sig;
}
/* ------ begin specific code ------ */
/*
* Compute a limit, given a string:
* unlimited unlimited
* nnn k nnn kilobytes
* nnn m nnn megabytes (minutes for CPU time)
* nnn h nnn hours (for CPU time only)
* mm : ss minutes and seconds (for CPU time only)
*/
static int
limit_value(int which, char *arg, rlim64_t *limit)
{
rlim64_t value;
rlim64_t unit;
char *lastc;
if (strcmp(arg, "unlimited") == 0) {
*limit = RLIM64_INFINITY;
return (0);
}
if (which == RLIMIT_CPU && strchr(arg, ':') != NULL) {
char *minutes = strtok_r(arg, " \t:", &lastc);
char *seconds = strtok_r(NULL, " \t", &lastc);
rlim64_t sec;
if (seconds != NULL && strtok_r(NULL, " \t", &lastc) != NULL)
return (1);
value = strtoull(minutes, &lastc, 10);
if (*lastc != '\0' || value > RLIM64_INFINITY / 60)
return (1);
if (seconds == NULL || *seconds == '\0')
sec = 0;
else {
sec = strtoull(seconds, &lastc, 10);
if (*lastc != '\0' || sec > 60)
return (1);
}
value = value * 60 + sec;
if (value > RLIM64_INFINITY)
value = RLIM64_INFINITY;
*limit = value;
return (0);
}
switch (*(lastc = arg + strlen(arg) - 1)) {
case 'k':
unit = 1024;
*lastc = '\0';
break;
case 'm':
if (which == RLIMIT_CPU)
unit = 60;
else
unit = 1024 * 1024;
*lastc = '\0';
break;
case 'h':
if (which == RLIMIT_CPU)
unit = 60 * 60;
else
return (1);
*lastc = '\0';
break;
default:
switch (which) {
case RLIMIT_CPU: unit = 1; break;
case RLIMIT_FSIZE: unit = 512; break;
case RLIMIT_DATA: unit = 1024; break;
case RLIMIT_STACK: unit = 1024; break;
case RLIMIT_CORE: unit = 512; break;
case RLIMIT_NOFILE: unit = 1; break;
case RLIMIT_VMEM: unit = 1024; break;
}
break;
}
value = strtoull(arg, &lastc, 10);
if (*lastc != '\0' || value > RLIM64_INFINITY / unit)
return (1);
value *= unit;
if (value > RLIM64_INFINITY)
value = RLIM64_INFINITY;
*limit = value;
return (0);
}
static int
parse_limits(int which, char *arg)
{
char *lastc;
char *soft = strtok_r(arg, " \t,", &lastc);
char *hard = strtok_r(NULL, " \t", &lastc);
struct rlimit64 *rp = &rlimit[which];
if (hard != NULL && strtok_r(NULL, " \t", &lastc) != NULL)
return (1);
if (soft == NULL || *soft == '\0') {
rp->rlim_cur = 0;
set_current[which] = FALSE;
} else {
if (limit_value(which, soft, &rp->rlim_cur) != 0)
return (1);
set_current[which] = TRUE;
}
if (hard == NULL || *hard == '\0') {
rp->rlim_max = 0;
set_maximum[which] = FALSE;
} else {
if (limit_value(which, hard, &rp->rlim_max) != 0)
return (1);
set_maximum[which] = TRUE;
}
if (set_current[which] && set_maximum[which] &&
rp->rlim_cur > rp->rlim_max)
return (1);
return (0);
}
static void
limit_adjust(struct rlimit64 *rp, int units)
{
if (rp->rlim_cur != RLIM64_INFINITY)
rp->rlim_cur /= units;
if (rp->rlim_max != RLIM64_INFINITY)
rp->rlim_max /= units;
}
static char *
limit_values(struct rlimit64 *rp)
{
static char buffer[64];
char buf1[32];
char buf2[32];
char *s1;
char *s2;
if (rp->rlim_cur == RLIM64_INFINITY)
s1 = "unlimited";
else {
(void) sprintf(s1 = buf1, "%lld", rp->rlim_cur);
if (strlen(s1) < 8)
(void) strcat(s1, "\t");
}
if (rp->rlim_max == RLIM64_INFINITY)
s2 = "unlimited";
else {
(void) sprintf(s2 = buf2, "%lld", rp->rlim_max);
}
(void) sprintf(buffer, "%s\t%s", s1, s2);
return (buffer);
}
static void
show_limits(struct ps_prochandle *Pr)
{
struct rlimit64 rlim;
int resource;
char buf[32];
char *s;
(void) printf(" resource\t\t current\t maximum\n");
for (resource = 0; resource < RLIM_NLIMITS; resource++) {
if (pr_getrlimit64(Pr, resource, &rlim) != 0)
continue;
switch (resource) {
case RLIMIT_CPU:
s = " time(seconds)\t\t";
break;
case RLIMIT_FSIZE:
if (kbytes) {
s = " file(kbytes)\t\t";
limit_adjust(&rlim, 1024);
} else if (mbytes) {
s = " file(mbytes)\t\t";
limit_adjust(&rlim, 1024 * 1024);
} else {
s = " file(blocks)\t\t";
limit_adjust(&rlim, 512);
}
break;
case RLIMIT_DATA:
if (mbytes) {
s = " data(mbytes)\t\t";
limit_adjust(&rlim, 1024 * 1024);
} else {
s = " data(kbytes)\t\t";
limit_adjust(&rlim, 1024);
}
break;
case RLIMIT_STACK:
if (mbytes) {
s = " stack(mbytes)\t\t";
limit_adjust(&rlim, 1024 * 1024);
} else {
s = " stack(kbytes)\t\t";
limit_adjust(&rlim, 1024);
}
break;
case RLIMIT_CORE:
if (kbytes) {
s = " coredump(kbytes)\t";
limit_adjust(&rlim, 1024);
} else if (mbytes) {
s = " coredump(mbytes)\t";
limit_adjust(&rlim, 1024 * 1024);
} else {
s = " coredump(blocks)\t";
limit_adjust(&rlim, 512);
}
break;
case RLIMIT_NOFILE:
s = " nofiles(descriptors)\t";
break;
case RLIMIT_VMEM:
if (mbytes) {
s = " vmemory(mbytes)\t";
limit_adjust(&rlim, 1024 * 1024);
} else {
s = " vmemory(kbytes)\t";
limit_adjust(&rlim, 1024);
}
break;
default:
(void) sprintf(buf, " rlimit #%d\t", resource);
s = buf;
break;
}
(void) printf("%s%s\n", s, limit_values(&rlim));
}
}
static int
set_one_limit(struct ps_prochandle *Pr, int which, rlim64_t cur, rlim64_t max)
{
struct rlimit64 rlim;
int be_su = 0;
prpriv_t *old_prpriv = NULL, *new_prpriv = NULL;
priv_set_t *eset, *pset;
int ret = 0;
if (pr_getrlimit64(Pr, which, &rlim) != 0) {
(void) fprintf(stderr,
"%s: unable to get process limit for pid %d: %s\n",
command, Pstatus(Pr)->pr_pid, strerror(errno));
return (1);
}
if (!set_current[which])
cur = rlim.rlim_cur;
if (!set_maximum[which])
max = rlim.rlim_max;
if (max < cur)
max = cur;
if (max > rlim.rlim_max && Pr != NULL)
be_su = 1;
rlim.rlim_cur = cur;
rlim.rlim_max = max;
if (be_su) {
new_prpriv = proc_get_priv(Pstatus(Pr)->pr_pid);
if (new_prpriv == NULL) {
(void) fprintf(stderr,
"%s: unable to get process privileges for pid"
" %d: %s\n", command, Pstatus(Pr)->pr_pid,
strerror(errno));
return (1);
}
/*
* We only have to change the process privileges if it doesn't
* already have PRIV_SYS_RESOURCE. In addition, we want to make
* sure that we don't leave a process with elevated privileges,
* so we make sure the process dies if we exit unexpectedly.
*/
eset = (priv_set_t *)
&new_prpriv->pr_sets[new_prpriv->pr_setsize *
priv_getsetbyname(PRIV_EFFECTIVE)];
pset = (priv_set_t *)
&new_prpriv->pr_sets[new_prpriv->pr_setsize *
priv_getsetbyname(PRIV_PERMITTED)];
if (!priv_ismember(eset, PRIV_SYS_RESOURCE)) {
/* Keep track of original privileges */
old_prpriv = proc_get_priv(Pstatus(Pr)->pr_pid);
if (old_prpriv == NULL) {
proc_free_priv(new_prpriv);
(void) fprintf(stderr,
"%s: unable to get process privileges "
"for pid %d: %s\n", command,
Pstatus(Pr)->pr_pid, strerror(errno));
return (1);
}
(void) priv_addset(eset, PRIV_SYS_RESOURCE);
(void) priv_addset(pset, PRIV_SYS_RESOURCE);
if (Psetflags(Pr, PR_KLC) != 0 ||
Psetpriv(Pr, new_prpriv) != 0) {
(void) fprintf(stderr,
"%s: unable to set process privileges for"
" pid %d: %s\n", command,
Pstatus(Pr)->pr_pid, strerror(errno));
(void) Punsetflags(Pr, PR_KLC);
proc_free_priv(new_prpriv);
proc_free_priv(old_prpriv);
return (1);
}
}
}
if (pr_setrlimit64(Pr, which, &rlim) != 0) {
(void) fprintf(stderr,
"%s: cannot set resource limit for pid %d: %s\n",
command, Pstatus(Pr)->pr_pid, strerror(errno));
ret = 1;
}
if (old_prpriv != NULL) {
if (Psetpriv(Pr, old_prpriv) != 0) {
/*
* If this fails, we can't leave a process hanging
* around with elevated privileges, so we'll have to
* release the process from libproc, knowing that it
* will be killed (since we set PR_KLC).
*/
Pdestroy_agent(Pr);
(void) fprintf(stderr,
"%s: cannot relinquish privileges for pid %d."
" The process was killed.",
command, Pstatus(Pr)->pr_pid);
ret = 1;
}
if (Punsetflags(Pr, PR_KLC) != 0) {
(void) fprintf(stderr,
"%s: cannot relinquish privileges for pid %d."
" The process was killed.",
command, Pstatus(Pr)->pr_pid);
ret = 1;
}
proc_free_priv(old_prpriv);
}
if (new_prpriv != NULL)
proc_free_priv(new_prpriv);
return (ret);
}
static int
set_limits(struct ps_prochandle *Pr)
{
int which;
int retc = 0;
for (which = 0; which < RLIM_NLIMITS; which++) {
if (set_current[which] || set_maximum[which]) {
if (set_one_limit(Pr, which, rlimit[which].rlim_cur,
rlimit[which].rlim_max) != 0)
retc = 1;
}
}
return (retc);
}