root/usr/src/cmd/prctl/prctl.c
/*
 * 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);
}