/*
 * Copyright (c) 1999-2003  Albert Dorofeev <albert@tigr.net>
 * For the updates see http://www.tigr.net/
 *
 * This software is distributed under GPL. For details see LICENSE file.
 */

/* kvm/uvm use (BSD port) code:
 * Copyright (c) 2000  Scott Aaron Bamford <sab@zeekuschris.com>
 * BSD additions for for this code are licensed BSD style.
 * All other code and the project as a whole is under the GPL.
 * For details see LICENSE.
 * BSD systems dont have /proc/meminfo. it is still posible to get the disired
 * information from the uvm/kvm functions. Linux machines shouldn't have
 * <uvm/vum_extern.h> so should use the /proc/meminfo way. BSD machines (NetBSD
 * i use, but maybe others?) dont have /proc/meminfo so we instead get our info
 * using kvm/uvm.
 */

/*
 * The FreeBSD port is
 * Copyright (c) 2000 Andre Yelistratov <andre@express.ru>
 */

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#include "state.h"
#include "config.h"

extern struct salmon_state state;

#if defined(__FreeBSD__)
/*
 * The FreeBSD port to versions <=400000 had become so
 * obviously broken that I decided to remove it all
 * together.  Salmon is mostly based on asmem which is
 * probably available from tigr.net if nowhere else.
 * If you want to port salmon to FreeBSD<=400000, you
 * may want to start by looking at the read_mem.c
 * that's in asmem.
 */

#include <kvm.h>
#include <sys/vmmeter.h>
#include <sys/param.h>
#include <sys/sysctl.h>
#include <sys/user.h>

#define SWAP_DEVICES	1		/* we only want the grand total */

kvm_t   *kd;

void open_meminfo() {

	kd = kvm_open(NULL, NULL, NULL, O_RDONLY, "kvm_open");
	if (getgid()>getegid())
		setgid(getgid());
}

void close_meminfo() {

	if (kd>0)
		kvm_close(kd);
}

void read_meminfo() {

	struct kvm_swap kswap[SWAP_DEVICES];
	struct timeval boottime;
	struct vmtotal vm_t;
	struct kinfo_proc kp, *t, *u;
	int mib[3], i, j;
	int swap_total;
	int swap_used;
	int pagesize;
	static long cp_time[5], cp_time_last[4];
	double avenrun[3];
	float tmp;
	time_t current_time;
	size_t len;
	fixpt_t ccpu;

	pagesize = getpagesize();
	current_time = time(NULL);
	/*
	 * Only change values for selected options.
	 *
	 * This reduces cpu cycles somewhat and in
	 * some cases it can reduce them a lot.
	 */
	if (!strlen(state.osname) && (state.opt.ostype || state.opt.version)) {
		/* we only need to do this once if at all */
		len = sizeof(state.osname);
		mib[0] = CTL_KERN;
		mib[1] = KERN_OSTYPE;
		if (sysctl(mib, 2, &state.osname, &len, NULL, 0) == -1)
			sprintf(state.osname, "%10s", "Unknown|OS");
		len = sizeof(state.osvers);
		mib[0] = CTL_KERN;
		mib[1] = KERN_OSRELEASE;
		if (sysctl(mib, 2, &state.osvers, &len, NULL, 0) == -1)
			sprintf(state.osvers, "%10s", "Unknown|VS");
	}
	if (state.opt.mem) {
		len = sizeof(ccpu);
		sysctlbyname("vm.stats.vm.v_page_count", &i, &len, NULL, 0);
		state.fresh.total = i * pagesize;
		sysctlbyname("vm.stats.vm.v_free_count", &i, &len, NULL, 0);
		state.fresh.free = i * pagesize;
		state.fresh.used = state.fresh.total - state.fresh.free;
	}
	if (state.opt.swap) {
		swap_total = 0;
		swap_used = 0;
		if (kd>0 && !(kvm_getswapinfo(kd, kswap, SWAP_DEVICES, 0))) {
			swap_total = kswap[0].ksw_total;
			swap_used = kswap[0].ksw_used;
		}
		state.fresh.swap_total = swap_total * pagesize;
		state.fresh.swap_used = swap_used * pagesize;
		state.fresh.swap_free = (swap_total - swap_used) * pagesize;
	}
	if (state.opt.share) {
		len = sizeof(vm_t);
		mib[0] = CTL_VM;
		mib[1] = VM_METER;
		if (sysctl(mib, 2, &vm_t, &len, NULL, 0)<0) {
			state.fresh.shared = 0;
		} else {
			state.fresh.shared = vm_t.t_rmshr + vm_t.t_armshr + vm_t.t_vmshr + vm_t.t_avmshr;
		}
	}
	if (state.opt.buffer) {
		len = sizeof(ccpu);
		sysctlbyname("vfs.bufspace", &i, &len, NULL, 0);
		state.fresh.buffers = i<0 ? 0 : i;
	}
	if (state.opt.cache) {
		len = sizeof(ccpu);
		sysctlbyname("vm.stats.vm.v_cache_count", &i, &len, NULL, 0);
		state.fresh.cached = i<0 ? 0 : i * pagesize;
	}
	if (state.opt.upt) {
		len = sizeof(boottime);
		mib[0] = CTL_KERN;
		mib[1] = KERN_BOOTTIME;
		if (sysctl(mib, 2, &boottime, &len, NULL, 0) != -1 && boottime.tv_sec != 0) {
			state.fresh.uptime = (current_time - boottime.tv_sec) / 60;
		} else {
			state.fresh.uptime = 0;
		}
	}
	if (state.opt.load[0] || state.opt.load[1] || state.opt.load[2]) {
		/* we either get them all or we get -1 */
		if (getloadavg(avenrun, sizeof(avenrun) / sizeof(avenrun[0]))>0) {
			for (i=0; i<3; ++i) {
				if (state.opt.load[i])
					state.fresh.loadavg[i] = avenrun[i];
			}
		}
	}
	if (state.opt.proc) {
		mib[0] = CTL_KERN;
		mib[1] = KERN_PROC;
		mib[2] = KERN_PROC_ALL;
		state.fresh.procs[0] = 0;
		state.fresh.procs[1] = 0;
		if(!sysctl(mib, 3, NULL, &len, NULL, 0)) {
			t = (struct kinfo_proc *)malloc(len);
			if(!sysctl(mib, 3, t, &len, NULL, 0)) {
				j = len / sizeof(struct kinfo_proc);
				state.fresh.procs[0] = j;
				i = 0;
				while(i<j) {
					u = (t + j - i - 1);
					if (u->kp_proc.p_stat == 2)	/* running */
						state.fresh.procs[1]++;
					i++;
				}
				free(t);
			}
		}
	}
	if (state.opt.states[0] || state.opt.states[1] || state.opt.states[2] || state.opt.states[3]) {
		len = sizeof(cp_time);
		sysctlbyname("kern.cp_time", &cp_time, &len, NULL, 0);
		if (cp_time[0] == -1) {
			for (i=1; i<4; ++i)
				state.fresh.times[i] = 0.0;
		} else {
			/* combine interrupt and system times */
			cp_time[2] += cp_time[3];
			cp_time[3] = cp_time[4];
			tmp = 0;
			for (i=0; i<4; ++i) {
				state.fresh.times[i] = cp_time[i] - cp_time_last[i];
				tmp += state.fresh.times[i];
				cp_time_last[i] = cp_time[i];
			}
			for (i=0; i<4; ++i)
				state.fresh.times[i] /= tmp;
		}
	}
	if (state.opt.lct || state.opt.gmt) {
		if (state.refresh>1)		/* only change time once a minute */
			current_time = current_time - (current_time % 60);
		state.fresh.cur_time = current_time;
	} else if (state.opt.date || state.opt.udate || state.opt.host
			|| state.opt.ostype || state.opt.version || state.opt.luna) {
		state.fresh.cur_time = current_time / 600;
	}
}

#else	/* Linux */

/*
 * Parts of the Linux read_meminfo() function are adapted from a file
 * named sysinfo.c that contained the following copyright information.
 * Other parts are taken from so many different BSD and GPL sources
 * that I couldn't even begin to count them.  Let alone find them again.
 */
/***********************************************************************\
*   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.    *
\***********************************************************************/

#define PROC_UPT	"/proc/uptime"		/* up time */
#define PROC_LOAD	"/proc/loadavg"		/* load averages */
#define PROC_STAT	"/proc/stat"		/* user, nice, system, idle */
#define PROC_VERS	"/proc/version"		/* os type, kernel version */
#define PROC_MEM	"/proc/meminfo"		/* mem, swap, shared, buffers, cached */
#define BUF_SIZE	1024

int upt_fd;
int load_fd;
int stat_fd;
int mem_fd;

void open_meminfo() {

	upt_fd = open(PROC_UPT, O_RDONLY);
	load_fd = open(PROC_LOAD, O_RDONLY);
	stat_fd = open(PROC_STAT, O_RDONLY);
	mem_fd = open(PROC_MEM, O_RDONLY);
}

void close_meminfo() {

	if (upt_fd>0)
		close(upt_fd);
	if (load_fd>0)
		close(load_fd);
	if (stat_fd>0)
		close(stat_fd);
	if (mem_fd>0)
		close(mem_fd);
}

void file_to_buf(int fd, char buf[BUF_SIZE]) {

	int n = 0;

	if (fd>0) {
		lseek(fd, 0L, SEEK_SET);
		if ((n = read(fd, buf, (size_t)(BUF_SIZE - 1)))<0) {
			close(fd);
			fd = -1;
			n = 0;
		}
	}
	buf[n] = '\0';
}

void read_meminfo() {

	double uptime;
	float loads[3];
	static unsigned long s_times[4];
	unsigned long times[4], tmp;
	char buf[BUF_SIZE];
	int i, j, k;
	time_t current_time;

	current_time = time(NULL);
	/*
	 * Only change values for selected options.
	 *
	 * This reduces cpu cycles somewhat and in
	 * some cases it can reduce them a lot.
	 */
	/* Damn the penguins!  Full speed backwards! */
	if (!strlen(state.osname) && (state.opt.ostype || state.opt.version)) {
		/* we only need to do this once if at all */
		k = open(PROC_VERS, O_RDONLY);
		file_to_buf(k, buf);
		i = sscanf(buf, "%s version %s", &state.osname, &state.osvers);
		if (i<2)
			sprintf(state.osvers, "%10s", "Unknown|VS");
		if (i<1)
			sprintf(state.osname, "%10s", "Unknown|OS");
		if (k>0)
			close(k);
	}
	if (state.opt.mem || state.opt.swap || state.opt.share || state.opt.buffer || state.opt.cache) {
		file_to_buf(mem_fd, buf);
		if (state.opt.mem) {
			state.fresh.total = 0;
			state.fresh.free = 0;
			if (strstr(buf, "MemTotal:") && sscanf(strstr(buf, "MemTotal:"), "%*s %d", &i)==1)
				state.fresh.total = i * 1024;
			if (strstr(buf, "MemFree:") && sscanf(strstr(buf, "MemFree:"), "%*s %d", &i)==1)
				state.fresh.free = i * 1024;
			state.fresh.used = state.fresh.total - state.fresh.free;
		}
		if (state.opt.swap) {
			state.fresh.swap_total = 0;
			state.fresh.swap_free = 0;
			if (strstr(buf, "SwapTotal:") && sscanf(strstr(buf, "SwapTotal:"), "%*s %d", &i)==1)
				state.fresh.swap_total = i * 1024;
			if (strstr(buf, "SwapFree:") && sscanf(strstr(buf, "SwapFree:"), "%*s %d", &i)==1)
				state.fresh.swap_free = i * 1024;
			state.fresh.swap_used = state.fresh.swap_total - state.fresh.swap_free;
		}
		if (state.opt.share) {
			state.fresh.shared = 0;
			if (strstr(buf, "Shared:") && sscanf(strstr(buf, "Shared:"), "%*s %d", &i)==1)
				state.fresh.shared = i * 1024;
		}
		if (state.opt.buffer) {
			state.fresh.buffers = 0;
			if (strstr(buf, "Buffers:") && sscanf(strstr(buf, "Buffers:"), "%*s %d", &i)==1)
				state.fresh.buffers = i * 1024;
		}
		if (state.opt.cache) {
			state.fresh.cached = 0;
			if (strstr(buf, "Cached:") && sscanf(strstr(buf, "Cached:"), "%*s %d", &i)==1)
				state.fresh.cached = i * 1024;
		}
	}
	if (state.opt.upt) {
		state.fresh.uptime = 0;
		file_to_buf(upt_fd, buf);
		if (sscanf(buf, "%lf", &uptime)==1)
			state.fresh.uptime = uptime / 60;
	}
	if (state.opt.load[0] || state.opt.load[1] || state.opt.load[2] || state.opt.proc) {
		state.fresh.loadavg[0] = 0;
		state.fresh.loadavg[1] = 0;
		state.fresh.loadavg[2] = 0;
		state.fresh.procs[0] = 0;
		state.fresh.procs[1] = 0;
		file_to_buf(load_fd, buf);
		if (sscanf(buf, "%f %f %f %i/%i", &loads[0], &loads[1], &loads[2], &j, &i)==5) {
			for (k=0; k<3; ++k) {
				if (state.opt.load[k])
					state.fresh.loadavg[k] = loads[k];
			}
			if (state.opt.proc) {
				state.fresh.procs[0] = i;
				state.fresh.procs[1] = j;
			}
		}
	}
	if (state.opt.states[0] || state.opt.states[1] || state.opt.states[2] || state.opt.states[3]) {
		for (i=0; i<4; ++i)
			state.fresh.times[i] = 0;
		file_to_buf(stat_fd, buf);
		if (sscanf(buf, "cpu %lu %lu %lu %lu", &times[0], &times[1], &times[2], &times[3])==4) {
			tmp = 0;
			for (i=0; i<4; ++i)
				tmp += (times[i] - s_times[i]);
			for (i=0; i<4; ++i) {
				state.fresh.times[i] = (float)(times[i] - s_times[i]) / (float)tmp;
				s_times[i] = times[i];
			}
		}
	}
	if (state.opt.lct || state.opt.gmt) {
		if (state.refresh>1)
			current_time = current_time - (current_time % 60);
		state.fresh.cur_time = current_time;
	} else if (state.opt.date || state.opt.udate || state.opt.host
			|| state.opt.ostype || state.opt.version || state.opt.luna) {
		state.fresh.cur_time = current_time / 600;
	}
}

#endif	/* defined(__FreeBSD__) */

