root/usr/src/cmd/ctstat/ctstat.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) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
 */

#include <sys/types.h>
#include <sys/ctfs.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <libuutil.h>
#include <sys/contract/process.h>
#include <sys/contract/device.h>
#include <limits.h>
#include <libcontract.h>
#include <libcontract_priv.h>
#include <dirent.h>

#include <locale.h>
#include <langinfo.h>

#include "statcommon.h"

static uint_t timestamp_fmt = NODATE;

static int opt_verbose = 0;
static int opt_showall = 0;

/*
 * usage
 *
 * Educate the user.
 */
static void
usage(void)
{
        (void) fprintf(stderr, gettext("Usage: %s [-a] [-i ctidlist] "
            "[-t typelist] [-T d|u] [-v] [interval [count]]\n"), uu_getpname());
        exit(UU_EXIT_USAGE);
}

/*
 * mystrtoul
 *
 * Convert a string into an int in [0, INT_MAX].  Exit if the argument
 * doen't fit this description.
 */
static int
mystrtoul(const char *arg)
{
        unsigned int result;

        if (uu_strtoint(arg, &result, sizeof (result), 10, 0, INT_MAX) == -1) {
                uu_warn(gettext("invalid numerical argument \"%s\"\n"), arg);
                usage();
        }

        return (result);
}

/*
 * int_compar
 *
 * A simple integer comparator.  Also used for id_ts, since they're the
 * same thing.
 */
static int
int_compar(const void *a1, const void *a2)
{
        int id1 = *(int *)a1;
        int id2 = *(int *)a2;

        if (id1 > id2)
                return (1);
        if (id2 > id1)
                return (-1);
        return (0);
}

typedef struct optvect {
        const char      *option;
        uint_t          bit;
} optvect_t;

static optvect_t option_params[] = {
        { "inherit", CT_PR_INHERIT },
        { "noorphan", CT_PR_NOORPHAN },
        { "pgrponly", CT_PR_PGRPONLY },
        { "regent", CT_PR_REGENT },
        { NULL }
};

static optvect_t option_events[] = {
        { "core", CT_PR_EV_CORE },
        { "signal", CT_PR_EV_SIGNAL },
        { "hwerr", CT_PR_EV_HWERR },
        { "empty", CT_PR_EV_EMPTY },
        { "fork", CT_PR_EV_FORK },
        { "exit", CT_PR_EV_EXIT },
        { NULL }
};

/*
 * print_bits
 *
 * Display a set whose membership is identified by a bitfield.
 */
static void
print_bits(uint_t bits, optvect_t *desc)
{
        int i, printed = 0;

        for (i = 0; desc[i].option; i++)
                if (desc[i].bit & bits) {
                        if (printed)
                                (void) putchar(' ');
                        printed = 1;
                        (void) fputs(desc[i].option, stdout);
                }
        if (printed)
                (void) putchar('\n');
        else
                (void) puts("none");
}

/*
 * print_ids
 *
 * Display a list of ids, sorted.
 */
static void
print_ids(id_t *ids, uint_t nids)
{
        int i;
        int first = 1;

        qsort(ids, nids, sizeof (int), int_compar);

        for (i = 0; i < nids; i++) {
                /*LINTED*/
                (void) printf(" %d" + first, ids[i]);
                first = 0;
        }
        if (first)
                (void) puts("none");
        else
                (void) putchar('\n');
}

typedef void printfunc_t(ct_stathdl_t);

/*
 * A structure defining a displayed field.  Includes a label to be
 * printed along side the field value, and a function which extracts
 * the data from a status structure, formats it, and displays it on
 * stdout.
 */
typedef struct verbout {
        const char      *label; /* field label */
        printfunc_t     *func;  /* field display function */
} verbout_t;

/*
 * verb_cookie
 *
 * Used to display an error encountered when reading a contract status
 * field.
 */
static void
verb_error(int err)
{
        (void) printf("(error: %s)\n", strerror(err));
}

/*
 * verb_cookie
 *
 * Display the contract's cookie.
 */
static void
verb_cookie(ct_stathdl_t hdl)
{
        (void) printf("%#llx\n", ct_status_get_cookie(hdl));
}

/*
 * verb_info
 *
 * Display the parameters in the parameter set.
 */
static void
verb_param(ct_stathdl_t hdl)
{
        uint_t param;
        int err;

        if (err = ct_pr_status_get_param(hdl, &param))
                verb_error(err);
        else
                print_bits(param, option_params);
}

/*
 * verb_info
 *
 * Display the events in the informative event set.
 */
static void
verb_info(ct_stathdl_t hdl)
{
        print_bits(ct_status_get_informative(hdl), option_events);
}

/*
 * verb_crit
 *
 * Display the events in the critical event set.
 */
static void
verb_crit(ct_stathdl_t hdl)
{
        print_bits(ct_status_get_critical(hdl), option_events);
}

/*
 * verb_minor
 *
 * Display the minor device
 */
static void
verb_minor(ct_stathdl_t hdl)
{
        int err;
        char *buf;

        if (err = ct_dev_status_get_minor(hdl, &buf))
                verb_error(err);
        else
                (void) printf("%s\n", buf);
}

/*
 * verb_state
 *
 * Display the state of the device
 */
static void
verb_dev_state(ct_stathdl_t hdl)
{
        int err;
        uint_t state;

        if (err = ct_dev_status_get_dev_state(hdl, &state))
                verb_error(err);
        else
                (void) printf("%s\n", state == CT_DEV_EV_ONLINE ? "online" :
                    state == CT_DEV_EV_DEGRADED ? "degraded" : "offline");
}

/*
 * verb_fatal
 *
 * Display the events in the fatal event set.
 */
static void
verb_fatal(ct_stathdl_t hdl)
{
        uint_t event;
        int err;

        if (err = ct_pr_status_get_fatal(hdl, &event))
                verb_error(err);
        else
                print_bits(event, option_events);
}

/*
 * verb_members
 *
 * Display the list of member contracts.
 */
static void
verb_members(ct_stathdl_t hdl)
{
        pid_t *pids;
        uint_t npids;
        int err;

        if (err = ct_pr_status_get_members(hdl, &pids, &npids)) {
                verb_error(err);
                return;
        }

        print_ids(pids, npids);
}

/*
 * verb_inherit
 *
 * Display the list of inherited contracts.
 */
static void
verb_inherit(ct_stathdl_t hdl)
{
        ctid_t *ctids;
        uint_t nctids;
        int err;

        if (err = ct_pr_status_get_contracts(hdl, &ctids, &nctids))
                verb_error(err);
        else
                print_ids(ctids, nctids);
}

/*
 * verb_svc_fmri
 *
 * Display the process contract service fmri
 */
static void
verb_svc_fmri(ct_stathdl_t hdl)
{
        char *svc_fmri;
        int err;
        if (err = ct_pr_status_get_svc_fmri(hdl, &svc_fmri))
                verb_error(err);
        else
                (void) printf("%s\n", svc_fmri);
}

/*
 * verb_svc_aux
 *
 * Display the process contract service fmri auxiliar
 */
static void
verb_svc_aux(ct_stathdl_t hdl)
{
        char *svc_aux;
        int err;
        if (err = ct_pr_status_get_svc_aux(hdl, &svc_aux))
                verb_error(err);
        else
                (void) printf("%s\n", svc_aux);
}

/*
 * verb_svc_ctid
 *
 * Display the process contract service fmri ctid
 */
static void
verb_svc_ctid(ct_stathdl_t hdl)
{
        ctid_t svc_ctid;
        int err;
        if (err = ct_pr_status_get_svc_ctid(hdl, &svc_ctid))
                verb_error(err);
        else
                (void) printf("%ld\n", svc_ctid);
}

/*
 * verb_svc_creator
 *
 * Display the process contract creator's execname
 */
static void
verb_svc_creator(ct_stathdl_t hdl)
{
        char *svc_creator;
        int err;
        if (err = ct_pr_status_get_svc_creator(hdl, &svc_creator))
                verb_error(err);
        else
                (void) printf("%s\n", svc_creator);
}

/*
 * Common contract status fields.
 */
static verbout_t vcommon[] = {
        "cookie", verb_cookie,
        NULL,
};

/*
 * Process contract-specific status fields.
 * The critical and informative event sets are here because the event
 * names are contract-specific.  They are listed first, however, so
 * they are displayed adjacent to the "normal" common output.
 */
static verbout_t vprocess[] = {
        "informative event set", verb_info,
        "critical event set", verb_crit,
        "fatal event set", verb_fatal,
        "parameter set", verb_param,
        "member processes", verb_members,
        "inherited contracts", verb_inherit,
        "service fmri", verb_svc_fmri,
        "service fmri ctid", verb_svc_ctid,
        "creator", verb_svc_creator,
        "aux", verb_svc_aux,
        NULL
};

static verbout_t vdevice[] = {
        "device", verb_minor,
        "dev_state", verb_dev_state,
        NULL
};

/*
 * print_verbose
 *
 * Displays a contract's verbose status, common fields first.
 */
static void
print_verbose(ct_stathdl_t hdl, verbout_t *spec, verbout_t *common)
{
        int i;
        int tmp, maxwidth = 0;

        /*
         * Compute the width of all the fields.
         */
        for (i = 0; common[i].label; i++)
                if ((tmp = strlen(common[i].label)) > maxwidth)
                        maxwidth = tmp;
        if (spec)
                for (i = 0; spec[i].label; i++)
                        if ((tmp = strlen(spec[i].label)) > maxwidth)
                                maxwidth = tmp;
        maxwidth += 2;

        /*
         * Display the data.
         */
        for (i = 0; common[i].label; i++) {
                tmp = printf("\t%s", common[i].label);
                if (tmp < 0)
                        tmp = 0;
                (void) printf("%-*s", maxwidth - tmp + 1, ":");
                common[i].func(hdl);
        }
        if (spec)
                for (i = 0; spec[i].label; i++) {
                        (void) printf("\t%s%n", spec[i].label, &tmp);
                        (void) printf("%-*s", maxwidth - tmp + 1, ":");
                        spec[i].func(hdl);
                }
}

struct {
        const char *name;
        verbout_t *verbout;
} cttypes[] = {
        { "process", vprocess },
        { "device", vdevice },
        { NULL }
};

/*
 * get_type
 *
 * Given a type name, return an index into the above array of types.
 */
static int
get_type(const char *typestr)
{
        int i;
        for (i = 0; cttypes[i].name; i++)
                if (strcmp(cttypes[i].name, typestr) == 0)
                        return (i);
        uu_die(gettext("invalid contract type: %s\n"), typestr);
        /* NOTREACHED */
}

/*
 * print_header
 *
 * Display the status header.
 */
static void
print_header(void)
{
        (void) printf("%-8s%-8s%-8s%-8s%-8s%-8s%-8s%-8s\n", "CTID", "ZONEID",
            "TYPE", "STATE", "HOLDER", "EVENTS", "QTIME", "NTIME");
}

/*
 * print_contract
 *
 * Display status for contract ID 'id' from type directory 'dir'.  If
 * only contracts of a specific set of types should be displayed,
 * 'types' will be a sorted list of type indices of length 'ntypes'.
 */
static void
print_contract(const char *dir, ctid_t id, verbout_t *spec,
    int *types, int ntypes)
{
        ct_stathdl_t status;
        char hstr[100], qstr[20], nstr[20];
        ctstate_t state;
        int fd = 0;
        int t;

        /*
         * Open and obtain status.
         */
        if ((fd = contract_open(id, dir, "status", O_RDONLY)) == -1) {
                if (errno == ENOENT)
                        return;
                uu_die(gettext("could not open contract status file"));
        }

        if (errno = ct_status_read(fd, opt_verbose ? CTD_ALL : CTD_COMMON,
            &status))
                uu_die(gettext("failed to get contract status for %d"), id);
        (void) close(fd);

        /*
         * Unless otherwise directed, don't display dead contracts.
         */
        state = ct_status_get_state(status);
        if (!opt_showall && state == CTS_DEAD) {
                ct_status_free(status);
                return;
        }

        /*
         * If we are only allowed to display certain contract types,
         * perform that filtering here.  We stash a copy of spec so we
         * don't have to recompute it later.
         */
        if (types) {
                int key = get_type(ct_status_get_type(status));
                spec = cttypes[key].verbout;
                if (bsearch(&key, types, ntypes, sizeof (int), int_compar) ==
                    NULL) {
                        ct_status_free(status);
                        return;
                }
        }

        /*
         * Precompute those fields which have both textual and
         * numerical values.
         */
        if ((state == CTS_OWNED) || (state == CTS_INHERITED))
                (void) snprintf(hstr, sizeof (hstr), "%ld",
                    ct_status_get_holder(status));
        else
                (void) snprintf(hstr, sizeof (hstr), "%s", "-");

        if ((t = ct_status_get_qtime(status)) == -1) {
                qstr[0] = nstr[0] = '-';
                qstr[1] = nstr[1] = '\0';
        } else {
                (void) snprintf(qstr, sizeof (qstr), "%d", t);
                (void) snprintf(nstr, sizeof (nstr), "%d",
                    ct_status_get_ntime(status));
        }

        /*
         * Emit the contract's status.
         */
        (void) printf("%-7ld %-7ld %-7s %-7s %-7s %-7d %-7s %-8s\n",
            ct_status_get_id(status),
            ct_status_get_zoneid(status),
            ct_status_get_type(status),
            (state == CTS_OWNED) ? "owned" :
            (state == CTS_INHERITED) ? "inherit" :
            (state == CTS_ORPHAN) ? "orphan" : "dead", hstr,
            ct_status_get_nevents(status), qstr, nstr);

        /*
         * Emit verbose status information, if requested.  If we
         * weren't provided a verbose output spec or didn't compute it
         * earlier, do it now.
         */
        if (opt_verbose) {
                if (spec == NULL)
                        spec = cttypes[get_type(ct_status_get_type(status))].
                            verbout;
                print_verbose(status, spec, vcommon);
        }

        ct_status_free(status);
}

/*
 * scan_type
 *
 * Display all contracts of the requested type.
 */
static void
scan_type(int typeno)
{
        DIR *dir;
        struct dirent64 *de;
        char path[PATH_MAX];

        verbout_t *vo = cttypes[typeno].verbout;
        const char *type = cttypes[typeno].name;

        if (snprintf(path, PATH_MAX, CTFS_ROOT "/%s", type) >= PATH_MAX ||
            (dir = opendir(path)) == NULL)
                uu_die(gettext("bad contract type: %s\n"), type);
        while ((de = readdir64(dir)) != NULL) {
                /*
                 * Eliminate special files (e.g. '.', '..').
                 */
                if (de->d_name[0] < '0' || de->d_name[0] > '9')
                        continue;
                print_contract(type, mystrtoul(de->d_name), vo, NULL, 0);
        }
        (void) closedir(dir);
}

/*
 * scan_ids
 *
 * Display all contracts with the requested IDs.
 */
static void
scan_ids(ctid_t *ids, int nids)
{
        int i;
        for (i = 0; i < nids; i++)
                print_contract("all", ids[i], NULL, NULL, 0);
}

/*
 * scan_all
 *
 * Display the union of the requested IDs and types.  So that the
 * output is sorted by contract ID, it takes the slow road by testing
 * each entry in /system/contract/all against its criteria.  Used when
 * the number of types is greater than 1, when we have a mixture of
 * types and ids, or no lists were provided at all.
 */
static void
scan_all(int *types, int ntypes, ctid_t *ids, int nids)
{
        DIR *dir;
        struct dirent64 *de;
        const char *path = CTFS_ROOT "/all";
        int key, test;

        if ((dir = opendir(path)) == NULL)
                uu_die(gettext("could not open %s"), path);
        while ((de = readdir64(dir)) != NULL) {
                /*
                 * Eliminate special files (e.g. '.', '..').
                 */
                if (de->d_name[0] < '0' || de->d_name[0] > '9')
                        continue;
                key = mystrtoul(de->d_name);

                /*
                 * If we are given IDs to look at and this contract
                 * isn't in the ID list, or if we weren't given a list
                 * if IDs but were given a list of types, provide the
                 * list of acceptable types to print_contract.
                 */
                test = nids ? (bsearch(&key, ids, nids, sizeof (int),
                    int_compar) == NULL) : (ntypes != 0);
                print_contract("all", key, NULL, (test ? types : NULL), ntypes);
        }
        (void) closedir(dir);
}

/*
 * walk_args
 *
 * Apply fp to each token in the comma- or space- separated argument
 * string str and store the results in the array starting at results.
 */
static int
walk_args(const char *str, int (*fp)(const char *), int *results)
{
        char *copy, *token;
        int count = 0;

        if ((copy = strdup(str)) == NULL)
                uu_die(gettext("strdup() failed"));

        token = strtok(copy, ", ");
        if (token == NULL) {
                free(copy);
                return (0);
        }

        do {
                if (fp)
                        *(results++) = fp(token);
                count++;
        } while (token = strtok(NULL, ", "));
        free(copy);

        return (count);
}

/*
 * parse
 *
 * Parse the comma- or space- separated string str, using fp to covert
 * the tokens to integers.  Append the list of integers to the array
 * pointed to by *idps, growing the array if necessary.
 */
static int
parse(const char *str, int **idsp, int nids, int (*fp)(const char *fp))
{
        int count;
        int *array;

        count = walk_args(str, NULL, NULL);
        if (count == 0)
                return (0);

        if ((array = calloc(nids + count, sizeof (int))) == NULL)
                uu_die(gettext("calloc() failed"));

        if (*idsp) {
                (void) memcpy(array, *idsp, nids * sizeof (int));
                free(*idsp);
        }

        (void) walk_args(str, fp, array + nids);

        *idsp = array;
        return (count + nids);
}

/*
 * parse_ids
 *
 * Extract a list of ids from the comma- or space- separated string str
 * and append them to the array *idsp, growing it if necessary.
 */
static int
parse_ids(const char *arg, int **idsp, int nids)
{
        return (parse(arg, idsp, nids, mystrtoul));
}

/*
 * parse_types
 *
 * Extract a list of types from the comma- or space- separated string
 * str and append them to the array *idsp, growing it if necessary.
 */
static int
parse_types(const char *arg, int **typesp, int ntypes)
{
        return (parse(arg, typesp, ntypes, get_type));
}

/*
 * compact
 *
 * Sorts and removes duplicates from array.  Initial size of array is
 * in *size; final size is stored in *size.
 */
static void
compact(int *array, int *size)
{
        int i, j, last = -1;

        qsort(array, *size, sizeof (int), int_compar);
        for (i = j = 0; i < *size; i++) {
                if (array[i] != last) {
                        last = array[i];
                        array[j++] = array[i];
                }
        }
        *size = j;
}

int
main(int argc, char **argv)
{
        unsigned int interval = 0, count = 1;
        ctid_t  *ids = NULL;
        int     *types = NULL;
        int     nids = 0, ntypes = 0;
        int     i, s;

        (void) setlocale(LC_ALL, "");
        (void) textdomain(TEXT_DOMAIN);

        (void) uu_setpname(argv[0]);

        while ((s = getopt(argc, argv, "ai:T:t:v")) != EOF) {
                switch (s) {
                case 'a':
                        opt_showall = 1;
                        break;
                case 'i':
                        nids = parse_ids(optarg, (int **)&ids, nids);
                        break;
                case 'T':
                        if (optarg) {
                                if (*optarg == 'u')
                                        timestamp_fmt = UDATE;
                                else if (*optarg == 'd')
                                        timestamp_fmt = DDATE;
                                else
                                        usage();
                        } else {
                                usage();
                        }
                        break;
                case 't':
                        ntypes = parse_types(optarg, &types, ntypes);
                        break;
                case 'v':
                        opt_verbose = 1;
                        break;
                default:
                        usage();
                }
        }

        argc -= optind;
        argv += optind;

        if (argc > 2 || argc < 0)
                usage();

        if (argc > 0) {
                interval = mystrtoul(argv[0]);
                count = 0;
        }

        if (argc > 1) {
                count = mystrtoul(argv[1]);
                if (count == 0)
                        return (0);
        }

        if (nids)
                compact((int *)ids, &nids);
        if (ntypes)
                compact(types, &ntypes);

        for (i = 0; count == 0 || i < count; i++) {
                if (i)
                        (void) sleep(interval);
                if (timestamp_fmt != NODATE)
                        print_timestamp(timestamp_fmt);
                print_header();
                if (nids && ntypes)
                        scan_all(types, ntypes, ids, nids);
                else if (ntypes == 1)
                        scan_type(*types);
                else if (nids)
                        scan_ids(ids, nids);
                else
                        scan_all(types, ntypes, ids, nids);
        }

        return (0);
}