/* System dependent file for System V Release 4.0      */
/* Tested on Unixware 1.1 (SVR4.2)                     */

/* $Id: s-svr4.c,v 1.4 2004/05/29 18:21:29 jdd Exp $ */

#include "s.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <nlist.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/immu.h>

#include <sys/utsname.h>
#include <sys/sysinfo.h>

#include <errno.h>

extern int   diskflag, cpuflag;
extern char *kernelSymbols;

extern void draw_bar(int bar_num, int *states, int num_states);
extern void display_bars(int);



/*  The following defines are used to index the variable kernel_nl[] */
#define KERNEL_UTSNAME   0
#define KERNEL_SYSINFO   1
#define KERNEL_LBOLT     2

/* kernel_nl[]  --  list of symbol addresses needed.
*/
static struct nlist kernel_nl[] =
{
  { "utsname",       0 },
  { "sysinfo",       0 },
  { "lbolt",         0 },

  { 0,               0 }
};



/* kernel_symbols[] -- list of places to look for the running kernel's symbols
*/
char *kernel_symbols[] = {
  "/stand/unix.diag",
  "/stand/unix",
  "/stand/unix.old",
  0,                   /* used by -kernel option  */

  0
};



enum stype
{
  CPU_STATE,
};


enum stype      state = CPU_STATE;
int             kmem;              /* file descriptor for /dev/kmem      */
char           *kernel_name;       /* name of kernel (/stand/unix)       */
struct utsname  nodename;          /* nodename.nodename                  */
int             kmem_is_mapped;
int             kmem_is_reading;
int             kmem_can_map    = 1;


/*  Fake kernel memory mapping information
*/
typedef struct kmem_map
{
  struct kmem_map *km_next;
  void            *km_addr;
  size_t           km_size;
  char             km_buf[4];
} kmem_map_t;

kmem_map_t *kmem_list;             /* linked list of fake memory mappings */

/*  CPU Information
*/
int             kncpu;             /* maximum number of cpu's on system  */
int             ncpus;             /* number of cpu's which exist        */
time_t         *lastcpu;           /* last snapshot of each cpu's times  */
struct sysinfo *cpuinfo;



#define CPU_STATE_SZ   sizeof(cpuinfo[0].cpu)

/* Computes the time spent in the specified state.
*/

#define delta(state) \
   ((int) cpuinfo[cpu].cpu[state] - cputime[state])


/* Save the state times for a specific cpu
*/
#define savecpuinfo(cpu) \
   memcpy(cputime, cpuinfo[cpu].cpu, CPU_STATE_SZ);


/* The size of the local state time array
*/
#define NSTATES   CPU_STATE_SZ
#define CPUSTATES (CPU_STATE_SZ / sizeof(cpuinfo[0].cpu[0]))

/* Display an error message based upon errno and
   return 0 to caller
*/
#define ERR_EXIT(msg) \
{               \
   perror(msg); \
   return 0;    \
}


/* Read a specified number of bytes of kernel memory
   and report any error conditions which might arise.
*/
#define seekreadok(desc, seekpos, buf, sz, msg) \
  if (-1 == lseek(desc, (off_t)seekpos, SEEK_SET))  \
    ERR_EXIT("lseeking " msg);               \
 \
  if ((sz) != read(desc, buf, (sz)))         \
    ERR_EXIT("reading " msg);

#define seekreaderr(desc, seekpos, buf, sz, msg) \
   ( -1  == lseek(desc, (off_t)seekpos, SEEK_SET) ||  \
    (sz) != read(desc, buf, (sz)))


/* fakethis()  -- provide faked out memory mappings if unable to mmap kernel.
*/
void *
fakethis(void *loc, size_t sz)
{
  kmem_map_t *mapping = (kmem_map_t *)malloc( sizeof(kmem_map_t) + sz );

  if (!mapping)
    return 0;

  mapping->km_addr = loc;
  mapping->km_size = sz;
  mapping->km_next = kmem_list;

  seekreadok(kmem, mapping->km_addr, mapping->km_buf, mapping->km_size,
	     "faking mmap");

  if (!kmem_list && kmem_can_map)
    fprintf(stderr, "Unable to mmap kernel addresses, using lseek/read\n");

  kmem_list = mapping;
  kmem_is_reading++;

  return (void *) mapping->km_buf;
}

/* refreshfakes()  --  Refresh fake mmap snapshots.
*/
int
refreshfakes(void)
{
  kmem_map_t *mapping;
  int         errs    = 0;

  for (mapping = kmem_list; mapping; mapping = mapping->km_next)
    if (seekreaderr(kmem, mapping->km_addr, mapping->km_buf, mapping->km_size,
		    "refreshing fake mmap's"))
      errs++;

  return errs;
}

/*  Given an address (and size in bytes) in kernel virtual memory,
    this function uses the mmap function to provide a mapping for
    those addresses in the current process.  The result is either
    a pointer to the data in this processes virtual space or
    (void *)-1 if unable to provide the mapping.
*/
void *
mapthis(int desc, void *locp, unsigned long sz)
{
  unsigned long pgoffs = (unsigned long) locp & POFFMASK;
  unsigned long pgstrt = (unsigned long) locp & PG_ADDR;
  unsigned long pgend  = ((unsigned long) locp + sz) & PG_ADDR;
  unsigned long mapsz  = pgend - pgstrt + NBPP;
  char         *map    = (char *)-1;

  if (kmem_can_map)
    map = mmap(0, mapsz, PROT_READ, MAP_SHARED, desc, pgstrt);

  if ((char *)-1 == map)
    {
      if (kmem_is_mapped)
	fprintf(stderr, "Mixing mapping and lseek/read kernel memory?\n");

      map = fakethis(locp, sz);
      return map ? map : (void *)-1;
    }

  kmem_is_mapped++;
  return (void *) (map + pgoffs);
}



/* get_kernel_symbols  --  Find the file containing the symbol definitions
                           for the currently running kernel.

   The kernel must be running out of /stand (on an svr4 system) and the
   symbol value for utsname must match the name returned by the uname()
   system call.

   This function is called given a list of possible files from /stand which
   may be running and returns the name of the first file which appears
   to be correct.
*/

char *
get_kernel_symbols(int kmemfd, char **knamelist)
{
  char **cpp;
  int    nf, errs;
  int    nsz = strlen(nodename.nodename) + 1;
  struct utsname nname;

  if (1 >= nsz)
    fprintf(stderr, "xcpustate: Empty node name\n");

  if (kernelSymbols)
    *knamelist = kernelSymbols;

  if (0 == *knamelist) knamelist++;  /* skip over initial NULL pointer */

  for (cpp = knamelist; *cpp; cpp++)
    {
      for (nf = 0; kernel_nl[nf].n_name; nf++)
	kernel_nl[nf].n_value = 0;

      nf = nlist(*cpp, kernel_nl);
      if (-1 == nf || 0 == kernel_nl[KERNEL_UTSNAME].n_value)
	continue;

      if (!seekreaderr(kmemfd, kernel_nl[KERNEL_UTSNAME].n_value,
		     &nname, sizeof(struct utsname), "utsname") &&
	  0 == strncmp(nodename.nodename, nname.nodename, nsz))
	{
	  for (errs = nf = 0; kernel_nl[nf].n_name; nf ++)
	    if (0 == kernel_nl[nf].n_value)
	      {
		fprintf(stderr, "kernel symbol `%s' not found\n",
			kernel_nl[nf].n_name);
		errs++;
	      }

	  if (errs)
	    return 0;

	  return *cpp;
	}
    }

  fprintf(stderr, "xcpustate: No kernel symbol file matches running kernel\n");
  return 0;
}



/* init_cpu_init()  --  Initialize for CPU display
*/
int
init_cpu_info(void)
{
  int nf;

  cpuinfo    = mapthis(kmem, (void *)kernel_nl[KERNEL_SYSINFO].n_value,
		       sizeof(struct sysinfo));
  if (-1 == (int)cpuinfo)
    ERR_EXIT("mapping sysinfo");

  ncpus = kncpu = 1;

  nf = kncpu * CPU_STATE_SZ;
  lastcpu = (time_t *) malloc(nf);
  memset(lastcpu, 0, nf);

  return ncpus;
}


/* Called by -version */
void
version()
{
	printf("SVR4: maxdisk=0\n");
}


/* Called at the beginning to inquire how many bars are needed
*/
int
num_bars(void)
{
  kmem   = open("/dev/kmem", O_RDONLY);
  if (-1 == kmem)
    ERR_EXIT("opening /dev/kmem");

  if (-1 == uname(&nodename))
    ERR_EXIT("uname");

  kernel_name = get_kernel_symbols(kmem, kernel_symbols);
  if (!kernel_name)
    return 0;

  if (diskflag)
    {
      fprintf(stderr, "Only cpu state information is supported\n");
      return 0;
    }

  return init_cpu_info();
}


/*
 * Indicates how many levels each bar has.  For most machines, each bar will
 * have the same stuff.  But one can, for instance, display memory use on one
 * bar, processor levels on others, etc.
 */
void
bar_items(nbars, items)
int nbars;
int items[];    /* nbars items in this */
{
    items[0] = CPUSTATES;
}


/* Called after num_bars to ask for bar names */
char **
label_cpu_bars(nbars)
  int    nbars;
{
  char **names;
  int    hsz   = strlen(nodename.nodename);
  int    rowsz = (hsz +   1 +   2 + 4) * kncpu;
  int    sz    = nbars * rowsz;
  int    cpu, i;
  char  *cp;

  shorten(nodename.nodename);

  names  = (char **)malloc(sz + sizeof(char *) * nbars);

  cp     = (char *)names + sizeof(char *) * nbars;

  for (cpu = i = 0; i < kncpu; i++)
    {
      names[cpu++] = cp;

      if (1 == kncpu)
	sprintf(cp, "%s", nodename.nodename);
      else if (0 == i)
	sprintf(cp, "%s #%d", nodename.nodename, i);
      else
	sprintf(cp, "%*s #%d", hsz, "", i);

      cp += rowsz;
    }

  return names;
}



char **
label_bars(nbars)
  int    nbars;
{
  switch (state)
    {
    case CPU_STATE:  return label_cpu_bars(nbars);
    }

  return NULL;
}




/*  Called after the bars are created to do machine dependent inits */
void
init_bars(nbars)
  int nbars;
{
  (void) display_bars(nbars);
}




/*
   This function is called every interval to compute and display
   the bars.  It should call draw_bar() with the bar number, the array of
   integer values to display in the bar, and the number of values in the
   array.
*/
void
display_cpu_bars(nbars)
  int nbars;
{
  int states[NSTATES];
  int nstates;
  int cpu, i;
  time_t *cputime = lastcpu;

  for (i = cpu = 0; i < kncpu && cpu < nbars; i++)
    {
      nstates = 0;

      if (kncpu)
	{
	  states[nstates++] = delta(CPU_IDLE);
	  states[nstates++] = delta(CPU_USER);
	  states[nstates++] = delta(CPU_KERNEL);
	  states[nstates++] = delta(CPU_WAIT) + delta(CPU_SXBRK);
#if 0
	  states[nstates++] = delta(CPU_SXBRK);
#endif
	}
      else
	{
	  states[nstates++] = 100;  /* CPU_IDLE   */
	  states[nstates++] =   0;  /* CPU_USER   */
	  states[nstates++] =   0;  /* CPU_KERNEL */
	  states[nstates++] =   0;  /* CPU_WAIT   */
#if 0
	  states[nstates++] =   0;  /* CPU_SXBRK  */
#endif
	}
      savecpuinfo(cpu);
      draw_bar(cpu, states, nstates);

      cpu++;
      cputime += CPU_STATE_SZ / sizeof(time_t);
    }
}



void
display_bars(nbars)
  int nbars;
{
  if (kmem_list)
    refreshfakes();

  switch (state)
    {
    case CPU_STATE:  display_cpu_bars(nbars);
    }

  return;
}


syntax highlighted by Code2HTML, v. 0.9.1