/*
 * UPS daemon for MGE Pulsar
 * At least on my ES5+ works fine
 * Copyright (c) Stanislav Voronyi <stas@esc.kharkov.com>
 * Version 0.3 28.12.1998
 */
#include <stdio.h>
#include <sys/types.h>
#include <termios.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <fcntl.h>
#include <signal.h>
#include <syslog.h>
#include <sysexits.h>
#include <stdlib.h>

/* some defaults */
#define DEFAULT_INTERVAL 10
#define DEFAULT_L_INTERVAL 30

int RTS = TIOCM_RTS, use_syslog = 0;
/* only bits I need for */
#define SS_BATTERY_LOW 4
#define SS_POWER_FAIL 5
#define SS_OVERLOAD 6
#define BS_POWER_FAIL 0
#define BS_POWER_OK 1
#define BS_BAT 2
#define BS_PWS 11
#define BS_TDP 12

/* status file for init */
#define PWRFILE "/var/run/powerstatus"
/* Linux usual */
#define LOCKDIR "/var/run"
#define RUNDIR	"/var/run"
char lockfile[40] = {0,};
char pidfile[40] = {0,};
char progname[40] = {0,};

void
sigexit (int ignore)
{
  if (*lockfile)
    unlink (lockfile);
  if (*pidfile)
    unlink (pidfile);
  if (use_syslog)
    syslog (LOG_NOTICE, "UPS daemon terminated");
  exit (EX_OK);
}

int
main (int argc, char **argv)
{
  int tty, fd;
  FILE *trace = NULL;
  char iline[40], Status[20], *av[3], *tfile = NULL, *p;
  char portname[40], *pwrfile = PWRFILE, *run_path = NULL;
  struct termios ts;
  struct timeval timeout;
  fd_set fds;
  int interval = DEFAULT_INTERVAL, lowinterval = DEFAULT_L_INTERVAL;
  int c, i, mode = 0, nr_count = 0, start = 1, prev = '0', ckbat = 0;
  int tdp = 0, pws = 0;		/* don't touch this flags by default */
  int before_pwroff = 0, pwr_on_wait = 0;
  time_t last_low_rep = 0, overload_time = 0;
  pid_t init_pid = 1;

  if ((p = strrchr (argv[0], '/')) != NULL)
    strcpy (progname, p + 1);
  else
    strcpy (progname, argv[0]);
/* now parsing argumets 
   We may have
   mgeupsd [-i check_interval] [-q] port 
   port may be as /dev/ttyxxx or ttyxx
   or you can do ln -s /dev/ttyXxx /dev/mgeups
   and use /dev/mgeups */
  for (i = 1; i < argc; i++)
    {
      if (strcmp (argv[i], "-pwroff") == 0)
	{
	  mode = 1;
	  continue;
	}
      else if (strcmp (argv[i], "-q") == 0)
	{			/* for comatibility with standart powerd */
	  mode = 1;
	  continue;
	}
      else if (strcmp (argv[i], "-l") == 0)	/* use syslog for reporting */
	{
	  use_syslog = 1;
	  continue;
	}
      else if (strcmp (argv[i], "-i") == 0)
	{
	  if ((interval = atoi (argv[++i])) == 0)
	    interval = DEFAULT_INTERVAL;	/* my default - check ups every 10 seconds */
	  continue;
	}
      else if (strcmp (argv[i], "-li") == 0)
	{
	  if ((lowinterval = atoi (argv[++i])) == 0)
	    lowinterval = DEFAULT_L_INTERVAL;	/* my default - report twice a minute */
	  continue;
	}
      else if (strcmp (argv[i], "-t") == 0)	/* trace commands, huge output - only for debbuging */
	{
	  tfile = strdup (argv[++i]);
	  continue;
	}
      else if (strcmp (argv[i], "-pon") == 0)	/* total discharge protection on */
	{
	tdp_on:
	  tdp = '0';
	  continue;
	}
      else if (strcmp (argv[i], "-poff") == 0)	/* total discharge protection off */
	{
	tdp_off:
	  tdp = '1';
	  continue;
	}
      else if (strcmp (argv[i], "-p") == 0)
	{
	  if (strcmp (argv[++i], "on") == 0)
	    goto tdp_on;
	  else if (strcmp (argv[i], "off") == 0)
	    goto tdp_off;
	  else
	    {
	      fprintf (stderr, "Invalid option -p %s\n", argv[i]);
	      break;
	    }
	}
      else if (strcmp (argv[i], "-son") == 0)	/* power saving mode on */
	{
	pws_on:
	  pws = '1';
	  continue;
	}
      else if (strcmp (argv[i], "-soff") == 0)	/* power saving mode off */
	{
	pws_off:
	  pws = '0';
	  continue;
	}
      else if (strcmp (argv[i], "-s") == 0)
	{
	  if (strcmp (argv[++i], "on") == 0)
	    goto pws_on;
	  else if (strcmp (argv[i], "off") == 0)
	    goto pws_off;
	  else
	    {
	      fprintf (stderr, "Invalid option -s %s\n", argv[i]);
	      break;
	    }
	}
      /* -pwrfile, -process & -run three options that allow monitoring not only machine
         but any other equipment as well */
      else if (strcmp (argv[i], "-pwrfile") == 0)	/* change name of power status file */
	{
	  pwrfile = argv[++i];
	  /* make some verification */
	  if ((fd = open (pwrfile, O_CREAT | O_WRONLY, 0644)) >= 0)
	    {
	      close (fd);
	      unlink (pwrfile);
	    }
	  else
	    {
	      fprintf (stderr, "Can't create file %s\n", argv[i]);
	      break;
	    }
	  continue;
	}
      else if (strcmp (argv[i], "-process") == 0)	/* change pid of process for send SIGPWR to */
	{
	  if ((init_pid = atoi (argv[++i])) == 0)
	    {
	      fprintf (stderr, "Invalid option -process %s\n", argv[i]);
	      break;
	    }
	  if (kill (init_pid, 0))
	    {			/* process not running */
	      fprintf (stderr, "Process %u does not running\n", init_pid);
	      break;
	    }
	  continue;
	}
      else if (strcmp (argv[i], "-run") == 0)	/* run specified command & give powerfile as argv[1] */
	{
	  /* must be full pathname */
	  run_path = argv[++i];
	  if (*run_path != '/')
	    {
	      fprintf (stderr, "You must specify full pathname for -run\n");
	      break;
	    }
	  if (access (run_path, X_OK))
	    {
	      fprintf (stderr, "Haven't permission to run %s\n", run_path);
	      break;
	    }
	  continue;
	}
      else if (strcmp (argv[i], "-swpwr") == 0)		/* may have two parameters time_off,time_before_off */
	{
	  mode = 2;
	  pwr_on_wait = (int) strtol (argv[++i], &p, 10);
	  if (*p == ',')
	    before_pwroff = atoi (++p);
	  continue;
	}
      else
	{			/* this must be portname */
	  if (argv[i][0] == '-')
	    {			/* portname can't start from '-' - this is wrong option */
	      fprintf (stderr, "Invalid option %s\n", argv[i]);
	      break;
	    }
	  if (argv[i][0] == '/')
	    strcpy (portname, argv[i]);
	  else
	    sprintf (portname, "/dev/%s", argv[i]);
/* check for port locking & lock port */
	  p = strrchr (portname, '/') + 1;
	  sprintf (lockfile, "%s/LCK..%s", LOCKDIR, p);
	  if ((trace = fopen (lockfile, "r")) == NULL)
	    {
	    newlock:
	      if (!mode)	/* don't make lock in poweroff mode - filesystems possible ro already */
		{
#ifndef TEST
		  if (fork ())
		    exit (EX_OK);
#endif
		  if ((trace = fopen (lockfile, "w")) == NULL)
		    {
		      fprintf (stderr, "Unable to create lock file, exiting!\n");
		      exit (EX_CANTCREAT);
		    }
		  fprintf (trace, "%u", getpid ());
		  fclose (trace);
		  trace = NULL;
		}
	    }
	  else
	    {
	      fscanf (trace, "%d", &c);
	      fclose (trace);
	      trace = NULL;
	      if (kill ((pid_t) c, 0) == 0)
		{
		  fprintf (stderr, "Device %s already locked by process %u\n", portname, c);
		  exit (EX_NOPERM);
		}
	      else
		goto newlock;

	    }
	  tty = open (portname, O_RDWR);
	  if (tty == -1 || isatty (tty) != 1)
	    {
	      fprintf (stderr, "mgeupsd: %s not a tty\n", portname);
	      exit (EX_USAGE);
	    }
	  else
	    {
	      close (tty);	/* I open it for real later */
	      goto operate;
	    }
	}
    }
/* if we here something wrong */
  fprintf (stderr, "Usage: mgeupsd [-i check_interval] [-l] [-q] [-p on|off] [-s on|off] port\n");
  exit (EX_USAGE);

operate:
/* we don't need stdxxx and control tty */
  fclose (stderr);
  fclose (stdout);
  fclose (stdin);
  setsid ();
  signal (SIGTERM, sigexit);
  signal (SIGCHLD, SIG_IGN);

/* setup port */
  tty = open (portname, O_RDWR | O_NOCTTY);
  ioctl (tty, TIOCMBIC, &RTS);
  tcgetattr (tty, &ts);
  cfmakeraw (&ts);
  cfsetospeed (&ts, B2400);
  cfsetispeed (&ts, B2400);
  ts.c_cflag &= ~(CRTSCTS);
  ts.c_cc[VMIN] = 0;
  ts.c_cc[VTIME] = 5;
  tcsetattr (tty, TCSANOW, &ts);
/* end setup */

  if (mode)
    {				/* turn power off */
    rz:
      write (tty, "Z\r\n", 3);
      c = read (tty, iline, sizeof iline);
      if (c != 0)
	goto rz;
    rm:
      c = sprintf (iline, "Sm %u\r\n", pwr_on_wait);
      write (tty, iline, c);
      for (c = 0, i = 0; (i = read (tty, iline + c, (sizeof iline) - c)); c += i);
      if (c != 4 || iline[0] != 'O' || iline[1] != 'K')
	goto rm;
    rn:
      c = sprintf (iline, "Sn %u\r\n", before_pwroff);
      write (tty, iline, c);
      for (c = 0, i = 0; (i = read (tty, iline + c, (sizeof iline) - c)); c += i);
      if (c != 4 || iline[0] != 'O' || iline[1] != 'K')
	goto rn;
    rx:
      write (tty, "Sx0\r\n", 5);
      for (c = 0, i = 0; (i = read (tty, iline + c, (sizeof iline) - c)); c += i);
      if (c != 4 || iline[0] != 'O' || iline[1] != 'K')
	goto rx;
/* power must be turned off now, just wait for it */
      if (mode == 1)
	pause ();
      else
	exit (EX_OK);		/* or I must waitng to turn power back to on? write me if anyone need this */
    }
  else
    {				/* normal operation */
      /* create pid-file first */
      sprintf (pidfile, "%s/%s.pid", RUNDIR, progname);
      if ((trace = fopen (pidfile, "w")) != NULL)
	{
	  fprintf (trace, "%u\n", getpid ());
	  fclose (trace);
	  trace = NULL;
	}
      if (use_syslog)
	{
	  openlog (progname, LOG_PID, LOG_DAEMON);
	  syslog (LOG_INFO, "UPS daemon started");
	}
      if (tfile)		/* trace mode */
	trace = fopen (tfile, "a");	/* I don't check for error
					   if trace == NULL just no trace */
      if (trace)
	setvbuf (trace, NULL, _IONBF, 0);
      /* init UPS */
      nr_count = -1;
    ri:
      if (nr_count++ > 60)
	{
	  nr_count = 0;
	  if (use_syslog)
	    syslog (LOG_NOTICE, "Unable to %s communication with UPS", start ? "start" : "restore");
	}
      write (tty, "Z\r\n", 3);
      if (trace)
	fprintf (trace, "Command: Z\n");
      c = read (tty, iline, sizeof iline);
      if (c != 0)
	{
	  if (trace)
	    {
	      iline[c] = 0;
	      fprintf (trace, "Answer: %s\n", iline);	/* junk ? */
	    }
	  goto ri;
	}
      write (tty, "Si 1\r\n", 6);
      if (trace)
	fprintf (trace, "Command: Si 1\n");
      c = read (tty, iline, sizeof iline);
      if (c == 0)
	goto ri;
      while (c < 10)
	{
	  i = read (tty, iline + c, sizeof iline);
	  if (i)
	    c += i;
	  else
	    goto ri;
	}
      if (strncmp (iline, "Pulsar", 6) != 0)
	goto ri;

      if (trace || use_syslog)
	{
	  iline[c] = 0;
	  if ((p = strchr (iline, '\n')) != NULL)
	    *p = 0;
	  if ((p = strchr (iline, '\r')) != NULL)
	    *p = 0;		/* remove CR LF for report */
	}
      if (trace)
	fprintf (trace, "Device report: %s\n", iline);
      if (use_syslog)
	{
	  if (!start)
	    syslog (LOG_NOTICE, "Communication with UPS restored");
	  syslog (LOG_INFO, "Device: %s", iline);
	}
      while (c != 0)
	{
	  c = read (tty, iline, sizeof iline);
	  if (trace && c)
	    fprintf (trace, "more: %s", iline);
	}
      /* check battery status first */
      nr_count = 0;
    rbs:
      write (tty, "Bs\r\n", 4);
      if (trace)
	fprintf (trace, "Command: Bs\n");
      /* answer must be 19 bytes long */
      for (c = 0, i = 0; (i = read (tty, Status + c, (sizeof Status) - c)); c += i);
      if (c != 19)
	{
	  if (trace)
	    {
	      Status[c] = 0;
	      fprintf (trace, "Answer: %s\n", Status);	/* somethig wrong */
	    }
	  if (nr_count++ < 3)
	    goto rbs;
	  else
	    goto ri;
	}
      else
	Status[17] = 0;
      if (trace)
	fprintf (trace, "Answer: %s\n", Status);
      if (Status[BS_POWER_FAIL] == '1')
	{			/* very strange - we start from battery */
	  if (use_syslog)
	    syslog (LOG_WARNING, "UPS started without main supply");
	}
      if (use_syslog)
	if (Status[BS_POWER_OK] == '1')
	  {
	    if (Status[BS_BAT] == '1')
	      {			/* battery not fully charged */
		ckbat = 1;
		syslog (LOG_INFO, "UPS battery not fully charged");
	      }
	    else
	      syslog (LOG_INFO, "UPS battery completely charged");
	  }
      if (tdp)
	{
	  if (Status[BS_TDP] != tdp)
	    nr_count = 0;
	  if (tdp == '1')
	    {
	    rbx3:
	      write (tty, "Bx3\r\n", 5);
	      if (trace)
		fprintf (trace, "Command: Bx3\n");
	      for (c = 0, i = 0; (i = read (tty, iline + c, (sizeof iline) - c)); c += i);
	      if (trace)
		{
		  iline[c] = 0;
		  fprintf (trace, "Answer: %s\n", iline);
		}
	      if (c != 4 || iline[0] != 'O' || iline[1] != 'K')
		if (nr_count++ < 3)
		  goto rbx3;
		else if (use_syslog)
		  syslog (LOG_WARNING, "Can't turn off total discharge protection");
	    }
	  else
	    {
	    rbx4:
	      write (tty, "Bx4\r\n", 5);
	      if (trace)
		fprintf (trace, "Command: Bx4\n");
	      for (c = 0, i = 0; (i = read (tty, iline + c, (sizeof iline) - c)); c += i);
	      if (trace)
		{
		  iline[c] = 0;
		  fprintf (trace, "Answer: %s\n", iline);
		}
	      if (c != 4 || iline[0] != 'O' || iline[1] != 'K')
		if (nr_count++ < 3)
		  goto rbx4;
		else if (use_syslog)
		  syslog (LOG_WARNING, "Can't turn on total discharge protection");
	    }
	}

      if (pws)
	{
	  if (Status[BS_PWS] != pws)
	    nr_count = 0;
	  if (pws == '1')
	    {
	    rsx11:
	      write (tty, "Sx11\r\n", 6);
	      if (trace)
		fprintf (trace, "Command: Sx11\n");
	      for (c = 0, i = 0; (i = read (tty, iline + c, (sizeof iline) - c)); c += i);
	      if (trace)
		{
		  iline[c] = 0;
		  fprintf (trace, "Answer: %s\n", iline);
		}
	      if (c != 4 || iline[0] != 'O' || iline[1] != 'K')
		if (nr_count++ < 3)
		  goto rsx11;
		else if (use_syslog)
		  syslog (LOG_WARNING, "Can't turn on power save mode");
	    }
	  else
	    {
	    rsx10:
	      write (tty, "Sx10\r\n", 6);
	      if (trace)
		fprintf (trace, "Command: Sx10\n");
	      for (c = 0, i = 0; (i = read (tty, iline + c, (sizeof iline) - c)); c += i);
	      if (trace)
		{
		  iline[c] = 0;
		  fprintf (trace, "Answer: %s\n", iline);
		}
	      if (c != 4 || iline[0] != 'O' || iline[1] != 'K')
		if (nr_count++ < 3)
		  goto rsx10;
		else if (use_syslog)
		  syslog (LOG_WARNING, "Can't turn off power save mode");
	    }
	}

      FD_ZERO (&fds);
      FD_SET (tty, &fds);
      start = 0;
      while (1)
	{
	  timeout.tv_sec = interval;
	  timeout.tv_usec = 0;
	rsel:
	  FD_SET (tty, &fds);
	  if (select (tty + 1, &fds, 0, 0, &timeout) != 0)
	    {
	      if (FD_ISSET (tty, &fds))
		{
		  for (c = 0, i = 0; (i = read (tty, iline + c, (sizeof iline) - c)); c += i);
		  if (trace)
		    {
		      iline[c] = 0;
		      fprintf (trace, "Unexpected answer: %s", iline);	/* answer usualy have \n at end */
		    }
		}
	      goto rsel;
	    }
	  /* now read status */
	  nr_count = 0;
	rs:
	  write (tty, "Ss\r\n", 4);
	  if (trace)
	    fprintf (trace, "Command: Ss\n");
	  /* read status string */
	  for (c = 0, i = 0; (i = read (tty, Status + c, (sizeof Status) - c)); c += i);
	  if (c == 0)
	    {
	      if (nr_count++ < 3)
		goto rs;
	      else
		{
		  if (use_syslog)
		    syslog (LOG_NOTICE, "Communication with UPS lost!");
		  goto ri;
		}
	    }
	  if (c != 10)
	    {
	      if (trace)
		{
		  Status[c] = 0;
		  fprintf (trace, "Short answer: %s\n", Status);
		}
	      goto rs;
	    }
	  if (trace)
	    {
	      Status[8] = 0;
	      fprintf (trace, "State: %s\n", Status);
	    }

	  /* check status */
	  if (Status[SS_OVERLOAD] == '1')
	    {			/* UPS overloaded */
	      if (use_syslog)
		syslog (LOG_WARNING, "UPS overloaded!");
	      /* UPS can operate no more 5 minutes, so do immediate reboot after 4 minutes */
	      if (!overload_time)
		(void) time (&overload_time);
	      else if (overload_time + 4 * 60 < time ((time_t *) NULL))		/* immediate shutdown */
		/* just set SS_BATTERY_LOW */
		Status[SS_BATTERY_LOW] = '1';
	    }
	  else if (overload_time)
	    {
	      if (use_syslog)
		syslog (LOG_INFO, "UPS overload obviated");
	      overload_time = 0;
	    }
	  if (Status[SS_BATTERY_LOW] == '1')
	    {			/* very bad, we must immidiate halt */
	      if (last_low_rep + lowinterval > time ((time_t *) NULL))
		continue;
	      if (use_syslog)
		syslog (LOG_CRIT, "UPS battery low! Shutdown immediately!");
	      unlink (pwrfile);
	      if ((fd = open (pwrfile, O_CREAT | O_WRONLY, 0644)) >= 0)
		{
		  write (fd, "LOWBATT\n", 8);
		  close (fd);
		  if (run_path)
		    {
		      if (fork () == 0)
			{
			  p = strrchr (run_path, '/') + 1;
			  av[0] = p;
			  av[1] = pwrfile;
			  av[2] = NULL;
			  close (tty);
			  execv (run_path, av);
			  exit (1);
			}
		    }
		  else
		    {
#ifndef TEST
		      if (kill (init_pid, SIGUSR2))
			syslog (LOG_CRIT, "Process %u doesn not exist! Can't send SIGPWR", init_pid);
#endif
		    }
		  time (&last_low_rep);
		  continue;
		}
	    }
	  if (Status[SS_POWER_FAIL] == '1')
	    {			/* mains suply absent */
	      if (prev == Status[SS_POWER_FAIL])	/* nothing changed */
		continue;
	      else
		{		/* report to init */
		  if (use_syslog)
		    syslog (LOG_WARNING, "Power fail!");
		  unlink (pwrfile);
		  if ((fd = open (pwrfile, O_CREAT | O_WRONLY, 0644)) >= 0)
		    {
		      write (fd, "FAIL\n", 5);
		      close (fd);
		      if (run_path)
			{
			  if (fork () == 0)
			    {
			      p = strrchr (run_path, '/') + 1;
			      av[0] = p;
			      av[1] = pwrfile;
			      av[2] = NULL;
			      close (tty);
			      execv (run_path, av);
			      exit (1);
			    }
			}
		      else
			{
#ifndef TEST
			  if (kill (init_pid, SIGINT))
			    syslog (LOG_CRIT, "Process %u doesn not exist! Can't send SIGPWR", init_pid);
#endif
			}
		      prev = Status[SS_POWER_FAIL];
		      continue;
		    }
		}
	    }
	  if (prev == '1')
	    {			/* mains suply return, report to init */
	      if (use_syslog)
		syslog (LOG_NOTICE, "Power restored!");
	      unlink (pwrfile);
	      if ((fd = open (pwrfile, O_CREAT | O_WRONLY, 0644)) >= 0)
		{
		  write (fd, "OK\n", 3);
		  close (fd);
		  if (run_path)
		    {
		      if (fork () == 0)
			{
			  p = strrchr (run_path, '/') + 1;
			  av[0] = p;
			  av[1] = pwrfile;
			  av[2] = NULL;
			  close (tty);
			  execv (run_path, av);
			  exit (1);
			}
		    }
		  else
		    {
#ifndef TEST
		      if (kill (init_pid, SIGHUP))
			syslog (LOG_CRIT, "Process %u doesn not exist! Can't send SIGPWR", init_pid);
#endif
		    }
		  prev = Status[SS_POWER_FAIL];
		}
	      if (use_syslog)
		ckbat = 1;
	    }
	  if (ckbat && use_syslog)
	    {
	    rckbat:
	      write (tty, "Bs\r\n", 4);
	      if (trace)
		fprintf (trace, "Command: Bs\n");
	      /* answer must be 19 bytes long */
	      for (c = 0, i = 0; (i = read (tty, Status + c, (sizeof Status) - c)); c += i);
	      if (c != 19)
		{
		  if (trace)
		    {
		      Status[c] = 0;
		      fprintf (trace, "Answer: %s\n", Status);	/* somethig wrong */
		    }
		  if (nr_count++ < 3)
		    goto rckbat;
		  else
		    {
		      if (use_syslog)
			syslog (LOG_NOTICE, "Communication with UPS lost!");
		      goto ri;
		    }
		}
	      else
		Status[17] = 0;
	      if (trace)
		fprintf (trace, "Answer: %s\n", Status);
	      if (Status[BS_POWER_FAIL] == '1')
		ckbat = 0;
	      if (Status[BS_POWER_OK] == '1' && Status[BS_BAT] == '0')
		{
		  ckbat = 0;	/* don't check in future */
		  syslog (LOG_INFO, "UPS battery completely recharged");
		}
	    }
	}			/* while(1) */
    }
  return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1