root/usr/src/cmd/prstat/prutil.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) 2013 Gary Mills
 *
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 * Portions Copyright 2009 Chad Mynhier
 * Copyright 2018 Joyent, Inc.  All rights reserved.
 */

#include <sys/types.h>
#include <sys/param.h>
#include <sys/resource.h>
#include <sys/priocntl.h>
#include <sys/rtpriocntl.h>
#include <sys/tspriocntl.h>
#include <zone.h>

#include <libintl.h>
#include <limits.h>
#include <wchar.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdio_ext.h>
#include <errno.h>
#include <ctype.h>
#include <poll.h>
#include <project.h>

#include "prfile.h"
#include "prstat.h"
#include "prutil.h"

static char PRG_FMT[] = "%s: ";
static char ERR_FMT[] = ": %s\n";
static char *progname;
static char projbuf[PROJECT_BUFSZ];

#define RLIMIT_NOFILE_MAX       32767

/*PRINTFLIKE1*/
void
Warn(char *format, ...)
{
        int err = errno;
        va_list alist;

        if (progname != NULL)
                (void) fprintf(stderr, PRG_FMT, progname);
        va_start(alist, format);
        (void) vfprintf(stderr, format, alist);
        va_end(alist);
        if (strchr(format, '\n') == NULL)
                (void) fprintf(stderr, gettext(ERR_FMT), strerror(err));
}

/*PRINTFLIKE1*/
void
Die(char *format, ...)
{
        int err = errno;
        va_list alist;

        if (progname != NULL)
                (void) fprintf(stderr, PRG_FMT, progname);
        va_start(alist, format);
        (void) vfprintf(stderr, format, alist);
        va_end(alist);
        if (strchr(format, '\n') == NULL)
                (void) fprintf(stderr, gettext(ERR_FMT), strerror(err));
        exit(1);
}

void
Progname(char *arg0)
{
        char *p = strrchr(arg0, '/');
        if (p == NULL)
                p = arg0;
        else
                p++;
        progname = p;
}

void
Usage()
{
        (void) fprintf(stderr, gettext(
            "Usage:\tprstat [-acHJLmrRtTvWZ] [-u euidlist] [-U uidlist]\n"
            "\t[-p pidlist] [-P cpulist] [-C psrsetlist] [-h lgrouplist]\n"
            "\t[-j projidlist] [-k taskidlist] [-z zoneidlist]\n"
            "\t[-s key | -S key] [-n nprocs[,nusers]] [-d d|u]\n"
            "\t[interval [counter]]\n"));
        exit(1);
}

int
Atoi(char *p)
{
        int i;
        char *q;
        errno = 0;
        i = (int)strtol(p, &q, 10);
        if (errno != 0 || q == p || i < 0 || *q != '\0')
                Die(gettext("illegal argument -- %s\n"), p);
                /*NOTREACHED*/
        else
                return (i);
        return (0);     /* keep gcc happy */
}

void
Format_size(char *str, size_t size, int length)
{
        char tag = 'K';
        if (size >= 10000) {
                size = (size + 512) / 1024;
                tag = 'M';
                if (size >= 10000) {
                        size = (size + 512) / 1024;
                        tag = 'G';
                }
        }
        (void) snprintf(str, length, "%4d%c", (int)size, tag);
}

void
Format_time(char *str, ulong_t time, int length)
{
        (void) snprintf(str, length, gettext("%3d:%2.2d:%2.2d"), /* hr:mm:ss */
            (int)time/3600, (int)(time % 3600)/60, (int)time % 60);
}

void
Format_pct(char *str, float val, int length)
{
        if (val > (float)100)
                val = 100;
        if (val < 0)
                val = 0;

        if (val < (float)9.95)
                (void) snprintf(str, length, "%1.1f", val);
        else
                (void) snprintf(str, length, "%.0f", val);
}

void
Format_num(char *str, int num, int length)
{
        if (num >= 100000) {
                (void) snprintf(str, length, ".%1dM", num/100000);
        } else {
                if (num >= 1000)
                        (void) snprintf(str, length, "%2dK", num/1000);
                else
                        (void) snprintf(str, length, "%3d", num);
        }
}

void
Format_state(char *str, char state, processorid_t pr_id, int length)
{
        switch (state) {
        case 'S':
                (void) strncpy(str, "sleep", length);
                break;
        case 'R':
                (void) strncpy(str, "run", length);
                break;
        case 'Z':
                (void) strncpy(str, "zombie", length);
                break;
        case 'T':
                (void) strncpy(str, "stop", length);
                break;
        case 'I':
                (void) strncpy(str, "idle", length);
                break;
        case 'W':
                (void) strncpy(str, "wait", length);
                break;
        case 'O':
                (void) snprintf(str, length, "cpu%-3d", (int)pr_id);
                break;
        default:
                (void) strncpy(str, "?", length);
                break;
        }
}

void *
Realloc(void *ptr, size_t size)
{
        int     cnt = 0;
        void    *sav = ptr;

eagain: if ((ptr = realloc(ptr, size)))
                return (ptr);

        if ((++cnt <= 3) && (errno == EAGAIN)) {
                Warn(gettext("realloc() failed, attempt %d"), cnt);
                (void) poll(NULL, 0, 5000); /* wait for 5 seconds */
                ptr = sav;
                goto eagain;
        }
        ptr = sav;
        Die(gettext("not enough memory"));
        /*NOTREACHED*/
        return (NULL);  /* keep gcc happy */
}

void *
Malloc(size_t size)
{
        return (Realloc(NULL, size));
}

void *
Zalloc(size_t size)
{
        return (memset(Realloc(NULL, size), 0, size));
}

int
Setrlimit()
{
        struct rlimit rlim;
        int fd_limit;
        if (getrlimit(RLIMIT_NOFILE, &rlim) == -1)
                Die(gettext("getrlimit failed"));
        fd_limit = rlim.rlim_cur;
        rlim.rlim_max = MIN(rlim.rlim_max, RLIMIT_NOFILE_MAX);
        rlim.rlim_cur = rlim.rlim_max;
        (void) enable_extended_FILE_stdio(-1, -1);
        if (setrlimit(RLIMIT_NOFILE, &rlim) == -1)
                return (fd_limit);
        else
                return (rlim.rlim_cur);
}

void
Priocntl(char *class)
{
        pcinfo_t pcinfo;
        pcparms_t pcparms;
        (void) strcpy(pcinfo.pc_clname, class);
        if (priocntl(0, 0, PC_GETCID, (caddr_t)&pcinfo) == -1) {
                Warn(gettext("cannot get real time class parameters"));
                return;
        }
        pcparms.pc_cid = pcinfo.pc_cid;
        ((rtparms_t *)pcparms.pc_clparms)->rt_pri = 0;
        ((rtparms_t *)pcparms.pc_clparms)->rt_tqsecs = 0;
        ((rtparms_t *)pcparms.pc_clparms)->rt_tqnsecs = RT_NOCHANGE;
        if (priocntl(P_PID, getpid(), PC_SETPARMS, (caddr_t)&pcparms) == -1)
                Warn(gettext("cannot enter the real time class"));
}

void
getprojname(projid_t projid, char *str, size_t len, int noresolve,
    int trunc, size_t width)
{
        struct project proj;
        size_t n;

        if (noresolve || getprojbyid(projid, &proj, projbuf, PROJECT_BUFSZ) ==
            NULL) {
                (void) snprintf(str, len, "%-6d", (int)projid);
        } else {
                n = mbstowcs(NULL, proj.pj_name, 0);
                if (n == (size_t)-1)
                        (void) snprintf(str, len, "%-28s", "ERROR");
                else if (trunc && n > width)
                        (void) snprintf(str, len, "%.*s%c", width - 1,
                            proj.pj_name, '*');
                else
                        (void) snprintf(str, len, "%-28s", proj.pj_name);
        }
}

void
getzonename(zoneid_t zoneid, char *str, size_t len, int trunc, size_t width)
{
        char zone_name[ZONENAME_MAX];
        size_t n;

        if (getzonenamebyid(zoneid, zone_name, sizeof (zone_name)) < 0) {
                (void) snprintf(str, len, "%-6d", (int)zoneid);
        } else {
                n = mbstowcs(NULL, zone_name, 0);
                if (n == (size_t)-1)
                        (void) snprintf(str, len, "%-28s", "ERROR");
                else if (trunc && n > width)
                        (void) snprintf(str, len, "%.*s%c", width - 1,
                            zone_name, '*');
                else
                        (void) snprintf(str, len, "%-28s", zone_name);
        }
}

/*
 * Remove all unprintable characters from process name
 */
static void
stripfname(char *buf, size_t bufsize, const char *pname)
{
        int bytesleft = PRFNSZ;
        wchar_t wchar;
        int length;
        char *cp;

        (void) strlcpy(buf, pname, bufsize);

        buf[bytesleft - 1] = '\0';

        for (cp = buf; *cp != '\0'; cp += length) {
                length = mbtowc(&wchar, cp, MB_LEN_MAX);
                if (length <= 0) {
                        *cp = '\0';
                        break;
                }
                if (!iswprint(wchar)) {
                        if (bytesleft <= length) {
                                *cp = '\0';
                                break;
                        }
                        (void) memmove(cp, cp + length, bytesleft - length);
                        length = 0;
                }
                bytesleft -= length;
        }
}


/*
 * prstat has always implicitly wanted a terminal width of at least 80 columns
 * (when a TTY is present).  If run in a terminal narrower than 80 columns,
 * prstat output may wrap.  For wider terminals, we allow the last column to use
 * the additional space.
 *
 * We never truncate if using -c, or not outputting to a TTY.
 */
static int
format_namewidth(void)
{
        int prefixlen = 0;

        if (opts.o_cols == 0 || !(opts.o_outpmode & (OPT_TERMCAP | OPT_TRUNC)))
                return (0);

        if (opts.o_outpmode & OPT_PSINFO) {
                if (opts.o_outpmode & OPT_LGRP)
                        prefixlen = 64;
                else
                        prefixlen = 59;
        } else if (opts.o_outpmode & OPT_MSACCT) {
                prefixlen = 64;
        }

        return (opts.o_cols - prefixlen);
}

void
format_name(lwp_info_t *lwp, char *buf, size_t buflen)
{
        int pname_width = PRFNSZ;
        char nr_suffix[20];
        char pname[PRFNSZ];
        int width;
        int n;

        stripfname(pname, sizeof (pname), lwp->li_info.pr_fname);

        if (opts.o_outpmode & OPT_LWPS) {
                n = snprintf(nr_suffix, sizeof (nr_suffix), "%d",
                    lwp->li_info.pr_lwp.pr_lwpid);
        } else {
                n = snprintf(nr_suffix, sizeof (nr_suffix), "%d",
                    lwp->li_info.pr_nlwp + lwp->li_info.pr_nzomb);
        }

        width = format_namewidth();

        /* If we're over budget, truncate the process name not the LWP part. */
        if (strlen(pname) > (width - n - 1)) {
                pname_width = width - n - 1;
                pname[pname_width - 1] = '*';
        }

        if ((opts.o_outpmode & OPT_LWPS) && lwp->li_lwpname[0] != '\0') {
                n = snprintf(buf, buflen, "%.*s/%s [%s]", pname_width,
                    pname, nr_suffix, lwp->li_lwpname);
        } else {
                n = snprintf(buf, buflen, "%.*s/%s", pname_width,
                    pname, nr_suffix);
        }

        if (width > 0 && strlen(buf) > width)
                buf[width] = '\0';
}