#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <signal.h>
#include <sys/stat.h>

#include <string.h>
#include <time.h>

#include "tmports.h"

#include "hardware.h"

static char Rcsid[] = "$Header: /jayar/i/tm4/tmu/src/tmumon/RCS/tmumon.c,v 1.6 2013/04/25 18:36:15 drg Exp $";

/* This should come from getservbyname, but it is too hard to make that work
 * from a unprivileged program. -- drg
 */
#define TMPORT_HOUSEKEEPING	3462
#define TMPORT_DATA	9100

int debug = 0;

FILE *debugfile;

#define DEBUGFILE	"/tmp/TMumon.debugout"

#define MAXDEBUGFILESIZE	1000000

char *get_arch_name(FILE *out);

main() {
	int sock_housekeeping, sock_data;
	struct sockaddr_in server;
	int msgsock;
	int optval;
	
	/* Try to initialize the interface */
	printf("Opening interface...\n");
	if(hardware_open() == -1) {
		printf("Failed to open interface.\n");
	}
	else {
		printf("Successfully opened interface.\n");
	}

	signal(SIGPIPE, SIG_IGN);

	sock_housekeeping = socket(AF_INET, SOCK_STREAM, 0);
	if(sock_housekeeping < 0) {
		perror("opening stream socket");
		exit(1);
		}
	optval = 1;
	if(setsockopt(sock_housekeeping, SOL_SOCKET, SO_REUSEADDR, (char *) &optval,
			sizeof(optval)) < 0)
		perror("setsockopt");
	server.sin_family = AF_INET;
	server.sin_addr.s_addr = INADDR_ANY;
	server.sin_port = htons(TMPORT_HOUSEKEEPING);
	if(bind(sock_housekeeping, (struct sockaddr *) &server, sizeof(server))) {
		perror("binding stream socket");
		exit(1);
		}
	listen(sock_housekeeping, 5);

	sock_data = socket(AF_INET, SOCK_STREAM, 0);
	if(sock_data < 0) {
		perror("opening stream socket");
		exit(1);
		}
	if(setsockopt(sock_data, SOL_SOCKET, SO_REUSEADDR, (char *) &optval,
			sizeof(optval)) < 0)
		perror("setsockopt");
	server.sin_family = AF_INET;
	server.sin_addr.s_addr = INADDR_ANY;
	server.sin_port = htons(TMPORT_DATA);
	if(bind(sock_data, (struct sockaddr *) &server, sizeof(server))) {
		perror("binding stream socket 2");
		exit(1);
		}
	listen(sock_data, 5);

	doprotocol(sock_housekeeping, sock_data);
	}



struct timeval zerotime;

/* Protocols understood by tmumon */
#define HOUSEKEEPING	0
#define PORTS		1

/* Flags in sockinfo */
#define CLOSEME		1

struct sockinfo {
	FILE *stream;
	int fd;
	int proto;
	int flags;
	} socks[FD_SETSIZE];
int nsocks;


struct tm_packet packet;

/* Gave up on stdio, since Solaris stdio doesn't handle I/O on a single
 * descriptor very well.  Use stdio only for writes to the socket, and use
 * this to read from it.
 */

readline(buf, bufsize, fd)
	char *buf;
	int bufsize, fd;
	{
	int retval;
	char *bufp, *cp;
	int nbytes;

	/* We know that the protocol is request/response.  We only get
	 * one line at a time.
	 */

	bufp = buf;
	nbytes = bufsize;
	while(1) {
		*bufp = '\0';
		retval = read(fd, bufp, nbytes);
		if(retval <= 0)
			return(retval);
		bufp[retval] = '\0';
		for(cp = bufp; cp < &bufp[retval]; cp++) {
			if(*cp == '\n')
				return(1);
			}
		nbytes -= retval;
		bufp += retval;
		if(nbytes <= 0)
			return(-1);
		}
	}



doprotocol(sock_housekeeping, sock_data)
	int sock_housekeeping, sock_data;
	{
	char buf[8192];
	char *cp;
	fd_set readmask, readbits;
	int i, j, found, new;
	int n, nbytes;
	int fd;
	FILE *client;
	int messages;
	struct sockaddr clientaddr;
	int size;

	FD_ZERO(&readmask);
	FD_SET(sock_housekeeping, &readmask);
	FD_SET(sock_data, &readmask);

	nsocks = 0;

	for(messages = 0; ; messages++) {

		if(debug)
			fflush(debugfile);

		if((messages%100) == 0)
			check_debug_output_size();

		readbits = readmask;
		n = select(FD_SETSIZE, &readbits, 0, 0, 0);
		if(n <= 0)
			break;
		if(FD_ISSET(sock_housekeeping, &readbits)) {
			new = accept(sock_housekeeping, 0, 0);
			client = fdopen(new, "w");
			socks[nsocks].stream = client;
			socks[nsocks].fd = new;
			socks[nsocks].proto = HOUSEKEEPING;
			socks[nsocks].flags = 0;
			nsocks++;
			FD_SET(new, &readmask);
			if(debug) {
				size = sizeof(clientaddr);
				if(getpeername(new, &clientaddr, &size) == 0) {
					fprintf(debugfile, "%s: ",
		inet_ntoa(((struct sockaddr_in *) &clientaddr)->sin_addr));
					}
				fprintf(debugfile, "new connect a %d; nsocks %d\n", new, nsocks);
				}
			}
		if(FD_ISSET(sock_data, &readbits)) {
			new = accept(sock_data, 0, 0);
			socks[nsocks].stream = NULL;
			socks[nsocks].fd = new;
			socks[nsocks].proto = PORTS;
			nsocks++;
			FD_SET(new, &readmask);
			if(debug) {
				size = sizeof(clientaddr);
				if(getpeername(new, &clientaddr, &size) == 0) {
					fprintf(debugfile, "%s: ",
		inet_ntoa(((struct sockaddr_in *) &clientaddr)->sin_addr));
					}
				fprintf(debugfile, "new connect b %d; nsocks %d\n", new, nsocks);
				}
			}
		for(j=0; j<nsocks; j++) {
			fd = socks[j].fd;
			if(FD_ISSET(fd, &readbits)) {
				if(socks[j].proto == HOUSEKEEPING) {
					if(readline(buf, sizeof(buf), fd)<=0) {
						socks[j].flags = CLOSEME;
						continue;
						}
					if((debug > 2)
						|| (debug &&
				     		  ((strstr(buf, "get") != buf)
						&& (strstr(buf, "read_temp") != buf))))
						fprintf(debugfile, "read %d %zd bytes '%s'\n", fd, strlen(buf), buf);
					do_command(buf, socks[j].stream);
					fprintf(socks[j].stream, "\n");
					fflush(socks[j].stream);
					}

				if(socks[j].proto == PORTS) {
					if(read(fd, &packet.h, TM_PACKET_HLEN)
							< TM_PACKET_HLEN) {
						socks[j].flags = CLOSEME;
						continue;
						}
					packet.h = ntohl(packet.h);
					nbytes = TM_PACKET_LENGTH(packet);
					if(TM_PACKET_WRITE(packet)) {
						cp = packet.data;
						while(nbytes) {
							n = read(fd, cp, nbytes);
							if(debug && (n != nbytes))
								fprintf(debugfile, "short read fd %d nbytes %d  n %d\n", fd, nbytes, n);
							if(n <= 0) {
								socks[j].flags = CLOSEME;
								goto nextfd;
								}
							nbytes -= n;
							cp += n;
							}
						}
					/* Talk to the Transmogrifier here */
					do_packet(&packet, j);
					}
				}
nextfd:			continue;
			}
		
		for(j=0; j<nsocks; j++) {
			if(socks[j].flags == CLOSEME) {
				fd = socks[j].fd;
				FD_CLR(fd, &readmask);
				discard(j);
				--j;
				}
			}
		}
	}



kill_sockets()
	{
	int j;

	for(j=0; j<nsocks; j++) {
		if(socks[j].proto == PORTS) {
			socks[j].flags = CLOSEME;
			}
		}
	}



discard(sock)
	int sock;
	{
	int i;

	/* The other end closed.
	 * Delete it from the list of open
	 * connections.
	 */
	if(debug)
		fprintf(debugfile, "deleted %d; nsocks %d\n", socks[sock].fd, nsocks);
	close(socks[sock].fd);
	if(socks[sock].stream != NULL)
		fclose(socks[sock].stream);
	for(i=sock; i<nsocks; i++)
		socks[i] = socks[i+1];
	nsocks--;
	}



do_packet(pp, sock)
	struct tm_packet *pp;
	int sock;
	{
	int i, h, ret, nbytes, nwords, iword, port_width_bytes, nvalues;
	int port_width_words, j;
	int *unpacked_data_ptr;
	char *data_ptr;
	struct tm_packet temp;

	if(debug) {
		fprintf(debugfile, "packet fd %d h %X rw %d needs_ack %d chip %d port %d len %d\n",
		socks[sock].fd,
		pp->h,
		TM_PACKET_WRITE(*pp),
		TM_PACKET_NEEDS_ACK(*pp),
		TM_PACKET_CHIP(*pp),
		TM_PACKET_PORT(*pp),
		TM_PACKET_LENGTH(*pp));
		}

	port_width_bytes = TM_PACKET_PORT_BYTES(*pp);
	port_width_words = (port_width_bytes + 3) / 4;

	if(TM_PACKET_WRITE(*pp)) {
		temp.h = hardware_write(
			TM_PACKET_PORT(*pp), pp->data, TM_PACKET_LENGTH(*pp));

		if(temp.h == -1) {
			if(debug > 1) {
				fprintf(debugfile, "data write failed\n");
				}
			}

		if(debug > 1) {
			for(i=0; i<TM_PACKET_LENGTH(*pp); i++) {
				fprintf(debugfile, "write 0x%X\n", pp->data[i] & 0xFF);
				}
			}
		}
	else {
		temp.h = hardware_read(
			TM_PACKET_PORT(*pp), temp.data, TM_PACKET_LENGTH(*pp));

		if(temp.h == -1) {
			if(debug > 1) {
				fprintf(debugfile, "data read failed\n");
				}
			}

		if(debug > 1) {
			for(i=0; i<TM_PACKET_LENGTH(*pp); i++) {
				fprintf(debugfile, "read 0x%X\n", temp.data[i] & 0xFF);
				}
			}
		}

	/* Send a result code, followed by any data */

	nbytes = sizeof(temp.h);
	if(!TM_PACKET_WRITE(*pp) && (temp.h > 0)) {
		nbytes = temp.h + sizeof(temp.h);
		}

	temp.h = htonl(temp.h);

	data_ptr = (char *) &temp;

	while(nbytes > 0) {
		ret = write(socks[sock].fd, data_ptr, nbytes);
		if(ret <= 0) {
			fprintf(stderr, "write %d bytes to socket %d failed\n",
					nbytes, socks[sock].fd);
			perror("write to socket");
			if(debug) {
				fprintf(debugfile, "write %d bytes to socket %d failed\n",
					nbytes, socks[sock].fd);
				}
			return;
			}
		nbytes -= ret;
		data_ptr += ret;
		}
	}


char portlistdir[1024] = "  ";
char designdir[1024] = "  ";
char designdate[32] = "  ";
char status1[1024] = "  ";
char status2[1024] = "  ";

time_t expire_time;
int priority;

#define MAXTOKENS	10

do_command(buf, out)
	char *buf;
	FILE *out;
	{
	char buf2[8192];
	int ntokens;
	char *tokens[MAXTOKENS];
	int value0, value1, value3;
	int legal = 0;
	time_t now;

	strncpy(buf2, buf, sizeof(buf2));
	ntokens = parse(buf2, tokens, MAXTOKENS);
	if(ntokens < 1)
		return;

	time(&now);

#define COMMAND(s, n)	(!strcmp(tokens[0], s) ? \
				(((ntokens == (n+1)) || (n == -1)) ? \
					(legal = 1) : fail(s, out, n)) \
				: 0)

	if(COMMAND("EXIT", 0))
		exit(0);

	/* Keep track of the directory that describes the ports on the design */
	if(COMMAND("setportlistdir", 1)) {
		strncpy(portlistdir, tokens[1], sizeof(portlistdir));
		}
	if(COMMAND("getportlistdir", 0)) {
		fprintf(out, "%s\n", portlistdir);
		}

	/* Keep track of the overall design directory */
	if(COMMAND("setdesigndir", 1)) {
		strncpy(designdir, tokens[1], sizeof(designdir));
		sprintf(designdate, "%s", (char *) ctime(&now));
		/* Delete stupid newline */
		designdate[strlen(designdate) - 1] = '\0';
		}
	if(COMMAND("getdesigndir", 0))
		fprintf(out, "%s\n", designdir);
	if(COMMAND("getdesigndate", 0))
		fprintf(out, "%s\n", designdate);

	if(COMMAND("setstatus1", -1)) {
		strncpy(status1, buf + strlen(tokens[0]) + 1, sizeof(status1));
		status1[strlen(status1) - 1] = '\0';
		}
	if(COMMAND("getstatus1", 0))
		fprintf(out, "%s\n", status1);

	if(COMMAND("setstatus2", -1)) {
		strncpy(status2, buf + strlen(tokens[0]) + 1, sizeof(status2));
		status2[strlen(status2) - 1] = '\0';
		}
	if(COMMAND("getstatus2", 0))
		fprintf(out, "%s\n", status2);

	if(COMMAND("acquire", 2)) {
		int new_priority;

		new_priority = atoi(tokens[1]);
		if((new_priority <= priority) && (now < expire_time)) {
			int seconds = expire_time - now;

			fprintf(out, "%d %d\n", priority, seconds);
			return;
			}
		priority = new_priority;
		expire_time = now + atoi(tokens[2]);
		fprintf(out, "0\n");
		}

	if(COMMAND("release", 0)) {
		expire_time = (time_t) 0;
		priority = 0;
		}

	/* Turn on debugging printfs in tmumon */
	if(COMMAND("debug", 1)) {
		debug = atoi(tokens[1]);
		if((debug == 0) && (debugfile != (FILE *) NULL)) {
			fclose(debugfile);
			debugfile = (FILE *) NULL;
			}
		if((debug != 0) && (debugfile == (FILE *) NULL)) {
			if((debugfile = fopen(DEBUGFILE, "w")) == (FILE *) NULL)
				debug = 0;
			else
				chmod(DEBUGFILE, 0666);
			}
		}

	if(COMMAND("getarch", 0)) {
		fprintf(out, "%s\n", get_arch_name(out));
		}

	if(COMMAND("hostname", 0)) {
		char name[1024];

		gethostname(name, sizeof(name));
		fprintf(out, "%s\n", name);
		}

	if(COMMAND("stop", 0)) {

		/* At this point, close off any sockets connected to programs
		 * that were talking to this design.
		 */
		kill_sockets();
		}

	if(!legal)
		fprintf(out, "tmumon: unrecognized command: %s\n",
				tokens[0]);
	}



fail(command, out, nargs)
	char *command;
	FILE *out;
	int nargs;
	{
	fprintf(out, "tmumon: %s needs %d arguments: command ignored\n",
		command, nargs);
	return(0);
	}


parse(s, tokens, max)
	char *s;
	char *tokens[];
	int max;
	{
	int i = 0;

	while(*s) {
		while(*s == ' ' || *s == '\t' || *s == '\n' || *s == '\r' || *s == '\0') {
			if(*s++ == '\0')
				return(i);
			}
		tokens[i++] = s;
		while(*s != ' ' && *s != '\t' && *s != '\n' && *s != '\r') {
			if(*s++ == '\0')
				return(i);
			}
		*s++ = '\0';
		if(i>=max)
			return(i);
		}
	return(i);
	}



check_debug_output_size()
	{
	struct stat buf;

	/* Every once in a while, check on DEBUGFILE and truncate it if
	 * it is too big.  Avoid filling the file system.
	 */

	if(debugfile == (FILE *) NULL)
		return;
	fstat(fileno(debugfile), &buf);
	if(buf.st_size > MAXDEBUGFILESIZE) {
		rewind(debugfile);
		ftruncate(fileno(debugfile), 0);
		}
	}



char *get_arch_name(FILE *out) {

	return("TMU");
}
