/* -*-mode:C;tab-width:8-*-
 * powerd   Monitor the UPS status.
 *
 * Copyright (C) 1993        Miquel van Smoorenburg
 * Copyright (C) 1995        Alessandro Rubini
 *
 * This program is designed to work with SysVInit-2.4 for Linux and later, by
 * Miquel van Smoorenburg. It may work with other operating systems.
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#define POWERD_RELEASE "2.0"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <syslog.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#define CFG_FILE     PREFIX "/etc/powerd.conf"

#ifdef linux
#  define PWRSTAT      "/etc/powerstatus"
#  define MOUNT_ROOT   "/sbin/mount -w -n -o remount /"
#  define MOUNT_ALL    "/sbin/mount -avt nonfs"
#else
#  error "Only Linux is supported, by now"
#endif

#define BIT_OUT 0x10000 /* to mark serial bits */

/*
 * PDEBUG(()) is enabled while compiling, and is only useful to code
 * developers
 */

#ifdef DEBUG
#  define PDEBUG(args) fprintf args
#else
#  define PDEBUG(args)
#endif
#define PDEBUGG(args)

/*
 * DEBUG_MODE instead depends on runtime options, and is there to help
 * understanding what is the program doing
 */

#define DEBUG_MODE (options[O_DEBUG].iv)

#define PERROR(s)    do { if(DEBUG_MODE) \
                            fprintf(stderr,"%s: %s: %s\n", \
				    currtime(),(s),strerror(errno)); \
                          else \
                            syslog(LOG_ERR,"%s: %s: %s\n",prgname,(s), \
				   strerror(errno)); \
		     } while(0)

#define MESSAGE(s)   do { if(DEBUG_MODE) \
                            fprintf(stderr,"%s: %s\n",currtime(),(s)); \
                          else \
                            syslog(LOG_ERR,"%s: %s\n",prgname,(s)); \
                     } while(0)


typedef struct Option {
  char *name;
  short multi_allowed;
  short usage; /* to detect multiple lines */
  enum { OT_BOOL,
	 OT_STRING,
	 OT_FILE,
	 OT_RSNAME,
	 OT_RS_ONENAME,
	 OT_STR_OR_LEV,
	 OT_INTEGER,
	 OT_END } type;
  int   iv;    /* integer */
  char *sv;    /* string  */
}    Option;

/*-------------------------------------------------------------------*/
/* This is the option database */

Option options[]= {

  {"disabled",     0, 0, OT_BOOL,           0, NULL},
  {"debug",        0, 0, OT_BOOL,           0, NULL},
  {"delay",        0, 0, OT_INTEGER,       10, NULL},
  {"sleepnow",     1, 0, OT_INTEGER,        0, NULL},
  {"cycletime",    0, 0, OT_INTEGER,     3600, NULL}, /* unused */
  {"remoteserver", 0, 0, OT_STRING,         0, NULL},
  {"serverport",   0, 0, OT_INTEGER,        0, NULL},
  {"serialline",   0, 0, OT_FILE,           0, NULL},
  {"monitor",      0, 0, OT_RSNAME, TIOCM_DTR, "DCD"},
  {"failwhen",     0, 0, OT_STR_OR_LEV,     0, "FAIL"},
  {"okwhen",       0, 0, OT_STRING,         0, "OK"},
  {"assertlow",    1, 0, OT_RSNAME,         0, NULL},
  {"asserthigh",   1, 0, OT_RSNAME,         0, NULL},
  {"readfifo",     0, 0, OT_FILE,           0, NULL},
  {"spawnmonitor", 0, 0, OT_STRING,         0, NULL},
  {"pollingstep",  1, 0, OT_INTEGER,       20, NULL},
  {"trials",       0, 0, OT_INTEGER,       18, NULL},
  {"timeout",      0, 0, OT_INTEGER,       10, NULL}, 
  {"logfile",      0, 0, OT_STRING,         0, NULL}, /* stderr by default */
  {"",             1, 0, OT_END,            0, NULL}
};

enum optnames {O_DISABLED,O_DEBUG,O_DELAY,O_SLEEPNOW,O_CYCLETIME,
		 O_REMOTESERVER,O_SERVERPORT,O_SERIALLINE,O_MONITOR,
		 O_FAILWHEN,O_OKWHEN,O_ASSERTLOW,O_ASSERTHIGH,O_READFIFO,
		 O_SPAWNMONITOR,O_POLLINGSTEP,O_TRIALS,O_TIMEOUT, O_LOGFILE};

struct client {
  int fd;                /* only used in tcp */
  struct sockaddr_in addr;  /* only used in udp */
  int addrlen;
  struct client *next;
  };

static int check_serial(int mode, int arg);
static int check_socket(int mode, int arg);
static int check_fifo(int mode, int arg);
static int check_child(int mode, int arg);

#define MODE_OPEN 0
#define MODE_READ 1

static inline int tellclients(int action, int serverfd, struct client *clPtr);
static int manageclient(int fd,struct client **list,fd_set *clientSet);

int powerfail(int action,int serverfd, struct client *list); /*  sys-dep */

enum actions {ACT_QUICKFAIL=-2, ACT_ABORT=-1,  /* not an fd to check */
		ACT_FAIL=0, ACT_OK=1,
		ACT_NOTHING=255, ACT_ASK=254
	      };

struct assert {
  int bit;
  int val;
  struct assert *next;
};

struct powerd_message {
  unsigned char cmd; /* silly, isn't it? */
  char magic[15];
};
 
#define CURRENT_MAGIC "powerd-2.0"

/*--------------------------------------------------------- globals -*/
struct assert *assertions=NULL; 
struct assert **nextassertion=&assertions;
int pollingmode=0;
char msgstring[80];
char prgname[64];

/*-------------------------------------------------------------------*/
/* Hmm... something to know our current status
 * This implementation is Linux specific
 */

static int current_status;

static int get_current_status(void)
{
int fd=-1;

unlink("/tmp/powerd.checkfile");
if ((fd=open("/tmp/powerd.checkfile", O_CREAT|O_WRONLY, 0644))<0
    && errno==EROFS)
  return ACT_FAIL;
close(fd);
return ACT_OK;
}

/*-------------------------------------------------------------------*/
/* Format the current time, so reports on stderr are more meaningful
 * when retrieved by a redirection file
 */
char *currtime(void)
{
  static time_t mytime;
  static char str_time[32];

  time(&mytime);
  strftime(str_time,32,"%a %b %d %H:%M:%S",localtime(&mytime));
  return str_time;
}


/*-------------------------------------------------------------------*/
/* This parses a single argument, either form the command line or
 * from a line in the configuration file. Escape sequences are not
 * interpreted, becuase it tries to remain small. The return value
 * is the number of errors up to now. Arguments are the oprion name and,
 * optional, the suggested value. If it is null, then the nameis split
 * at an equal sign or at the first blank space.
 */

int parseLine(char *opt,char *svalue)
{
  static int parserrors=0;
  char *value,*ptr;
  Option *optr;
  struct stat buf;

  if (opt[strlen(opt)-1]=='\n') opt[strlen(opt)-1]='\0';
  if (svalue && svalue[strlen(svalue)-1]=='\n') svalue[strlen(svalue)-1]='\0';

  PDEBUGG((stderr,"\tparseLine: \"%s\" \"%s\"\n",opt,svalue));

  /* retrieve the value */
 
  if (svalue)        /* preparsed */
    value=svalue; 
  else               /* parse it */
    {
    while (isspace(*opt))
      opt++; 
    if (*opt=='#' || *opt=='\0')
      return parserrors;
    if (  !(ptr=strchr(opt,'='))    /* '=' separates option and value */
       && !(ptr=strchr(opt,' '))    /* or it is a blank */
       && !(ptr=strchr(opt,'\t')))  /* or a tab */
      {ptr=""; value="";}           /* or there's no value */
    else
      {*ptr=0;value=ptr+1;}

    while (isspace(*value)) /* strip any blank */
      value++;
    for (ptr=opt+strlen(opt)-1; isblank(*opt); *(opt--)='\0')
      ;
    }

  /* Now, if the first *and* the last are quotes, remove them */

 if (*value=='"' && value[strlen(value)-1]=='"')
    {value++; value[strlen(value)-1]='\0';}

  /* MISSING: special chars (escape sequences) */

  if (DEBUG_MODE)
    fprintf(stderr,"%20s --> %s ",opt,value);

/*---------------------------- Now do the job */

  for (optr=options; optr->name[0] && strcmp(optr->name,opt); optr++)
    /* nothing */ ;

  if (optr->usage++ > 0 && !(optr->multi_allowed))
    {
    fprintf(stderr,"%s: \"%s\" is multiply defined\n",prgname,opt);
    return ++parserrors;
    }
     
  switch(optr->type)
    {
    case OT_BOOL:
      if (!value || !(value[0]) || tolower(value[0])!='n')
	optr->iv=1;
      else
	optr->iv=0;
      break;

    case OT_STRING:
      optr->sv=strdup(value);
      if (optr-options == O_REMOTESERVER) /* FIXME -- very dirty */
	parseLine("delay","0");
      break;
      
    case OT_FILE:
      optr->sv=strdup(value);
      if (lstat(value,&buf))
	{
	fprintf(stderr,"%s: %s: %s\n",prgname,value,strerror(errno));
	return parserrors;
	}
      optr->iv = buf.st_mode&S_IFMT;
      break;

    case OT_RSNAME:
      optr->sv=strdup(value);
      optr->iv='!';  /* just a warning */
      if (!strcasecmp(value,"rts")) optr->iv = TIOCM_RTS | BIT_OUT;
      if (!strcasecmp(value,"dtr")) optr->iv = TIOCM_DTR | BIT_OUT;
      if (!strcasecmp(value,"dcd")) optr->iv = TIOCM_CD;
      if (!strcasecmp(value,"dsr")) optr->iv = TIOCM_DSR;
      if (!strcasecmp(value,"cts")) optr->iv = TIOCM_CTS;

      /* if a pulse is required, we must record it, to execute after opening */
      if (optr-options==O_ASSERTHIGH || optr-options==O_ASSERTLOW)
	{
	if (!(optr->iv&BIT_OUT))
	  {
	  fprintf(stderr,"%s: can't assert \"%s\"\n",prgname,optr->sv);
          return ++parserrors;
	  }
	optr->iv &= ~BIT_OUT;
	*nextassertion=malloc(sizeof(struct assert));
	if (!*nextassertion) 
	  {
	  fprintf(stderr,"%s: malloc(): %s\n",prgname,strerror(errno));
	  return ++parserrors;
	  }
	(*nextassertion)->bit=optr->iv;
	(*nextassertion)->val= (optr-options==O_ASSERTHIGH ? 0xffff : 0);
	(*nextassertion)->next=NULL; nextassertion=&((*nextassertion)->next);
	}
      break;

    case OT_STR_OR_LEV:
      optr->sv=strdup(value);
      optr->iv='!';  /* just a warning */
      if (!strcasecmp(value,"low"))  optr->iv=0;
      if (!strcasecmp(value,"high")) optr->iv=0xffff;
      break;

    case OT_INTEGER:
      optr->iv=atoi(value);
      if (optr-options == O_SLEEPNOW)
	{
	if (DEBUG_MODE) fprintf(stderr,"Sleeping");
	sleep(optr->iv);
	}
      if (optr-options == O_POLLINGSTEP)
	pollingmode++;
      break;
      
    default:
      fprintf(stderr,"%s: Unknown option \"%s\"\n",prgname,opt);
      break;
    }
  if (DEBUG_MODE)
    fprintf(stderr," (%i)\n",optr->iv);

  return parserrors;
}

/*-------------------------------------------------------------------*/


/*-------------------------------------------------------------------*/
/*
 * Here we are, this is the main function, which sets it all up
 */
int main(int argc, char **argv)
{
  int i, sensefd, serverfd=-1, maxfd;
  char *cfg_file=CFG_FILE;
  int cfg_file_specified;
  int (*handler)(int mode, int arg)=NULL;
  struct timeval timeout;
  fd_set selSet, clientSet;
  struct   sockaddr_in serveraddr;
  struct client *clientlist=NULL;

  int status, newstatus;

  sprintf(prgname,"%s[%i]",argv[0],getpid());

/*
 * If the last arguments are "-port <port>", the program should accept
 * client connections on that very tcp port and speak to them.
 */

  if (argc>2 && !strcmp(argv[argc-2],"-port"))
    {
    if (parseLine("serverport",argv[argc-1])) exit(1);
    argc-=2;
    }
  else if (argc>3 && !strcmp(argv[argc-3],"-host"))
    {
    if (parseLine("remoteserver",argv[argc-2]) ||
	parseLine("serverport",argv[argc-1]))
      exit(1);
    argc-=3;
    cfg_file=NULL;
    }


/*
 * If there is a single argument, it is a file. It can be:
 *     a fifo                then, wait for "OK" and/or "FAIL"
 *     a char device         then, check DTR, like to old powerd used to do
 *     a regular file        use it as a config file
 * otherwise issue an error
 */

  if (argc>1 && !strchr(argv[1],'=')) /* not an option */
    {
    struct stat buf;
    int type;

    if (lstat(argv[1],&buf)<0)
      {
      fprintf(stderr,"%s: %s: %s\n",prgname,argv[1],strerror(errno));
      exit(1);
      }
    switch(type = buf.st_mode&S_IFMT)
      {
      case S_IFREG:
	     cfg_file=argv[1]; cfg_file_specified++; break; /* use it */
      case S_IFIFO:
	     parseLine("readfifo",argv[1]); break;
      case S_IFSOCK: fprintf(stderr,"%s: %s: Is a socket\n",prgname,argv[1]);
	     exit(1);
      case S_IFBLK: fprintf(stderr,"%s: %s: Is a block device\n",prgname,argv[1]);
	     exit(1);
      case S_IFDIR: fprintf(stderr,"%s: %s: %s\n",prgname,argv[1],strerror(EISDIR));
	     exit(1);

      case S_IFCHR: 
	     /*
	      * Ok, it is chr dev, so we must be compatible with powerd-1.1
	      */
	     parseLine("serialline",argv[1]);
	     parseLine("monitor","DCD");
	     parseLine("failwhen","low");
	     parseLine("pollingstep","20");
	     break;

      default: fprintf(stderr,"%s: %s: Can't manage such node\n",prgname,argv[1]);
	      exit(1);
      }
    argc--; argv++;
    }

/*
 * If there are no arguments, read the configuration file.
 */

  if (argc < 2 && cfg_file)
    {
    FILE *f=fopen(cfg_file,"r");
    char s[256];
    int errors=0;

    if (!f && cfg_file_specified)
      {
      fprintf(stderr,"%s: %s: %s\n",prgname,cfg_file,strerror(errno));
      exit(1);
      }
    while (f && fgets(s,256,f))
      errors=parseLine(s,NULL);
    if (errors) exit(errors);
    }

/*
 * Get options from args
 */

  while (argc>1)
    {
    parseLine(argv[1],NULL);
    argc--; argv++;
    }

/*
 * Ok, now we consider running
 */

  if (options[O_DISABLED].iv)
    fprintf(stderr,"Powerd version " POWERD_RELEASE " (disabled)\n");

/*
 * Check for unique task
 */

  handler=NULL;
  i=0;
  if (options[O_REMOTESERVER].sv) 
    {
    handler=check_socket; i++;
    if(DEBUG_MODE)
      fprintf(stderr,"%s: should reach %s\n",
	      prgname, options[O_REMOTESERVER].sv);
    }
  if (options[O_READFIFO].sv)
    {
    handler=check_fifo; i++;
    if(DEBUG_MODE)
      fprintf(stderr,"%s: should read fifo %s\n",
	      prgname, options[O_READFIFO].sv);
    }
  if (options[O_SERIALLINE].sv)
    {
    handler=check_serial; i++; pollingmode++;
    if(DEBUG_MODE)
      fprintf(stderr,"%s: should rd serial %s\n",
	      prgname,options[O_SERIALLINE].sv);
    }
  if (options[O_SPAWNMONITOR].sv)
    {
    handler=check_child; i++;
    if(DEBUG_MODE)
      fprintf(stderr,"%s: should rd child %s\n",
	      prgname,options[O_SPAWNMONITOR].sv);
    }

  if (i!=1)
    {
    if (i) fprintf(stderr,"%s: Ambiguous setup\n",prgname);
    else   fprintf(stderr,"%s: Nothing to check\n",prgname);
    exit(1);
    }

/*
 * Redirect messages, if such requested
 */

  if (options[O_LOGFILE].sv)
    {
    do
      {
      int fd;

      if (DEBUG_MODE) 
	{
	sprintf(msgstring,"Trying to redirect stderr to \"%s\"",
		options[O_LOGFILE].sv);
	MESSAGE(msgstring);
	}
      if ((fd=open(options[O_LOGFILE].sv,O_WRONLY | O_APPEND))<0)
	{ MESSAGE("Failed to redirect stderr"); break; }

      close(fd);
      if (!freopen(options[O_LOGFILE].sv,"a",stderr))
	{
	printf("%s: %s: %s\n",prgname,options[O_LOGFILE].sv,strerror(errno));
	break;
	}
      setvbuf(stderr,NULL,_IONBF,0); /* prevent delays */
      }
    while(0);
    }
    

/*====================================================== Daemonize */

  if (options[O_LOGFILE].sv || !DEBUG_MODE)
    {
    switch(i=fork()) 
      {
      case 0: /* Child */
	chdir("/");
	setsid();
	break;

      case -1: /* Error */
	fprintf(stderr,"%s: %s\n",prgname,strerror(errno));
	exit(1);

      default: /* Parent */
	exit(0);
      }
    }
  sprintf(msgstring,"powerd[%i]",getpid());
/* openlog(msgstring, LOG_PID, LOG_DAEMON); */

  sensefd=(*handler)(MODE_OPEN,0);
  if (sensefd==ACT_ABORT)
    { closelog(); exit(1); }
  if (sensefd==ACT_QUICKFAIL)
    {
    MESSAGE("Server is dead, dying too...");
    powerfail(ACT_FAIL,-1,NULL);
    exit(1);
    }

  fcntl(sensefd,F_SETFL,fcntl(sensefd,F_GETFL)|O_NONBLOCK);

  /* How are we? */
  current_status=get_current_status();
  sprintf(msgstring,"We are %s user",
	  current_status==ACT_OK ? "multi" : "single");
  MESSAGE(msgstring);

  /*
   * Now, we should open the server node, if needed
   */

  if (options[O_REMOTESERVER].sv==NULL && options[O_SERVERPORT].iv!=0)

    do /* just to use break */
      {
      int len;
      if (DEBUG_MODE)
	fprintf(stderr,"Opening udp socket...\n");
      serverfd=socket(AF_INET, SOCK_DGRAM, 0);
      if (serverfd==-1) {PERROR("socket()"); break;}

      serveraddr.sin_family = AF_INET;
      serveraddr.sin_addr.s_addr = INADDR_ANY;
      serveraddr.sin_port = htons(options[O_SERVERPORT].iv);
      if (bind(serverfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)))
	{ PERROR("bind()"); close(serverfd); serverfd=-1; break; }

      len = sizeof(serveraddr);
      if (getsockname(serverfd, (struct sockaddr *)&serveraddr, &len))
	{ PERROR("getsockname()"); close(serverfd); serverfd=-1; break; }

      if (DEBUG_MODE) 
	fprintf(stderr, "Got udp port %i\n",
		ntohs(serveraddr.sin_port));
      listen(serverfd,5);
      }
    while (0);

/*..................................................... main loop */

  FD_ZERO(&clientSet);
  maxfd = (sensefd>serverfd) ? sensefd : serverfd;
  timeout.tv_sec=options[O_POLLINGSTEP].iv;
  while (1)
    {
    int pending;

    selSet=clientSet;
    FD_SET(sensefd,&selSet);
    if (serverfd>=0)
      FD_SET(serverfd,&selSet);
    pending=select(maxfd+1,&selSet,(fd_set *)NULL,(fd_set *)NULL,&timeout);
    timeout.tv_sec=options[O_POLLINGSTEP].iv; /* reset it now */
    if (pollingmode && !FD_ISSET(sensefd,&selSet)) 
      {pending++; FD_SET(sensefd,&selSet); }
    if (pending<1)
      continue;

    if (FD_ISSET(sensefd,&selSet))
      do
	{ /* just to use break */
	pending--;
	status=(*handler)(MODE_READ,sensefd);
	if (status==ACT_NOTHING)
	  continue;
	if (status==current_status)
	  {
	  PDEBUGG((stderr,"%s: Nothing new\n",currtime()));
	  break;
	  }
	else
	  {
	  fd_set newSet;
	  struct timeval to={options[O_DELAY].iv,0};
	  
	  /*
	   * Try to sleep for a while, and if there's sth new, read it.
	   * I've had a lot of problem in avoiding race conditions, but
	   * it should work well now.
	   */
#if 0	  
	  sprintf(msgstring, "WARNING: The power is %s",
		  status==ACT_FAIL ? "failing" : "back");
	  MESSAGE(msgstring);
#else
	  if (status==ACT_FAIL) MESSAGE("Advisory: the power is failing");
	  else                  MESSAGE("Advisory: the power is back");
#endif
	  if (DEBUG_MODE && options[O_DELAY].iv) MESSAGE("Delaying action...");
	  sleep(options[O_DELAY].iv);
	  FD_ZERO(&newSet); FD_SET(sensefd,&newSet);
	  newstatus=status; /* default */
	  if (select(sensefd+1,&newSet,NULL,NULL,&to)==1 || pollingmode)
	    newstatus=(*handler)(MODE_READ,sensefd); /* if it changes */
	  if (DEBUG_MODE)
	    {
	    sprintf(msgstring,"current %i, info %i, delayed %i",
		    current_status,status,newstatus);
	    MESSAGE(msgstring);
	    }
	  if (newstatus == ACT_NOTHING) newstatus=status;
	  if (newstatus == current_status)
	    MESSAGE("'Twas a glitch");
	  else
	    powerfail(newstatus, serverfd, clientlist);
	  } 
	} /* do */
      while (0);

    if (!pending) continue;

    if (serverfd>=0 && FD_ISSET(serverfd,&selSet))
      {
      pending--;
      manageclient(serverfd,&clientlist,&clientSet);
      }

    if (pending) /* something to read (tcp) */
      {
      i=manageclient(0,&clientlist,&selSet);
      if (i>=0) FD_CLR(i,&clientSet);
      }
    }
  MESSAGE("Aaargh -- fallen out of the loop");
  exit(1); /* shouldn't happen */
}


/*-------------------------------------------------------------------*/

/*
 * This section embeds the engines, the different monitoring utilities.
 *
 * You're free to add new engines.
 */

static inline int checkstring(char *buffer)
{
  if (strstr(buffer,options[O_FAILWHEN].sv)) return ACT_FAIL;
  if (strstr(buffer,options[O_OKWHEN].sv)) return ACT_OK;
  return ACT_NOTHING;      
}

#define RDBUFLEN 256
static char buffer[RDBUFLEN];

/*.........................................................................*/
static int check_serial(int mode, int arg)
{
int fd,i;
static int bit, goodval, prevval=BIT_OUT; /* impossible value */
struct assert *curr, *prev;

  if (mode==MODE_OPEN)
    {
    if (options[O_SERIALLINE].iv != S_IFCHR)
      {
      sprintf(msgstring,"%s is not a char device",options[O_SERIALLINE].sv);
      MESSAGE(msgstring);
      return ACT_ABORT;
      }
    if (options[O_MONITOR].iv=='!' || options[O_FAILWHEN].iv=='!')
      {
      sprintf(msgstring,"Wrong options for \"monitor\" or \"failwhen\"");
      MESSAGE(msgstring);
      return ACT_ABORT;
      }
    if (options[O_MONITOR].iv & BIT_OUT)
      {
      sprintf(msgstring,"Can't monitor an outgoing line (\"%s\")",
	      options[O_MONITOR].sv);
      MESSAGE(msgstring);
      return ACT_ABORT;
      }
    if ((fd = open(options[O_SERIALLINE].sv, O_RDWR | O_NDELAY)) < 0) 
      {
      PERROR(options[O_SERIALLINE].sv);
      return ACT_ABORT;
      }

    /* perform all the assertions we're required to */
    prev=NULL;curr=assertions;
    while (curr)
      {
      if (DEBUG_MODE)
	fprintf(stderr,"%s: Assert %i to %i\n",currtime(),curr->bit,curr->val);
      ioctl(fd,TIOCMGET,&bit);
      bit &= (~(curr->bit));
      bit |= (curr->val & curr->bit);
      ioctl(fd,TIOCMSET,&bit);
      if (prev) free(prev);
      prev=curr; curr=curr->next;
      sleep(1);
      }

    /* opened, now we must remember what to look for */
    bit=options[O_MONITOR].iv; goodval=~(options[O_FAILWHEN].iv) & bit;
    ioctl(arg,TIOCMGET,&i);

    return fd;
    }
  /* real monitoring */
  ioctl(arg,TIOCMGET,&i);
  i= (i&bit)^goodval;
  if (i!=prevval)
    MESSAGE("Line change detected");
  prevval=i;
  return i ? ACT_FAIL : ACT_OK;
}

/*.........................................................................*/
static int check_socket(int mode, int arg)
{
int i,j, len;
struct powerd_message buf;
static struct hostent *hostPtr;
char *host=options[O_REMOTESERVER].sv;
static struct sockaddr_in remote;
int fd=arg;
FILE *f;
fd_set set;

  buf.cmd=ACT_ASK;
  strcpy(buf.magic,CURRENT_MAGIC);

  if (mode==MODE_OPEN) /* must connect */
    {
    sprintf(msgstring,"Connecting to %s",host);
    MESSAGE(msgstring);
    hostPtr=gethostbyname(host);
    if (!hostPtr) { PERROR("gethostbyname()"); return ACT_ABORT; }

    bzero((char *) &remote, sizeof(remote));
    bcopy(hostPtr->h_addr,(char *)&remote.sin_addr,hostPtr->h_length);
    remote.sin_family=hostPtr->h_addrtype;
    remote.sin_port=(unsigned short)htons(options[O_SERVERPORT].iv);

    fd=socket(AF_INET, SOCK_DGRAM, 0);
    if (fd==-1) { PERROR("socket()"); return ACT_ABORT; }

    for (i=0;i<options[O_TRIALS].iv;i++)
      {
      struct timeval to={options[O_TIMEOUT].iv,0};

      if (i) MESSAGE("still trying...");

      if (sendto(fd,&buf,sizeof(buf),0,(struct sockaddr *)&remote,
		 sizeof(remote))<0)
	PERROR("sendto()");
      FD_ZERO(&set); FD_SET(fd,&set);
      if (select(fd+1,&set,NULL,NULL,&to)<=0)
	{ MESSAGE("No reply from server"); continue; }

      PDEBUGG((stderr,"%s: Readable\n",currtime()));
      len=sizeof(remote);
      j=recvfrom(fd,&buf,sizeof(buf), 0, (struct sockaddr *)&remote, &len);
      if (j<0) {PERROR("recvfrom()"); continue;}
      PDEBUGG((stderr,"%s: Got %i bytes (action %i) from port %i on %s\n",
	      currtime(),
	      j, buf.cmd, ntohs(remote.sin_port), inet_ntoa(remote.sin_addr)));

      /* Check it really is the server. FIXME -- it should obviously be */
      if ( ntohs(remote.sin_port)!=options[O_SERVERPORT].iv ||
	   memcmp(&(remote.sin_addr),hostPtr->h_addr,hostPtr->h_length) )
	{ MESSAGE("Bogus message from unknown sender"); continue;}
      break;
      }
    if (i==options[O_TRIALS].iv) 
      {
      close(fd);
      /* return (int)ACT_QUICKFAIL; */
      exit(3);
      }
    if (i) MESSAGE("Succeeded");
    return fd;
    }
/*
 * mode != open: we got a real message (or have to ask about)
 */

  if (pollingmode)
    {
    struct timeval to={0,0};
    
    FD_ZERO(&set); FD_SET(fd,&set);
    if (select(fd+1,&set,NULL,NULL,&to)<1) /* empty, go on asking */
      {
      if(DEBUG_MODE) MESSAGE("Polling server");
      buf.cmd=ACT_ASK; strcpy(buf.magic,CURRENT_MAGIC); 
      if (sendto(fd,&buf,sizeof(buf),0,(struct sockaddr *)&remote,
		 sizeof(remote))<0)
	PERROR("sendto()");
      return ACT_NOTHING;   /* select will bring us the reply */
      }
    }

  len=sizeof(remote);
  j=recvfrom(fd,&buf,sizeof(buf), 0, (struct sockaddr *)&remote, &len);
  if (j<0) {PERROR("recvfrom()"); return ACT_NOTHING; }
  PDEBUGG((stderr,"%s: Got %i bytes (action %i) from port %i on %s\n",
	  currtime(),
	  j, buf.cmd, ntohs(remote.sin_port), inet_ntoa(remote.sin_addr)));

   /* Check it really is the server. FIXME -- it should obviously be*/
  if ( ntohs(remote.sin_port)!=options[O_SERVERPORT].iv ||
      memcmp(&(remote.sin_addr),hostPtr->h_addr,hostPtr->h_length) )
    { MESSAGE("Bogus message from unknown sender"); return ACT_NOTHING;}
  if (j!=sizeof(buf) || strcmp(buf.magic,CURRENT_MAGIC))
    { MESSAGE("Bogus message from server"); return ACT_NOTHING;}
  if (DEBUG_MODE) MESSAGE("Got packet");
  return buf.cmd;
}

/*.........................................................................*/
static int check_fifo(int mode, int arg)
{
int i;

  if (mode==MODE_OPEN)
    {
    if (options[O_READFIFO].iv != S_IFIFO)
      {
      sprintf(msgstring,"%s is not a fifo",options[O_READFIFO].sv);
      MESSAGE(msgstring);
      return ACT_ABORT;
      }
    sprintf(msgstring,"waiting for \"%s\" or \"%s\"\n",
	      options[O_FAILWHEN].sv,options[O_OKWHEN].sv);
    MESSAGE(msgstring);
    return open(options[O_READFIFO].sv,O_RDONLY|O_NONBLOCK);
    }

  /*
   * Then, it is identical to reading a tcp socket
   */
  i=read(arg,buffer,RDBUFLEN);
  buffer[i]='\0';
  if (i==0)
    {MESSAGE("Connection is lost\n"); exit(1);}
  PDEBUGG((stderr,"%s: Got \"%s\"\n",currtime(),buffer));
  if (DEBUG_MODE) MESSAGE("Got new info");
  return checkstring(buffer);
 }

/*.........................................................................*/
static int check_child(int mode, int arg)
{
FILE *f;

  if (mode==MODE_OPEN)
    {
    f=popen(options[O_SPAWNMONITOR].sv,"r");
    if (!f) { PERROR(options[O_SPAWNMONITOR].sv); return ACT_ABORT;}
    return fileno(f);
    }

  return check_fifo(mode,arg);
	      
}    

/*-------------------------------------------------------------------*/
/*
 * This handles clients, for udp
 */

static int manageclient(int fd,struct client **list,fd_set *clientSet)
{
struct client *clnt=NULL;
struct sockaddr_in addr;
int len=sizeof(addr);

/*
 * This is only called on "connection". The client must be
 * registered on the list, and the current power status is returned to it.
 */

  struct powerd_message buf;
  int count;

  if (fd==0)
    {
    MESSAGE("Unknown socket selected - fatal\n");
    exit(1);
    }
  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = INADDR_ANY;

  count=recvfrom(fd,&buf,sizeof(buf), 0, (struct sockaddr *)&addr, &len);
  if (count<0) {PERROR("recvfrom()"); return 0;}

  if (count != sizeof(struct powerd_message))
    {
    sprintf(msgstring,"invalid message size (%i) from %s",count,
	    inet_ntoa(addr.sin_addr));
    MESSAGE(msgstring);
    return -1;
    }
  if (strcmp(buf.magic,CURRENT_MAGIC) || buf.cmd!=ACT_ASK)
    {
    MESSAGE("Bad packet received");
    return -1;
    }

  /* Identify this client in the list (only the client, not thw port */
  for (clnt=*list; clnt; clnt=clnt->next)
    if (!memcmp(&(clnt->addr.sin_addr),&(addr.sin_addr),sizeof(addr.sin_addr)))
      break;

  if (clnt)
    {
    if (DEBUG_MODE)
      {
      sprintf(msgstring,"Client %s is polling",inet_ntoa(addr.sin_addr));
      MESSAGE(msgstring);
      }
    }
  else
    {
    if (DEBUG_MODE)
      {
      sprintf(msgstring,"New client %s",inet_ntoa(addr.sin_addr));
      MESSAGE(msgstring);
      }
    clnt=malloc(sizeof(struct client));
    if (!clnt) { PERROR("malloc()"); return -1; }
    clnt->addr=addr; clnt->addrlen=len;
    clnt->next=*list;
    *list=clnt;
    }

  /* tell the current status */
  buf.cmd=current_status;
  strcpy(buf.magic,CURRENT_MAGIC);
  sendto(fd,&buf,sizeof(buf),0,(struct sockaddr *)&addr,len);
  PDEBUGG((stderr,"%s: Sent %i\n",currtime(),buf.cmd));

  return 0;

}

static inline int tellclients(int action, int serverfd, struct client *clPtr)
{
  struct powerd_message buf;
  struct client *ptr;
  int i;

  buf.cmd = action;
  strcpy(buf.magic,CURRENT_MAGIC);

  for (i=0; i<3; i++)  /* three times: isn't udp unreliable? */
    {
    for( ptr=clPtr; ptr; ptr=ptr->next)
      {
      sendto(serverfd,&buf,sizeof(buf),0,
	     (struct sockaddr *)&(ptr->addr),sizeof(ptr->addr));
      PDEBUGG((stderr,"%s: Told %s about\n",currtime(),
	      inet_ntoa(ptr->addr.sin_addr)));
      }
    sleep(1);
    }

  return 0;
}

/*-------------------------------------------------------------------*/
/*
 * This function, finally, is in charge of speaking to init. It is
 * system dependent, and systemwide configuration is documented in
 * README.`uname` in the source directory. (Not yet...)
 */

int powerfail(int action, int serverfd, struct client *clPtr)
{
  int fd;
  struct client *prev=NULL;

  PDEBUGG((stderr,"%s: powerfail(%i)\n",currtime(),action));
  if (action!=ACT_FAIL && action!=ACT_OK) return 0;

  if (action!=ACT_NOTHING)
    {
    MESSAGE(action==ACT_FAIL ? "Power is failing" 
	    : "Power is back");
    tellclients(action, serverfd, clPtr);
    }

  current_status=action; /* do it now, and do it quick. */
    for ( ; clPtr; prev=clPtr, clPtr=clPtr->next)
      free(prev);

  if (options[O_DISABLED].iv)
    {
    char s[64];
    sprintf(s,"powerfail(%i) -- disabled",action);
    MESSAGE(s);
    return 0;
    }
  

#ifdef linux                 /******** This is the linux way to do it *******/


  if (get_current_status()==ACT_FAIL)
    {
    /*
     * We have a read-only filesystem.
     * This information is used to know if we must proceed or not.
     * Only in single user mode we have a read-only filesystem.
     * I know of no other means to know the current runlevel.
     * The program runlevel, in SysVInit-2.5 only works with 2.5 and later.
     * I'll look to upgrade, sooner or later.
     */
    if (action==ACT_FAIL)
      {
      PDEBUGG((stderr,"%s: Already single-user\n",currtime()));
      return 0;
      }
    /*
     * Otherwise, we need to remount root r/w and "mount -avt nonfs"
     */
    MESSAGE("Remounting filesystems");
    system(MOUNT_ROOT);
    system(MOUNT_ALL);
    }
  else if (action==ACT_OK)
    {
    PDEBUGG((stderr,"%s: Already multi-user\n",currtime()));
    return 0;
    }
     
  /* Create an info file for init. */
  unlink(PWRSTAT);
  fd = open(PWRSTAT, O_CREAT|O_WRONLY, 0644);
  
  if (fd<0)
    {
    PERROR(PWRSTAT);
    return 1; /* maybe next time... */
    }

  if (action==ACT_OK)
    write(fd, "OK\n", 3);
  else
    write(fd, "FAIL\n", 5);
  close(fd);

  MESSAGE("Telling init about...");
  kill(1, SIGPWR);

#else                                 /******** Open to enhancments *******/
	  f=popen("wall","w");
  if (!f) f=popen("/etc/wall","w");
  if (!f) f=popen("/sbin/wall","w");
  if (!f) f=stderr;
  fprintf(f,"The power is going %s\n", action==ACT_OK ? "up" : "down");
  if (f!=stderr) pclose(f);

#endif

  return 0;
}

