/*
 * Copyright (c) 2003  Rudolf Cejka
 *
 * $Id: mrtginfo.c,v 1.4 2004/11/18 14:32:55 cejkar Exp cejkar $
 */

#include <sys/param.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <sys/user.h>
#include <net/if.h>
#include <net/if_var.h>
#include <err.h>
#include <fcntl.h>
#include <kvm.h>
#include <limits.h>
#include <nlist.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define	MAXVALLEN	(80)

enum DIR { D_IN, D_OUT };

void usage(void)
{
	fprintf(stderr, "Usage: %s [-s] [-i[clnp]...] [-o[clnp]...]\n"
			"       c<text>        constant text\n"
			"       l[012]         load average for 1/5/15 mins\n"
			"       n<iface>       network statistics\n"
			"       p<proc>:<decr> processes with decrement\n",
	    getprogname());
	exit(1);
}

struct nlist nl[] = {
	{ "_ifnet" },
	{ NULL }
};

quad_t netstat(char *p, enum DIR dir)
{
	kvm_t			*kvmd;
	char			errbuf[_POSIX2_LINE_MAX];
	struct ifnethead	ifnethead;
	struct ifnet		ifnet;
#if defined(__FreeBSD_version) && (__FreeBSD_version < 501113)
	char			ifname[IFNAMSIZ];
#endif
	char			name[IFNAMSIZ + 16];
	u_long			ifnetaddr;

	if ((kvmd = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, errbuf)) == NULL)
		errx(1, "kvm_openfiles(): %s", errbuf);
	if (kvm_nlist(kvmd, nl) < 0)
		errx(1, "kvm_nlist(): %s", kvm_geterr(kvmd));
	if (nl[0].n_type == 0 || nl[0].n_value == 0)
		errx(1, "kvm_nlist(): no namelist");
	if (kvm_read(kvmd, nl[0].n_value, &ifnethead, sizeof(ifnethead))
	    != sizeof(ifnethead))
		errx(1, "kvm_read(): %s", kvm_geterr(kvmd));
	ifnetaddr = (u_long)TAILQ_FIRST(&ifnethead);
	while (ifnetaddr != (u_long)NULL) {
		if (kvm_read(kvmd, ifnetaddr, (char *)&ifnet, sizeof(ifnet))
		    != sizeof(ifnet))
			errx(1, "kvm_read(): %s", kvm_geterr(kvmd));
#if defined(__FreeBSD_version) && (__FreeBSD_version >= 501113)
		strlcpy(name, ifnet.if_xname, sizeof(name));
#else
		if (kvm_read(kvmd, (u_long)ifnet.if_name, ifname,
		    sizeof(ifname)) != sizeof(ifname))
			errx(1, "kvm_read(): %s", kvm_geterr(kvmd));
		ifname[IFNAMSIZ - 1] = '\0';
		snprintf(name, sizeof(name), "%s%d", ifname, ifnet.if_unit);
#endif
		if (strcmp(p, name) == 0) {
			kvm_close(kvmd);
			return (dir == D_IN)
			    ? ifnet.if_ibytes : ifnet.if_obytes;
		}
		ifnetaddr = (u_long)TAILQ_NEXT(&ifnet, if_link);
	}
	errx(1, "nestat(): Interface %s not found", p);
	/* NOTREACHED */
}

quad_t ps(char *p)
{
	kvm_t			*kvmd;
	char			errbuf[_POSIX2_LINE_MAX];
	struct kinfo_proc	*kp;
	int			nentries;
	quad_t			count, subtract;
	char			*q, *r;

	subtract = 0;
	if ((q = strchr(p, ':')) != NULL) {
		*q = '\0';
		subtract = strtoq(q + 1, &r, 0);
		if (*r != '\0')
			usage();
	}
	if ((kvmd = kvm_openfiles(NULL, NULL, NULL, O_RDONLY, errbuf)) == NULL)
		errx(1, "kvm_openfiles(): %s", errbuf);
	if ((kp = kvm_getprocs(kvmd, KERN_PROC_ALL, 0, &nentries)) == NULL)
		errx(1, "kvm_getprocs(): %s", kvm_geterr(kvmd));
	count = 0;
	while (nentries-- > 0) {
#if defined(__FreeBSD_version) && (__FreeBSD_version >= 500000)
		if (strcmp(p, kp->ki_comm) == 0)
#else
		if (strcmp(p, kp->kp_proc.p_comm) == 0)
#endif
			count++;
		kp++;
	}
	return (count - subtract);
}

void getval(char *s, int n, enum DIR dir, char t, char *p)
{
	quad_t		i;
	double		d[3];
	char		*q, *r;

	switch (t) {
	case 'c':
		/* Copy */
		strlcpy(s, p, n);
		break;
	case 'l':
		/* Load average */
		i = strtoq(p, &r, 0);
		if (*r != '\0' || i < 0 || i > 2)
			usage();
		if (getloadavg(d, 3) != 3)
			err(1, "getloadavg()");
		snprintf(s, n, "%qd", (quad_t)(d[i] * 100));
		break;
	case 'n':
		/* Network load */
		snprintf(s, n, "%qd", netstat(p, dir));
		break;
	case 'p':
		/* Number of processes */
		i = 0;
		if ((q = strchr(p, ':')) != NULL) {
			*q = '\0';
			i = strtoq(q + 1, &r, 0);
			if (*r != '\0')
				usage();
		}
		snprintf(s, n, "%qd", ps(p) - i);
		break;
	default:
		usage();
		break;
	}
}

int main(int argc, char *argv[])
{
	int		ch;
	int		sflag;
	char		itype, otype;
	char		*iparam, *oparam;
	char		sin[MAXVALLEN];
	char		sout[MAXVALLEN];
	char		suptime[MAXVALLEN];
	int		mib[2];
	struct timeval	boottime;
	size_t		size;
	time_t		now;
	time_t		uptime;
	int		days, hrs, mins, secs;
	char		sdays[MAXVALLEN];
	char		shostname[MAXHOSTNAMELEN];
	char		*p;

	/* Parameter parsing */

	sflag = 0;
	itype = '?';
	otype = '?';
	iparam = NULL;
	oparam = NULL;
	while ((ch = getopt(argc, argv, "si:o:")) != -1)
		switch (ch) {
		case 's':
			sflag = 1;
			break;
		case 'i':
			if (*optarg != '\0') {
				itype = *optarg;
				iparam = strdup(optarg + 1);
			} else {
				usage();
			}
			break;
		case 'o':
			if (*optarg != '\0') {
				otype = *optarg;
				oparam = strdup(optarg + 1);
			} else {
				usage();
			}
			break;
		case '?':
		default:
			usage();
		}
	argc -= optind;
	argv += optind;
	if (argc > 0 || strchr("clnp", itype) == NULL
	    || strchr("clnp", otype) == NULL)
		usage();

	/* Input and output */

	getval(sin, MAXVALLEN, D_IN, itype, iparam);
	getval(sout, MAXVALLEN, D_OUT, otype, oparam);

	/* Uptime */

	mib[0] = CTL_KERN;
	mib[1] = KERN_BOOTTIME;
	size = sizeof(boottime);
	time(&now);
	if (sysctl(mib, 2, &boottime, &size, NULL, 0) != 0)
		err(1, "sysctl(kern.boottime)");
	uptime = now - boottime.tv_sec;
	if (uptime > 60)
		uptime += 30;
	days = uptime / 86400;
	uptime %= 86400;
	hrs = uptime / 3600;
	uptime %= 3600;
	mins = uptime / 60;
	secs = uptime % 60;
	if (days > 0)
		snprintf(sdays, sizeof(sdays),
		    "%d day%s, ", days, days > 1 ? "s" : "");
	else
		sdays[0] = '\0';
	if (hrs > 0)
		snprintf(suptime, sizeof(suptime),
		    "%s%d:%02d", sdays, hrs, mins);
	else if (mins > 0)
		snprintf(suptime, sizeof(suptime),
		    "%s%d min%s", sdays, mins, mins > 1 ? "s" : "");
	else
		snprintf(suptime, sizeof(suptime),
		    "%s%d sec%s", sdays, secs, secs > 1 ? "s" : "");

	/* Hostname */

	if (gethostname(shostname, sizeof(shostname)) != 0)
		err(1, "gethostname()");
	if (sflag && (p = strchr(shostname, '.')) != NULL)
		*p = '\0';

	/*
	 * Output:
	 *   Line 1: First variable (incoming bytes count)
	 *   Line 2: Second variable (outgoing bytes count)
	 *   Line 3: Uptime
	 *   Line 4: Hostname
	 */
	printf("%s\n%s\n%s\n%s\n", sin, sout, suptime, shostname);
	return 0;
}

