/**************************/
/* CLARiiON SPS Simulator */
/* daemon program         */
/*                        */
/* Derek Konigsberg       */
/**************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>
#include <sys/termios.h>
#include <sys/uio.h>
#include <sys/wait.h>
#include "spsd.h"

int dflag;
int csock;
char spsmsg;
struct sockaddr_un saddr, caddr;
int slen;
char sname[80];
int ser_fd;

int valid_msg(char msg)
{
  if((msg=='0')||(msg=='1')||(msg=='2'))
    return 1;
  else
    return 0;
}

int become_daemon()
{
  /*int fd, fdlimit;*/
  switch(fork()) {
  case 0: break;
  case -1: return 0;
  default: _exit(0);
  }

  if(setsid() < 0) return 0;

  switch(fork()) {
  case 0: break;
  case -1: return 0;
  default: _exit(0);
  }

  chdir("/");

  /* Should probably run this code, but it causes problems */
  /* Besides, the program doesn't try any I/O anyways,     */
  /* unless the debug option is set.                       */
  /* Will hopefully be fixed in the next version           */
  /*
  fd = 0;
  fdlimit = sysconf(_SC_OPEN_MAX);
  while(0 < fdlimit) close(fd++);

  open("/dev/null", O_RDWR);
  dup(0); dup(0);
  */

  return 1;
}

void spsd_shutdown(int signo)
{
  if(dflag) printf("spsd: shutting down\n");
  close(csock);
  unlink(sname);
  exit(1);
}

void spsd_io(int signo)
{
  int len;
  char buf[81];
  len=recvfrom(csock, buf, 80, NULL, (struct sockaddr *)&saddr, &slen);
  if(len>0) {
    buf[len]='\0';
    if(dflag) printf("spsd: recvfrom='%s'\n", buf);
    if(valid_msg(buf[0])) spsmsg=buf[0];
  }
}

int init_serial(char *port)
{
  struct termios tp;
  int fd;
  fd = open(port, O_RDWR | O_NOCTTY | O_NDELAY);
  if(fd == -1) return -1;
  tcgetattr(fd, &tp);
  cfsetspeed(&tp, B9600);
  tcsetattr(fd, TCSAFLUSH, &tp);
  fcntl(fd, F_SETFL, 0);
  return fd;
}

void spsd_stop()
{
  pid_t pid;
  int status;
  switch(pid=fork()) {
  case 0:
    if(dflag) printf("spsd: Executing '%s'\n", STOP_PATH);
    execl(STOP_PATH, STOP_COMMAND, 0);
  default:
    wait(&status);
  }
}

void run_loop()
{
  char buf[255], mstr[4];
  char *bufptr;
  int nbytes;
  while(1) {
    bufptr = buf;
    while((nbytes=read(ser_fd, bufptr, buf + sizeof(buf)-bufptr-1))>0) {
      bufptr += nbytes;
      if(bufptr[-1] == 0x0A) break;
    }
    *bufptr--;
    bufptr = '\0';
    if(dflag) printf("spsd: got query '%s'\n", buf);
    if(strncmp(buf, "COND?", 5) == 0) {
      mstr[0]=spsmsg;
      mstr[1]=0x0A;
      mstr[2]='\0';
      write(ser_fd, mstr, 2);
      if(dflag) printf("spsd: sent response '%c'\n", mstr[0]);
    }
    else if(strncmp(buf, "STOP", 4) == 0) {
      if(dflag) printf("spsd: got shutdown message\n");
      spsd_stop();
    }
  }
}

void usage(char *pname)
{
  printf("Usage: %s [options]\n", pname);
  printf("\t-d\tDebug mode\n");
  printf("\t-f\tRun in foreground\n");
  printf("\t-p port\tSerial port to use\n");
  printf("\t-m msg\tInitial message to send\n");
  printf("\t-h\tPrint this help message\n");
}

int main(int argc, char *argv[])
{
  int ch, fflag, i;
  char port[80];
  struct sigaction act_shutdown, act_io;
  dflag=0; fflag=0; spsmsg='2';
  strcpy(port, DEFAULT_PORT);
  while((ch = getopt(argc, argv, "dfp:m:h")) != -1)
    switch(ch) {
    case 'd':
      dflag=1;
      break;
    case 'f':
      fflag=1;
      break;
    case 'p':
      strncpy(port, optarg, 80);
      break;
    case 'm':
      if(valid_msg(optarg[0]))
	spsmsg=optarg[0];
      break;
    case 'h':
    default:
      usage(argv[0]);
      return 0;
    }
  argc -= optind;
  argv += optind;

  i=strlen(port);
  while(port[--i]!='/') {
    if(i==0) { i--; break; }
  }
  i++;
  strcpy(sname, "/tmp/");
  strcat(sname, "spsd.");
  strcat(sname, port+i);

  if(!fflag)
    if(!become_daemon()) {
      printf("spsd: Error, could not become a daemon\n");
      return 0;
    }

  if((csock = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
    if(dflag) printf("spsd: Error creating socket\n");
    return 0;
  }
  strcpy(saddr.sun_path, sname);
  saddr.sun_family = AF_UNIX;
  umask(077);
  unlink(sname);
  slen = sizeof(saddr.sun_family) + strlen(saddr.sun_path) + 1;
  if(bind(csock, (struct sockaddr *)&saddr, slen) < 0) {
    if(dflag) printf("spsd: Error binding to socket\n");
    return 0;
  }
  fcntl(csock, F_SETOWN, getpid());
  i=fcntl(csock, F_GETFL, 0);
  fcntl(csock, F_SETFL, i|O_NONBLOCK|O_ASYNC);

  if(dflag) printf("spsd: foreground=%i port='%s' socket='%s' message=%c\n", fflag, port, saddr.sun_path, spsmsg);

  /*
  if(!fflag)
    if(!become_daemon()) {
      printf("spsd: Error, could not become a daemon\n");
      return 0;
    }
  */
  act_shutdown.sa_handler = spsd_shutdown;
  sigemptyset(&act_shutdown.sa_mask);
  act_shutdown.sa_flags = SA_NOCLDSTOP;
  if(sigaction(SIGINT, &act_shutdown, NULL) < 0) {
    if(dflag) printf("spsd: sigaction failed\n");
    return 0;
  }

  act_io.sa_handler = spsd_io;
  sigemptyset(&act_io.sa_mask);
  act_io.sa_flags = SA_NOCLDSTOP;
  if(sigaction(SIGIO, &act_io, NULL) < 0) {
    if(dflag) printf("spsd: sigaction failed\n");
    return 0;
  }

  ser_fd=init_serial(port);
  if(ser_fd == -1) {
    printf("spsd: Error opening serial port!\n");
    close(csock);
    unlink(sname);
    return 0;
  }

  run_loop();

  close(csock);
  unlink(sname);
  return 1;
}
