SUMMARY: top under Solaris 2.X

From: Marc Cohen (marc@aai.com)
Date: Thu Mar 04 1993 - 09:32:29 CST


Back about mid-January I posted a query about running top under Solaris
2.1/SunOS 5.1. I wasn't able to get it working initially but I have it
working now and thought I'd share what I did. Sorry for the lateness
and hope this info is still worthwhile.

First I'd like to thank Robert Boucher for writing m_svr4.c and
David J. MacKenzie who sent me a copy of it.

To build top I started with the top 3.0 distribution and Robert's
m_svr4.c (a copy is included below). I used the unbundled Sun C.

To get it to build I had to:

o remove /usr/ucb from my path so I wouldn't get Berkeley
   compatibilty stuff when I compiled.
o run configure and choose "svr4" as the system type.
o edit the Makefile so the "CDEFS" and "LIBS" lines look like:

   CDEFS = -O -DPRIO_MIN=-20 -DPRIO_MAX=20
   LIBS = -lelf -lc -L/usr/ucblib -lucb

o install it as setuid root so it can read stuff in /proc.

-Marc
-----

m_svr4.c:

/*
 * top - a top users display for Unix
 *
 * SYNOPSIS: For System V Release 4
 *
 * DESCRIPTION:
 *
 * The following must be added to CFLAGS in the Makefile:
 * [From <ucbinclude/sys/resource.h>]
 * -DPRIO_MIN=-20 -DPRIO_MAX=20
 *
 * NOTES: The memory sizes displayed for every process is greater by
 * a factor of 2 from DELL's version. The original port was using the
 * pagesize (bytes per click) from the UCB compatibility package (2048
 * bytes) instead of the native pagesize (4096 bytes). To anybody who
 * can read the source, is this correct? I've somewhat confirmed it by
 * summing the in-core size of all processes and comparing the results to
 * vmtotal->t_rm.
 *
 * SECURITY: The end of this module contains the patch to commands.c and
 * top.c to add some sort of security. Apply the patches, add -DSECURITY_OPTION
 * to CFLAGS and recompile. This patch is essentialy a verbatim port of
 * DELL's security mechanism from top 2.1.
 *
 * LIBS: -lelf -lc -lucb
 *
 * AUTHOR: Robert Boucher <boucher@ncs.dnd.ca>
 */

#include <sys/types.h>
#include <sys/param.h>
#include <sys/signal.h>
#include <sys/fault.h>
#include <sys/sysinfo.h>
#include <sys/sysmacros.h>
#include <sys/syscall.h>
#include <dirent.h>
#include <sys/user.h>
#include <sys/proc.h>
#include <sys/procfs.h>
#include <sys/vm.h>
#include <sys/var.h>
#include <sys/file.h>
#include <dirent.h>
#include <stdio.h>
#include <nlist.h>
#include <fcntl.h>
#include <math.h>

#include "top.h"
#include "machine.h"

#ifdef sun
#define sysinfo cpu_sysinfo
#endif

#ifndef FSCALE
/*
 * [From /usr/ucbinclude/sys/param.h]
 * Scale factor for scaled integers used to count
 * %cpu time and load averages.
 */
#define FSHIFT 8 /* bits to right of fixed binary point */
#define FSCALE (1<<FSHIFT)
#endif /* FSCALE */

/* [This is from <sys/vm/anon.h>, which run-time SVR4's don't have] */
struct anoninfo
{
     u_int ani_max; /* maximum anon pages available */
     u_int ani_free; /* number of anon pages currently free */
     u_int ani_resv; /* number of anon pages reserved */
};

/* get_process_info passes back a handle. This is what it looks like: */

struct handle
{
    struct prpsinfo **next_proc;/* points to next valid proc pointer */
    int remaining; /* number of pointers remaining */
};

#define CPUSTATES 5

/* declarations for load_avg */
typedef long load_avg;
#define loaddouble(la) ((double)(la) / FSCALE)

/* define what weighted cpu is. */
#define percent_cpu(pp) ((double)(pp)->pr_cpu / FSCALE)

#define weighted_cpu(pct, pp) ( ((pp)->pr_time.tv_sec) == 0 ? 0.0 : \
        ((pp)->pr_cpu) / ((pp)->pr_time.tv_sec) )

#ifdef sun
#undef ctob
#define ctob(x) ((x) << 12)
#endif

/* Convert clicks (kernel pages) to kbytes ... */
#define pagetok(size) ctob(size) >> LOG1024

/* definitions for indices in the nlist array */
#define X_AVENRUN 0
#define X_MPID 1
#define X_SYSINFO 2
#define X_V 3
#define X_NPROC 4

#define X_ANONINFO 5
#define X_TOTAL 6

static struct nlist nlst[] = {
    { "avenrun" }, /* 0 */
    { "mpid" }, /* 1 */
    { "sysinfo" }, /* 2 */
    { "v" }, /* 3 */
    { "nproc" }, /* 4 */
    { "anoninfo"}, /* 5 */
#ifndef sun
    { "total" }, /* 6 */
#endif
    { 0 }
};

/*
 * These definitions control the format of the per-process area
 */

static char header[] =
  " PID X PRI NICE SIZE RES STATE TIME WCPU CPU COMMAND";
/* 0123456 -- field to fill in starts at header+6 */
#define UNAME_START 6

#define Proc_format \
        "%5d %-8.8s %3d %4d%6dK %4dK %-5s%4d:%02d %3d.0%% %5.2f%% %.16s"

/* process state names for the "STATE" column of the display */
/* the extra nulls in the string "run" are for adding a slash and
   the processor number when needed */

char *state_abbrev[] =
{
    "", "sleep", "run", "zombie", "stop", "start", "cpu", "swap"
};

#ifdef sun
#define UNIX "/kernel/unix"
#else
#define UNIX "/stand/unix"
#endif
#define KMEM "/dev/kmem"
#define PROCFS "/proc"

static int kmem = -1;
static int mem = -1;

#ifndef sun
struct vmtotal total;
#endif
struct anoninfo anoninfo;

/* these are retrieved from the kernel in _init */

static int nproc;

/* these are offsets obtained via nlist and used in the get_ functions */

static unsigned long mpid_offset;
static unsigned long nproc_offset;
static unsigned long avenrun_offset;
static unsigned long total_offset;
static unsigned long sysinfo_offset;
static unsigned long anoninfo_offset;

/* these are for calculating cpu state percentages */

static long cp_time[CPUSTATES];
static long cp_old[CPUSTATES];
static long cp_diff[CPUSTATES];

/* these are for detailing the process states */

int process_states[8];
char *procstatenames[] = {
    "", " sleeping, ", " running, ", " zombie, ", " stopped, ",
    " starting, ", " on cpu, ", " swapped, ",
    NULL
};

/* these are for detailing the cpu states */

int cpu_states[CPUSTATES];
char *cpustatenames[] = {
    "idle", "user", "kernel", "wait", "swap",
    NULL
};

/* these are for detailing the memory statistics */

int memory_stats[5];
char *memorynames[] = {
    "K real, ", "K active, ", "K free, ", "K swap, " , "K free swap", NULL
};

/* these are for keeping track of the proc array */

static int bytes;
static struct prpsinfo *pbase;
static struct prpsinfo **pref;

/* useful externals */
extern int errno;
extern char *sys_errlist[];
extern char *myname;

long lseek();
long percentages();

machine_init(statics)
struct statics *statics;
{
    register int i;
    static struct var v;

    /* open kernel memory */
    if ((kmem = open(KMEM, 0)) < 0)
    {
        perror(KMEM);
        exit(20);
    }

    /* get the list of symbols we want to access in the kernel */
    if ((i = nlist(UNIX, nlst)) < 0)
    {
        fprintf(stderr, "%s: nlist failed\n",myname);
        return(-1);
    }

    /* make sure they were all found */
    if (i > 0 && check_nlist(nlst) > 0)
    {
        return(-1);
    }

    /* get the symbol values out of kmem */
    /* NPROC Tuning parameter for max number of processes */
    (void) getkval(nlst[X_V].n_value, &v, sizeof(struct var), nlst[X_V].n_name);
    nproc = v.v_proc;

    /* stash away certain offsets for later use */
    mpid_offset = nlst[X_MPID].n_value;
    nproc_offset = nlst[X_NPROC].n_value;
    avenrun_offset = nlst[X_AVENRUN].n_value;
    anoninfo_offset = nlst[X_ANONINFO].n_value;
#ifndef sun
    total_offset = nlst[X_TOTAL].n_value;
#else
    total_offset = 0;
#endif
    sysinfo_offset = nlst[X_SYSINFO].n_value;

    /* allocate space for proc structure array and array of pointers */
    bytes = nproc * sizeof(struct prpsinfo);
    pbase = (struct prpsinfo *)malloc(bytes);
    pref = (struct prpsinfo **)malloc(nproc * sizeof(struct prpsinfo *));

    /* Just in case ... */
    if (pbase == (struct prpsinfo *)NULL || pref == (struct prpsinfo **)NULL)
    {
        fprintf(stderr, "%s: can't allocate sufficient memory\n", myname);
        return(-1);
    }

    /* fill in the statics information */
    statics->procstate_names = procstatenames;
    statics->cpustate_names = cpustatenames;
    statics->memory_names = memorynames;

    /* all done! */
    return(0);
}

char *format_header(uname_field)
register char *uname_field;
{
    register char *ptr;

    ptr = header + UNAME_START;
    while (*uname_field != '\0')
    {
        *ptr++ = *uname_field++;
    }

    return(header);
}

get_system_info(si)
struct system_info *si;
{
    load_avg avenrun[3];
    struct sysinfo sysinfo;
    register int j;

    /* get the cp_time array */
    for (j = 0; j < CPUSTATES; j++) cp_time[j] = 0L;
        (void) getkval(sysinfo_offset, &sysinfo, sizeof(struct sysinfo), "sysinfo");
    for (j = 0; j < CPUSTATES; j++) cp_time[j] += (long)sysinfo.cpu[j];

    /* get load average array */
    (void) getkval(avenrun_offset, (int *)avenrun, sizeof(avenrun),
                   "avenrun");

    /* get mpid -- process id of last process */
    (void) getkval(mpid_offset, &(si->last_pid), sizeof(si->last_pid),
                   "mpid");

    /* convert load averages to doubles */
    {
        register int i;
        register double *infoloadp;
        register load_avg *sysloadp;

        infoloadp = si->load_avg;
        sysloadp = avenrun;
        for (i = 0; i < 3; i++)
        {
            *infoloadp++ = loaddouble(*sysloadp++);
        }
    }

    /* convert cp_time counts to percentages */
    (void) percentages(CPUSTATES, cpu_states, cp_time, cp_old, cp_diff);

#ifndef sun
    /* get total -- systemwide main memory usage structure */
    (void) getkval(total_offset, (int *)(&total), sizeof(total), "total");
    /* convert memory stats to Kbytes */
    memory_stats[0] = pagetok(total.t_rm);
    memory_stats[1] = pagetok(total.t_arm);
    memory_stats[2] = pagetok(total.t_free);
#else
    memory_stats[0] = 0;
    memory_stats[1] = 0;
    memory_stats[2] = 0;
#endif
    (void) getkval(anoninfo_offset, (int *)(&anoninfo), sizeof(anoninfo),
                "anoninfo");
    memory_stats[3] = pagetok(anoninfo.ani_max - anoninfo.ani_free);
    memory_stats[4] = pagetok(anoninfo.ani_max - anoninfo.ani_resv);

    /* set arrays and strings */
    si->cpustates = cpu_states;
    si->memory = memory_stats;
}

static struct handle handle;

caddr_t get_process_info(si, sel, compare)
struct system_info *si;
struct process_select *sel;
int (*compare)();
{
    register int i;
    register int total_procs;
    register int active_procs;
    register struct prpsinfo **prefp;
    register struct prpsinfo *pp;

    /* these are copied out of sel for speed */
    int show_idle;
    int show_system;
    int show_uid;

    /* Get current number of processes */
    (void) getkval(nproc_offset, (int *)(&nproc), sizeof(nproc), "nproc");

    /* read all the proc structures */
    getptable(pbase);

    /* get a pointer to the states summary array */
    si->procstates = process_states;

    /* set up flags which define what we are going to select */
    show_idle = sel->idle;
    show_system = sel->system;
    show_uid = sel->uid != -1;

    /* count up process states and get pointers to interesting procs */
    total_procs = 0;
    active_procs = 0;
    bzero((char *)process_states, sizeof(process_states));
    prefp = pref;

    for (pp = pbase, i = 0; i < nproc; pp++, i++)
    {
        /*
         * Place pointers to each valid proc structure in pref[].
         * Process slots that are actually in use have a non-zero
         * status field. Processes with SSYS set are system
         * processes---these get ignored unless show_sysprocs is set.
         */
        if (pp->pr_state != 0 &&
           (show_system || ((pp->pr_flag & SSYS) == 0)))
        {
            total_procs++;
            process_states[pp->pr_state]++;
            if ( (!pp->pr_zomb) &&
                (show_idle || (pp->pr_state == SRUN) || (pp->pr_state == SONPROC) ) &&
                (!show_uid || pp->pr_uid == (uid_t)sel->uid))
            {
                *prefp++ = pp;
                active_procs++;
            }
        }
    }

    /* if requested, sort the "interesting" processes */
    if (compare != NULL)
    {
        qsort((char *)pref, active_procs, sizeof(struct prpsinfo *), compare);
    }

    /* remember active and total counts */
    si->p_total = total_procs;
    si->p_active = active_procs;

    /* pass back a handle */
    handle.next_proc = pref;
    handle.remaining = active_procs;
    return((caddr_t)&handle);
}

char fmt[128]; /* static area where result is built */

char *format_next_process(handle, get_userid)
caddr_t handle;
char *(*get_userid)();
{
    register struct prpsinfo *pp;
    struct handle *hp;
    register long cputime;
    register double pctcpu;

    /* find and remember the next proc structure */
    hp = (struct handle *)handle;
    pp = *(hp->next_proc++);
    hp->remaining--;
    
    /* get the cpu usage and calculate the cpu percentages */
    cputime = pp->pr_time.tv_sec;
    pctcpu = percent_cpu(pp);

    /* format this entry */
    sprintf(fmt,
            Proc_format,
            pp->pr_pid,
            (*get_userid)(pp->pr_uid),
            pp->pr_pri - PZERO,
            pp->pr_nice - NZERO,
            pagetok(pp->pr_size),
            pagetok(pp->pr_rssize),
            state_abbrev[pp->pr_state],
            cputime / 60l,
            cputime % 60l,
            (pp->pr_cpu & 0377),
            100.0 * pctcpu,
            pp->pr_fname);
/* pp->pr_fname ); */

    /* return the result */
    return(fmt);
}

/*
 * check_nlist(nlst) - checks the nlist to see if any symbols were not
 * found. For every symbol that was not found, a one-line
 * message is printed to stderr. The routine returns the
 * number of symbols NOT found.
 */
int check_nlist(nlst)
register struct nlist *nlst;
{
    register int i;

    /* check to see if we got ALL the symbols we requested */
    /* this will write one line to stderr for every symbol not found */

    i = 0;
    while (nlst->n_name != NULL)
    {
        if (nlst->n_type == 0)
        {
            /* this one wasn't found */
            fprintf(stderr, "kernel: no symbol named `%s'\n", nlst->n_name);
            i = 1;
        }
        nlst++;
    }
    return(i);
}

/*
 * getkval(offset, ptr, size, refstr) - get a value out of the kernel.
 * "offset" is the byte offset into the kernel for the desired value,
 * "ptr" points to a buffer into which the value is retrieved,
 * "size" is the size of the buffer (and the object to retrieve),
 * "refstr" is a reference string used when printing error meessages,
 * if "refstr" starts with a '!', then a failure on read will not
 * be fatal (this may seem like a silly way to do things, but I
 * really didn't want the overhead of another argument).
 *
 */
getkval(offset, ptr, size, refstr)
unsigned long offset;
int *ptr;
int size;
char *refstr;
{
    if (lseek(kmem, (long)offset, 0) == -1)
    {
        if (*refstr == '!')
        {
            refstr++;
        }
        fprintf(stderr, "%s: lseek to %s: %s\n",
            KMEM, refstr, sys_errlist[errno]);
        quit(22);
    }
    if (read(kmem, (char *)ptr, size) == -1)
    {
        if (*refstr == '!')
        {
            /* we lost the race with the kernel, process isn't in memory */
            return(0);
        }
        else
        {
            fprintf(stderr, "%s: reading %s: %s\n",
                KMEM, refstr, sys_errlist[errno]);
            quit(23);
        }
    }
    return(1);
}
    
/* comparison routine for qsort */

/*
 * proc_compare - comparison function for "qsort"
 * Compares the resource consumption of two processes using five
 * distinct keys. The keys (in descending order of importance) are:
 * percent cpu, cpu ticks, state, resident set size, total virtual
 * memory usage. The process states are ordered as follows (from least
 * to most important): WAIT, zombie, sleep, stop, start, run. The
 * array declaration below maps a process state index into a number
 * that reflects this ordering.
 */

unsigned char sorted_state[] =
{
    0, /* not used */
    3, /* sleep */
    6, /* run */
    2, /* zombie */
    4, /* stop */
    5, /* start */
    7, /* run on a processor */
    1 /* being swapped (WAIT) */
};
 
proc_compare(pp1, pp2)
struct prpsinfo **pp1;
struct prpsinfo **pp2;
{
    register struct prpsinfo *p1;
    register struct prpsinfo *p2;
    register long result;

    /* remove one level of indirection */
    p1 = *pp1;
    p2 = *pp2;

    /* compare percent cpu (pctcpu) */
     if ((result = (long)(p2->pr_cpu - p1->pr_cpu)) == 0)
    {
        /* use cpticks to break the tie */
        if ((result = p2->pr_time.tv_sec - p1->pr_time.tv_sec ) == 0)
        {
            /* use process state to break the tie */
            if ((result = (long) (sorted_state[p2->pr_state] -
                          sorted_state[p1->pr_state])) == 0)
            {
                /* use priority to break the tie */
                if ((result = p2->pr_oldpri - p1->pr_oldpri) == 0)
                {
                    /* use resident set size (rssize) to break the tie */
                    if ((result = p2->pr_rssize - p1->pr_rssize) == 0)
                    {
                        /* use total memory to break the tie */
                        result = (p2->pr_size - p1->pr_size);
                    }
                }
            }
        }
    }
    return(result);
}

/*
get process table
 V.4 only has a linked list of processes so we want to follow that
 linked list, get all the process structures, and put them in our own
 table
*/
getptable(baseptr)
struct prpsinfo *baseptr;
{
   struct prpsinfo *currproc; /* pointer to current proc structure */
   int numprocs=0;
   DIR *procdir;
   struct dirent *direntp;
   int fd;
   char fname[40];

   if ( (procdir = opendir(PROCFS)) == NULL ) {
        fprintf(stderr,"%s: Unable to open %s\n", myname, PROCFS);
        exit(1);
   }

   while (((direntp = readdir(procdir))!=NULL) && (numprocs<nproc)) {
      if( (!strcmp(direntp->d_name,"."))||(!strcmp(direntp->d_name,"..")) ) {
        continue;
        }
      strcpy(fname,PROCFS);
      strcat(fname,"/");
      strcat(fname,direntp->d_name);
      if ( (fd = open(fname, O_RDONLY)) < 0 ) {
/* fprintf(stderr,"%s: Unable to open %s\n",myname, direntp->d_name); */
           close(fd);
           continue;
      }
      currproc = &baseptr[numprocs];
      if ( ioctl(fd, PIOCPSINFO, currproc) < 0 ) {
/* fprintf(stderr,"%s: Unable to get info from %s\n", myname, direntp->d_name); */
           close(fd);
           continue;
      }
      numprocs++;
      close(fd);
   }
   closedir(procdir);
   if (nproc!=numprocs) nproc=numprocs;
}

#ifdef SECURITY_OPTION
#include <sys/resource.h> /* for PRIO_PROCESS */

#define ROOT_ID 0
#define NICE_VALUE 10
#define TRUE 1
#define FALSE 0

int not_su;
int real_uid;

/* if su return ok, else check to see if it's our process */
/* if it's ours ok, else -1 */
check_secure(procnum)
int procnum;
{
register struct prpsinfo *p;
register int i;

        if (not_su) {
           for (i=0,p=pbase;i<nproc;i++,p++)
                if ( p->pr_pid == procnum )
                    return( (p->pr_uid == real_uid) ? 0 : -1);
        } else return(0);
}

/* find out if we're really the superuser or just pretending */
init_secure()
{
        /* get real uid and find out if it's really root */
        real_uid=getuid();

        not_su = (real_uid != ROOT_ID) ? TRUE : FALSE;
}

nice_us()
{
        setpriority(PRIO_PROCESS, getpid(), NICE_VALUE);
}

#endif /* SECURITY_OPTION */

/* Begin of SECURITY PATCH

diff orig/commands.c secure/commands.c
23a24
> #include <sys/errno.h>
392a394,397
> else if (check_secure(procnum) == -1)
> {
> ERROR(str, EPERM);
> }
453a459,462
> }
> else if (check_secure(procnum) == -1)
> {
> ERROR(str, EPERM);
diff orig/top.c secure/top.c
309a310,313
> init_secure();
>
> nice_us();
>

End of SECURITY PATCH */



This archive was generated by hypermail 2.1.2 : Fri Sep 28 2001 - 23:07:33 CDT