/***********************************************************************\
*   Copyright (C) 1992-1998 by Michael K. Johnson, johnsonm@redhat.com *
*   Copyright (C) 2002 by Rik van Riel, riel@conectiva.com.br          *
*                                                                      *
*      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 <netinet/in.h>
#include "proc/version.h"

static int stat_fd = -1;
static int uptime_fd = -1;
static int loadavg_fd = -1;
static int meminfo_fd = -1;

static char buf[1024];

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

	FILE_TO_BUF(UPTIME_FILE, uptime_fd);
	p = ONE_DOUBLE(up, buf, failed);
	if (*p != ' ' && *p != '\t')
		failed = 1;
	ONE_DOUBLE(idle, p, failed);
	if (failed) {
		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.
 *
 * (Albert Cahalan, I think, wrote the rant above.)
 *
 * Unfortunately, this code does not always succeed.  Anyone who can do
 * better is welcome to do so...
 */
unsigned long long Hertz;
static int init_Hertz_value(void) __attribute__ ((constructor));
static int
init_Hertz_value(void)
{
	/* measured in jiffies (clock ticks) */
	unsigned long long user_j, nice_j, sys_j, idle_j, iowait_j;
	double up_1, up_2, seconds;
	unsigned long long jiffies, h;
	int i, failed = 0;
	char *cp;
	if ((i = sysconf(_SC_CLK_TCK)) > 0) {
		Hertz = (unsigned long long)i;
		return 0;
	}
	do {
		user_j = nice_j = sys_j = idle_j = iowait_j = 0;
		FILE_TO_BUF(UPTIME_FILE, uptime_fd);
		ONE_DOUBLE(up_1, buf, failed);
		/* 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.  Note that this code
		 * needs to handle 4- or 5-field /proc/stat formats.
		 */
		cp = strchr(buf, '\n');
		if (cp)
			*cp++ = '\0';
		sscanf(buf, "cpu %Lu %Lu %Lu %Lu %Lu",
			&user_j, &nice_j, &sys_j, &idle_j, &iowait_j);
		if (cp)
			sscanf(cp, "cpu0 %Lu %Lu %Lu %Lu %Lu",
				&user_j, &nice_j, &sys_j, &idle_j, &iowait_j);
		FILE_TO_BUF(UPTIME_FILE, uptime_fd);
		ONE_DOUBLE(up_2, buf, failed);
		/* uptime(&up_2, NULL); */
	} while ((long long) ((up_2 - up_1) * 1000.0 / up_1));	/* want under 0.1% error */
	jiffies = user_j + nice_j + sys_j + idle_j + iowait_j;
	seconds = (up_1 + up_2) / 2;
	h = (unsigned long) ((double) jiffies / seconds);
	/* actual values used by 2.4 kernels: 32 64 100 128 1000 1024 1200 */
	switch (h) {
	case 9 ... 11:
		Hertz = 10;
		break;		/* S/390 (sometimes) */
	case 18 ... 22:
		Hertz = 20;
		break;		/* User Mode Linux */
	case 30 ... 34:
		Hertz = 32;
		break;		/* ia64 emulator */
	case 48 ... 52:
		Hertz = 50;
		break;
	case 58 ... 62:
		Hertz = 60;
		break;
	case 63 ... 65:
		Hertz = 64;
		break;		/* StrongARM /Shark */
	case 95 ... 105:
		Hertz = 100;
		break;		/* normal Linux */
	case 124 ... 132:
		Hertz = 128;
		break;		/* MIPS, ARM */
	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;		/* ARM */
	case 1015 ... 1035:
		Hertz = 1024;
		break;		/* Alpha, ia64 */
	case 1180 ... 1220:
		Hertz = 1200;
		break;		/* Alpha */
	default:
#ifdef HZ
		Hertz = (unsigned long long) HZ;	/* <asm/param.h> */
#else
		/* If 32-bit or big-endian (not Alpha or ia64), assume HZ is 100. */
		Hertz = (sizeof (long) == sizeof (int)
			 || htons(999) == 999) ? 100UL : 1024UL;
#endif
#if 0				/* This ends up causing more harm than good.  :-( */
		fprintf(stderr, "Unknown HZ value! (%ld) Assume %ld.\n", h,
			Hertz);
#endif
	}
	return 0;		/* useless, but FILE_TO_BUF has a return in it */
}

/***********************************************************************/
#define JT unsigned long long
int
five_cpu_numbers(JT * uret, JT * nret, JT * sret, JT * iret, JT * iowait)
{
	static JT u = 0, m = 0, s = 0, i = 0, iw = 0;
	JT user_j, nice_j, sys_j, idle_j, iowait_j = 0;

	FILE_TO_BUF(STAT_FILE, stat_fd);
	sscanf(buf, "cpu %Lu %Lu %Lu %Lu %Lu", &user_j, &nice_j, &sys_j,
			&idle_j, &iowait_j);
	SET_IF_DESIRED(uret, user_j - u);
	SET_IF_DESIRED(nret, nice_j - m);
	SET_IF_DESIRED(sret, sys_j - s);
	/* Idle can go backwards one tick due to kernel calculation issues */
	SET_IF_DESIRED(iret, (idle_j > i) ? (idle_j - i) : 0);
	SET_IF_DESIRED(iowait, iowait_j - iw);
	u = user_j;
	m = nice_j;
	s = sys_j;
	i = idle_j;
	iw = iowait_j;
	return 0;
}

#undef JT

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

	FILE_TO_BUF(LOADAVG_FILE, loadavg_fd);
	p = ONE_DOUBLE(avg_1, buf, failed);
	if (*p != ' ' && *p != '\t')
		failed = 1;
	p = ONE_DOUBLE(avg_5, p, failed);
	if (*p != ' ' && *p != '\t')
		failed = 1;
	ONE_DOUBLE(avg_15, p, failed);
	if (failed) {
		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;
}

/*
 * Compatibility /proc/meminfo parsing for Linux kernels earlier than 2.0,
 * this meminfo uses a table laid out as follows:
 *
 *         total:    used:    free:  shared: buffers:  cached:
 *         Mem:  196087808 190554112  5533696        0 11317248 73461760
 *         Swap: 520982528 123879424 397103104
 */
int
meminfo_compat_20(struct meminfo_struct *mem_i)
{
#define MAX_ROW 3
#define MAX_COL 8
	char *p;
	unsigned long long oldmem[MAX_ROW][MAX_COL];
	int i, j, k, l;

	FILE_TO_BUF(MEMINFO_FILE, meminfo_fd);
	p = buf;

	for (i = 0; i < MAX_ROW; i++)
		for (j = 0; j < MAX_COL; j++)
			oldmem[i][j] = 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, "%llu%n", &(oldmem[i][j]), &k);
			p += k;	/* step over used buffer */
			if (*p == '\n' || l < 1)	/* end of line/buffer */
				break;
		}
	}

	/* Follow the layout of /proc/meminfo ... not pretty, but localised */
	mem_i->mem.total = oldmem[0][0];
	mem_i->mem.used = oldmem[0][1];
	mem_i->mem.free = oldmem[0][2];
	mem_i->mem.shared = oldmem[0][3];
	mem_i->mem.buffers = oldmem[0][4];
	mem_i->mem.cached = oldmem[0][5];
	mem_i->swap.total = oldmem[1][0];
	mem_i->swap.used = oldmem[1][1];
	mem_i->swap.free = oldmem[1][2];

	return 0;
}

/************************************************************************
 * 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.
 */
int
meminfo(struct meminfo_struct *mem_i)
{
	char *p;
	char fieldbuf[15];	/* bigger than any field name or size in kb */
	unsigned long long val;
	int k;
	int error = 0;

	/*
	 * Kernels older than 2.0 don't have the modern /proc/meminfo format,
	 * use the old compatibility version instead.
	 */
	if (linux_version_code < LINUX_VERSION(2, 0, 0))
		return meminfo_compat_20(mem_i);

	FILE_TO_BUF(MEMINFO_FILE, meminfo_fd);
	p = buf;

	while (*p) {
		sscanf(p, "%13s%n", fieldbuf, &k);
		if (!strcmp(fieldbuf, "MemTotal:")) {
			p += k;
			sscanf(p, " %Ld", &val);
			mem_i->mem.total = val << 10;
			while (*p++ != '\n') ;
		} else if (!strcmp(fieldbuf, "MemFree:")) {
			p += k;
			sscanf(p, " %Ld", &val);
			mem_i->mem.free = val << 10;
			while (*p++ != '\n') ;
		} else if (!strcmp(fieldbuf, "MemShared:")) {
			p += k;
			sscanf(p, " %Ld", &val);
			mem_i->mem.shared = val << 10;
			while (*p++ != '\n') ;
		} else if (!strcmp(fieldbuf, "Buffers:")) {
			p += k;
			sscanf(p, " %Ld", &val);
			mem_i->mem.buffers = val << 10;
			while (*p++ != '\n') ;
		} else if (!strcmp(fieldbuf, "Cached:")) {
			p += k;
			sscanf(p, " %Ld", &val);
			mem_i->mem.cached = val << 10;
			while (*p++ != '\n') ;
		} else if (!strcmp(fieldbuf, "SwapCached:")) {
			p += k;
			sscanf(p, " %Ld", &val);
			mem_i->mem.swapcached = val << 10;
			while (*p++ != '\n') ;
		} else if (!strcmp(fieldbuf, "Active:")) {
			p += k;
			sscanf(p, " %Ld", &val);
			mem_i->mem.active = val << 10;
			while (*p++ != '\n') ;
		}
		/* > 2.4.10, 2.4-aa and 2.5 have just one inactive list */
		else if (!strcmp(fieldbuf, "Inactive:")) {
			p += k;
			sscanf(p, " %Ld", &val);
			mem_i->mem.inactive_dirty = val << 10;
			while (*p++ != '\n') ;
		}
		/* < 2.4.9, 2.4.12-ac and 2.4-rmap have a split inactive list */
		else if (!strcmp(fieldbuf, "Inact_dirty:")) {
			p += k;
			sscanf(p, " %Ld", &val);
			mem_i->mem.inactive_dirty = val << 10;
			while (*p++ != '\n') ;
		} else if (!strcmp(fieldbuf, "Inact_clean:")) {
			p += k;
			sscanf(p, " %Ld", &val);
			mem_i->mem.inactive_clean = val << 10;
			while (*p++ != '\n') ;
		} else if (!strcmp(fieldbuf, "Inact_target:")) {
			p += k;
			sscanf(p, " %Ld", &val);
			mem_i->mem.inactive_target = val << 10;
			while (*p++ != '\n') ;
		} else if (!strcmp(fieldbuf, "LowTotal:")) {
			p += k;
			sscanf(p, " %Ld", &val);
			mem_i->mem.low_total = val << 10;
			while (*p++ != '\n') ;
		} else if (!strcmp(fieldbuf, "LowFree:")) {
			p += k;
			sscanf(p, " %Ld", &val);
			mem_i->mem.low_free = val << 10;
			while (*p++ != '\n') ;
		} else if (!strcmp(fieldbuf, "HighTotal:")) {
			p += k;
			sscanf(p, " %Ld", &val);
			mem_i->mem.high_total = val << 10;
			while (*p++ != '\n') ;
		} else if (!strcmp(fieldbuf, "HighFree:")) {
			p += k;
			sscanf(p, " %Ld", &val);
			mem_i->mem.high_free = val << 10;
			while (*p++ != '\n') ;
		} else if (!strcmp(fieldbuf, "SwapTotal:")) {
			p += k;
			sscanf(p, " %Ld", &val);
			mem_i->swap.total = val << 10;
			while (*p++ != '\n') ;
		} else if (!strcmp(fieldbuf, "SwapFree:")) {
			p += k;
			sscanf(p, " %Ld", &val);
			mem_i->swap.free = val << 10;
			while (*p++ != '\n') ;
		} else
			while (*p++ != '\n') ;	/* ignore lines we don't understand */
	}
	mem_i->swap.used = mem_i->swap.total - mem_i->swap.free;
	mem_i->mem.used = mem_i->mem.total - mem_i->mem.free;
	mem_i->mem.low_used = mem_i->mem.low_total - mem_i->mem.low_free;
	mem_i->mem.high_used = mem_i->mem.high_total - mem_i->mem.high_free;

	return error;
}

/**************************************************************************
 * shorthand for read_table("/proc/meminfo")[meminfo_main][meminfo_total] */
unsigned long long
read_total_main(void)
{
	struct meminfo_struct mem = MEMINFO_ZERO;
	meminfo(&mem);
	return mem.mem.total;
}
