/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (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 (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved.
* Copyright 2015, Joyent, Inc.
*/
#include <unistd.h>
#include <rctl.h>
#include <libproc.h>
#include <stdio.h>
#include <libintl.h>
#include <locale.h>
#include <string.h>
#include <signal.h>
#include <strings.h>
#include <ctype.h>
#include <project.h>
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
#include <stdlib.h>
#include <sys/varargs.h>
#include <priv.h>
#include <zone.h>
#include "utils.h"
/* Valid user actions */
#define ACTION_DISABLE 0x01
#define ACTION_ENABLE 0x02
#define ACTION_SET 0x04
#define ACTION_REPLACE 0x08
#define ACTION_DELETE 0x10
#define PRCTL_VALUE_WIDTH 4
/* Maximum string length for deferred errors */
#define GLOBAL_ERR_SZ 1024
/* allow important process values to be passed together easily */
typedef struct pr_info_handle {
struct ps_prochandle *pr;
pid_t pid;
psinfo_t psinfo;
taskid_t taskid;
projid_t projid;
char *projname;
zoneid_t zoneid;
char *zonename;
} pr_info_handle_t;
/* Structures for list of resource controls */
typedef struct prctl_value {
rctlblk_t *rblk;
struct prctl_value *next;
} prctl_value_t;
typedef struct prctl_list {
char *name;
rctl_qty_t *usage;
prctl_value_t *val_list;
struct prctl_list *next;
} prctl_list_t;
static volatile int interrupt;
static prctl_list_t *global_rctl_list_head = NULL;
static prctl_list_t *global_rctl_list_tail = NULL;
static char global_error[GLOBAL_ERR_SZ];
/* global variables that contain commmand line option info */
static int arg_operation = 0;
static int arg_force = 0;
/* String and type from -i */
static rctl_entity_t arg_entity_type = RCENTITY_PROCESS;
static char *arg_entity_string = NULL;
/* -n argument */
static char *arg_name = NULL;
static rctl_entity_t arg_name_entity = 0;
/* -t argument value */
static int arg_priv = 0;
/* -v argument string */
static char *arg_valuestring = NULL;
/* global flags of rctl name passed to -n */
static int arg_global_flags = 0;
static rctl_qty_t arg_global_max;
/* appropriate scaling variables determined by rctl unit type */
scale_t *arg_scale;
static char *arg_unit = NULL;
/* -v argument string converted to uint64_t */
static uint64_t arg_value = 0;
/* if -v argument is scaled value, points to "K", "M", "G", ... */
static char *arg_modifier = NULL;
/* -e/-d argument string */
static char *arg_action_string = NULL;
/* Set to RCTL_LOCAL_SIGNAL|DENY based on arg_action_string */
static int arg_action = 0;
/* if -e/-d arg is signal=XXX, set to signal number of XXX */
static int arg_signal = 0;
/* -p arg if -p is specified */
static int arg_pid = -1;
static char *arg_pid_string = NULL;
/* Set to 1 if -P is specified */
static int arg_parseable_mode = 0;
/* interupt handler */
static void intr(int);
static int get_rctls(struct ps_prochandle *);
static int store_rctls(const char *rctlname, void *walk_data);
static prctl_value_t *store_value_entry(rctlblk_t *rblk, prctl_list_t *list);
static prctl_list_t *store_list_entry(const char *name);
static void free_lists();
static int change_action(rctlblk_t *blk);
static int prctl_setrctl(struct ps_prochandle *Pr, const char *name,
rctlblk_t *, rctlblk_t *, uint_t);
static int match_rctl(struct ps_prochandle *Pr, rctlblk_t **rctl, char *name,
char *valuestringin, int valuein, rctl_priv_t privin,
int pidin);
static int match_rctl_blk(rctlblk_t *rctl, char *valuestringin,
uint64_t valuein,
rctl_priv_t privin, int pidin);
static pid_t regrab_process(pid_t pid, pr_info_handle_t *p, int, int *gret);
static pid_t grab_process_by_id(char *idname, rctl_entity_t type,
pr_info_handle_t *p, int, int *gret);
static int grab_process(pr_info_handle_t *p, int *gret);
static void release_process(struct ps_prochandle *Pr);
static void preserve_error(char *format, ...);
static void print_rctls(pr_info_handle_t *p);
static void print_priv(rctl_priv_t local_priv, char *format);
static void print_local_action(int action, int *signalp, char *format);
static const char USAGE[] = ""
"usage:\n"
" Report resource control values and actions:\n"
" prctl [-P] [-t [basic | privileged | system]\n"
" [-n name] [-i process | task | project | zone] id ...\n"
" -P space delimited output\n"
" -t privilege level of rctl values to get\n"
" -n name of resource control values to get\n"
" -i idtype of operand list\n"
" Manipulate resource control values:\n"
" prctl [-t [basic | privileged | system]\n"
" -n name [-srx] [-v value] [-p pid ] [-e | -d action]\n"
" [-i process | task | project | zone] id ...\n"
" -t privilege level of rctl value to set/replace/delete/modify\n"
" -n name of resource control to set/replace/delete/modify\n"
" -s set new resource control value\n"
" -r replace first rctl value of matching privilege\n"
" -x delete first rctl value of matching privilege, value, and \n"
" recipient pid\n"
" -v value of rctl to set/replace/delete/modify\n"
" -p recipient pid of rctl to set/replace/delete/modify\n"
" -e enable action of first rctl value of matching privilege,\n"
" value, and recipient pid\n"
" -d disable action of first rctl value of matching privilege,\n"
" value, and recipient pid\n"
" -i idtype of operand list\n";
static void
usage()
{
(void) fprintf(stderr, gettext(USAGE));
exit(2);
}
int
main(int argc, char **argv)
{
int flags;
int opt, errflg = 0;
rctlblk_t *rctlblkA = NULL;
rctlblk_t *rctlblkB = NULL;
rctlblk_t *tmp = NULL;
pid_t pid;
char *target_id;
int search_type;
int signal;
int localaction;
int printed = 0;
int gret;
char *end;
(void) setlocale(LC_ALL, "");
(void) textdomain(TEXT_DOMAIN);
(void) setpname(argv[0]);
while ((opt = getopt(argc, argv, "sPp:Fd:e:i:n:rt:v:x")) != EOF) {
switch (opt) {
case 'F': /* force grabbing (no O_EXCL) */
arg_force = PGRAB_FORCE;
break;
case 'i': /* id type for arguments */
arg_entity_string = optarg;
if (strcmp(optarg, "process") == 0 ||
strcmp(optarg, "pid") == 0)
arg_entity_type = RCENTITY_PROCESS;
else if (strcmp(optarg, "project") == 0 ||
strcmp(optarg, "projid") == 0)
arg_entity_type = RCENTITY_PROJECT;
else if (strcmp(optarg, "task") == 0 ||
strcmp(optarg, "taskid") == 0)
arg_entity_type = RCENTITY_TASK;
else if (strcmp(optarg, "zone") == 0 ||
strcmp(optarg, "zoneid") == 0)
arg_entity_type = RCENTITY_ZONE;
else {
warn(gettext("unknown idtype %s"), optarg);
errflg = 1;
}
break;
case 'd':
arg_action_string = optarg;
arg_operation |= ACTION_DISABLE;
break;
case 'e':
arg_action_string = optarg;
arg_operation |= ACTION_ENABLE;
break;
case 'n': /* name of rctl */
arg_name = optarg;
if (strncmp(optarg, "process.",
strlen("process.")) == 0)
arg_name_entity = RCENTITY_PROCESS;
else if (strncmp(optarg, "project.",
strlen("project.")) == 0)
arg_name_entity = RCENTITY_PROJECT;
else if (strncmp(optarg, "task.",
strlen("task.")) == 0)
arg_name_entity = RCENTITY_TASK;
else if (strncmp(optarg, "zone.",
strlen("zone.")) == 0)
arg_name_entity = RCENTITY_ZONE;
break;
case 'r':
arg_operation |= ACTION_REPLACE;
break;
case 't': /* rctl type */
if (strcmp(optarg, "basic") == 0)
arg_priv = RCPRIV_BASIC;
else if (strcmp(optarg, "privileged") == 0)
arg_priv = RCPRIV_PRIVILEGED;
else if (strcmp(optarg, "priv") == 0)
arg_priv = RCPRIV_PRIVILEGED;
else if (strcmp(optarg, "system") == 0)
arg_priv = RCPRIV_SYSTEM;
else {
warn(gettext("unknown privilege %s"), optarg);
errflg = 1;
}
break;
case 'v': /* value */
arg_valuestring = optarg;
break;
case 's':
arg_operation |= ACTION_SET;
break;
case 'x': /* delete */
arg_operation |= ACTION_DELETE;
break;
case 'p':
errno = 0;
/* Stick with -1 if arg is "-" */
if (strcmp("-", optarg) == 0)
break;
arg_pid_string = optarg;
arg_pid = strtoul(optarg, &end, 10);
if (errno || *end != '\0' || end == optarg) {
warn(gettext("invalid pid %s"), optarg);
errflg = 1;
break;
}
break;
case 'P':
arg_parseable_mode = 1;
break;
default:
warn(gettext("unknown option"));
errflg = 1;
break;
}
}
argc -= optind;
argv += optind;
if (argc < 1) {
warn(gettext("no arguments specified"));
errflg = 1;
goto done_parse;
}
/* if -v is specified without -r, -x, -d, or -e, -s is implied */
if (arg_valuestring &&
(!(arg_operation & (ACTION_REPLACE | ACTION_DELETE |
ACTION_DISABLE | ACTION_ENABLE)))) {
arg_operation |= ACTION_SET;
}
/* operations require -n */
if (arg_operation && (arg_name == NULL)) {
warn(gettext("-n is required with -s, -r, -x, -e, or -d"));
errflg = 1;
goto done_parse;
}
/* enable and disable are exclusive */
if ((arg_operation & ACTION_ENABLE) &&
(arg_operation & ACTION_DISABLE)) {
warn(gettext("options -d and -e are exclusive"));
errflg = 1;
goto done_parse;
}
/* -s, -r, and -x are exclusive */
flags = arg_operation &
(ACTION_REPLACE | ACTION_SET | ACTION_DELETE);
if (flags & (flags - 1)) {
warn(gettext("options -s, -r, and -x are exclusive"));
errflg = 1;
goto done_parse;
}
/* -e or -d makes no sense with -x */
if ((arg_operation & ACTION_DELETE) &
(arg_operation & (ACTION_ENABLE | ACTION_DISABLE))) {
warn(gettext("options -e or -d not allowed with -x"));
errflg = 1;
goto done_parse;
}
/* if -r is specified -v must be as well */
if ((arg_operation & ACTION_REPLACE) && (!arg_valuestring)) {
warn(gettext("option -r requires use of option -v"));
errflg = 1;
goto done_parse;
}
/* if -s is specified -v must be as well */
if ((arg_operation & ACTION_SET) && (!arg_valuestring)) {
warn(gettext("option -s requires use of option -v"));
errflg = 1;
goto done_parse;
}
/* Specifying a recipient pid on a non-basic rctl makes no sense */
if (arg_pid != -1 && arg_priv > RCPRIV_BASIC) {
warn(gettext("option -p not allowed on non-basic rctl"));
errflg = 1;
goto done_parse;
}
/* Specifying a recipient pid on a privileged rctl makes no sense */
if (arg_pid != -1 &&
arg_priv == RCPRIV_PRIVILEGED) {
warn(gettext("option -p not allowed with privileged rctl"));
errflg = 1;
goto done_parse;
}
if (arg_operation) {
/* do additional checks if there is an operation */
if (arg_parseable_mode == 1) {
warn(gettext("-P not valid when manipulating "
"resource control values"));
errflg = 1;
goto done_parse;
}
/* get rctl global flags to determine if actions are valid */
if ((rctlblkA = calloc(1, rctlblk_size())) == NULL) {
warn(gettext("malloc failed: %s"),
strerror(errno));
errflg = 1;
goto done_parse;
}
if ((rctlblkB = calloc(1, rctlblk_size())) == NULL) {
warn(gettext("malloc failed: %s"),
strerror(errno));
errflg = 1;
goto done_parse;
}
/* get system rctl to get global flags and max value */
if (getrctl(arg_name, NULL, rctlblkA, RCTL_FIRST)) {
warn(gettext("failed to get resource control "
"for %s: %s"), arg_name, strerror(errno));
errflg = 1;
goto done_parse;
}
while (getrctl(arg_name, rctlblkA, rctlblkB, RCTL_NEXT) == 0) {
/* allow user interrupt */
if (interrupt) {
errflg = 1;
goto done_parse;
}
tmp = rctlblkB;
rctlblkB = rctlblkA;
rctlblkA = tmp;
if (rctlblk_get_privilege(rctlblkA) ==
RCPRIV_SYSTEM) {
break;
}
}
if (rctlblk_get_privilege(rctlblkA) != RCPRIV_SYSTEM) {
warn(gettext("failed to get system resource control "
"for %s: %s"), arg_name, strerror(errno));
errflg = 1;
goto done_parse;
}
/* figure out the correct scale and unit for this rctl */
arg_global_flags = rctlblk_get_global_flags(rctlblkA);
arg_global_max = rctlblk_get_value(rctlblkA);
if (arg_global_flags & RCTL_GLOBAL_BYTES) {
arg_unit = SCALED_UNIT_BYTES;
arg_scale = scale_binary;
} else if (arg_global_flags & RCTL_GLOBAL_SECONDS) {
arg_unit = SCALED_UNIT_SECONDS;
arg_scale = scale_metric;
} else {
arg_unit = SCALED_UNIT_NONE;
arg_scale = scale_metric;
}
/* parse -v value string */
if (arg_valuestring) {
if (scaledtouint64(arg_valuestring,
&arg_value, NULL, &arg_modifier, NULL,
arg_scale, arg_unit,
SCALED_ALL_FLAGS)) {
warn(gettext("invalid -v value %s"),
arg_valuestring);
errflg = 1;
goto done_parse;
}
if (arg_value > arg_global_max) {
warn(gettext("-v value %s exceeds system "
"limit for resource control: %s"),
arg_valuestring, arg_name);
errflg = 1;
goto done_parse;
}
}
/* parse action */
if (arg_action_string) {
char *sigchr;
char *iter;
if ((strcmp(arg_action_string, "signal") == 0) ||
(strcmp(arg_action_string, "sig") == 0)) {
if (arg_operation & ACTION_ENABLE) {
warn(gettext(
"signal name or number must be "
"specified with -e"));
errflg = 1;
goto done_parse;
}
arg_action = RCTL_LOCAL_SIGNAL;
arg_signal = -1;
} else if ((strncmp(arg_action_string,
"signal=", strlen("signal=")) == 0) ||
(strncmp(arg_action_string,
"sig=", strlen("sig=")) == 0)) {
arg_action = RCTL_LOCAL_SIGNAL;
sigchr = strrchr(arg_action_string, '=');
sigchr++;
iter = sigchr;
while (*iter) {
*iter = toupper(*iter);
iter++;
}
if (strncmp("SIG", sigchr, 3) == 0)
sigchr += 3;
if (str2sig(sigchr, &arg_signal) != 0) {
warn(gettext("signal invalid"));
errflg = 1;
goto done_parse;
}
} else if (strcmp(arg_action_string, "deny") == 0) {
arg_action = RCTL_LOCAL_DENY;
} else if (strcmp(arg_action_string, "all") == 0) {
if (arg_operation & ACTION_ENABLE) {
warn(gettext(
"cannot use action 'all' with -e"));
errflg = 1;
goto done_parse;
}
arg_action = RCTL_LOCAL_DENY |
RCTL_LOCAL_SIGNAL;
arg_signal = -1;
goto done_parse;
} else {
warn(gettext("action invalid"));
errflg = 1;
goto done_parse;
}
}
/* cannot manipulate system rctls */
if (arg_priv == RCPRIV_SYSTEM) {
warn(gettext("cannot modify system values"));
errflg = 1;
goto done_parse;
}
/* validate that the privilege is allowed */
if ((arg_priv == RCPRIV_BASIC) &&
(arg_global_flags & RCTL_GLOBAL_NOBASIC)) {
warn(gettext("basic values not allowed on rctl %s"),
arg_name);
errflg = 1;
goto done_parse;
}
/* validate that actions are appropriate for given rctl */
if ((arg_operation & ACTION_ENABLE) &&
(arg_action & RCTL_LOCAL_DENY) &&
(arg_global_flags & RCTL_GLOBAL_DENY_NEVER)) {
warn(gettext("unable to enable deny on rctl with "
"global flag 'no-deny'"));
errflg = 1;
goto done_parse;
}
if ((arg_operation & ACTION_DISABLE) &&
(arg_action & RCTL_LOCAL_DENY) &&
(arg_global_flags & RCTL_GLOBAL_DENY_ALWAYS)) {
warn(gettext("unable to disable deny on rctl with "
"global flag 'deny'"));
errflg = 1;
goto done_parse;
}
if ((arg_operation & ACTION_ENABLE) &&
(arg_action & RCTL_LOCAL_SIGNAL) &&
(arg_global_flags & RCTL_GLOBAL_SIGNAL_NEVER)) {
warn(gettext("unable to enable signal on rctl with "
"global flag 'no-signal'"));
errflg = 1;
goto done_parse;
}
/* now set defaults for options not supplied */
/*
* default privilege to basic if this is a seting an rctl
* operation
*/
if (arg_operation & ACTION_SET) {
if (arg_priv == 0) {
arg_priv = RCPRIV_BASIC;
}
}
/*
* -p is required when set a basic task,
* project or zone rctl
*/
if ((arg_pid == -1) &&
(arg_priv == RCPRIV_BASIC) &&
(arg_entity_type != RCENTITY_PROCESS) &&
(arg_operation & ACTION_SET) &&
(arg_name) &&
(arg_name_entity == RCENTITY_TASK ||
arg_name_entity == RCENTITY_PROJECT ||
arg_name_entity == RCENTITY_ZONE)) {
warn(gettext("-p pid required when setting or "
"replacing task or project rctl"));
errflg = 1;
goto done_parse;
}
} else {
/* validate for list mode */
/* -p is not valid in list mode */
if (arg_pid != -1) {
warn(gettext("-p pid requires -s, -r, -x, -e, or -d"));
errflg = 1;
goto done_parse;
}
}
/* getting/setting process rctl on task or project is error */
if ((arg_name && (arg_name_entity == RCENTITY_PROCESS)) &&
((arg_entity_type == RCENTITY_TASK) ||
(arg_entity_type == RCENTITY_PROJECT))) {
warn(gettext("cannot get/set process rctl on task "
"or project"));
errflg = 1;
goto done_parse;
}
/* getting/setting task rctl on project is error */
if ((arg_name && (arg_name_entity == RCENTITY_TASK)) &&
(arg_entity_type == RCENTITY_PROJECT)) {
warn(gettext("cannot get/set task rctl on project"));
errflg = 1;
goto done_parse;
}
done_parse:
/* free any rctlblk's that we may have allocated */
if (rctlblkA) {
free(rctlblkA);
rctlblkA = NULL;
}
if (rctlblkB) {
free(rctlblkB);
rctlblkB = NULL;
}
if (errflg)
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(SIGTERM, intr);
while (--argc >= 0 && !interrupt) {
pr_info_handle_t p;
char *arg = *argv++;
int intarg;
char *end;
errflg = 0;
gret = 0;
/* Store int version of arg */
errno = 0;
intarg = strtoul(arg, &end, 10);
if (errno || *end != '\0' || end == arg) {
intarg = -1;
}
/*
* -p defaults to arg if basic and collective rctl
* and -i process is specified
*/
if ((arg_pid == -1) &&
(arg_priv == RCPRIV_BASIC) &&
(arg_entity_type == RCENTITY_PROCESS) &&
(arg_name) &&
(arg_name_entity == RCENTITY_TASK ||
arg_name_entity == RCENTITY_PROJECT)) {
arg_pid_string = arg;
errno = 0;
arg_pid = intarg;
}
/* Specifying a recipient pid and -i pid is redundent */
if (arg_pid != -1 && arg_entity_type == RCENTITY_PROCESS &&
arg_pid != intarg) {
warn(gettext("option -p pid must match -i process"));
errflg = 1;
continue;
}
/* use recipient pid if we have one */
if (arg_pid_string != NULL) {
target_id = arg_pid_string;
search_type = RCENTITY_PROCESS;
} else {
target_id = arg;
search_type = arg_entity_type;
}
(void) fflush(stdout); /* process-at-a-time */
if (arg_operation != 0) {
if ((pid = grab_process_by_id(target_id,
search_type, &p, arg_priv, &gret)) < 0) {
/*
* Mark that an error occurred so that the
* return value can be set, but continue
* on with other processes
*/
errflg = 1;
continue;
}
/*
* At this point, the victim process is held.
* Do not call any Pgrab-unsafe functions until
* the process is released via release_process().
*/
errflg = get_rctls(p.pr);
if (arg_operation & ACTION_DELETE) {
/* match by privilege, value, and pid */
if (match_rctl(p.pr, &rctlblkA, arg_name,
arg_valuestring, arg_value, arg_priv,
arg_pid) != 0 || rctlblkA == NULL) {
if (interrupt)
goto out;
preserve_error(gettext("no matching "
"resource control found for "
"deletion"));
errflg = 1;
goto out;
}
/*
* grab correct process. This is neccessary
* if the recipient pid does not match the
* one we grabbed
*/
pid = regrab_process(
rctlblk_get_recipient_pid(rctlblkA),
&p, arg_priv, &gret);
if (pid < 0) {
errflg = 1;
goto out;
}
if (prctl_setrctl(p.pr, arg_name, NULL,
rctlblkA, RCTL_DELETE) != 0) {
errflg = 1;
goto out;
}
} else if (arg_operation & ACTION_SET) {
/* match by privilege, value, and pid */
if (match_rctl(p.pr, &rctlblkA, arg_name,
arg_valuestring, arg_value, arg_priv,
arg_pid) == 0) {
if (interrupt)
goto out;
preserve_error(gettext("resource "
"control already exists"));
errflg = 1;
goto out;
}
rctlblkB = calloc(1, rctlblk_size());
if (rctlblkB == NULL) {
preserve_error(gettext(
"malloc failed"), strerror(errno));
errflg = 1;
goto out;
}
rctlblk_set_value(rctlblkB, arg_value);
rctlblk_set_privilege(rctlblkB, arg_priv);
if (change_action(rctlblkB)) {
errflg = 1;
goto out;
}
if (prctl_setrctl(p.pr, arg_name, NULL,
rctlblkB, RCTL_INSERT) != 0) {
errflg = 1;
goto out;
}
} else if (arg_operation & ACTION_REPLACE) {
/*
* match rctl for deletion by privilege and
* pid only
*/
if (match_rctl(p.pr, &rctlblkA, arg_name,
NULL, 0, arg_priv,
arg_pid) != 0 || rctlblkA == NULL) {
if (interrupt)
goto out;
preserve_error(gettext("no matching "
"resource control to replace"));
errflg = 1;
goto out;
}
/*
* grab correct process. This is neccessary
* if the recipient pid does not match the
* one we grabbed
*/
pid = regrab_process(
rctlblk_get_recipient_pid(rctlblkA),
&p, arg_priv, &gret);
if (pid < 0) {
errflg = 1;
goto out;
}
pid = rctlblk_get_recipient_pid(rctlblkA);
/*
* match by privilege, value and pid to
* check if new rctl already exists
*/
if (match_rctl(p.pr, &rctlblkB, arg_name,
arg_valuestring, arg_value, arg_priv,
pid) < 0) {
if (interrupt)
goto out;
preserve_error(gettext(
"Internal Error"));
errflg = 1;
goto out;
}
/*
* If rctl already exists, and it does not
* match the one that we will delete, than
* the replace will fail.
*/
if (rctlblkB != NULL &&
arg_value != rctlblk_get_value(rctlblkA)) {
preserve_error(gettext("replacement "
"resource control already "
"exists"));
errflg = 1;
goto out;
}
/* create new rctl */
rctlblkB = calloc(1, rctlblk_size());
if (rctlblkB == NULL) {
preserve_error(gettext(
"malloc failed"), strerror(errno));
errflg = 1;
goto out;
}
localaction =
rctlblk_get_local_action(rctlblkA, &signal);
rctlblk_set_local_action(rctlblkB, localaction,
signal);
rctlblk_set_value(rctlblkB, arg_value);
rctlblk_set_privilege(rctlblkB,
rctlblk_get_privilege(rctlblkA));
if (change_action(rctlblkB)) {
errflg = 1;
goto out;
}
/* do replacement */
if (prctl_setrctl(p.pr, arg_name, rctlblkA,
rctlblkB, RCTL_REPLACE) != 0) {
errflg = 1;
goto out;
}
} else if (arg_operation &
(ACTION_ENABLE | ACTION_DISABLE)) {
rctlblkB = calloc(1, rctlblk_size());
if (rctlblkB == NULL) {
preserve_error(gettext(
"malloc failed"), strerror(errno));
errflg = 1;
goto out;
}
/* match by privilege, value, and pid */
if (match_rctl(p.pr, &rctlblkA, arg_name,
arg_valuestring, arg_value, arg_priv,
arg_pid) != 0) {
if (interrupt)
goto out;
/* if no match, just set new rctl */
if (arg_priv == 0)
arg_priv = RCPRIV_BASIC;
if ((arg_priv == RCPRIV_BASIC) &&
(arg_entity_type !=
RCENTITY_PROCESS) &&
(arg_pid_string == NULL)) {
preserve_error(gettext(
"-p required when setting "
"basic rctls"));
errflg = 1;
goto out;
}
rctlblk_set_value(rctlblkB,
arg_value);
rctlblk_set_privilege(
rctlblkB, arg_priv);
if (change_action(rctlblkB)) {
errflg = 1;
goto out;
}
if (prctl_setrctl(p.pr,
arg_name, NULL, rctlblkB,
RCTL_INSERT) != 0) {
errflg = 1;
goto out;
}
goto out;
}
if (rctlblkA == NULL) {
preserve_error(gettext("no matching "
"resource control found"));
errflg = 1;
goto out;
}
/*
* grab correct process. This is neccessary
* if the recipient pid does not match the
* one we grabbed
*/
pid = regrab_process(
rctlblk_get_recipient_pid(rctlblkA),
&p, arg_priv, &gret);
if (pid < 0) {
errflg = 1;
goto out;
}
localaction =
rctlblk_get_local_action(rctlblkA,
&signal);
rctlblk_set_local_action(rctlblkB, localaction,
signal);
rctlblk_set_privilege(rctlblkB,
rctlblk_get_privilege(rctlblkA));
rctlblk_set_value(rctlblkB,
rctlblk_get_value(rctlblkA));
if (change_action(rctlblkB)) {
errflg = 1;
goto out;
}
if (prctl_setrctl(p.pr, arg_name, rctlblkA,
rctlblkB, RCTL_REPLACE) != 0) {
errflg = 1;
goto out;
}
}
out:
release_process(p.pr);
if (rctlblkA)
free(rctlblkA);
if (rctlblkB)
free(rctlblkB);
/* Print any errors that occurred */
if (errflg && *global_error != '\0') {
proc_unctrl_psinfo(&(p.psinfo));
(void) fprintf(stderr, "%d:\t%.70s\n",
(int)p.pid, p.psinfo.pr_psargs);
warn("%s\n", global_error);
break;
}
} else {
struct project projent;
char buf[PROJECT_BUFSZ];
char zonename[ZONENAME_MAX];
/*
* Hack to allow the user to specify a system
* process.
*/
gret = G_SYS;
pid = grab_process_by_id(
target_id, search_type, &p, RCPRIV_BASIC, &gret);
/*
* Print system process if user chose specifically
* to inspect a system process.
*/
if (arg_entity_type == RCENTITY_PROCESS &&
pid < 0 &&
gret == G_SYS) {
/*
* Add blank lines between output for
* operands.
*/
if (printed) {
(void) fprintf(stdout, "\n");
}
proc_unctrl_psinfo(&(p.psinfo));
(void) printf(
"process: %d: %s [ system process ]\n",
(int)p.pid, p.psinfo.pr_psargs);
printed = 1;
continue;
} else if (pid < 0) {
/*
* Mark that an error occurred so that the
* return value can be set, but continue
* on with other processes
*/
errflg = 1;
continue;
}
errflg = get_rctls(p.pr);
release_process(p.pr);
/* handle user interrupt of getting rctls */
if (interrupt)
break;
/* add blank lines between output for operands */
if (printed) {
(void) fprintf(stdout, "\n");
}
/* First print any errors */
if (errflg) {
warn("%s\n", global_error);
free_lists();
break;
}
if (getprojbyid(p.projid, &projent, buf,
sizeof (buf))) {
p.projname = projent.pj_name;
} else {
p.projname = "";
}
if (getzonenamebyid(p.zoneid, zonename,
sizeof (zonename)) > 0) {
p.zonename = zonename;
} else {
p.zonename = "";
}
print_rctls(&p);
printed = 1;
/* Free the resource control lists */
free_lists();
}
}
if (interrupt)
errflg = 1;
/*
* return error if one occurred
*/
return (errflg);
}
static void
intr(int sig)
{
interrupt = sig;
}
/*
* get_rctls(struct ps_prochandle *, const char *)
*
* If controlname is given, store only controls for that named
* resource. If controlname is NULL, store all controls for all
* resources.
*
* This function is Pgrab-safe.
*/
static int
get_rctls(struct ps_prochandle *Pr)
{
int ret = 0;
if (arg_name == NULL) {
if (rctl_walk(store_rctls, Pr) != 0)
ret = 1;
} else {
ret = store_rctls(arg_name, Pr);
}
return (ret);
}
/*
* store_rctls(const char *, void *)
*
* Store resource controls for the given name in a linked list.
* Honor the user's options, and store only the ones they are
* interested in. If priv is not 0, show only controls that match
* the given privilege.
*
* This function is Pgrab-safe
*/
static int
store_rctls(const char *rctlname, void *walk_data)
{
struct ps_prochandle *Pr = walk_data;
rctlblk_t *rblk2, *rblk_tmp, *rblk1 = NULL;
prctl_list_t *list = NULL;
rctl_priv_t rblk_priv;
rctl_entity_t rblk_entity;
if (((rblk1 = calloc(1, rctlblk_size())) == NULL) ||
((rblk2 = calloc(1, rctlblk_size())) == NULL)) {
if (rblk1 != NULL)
free(rblk1);
preserve_error(gettext("malloc failed: %s"),
strerror(errno));
return (1);
}
if (pr_getrctl(Pr, rctlname, NULL, rblk1, RCTL_FIRST)) {
preserve_error(gettext("failed to get resource control "
"for %s: %s"), rctlname, strerror(errno));
free(rblk1);
free(rblk2);
return (1);
}
/* Store control if it matches privilege and enity type criteria */
rblk_priv = rctlblk_get_privilege(rblk1);
rblk_entity = 0;
if (strncmp(rctlname, "process.",
strlen("process.")) == 0)
rblk_entity = RCENTITY_PROCESS;
else if (strncmp(rctlname, "project.",
strlen("project.")) == 0)
rblk_entity = RCENTITY_PROJECT;
else if (strncmp(rctlname, "task.",
strlen("task.")) == 0)
rblk_entity = RCENTITY_TASK;
else if (strncmp(rctlname, "zone.",
strlen("zone.")) == 0)
rblk_entity = RCENTITY_ZONE;
if (((arg_priv == 0) || (rblk_priv == arg_priv)) &&
((arg_name == NULL) ||
strncmp(rctlname, arg_name, strlen(arg_name)) == 0) &&
(arg_entity_string == NULL || rblk_entity >= arg_entity_type)) {
/* Once we know we have some controls, store the name */
if ((list = store_list_entry(rctlname)) == NULL) {
free(rblk1);
free(rblk2);
return (1);
}
if (store_value_entry(rblk1, list) == NULL) {
free(rblk1);
free(rblk2);
return (1);
}
}
while (pr_getrctl(Pr, rctlname, rblk1, rblk2, RCTL_NEXT) == 0) {
/*
* in case this is stuck for some reason, allow manual
* interrupt
*/
if (interrupt) {
free(rblk1);
free(rblk2);
return (1);
}
rblk_priv = rctlblk_get_privilege(rblk2);
/*
* Store control if it matches privilege and entity type
* criteria
*/
if (((arg_priv == 0) || (rblk_priv == arg_priv)) &&
((arg_name == NULL) ||
strncmp(rctlname, arg_name, strlen(arg_name)) == 0) &&
(arg_entity_string == NULL ||
rblk_entity == arg_entity_type)) {
/* May not have created the list yet. */
if (list == NULL) {
if ((list = store_list_entry(rctlname))
== NULL) {
free(rblk1);
free(rblk2);
return (1);
}
}
if (store_value_entry(rblk2, list) == NULL) {
free(rblk1);
free(rblk2);
return (1);
}
}
rblk_tmp = rblk1;
rblk1 = rblk2;
rblk2 = rblk_tmp;
}
/*
* Get the current usage for the resource control if it matched the
* privilege and entity type criteria.
*/
if (list != NULL) {
if (pr_getrctl(Pr, rctlname, NULL, rblk2, RCTL_USAGE) == 0) {
list->usage = (rctl_qty_t *)malloc(sizeof (rctl_qty_t));
if (list->usage == NULL) {
preserve_error(gettext("malloc failed: %s"),
strerror(errno));
free(rblk1);
free(rblk2);
return (1);
}
*list->usage = rctlblk_get_value(rblk2);
} else {
list->usage = NULL;
if (errno != ENOTSUP) {
preserve_error(gettext("failed to get "
"resource control usage for %s: %s"),
rctlname, strerror(errno));
free(rblk1);
free(rblk2);
return (1);
}
}
}
free(rblk1);
free(rblk2);
return (0);
}
/*
* store_value_entry(rctlblk_t *, prctl_list_t *)
*
* Store an rblk for a given resource control into the global list.
*
* This function is Pgrab-safe.
*/
prctl_value_t *
store_value_entry(rctlblk_t *rblk, prctl_list_t *list)
{
prctl_value_t *e = calloc(1, sizeof (prctl_value_t));
rctlblk_t *store_blk = calloc(1, rctlblk_size());
prctl_value_t *iter = list->val_list;
if (e == NULL || store_blk == NULL) {
preserve_error(gettext("malloc failed %s"),
strerror(errno));
if (e != NULL)
free(e);
if (store_blk != NULL)
free(store_blk);
return (NULL);
}
if (iter == NULL)
list->val_list = e;
else {
while (iter->next != NULL) {
iter = iter->next;
}
iter->next = e;
}
bcopy(rblk, store_blk, rctlblk_size());
e->rblk = store_blk;
e->next = NULL;
return (e);
}
/*
* store_list_entry(const char *)
*
* Store a new resource control value in the global list. No checking
* for duplicates done.
*
* This function is Pgrab-safe.
*/
prctl_list_t *
store_list_entry(const char *name)
{
prctl_list_t *e = calloc(1, sizeof (prctl_list_t));
if (e == NULL) {
preserve_error(gettext("malloc failed %s"),
strerror(errno));
return (NULL);
}
if ((e->name = calloc(1, strlen(name) + 1)) == NULL) {
preserve_error(gettext("malloc failed %s"),
strerror(errno));
free(e);
return (NULL);
}
(void) strcpy(e->name, name);
e->val_list = NULL;
if (global_rctl_list_head == NULL) {
global_rctl_list_head = e;
global_rctl_list_tail = e;
} else {
global_rctl_list_tail->next = e;
global_rctl_list_tail = e;
}
e->next = NULL;
return (e);
}
/*
* free_lists()
*
* Free all resource control blocks and values from the global lists.
*
* This function is Pgrab-safe.
*/
void
free_lists()
{
prctl_list_t *new_list, *old_list = global_rctl_list_head;
prctl_value_t *old_val, *new_val;
while (old_list != NULL) {
old_val = old_list->val_list;
while (old_val != NULL) {
free(old_val->rblk);
new_val = old_val->next;
free(old_val);
old_val = new_val;
}
free(old_list->name);
free(old_list->usage);
new_list = old_list->next;
free(old_list);
old_list = new_list;
}
global_rctl_list_head = NULL;
global_rctl_list_tail = NULL;
}
void
print_heading()
{
/* print headings */
(void) fprintf(stdout, "%-8s%-16s%-9s%-7s%-28s%10s\n",
"NAME", "PRIVILEGE", "VALUE",
"FLAG", "ACTION", "RECIPIENT");
}
/*
* print_rctls()
*
* Print all resource controls from the global list that was
* previously populated by store_rctls.
*/
void
print_rctls(pr_info_handle_t *p)
{
prctl_list_t *iter_list = global_rctl_list_head;
prctl_value_t *iter_val;
rctl_qty_t rblk_value;
rctl_priv_t rblk_priv;
uint_t local_action;
int signal, local_flags, global_flags;
pid_t pid;
char rctl_valuestring[SCALED_STRLEN];
char *unit = NULL;
scale_t *scale;
char *string;
int doneheading = 0;
if (iter_list == NULL)
return;
while (iter_list != NULL) {
if (doneheading == 0 &&
arg_entity_type == RCENTITY_PROCESS) {
proc_unctrl_psinfo(&(p->psinfo));
doneheading = 1;
(void) fprintf(stdout,
"process: %d: %.70s\n", (int)p->pid,
p->psinfo.pr_psargs);
if (!arg_parseable_mode)
print_heading();
}
if (doneheading == 0 &&
arg_entity_type == RCENTITY_TASK) {
doneheading = 1;
(void) fprintf(stdout, "task: %d\n", (int)p->taskid);
if (!arg_parseable_mode)
print_heading();
}
if (doneheading == 0 &&
arg_entity_type == RCENTITY_PROJECT) {
if (!arg_parseable_mode && doneheading)
(void) fprintf(stdout, "\n");
doneheading = 1;
(void) fprintf(stdout,
"project: %d: %.70s\n", (int)p->projid,
p->projname);
if (!arg_parseable_mode)
print_heading();
}
if (doneheading == 0 &&
arg_entity_type == RCENTITY_ZONE) {
doneheading = 1;
(void) fprintf(stdout,
"zone: %d: %.70s\n", (int)p->zoneid,
p->zonename);
if (!arg_parseable_mode)
print_heading();
}
/* only print name once in normal output */
if (!arg_parseable_mode)
(void) fprintf(stdout, "%s\n", iter_list->name);
iter_val = iter_list->val_list;
/* if for some reason there are no values, skip */
if (iter_val == 0)
continue;
/* get the global flags the first rctl only */
global_flags = rctlblk_get_global_flags(iter_val->rblk);
if (global_flags & RCTL_GLOBAL_BYTES) {
unit = SCALED_UNIT_BYTES;
scale = scale_binary;
} else if (global_flags & RCTL_GLOBAL_SECONDS) {
unit = SCALED_UNIT_SECONDS;
scale = scale_metric;
} else {
unit = SCALED_UNIT_NONE;
scale = scale_metric;
}
/* print the current usage for the rctl if available */
if (iter_list->usage != NULL) {
rblk_value = *(iter_list->usage);
if (!arg_parseable_mode) {
(void) uint64toscaled(rblk_value, 4, "E",
rctl_valuestring, NULL, NULL,
scale, NULL, 0);
(void) fprintf(stdout, "%8s%-16s%5s%-4s\n",
"", "usage", rctl_valuestring, unit);
} else {
(void) fprintf(stdout, "%s %s %llu - - -\n",
iter_list->name, "usage", rblk_value);
}
}
/* iterate over an print all control values */
while (iter_val != NULL) {
/* print name or empty name field */
if (!arg_parseable_mode)
(void) fprintf(stdout, "%8s", "");
else
(void) fprintf(stdout, "%s ", iter_list->name);
rblk_priv = rctlblk_get_privilege(iter_val->rblk);
if (!arg_parseable_mode)
print_priv(rblk_priv, "%-16s");
else
print_priv(rblk_priv, "%s ");
rblk_value = rctlblk_get_value(iter_val->rblk);
if (arg_parseable_mode) {
(void) fprintf(stdout, "%llu ", rblk_value);
} else {
(void) uint64toscaled(rblk_value, 4, "E",
rctl_valuestring, NULL, NULL,
scale, NULL, 0);
(void) fprintf(stdout, "%5s",
rctl_valuestring);
(void) fprintf(stdout, "%-4s", unit);
}
local_flags = rctlblk_get_local_flags(iter_val->rblk);
if (local_flags & RCTL_LOCAL_MAXIMAL) {
if (global_flags & RCTL_GLOBAL_INFINITE) {
string = "inf";
} else {
string = "max";
}
} else {
string = "-";
}
if (arg_parseable_mode)
(void) fprintf(stdout, "%s ", string);
else
(void) fprintf(stdout, "%4s%3s",
string, "");
local_action = rctlblk_get_local_action(iter_val->rblk,
&signal);
if (arg_parseable_mode)
print_local_action(local_action, &signal,
"%s ");
else
print_local_action(local_action, &signal,
"%-28s");
pid = rctlblk_get_recipient_pid(iter_val->rblk);
if (arg_parseable_mode) {
if (pid < 0) {
(void) fprintf(stdout, "%s\n", "-");
} else {
(void) fprintf(stdout, "%d\n",
(int)pid);
}
} else {
if (pid < 0) {
(void) fprintf(stdout, "%10s\n", "-");
} else {
(void) fprintf(stdout, "%10d\n",
(int)pid);
}
}
iter_val = iter_val->next;
}
iter_list = iter_list->next;
}
}
/*
*
* match_rctl
*
* find the first rctl with matching name, value, priv, and recipient pid
*/
int
match_rctl(struct ps_prochandle *Pr, rctlblk_t **rctl, char *name,
char *valuestringin, int valuein, rctl_priv_t privin, int pidin)
{
rctlblk_t *next;
rctlblk_t *last;
rctlblk_t *tmp;
*rctl = NULL;
next = calloc(1, rctlblk_size());
last = calloc(1, rctlblk_size());
if ((last == NULL) || (next == NULL)) {
preserve_error(gettext("malloc failed"), strerror(errno));
return (-1);
}
/*
* For this resource name, now iterate through all
* the controls, looking for a match to the
* user-specified input.
*/
if (pr_getrctl(Pr, name, NULL, next, RCTL_FIRST)) {
preserve_error(gettext("failed to get resource control "
"for %s: %s"), name, strerror(errno));
return (-1);
}
if (match_rctl_blk(next, valuestringin, valuein, privin, pidin) == 1) {
free(last);
*rctl = next;
return (0);
}
tmp = next;
next = last;
last = tmp;
while (pr_getrctl(Pr, name, last, next, RCTL_NEXT) == 0) {
/* allow user interrupt */
if (interrupt)
break;
if (match_rctl_blk(next, valuestringin, valuein, privin, pidin)
== 1) {
free(last);
*rctl = next;
return (0);
}
tmp = next;
next = last;
last = tmp;
}
free(next);
free(last);
return (1);
}
/*
* int match_rctl_blk(rctlblk_t *, char *, uint64, rctl_priv_t, int pid)
*
* Input
* Must supply a valid rctl, value, privilege, and pid to match on.
* If valuestring is NULL, then valuestring and valuein will not be used
* If privilege type is 0 it will not be used.
* If pid is -1 it will not be used.
*
* Return values
* Returns 1 if a matching rctl given matches the parameters specified, and
* 0 if they do not.
*
* This function is Pgrab-safe.
*/
int
match_rctl_blk(rctlblk_t *rctl, char *valuestringin,
uint64_t valuein, rctl_priv_t privin, int pidin)
{
rctl_qty_t value;
rctl_priv_t priv;
pid_t pid;
int valuematch = 1;
int privmatch = 1;
int pidmatch = 1;
value = rctlblk_get_value(rctl);
priv = rctlblk_get_privilege(rctl);
pid = rctlblk_get_recipient_pid(rctl);
if (valuestringin) {
if (arg_modifier == NULL) {
valuematch = (valuein == value);
} else {
valuematch = scaledequint64(valuestringin, value,
PRCTL_VALUE_WIDTH,
arg_scale, arg_unit,
SCALED_ALL_FLAGS);
}
}
if (privin != 0) {
privmatch = (privin == priv);
}
if (pidin != -1) {
pidmatch = (pidin == pid);
}
return (valuematch && privmatch && pidmatch);
}
static int
change_action(rctlblk_t *blk)
{
int signal = 0;
int action;
action = rctlblk_get_local_action(blk, &signal);
if (arg_operation & ACTION_ENABLE) {
if (arg_action & RCTL_LOCAL_SIGNAL) {
signal = arg_signal;
}
action = action | arg_action;
/* add local action */
rctlblk_set_local_action(blk, action, signal);
} else if (arg_operation & ACTION_DISABLE) {
/*
* if deleting signal and signal number is specified,
* then signal number must match
*/
if ((arg_action & RCTL_LOCAL_SIGNAL) &&
(arg_signal != -1)) {
if (arg_signal != signal) {
preserve_error(gettext("signal name or number "
"does not match existing action"));
return (-1);
}
}
/* remove local action */
action = action & (~arg_action);
rctlblk_set_local_action(blk, RCTL_LOCAL_NOACTION, 0);
rctlblk_set_local_action(blk, action, signal);
}
/* enable deny if it must be enabled */
if (arg_global_flags & RCTL_GLOBAL_DENY_ALWAYS) {
rctlblk_set_local_action(blk, RCTL_LOCAL_DENY | action,
signal);
}
return (0);
}
/*
* prctl_setrctl
*
* Input
* This function expects that input has been validated. In the
* case of a replace operation, both old_rblk and new_rblk must
* be valid resource controls. If a resource control is being
* created, only new_rblk must be supplied. If a resource control
* is being deleted, only new_rblk must be supplied.
*
* If the privilege is a priviliged type, at this time, the process
* tries to take on superuser privileges.
*/
int
prctl_setrctl(struct ps_prochandle *Pr, const char *name,
rctlblk_t *old_rblk, rctlblk_t *new_rblk, uint_t flags)
{
int ret = 0;
rctl_priv_t rblk_priv;
psinfo_t psinfo;
zoneid_t oldzoneid = GLOBAL_ZONEID;
prpriv_t *old_prpriv = NULL, *new_prpriv = NULL;
priv_set_t *eset, *pset;
boolean_t relinquish_failed = B_FALSE;
rblk_priv = rctlblk_get_privilege(new_rblk);
if (rblk_priv == RCPRIV_SYSTEM) {
preserve_error(gettext("cannot modify system values"));
return (1);
}
if (rblk_priv == RCPRIV_PRIVILEGED) {
new_prpriv = proc_get_priv(Pstatus(Pr)->pr_pid);
if (new_prpriv == NULL) {
preserve_error(gettext("cannot get process privileges "
"for pid %d: %s"), 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) {
preserve_error(gettext("cannot get process "
"privileges for pid %d: %s"),
Pstatus(Pr)->pr_pid, strerror(errno));
proc_free_priv(new_prpriv);
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) {
preserve_error(gettext("cannot set process "
"privileges for pid %d: %s"),
Pstatus(Pr)->pr_pid, strerror(errno));
(void) Punsetflags(Pr, PR_KLC);
proc_free_priv(new_prpriv);
proc_free_priv(old_prpriv);
return (1);
}
}
/*
* If this is a zone.* rctl, it requires more than
* PRIV_SYS_RESOURCE: it wants the process to have global-zone
* credentials. We temporarily grant non-global zone processes
* these credentials, and make sure the process dies if we exit
* unexpectedly.
*/
if (arg_name &&
arg_name_entity == RCENTITY_ZONE &&
getzoneid() == GLOBAL_ZONEID &&
proc_get_psinfo(Pstatus(Pr)->pr_pid, &psinfo) == 0 &&
(oldzoneid = psinfo.pr_zoneid) != GLOBAL_ZONEID) {
/*
* We need to give this process superuser
* ("super-zone") privileges.
*
* Must never return without setting this back!
*/
if (Psetflags(Pr, PR_KLC) != 0 ||
Psetzoneid(Pr, GLOBAL_ZONEID) < 0) {
preserve_error(gettext(
"cannot set global-zone "
"privileges for pid %d: %s"),
Pstatus(Pr)->pr_pid, strerror(errno));
/*
* We couldn't set the zoneid to begin with, so
* there's no point in warning the user about
* trying to un-set it.
*/
oldzoneid = GLOBAL_ZONEID;
ret = 1;
goto bail;
}
}
}
/* Now, actually populate the rctlblk in the kernel */
if (flags == RCTL_REPLACE) {
/*
* Replace should be a delete followed by an insert. This
* allows us to replace rctl value blocks which match in
* privilege and value, but have updated actions, etc.
* setrctl() doesn't allow a direct replace, but we
* should do the right thing for the user in the command.
*/
if (pr_setrctl(Pr, name, NULL,
old_rblk, RCTL_DELETE)) {
preserve_error(gettext("failed to delete resource "
"control %s for pid %d: %s"), name,
Pstatus(Pr)->pr_pid, strerror(errno));
ret = 1;
goto bail;
}
if (pr_setrctl(Pr, name, NULL,
new_rblk, RCTL_INSERT)) {
preserve_error(gettext("failed to insert resource "
"control %s for pid %d: %s"), name,
Pstatus(Pr)->pr_pid, strerror(errno));
ret = 1;
goto bail;
}
} else if (flags == RCTL_INSERT) {
if (pr_setrctl(Pr, name, NULL,
new_rblk, RCTL_INSERT)) {
preserve_error(gettext("failed to create resource "
"control %s for pid %d: %s"), name,
Pstatus(Pr)->pr_pid, strerror(errno));
ret = 1;
goto bail;
}
} else if (flags == RCTL_DELETE) {
if (pr_setrctl(Pr, name, NULL,
new_rblk, RCTL_DELETE)) {
preserve_error(gettext("failed to delete resource "
"control %s for pid %d: %s"), name,
Pstatus(Pr)->pr_pid, strerror(errno));
ret = 1;
goto bail;
}
}
bail:
if (oldzoneid != GLOBAL_ZONEID) {
if (Psetzoneid(Pr, oldzoneid) != 0)
relinquish_failed = B_TRUE;
}
if (old_prpriv != NULL) {
if (Psetpriv(Pr, old_prpriv) != 0)
relinquish_failed = B_TRUE;
proc_free_priv(old_prpriv);
}
if (relinquish_failed) {
/*
* If this failed, 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);
preserve_error(gettext("cannot relinquish privileges "
"for pid %d. The process was killed."),
Pstatus(Pr)->pr_pid);
} else {
if (Punsetflags(Pr, PR_KLC) != 0)
preserve_error(gettext("cannot relinquish privileges "
"for pid %d. The process was killed."),
Pstatus(Pr)->pr_pid);
}
if (new_prpriv != NULL)
proc_free_priv(new_prpriv);
return (ret);
}
void
print_priv(rctl_priv_t local_priv, char *format)
{
char pstring[11];
switch (local_priv) {
case RCPRIV_BASIC:
(void) strcpy(pstring, "basic");
break;
case RCPRIV_PRIVILEGED:
(void) strcpy(pstring, "privileged");
break;
case RCPRIV_SYSTEM:
(void) strcpy(pstring, "system");
break;
default:
(void) sprintf(pstring, "%d", local_priv);
break;
}
/* LINTED */
(void) fprintf(stdout, format, pstring);
}
void
print_local_action(int action, int *signalp, char *format)
{
char sig[SIG2STR_MAX];
char sigstring[SIG2STR_MAX + 7];
char astring[5 + SIG2STR_MAX + 7];
int set = 0;
astring[0] = '\0';
if (action == RCTL_LOCAL_NOACTION) {
(void) strcat(astring, "none");
set++;
}
if (action & RCTL_LOCAL_DENY) {
(void) strcat(astring, "deny");
set++;
}
if ((action & RCTL_LOCAL_DENY) &&
(action & RCTL_LOCAL_SIGNAL)) {
(void) strcat(astring, ",");
}
if (action & RCTL_LOCAL_SIGNAL) {
if (sig2str(*signalp, sig))
(void) snprintf(sigstring, sizeof (astring),
"signal=%d", *signalp);
else
(void) snprintf(sigstring, sizeof (astring),
"signal=%s", sig);
(void) strcat(astring, sigstring);
set++;
}
if (set)
/* LINTED */
(void) fprintf(stdout, format, astring);
else
/* LINTED */
(void) fprintf(stdout, format, action);
}
/*
* This function is used to grab the process matching the recipient pid
*/
pid_t
regrab_process(pid_t pid, pr_info_handle_t *p, int priv, int *gret)
{
char pidstring[24];
gret = 0;
if (pid == -1)
return (p->pid);
if (p->pid == pid)
return (p->pid);
release_process(p->pr);
(void) memset(p, 0, sizeof (*p));
(void) snprintf(pidstring, 24, "%d", pid);
return (grab_process_by_id(
pidstring, RCENTITY_PROCESS, p, priv, gret));
}
/*
* int grab_process_by_id(char *, rctl_entity_t, pr_info_handle_t *, int, int *)
*
* Input
* Supply a non-NULL string containing:
* - logical project/zone name or project/zone number if type is
* RCENTITY_PROJECT or RCENTITY_ZONE
* - task number if type is RCENTITY_TYPE
* - a pid if type is RCENTITY_PID
* Also supply an un-allocated prochandle, and an allocated info_handle.
* This function assumes that the type is set.
* If priv is not RCPRIV_BASIC, the grabbed process is required to have
* PRIV_SYS_RESOURCE in it's limit set.
*
* Return Values
* Returns 0 on success and 1 on failure. If there is a process
* running under the specified id, success is returned, and
* Pr is pointed to the process. Success will be returned and Pr
* set to NULL if the matching process is our own.
* If success is returned, psinfo will be valid, and pid will
* be the process number. The process will also be held at the
* end, so release_process should be used by the caller.
*
* This function assumes that signals are caught already so that libproc
* can be safely used.
*
* Return Values
* pid - Process found and grabbed
* -1 - Error
*/
pid_t
grab_process_by_id(char *idname, rctl_entity_t type, pr_info_handle_t *p,
int priv, int *gret)
{
char prbuf[PROJECT_BUFSZ];
projid_t projid;
taskid_t taskid;
zoneid_t zoneid;
zoneid_t zone_self;
struct project proj;
DIR *dirp;
struct dirent *dentp;
int found = 0;
int pid_self;
int ret;
int gret_in;
int intidname;
char *end;
prpriv_t *prpriv;
priv_set_t *prset;
gret_in = *gret;
/* get our pid se we do not try to operate on self */
pid_self = getpid();
/* Store integer version of id */
intidname = strtoul(idname, &end, 10);
if (errno || *end != '\0' || end == idname) {
intidname = -1;
}
/*
* get our zoneid so we don't try to operate on a project in
* another zone
*/
zone_self = getzoneid();
if (idname == NULL || strcmp(idname, "") == 0) {
warn(gettext("id name cannot be nuint64\n"));
return (-1);
}
/*
* Set up zoneid, projid or taskid, as appropriate, so that comparisons
* can be done later with the input.
*/
if (type == RCENTITY_ZONE) {
if (zone_get_id(idname, &zoneid) != 0) {
warn(gettext("%s: unknown zone\n"), idname);
return (-1);
}
} else if (type == RCENTITY_PROJECT) {
if (getprojbyname(idname, &proj, prbuf, PROJECT_BUFSZ)
== NULL) {
if (getprojbyid(intidname, &proj, prbuf,
PROJECT_BUFSZ) == NULL) {
warn(gettext("%s: cannot find project\n"),
idname);
return (-1);
}
}
projid = proj.pj_projid;
} else if (type == RCENTITY_TASK) {
taskid = (taskid_t)atol(idname);
}
/*
* Projects and tasks need to search through /proc for
* a parent process.
*/
if (type == RCENTITY_ZONE || type == RCENTITY_PROJECT ||
type == RCENTITY_TASK) {
if ((dirp = opendir("/proc")) == NULL) {
warn(gettext("%s: cannot open /proc directory\n"),
idname);
return (-1);
}
/*
* Look through all processes in /proc. For each process,
* check if the pr_projid in their psinfo matches the
* specified id.
*/
while (dentp = readdir(dirp)) {
p->pid = atoi(dentp->d_name);
/* Skip self */
if (p->pid == pid_self)
continue;
if (proc_get_psinfo(p->pid, &(p->psinfo)) != 0)
continue;
/* Skip process if it is not what we are looking for */
if (type == RCENTITY_ZONE &&
(p->psinfo).pr_zoneid != zoneid) {
continue;
} else if (type == RCENTITY_PROJECT &&
((p->psinfo).pr_projid != projid ||
(p->psinfo).pr_zoneid != zone_self)) {
continue;
} else if (type == RCENTITY_TASK &&
(p->psinfo).pr_taskid != taskid) {
continue;
}
/* attempt to grab process */
if (grab_process(p, gret) != 0)
continue;
/*
* Re-confirm that this process is still running as
* part of the specified project or task. If it
* doesn't match, release the process and return an
* error. This should only be done if the Pr struct is
* not NULL.
*/
if (type == RCENTITY_PROJECT) {
if (pr_getprojid(p->pr) != projid ||
pr_getzoneid(p->pr) != zone_self) {
release_process(p->pr);
continue;
}
} else if (type == RCENTITY_TASK) {
if (pr_gettaskid(p->pr) != taskid) {
release_process(p->pr);
continue;
}
} else if (type == RCENTITY_ZONE) {
if (pr_getzoneid(p->pr) != zoneid) {
release_process(p->pr);
continue;
}
}
/*
* If we are setting a privileged resource control,
* verify that process has PRIV_SYS_RESOURCE in it's
* limit set. If it does not, then we will not be
* able to give this process the privilege it needs
* to set the resource control.
*/
if (priv != RCPRIV_BASIC) {
prpriv = proc_get_priv(p->pid);
if (prpriv == NULL) {
release_process(p->pr);
continue;
}
prset = (priv_set_t *)
&prpriv->pr_sets[prpriv->pr_setsize *
priv_getsetbyname(PRIV_LIMIT)];
if (!priv_ismember(prset, PRIV_SYS_RESOURCE)) {
proc_free_priv(prpriv);
release_process(p->pr);
continue;
}
proc_free_priv(prpriv);
}
found = 1;
p->taskid = pr_gettaskid(p->pr);
p->projid = pr_getprojid(p->pr);
p->zoneid = pr_getzoneid(p->pr);
break;
}
(void) closedir(dirp);
if (found == 0) {
warn(gettext("%s: No controllable process found in "
"task, project, or zone.\n"), idname);
return (-1);
}
return (p->pid);
} else if (type == RCENTITY_PROCESS) {
/* fail if self */
if (p->pid == pid_self) {
warn(gettext("%s: cannot control self"), idname);
return (-1);
}
/*
* Process types need to be set up with the correct pid
* and psinfo structure.
*/
if ((p->pid = proc_arg_psinfo(idname, PR_ARG_PIDS,
&(p->psinfo), gret)) == -1) {
warn(gettext("%s: cannot examine: %s"), idname,
Pgrab_error(*gret));
return (-1);
}
/* grab process */
ret = grab_process(p, gret);
if (ret == 1) {
/* Don't print error if G_SYS is allowed */
if (gret_in == G_SYS && *gret == G_SYS) {
return (-1);
} else {
warn(gettext("%s: cannot control: %s"), idname,
Pgrab_error(*gret));
return (-1);
}
} else if (ret == 2) {
ret = errno;
warn(gettext("%s: cannot control: %s"), idname,
strerror(ret));
return (-1);
}
p->taskid = pr_gettaskid(p->pr);
p->projid = pr_getprojid(p->pr);
p->zoneid = pr_getzoneid(p->pr);
return (p->pid);
} else {
warn(gettext("%s: unknown resource entity type %d\n"), idname,
type);
return (-1);
}
}
/*
* Do the work required to manipulate a process through libproc.
* If grab_process() returns no errors (0), then release_process()
* must eventually be called.
*
* Return values:
* 0 Successful creation of agent thread
* 1 Error grabbing
* 2 Error creating agent
*/
int
grab_process(pr_info_handle_t *p, int *gret)
{
if ((p->pr = Pgrab(p->pid, arg_force, gret)) != NULL) {
if (Psetflags(p->pr, PR_RLC) != 0) {
Prelease(p->pr, 0);
return (1);
}
if (Pcreate_agent(p->pr) == 0) {
return (0);
} else {
Prelease(p->pr, 0);
return (2);
}
} else {
return (1);
}
}
/*
* Release the specified process. This destroys the agent
* and releases the process. If the process is NULL, nothing
* is done. This function should only be called if grab_process()
* has previously been called and returned success.
*
* This function is Pgrab-safe.
*/
void
release_process(struct ps_prochandle *Pr)
{
if (Pr == NULL)
return;
Pdestroy_agent(Pr);
Prelease(Pr, 0);
}
/*
* preserve_error(char *, ...)
*
* preserve_error() should be called rather than warn() by any
* function that is called while the victim process is held by Pgrab.
* It will save the error until the process has been un-controlled
* and output is reasonable again.
*
* Note that multiple errors are not stored. Any error in these
* sections should be critical and return immediately.
*
* This function is Pgrab-safe.
*
* Since this function may copy untrusted command line arguments to
* global_error, security practices require that global_error never be
* printed directly. Use printf("%s\n", global_error) or equivalent.
*/
/*PRINTFLIKE1*/
void
preserve_error(char *format, ...)
{
va_list alist;
va_start(alist, format);
/*
* GLOBAL_ERR_SZ is pretty big. If the error is longer
* than that, just truncate it, rather than chance missing
* the error altogether.
*/
(void) vsnprintf(global_error, GLOBAL_ERR_SZ-1, format, alist);
va_end(alist);
}