/*
 * |-----------------------------------------------------------|
 * | Copyright (c) 1991, 1990 MIPS Computer Systems, Inc.      |
 * | All Rights Reserved                                       |
 * |-----------------------------------------------------------|
 * |          Restricted Rights Legend                         |
 * | Use, duplication, or disclosure by the Government is      |
 * | subject to restrictions as set forth in                   |
 * | subparagraph (c)(1)(ii) of the Rights in Technical        |
 * | Data and Computer Software Clause of DFARS 252.227-7013.  |
 * |         MIPS Computer Systems, Inc.                       |
 * |         950 DeGuigne Avenue                               |
 * |         Sunnyvale, California 94088-3650, USA             |
 * |-----------------------------------------------------------|
 */
#ident	"$Header: rpc.rstatd.c,v 1.4.2.1 92/06/24 13:51:11 marker Exp $"
#ifndef lint
static char sccsid[] = 	"@(#)rpc.rstatd.c	1.1 88/03/07 4.0NFSSRC Copyr 1988 Sun Micro";
#endif

/*
 * Copyright (c) 1984 by Sun Microsystems, Inc.
 */

/* 
 * rstat demon:  called from inet
 *
 */

#include <signal.h>
#include <stdio.h>
#include <rpc/rpc.h>
#include <sys/socket.h>
#include <nlist.h>
#ifdef _RISCOS
#include <sysv/sys/sysinfo.h>
#include <sysv/sys/var.h>
#include <sys/fixpoint.h>
#include <math.h>
extern char *malloc();
extern char *calloc();
#include <sysv/sys/elog.h>
#include <sysv/sys/sysmips.h>
#include <sysv/sys.s>
#else _RISCOS
#include <sys/dk.h>
#endif _RISCOS
#include <sys/errno.h>
#include <sys/vmmeter.h>
#include <net/if.h>
#include <sys/time.h>
#include <rpcsvc/rstat.h>
#include <syslog.h>

#ifdef _RISCOS
struct nlist nl[] = {
#define	X_SUM		0
	{ "sum" },
#define	X_IFNET		1
	{ "ifnet" },
#define	X_BOOTTIME	2
	{ "boottime" },
#define	X_AVENRUN	3
	{ "avenrun" },
#define X_SYSINFO	4
	{ "sysinfo" },
#define X_V		5
	{ "v" },
#define X_CPTIME	6
	{ "cp_time" },
#define X_ID_STRING	7
	{ "id_string" },
	{ "" },
};

struct var	v;
struct sysinfo	sysinfo;

#ifndef HZ
#define HZ 100
#endif HZ
#define RSTAT_HZ 60

#else _RISCOS
struct nlist nl[] = {
#define	X_CPTIME	0
	{ "_cp_time" },
#define	X_SUM		1
	{ "_sum" },
#define	X_IFNET		2
	{ "_ifnet" },
#define	X_DKXFER	3
	{ "_dk_xfer" },
#define	X_BOOTTIME	4
	{ "_boottime" },
#define	X_AVENRUN	5
	{ "_avenrun" },
#define X_HZ		6
	{ "_hz" },
	"",
};
#endif _RISCOS
int kmem;
int firstifnet, numintfs;	/* chain of ethernet interfaces */
int stats_service();

int sincelastreq = 0;		/* number of alarms since last request */
#define CLOSEDOWN 20		/* how long to wait before do_exiting */

union {
    struct stats s1;
    struct statsswtch s2;
    struct statstime s3;
} stats;

int updatestat();
extern int errno;

#ifndef FSCALE
#define FSCALE (1 << 8)
#endif

complain(s)
	char *s;
{
#ifdef DEBUG
	/* use stderr */
	fprintf(stderr, "rstatd: %s.\n", s);
	fflush(stderr);
#else
	static int initted;

	if (!initted) {
		openlog("rstatd", LOG_PID, LOG_DAEMON);
		initted = 1;
	}
	syslog(LOG_ERR, s);
#endif
}

main(argc, argv)
	char **argv;
{
	SVCXPRT *transp;
	struct sockaddr_in addr;
	int len = sizeof(struct sockaddr_in);
	int readfds, port, readfdstmp;


#ifdef DEBUG
	{
	int s;
	struct sockaddr_in addr;
	int len = sizeof(struct sockaddr_in);
	
	if ((s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
		perror("inet: socket");
		return -1;
	}
	if (bind(s, &addr, sizeof(addr)) < 0) {
		perror("bind");
		return -1;
	}
	if (getsockname(s, &addr, &len) != 0) {
		perror("inet: getsockname");
		(void)close(s);
		return -1;
	}
	pmap_unset(RSTATPROG, RSTATVERS_ORIG);
	pmap_set(RSTATPROG, RSTATVERS_ORIG, IPPROTO_UDP,ntohs(addr.sin_port));
	pmap_unset(RSTATPROG, RSTATVERS_SWTCH);
	pmap_set(RSTATPROG, RSTATVERS_SWTCH,IPPROTO_UDP,ntohs(addr.sin_port));
	pmap_unset(RSTATPROG, RSTATVERS_TIME);
	pmap_set(RSTATPROG, RSTATVERS_TIME,IPPROTO_UDP,ntohs(addr.sin_port));
	if (dup2(s, 0) < 0) {
		perror("dup2");
		do_exit(1);
	}
	}
#endif	
	if (getsockname(0, &addr, &len) != 0) {
		complain("getsockname: error");
		do_exit(1);
	}
	if ((transp = svcudp_bufcreate(0, RPCSMALLMSGSIZE, RPCSMALLMSGSIZE))
	    == NULL) {
		complain("svc_rpc_udp_create: error");
		do_exit(1);
	}
	if (!svc_register(transp,RSTATPROG,RSTATVERS_ORIG,stats_service, 0)) {
		complain("svc_rpc_register: error");
		do_exit(1);
	}
	if (!svc_register(transp,RSTATPROG,RSTATVERS_SWTCH,stats_service,0)) {
		complain("svc_rpc_register: error");
		do_exit(1);
	}
	if (!svc_register(transp,RSTATPROG,RSTATVERS_TIME,stats_service,0)) {
		complain("svc_rpc_register: error");
		do_exit(1);
	}
	setup();
	updatestat();
	alarm(1);
	signal(SIGALRM, updatestat);
	svc_run();
	complain("svc_run should never return");
}

static int
stats_service(reqp, transp)
	 struct svc_req  *reqp;
	 SVCXPRT  *transp;
{
	int have;
	
#ifdef DEBUG
	complain("entering stats_service");
#endif
	switch (reqp->rq_proc) {
		case RSTATPROC_STATS:
			sincelastreq = 0;
			if (reqp->rq_vers == RSTATVERS_ORIG) {
				if (svc_sendreply(transp, xdr_stats,
				    &stats.s1, TRUE) == FALSE) {
					complain("err: svc_rpc_send_results");
					do_exit(1);
				}
				return;
			}
			if (reqp->rq_vers == RSTATVERS_SWTCH) {
				if (svc_sendreply(transp, xdr_statsswtch,
				    &stats.s2, TRUE) == FALSE) {
					complain("err: svc_rpc_send_results");
					do_exit(1);
				    }
				return;
			}
			if (reqp->rq_vers == RSTATVERS_TIME) {
				if (svc_sendreply(transp, xdr_statstime,
				    &stats.s3, TRUE) == FALSE) {
					complain("err: svc_rpc_send_results");
					do_exit(1);
				    }
				return;
			}
		case RSTATPROC_HAVEDISK:
			have = havedisk();
			if (svc_sendreply(transp,xdr_long, &have, TRUE) == 0){
			    complain("err: svc_sendreply");
			    do_exit(1);
			}
			return;
		case 0:
			if (svc_sendreply(transp, xdr_void, 0, TRUE)
			    == FALSE) {
				complain("err: svc_rpc_send_results");
				do_exit(1);
			    }
			return;
		default: 
			svcerr_noproc(transp);
			return;
		}
}

updatestat()
{
	int off, i, hz;
	struct vmmeter sum;
	struct ifnet ifnet;
	double avrun[3];
	struct timeval tm, btm;
	
#ifdef DEBUG
	complain("entering updatestat");
#endif
	if (sincelastreq >= CLOSEDOWN) {
#ifdef DEBUG
	complain("about to closedown");
#endif
		do_exit(0);
	}
	sincelastreq++;
#ifdef _RISCOS
	hz = HZ;
	syscall (SYS_sysmips, MIPS_GET_SYSINFO, &sysinfo, 0);
	if (nl[X_CPTIME].n_type != 0) {
		lseek(kmem, (long)nl[X_CPTIME].n_value, 0);
		read(kmem,stats.s1.cp_time, sizeof stats.s1.cp_time);
	} else {
		stats.s1.cp_time[CP_USER] = sysinfo.cpu[CPU_USER];
		stats.s1.cp_time[CP_NICE] = 0;
		stats.s1.cp_time[CP_SYS] = sysinfo.cpu[CPU_KERNEL];
		stats.s1.cp_time[CP_IDLE] = sysinfo.cpu[CPU_IDLE]
				+ sysinfo.cpu[CPU_WAIT]
				+ sysinfo.cpu[CPU_SXBRK];
	};
#else _RISCOS
	if (lseek(kmem, (long)nl[X_HZ].n_value, 0) == -1) {
		complain("can't seek in kmem");
		do_exit(1);
	}
	if (read(kmem, &hz, sizeof hz) != sizeof hz) {
		complain("can't read hz from kmem");
		do_exit(1);
	}
	if (lseek(kmem, (long)nl[X_CPTIME].n_value, 0) == -1) {
		complain("can't seek in kmem");
		do_exit(1);
	}
 	if (read(kmem, stats.s1.cp_time, sizeof (stats.s1.cp_time))
	    != sizeof (stats.s1.cp_time)) {
		complain("can't read cp_time from kmem");
		do_exit(1);
	}
#endif _RISCOS
	if (lseek(kmem, (long)nl[X_AVENRUN].n_value, 0) ==-1) {
		complain("can't seek in kmem");
		do_exit(1);
	}
#ifdef vax
 	if (read(kmem, avrun, sizeof (avrun)) != sizeof (avrun)) {
		complain("can't read avenrun from kmem");
		do_exit(1);
	}
	stats.s2.avenrun[0] = avrun[0] * FSCALE;
	stats.s2.avenrun[1] = avrun[1] * FSCALE;
	stats.s2.avenrun[2] = avrun[2] * FSCALE;
#endif
#ifdef sun
 	if (read(kmem, stats.s2.avenrun, sizeof (stats.s2.avenrun))
	    != sizeof (stats.s2.avenrun)) {
		complain("can't read avenrun from kmem");
		do_exit(1);
	}
#endif
#ifdef _RISCOS
	{
		fix	avenrun_tmp[3];
	 	if (read(kmem, avenrun_tmp, sizeof (avenrun_tmp)) != sizeof (avenrun_tmp)) {
			complain("can't read avenrun from kmem");
			do_exit(1);
		}
		stats.s2.avenrun[0] = FIX_TO_DBL(avenrun_tmp[0]) * FSCALE;
		stats.s2.avenrun[1] = FIX_TO_DBL(avenrun_tmp[1]) * FSCALE;
		stats.s2.avenrun[2] = FIX_TO_DBL(avenrun_tmp[2]) * FSCALE;
	}
#endif _RISCOS
	if (lseek(kmem, (long)nl[X_BOOTTIME].n_value, 0) == -1) {
		complain("can't seek in kmem");
		do_exit(1);
	}
 	if (read(kmem, &btm, sizeof (stats.s2.boottime))
	    != sizeof (stats.s2.boottime)) {
		complain("can't read boottime from kmem");
		do_exit(1);
	}
	stats.s2.boottime = btm;


#ifdef DEBUG
	{
	char logline[64];

	sprintf(logline, "%d %d %d %d", stats.s1.cp_time[0],
            stats.s1.cp_time[1], stats.s1.cp_time[2], stats.s1.cp_time[3]);
	complain(logline);
	}
#endif

	if (lseek(kmem, (long)nl[X_SUM].n_value, 0) ==-1) {
		complain("can't seek in kmem");
		do_exit(1);
	}
 	if (read(kmem, &sum, sizeof sum) != sizeof sum) {
		complain("can't read sum from kmem");
		do_exit(1);
	}
	stats.s1.v_pgpgin = sum.v_pgpgin;
	stats.s1.v_pgpgout = sum.v_pgpgout;
	stats.s1.v_pswpin = sum.v_pswpin;
	stats.s1.v_pswpout = sum.v_pswpout;
	stats.s1.v_intr = sum.v_intr;
	gettimeofday(&tm, NULL);
	stats.s1.v_intr -= hz*(tm.tv_sec - btm.tv_sec) +
	    hz*(tm.tv_usec - btm.tv_usec)/1000000;
	stats.s2.v_swtch = sum.v_swtch;

#ifdef _RISCOS
	{
		int	i;
		int	ndrives;
		int	xfer_size;
		struct	iotime *xfer;

		if (syscall (SYS_sysmips, MIPS_GET_NUMDISKS, &ndrives, 0) == -1) {
			complain("Unable to get the number of disks");
			do_exit(1);
		}
		xfer_size = sizeof(struct iotime *) * ndrives;
		xfer = (struct iotime *) malloc(xfer_size);
		if (syscall (SYS_sysmips, MIPS_GET_DISKSTATS, xfer, 0) == -1) {
			complain("Unable to get disk stats");
			do_exit(1);
		}
		bzero(stats.s1.dk_xfer,sizeof(stats.s1.dk_xfer));
		for (i=0; i < ndrives && i < DK_NDRIVE; i++)
			stats.s1.dk_xfer[i] = xfer[i].io_cnt;
		free((char *) xfer);
      }
#else _RISCOS
	if (lseek(kmem, (long)nl[X_DKXFER].n_value, 0) == -1) {
		complain("can't seek in kmem");
		do_exit(1);
	}
 	if (read(kmem, stats.s1.dk_xfer, sizeof (stats.s1.dk_xfer))
	    != sizeof (stats.s1.dk_xfer)) {
		complain("can't read dk_xfer from kmem");
		do_exit(1);
	}
#endif _RISCOS

	stats.s1.if_ipackets = 0;
	stats.s1.if_opackets = 0;
	stats.s1.if_ierrors = 0;
	stats.s1.if_oerrors = 0;
	stats.s1.if_collisions = 0;
	for (off = firstifnet, i = 0; off && i < numintfs; i++) {
		if (lseek(kmem, off, 0) == -1) {
			complain("can't seek in kmem");
			do_exit(1);
		}
		if (read(kmem, &ifnet, sizeof ifnet) != sizeof ifnet) {
			complain("can't read ifnet from kmem");
			do_exit(1);
		}
		stats.s1.if_ipackets += ifnet.if_ipackets;
		stats.s1.if_opackets += ifnet.if_opackets;
		stats.s1.if_ierrors += ifnet.if_ierrors;
		stats.s1.if_oerrors += ifnet.if_oerrors;
		stats.s1.if_collisions += ifnet.if_collisions;
		off = (int) ifnet.if_next;
	}
	gettimeofday(&stats.s3.curtime, NULL);
	alarm(1);
}

static 
setup()
{
	struct ifnet ifnet;
	int off, *ip;
	
#ifdef _RISCOS
	nlist("/unix",nl);
#else _RISCOS
	nlist("/vmunix", nl);
#endif _RISCOS
	if (nl[0].n_value == 0) {
		complain("Variables missing from namelist");
		do_exit(1);
	}
	if ((kmem = open("/dev/kmem", 0)) < 0) {
		complain("can't open kmem");
		do_exit(1);
	}

	off = nl[X_IFNET].n_value;
	if (lseek(kmem, off, 0) == -1) {
		complain("can't seek in kmem");
		do_exit(1);
	}
	if (read(kmem, &firstifnet, sizeof(int)) != sizeof (int)) {
		complain("can't read firstifnet from kmem");
		do_exit(1);
	}
	numintfs = 0;
	for (off = firstifnet; off;) {
		if (lseek(kmem, off, 0) == -1) {
			complain("can't seek in kmem");
			do_exit(1);
		}
		if (read(kmem, &ifnet, sizeof ifnet) != sizeof ifnet) {
			complain("can't read ifnet from kmem");
			do_exit(1);
		}
		numintfs++;
		off = (int) ifnet.if_next;
	}
}

/* 
 * returns true if have a disk
 */
static
havedisk()
{
	int i, cnt;

#ifdef _RISCOS
	int	ndrives;
	int	xfer_size;
	struct	iotime *xfer;

	if (syscall (SYS_sysmips, MIPS_GET_NUMDISKS, &ndrives, 0) == -1) {
		complain("Unable to get the number of drives");
		do_exit(1);
	}
	xfer_size = sizeof(struct iotime *) * ndrives;
	xfer = (struct iotime *) malloc(xfer_size);
	if (xfer == NULL) {
		complain("can't allocate buffer");
		do_exit(1);
	};
	if (syscall (SYS_sysmips, MIPS_GET_DISKSTATS, xfer, 0) == -1) {
		complain("Unable to get disk stats");
		do_exit(1);
	}
	for (cnt = 0, i=0; i < ndrives; i++)
		cnt += xfer[i].io_cnt;
	free((char *) xfer);
#else _RISCOS
	long xfer[DK_NDRIVE];

	if (nl[X_DKXFER].n_value == 0) {
		complain("Variables missing from namelist");
		do_exit(1);
	}
	if (lseek(kmem, (long)nl[X_DKXFER].n_value, 0) == -1) {
		complain("can't seek in kmem");
		do_exit(1);
	}
	if (read(kmem, xfer, sizeof xfer)!= sizeof xfer) {
		complain("can't read kmem");
		do_exit(1);
	}
	for (cnt = 0, i=0; i < DK_NDRIVE; i++)
		cnt += xfer[i];
#endif _RISCOS
	return (cnt != 0);
}

static
do_exit(stat)
	int stat;
{

#ifndef DEBUG
	if (stat != 0) {
		/*
		 * something went wrong.  Sleep awhile and drain our queue
		 * before exiting.  This keeps inetd from immediately retrying.
		 */
		int cc, fromlen;
		char buf[RPCSMALLMSGSIZE];
		struct sockaddr from;

		sleep(CLOSEDOWN);
		for (;;) {
			fromlen = sizeof (from);
			cc = recvfrom(0, buf, sizeof (buf), 0, &from, &fromlen);
			if (cc <= 0)
				break;
		}
	}
#endif
	exit(stat);
}
