/*
 * Copyright (c) 2006
 *	Protium Computing, Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by Protium Computing, Inc.
 * 4. The name of Protium Computing, Inc. may not be used to endorse or 
 *    promote products derived from this software without specific prior 
 *    written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY PROTIUM COMPUTING ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL PROTIUM COMPUTING BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
#include <stdio.h>
#include <fcntl.h>
#include <time.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/syslog.h>
#include <sys/resource.h>
#include <termios.h>

#include "scc.h"

int scc_die = 0;

char
scc_cksum(int *i, int *j)
{
	unsigned char s;
	unsigned char *b;

	b = (unsigned char *)i;
	s = b[0] + b[1] + b[2] + b[3];
	b = (unsigned char *)j;
	s = s + b[0] + b[1] + b[2];
 	s = (s & 0x7f);

	return((char)s);
}

int 
scc_validate(int *i, int *j, int verbose)
{
	int r, s;

	/* if (verbose) printf("Packet: [0x%08x 0x%08x]\n", *i, *j); */

	switch (*i & SCC_POWERMASK) {
	case SCC_RUN:
		if (verbose) printf("\tPower: run\n");
		break;
	case SCC_STOP:
		if (verbose) printf("\tPower: stop\n");
		break;
	case SCC_ADVISESTOP:
		if (verbose) printf("\tPower: advise stop\n");
		break;
	case SCC_RESTART:
		if (verbose) printf("\tPower: restart\n");
		break;
	case SCC_ADVISERESTART:
		if (verbose) printf("\tPower: advise restart\n");
		break;
	case SCC_RESET:
		if (verbose) printf("\tPower: reset\n");
		break;
	case SCC_ADVISERESET:
		if (verbose) printf("\tPower: advise reset\n");
		break;
	default:
		if (verbose) printf("validate: bad power 0x%08x\n", *i);
		return(-1);	
	}

	switch (*i & SCC_LEDMASK) {
	case SCC_LEDOFF:
		if (verbose) printf("\tLed: off\n");
		break;
	case SCC_LEDBLUE:
		if (verbose) printf("\tLed: blue\n");
		break;
	case SCC_LEDRED:
		if (verbose) printf("\tLed: red\n");
		break;
	case SCC_LEDBLUEFLASH:
		if (verbose) printf("\tLed: blue flash\n");
		break;
	case SCC_LEDREDFLASH:
		if (verbose) printf("\tLed: red flash\n");
		break;
	case SCC_LEDALTERNATE1:
		if (verbose) printf("\tLed: alternate1\n");
		break;
	case SCC_LEDALTERNATE3:
		if (verbose) printf("\tLed: alternate3\n");
		break;
	default:
		if (verbose) printf("validate: bad led value 0x%08x\n", *i);
		return(-1);	
	}	

	r = ((*i & SCC_LEDRATEMASK)  >> SCC_LEDRATESHIFT);

	if (verbose) {
		printf("\tLed Flash Rate: %d\n", r);
	}

	if (r >= SCC_LEDRATEHI) 
		printf("warning: led rate too high - led on (%d)\n", r);
	if (r <= SCC_LEDRATELO) 
		printf("warning: led rate too low - led off (%d)\n", r);


	switch (*i & SCC_FANMASK) {
	case SCC_FANAUTO:
		if (verbose) printf("\tFan: auto\n");
		break;
	case SCC_FANON:	
		if (verbose) printf("\tFan: on\n");
		break;
	default:
		if (verbose) printf("validate: bad fan value 0x%08x\n", *i);
		return(-1);	
	}

	r = ((*j & SCC_FANTEMPONMASK)  >> SCC_FANTEMPONSHIFT);
	s = ((*j & SCC_FANTEMPOFFMASK) >> SCC_FANTEMPOFFSHIFT);

	if (verbose) {
		printf("\tFan On  Temprature: %dC\n", r);
		printf("\tFan Off Temprature: %dC\n", s);
	}

	if (r >= SCC_FANTEMPONHI) 
		printf("warning: fan on temp too high - fan off (%dC)\n", r);
	if (s <= SCC_FANTEMPONLO) 
		printf("warning: fan on temp too low - fan on (%dC)\n", s);
	if ((r - s) < SCC_FANTEMPDIFF) {
		printf("warning: fan on/off temp too close or on < off \n");
		printf("warning:\tfan on (%dC) fan off (%dC) \n", r, s);
	}

	r = *j & SCC_IDMASK;
	if ((r != SCC_HOST) && 
	    (r != SCC_CTLR00) &&  
	    (r != SCC_CTLR12)) {
		printf("warning: bad id %08x\n", r);
	}	

	r = *j & SCC_CKSUMMASK; 
	if (r != scc_cksum(i, j)) {
		printf("warning: checksum incorrect (%02x != %02x)\n", 
		       r, scc_cksum(i, j));
	}
	return(0);	
}

int
scc_setval(int *i, int mask, int val)
{
	/* clear the field */
	*i = (*i & (mask ^ 0xffffffff));

	/* insert the field */
	*i = (*i ^ val);
	return(0);
}

int
scc_setrate(int *i, int mask, int shift, int val)
{
	/* Assumption: val is < field width */

	/* clear the field */
	*i = (*i & (mask ^ 0xffffffff));

	/* insert the field */
	*i = (*i ^ (val << shift));
	return(0);
}

int 
scc_defaults(int *i, int *j)
{
	scc_setval(i,  SCC_POWERMASK, SCC_POWERDEFAULT);
	scc_setval(i,  SCC_LEDMASK, SCC_LEDDEFAULT);	
	scc_setrate(i, SCC_LEDRATEMASK, SCC_LEDRATESHIFT, SCC_LEDRATEDEFAULT);
	scc_setval(i,  SCC_FANMASK, SCC_FANDEFAULT);

	scc_setrate(j, SCC_FANTEMPONMASK, 
		SCC_FANTEMPONSHIFT, SCC_FANTEMPONDEFAULT);
	scc_setrate(j, SCC_FANTEMPOFFMASK, 
		SCC_FANTEMPOFFSHIFT, SCC_FANTEMPOFFDEFAULT);
	return(0);
}

void 
scc_sighandler(int sig)
{
	/*
	 * Just catch quit and term and set die
	 */
	switch(sig) {
	case SIGQUIT:
	case SIGTERM:
		syslog(LOG_INFO, "Terminating");
		scc_die=1;
		break;
	}
}

int
scc_daemonize(int cores)
{
	int	fd, i;
	char	pidstr[20];
	pid_t	pid;

	struct rlimit limit[1] = {{ 0, 0 }};


	if (!cores) {
		/* 
		 * No corefiles please 
		 */
		if (getrlimit(RLIMIT_CORE, limit) == -1) {
			perror("getrlimit");
			return -1;
		}

		limit->rlim_cur = 0;

		if (setrlimit(RLIMIT_CORE, limit) != 0) {
			perror("setrlimit");
			return -1;
		}
	}
	
	/*
	 * Must be root
	 */
	if (getuid() != 0) {
		fprintf(stderr, "Must be root\n");
		return -1;
	}

	/* 
	 * If parent isn't init, make it init.
	 */
	if (getppid() != 1) {
    
		pid = fork();
		if (pid == -1) {
			return -1;
		}
		if (pid != 0) {
			exit(EXIT_SUCCESS);
		}

		setsid();

		/*
		 * ignore sig hup so that when our new padre exits
		 * the kinder won't exit
		 */
		signal(SIGHUP, SIG_IGN);

		pid = fork();
		if (pid == -1) {
			return -1;
		}
		if (pid != 0) {
			exit(EXIT_SUCCESS);
		}
	}

	/*
	 * Set working dir to root
	 */
	chdir("/");

	/*
	 * Make sure file creations are created with explicit perms
	 */
	umask(0);

	/*
	 * Close all FDs
	 */
	for (i = 0; i < sysconf(_SC_OPEN_MAX); i++) {
		close(i);
	}
	
	/* 
	 * set std in, out, err to /dev/null
	 */
	for (i = 0; i < 3; i++) {

		if ((fd = open("/dev/null", (i==0?O_RDONLY:O_WRONLY))) == -1) {
			perror("setting up stdin, stdout, stderr");
			return -1;
		}
		
		if (i != fd) {
			if (dup2(fd, i) == -1) {
				perror("setting up stdin, stdout, stderr");
				return -1;
			}
			close(fd);
		}
	}

	/*
	 * Open and lock the pid file. Ensures only one daemon
	 * Write our pid into the file 
	 */
	fd = open(SCC_PIDFILE, O_RDWR|O_CREAT, 0640);
	if (fd < 0) {
		return(-1);
		
	}
	
	if (lockf(fd, F_TLOCK, 0) < 0) {
		/* we are not alone */
		close(fd);
		return(-1);
	}

	sprintf(pidstr,"%-6d\n", getpid());
	write(fd, pidstr, strlen(pidstr)); 

	/*
	 * Open syslog
	 */
	openlog("sccd", LOG_PID, LOG_DAEMON);
	syslog(LOG_INFO, "Starting");

	/*
	 * Ignore some signals and handle others
	 */
	signal(SIGCHLD, SIG_IGN);
	signal(SIGTSTP, SIG_IGN);
	signal(SIGTTOU, SIG_IGN);
	signal(SIGTTIN, SIG_IGN);
	signal(SIGQUIT, scc_sighandler);
	signal(SIGTERM, scc_sighandler);

	return(0);
}