/** \file clients/lcdproc/machine_Linux.c
 * Collects system information on Linux.
 */

/*-
 * This file is part of lcdproc, the lcdproc client.
 *
 * This file is released under the GNU General Public License.
 * Refer to the COPYING file distributed with this package.
 */

#ifdef linux

#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <ctype.h>
#include <string.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/param.h>
#include <sys/utsname.h>
#include <sys/stat.h>
#include <sys/statvfs.h>
#include <errno.h>

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif

#ifdef HAVE_GETLOADAVG
# define USE_GETLOADAVG
#endif

#ifdef USE_GETLOADAVG
# ifdef HAVE_SYS_LOADAVG_H
#  include <sys/loadavg.h>
# endif
#endif

#ifdef HAVE_PROCFS_H
# include <procfs.h>
#endif

#ifdef HAVE_SYS_PROCFS_H
# include <sys/procfs.h>
#endif

#include "main.h"
#include "mode.h"
#include "machine.h"
#include "shared/LL.h"


static int batt_fd;
static int load_fd;
static int loadavg_fd;
static int meminfo_fd;
static int uptime_fd;

static char procbuf[1024]; //TODO ugly hack!

static FILE *mtab_fd;


int machine_init(void)
{
	uptime_fd	= -1;
	batt_fd		= -1;
	load_fd		= -1;
	loadavg_fd	= -1;
	meminfo_fd	= -1;

	if (uptime_fd < 0) {
		uptime_fd = open("/proc/uptime", O_RDONLY);
		if (uptime_fd < 0) {
			perror("open /proc/uptime");
			return(FALSE);
		}
	}

	if (load_fd < 0) {
		load_fd = open("/proc/stat", O_RDONLY);
		if (load_fd < 0) {
			perror("open /proc/stat");
			return(FALSE);
		}
	}

#ifndef USE_GETLOADAVG
	if (loadavg_fd < 0) {
		loadavg_fd = open("/proc/loadavg", O_RDONLY);
		if (loadavg_fd < 0) {
			perror("open /proc/loadavg");
			return(FALSE);
		}
	}
#endif

	if (meminfo_fd < 0) {
		meminfo_fd = open("/proc/meminfo", O_RDONLY);
		if (meminfo_fd < 0) {
			perror("open /proc/meminfo");
			return(FALSE);
		}
	}

	if (batt_fd < 0) {
		batt_fd = open("/proc/apm", O_RDONLY);
		if (batt_fd < 0) {
			//perror("open /proc/apm");
			/* allow opening /proc/apm to fail */
			batt_fd = -1;
		}
	}

	return(TRUE);
}

int machine_close(void)
{
	if (batt_fd >= 0)
		close(batt_fd);
	batt_fd = -1;

	if (load_fd >= 0)
		close(load_fd);
	load_fd = -1;

#ifndef USE_GETLOADAVG
	if (loadavg_fd >= 0)
		close(loadavg_fd);
	loadavg_fd = -1;
#endif

	if (meminfo_fd >= 0)
		close(meminfo_fd);
	meminfo_fd = -1;

	if (uptime_fd >= 0)
		close(uptime_fd);
	uptime_fd = -1;

	return(TRUE);
}

static void
reread (int f, char *errmsg)
{
	if (lseek(f, 0L, 0) == 0 && read(f, procbuf, sizeof(procbuf) - 1) > 0)
		return;
	perror(errmsg);
	exit(1);
}

static int
getentry (const char *tag, const char *bufptr, long *value)
{
	char *tail;
	int len = strlen(tag);
	long val;

	while (bufptr != NULL) {
		if (*bufptr == '\n')
			bufptr++;
		if (!strncmp(tag, bufptr, len)) {
			errno = 0;
			val = strtol(bufptr + len, &tail, 10);
			if ((errno == 0) && (tail != bufptr + len)) {
				*value = val;
				return TRUE;
			}
			else
				return FALSE;
		}
		bufptr = strchr(bufptr, '\n');
	}
	return FALSE;
}

int machine_get_battstat(int *acstat, int *battflag, int *percent)
{
	char str[64];
	int battstat;

	/* no battery status available: fake one ;-) */
	if (batt_fd < 0) {
		*acstat   = LCDP_AC_ON;
		*battflag = LCDP_BATT_ABSENT;
		*percent  = 100;
		return(TRUE);
	}

	if (lseek(batt_fd, 0, 0) != 0)
		return(FALSE);

	if (read(batt_fd, str, sizeof(str) - 1) < 0)
		return(FALSE);

	if (3 > sscanf(str + 13, "0x%x 0x%x 0x%x %d", acstat, &battstat, battflag, percent))
		return(FALSE);

	if (*battflag == 0xff)
		*battflag = LCDP_BATT_UNKNOWN;
	else {
		if (*battflag & 1)
			*battflag = LCDP_BATT_HIGH;
		if (*battflag & 2)
			*battflag = LCDP_BATT_LOW;
		if (*battflag & 4)
			*battflag = LCDP_BATT_CRITICAL;
		if (*battflag & 8 || battstat == 3)
			*battflag = LCDP_BATT_CHARGING;
		if (*battflag & 128)
			*battflag = LCDP_BATT_ABSENT;
	}

	switch(*acstat) {
		case 0:
			*acstat = LCDP_AC_OFF;
			break;
		case 1:
			*acstat = LCDP_AC_ON;
			break;
		case 2:
			*acstat = LCDP_AC_BACKUP;
			break;
		default:
			*acstat = LCDP_AC_UNKNOWN;
			break;
	}

	return(TRUE);
}

int machine_get_fs(mounts_type fs[], int *cnt)
{
#ifdef STAT_STATVFS
	struct statvfs fsinfo;
#else
	struct statfs fsinfo;
#endif
	char line[256];
	int x = 0, y;

#ifdef MTAB_FILE
	mtab_fd = fopen(MTAB_FILE, "r");
#else
# error "Can't find your mounted filesystem table file."
#endif

	// Get rid of old, unmounted filesystems...
	memset(fs, 0, sizeof(mounts_type) * 256);

	while (x < 256) {
		if (fgets(line, 256, mtab_fd) == NULL) {
			fclose(mtab_fd);
			*cnt = x;
			return(FALSE);
		}

		sscanf(line, "%s %s %s", fs[x].dev, fs[x].mpoint, fs[x].type);

		if (strcmp(fs[x].type, "proc")
			&& strcmp(fs[x].type, "tmpfs")
#ifndef STAT_NFS
			 && strcmp(fs[x].type, "nfs")
#endif
#ifndef STAT_SMBFS
			 && strcmp(fs[x].type, "smbfs")
#endif
			 )
		{
#ifdef STAT_STATVFS
			y = statvfs(fs[x].mpoint, &fsinfo);
#elif STAT_STATFS2_BSIZE
			y = statfs(fs[x].mpoint, &fsinfo);
#elif STAT_STATFS4
			y = statfs(fs[x].mpoint, &fsinfo, sizeof (fsinfo), 0);
#else
#error "statfs for this system not yet supported"
#endif

			fs[x].blocks = fsinfo.f_blocks;
			if (fs[x].blocks > 0) {
				fs[x].bsize = fsinfo.f_bsize;
				fs[x].bfree = fsinfo.f_bfree;
				fs[x].files = fsinfo.f_files;
				fs[x].ffree = fsinfo.f_ffree;
				x++;
			}
		}
	}

	fclose(mtab_fd);
	*cnt = x;
	return(TRUE);
}

int machine_get_load(load_type *curr_load)
{
	static load_type last_load = { 0, 0, 0, 0, 0 };
	load_type load;
	int ret;
	unsigned long load_iowait, load_irq, load_softirq;

	reread(load_fd, "get_load");

	ret = sscanf(procbuf, "cpu %lu %lu %lu %lu %lu %lu %lu",
	             &load.user, &load.nice, &load.system, &load.idle,
	             &load_iowait, &load_irq, &load_softirq);

	if (ret >= 5)
		load.idle += load_iowait;
	if (ret >= 6)
		load.system += load_irq;
	if (ret >= 7)
		load.system += load_softirq;

	load.total = load.user + load.nice + load.system + load.idle;

	curr_load->user   = load.user   - last_load.user;
	curr_load->nice   = load.nice   - last_load.nice;
	curr_load->system = load.system - last_load.system;
	curr_load->idle   = load.idle   - last_load.idle;
	curr_load->total  = load.total  - last_load.total;

	// struct assignment is legal in C89
	last_load = load;

	return(TRUE);
}

int machine_get_loadavg(double *load)
{
#ifdef USE_GETLOADAVG
	double loadavg[LOADAVG_NSTATS];

	if (getloadavg(loadavg, LOADAVG_NSTATS) <= LOADAVG_1MIN) {
		perror("getloadavg");	/* ToDo: correct error reporting */
		return(FALSE);
	}
	*load = loadavg[LOADAVG_1MIN];
#else
	reread(loadavg_fd, "get_loadavg");
	sscanf(procbuf, "%lf", load);
#endif
	return(TRUE);
}

int machine_get_meminfo(meminfo_type *result)
{
	long tmp;

	reread(meminfo_fd, "get_meminfo");
	result[0].total   = (getentry("MemTotal:", procbuf, &tmp)  == TRUE) ? tmp : 0L;
	result[0].free    = (getentry("MemFree:", procbuf, &tmp)   == TRUE) ? tmp : 0L;
	result[0].shared  = (getentry("MemShared:", procbuf, &tmp) == TRUE) ? tmp : 0L;
	result[0].buffers = (getentry("Buffers:", procbuf, &tmp)   == TRUE) ? tmp : 0L;
	result[0].cache   = (getentry("Cached:", procbuf, &tmp)    == TRUE) ? tmp : 0L;
	result[1].total   = (getentry("SwapTotal:", procbuf, &tmp) == TRUE) ? tmp : 0L;
	result[1].free    = (getentry("SwapFree:", procbuf, &tmp)  == TRUE) ? tmp : 0L;

	return(TRUE);
}

int machine_get_procs(LinkedList *procs)
{
	// Much of this code was ripped from "gmemusage"
	DIR *proc;
	FILE *StatusFile;
	struct dirent *procdir;

	char	procName[16];
	int		procSize, procRSS, procData, procStk, procExe;
	const char
			*NameLine	= "Name:",
			*VmSizeLine	= "VmSize:",
			*VmRSSLine	= "VmRSS",
			*VmDataLine	= "VmData",
			*VmStkLine	= "VmStk",
			*VmExeLine	= "VmExe";
	const int
			NameLineLen	= strlen(NameLine),
			VmSizeLineLen	= strlen(VmSizeLine),
			VmDataLineLen	= strlen(VmDataLine),
			VmStkLineLen	= strlen(VmStkLine),
			VmExeLineLen	= strlen(VmExeLine),
			VmRSSLineLen	= strlen(VmRSSLine);
	int threshold = 400, unique;

	if ((proc = opendir("/proc")) == NULL) {
		perror("mem_top_screen: unable to open /proc"); /* ToDo: correct error reporting */
		return(FALSE);
	}

	while ((procdir = readdir(proc))) {
		char buf[128];

		/* ignore everything in proc except process ids */
		if (!strchr("1234567890", procdir->d_name[0]))
			continue;

		sprintf(buf, "/proc/%s/status", procdir->d_name);
		if ((StatusFile = fopen(buf, "r")) == NULL) {
			// Not a serious error; process has finished before we could
			// examine it:
			continue;
		}

		procRSS = procSize = procData = procStk = procExe = 0;
		while (fgets(buf, sizeof(buf), StatusFile)) {
			if (!strncmp(buf, NameLine, NameLineLen)) {
				/* Name: procName */
				sscanf(buf, "%*s %s", procName);
			} else if (!strncmp(buf, VmSizeLine, VmSizeLineLen)) {
				/* VmSize: procSize kB */
				sscanf(buf, "%*s %d", &procSize);
			} else if (!strncmp (buf, VmRSSLine, VmRSSLineLen)) {
				/* VmRSS: procRSS kB */
				sscanf(buf, "%*s %d", &procRSS);
			} else if (!strncmp(buf, VmDataLine, VmDataLineLen)) {
				/* VmData: procData kB */
				sscanf(buf, "%*s %d", &procData);
			} else if (!strncmp(buf, VmStkLine, VmStkLineLen)) {
				/* VmStk: procStk kB */
				sscanf(buf, "%*s %d", &procStk);
			} else if (!strncmp(buf, VmExeLine, VmExeLineLen)) {
				/* VmExe: procExe kB */
				sscanf(buf, "%*s %d", &procExe);
			}
		}
		fclose(StatusFile);

		if (procSize > threshold) {
			// Figure out if it's sharing any memory...
			unique = 1;
			LL_Rewind(procs);
			do {
				procinfo_type *p = LL_Get(procs);

				if ((p != NULL) && (0 == strcmp(p->name, procName))) {
					unique = 0;
					p->number++;
					p->totl += procData + procStk + procExe;
				}
			} while (LL_Next(procs) == 0);

			// If this is the first one by this name...
			if (unique) {
				procinfo_type *p = malloc(sizeof(procinfo_type));
				if (p == NULL) {
					perror("mem_top_screen: Error allocating process entry");
					break;
				}
				strcpy(p->name, procName);
				p->totl = procData + procStk + procExe;
				p->number = 1;
				// TODO:  Check for errors here?
				LL_Push(procs, (void *) p);
			}
		}
	}
	closedir(proc);

	return(TRUE);
}

int machine_get_smpload(load_type *result, int *numcpus)
{
	char *token;
	static load_type last_load[MAX_CPUS];
	load_type curr_load[MAX_CPUS];
	int ncpu = 0;
	int ret;
	unsigned long load_iowait, load_irq, load_softirq;

	reread(load_fd, "get_load");

	// Look for lines starting with "cpu0", "cpu1", etc.
	token = strtok(procbuf, "\n");
	while (token != NULL) {
		if ((strlen(token) > 3) && (!strncmp(token, "cpu", 3)) && isdigit(token[3])) {
			ret = sscanf(token, "cpu%*d %lu %lu %lu %lu %lu %lu %lu",
			             &curr_load[ncpu].user, &curr_load[ncpu].nice, &curr_load[ncpu].system,
			             &curr_load[ncpu].idle, &load_iowait, &load_irq, &load_softirq);

			if (ret >= 5)
				curr_load[ncpu].idle += load_iowait;
			if (ret >= 6)
				curr_load[ncpu].system += load_irq;
			if (ret >= 7)
				curr_load[ncpu].system += load_softirq;

			curr_load[ncpu].total = curr_load[ncpu].user + curr_load[ncpu].nice +
						curr_load[ncpu].system + curr_load[ncpu].idle;

			result[ncpu].total	= curr_load[ncpu].total - last_load[ncpu].total;
			result[ncpu].user	= curr_load[ncpu].user - last_load[ncpu].user;
			result[ncpu].nice	= curr_load[ncpu].nice - last_load[ncpu].nice;
			result[ncpu].system	= curr_load[ncpu].system - last_load[ncpu].system;
			result[ncpu].idle	= curr_load[ncpu].idle - last_load[ncpu].idle;

			// struct assignment is legal in C89
			last_load[ncpu] = curr_load[ncpu];

			// restrict # CPUs to min(*numcpus, MAX_CPUS)
			ncpu++;
			if ((ncpu >= *numcpus) || (ncpu >= MAX_CPUS))
				break;
		}
		token = strtok(NULL, "\n");
	}
	*numcpus = ncpu;

	return(TRUE);
}

int machine_get_uptime(double *up, double *idle)
{
	double local_up, local_idle;

	reread(uptime_fd, "get_uptime");
	sscanf(procbuf, "%lf %lf", &local_up, &local_idle);
	if (up != NULL)
			*up = local_up;
	if (idle != NULL)
			*idle = (local_up != 0)
				? 100 * local_idle / local_up
				: 100;

	return(TRUE);
}


/*************************************************************************
 * Read interface statistics from system and  store in the struct
 * passed as a pointer. If there are no errors, it returns 1. If errors,
 * returns 0.
 *************************************************************************
 */
int machine_get_iface_stats (IfaceInfo *interface)
{
	FILE *file;   /* file handler */
	char buffer[1024];  /* buffer to work with the file */
	static int first_time = 1;  /* is it first time we call this function? */
	char *ch_pointer = NULL;  /* pointer to where interface values are in file */

	/* Open the file in read-only mode and parse */

	if ((file = fopen("/proc/net/dev", "r")) != NULL) {
		/* Skip first 2 header lines of file */
		fgets(buffer, sizeof(buffer), file);
		fgets(buffer, sizeof(buffer), file);

		/* By default, treat interface as down */
		interface->status = down;

		/* Search iface_name and scan values */
		while ((fgets(buffer, sizeof(buffer), file) != NULL)) {
			if (strstr(buffer, interface->name)) {
				/* interface exists */
				interface->status = up; /* is up */
				interface->last_online = time(NULL); /* save actual time */

				/* search ':' and skip over it */
				ch_pointer = strchr(buffer, ':');
				ch_pointer++;

				/* Now ch_pointer points to values of iface_name */
				/* Scan values from here */
				sscanf(ch_pointer, "%lf %lf %*s %*s %*s %*s %*s %*s %lf %lf",
					&interface->rc_byte,
					&interface->rc_pkt,
					&interface->tr_byte,
					&interface->tr_pkt);

				/* if is the first time we call this function,
				 * old values are the same as new so we don't
				 * get big speeds when calculating
				 */
				if (first_time) {
					interface->rc_byte_old = interface->rc_byte;
					interface->tr_byte_old = interface->tr_byte;
					interface->rc_pkt_old = interface->rc_pkt;
					interface->tr_pkt_old = interface->tr_pkt;
					first_time = 0;  /* now it isn't first time */
				}
			}
		} /* while */

		fclose(file);  /* close file */
		return 1;  /* everything went OK */

	}
	else {  /* error when opening the file */
		perror("Error: Could not open DEVFILE");
		return 0; /* something went wrong */
	}
} /* get_iface_stats() */

#endif /* linux */
