/***********************************************************************\
*   Copyright (C) 1992-1998 by Michael K. Johnson, johnsonm@redhat.com *
*                                                                      *
*      This file is placed under the conditions of the GNU Library     *
*      General Public License, version 2, or any later version.        *
*      See file COPYING for information on distribution conditions.    *
\***********************************************************************/

/* File for parsing top-level /proc entities. */
#include "proc/sysinfo.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include <unistd.h>
#include <fcntl.h>
#include "proc/version.h"

#define BAD_OPEN_MESSAGE					\
"Error: /proc must be mounted\n"				\
"  To mount /proc at boot you need an /etc/fstab line like:\n"	\
"      /proc   /proc   proc    defaults\n"			\
"  In the meantime, mount /proc /proc -t proc\n"

#define STAT_FILE    "/proc/stat"
static int stat_fd = -1;
#define UPTIME_FILE  "/proc/uptime"
static int uptime_fd = -1;
#define LOADAVG_FILE "/proc/loadavg"
static int loadavg_fd = -1;
#define MEMINFO_FILE "/proc/meminfo"
static int meminfo_fd = -1;

static char buf[1024];

/* This macro opens filename only if necessary and seeks to 0 so
 * that successive calls to the functions are more efficient.
 * It also reads the current contents of the file into the global buf.
 */
#define FILE_TO_BUF(filename, fd) do{				\
    static int n;						\
    if (fd == -1 && (fd = open(filename, O_RDONLY)) == -1) {	\
	fprintf(stderr, BAD_OPEN_MESSAGE);			\
	close(fd);						\
	_exit(0);						\
    }								\
    lseek(fd, 0L, SEEK_SET);					\
    if ((n = read(fd, buf, sizeof buf - 1)) < 0) {		\
	perror(filename);					\
	close(fd);						\
	fd = -1;						\
	return 0;						\
    }								\
    buf[n] = '\0';						\
}while(0)

#define SET_IF_DESIRED(x,y)  if(x) *(x) = (y)	/* evals 'x' twice */

/***********************************************************************/
int
uptime(double *uptime_secs, double *idle_secs)
{
	double up = 0, idle = 0;

	FILE_TO_BUF(UPTIME_FILE, uptime_fd);
	if (sscanf(buf, "%lf %lf", &up, &idle) < 2) {
		fprintf(stderr, "bad data in " UPTIME_FILE "\n");
		return 0;
	}
	SET_IF_DESIRED(uptime_secs, up);
	SET_IF_DESIRED(idle_secs, idle);
	return up;		/* assume never be zero seconds in practice */
}

/***********************************************************************
 * Some values in /proc are expressed in units of 1/HZ seconds, where HZ
 * is the kernel clock tick rate. One of these units is called a jiffy.
 * The HZ value used in the kernel may vary according to hacker desire.
 * According to Linus Torvalds, this is not true. He considers the values
 * in /proc as being in architecture-dependant units that have no relation
 * to the kernel clock tick rate. Examination of the kernel source code
 * reveals that opinion as wishful thinking.
 *
 * In any case, we need the HZ constant as used in /proc. (the real HZ value
 * may differ, but we don't care) There are several ways we could get HZ:
 *
 * 1. Include the kernel header file. If it changes, recompile this library.
 * 2. Use the sysconf() function. When HZ changes, recompile the C library!
 * 3. Ask the kernel. This is obviously correct...
 *
 * Linus Torvalds won't let us ask the kernel, because he thinks we should
 * not know the HZ value. Oh well, we don't have to listen to him.
 * Someone smuggled out the HZ value. :-)
 *
 * This code should work fine, even if Linus fixes the kernel to match his
 * stated behavior. The code only fails in case of a partial conversion.
 *
 */
unsigned long Hertz;
static int init_Hertz_value(void) __attribute__ ((constructor));
static int
init_Hertz_value(void)
{
	unsigned long user_j, nice_j, sys_j, other_j;	/* jiffies (clock ticks) */
	double up_1, up_2, seconds;
	unsigned long jiffies, h;
	int i = 0;
	do {
		FILE_TO_BUF(UPTIME_FILE, uptime_fd);
		sscanf(buf, "%lf", &up_1);
		/* uptime(&up_1, NULL); */
		FILE_TO_BUF(STAT_FILE, stat_fd);
		/* If we are SMP, then the first line is the sum of jiffies by all CPUs */
		/* In that case, skip it and use the jiffies of the first CPU instead. */
		/* On a single-CPU machine, the 2nd sscanf should harmlessly fail. */
		sscanf(buf, "cpu %lu %lu %lu %lu\n%n",
		       &user_j, &nice_j, &sys_j, &other_j, &i);
		sscanf(buf + i, "cpu0 %lu %lu %lu %lu", &user_j, &nice_j,
		       &sys_j, &other_j);
		FILE_TO_BUF(UPTIME_FILE, uptime_fd);
		sscanf(buf, "%lf", &up_2);
		/* uptime(&up_2, NULL); */
	} while ((long) ((up_2 - up_1) * 1000.0 / up_1));	/* want under 0.1% error */
	jiffies = user_j + nice_j + sys_j + other_j;
	seconds = (up_1 + up_2) / 2;
	h = (unsigned long) ((double) jiffies / seconds);
	switch (h) {
	case 48 ... 52:
		Hertz = 50;
		break;
	case 58 ... 62:
		Hertz = 60;
		break;
	case 95 ... 105:
		Hertz = 100;
		break;		/* normal Linux */
	case 124 ... 132:
		Hertz = 128;
		break;
	case 195 ... 204:
		Hertz = 200;
		break;		/* normal << 1 */
	case 253 ... 260:
		Hertz = 256;
		break;
	case 393 ... 408:
		Hertz = 400;
		break;		/* normal << 2 */
	case 790 ... 808:
		Hertz = 800;
		break;		/* normal << 3 */
	case 990 ... 1010:
		Hertz = 1000;
		break;
	case 1015 ... 1035:
		Hertz = 1024;
		break;		/* Alpha */
	default:
#ifdef HZ
		Hertz = (unsigned long) HZ;	/* <asm/param.h> */
#else
		Hertz = (sizeof (long) == sizeof (int)) ? 100UL : 1024UL;
#endif
		fprintf(stderr, "Unknown HZ value! (%ld) Assume %ld.\n", h,
			Hertz);
	}
	return 0;		/* useless, but FILE_TO_BUF has a return in it */
}

/***********************************************************************/
#define JT unsigned long
int
four_cpu_numbers(JT * uret, JT * nret, JT * sret, JT * iret)
{
	static JT u, n, s, i;
	JT user_j, nice_j, sys_j, idle_j;

	FILE_TO_BUF(STAT_FILE, stat_fd);
	sscanf(buf, "cpu %lu %lu %lu %lu", &user_j, &nice_j, &sys_j, &idle_j);
	SET_IF_DESIRED(uret, user_j - u);
	SET_IF_DESIRED(nret, nice_j - n);
	SET_IF_DESIRED(sret, sys_j - s);
	SET_IF_DESIRED(iret, idle_j - i);
	u = user_j;
	n = nice_j;
	s = sys_j;
	i = idle_j;
	return 0;
}

#undef JT

/***********************************************************************/
int
loadavg(double *av1, double *av5, double *av15)
{
	double avg_1 = 0, avg_5 = 0, avg_15 = 0;

	FILE_TO_BUF(LOADAVG_FILE, loadavg_fd);
	if (sscanf(buf, "%lf %lf %lf", &avg_1, &avg_5, &avg_15) < 3) {
		fprintf(stderr, "bad data in " LOADAVG_FILE "\n");
		exit(1);
	}
	SET_IF_DESIRED(av1, avg_1);
	SET_IF_DESIRED(av5, avg_5);
	SET_IF_DESIRED(av15, avg_15);
	return 1;
}

/************************************************************************
 * The following /proc/meminfo parsing routine assumes the following format:
 * [ <label> ... ]				# header lines
 * [ <label> ] <num> [ <num> ... ]		# table rows
 * [ repeats of above line ]
 * 
 * Any lines with fewer <num>s than <label>s get trailing <num>s set to zero.
 * The return value is a NULL terminated unsigned** which is the table of
 * numbers without labels.  Convenient enumeration constants for the major and
 * minor dimensions are available in the header file.  Note that this version
 * requires that labels do not contain digits.  It is readily extensible to
 * labels which do not *begin* with digits, though.
 */
#define MAX_ROW 3		/* these are a little liberal for flexibility */
#define MAX_COL 7
unsigned **
meminfo(void)
{
	static unsigned *row[MAX_ROW + 1];	/* row pointers */
	static unsigned num[MAX_ROW * MAX_COL];	/* number storage */
	char *p;
	char fieldbuf[12];	/* bigger than any field name or size in kb */
	int i, j, k, l;

	FILE_TO_BUF(MEMINFO_FILE, meminfo_fd);
	if (!row[0])		/* init ptrs 1st time through */
		for (i = 0; i < MAX_ROW; i++)	/* std column major order: */
			row[i] = num + MAX_COL * i;	/* A[i][j] = A + COLS*i + j */
	p = buf;
	for (i = 0; i < MAX_ROW; i++)	/* zero unassigned fields */
		for (j = 0; j < MAX_COL; j++)
			row[i][j] = 0;
	if (linux_version_code < LINUX_VERSION(2, 0, 0)) {
		for (i = 0; i < MAX_ROW && *p; i++) {	/* loop over rows */
			while (*p && !isdigit(*p))
				p++;	/* skip chars until a digit */
			for (j = 0; j < MAX_COL && *p; j++) {	/* scanf column-by-column */
				l = sscanf(p, "%u%n", row[i] + j, &k);
				p += k;	/* step over used buffer */
				if (*p == '\n' || l < 1)	/* end of line/buffer */
					break;
			}
		}
	} else {
		while (*p) {
			sscanf(p, "%11s%n", fieldbuf, &k);
			if (!strcmp(fieldbuf, "MemTotal:")) {
				p += k;
				sscanf(p, " %d",
				       &(row[meminfo_main][meminfo_total]));
				row[meminfo_main][meminfo_total] <<= 10;
				while (*p++ != '\n') ;
			} else if (!strcmp(fieldbuf, "MemFree:")) {
				p += k;
				sscanf(p, " %d",
				       &(row[meminfo_main][meminfo_free]));
				row[meminfo_main][meminfo_free] <<= 10;
				while (*p++ != '\n') ;
			} else if (!strcmp(fieldbuf, "MemShared:")) {
				p += k;
				sscanf(p, " %d",
				       &(row[meminfo_main][meminfo_shared]));
				row[meminfo_main][meminfo_shared] <<= 10;
				while (*p++ != '\n') ;
			} else if (!strcmp(fieldbuf, "Buffers:")) {
				p += k;
				sscanf(p, " %d",
				       &(row[meminfo_main][meminfo_buffers]));
				row[meminfo_main][meminfo_buffers] <<= 10;
				while (*p++ != '\n') ;
			} else if (!strcmp(fieldbuf, "Cached:")) {
				p += k;
				sscanf(p, " %d",
				       &(row[meminfo_main][meminfo_cached]));
				row[meminfo_main][meminfo_cached] <<= 10;
				while (*p++ != '\n') ;
			} else if (!strcmp(fieldbuf, "SwapTotal:")) {
				p += k;
				sscanf(p, " %d",
				       &(row[meminfo_swap][meminfo_total]));
				row[meminfo_swap][meminfo_total] <<= 10;
				while (*p++ != '\n') ;
			} else if (!strcmp(fieldbuf, "SwapFree:")) {
				p += k;
				sscanf(p, " %d",
				       &(row[meminfo_swap][meminfo_free]));
				row[meminfo_swap][meminfo_free] <<= 10;
				while (*p++ != '\n') ;
			} else
				while (*p++ != '\n') ;	/* ignore lines we don't understand */
		}
		row[meminfo_swap][meminfo_used] =
		    row[meminfo_swap][meminfo_total] -
		    row[meminfo_swap][meminfo_free];
		row[meminfo_main][meminfo_used] =
		    row[meminfo_main][meminfo_total] -
		    row[meminfo_main][meminfo_free];
	}
	return row;		/* NULL return ==> error */
}

/**************************************************************************
 * shorthand for read_table("/proc/meminfo")[meminfo_main][meminfo_total] */
unsigned
read_total_main(void)
{
	unsigned **mem;
	return (mem = meminfo())? mem[meminfo_main][meminfo_total] : -1;
}
