/*
   procinfo.c

   Displays general info from /proc.

   copyright (c) 1994 svm@kozmix.hacktic.nl

   This software is released under the GNU Public Licence. See the
   file `COPYING' for details. Since you're probably running Linux I'm
   sure your hard disk is already infested with copies of this file,
   but if not, mail me and I'll send you one.

   $Id: procinfo.c,v 1.23 1994/11/01 23:03:19 svm Exp svm $
 */

#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <termcap.h>
#include <signal.h>
#include <getopt.h>
#include <unistd.h>
#include <termios.h>
#include <sys/ioctl.h>

#define VERSION		"0.6 (1994-11-01)"
#ifndef PROC_DIR
#define PROC_DIR	"/proc/"	/* Needs the trailing slash. */
#endif
#define ISSTR(s)	(!strcmp(s, type))
#define VAL		(atol(strtok(NULL, " ")))
#define MAX(a,b)	((a)>(b)?(a):(b))
#define DIFF(x)		(show_diff?(new.x)-(old.x):(new.x))

#define CDRV		0
#define BDRV		1
#ifndef MAX_CHRDEV
#define MAX_CHRDEV	32
#endif
#ifndef MAX_BLKDEV
#define MAX_BLKDEV	32
#endif
#define MAX_DEV		MAX(MAX_CHRDEV, MAX_BLKDEV)

struct info
{
    long uptime;
    long m_to, m_us, m_fr, m_sh, m_bu;
    long s_to, s_us, s_fr;
    long cpu_user, cpu_nice, cpu_sys, cpu_idle;
    long disk[4];
    long pgin, pgout, swin, swout;
    long intr[16];
    long old_intr;
    long ctxt;
};

struct info new, old;

void init_terminal_data (void);
char *my_tgets (char *te);

char *cd;			/* clear to eos */
char *ce;			/* clear to eol */
char *cl;			/* clear screen */
char *cm;			/* move cursor */
char *ho;			/* home */
char *se;			/* standout off */
char *so;			/* standout on */
char *ve;			/* cursor on */
char *vi;			/* cursor off */
int co;				/* columns */
int li;				/* lines */
int sg;				/* cookie width */

int fs = 0, redrawn = 0;
int show_moddev = 0;
int show_all = 0;
int show_diff = 0;
int show_new_mem = 0;
int irq_array = 0;

static char booted[40];

FILE *
myfopen (char *name)
{
    FILE *fp;

    if ((fp = fopen (name, "r")) == NULL)
    {
	fprintf (stdout, "can't open file %s: %s\n", name, strerror (errno));
	exit (1);
    }
    return fp;
}

/* Note: we're using a static char array, so use this only once per printf. */
inline char *
hms (long t)
{
    int d, h, m, s;
    static char buf[22];

    d = (int) (t / 8640000);
    t = t - (long) (d * 8640000);
    h = (int) (t / 360000);
    t = t - (long) (h * 360000);
    m = (int) (t / 6000);
    t = t - (long) (m * 6000);
    s = (int) (t / 100);
    t = t - (long) (s * 100);
    if (d > 0)
	sprintf (buf, "%3dd %2d:%02d:%02d.%02d", d, h, m, s, (int) t);
    else
	sprintf (buf, "     %2d:%02d:%02d.%02d", h, m, s, (int) t);
    return buf;
}

/* Note: we're using a static char array, so use this only once per printf. */
inline char *
perc (long i, long t)
{
    int v;
    static char buf[16];

    v = (int) (i < 1000000 ?
	       ((1000 * i + t / 2) / t) :
	       ((i + t / 2000) / (t / 1000)));
    sprintf (buf, "%3d.%d%%", v / 10, v % 10);
    return buf;
}

void
bye (int i)
{
    if (i == SIGINT)		/* The 'official' exit keystroke. */
    {
	printf ("%s%s%s", ve, se, tgoto (cm, 0, li + 1));
	exit (0);
    }
    else
    {
	printf ("%s%s%s", ve, se, tgoto (cm, 0, li));
	printf ("[%s]\n", sys_siglist[i]);
	exit (128 + i);
    }
}

void
tstp (int i)
{
   /* Restore cursor & attributes, then do the actual suspend. Should be fun
      to try this unaltered on BSD-based systems. :-) */
    printf ("%s%s%s", ve, se, tgoto (cm, 0, li - 1));
    fflush (stdout);
    raise (SIGTSTP);
}

void
cont (int i)
{
    signal (SIGTSTP, tstp);
    signal (SIGCONT, cont);
    printf ("%s%s", cl, vi);
    fflush (stdout);
}

/* This function stolen from top(1) (kmem-version). */
void
window_init (int i)
{
    struct winsize ws;
    struct sigaction sa;

    co = li = 0;
    if (ioctl (1, TIOCGWINSZ, &ws) >= 0)
    {
	co = ws.ws_col;
	li = ws.ws_row;
    }
    if (co == 0)
	co = tgetnum ("co");
    if (li == 0)
	li = tgetnum ("li");
    li -= 2;
    if (fs)
	redrawn = 1;
    sa.sa_handler = window_init;
    sa.sa_flags = 0;
    sigemptyset (&sa.sa_mask);
    sigaction (SIGWINCH, &sa, NULL);
}

int
main (int ac, char **av)
{
    FILE *loadavgfp, *meminfofp, *modulesfp, *statfp, *uptimefp, *versionfp,
       *devicesfp, *filesystemsfp, *interruptsfp, *dmafp;
    char line[1024], version[1024], tmpbuf[1024];
    int sl = 5;
    int getoptopt;
    char outbuf[4096];
    char *p;			/* random pointer */
    int i;			/* random int */

    while ((getoptopt = getopt (ac, av, "fn:madDF:hv")) != EOF)
    {
	switch (getoptopt)
	{
	case 'n':
	    sl = atoi (optarg);
	   /* PLUMMET */
	case 'f':
	    fs = 1;
	    break;
	case 'm':
	    show_moddev = 1;
	    break;
	case 'a':
	    show_all = 1;
	    break;
	case 'F':
	    if( (freopen(optarg, "wb", stdout)) == NULL )
	    {
		fprintf (stderr, "%s: ", av[0]);
		perror ("unable to open new output file");
		exit (errno);
	    }
	    break;
	case 'D':
	    show_new_mem = fs = 1;	/* show new mamory & page stats, diff 
					   on rest */
	case 'd':
	    show_diff = fs = 1;
	    break;
	case 'v':
	    printf ("This is procinfo version " VERSION "\n");
	    exit (0);
	case 'h':
	default:
	    printf ("procinfo version %s\n"
		    "usage: %s [-fmadDvh] [-nN]\n"
		    "\n"
		    "\t-f\trun full screen\n"
		    "\t-nN\tpause N second between updates (implies -f)\n"
		    "\t-m\tdisplay module and device info\n"
		    "\t-a\tdisplay all info\n"
		    "\t-d\tshow differences rather than totals (implies -f)\n"
		    "\t-D\tshow current memory/swap usage, differences on rest\n"
		    "\t-F<file>  print output to file -- normally a tty\n"
		    "\t-v\tprint version info\n"
		    "\t-h\tprint this help\n",
		    VERSION, av[0]);
	    exit (getoptopt == 'h' ? 0 : 1);
	}
    }
    if (sl == 0)
	nice (-20);

    for (i = 1; i < NSIG; i++)
	signal (i, bye);
    signal (SIGTSTP, tstp);
    signal (SIGCONT, cont);

    setvbuf (stdout, outbuf, _IOFBF, sizeof (outbuf));
    init_terminal_data ();
    window_init (0);

    cd = my_tgets ("cd");
    ce = my_tgets ("ce");
    cl = my_tgets ("cl");
    cm = my_tgets ("cm");
    ho = my_tgets ("ho");
    se = my_tgets ("se");
    so = my_tgets ("so");
    ve = my_tgets ("ve");
    vi = my_tgets ("vi");
    sg = tgetnum ("sg");	/* Oh god. */

/* If you're suffering from terminals with magic cookies, you might wish
   just to forget about standout altogether. In that case, define
   COOKIE_NOSOSE.
 */

#ifdef COOKIE_NOSOSE
    if (sg > 0)
    {
	sg = 0;			/* Just forget about se/so */
	se = so = "";
    }
#endif

    if (sg < 0)
	sg = 0;

    gethostname (tmpbuf, 65);
    versionfp = myfopen (PROC_DIR "version");
    fgets (line, sizeof (version), versionfp);
   /* Junk everything after '#xx' or it'll get too long with later kernels. */
    p = strchr (line, '#');
    p = strchr (p, ' ');
    strncpy (version, line, p - line);
    sprintf (&version[p - line], "%*s [%s]",
	     fs ? -(co - strlen (tmpbuf) - strlen (version) - 3 - 2 * sg) :
	     0, "", tmpbuf);
    fclose (versionfp);

    uptimefp = myfopen (PROC_DIR "uptime");
    loadavgfp = myfopen (PROC_DIR "loadavg");
    meminfofp = myfopen (PROC_DIR "meminfo");
    statfp = myfopen (PROC_DIR "stat");
    modulesfp = myfopen (PROC_DIR "modules");
   /* These may be missing, so check for NULL later. */
    devicesfp = fopen (PROC_DIR "devices", "r");
    filesystemsfp = fopen (PROC_DIR "filesystems", "r");
    interruptsfp = fopen (PROC_DIR "interrupts", "r");
    dmafp = fopen (PROC_DIR "dma", "r");

   /* See what the intr line in /proc/stat says. */
    while (fgets (line, sizeof (line), statfp))
    {
	if (!strncmp ("intr", line, 4))
	{
	   /* If this line has a space somewhere after line[5], it's a
	      new-style intr. */
	    if (strchr (&line[5], ' '))
		irq_array = 1;
	    continue;
	}
       /* While we're at it, fill in booted. */
	else if (!strncmp ("btime", line, 5))
	{
	    time_t btime;

	    btime = (time_t) atol (&line[6]);
	    strftime (booted, sizeof (booted), "%c", localtime (&btime));
	    continue;
	}
    }

    if (fs)
	printf ("%s%s", cl, vi);

    while (42)
    {
	int i;
	char loadavg[32];

	if (redrawn)
	{
	    redrawn = 0;
	    fputs (cl, stdout);
	}
	if (fs)
	    printf ("%s%s%*s%s\n\n", ho, so, -(co - 2 * sg), version, se);
	else
	    printf ("%s\n\n", version);

	fseek (uptimefp, 0L, SEEK_SET);
	fgets (line, sizeof (line), uptimefp);
	new.uptime = (long) (atof (strtok (line, " ")) * 100.0);
	fgets (line, sizeof (line), uptimefp);	/* For libc 4.4; more below. */

	fseek (loadavgfp, 0L, SEEK_SET);
	fgets (line, sizeof (line), loadavgfp);
	strcpy (loadavg, line);
	loadavg[strlen (loadavg) - 1] = '\0';
	fgets (line, sizeof (line), loadavgfp);

	if (!show_moddev || show_all)
	{
	    fseek (meminfofp, 0L, SEEK_SET);
	    fputs ("Memory:        Total        Used        Free      "
		   "Shared     Buffers\n", stdout);
	    fgets (line, sizeof (line), meminfofp);
	    fgets (line, sizeof (line), meminfofp);
	    strtok (line, " ");
	    new.m_to = VAL / 1024;
	    new.m_us = VAL / 1024;
	    new.m_fr = VAL / 1024;
	    new.m_sh = VAL / 1024;
	    new.m_bu = VAL / 1024;
	    if (show_new_mem)
	    {
		printf ("Mem:    %12ld%12ld%12ld%12ld%12ld\n",
			new.m_to, new.m_us, new.m_fr, new.m_sh, new.m_bu);
	    }
	    else
	    {
		printf ("Mem:    %12ld%12ld%12ld%12ld%12ld\n",
			DIFF (m_to), DIFF (m_us), DIFF (m_fr), DIFF (m_sh), DIFF (m_bu));
	    }
	    fgets (line, sizeof (line), meminfofp);
	    strtok (line, " ");
	    new.s_to = VAL / 1024;
	    new.s_us = VAL / 1024;
	    new.s_fr = VAL / 1024;
	    if (show_new_mem)
	    {
		printf ("Swap:   %12ld%12ld%12ld\n\n",
			new.s_to, new.s_us, new.s_fr);
	    }
	    else
	    {
		printf ("Swap:   %12ld%12ld%12ld\n\n",
			DIFF (s_to), DIFF (s_us), DIFF (s_fr));
	    }
	    fgets (line, sizeof (line), meminfofp);
	}

	fseek (statfp, 0L, SEEK_SET);
	while (fgets (line, sizeof (line), statfp))
	{
	    char *type = strtok (line, " ");

	    if (ISSTR ("cpu"))
	    {
		new.cpu_user = VAL;
		new.cpu_nice = VAL;
		new.cpu_sys = VAL;
		new.cpu_idle = VAL;
	    }
	    else if (ISSTR ("disk"))
	    {
		new.disk[0] = VAL;
		new.disk[1] = VAL;
		new.disk[2] = VAL;
		new.disk[3] = VAL;
	    }
	    else if (ISSTR ("page"))
	    {
		new.pgin = VAL;
		new.pgout = VAL;
	    }
	    else if (ISSTR ("swap"))
	    {
		new.swin = VAL;
		new.swout = VAL;
	    }
	    else if (ISSTR ("intr"))
	    {
		if (irq_array)
		{
		    VAL;	/* First value is total of all interrupts,
				   for compatibility with rpc.rstatd. We
				   ignore it. */
		    for (i = 0; i < 16; i++)
			new.intr[i] = VAL;
		}
		else
		    new.old_intr = VAL;
	    }
	    else if (ISSTR ("ctxt"))
		new.ctxt = VAL;
	}

	printf ("Bootup: %s    Load average: %s\n\n", booted, loadavg);

	if (!show_moddev || show_all)
	{
	    long elapsed;

	    elapsed = new.uptime;

	    if (irq_array)
	    {
		if (fs && old.uptime)
		    elapsed = DIFF (intr[0]);
	    }
	    else
	    {
		if (fs && old.uptime)
		   /* This won't be exact... */
		    elapsed = 100 * sl;
	    }

	    printf ("user  : %s %s\t",
		    hms (DIFF (cpu_user)), perc (DIFF (cpu_user), elapsed));
	    printf ("    page in : %8ld\t", DIFF (pgin));
	    if (new.disk[0])
		printf ("  disk 1: %8ld\n", DIFF (disk[0]));
	    else
		putchar ('\n');

	    printf ("nice  : %s %s\t",
		    hms (DIFF (cpu_nice)), perc (DIFF (cpu_nice), elapsed));
	    printf ("    page out: %8ld\t", DIFF (pgout));
	    if (new.disk[1])
		printf ("  disk 2: %8ld\n", DIFF (disk[1]));
	    else
		putchar ('\n');

	    printf ("system: %s %s\t",
		    hms (DIFF (cpu_sys)), perc (DIFF (cpu_sys), elapsed));
	    printf ("    swap in : %8ld\t", DIFF (swin));
	    if (new.disk[2])
		printf ("  disk 3: %8ld\n", DIFF (disk[2]));
	    else
		putchar ('\n');

	    printf ("idle  : %s %s\t",
		    hms (DIFF (cpu_idle)), perc (DIFF (cpu_idle), elapsed));
	    printf ("    swap out: %8ld\t", DIFF (swout));
	    if (new.disk[3])
		printf ("  disk 4: %8ld\n", DIFF (disk[3]));
	    else
		putchar ('\n');

	    printf ("uptime: %s\t    context : %8ld", hms (new.uptime),
		    DIFF (ctxt));

	    if (irq_array)
	    {
		char irq_label[22][16];

		if (interruptsfp)
		{
		    int i;

		    for (i = 0; i < 16; i++)
			irq_label[i][0] = '\0';

		    fseek (interruptsfp, 0L, SEEK_SET);
		    while (fgets (line, sizeof (line), interruptsfp))
		    {
			i = atol (strtok (line, ":"));
			strncpy (irq_label[i], &line[15], 20);
			irq_label[i][strlen (irq_label[i]) - 1] = '\0';
			if (line[13] == '+')
			    strcat (irq_label[i], "+");
		    }
		}

		if (dmafp)
		{
		    int i;
		    char tmp[22];

		    fseek (dmafp, 0L, SEEK_SET);
		    while (fgets (line, sizeof (line), dmafp))
		    {
			for (i = 0; i < 16; i++)
			{
			    if (!(strncmp (&line[4], irq_label[i],
					   strlen (&line[4]) - 1)))
			    {
				sprintf (tmp, " [%ld]", atol (strtok (line, ":")));
				strcat (irq_label[i], tmp);
			    }
			}
		    }
		}

		fputs ("\n\n", stdout);
		for (i = 0; i < 8; i++)
		    printf ("irq %2d: %9ld %-21s "
			    "irq %2d: %9ld %-21s\n",
			    i, DIFF (intr[i]), irq_label[i],
			    i + 8, DIFF (intr[i + 8]), irq_label[i + 8]);
	    }
	    else
		printf ("\tinterrupts: %8ld\n", DIFF (old_intr));
	}

	if (show_moddev || show_all)
	{
	    int barf = 0;

	    fseek (modulesfp, 0L, SEEK_SET);
	    if (show_all)
		putchar ('\n');
	    printf ("Modules:\n");
	    while (fgets (line, sizeof (line), modulesfp))
	    {
		char *mod;
		long pg;
		char *status, *tmp;
		int used = 0;

		tmp = strdup (line);
		barf += 20;
		mod = strtok (line, " ");
		pg = atol (strtok (NULL, " ")) * 4;
		status = strtok (NULL, ")");
		if (strchr (tmp, ']'))
		    used++;
		printf ("%c%4ld %1s%-10s   ",
			status ? status[2] : ' ', pg,
			used ? "*" : " ", mod);
		free (tmp);
	    }
/*          if (co && (barf % co)) */
	    printf ("%s\n", fs ? ce : "");

	    if (devicesfp)
	    {
		int maj[2][MAX_DEV];
		char *n[2][MAX_DEV];
		int count[2] =
		{0, 0};
		int which = 0, i, j;

		memset (n, 0, sizeof (n));
		fseek (devicesfp, 0L, SEEK_SET);
		printf ("%s\nCharacter Devices:                      "
			"Block Devices:\n",
			fs ? ce : "");
		while (fgets (line, sizeof (line), devicesfp))
		{
		    switch (line[0])
		    {
		    case 'C':
			which = CDRV;
			break;
		    case 'B':
			which = BDRV;
			break;
		    case '\n':
			break;
		    default:
			maj[which][count[which]] =
			    atoi (strtok (line, " "));
			n[which][count[which]] =
			    strdup (strtok (NULL, "\n"));
			count[which]++;
			break;
		    }
		}

		j = (1 + MAX (count[0], count[1])) / 2;
		for (i = 0; i < j; i++)
		{
		    if (n[CDRV][i])
		    {
			printf ("%2d %-16s ", maj[CDRV][i], n[CDRV][i]);
			free (n[CDRV][i]);
		    }
		    else
			fputs ("                    ", stdout);
		    if (n[CDRV][i + j])
		    {
			printf ("%2d %-16s ",
				maj[CDRV][i + j], n[CDRV][i + j]);
			free (n[CDRV][i + j]);
		    }
		    else
			fputs ("                    ", stdout);
		    if (n[BDRV][i])
		    {
			printf ("%2d %-16s ", maj[BDRV][i], n[BDRV][i]);
			free (n[BDRV][i]);
		    }
		    else
			fputs ("                     ", stdout);
		    if (n[BDRV][i + j])
		    {
			printf ("%2d %-16s ",
				maj[BDRV][i + j], n[BDRV][i + j]);
			free (n[BDRV][i + j]);
		    }
		    printf ("%s\n", fs ? ce : "");
		    if (i >= count[CDRV] && i >= count[BDRV])
			break;
		}
	    }			/* devicesfp */

	    if (filesystemsfp)
	    {
		barf = 0;
		fseek (filesystemsfp, 0L, SEEK_SET);
		printf ("%s\n", fs ? ce : "");
		printf ("File Systems:%s\n", fs ? ce : "");
		while (fgets (line, sizeof (line), filesystemsfp))
		{
		    char *fs;
		    char tmp[21];

		    barf += 20;
		    fs = strchr (line, '\t');
		    fs = strtok (fs + 1, "\n");
		    if (line[0] == 'n')
		    {
			sprintf (tmp, "[%s]", fs);
			printf ("%-20s", tmp);
		    }
		    else
			printf ("%-20s", fs);
		}
		if (co && (barf % co))
		    printf ("%s\n", fs ? ce : "");
	    }			/* filesystemsfp */
	}			/* show_moddev || show_all */

	if (fs)
	{
	    fputs (cd, stdout);
	    fflush (stdout);
	    if (sl)
		sleep (sl);
	}
	else
	{
	    putchar ('\n');
	    exit (0);
	}
	memcpy (&old, &new, sizeof (struct info));
    }				/* 42 */
}
