/*
 *	openupsd.c -- Belkin UPS status getter (serial port version)
 *	Copyright (C) 2002-2003 Fred Barnes <frmb2@ukc.ac.uk>
 *
 *	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.
 */

/*
 * the protocol documentation was obtained from:
 *     http://www.exploits.org/nut/library/protocols/belkin.html
 *
 * Thanks guys :)
 */

/*{{{  includes, defines, etc.*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <stdarg.h>

#include "support.h"
#include "openupsd.h"

#ifdef SUPPORT_SYSLOG
#include <syslog.h>
#endif

#ifndef VERSION
#error VERSION not defined..!  autoconf/automake well ?
#endif
/*}}}*/
/*{{{  globals*/

char *progname = NULL;

/*}}}*/
/*{{{  statics*/

static struct sigaction old_sigint, old_sigchld, old_sighup;
static volatile int sigdata[3];		/* SIGINTs, SIGCHLDs, SIGHUPs */
static volatile int sigflag;
static sigset_t tsigset;		/* used to avoid slight races */

static char *field_strings[] = UPSD_DS_STRINGS;

/*}}}*/

/*{{{  static void show_help (FILE *stream)*/
/*
 *	shows the help/usage-info
 */
static void show_help (FILE *stream)
{
	fprintf (stream, "%s " VERSION " -- Belkin UPS monitoring program (use their tools for configuration)\n", progname);
	fprintf (stream, "Usage: %s [options]\n", progname);
	fprintf (stream, "where options are:\n");
	fprintf (stream, "\t-h | --help            show this help\n");
	fprintf (stream, "\t-V | --version         show version and exit\n");
	fprintf (stream, "\t-c | --config  <file>  sets the config file\n");
	fprintf (stream, "\t-v | --verbose         be verbose (VERBOSE config option)\n");
	fprintf (stream, "\t-t | --tty             don\'t daemonise (NODAEMON config option)\n");
	fprintf (stream, "\t-p | --pidfile <file>  write PID to specified file\n");
	fprintf (stream, "If no configuration file is specified with -c or --config, the default config\n");
	fprintf (stream, "will be read from " SYSCONFDIR "/openupsd.conf, if it exists.");
	fprintf (stream, "The --tty option will cause any logging to be done on stderr.\n");
	return;
}
/*}}}*/
/*{{{  static void show_version (FILE *stream)*/
/*
 *	shows the version
 */
static void show_version (FILE *stream)
{
	fprintf (stream, "openupsd " VERSION "\n");
	return;
}
/*}}}*/

/*{{{  static int set_serial_state (openupsd_sdev_t *device)*/
/*
 *	sets up a serial port.  return 0 on success, -1 on error
 */
static int set_serial_state (openupsd_sdev_t *device)
{
	struct termios term;
	speed_t baud;

	if (device->fd > -1) {
		return -1;
	}
	device->fd = open (device->device, O_RDWR | O_NONBLOCK | O_NOCTTY);
	if (device->fd < 0) {
		return -1;
	}
	if (tcgetattr (device->fd, &term) < 0) {
		/* failing this is pretty serious, avoid knackered restore later */
		close (device->fd);
		device->fd = -1;
		return -1;
	}
	memcpy (&(device->saved_state), &term, sizeof (struct termios));

	switch (device->s_baud) {
	case 1200: baud = B1200; break;
	case 2400: baud = B2400; break;
	case 4800: baud = B4800; break;
	case 9600: baud = B9600; break;
	case 19200: baud = B19200; break;
	case 38400: baud = B38400; break;
	default:
		return -1;
	}
	if (cfsetospeed (&term, baud) < 0) {
		return -1;
	}
	if (cfsetispeed (&term, baud) < 0) {
		return -1;
	}
	term.c_cflag &= ~CSIZE;
	term.c_cflag |= CS8;
	term.c_lflag &= ~(ISIG | ICANON | ECHO);
	/* term.c_iflag = 0; */
	term.c_oflag &= ~OPOST;
	/* term.c_cc[VMIN] = 1;
	term.c_cc[VTIME] = 5; */
	/* term.c_cflag &= ~(PARENB | PARODD); */
	tcsetattr (device->fd, TCSANOW, &term);
	return 0;
}
/*}}}*/
/*{{{  static int restore_serial_state (openuspd_sdev_t *device)*/
/*
 *	restores serial state and closes the device.  returns 0 on success, -1 on error
 */
static int restore_serial_state (openupsd_sdev_t *device)
{
	int err;

	if (device->fd < 0) {
		/* already closed */
		return 0;
	}
	err = tcsetattr (device->fd, TCSANOW, &(device->saved_state));
	close (device->fd);
	device->fd = -1;
	if (err < 0) {
		return -1;
	}
	return 0;
}
/*}}}*/
/*{{{  static void microdelay (int n)*/
/*
 *	delays for n micro-seconds
 */
static void microdelay (int n)
{
	struct timeval tv = {(n / 1000000), (n % 1000000)};

	select (0, NULL, NULL, NULL, &tv);
	return;
}
/*}}}*/

/*{{{  static void openupsd_log (openupsd_t *upsinfo, int urgency, const char *fmt, ...)*/
/*
 *	dumps a message to some log file, or syslog
 */
void openupsd_log (openupsd_t *upsinfo, int urgency, const char *fmt, ...)
{
	va_list ap;

	va_start (ap, fmt);
	if (!upsinfo->daemonise) {
		/* stuck in foreground, use stderr */
		vfprintf (stderr, fmt, ap);
		fprintf (stderr, "\n");
	}
#ifdef SUPPORT_SYSLOG
	else if (upsinfo->use_syslog) {
		vsyslog (LOG_DAEMON | urgency, fmt, ap);
	}
#endif
	else if (upsinfo->logfile) {
		vfprintf (upsinfo->logfile, fmt, ap);
		fprintf (upsinfo->logfile, "\n");
	}
	va_end (ap);
	return;
}
/*}}}*/
/*{{{  SIGHANDLER   void openupsd_sighandler (int signum)*/
void openupsd_sighandler (int signum)
{
	switch (signum) {
	case SIGINT:
		sigdata[0]++;
		break;
	case SIGCHLD:
		sigdata[1]++;
		break;
	case SIGHUP:
		sigdata[2]++;
		break;
	}
	sigflag++;
	return;
}
/*}}}*/
/*{{{  static int init_signal_handlers (void)*/
static int init_signal_handlers (void)
{
	struct sigaction act_int, act_chld, act_hup;
	int i = 0;

	/* bimey, what a mess -- seems that .sa_sigaction and .sa_handler are really the same thing..! */
	/* save old handlers */
	sigaction (SIGINT, NULL, &old_sigint);
	sigaction (SIGCHLD, NULL, &old_sigchld);
	sigaction (SIGHUP, NULL, &old_sighup);

	sigemptyset ((&act_int.sa_mask));
	sigaddset ((&act_int.sa_mask), SIGINT);
	sigaddset ((&act_int.sa_mask), SIGCHLD);
	sigaddset ((&act_int.sa_mask), SIGHUP);
	sigemptyset ((&act_chld.sa_mask));
	sigaddset ((&act_chld.sa_mask), SIGINT);
	sigaddset ((&act_chld.sa_mask), SIGCHLD);
	sigaddset ((&act_chld.sa_mask), SIGHUP);
	sigemptyset ((&act_hup.sa_mask));
	sigaddset ((&act_hup.sa_mask), SIGINT);
	sigaddset ((&act_hup.sa_mask), SIGCHLD);
	sigaddset ((&act_hup.sa_mask), SIGHUP);
	act_int.sa_sigaction = NULL;
	act_int.sa_handler = openupsd_sighandler;
	act_int.sa_flags = 0;
	act_chld.sa_sigaction = NULL;
	act_chld.sa_handler = openupsd_sighandler;
	act_chld.sa_flags = SA_RESTART | SA_NOCLDSTOP;		/* not interested in stopping/etc. children */
	act_hup.sa_sigaction = NULL;
	act_hup.sa_handler = openupsd_sighandler;
	act_hup.sa_flags = SA_RESTART;

	if (sigaction (SIGINT, &act_int, NULL)) {
		i++;
	}
	if (sigaction (SIGCHLD, &act_chld, NULL)) {
		i++;
	}
	if (sigaction (SIGHUP, &act_hup, NULL)) {
		i++;
	}
	if (i) {
		return -1;
	}

	/* make sure the application gets these */
	sigemptyset (&tsigset);
	sigaddset (&tsigset, SIGINT);
	sigaddset (&tsigset, SIGCHLD);
	sigaddset (&tsigset, SIGHUP);
	if (sigprocmask (SIG_UNBLOCK, &tsigset, NULL)) {
		return -1;
	}
	return 0;
}
/*}}}*/
/*{{{  static void restore_signal_handlers (void)*/
static void restore_signal_handlers (void)
{
	/* don't care if we fail, really */
	sigaction (SIGINT, &old_sigint, NULL);
	sigaction (SIGCHLD, &old_sigchld, NULL);
	sigaction (SIGHUP, &old_sighup, NULL);
	return;
}
/*}}}*/

/*{{{  static int null_read (int fd, int size)*/
/*
 *	tries to read some data, just to throw it away
 */
static int null_read (int fd, int size)
{
	static char nullbuffer[4096];

	return read (fd, nullbuffer, (size > 4096) ? 4096 : size);
}
/*}}}*/
/*{{{  static int serial_write (int fd, unsigned char *buffer, int len, int retry, int interval)*/
/*
 *	writes stuff to the serial port
 */
static int serial_write (int fd, unsigned char *buffer, int len, int retry, int interval)
{
	int gone = 0;
	int tries = retry;

	while (gone < len) {
		int n = write (fd, buffer + gone, len - gone);

		if ((n < 0) && (errno == EAGAIN)) {
			if (!tries) {
				return -1;
			}
			microdelay (interval);
			tries--;
			continue;
		} else if (n < 0) {
			return -1;
		}
		tries = retry;
		gone += n;
	}
	return gone;
}
/*}}}*/
/*{{{  static int serial_read (int fd, unsigned char *buffer, int len, int retry, int interval)*/
/*
 *	reads stuff from the serial port
 */
static int serial_read (int fd, unsigned char *buffer, int len, int retry, int interval)
{
	int got = 0;
	int tries = retry;

	while (got < len) {
		int n = read (fd, buffer + got, len - got);

		if ((n < 0) && (errno == EAGAIN)) {
			if (!tries) {
				return -1;
			}
			microdelay (interval);
			tries--;
			continue;
		} else if (n < 0) {
			return -1;
		}
		tries = retry;
		got += n;
	}
	return got;
}
/*}}}*/

/*{{{  static int str_devstatusfield (openupsd_ds_t *ds, int field, char *str)*/
/* 
 * NOTE: assumes "str" is big enough (it won't write more than 128 bytes tho)
 * returns -1 on error, number of bytes popped into "str" otherwise
 */
static int str_devstatusfield (openupsd_ds_t *ds, int field, char *str)
{
	int r, x;

	if (!ds || !str) {
		return -1;
	}
	r = 0;
	switch (field) {
	case UPSD_DS_MODEL:
		r += sprintf (str, "%s.%s: %s\n", ds->devname, field_strings[field], ds->model_name);
		break;
	case UPSD_DS_BAT_COND:
		r += sprintf (str, "%s.%s: %s\n", ds->devname, field_strings[field], ((ds->bat_flags & UPSD_BAT_WEAK_MASK) == UPSD_BAT_WEAK) ? "weak" : "normal");
		break;
	case UPSD_DS_BAT_IS:
		r += sprintf (str, "%s.%s: %s\n", ds->devname, field_strings[field], ((ds->bat_flags & UPSD_BAT_DISCHARGE_MASK) == UPSD_BAT_DISCHARGE) ? "discharging" : "charging");
		break;
	case UPSD_DS_OUT_FROM:
		r += sprintf (str, "%s.%s: %s\n", ds->devname, field_strings[field], ((ds->out_flags & UPSD_OUT_INVERTER_MASK) == UPSD_OUT_INVERTER) ? "inverter" : "utility");
		break;
	case UPSD_DS_BAT_VOLTS:
	case UPSD_DS_IN_FREQ:
	case UPSD_DS_IN_VOLTS:
	case UPSD_DS_OUT_FREQ:
	case UPSD_DS_OUT_VOLTS:
		{
			int ifield = 0;

			switch (field) {
			case UPSD_DS_BAT_VOLTS:	ifield = ds->x_bvolts; break;
			case UPSD_DS_IN_FREQ:	ifield = ds->x_ifreq; break;
			case UPSD_DS_IN_VOLTS:	ifield = ds->x_ivolts; break;
			case UPSD_DS_OUT_FREQ:	ifield = ds->x_ofreq; break;
			case UPSD_DS_OUT_VOLTS:	ifield = ds->x_ovolts; break;
			}
			r += sprintf (str, "%s.%s: %.1f\n", ds->devname, field_strings[field], (double)ifield / 10.0);
		}
		break;
	case UPSD_DS_BAT_TEMP:
	case UPSD_DS_BAT_CHARGE:
	case UPSD_DS_OUT_LOAD:
		{
			int ifield = 0;

			switch (field) {
			case UPSD_DS_BAT_TEMP:	ifield = ds->btemp; break;
			case UPSD_DS_BAT_CHARGE:	ifield = ds->bcharge; break;
			case UPSD_DS_OUT_LOAD:	ifield = ds->out_load; break;
			}
			r += sprintf (str, "%s.%s: %d\n", ds->devname, field_strings[field], ifield);
		}
		break;
	case UPSD_DS_STATUS:
		x = sprintf (str, "%s.%s:", ds->devname, field_strings[field]);
		r += x;
		str += x;
		if (ds->st_flags & UPSD_ST_OVERHEAT) {
			x = sprintf (str, " overheat");
			r += x;
			str += x;
		}
		if (ds->st_flags & UPSD_ST_ONBATTERY) {
			x = sprintf (str, " on-battery");
			r += x;
			str += x;
		} else {
			x = sprintf (str, " on-line");
			r += x;
			str += x;
		}
		if (ds->st_flags & UPSD_ST_OUTPUTBAD) {
			x = sprintf (str, " output-bad");
			r += x;
			str += x;
		}
		if (ds->st_flags & UPSD_ST_OVERLOAD) {
			x = sprintf (str, " overload");
			r += x;
			str += x;
		}
		if (ds->st_flags & UPSD_ST_BYPASSBAD) {
			x = sprintf (str, " bypass-bad");
			r += x;
			str += x;
		}
		if (ds->st_flags & UPSD_ST_OUTPUTOFF) {
			x = sprintf (str, " output-off");
			r += x;
			str += x;
		}
		if (ds->st_flags & UPSD_ST_CHARGERBAD) {
			x = sprintf (str, " charger-bad");
			r += x;
			str += x;
		}
		if (ds->st_flags & UPSD_ST_UPSOFF) {
			x = sprintf (str, " ups-off");
			r += x;
			str += x;
		}
		if (ds->st_flags & UPSD_ST_FANFAIL) {
			x = sprintf (str, " fan-failure");
			r += x;
			str += x;
		}
		if (ds->st_flags & UPSD_ST_FUSEBREAK) {
			x = sprintf (str, " fuse-break");
			r += x;
			str += x;
		}
		if (ds->st_flags & UPSD_ST_FAULT) {
			x = sprintf (str, " ups-fault");
			r += x;
			str += x;
		}
		if (ds->st_flags & UPSD_ST_AWAITPOWER) {
			x = sprintf (str, " awaiting-power");
			r += x;
			str += x;
		}
		if (ds->st_flags & UPSD_ST_BUZZERON) {
			x = sprintf (str, " buzzer-alarm-on");
			r += x;
			str += x;
		} else {
			x = sprintf (str, " buzzer-alaram-off");
			r += x;
			str += x;
		}
		r += sprintf (str, "\n");
		break;
	default:
		r += sprintf (str, "%s.unknown-%d: none\n", ds->devname, field);
		break;
	}
	return r;
}
/*}}}*/
/*{{{  static void getalarmstr (openupsd_trigger_t *alarm, char *str)*/
static void getalarmstr (openupsd_trigger_t *alarm, char *str)
{
	if ((unsigned int)alarm < UPSD_ALARM_MAXINT) {
		strcpy (str, "<unknown/internal>");
	} else {
		strcpy (str, alarm->name);
	}
	return;
}
/*}}}*/
/*{{{  static int parse_devstatusfield (openupsd_ds_t *ds, char *str)*/
/*
 *	parses some data and puts it in "ds" appropriately
 *	returns 0 on success, -1 on failure
 */
static int parse_devstatusfield (openupsd_ds_t *ds, char *str)
{
	char *ch, *dh;

	/* make sure there's no newline at the end */
	if ((dh = strchr (str, '\n')) != NULL) {
		*dh = '\0';
	}
	/* scan for dot after device name */
	ch = strchr (str, '.');
	if (!ch) {
		return -1;
	}
	ch++;
	if (!strncmp (ch, "model: ", 7)) {
		strncpy (ds->model_name, ch + 7, 31);
	} else if (!strncmp (ch, "battery-condition: ", 19)) {
		ds->bat_flags &= ~UPSD_BAT_WEAK_MASK;
		if (ch[19] == 'w') {
			ds->bat_flags |= UPSD_BAT_WEAK;
		}
	} else if (!strncmp (ch, "battery-is: ", 12)) {
		ds->bat_flags &= ~UPSD_BAT_DISCHARGE;
		if (ch[12] == 'd') {
			ds->bat_flags |= UPSD_BAT_DISCHARGE;
		}
	} else if (!strncmp (ch, "battery-voltage: ", 17)) {
		int h, l;

		if (sscanf (ch + 17, "%d.%d", &h, &l) != 2) {
			return -1;
		}
		ds->x_bvolts = (h * 10) + l;
	} else if (!strncmp (ch, "battery-temperature: ", 21)) {
		if (sscanf (ch + 21, "%d", &(ds->btemp)) != 1) {
			return -1;
		}
	} else if (!strncmp (ch, "battery-charge: ", 16)) {
		if (sscanf (ch + 16, "%d", &(ds->bcharge)) != 1) {
			return -1;
		}
	} else if (!strncmp (ch, "input-frequency: ", 17)) {
		int h, l;

		if (sscanf (ch + 17, "%d.%d", &h, &l) != 2) {
			return -1;
		}
		ds->x_ifreq = (h * 10) + l;
	} else if (!strncmp (ch, "input-voltage: ", 15)) {
		int h, l;

		if (sscanf (ch + 15, "%d.%d", &h, &l) != 2) {
			return -1;
		}
		ds->x_ivolts = (h * 10) + l;
	} else if (!strncmp (ch, "output-from: ", 13)) {
		ds->out_flags &= ~UPSD_OUT_INVERTER_MASK;
		if (ch[13] == 'i') {
			ds->out_flags |= UPSD_OUT_INVERTER;
		}
	} else if (!strncmp (ch, "output-frequency: ", 18)) {
		int h, l;

		if (sscanf (ch + 18, "%d.%d", &h, &l) != 2) {
			return -1;
		}
		ds->x_ofreq = (h * 10) + l;
	} else if (!strncmp (ch, "output-voltage: ", 16)) {
		int h, l;

		if (sscanf (ch + 16, "%d.%d", &h, &l) != 2) {
			return -1;
		}
		ds->x_ovolts = (h * 10) + l;
	} else if (!strncmp (ch, "output-load: ", 13)) {
		if (sscanf (ch + 13, "%d", &(ds->out_load)) != 1) {
			return -1;
		}
	} else if (!strncmp (ch, "status-field: ", 14)) {
		/* scan options */
		ds->st_flags = 0;
		for (dh = ch + 14; dh && (*dh != '\0');) {
			if (!strncmp (dh, "overheat", 8)) {
				ds->st_flags |= UPSD_ST_OVERHEAT;
			} else if (!strncmp (dh, "on-battery", 10)) {
				ds->st_flags |= UPSD_ST_ONBATTERY;
			} else if (!strncmp (dh, "on-line", 7)) {
				/* SKIP */
			} else if (!strncmp (dh, "output-bad", 10)) {
				ds->st_flags |= UPSD_ST_OUTPUTBAD;
			} else if (!strncmp (dh, "overload", 8)) {
				ds->st_flags |= UPSD_ST_OVERLOAD;
			} else if (!strncmp (dh, "bypass-bad", 10)) {
				ds->st_flags |= UPSD_ST_BYPASSBAD;
			} else if (!strncmp (dh, "output-off", 10)) {
				ds->st_flags |= UPSD_ST_OUTPUTOFF;
			} else if (!strncmp (dh, "charger-bad", 11)) {
				ds->st_flags |= UPSD_ST_CHARGERBAD;
			} else if (!strncmp (dh, "ups-off", 7)) {
				ds->st_flags |= UPSD_ST_UPSOFF;
			} else if (!strncmp (dh, "fan-failure", 11)) {
				ds->st_flags |= UPSD_ST_FANFAIL;
			} else if (!strncmp (dh, "fuse-break", 10)) {
				ds->st_flags |= UPSD_ST_FUSEBREAK;
			} else if (!strncmp (dh, "ups-fault", 9)) {
				ds->st_flags |= UPSD_ST_FAULT;
			} else if (!strncmp (dh, "awaiting-power", 14)) {
				ds->st_flags |= UPSD_ST_AWAITPOWER;
			} else if (!strncmp (dh, "buzzer-alarm-on", 15)) {
				ds->st_flags |= UPSD_ST_BUZZERON;
			} else if (!strncmp (dh, "buzzer-alarm-off", 16)) {
				ds->st_flags &= ~UPSD_ST_BUZZERON;
			} else {
				/* unknown flag */
				return -1;
			}
			dh = strchr (dh, ' ');
			if (dh) {
				dh++;
			}
		}
	} else {
		return -1;
	}
	return 0;
}
/*}}}*/
/*{{{  static int dump_devstatus (openupsd_ds_t *ds, FILE *ostream)*/
/*
 *	dumps device status in `ds' in text to `ostream'
 *	returns number of bytes written
 */
static int dump_devstatus (openupsd_ds_t *ds, FILE *ostream)
{
	int r, i;
	char lstr[128];

	if (!ostream || !ds) {
		return -1;
	}
	r = 0;
	for (i=0; i<13; i++) {
		int x = str_devstatusfield (ds, i, lstr);

		if (x < 0) {
			return -1;
		} else {
			r += fprintf (ostream, "%s", lstr);
		}
	}
	return r;
}
/*}}}*/
/*{{{  static int do_dump_devstatus (openupsd_ds_t *ds, openupsd_ofile_t *ofile)*/
/*
 *	dumps device status to ofile_t sort, putting in a temporary first, then moving.  chmod's the file to 0644
 */
static int do_dump_devstatus (openupsd_ds_t *ds, openupsd_ofile_t *ofile)
{
	FILE *temp;
	char tmpname[FILENAME_MAX];
	int r, tfd;

	if (strlen (ofile->fname) > (FILENAME_MAX - 12)) {
		return -1;
	}
	sprintf (tmpname, "%s.%d.tmp", ofile->fname, getpid());
	if (!access (tmpname, F_OK)) {
		return -1;
	}
	tfd = open (tmpname, O_CREAT | O_TRUNC | O_RDWR | O_NOCTTY, 0644);
	if (tfd < 0) {
		unlink (tmpname);
		return -1;
	}
	temp = fdopen (tfd, "w");
	if (!temp) {
		close (tfd);
		return -1;
	}
	r = dump_devstatus (ds, temp);
	if (r < 0) {
		fclose (temp);
		unlink (tmpname);
		return -1;
	} else {
		fclose (temp);
		if (rename (tmpname, ofile->fname)) {
			return -1;
		}
	}
	return 0;

}
/*}}}*/
/*{{{  static int rclient_shift_buffer (openupsd_rclient_t *rc)*/
static int rclient_shift_buffer (openupsd_rclient_t *rc)
{
	int r = rc->gone;

	if (!rc->gone || !rc->outbuf) {
		return 0;
	}
	if (!rc->left) {
		/* reset buffer pointer (gone) */
		rc->gone = 0;
	} else {
		memmove (rc->outbuf, rc->outbuf + rc->gone, rc->left);
		rc->gone = 0;
	}
	return r;
}
/*}}}*/
/*{{{  static int do_queue_netdata (openupsd_ds_t *upsdata, openupsd_rclient_t *rc)*/
/*
 *	enqueues device stats in "upsdata" for the remotely connected client in "rc".
 *	returns -1 on failure (queue got too big), bytes enqueued otherwise
 */
static int do_queue_netdata (openupsd_ds_t *upsdata, openupsd_rclient_t *rc)
{
	char *lbuf;
	int lbufsize;
	int x, i;

	if (!upsdata || !rc) {
		return 0;
	}
	/* allocate a sensibly sized local buffer to start with */
	lbufsize = 512;
	lbuf = (char *)smalloc (lbufsize);

	/* popluate lbuf with all the data first */
	x = 0;
	/* header */
	x += sprintf (lbuf, "BEGIN UPS DATABLOCK VERSION " VERSION "\n");
	for (i=0; i<13; i++) {
		int y;

		if ((lbufsize - x) < ((i == 12) ? 192 : 128)) {
			/* need some more buffer (bit more at the end)*/
			lbuf = (char *)srealloc (lbuf, lbufsize, lbufsize + 512);
			lbufsize += 512;
		}
		y = str_devstatusfield (upsdata, i, lbuf + x);
		if (y < 0) {
			/* errored somewhere -- clean up */
			sfree (lbuf);
			return -1;
		}
		x += y;
	}
	/* footer */
	i = sprintf (lbuf + x, "END UPS DATABLOCK VERSION " VERSION "\n");
	x += i;

	/* see if it'll fit in the existing client buffer, if there is one */
	if (!rc->outbuf) {
		/* use this buffer */
		rc->outbuf = lbuf;
		rc->bufsize = lbufsize;
		rc->gone = 0;
		rc->left = x;
	} else {
		rclient_shift_buffer (rc);		/* ensures left-alignedness of any buffer */
		if ((x + rc->left) > rc->bufsize) {
			/* maybe make more room */
			if (rc->bufsize >= 8192) {
				/* nope, it's too big already..! */
				sfree (lbuf);
				return -1;
			}
			rc->outbuf = (char *)srealloc (rc->outbuf, rc->bufsize, rc->bufsize + x);	/* be generous */
			rc->bufsize = rc->bufsize + x;
		}
		/* copy data and free local buffer */
		memcpy (rc->outbuf + rc->left, lbuf, x);
		rc->left += x;
		sfree (lbuf);
	}
	return x;
}
/*}}}*/
/*{{{  static int check_extract_netdata (openupsd_ds_t *upsdata, openupsd_netcli_t *ns)*/
/*
 *	attempts to extract a complete entry from the buffer client `ns' and stuff it in `upsdatabuffer'
 *	returns 1 on something extracted, 0 on nothing complete to extract, -1 on error.
 *	automatically adjusts the buffer when returning 1.
 */
static int check_extract_netdata (openupsd_ds_t *upsdata, openupsd_netcli_t *ns)
{
	char *buffer = ns->inbuf;
	int buflen = ns->inbytes;
	static char *this_version = VERSION;
	static int this_maj = -1, this_min = 0, this_patch = 0;
	char *ch, *dh;
	int left;
	int vmaj, vmin, vpatch;
	char expect[64];
	int elen;

	if (this_maj < 0) {
		if (sscanf (this_version, "%d.%d.%d", &this_maj, &this_min, &this_patch) != 3) {
			return -1;
		}
	}
	buffer[buflen] = '\0';		/* other code makes sure there's room for this */

	/* check for block start */
	if (buflen < 36) {
		return 0;		/* not enough to test */
	}
	if (strncmp (buffer, "BEGIN UPS DATABLOCK VERSION ", 28)) {
		/* bad start.. */
		return -1;
	}
	ch = strchr (buffer, '\n');
	if (!ch) {
		/* no newline (yet) something bad about that.. */
		return -1;
	}
	/* pick up version */
	if (sscanf (buffer + 28, "%d.%d.%d", &vmaj, &vmin, &vpatch) != 3) {
		/* bad version layout.. */
		return -1;
	}
	/* oki, scan forwards from ch looking for END ++ [buffer FROM 6 FOR 22] ++ <version> */
	elen = sprintf (expect, "END UPS DATABLOCK VERSION %d.%d.%d\n", vmaj, vmin, vpatch);

	/* this isn't terribly efficient, but it'll do.. (sometime when i've got the knuth books or bayer-moore (?) algorithm to hand i think..) */
	left = (buflen - (int)(ch - buffer)) - 1;
	for (dh = ch+1; (left >= elen) && (*dh != '\0'); dh++, left--) {
		int x;

		for (x = 0; (x < elen) && (dh[x] == expect[x]); x++);
		if (x == elen) {
			/* found it :) */
			left = 0;
			break;	/* for() */
		}
		dh += x;
		left -= x;
	}
	if (!left) {
		/* found a complete one.  dh points at where "expect" was found, ch + 1 is where the data starts */
		memset (upsdata, 0, sizeof (openupsd_ds_t));		/* zero buffer before filling it.. */
		upsdata->devname = ns->name;

		/* run through, line by line */
		for (ch++; ch != dh; ch++) {
			char *line = ch;

			ch = strchr (ch, '\n');
			if (!ch) {
				/* something not right */
				return -1;
			}
			*ch = '\0';		/* terminate line */
			if (parse_devstatusfield (upsdata, line)) {
				/* failed to parse something there.. */
				return -1;
			}
		}
		/* dandy */
		dh += elen;	/* skip to possible start of next record */
		left = (int)(dh - buffer);	/* size of this record */

		if (ns->inbytes > left) {
			memmove (ns->inbuf, ns->inbuf + left, (ns->inbytes - left));
			ns->inbytes -= left;
		} else {
			/* one record exactly */
			ns->inbytes = 0;
		}
		/* we keep the buffer.. */
		return 1;
	}
	/* nowt found, read some more */
	return 0;
}
/*}}}*/

/*{{{  static int do_command (openupsd_sdev_t *device, unsigned char *to_ups, int to_len, unsigned char *from_ups, int from_len)*/
/*
 *	sends a command and wants for a response (using the defined protocol)
 */
static int do_command (openupsd_sdev_t *device, unsigned char *to_ups, int to_len, unsigned char *from_ups, int from_len)
{
	int i, tlen;

	tcflush (device->fd, TCIFLUSH);
	if (serial_write (device->fd, to_ups, to_len, 3, 100000) != to_len) {
		return -1;
	}

	microdelay (100000);
	/* wait for a response.  do this by first reading 7 bytes */
	if (serial_read (device->fd, from_ups, 7, 4, 100000) != 7) {
		return -1;
	}

	if (memcmp (from_ups, "~00", 3)) {
		return -1;
	}
	if ((from_ups[3] != 'R') && (from_ups[3] != 'D')) {
		return -1;
	}
	for (tlen = 0, i=4; i<7; i++) {
		tlen *= 10;
		if ((from_ups[i] < '0') || (from_ups[i] > '9')) {
			return -1;
		}
		tlen += (from_ups[i] - '0');
	}
	if (tlen > (from_len - 8)) {
		return -1;
	}
	if (serial_read (device->fd, from_ups + 7, tlen, 3, 100000) != tlen) {
		return -1;
	}
	return (tlen + 7);
}
/*}}}*/
/*{{{  static int init_loops (openupsd_t *upsinfo, openupsd_sdev_t *device, int max, int req)*/
/*
 *	strange initialisation stuff
 */
static int init_loops (openupsd_t *upsinfo, openupsd_sdev_t *device, int max, int req)
{
	int left = max;
	int got = 0;

	null_read (device->fd, 127);

	if (upsinfo->verbose) {
		openupsd_log (upsinfo, LOG_INFO, "Looking for %s on serial device %s...", device->name, device->device);
	}
	while (left && (got < req)) {
		unsigned char buf[32];
		int i, p;

		p = 0;
		if (serial_write (device->fd, "~\003\002\001\000\204", 6, 3, 100000) != 6) {
			if (upsinfo->verbose) {
				openupsd_log (upsinfo, LOG_NOTICE, "Write error while initialising device %s", device->name);
			}
			return -1;
		}
		for (i=0; i<10; i++) {
			int n = read (device->fd, buf + p, 4);

			if ((n < 0) && (errno == EAGAIN)) {
				microdelay (100000);
			} else if (n < 0) {
				if (upsinfo->verbose) {
					openupsd_log (upsinfo, LOG_NOTICE, "Read error while initialising device %s", device->name);
				}
				return -1;
			} else {
				i--;
				p += n;
			}
		}
		if ((p == 7) && !memcmp (buf, "~00R000", 7)) {
			got++;
		} else {
			left--;
		}
		microdelay (100000);
	}
	if (got == req) {
		if (upsinfo->verbose) {
			openupsd_log (upsinfo, LOG_INFO, "Found UPS device successfully");
		}
		return 0;
	}
	if (upsinfo->verbose) {
		openupsd_log (upsinfo, LOG_NOTICE, "Failed to find a UPS device");
	}
	return -1;
}
/*}}}*/
/*{{{  static int stock_initialisation (openupsd_t *upsinfo, openupsd_sdev_t *device)*/
/*
 *	does some stock initialisation stuff
 */
static int stock_initialisation (openupsd_t *upsinfo, openupsd_sdev_t *device)
{
	int n;
	static unsigned char pbuf[128];

	if ((n = do_command (device, "~00P003MNU", 10, pbuf, 127)) < 0) {
		return -1;
	}
	if (upsinfo->verbose) {
		pbuf[n] = '\0';
		openupsd_log (upsinfo, LOG_INFO, "%s manufacturer: %s", device->name, pbuf + 7);
	}
	if ((n = do_command (device, "~00P003MOD", 10, pbuf, 127)) < 0) {
		return -1;
	}
	if (upsinfo->verbose) {
		pbuf[n] = '\0';
		openupsd_log (upsinfo, LOG_INFO, "%s model: %s", device->name, pbuf + 7);
	}
	if ((n = do_command (device, "~00P003VER", 10, pbuf, 127)) < 0) {
		return -1;
	}
	if (upsinfo->verbose) {
		pbuf[n] = '\0';
		openupsd_log (upsinfo, LOG_INFO, "%s firmware: %s", device->name, pbuf + 7);
	}
	return 0;
}
/*}}}*/
/*{{{  static int grab_data (openupsd_sdev_t *device, openupsd_ds_t *ds) */
/*
 *	grabs data from the device specified by `device' and sticks it into `ds'
 */
static int grab_data (openupsd_sdev_t *device, openupsd_ds_t *ds)
{
	int n, fields;
	static unsigned char pbuf[1024];
	unsigned char *ch, *dh;

	if ((n = do_command (device, "~00P003MOD", 10, pbuf, 1023)) < 0) {
		return -1;
	}
	ds->devname = device->name;
	if ((n - 7) > 31) {
		n = 38;
	}
	pbuf[n] = '\0';
	strcpy (ds->model_name, pbuf + 7);
	if ((n = do_command (device, "~00P003STB", 10, pbuf, 1023)) < 0) {
		return -1;
	}
	pbuf[n] = '\0';
	ch = pbuf;
	ds->bat_flags = 0;
	ds->x_bvolts = 0;
	ds->btemp = 0;
	ds->bcharge = 0;
	for (fields = 0; fields < 10; fields++) {
		for (dh = ch; (*dh != ';') && (*dh != '\0'); dh++);
		if ((*dh == '\0') && (fields < 9)) {
			return -1;
		} else {
			*dh = '\0';
		}
		switch (fields) {
		case 1:
			if (*ch != '0') {
				ds->bat_flags |= UPSD_BAT_WEAK;
			}
			break;
		case 2:
			if (*ch != '1') {
				ds->bat_flags |= UPSD_BAT_DISCHARGE;
			}
			break;
		case 6:
			ds->x_bvolts = atoi (ch);
			break;
		case 8:
			ds->btemp = atoi (ch);
			break;
		case 9:
			ds->bcharge = atoi (ch);
			break;
		}
		if (fields < 9) {
			ch = dh + 1;
		}
	}
	if ((n = do_command (device, "~00P003STI", 10, pbuf, 1023)) < 0) {
		return -1;
	}
	pbuf[n] = '\0';
	ch = pbuf;
	ds->x_ifreq = 0;
	ds->x_ivolts = 0;
	for (fields = 0; fields < 3; fields++) {
		for (dh = ch; (*dh != ';') && (*dh != '\0'); dh++);
		if ((*dh == '\0') && (fields < 2)) {
			return -1;
		} else {
			*dh = '\0';
		}
		switch (fields) {
		case 1:
			ds->x_ifreq = atoi (ch);
			break;
		case 2:
			ds->x_ivolts = atoi (ch);
			break;
		}
		if (fields < 2) {
			ch = dh + 1;
		}
	}
	if ((n = do_command (device, "~00P003STO", 10, pbuf, 1023)) < 0) {
		return -1;
	}
	pbuf[n] = '\0';
	ch = pbuf + 7;
	ds->out_flags = 0;
	ds->x_ofreq = 0;
	ds->x_ovolts = 0;
	ds->out_load = 0;
	for (fields = 0; fields < 7; fields++) {
		for (dh = ch; (*dh != ';') && (*dh != '\0'); dh++);
		if ((*dh == '\0') && (fields < 6)) {
			return -1;
		} else {
			*dh = '\0';
		}
		switch (fields) {
		case 0:
			if (*ch == '1') {
				ds->out_flags |= UPSD_OUT_INVERTER;
			}
			break;
		case 1:
			ds->x_ofreq = atoi (ch);
			break;
		case 3:
			ds->x_ovolts = atoi (ch);
			break;
		case 6:
			ds->out_load = atoi (ch);
			break;
		}
		if (fields < 6) {
			ch = dh + 1;
		}
	}
	if ((n = do_command (device, "~00P003STA", 10, pbuf, 1023)) < 0) {
		return -1;
	}
	pbuf[n] = '\0';
	ch = pbuf + 7;
	ds->st_flags = 0;
	for (fields = 0; fields < 16; fields++) {
		for (dh=ch; (*dh != ';') && (*dh != '\0'); dh++);
		if ((*dh == '\0') && (fields < 15)) {
			return -1;
		} else {
			*dh = '\0';
		}
		switch (fields) {
		case 0:
			if (*ch == '1') {
				ds->st_flags |= UPSD_ST_OVERHEAT;
			}
			break;
		case 1:
			if (*ch == '1') {
				ds->st_flags |= UPSD_ST_ONBATTERY;
			}
			break;
		case 2:
			if (*ch == '1') {
				ds->st_flags |= UPSD_ST_OUTPUTBAD;
			}
			break;
		case 3:
			if (*ch == '1') {
				ds->st_flags |= UPSD_ST_OVERLOAD;
			}
			break;
		case 4:
			if (*ch == '1') {
				ds->st_flags |= UPSD_ST_BYPASSBAD;
			}
			break;
		case 5:
			if (*ch == '1') {
				ds->st_flags |= UPSD_ST_OUTPUTOFF;
			}
			break;
		case 7:
			if (*ch == '1') {
				ds->st_flags |= UPSD_ST_CHARGERBAD;
			}
			break;
		case 8:
			if (*ch == '1') {
				ds->st_flags |= UPSD_ST_UPSOFF;
			}
			break;
		case 9:
			if (*ch == '1') {
				ds->st_flags |= UPSD_ST_FANFAIL;
			}
			break;
		case 10:
			if (*ch == '1') {
				ds->st_flags |= UPSD_ST_FUSEBREAK;
			}
			break;
		case 11:
			if (*ch == '1') {
				ds->st_flags |= UPSD_ST_FAULT;
			}
			break;
		case 12:
			if (*ch == '1') {
				ds->st_flags |= UPSD_ST_AWAITPOWER;
			}
			break;
		case 15:
			if (*ch == '1') {
				ds->st_flags |= UPSD_ST_BUZZERON;
			} else if (*ch == '2') {
				ds->st_flags &= ~UPSD_ST_BUZZERON;
			}
		}
		if (fields < 15) {
			ch = dh + 1;
		}
	}
	return 0;

}
/*}}}*/

/*{{{  static void remove_outfile (openupsd_ofile_t *ofile, openupsd_sdev_t *sdevs, openupsd_netcli_t *netclis)*/
/*
 *	removes an output file from the serial-device and remote-device things
 */
static void remove_outfile (openupsd_ofile_t *ofile, openupsd_sdev_t *sdevs, openupsd_netcli_t *netclis)
{
	openupsd_sdev_t *ts;
	openupsd_netcli_t *ns;

	for (ts = sdevs; ts; ts = ts->next) {
		dynarray_rmitem (ts->outfiles, ofile);
	}
	for (ns = netclis; ns; ns = ns->next) {
		dynarray_rmitem (ns->outfiles, ofile);
	}
	return;
}
/*}}}*/
/*{{{  static void remove_sdev (openupsd_t *upsinfo, openupsd_sdev_t *sdev)*/
/*
 *	removes a serial device from the sdev list in `upsinfo'
 */
static void remove_sdev (openupsd_t *upsinfo, openupsd_sdev_t *sdev)
{
	if (sdev == upsinfo->sdev) {
		/* removing from list head */
		upsinfo->sdev = sdev->next;
		if (upsinfo->sdev) {
			upsinfo->sdev->prev = NULL;
		}
	} else if (!sdev->next) {
		/* removing from list tail */
		sdev->prev->next = NULL;
	} else {
		/* removing from list middle */
		sdev->prev->next = sdev->next;
		sdev->next->prev = sdev->prev;
	}
	return;
}
/*}}}*/
/*{{{  static void remove_netsvr (openupsd_t *upsinfo, openupsd_netsvr_t *netsvr)*/
/*
 *	removes a network server device from the netsvr list in `upsinfo'
 *	also removes any references to it in "sdev" or "netcli" in `upsinfo'
 */
static void remove_netsvr (openupsd_t *upsinfo, openupsd_netsvr_t *netsvr)
{
	openupsd_sdev_t *ts;
	openupsd_netcli_t *ns;

	for (ts = upsinfo->sdev; ts; ts = ts->next) {
		dynarray_rmitem (ts->outtcpsvrs, netsvr);
	}
	for (ns = upsinfo->netcli; ns; ns = ns->next) {
		dynarray_rmitem (ns->outtcpsvrs, netsvr);
	}
	if (netsvr == upsinfo->netsvr) {
		/* removing from list head */
		upsinfo->netsvr = netsvr->next;
		if (upsinfo->netsvr) {
			upsinfo->netsvr->prev = NULL;
		}
	} else if (!netsvr->next) {
		/* removing from list tail */
		netsvr->prev->next = NULL;
	} else {
		/* removing from list middle */
		netsvr->prev->next = netsvr->next;
		netsvr->next->prev = netsvr->prev;
	}
	return;
}
/*}}}*/
/*{{{  static void remove_netcli (openupsd_t *upsinfo, openupsd_netcli_t *netcli)*/
static void remove_netcli (openupsd_t *upsinfo, openupsd_netcli_t *netcli)
{
	if (netcli == upsinfo->netcli) {
		/* removing from list head */
		upsinfo->netcli = netcli->next;
		if (upsinfo->netcli) {
			upsinfo->netcli->prev = NULL;
		}
	} else if (!netcli->next) {
		/* removing from list tail */
		netcli->prev->next = NULL;
	} else {
		/* removing from list middle */
		netcli->prev->next = netcli->next;
		netcli->next->prev = netcli->prev;
	}
	return;
}
/*}}}*/
/*{{{  static int checkfortrigger (openupsd_trigger_t *trig, openupsd_ds_t *data)*/
/* returns truth value (0 or 1) */
static int checkfortrigger (openupsd_trigger_t *trig, openupsd_ds_t *data)
{
	switch (trig->trig) {
	case UPSD_DS_MODEL:		/* EQ, NE.  RHS is string */
		if (!strcmp (data->model_name, (char *)(trig->rhs))) {
			return (trig->comp == UPSD_CMP_EQ);
		} else {
			return (trig->comp == UPSD_CMP_NE);
		}
		break;
	case UPSD_DS_BAT_COND:		/* EQ, NE.  RHS is int, 1 if "weak" */
		if (((data->bat_flags & UPSD_BAT_WEAK_MASK) == UPSD_BAT_WEAK) && ((int)trig->rhs)) {
			return (trig->comp == UPSD_CMP_EQ);
		} else {
			return (trig->comp == UPSD_CMP_NE);
		}
		break;
	case UPSD_DS_BAT_IS:		/* EQ, NE.  RHS is int, 1 if "discharging" */
		if (((data->bat_flags & UPSD_BAT_DISCHARGE_MASK) == UPSD_BAT_DISCHARGE) && ((int)trig->rhs)) {
			return (trig->comp == UPSD_CMP_EQ);
		} else {
			return (trig->comp == UPSD_CMP_NE);
		}
		break;
	case UPSD_DS_OUT_FROM:		/* EQ, NE.  RHS is int, 1 if "inverter" */
		if (((data->out_flags & UPSD_OUT_INVERTER_MASK) == UPSD_OUT_INVERTER) && ((int)trig->rhs)) {
			return (trig->comp == UPSD_CMP_EQ);
		} else {
			return (trig->comp == UPSD_CMP_NE);
		}
		break;
	case UPSD_DS_BAT_VOLTS:		/* allow all comparisons, numeric compare (RHS is int) */
	case UPSD_DS_IN_FREQ:
	case UPSD_DS_IN_VOLTS:
	case UPSD_DS_OUT_FREQ:
	case UPSD_DS_OUT_VOLTS:
	case UPSD_DS_BAT_TEMP:
	case UPSD_DS_BAT_CHARGE:
	case UPSD_DS_OUT_LOAD:
		{
			int ifield = 0;

			switch (trig->trig) {
			case UPSD_DS_BAT_VOLTS:		ifield = data->x_bvolts; break;
			case UPSD_DS_IN_FREQ:		ifield = data->x_ifreq; break;
			case UPSD_DS_IN_VOLTS:		ifield = data->x_ivolts; break;
			case UPSD_DS_OUT_FREQ:		ifield = data->x_ofreq; break;
			case UPSD_DS_OUT_VOLTS:		ifield = data->x_ovolts; break;
			case UPSD_DS_BAT_TEMP:		ifield = data->btemp; break;
			case UPSD_DS_BAT_CHARGE:	ifield = data->bcharge; break;
			case UPSD_DS_OUT_LOAD:		ifield = data->out_load; break;
			}

			switch (trig->comp) {
			case UPSD_CMP_EQ:
				return (ifield == ((int)trig->rhs));
			case UPSD_CMP_NE:
				return (ifield != ((int)trig->rhs));
			case UPSD_CMP_LT:
				return (ifield < ((int)trig->rhs));
			case UPSD_CMP_GT:
				return (ifield > ((int)trig->rhs));
			case UPSD_CMP_LE:
				return (ifield <= ((int)trig->rhs));
			case UPSD_CMP_GE:
				return (ifield >= ((int)trig->rhs));
			}
		}
		break;
	case UPSD_DS_STATUS:		/* EQ, NE.  RHS is int and is a bit-field of the states given */
		if ((data->st_flags & ((int)trig->rhs)) == ((int)trig->rhs)) {
			return (trig->comp == UPSD_CMP_EQ);
		} else {
			return (trig->comp == UPSD_CMP_NE);
		}
		break;
	}
	return 0;
}
/*}}}*/
/*{{{  static int checkforalarm (openupsd_trigger_t *alarm, openupsd_ds_t *ls, openupsd_ds_t *ns)*/
/*
 *	tests for a specific alarm condition, tests for that state if "ls" NULL
 *	returns 1 on alarm, 0 otherwise
 */
static int checkforalarm (openupsd_trigger_t *alarm, openupsd_ds_t *ls, openupsd_ds_t *ns)
{
	if ((unsigned int)alarm < UPSD_ALARM_MAXINT) {
		return 0;
	}
	if (ls) {
		if (!checkfortrigger (alarm, ns) || checkfortrigger (alarm, ls)) {
			return 0;
		}
		return 1;
	}
	if (checkfortrigger (alarm, ns)) {
		return 1;
	}
	return 0;
}
/*}}}*/
/*{{{  static openupsd_alarm_t *alarm_create (time_t time, openupsd_trigger_t *alarm, void *xdata)*/
static openupsd_alarm_t *alarm_create (time_t time, openupsd_trigger_t *alarm, void *xdata)
{
	openupsd_alarm_t *tmp;

	tmp = (openupsd_alarm_t *)smalloc (sizeof (openupsd_alarm_t));
	tmp->next = tmp->prev = NULL;
	tmp->time = time;
	tmp->alarm = alarm;
	tmp->xdata = xdata;
	tmp->action = 0;
	tmp->initial = 0;
	return tmp;
}
/*}}}*/
/*{{{  static int alarm_addtolist (openupsd_alarm_t **list, time_t now, time_t *left, openupsd_alarm_t *newalarm)*/
/*
 *	adds an alarm to the specified list.  "left" is updated to indicate how long (in seconds) until the next alarm
 *	returns 1 if something is ready for processing now, 0 otherwise
 */
static int alarm_addtolist (openupsd_alarm_t **list, time_t now, time_t *left, openupsd_alarm_t *newalarm)
{
	openupsd_alarm_t *tmp, *last;

	last = NULL;
	for (tmp = *list; tmp; tmp = tmp->next) {
		if (newalarm->time < tmp->time) {
			break;
		}
		last = tmp;
	}
	if (!last) {
		/* insert at list head */
		newalarm->next = *list;
		if (*list) {
			(*list)->prev = newalarm;
		}
		*list = newalarm;
	} else {
		/* insert after "last" */
		newalarm->next = last->next;
		newalarm->prev = last;
		if (newalarm->next) {
			newalarm->next->prev = newalarm;
		}
		last->next = newalarm;
	}
	*left = ((*list)->time - now);
	if (*left < 0) {
		return 1;
	}
	return 0;
}
/*}}}*/
/*{{{  static int alarm_clearfromlist (openupsd_alarm_t **list, time_t now, time_t *left, openupsd_alarm_t *inv)*/
/* alarm invalidation -- dependant on the alarm.  returns number of things deleted */
static int alarm_clearfromlist (openupsd_alarm_t **list, time_t now, time_t *left, openupsd_alarm_t *inv)
{
	openupsd_alarm_t *tmp, *next;
	int n = 0;
	int i;

	for (tmp = *list; tmp; tmp = next) {
		int xflag = 0;
		openupsd_trigger_t *alrm = inv->alarm;		/* alarm that has occured and we need to invalidate others */

		next = tmp->next;
		if (inv->devname == tmp->devname) {
			for (i = 0; i < DA_CUR(alrm->inv); i++) {
				if ((DA_NTHITEM (alrm->inv, i))->trash == tmp->alarm) {
					/* yes, trash tmp */
					xflag = 1;
				}
			}
		}
		if (xflag) {
			/* remove this one */
			if (tmp == *list) {
				/* remove from list head */
				*list = next;
				if (next) {
					next->prev = NULL;
				}
			} else if (!next) {
				/* remove from list tail */
				tmp->prev->next = NULL;
			} else {
				/* remove from list middle */
				tmp->prev->next = tmp->next;
				tmp->next->prev = tmp->prev;
			}
			sfree (tmp);
		}
	}
	return n;
}
/*}}}*/
/*{{{  static int trigger_clearinvalidated (openupsd_alarm_t *alarm, int n_alrms, openupsd_alarm_t **alrms, int *triggered)*/
/*
 *	clears any triggers that might be invalidated by "alarm", where alarms and triggers are in "alrms" and "triggered", of which there are "n_alrms".
 */
static int trigger_clearinvalidated (openupsd_alarm_t *alarm, int n_alrms, openupsd_alarm_t **alrms, int *triggered)
{
	openupsd_trigger_t *trig = alarm->alarm;
	int i, j;
	int n = 0;

	for (j=0; j<n_alrms; j++) {
		for (i=0; i<DA_CUR(trig->inv); i++) {
			if ((alrms[j]->alarm) == (DA_NTHITEM(trig->inv, i))->trash) {
				triggered[j] = 0;	/* clear this one */
				n++;
				break;	/* inner for() */
			}
		}
	}
	return n;
}
/*}}}*/
/*{{{  static int alarm_checkdsandadd (char *devname, openupsd_alarm_t **alist, time_t now, time_t *left, int n_alrms, openupsd_alarm_t **alrms, openupsd_ds_t *last_state, openupsd_ds_t *new_state)*/
/*
 *	this checks device state changes between `last_state' (maybe NULL) and `new_state', and depending on what's going on, replicates alarms
 *	from `alrms' (of which there are `n_alrms') adding to the active list `alist'.  `now' and `left' are the usual -- passed to alarm_addtolist()
 *	returns the number of added alarms, quite possibly 0.
 */
static int alarm_checkdsandadd (char *devname, openupsd_alarm_t **alist, time_t now, time_t *left, int n_alrms, openupsd_alarm_t **alrms, int *triggered, openupsd_ds_t *new_state)
{
	openupsd_alarm_t *tmp;
	int i;
	int n = 0;

	/* iterate over alarms */
	for (i=0; i<n_alrms; i++) {
		int trig = 0;
		openupsd_alarm_t *slarm = alrms[i];

		trig = checkforalarm (slarm->alarm, NULL, new_state);
		if ((triggered[i] < 0) && trig) {
			/* initial alarm, schedule it */
			n++;
			triggered[i] = 1;
			tmp = alarm_create (now + slarm->time, slarm->alarm, NULL);
			tmp->action = slarm->action;
			tmp->devname = devname;
			tmp->xdata = slarm->xdata;
			alarm_clearfromlist (alist, now, left, tmp);		/* invalidate others */
			trigger_clearinvalidated (tmp, n_alrms, alrms, triggered);	/* clear invalidated triggers */
			alarm_addtolist (alist, now, left, tmp);
		} else if (triggered[i] < 0) {
			/* initial alarm, but nothing interesting happening here */
			triggered[i] = 0;
		} else if ((triggered[i] == 0) && trig) {
			/* regular alarm, schedule it */
			triggered[i] = 1;
			n++;
			tmp = alarm_create (now + slarm->time, slarm->alarm, NULL);
			tmp->action = slarm->action;
			tmp->devname = devname;
			tmp->xdata = slarm->xdata;
			alarm_clearfromlist (alist, now, left, tmp);		/* invalidate others */
			trigger_clearinvalidated (tmp, n_alrms, alrms, triggered);	/* clear invalidated triggers */
			alarm_addtolist (alist, now, left, tmp);
		} else if (triggered[i] > 0) {
			/* do nothing -- already active.  only cleared by invalidators */
		}
	}
	return n;
}
/*}}}*/
/*{{{  static int check_client_connect_allowed (openupsd_netsvr_t *vs, struct sockaddr_in *raddr)*/
/*
 *	checks to see if a client is allowed to connect.
 *	returns 0 on success, -1 on failure
 */
static int check_client_connect_allowed (openupsd_netsvr_t *vs, struct sockaddr_in *raddr)
{
	int i;

	if (!vs || !raddr) {
		return -1;
	}
	if (!DA_CUR(vs->allow) && !DA_CUR(vs->disallow)) {
		/* no access control here */
		return 0;
	}
	/* check for explicitly denied first */
	for (i=0; i<DA_CUR(vs->disallow); i++) {
		openupsd_netnet_t *nn = DA_NTHITEM(vs->disallow, i);

		if ((nn->allow_net & nn->allow_mask) == ((unsigned long)(raddr->sin_addr.s_addr) & nn->allow_mask)) {
			/* denied */
			return -1;
		}
	}
	/* then check for explicitly allowed */
	for (i=0; i<DA_CUR(vs->allow); i++) {
		openupsd_netnet_t *nn = DA_NTHITEM(vs->allow, i);

		if ((nn->allow_net & nn->allow_mask) == ((unsigned long)(raddr->sin_addr.s_addr) & nn->allow_mask)) {
			/* allowed */
			return 0;
		}
	}
	/* otherwise, if have ALLOWs, deny, else allow */
	if (DA_CUR(vs->allow)) {
		return -1;
	}
	return 0;
}
/*}}}*/
/*{{{  static int do_exec_alarm (openupsd_t *upsinfo, openupsd_alarm_t *alarm, openupsd_exec_t *exec)*/
static int do_exec_alarm (openupsd_t *upsinfo, openupsd_alarm_t *alarm, openupsd_exec_t *exec)
{
	openupsd_exec_t *tmp;
	openupsd_eenv_t *env;
	int out_fds[2];

	for (tmp = upsinfo->proclist; tmp; tmp = tmp->next) {
		if (tmp == exec) {
			return 1;	/* already running */
		}
	}

	/* setup execution environment */
	if (pipe (out_fds)) {
		openupsd_log (upsinfo, LOG_ERR, "failed to create pipe for new process: %s", strerror (errno));
		return -1;
	}
	env = (openupsd_eenv_t *)smalloc (sizeof (openupsd_eenv_t));
	env->out_fd = out_fds[0];	/* reading end */
	env->bytesout = 0;
	exec->env = env;		/* link in environment */

	if (!upsinfo->daemonise) {
		fflush (stderr);
	}
	env->pid = fork ();
	switch (env->pid) {
	case 0:
		/* new child process. */
#ifdef HAVE_SYSCONF
		{
			int i;

			i = (int)sysconf (_SC_OPEN_MAX);
			while (i > 0) {
				i--;
				if (i != out_fds[1]) {
					/* don't close the output one! */
					close (i);
				}
			}
		}
#endif
		if (out_fds[1] != 1) {
			dup2 (out_fds[1], 1);
		}
		if (out_fds[1] != 2) {
			dup2 (out_fds[1], 2);
		}
		if (out_fds[1] == 0) {
			/* make sure this one ain't stdin! */
			dup2 (out_fds[1], 3);
			out_fds[1] = 3;
			close (0);
		}
		/* now to execute the process..! */
		execv (exec->path_to_run, exec->args);
		fprintf (stderr, "failed to run %s: %s\n", exec->path_to_run, strerror (errno));
		fflush (stderr);
		_exit (EXIT_FAILURE);
		break;
	case -1:
		/* failed to fork(), close pipes report error */
		close (out_fds[0]);
		close (out_fds[1]);
		sfree (env);
		exec->env = NULL;
		openupsd_log (upsinfo, LOG_ERR, "failed to fork(): %s", strerror (errno));
		return -1;
	}
	/* else we were the parent process -- close writing end of pipe */
	close (out_fds[1]);

	/* set non-blocking option on reading end */
	if (fcntl (out_fds[0], F_SETFL, O_NONBLOCK) < 0) {
		openupsd_log (upsinfo, LOG_NOTICE, "failed to set non-blocking option on pipe: %s, but continuing anyway.", strerror (errno));
	}

	/* link in to running list */
	if (upsinfo->proclist) {
		exec->next = upsinfo->proclist;
		upsinfo->proclist->prev = exec;
	}
	upsinfo->proclist = exec;
	if (upsinfo->verbose) {
		openupsd_log (upsinfo, LOG_INFO, "launched process %s with PID %d", exec->path_to_run, env->pid);
	}
	return 0;
}
/*}}}*/
/*{{{  static int do_cleanup_child_processes (openupsd_t *upsinfo, fd_set *read_set)*/
static int do_cleanup_child_processes (openupsd_t *upsinfo, fd_set *read_set)
{
	pid_t r;
	openupsd_exec_t *tmp, *next;
	int status;

	while ((r = waitpid ((pid_t)-1, &status, WNOHANG)) > 0) {
		for (tmp = upsinfo->proclist; tmp; tmp = next) {
			next = tmp->next;

			if (tmp->env->pid == (int)r) {
				/* yup, this one exited */
				if (upsinfo->verbose) {
					if (WIFEXITED (status)) {
						openupsd_log (upsinfo, LOG_INFO, "child process %d exited with status %d", (int)r, WEXITSTATUS (status));
					} else {
						openupsd_log (upsinfo, LOG_INFO, "child process %d was terminated with signal %d", (int)r, WTERMSIG (status));
					}
				}
				if (tmp == upsinfo->proclist) {
					/* removing from list head */
					upsinfo->proclist = next;
					if (next) {
						next->prev = NULL;
					}
				} else if (!tmp->next) {
					/* removing from list tail */
					tmp->prev->next = NULL;
				} else {
					/* removing from list middle */
					tmp->prev->next = tmp->next;
					tmp->next->prev = tmp->prev;
				}
				if (tmp->env->out_fd >= 0) {
					/* just see if there's anything in the pipe waiting for us */
					int x = read (tmp->env->out_fd, tmp->env->outbuf + tmp->env->bytesout, 255 - tmp->env->bytesout);

					if (x > 0) {
						tmp->env->bytesout += x;
						tmp->env->outbuf[tmp->env->bytesout] = '\0';
					}
					FD_CLR (tmp->env->out_fd, read_set);
					close (tmp->env->out_fd);
					tmp->env->out_fd = -1;
				}
				if (tmp->env->bytesout) {
					char *ch;

					ch = strchr (tmp->env->outbuf, '\n');
					if (ch) {
						*ch = '\0';
					}
					openupsd_log (upsinfo, LOG_INFO, "child process %d output: %s", (int)r, tmp->env->outbuf);
					tmp->env->bytesout = 0;
				}
				sfree (tmp->env);
				tmp->env = NULL;
				tmp->next = tmp->prev = NULL;
				break;		/* from for () */
			}
		}
		if (!tmp) {
			/* huh, not here! */
			openupsd_log (upsinfo, LOG_NOTICE, "child process %d exited, but was not started by this program..", (int)r);
		}
	}
	return 0;
}
/*}}}*/
/*{{{  static int openupsd_mainprog (openupsd_t *upsinfo)*/
/*
 *	 main-loop
 */
static int openupsd_mainprog (openupsd_t *upsinfo)
{
	fd_set read_set;
	fd_set write_set;
	int running = 1;
	int result = 0;
	time_t now, left;
	int high_fd = -1;

	/*{{{  init state*/
	FD_ZERO (&read_set);
	FD_ZERO (&write_set);
	now = time (NULL);
	/*}}}*/
	/*{{{  add polls to the aalarm list, all 5s away initially*/
	{
		openupsd_sdev_t *ts;

		for (ts=upsinfo->sdev; ts; ts=ts->next) {
			openupsd_alarm_t *tmp;

			tmp = alarm_create (now + 5, (openupsd_trigger_t *)UPSD_ALARM_POLL, (void *)ts);
			alarm_addtolist (&(upsinfo->aalarms), now, &left, tmp);
		}
	}
	/*}}}*/
	/*{{{  add listening servers to read_set*/
	{
		openupsd_netsvr_t *ns;

		for (ns=upsinfo->netsvr; ns; ns = ns->next) {
			FD_SET (ns->fd, &read_set);
			if (ns->fd > high_fd) {
				high_fd = ns->fd;
			}
		}
	}
	/*}}}*/
	/*{{{  add half-connected initial clients to write_set, fully connected clients to the read_set, and internal alarms for trying again*/
	{
		openupsd_netcli_t *ns;

		for (ns=upsinfo->netcli; ns; ns = ns->next) {
			if (ns->state == UPSD_NETCLI_INACTIVE) {
				openupsd_alarm_t *tmp;

				tmp = alarm_create (now + ns->retry, (openupsd_trigger_t *)UPSD_ALARM_RECONNECT, (void *)ns);
				alarm_addtolist (&(upsinfo->aalarms), now, &left, tmp);
			} else if (ns->state == UPSD_NETCLI_CONNECTING) {
				FD_SET (ns->fd, &write_set);
				if (ns->fd > high_fd) {
					high_fd = ns->fd;
				}
			} else if (ns->state == UPSD_NETCLI_CONNECTED) {
				FD_SET (ns->fd, &read_set);
				if (ns->fd > high_fd) {
					high_fd = ns->fd;
				}
			}
		}
	}
	/*}}}*/
	/*{{{  add any listening sockets to the read_set -- won't have any clients yet*/
	{
		openupsd_netsvr_t *vs;

		for (vs=upsinfo->netsvr; vs; vs = vs->next) {
			FD_SET (vs->fd, &read_set);
			if (vs->fd > high_fd) {
				high_fd = vs->fd;
			}
		}
	}
	/*}}}*/
	/*{{{  loop in select()*/
	while (running) {
		fd_set lr_set, lw_set;
		int sr;
		struct timeval tv = {(long)left, 500000};

		memcpy (&lr_set, &read_set, sizeof (fd_set));
		memcpy (&lw_set, &write_set, sizeof (fd_set));

#if 0
/*{{{  debug code -- print alarm queue*/
{
	openupsd_alarm_t *aa;
	
	fprintf (stderr, "time now = %d, left = %d\n", (int)time(NULL), (int)left);
	for (aa = upsinfo->aalarms; aa; aa = aa->next) {
		fprintf (stderr, "alarm @ %p: time %d, alarm %d, action %d, devname = [%s], xdata @ %p\n", aa, (int)aa->time, aa->alarm, aa->action, aa->devname, aa->xdata);
	}
}
/*}}}*/
#endif
		if (upsinfo->aalarms && (left > 0)) {
			sr = select (high_fd + 1, &lr_set, &lw_set, NULL, &tv);
		} else if (!upsinfo->aalarms) {
			/* no alarms.. just wait for something to happen then! */
			sr = select (high_fd + 1, &lr_set, &lw_set, NULL, NULL);
		} else {
			/* poll */
			tv.tv_sec = 0;
			tv.tv_usec = 0;
			sr = select (high_fd + 1, &lr_set, &lw_set, NULL, &tv);
		}
		now = time (NULL);
		if ((sr < 0) && (errno != EINTR)) {
			openupsd_log (upsinfo, LOG_NOTICE, "select() failed with %s", strerror (errno));
			sr = 0;
		}
		/*{{{  process any pending signals*/
		if (sigflag) {
			/* block others while we process what's already here */
			sigprocmask (SIG_BLOCK, &tsigset, NULL);
			sigflag = 0;
			if (sigdata[0]) {
				/* SIGINT */
				sigdata[0] = 0;
				if (upsinfo->verbose) {
					openupsd_log (upsinfo, LOG_INFO, "got SIGINT, exiting cleanly.");
				}
				running = 0;
				continue;	/* to while() */
			}
			if (sigdata[1]) {
				/* SIGCHLD */
				sigdata[1] = 0;
				do_cleanup_child_processes (upsinfo, &read_set);
			}
			if (sigdata[2]) {
				/* SIGHUP */
				sigdata[2] = 0;
				if (upsinfo->verbose) {
					openupsd_log (upsinfo, LOG_INFO, "got SIGHUP, should do something..");
				}
			}
			sigprocmask (SIG_UNBLOCK, &tsigset, NULL);
		}
		/*}}}*/
		/*{{{  process output from any programs run as the result of alarms*/
		{
			openupsd_exec_t *ts;

			for (ts = upsinfo->proclist; sr && ts; ts = ts->next) {
				if ((ts->env->out_fd >= 0) && FD_ISSET (ts->env->out_fd, &lr_set)) {
					int r;

					sr--;
					FD_CLR (ts->env->out_fd, &lr_set);
					do {
						r = read (ts->env->out_fd, ts->env->outbuf + ts->env->bytesout, 255 - ts->env->bytesout);
						if (!r) {
							/* EOF, other end closed pipe */
							if (upsinfo->verbose) {
								openupsd_log (upsinfo, LOG_INFO, "child process %d closed output stream.", (int)(ts->env->pid));
							}
							FD_CLR (ts->env->out_fd, &read_set);
							close (ts->env->out_fd);
							ts->env->out_fd = -1;
						} else if ((r < 0) && (errno != EAGAIN)) {
							/* read error of some form */
							openupsd_log (upsinfo, LOG_NOTICE, "read error from child process %d: %s", (int)(ts->env->pid), strerror (errno));
							FD_CLR (ts->env->out_fd, &read_set);
							close (ts->env->out_fd);
							ts->env->out_fd = -1;
						} else if (r > 0) {
							int h, i;

							/* read some */
							h = ts->env->bytesout;
							ts->env->bytesout += r;
							ts->env->outbuf[ts->env->bytesout] = '\0';
							do {
								for (i=h; (i<ts->env->bytesout) && (ts->env->outbuf[i] != '\n'); i++);
								if (i < ts->env->bytesout) {
									/* have a complete line, yay! */
									ts->env->outbuf[i] = '\0';
									openupsd_log (upsinfo, LOG_INFO, "child process %d outputted: %s", (int)(ts->env->pid), ts->env->outbuf);
									/* shift string */
									i++;
									if (i < ts->env->bytesout) {
										memmove (ts->env->outbuf, ts->env->outbuf + i, (ts->env->bytesout - i));
									}
									ts->env->bytesout -= i;
									h = 0;
								} else if (i == 255) {
									/* buffer full.. write out anyway */
									openupsd_log (upsinfo, LOG_INFO, "child process %d outputted: %s", (int)(ts->env->pid), ts->env->outbuf);
									ts->env->bytesout = 0;
									h = 0;
								} else {
									break;	/* from do/while */
								}
							} while (ts->env->bytesout);
						}
					} while (r > 0);
				}
			}
		}
		/*}}}*/
		/*{{{  process any alarms that have expired*/
		{
			openupsd_alarm_t *tmp = upsinfo->aalarms;

			while (tmp && (tmp->time <= now)) {
				openupsd_alarm_t *next = tmp->next;
				char astr[32];
				int v;

				/* chop this one off the list (always at the start) */
				upsinfo->aalarms = next;
				if (next) {
					next->prev = NULL;
				}
				tmp->next = NULL;
				tmp->prev = NULL;

				switch ((int)tmp->alarm) {
					/*{{{  default: any defined alarm (trigger)*/
				default:
					/* user-alarm, do something based on the action */
					if (upsinfo->verbose) {
						getalarmstr (tmp->alarm, astr);
						openupsd_log (upsinfo, LOG_INFO, "ALARM activated on device %s: %s", tmp->devname, astr);
					}
					switch (tmp->action) {
					case UPSD_ACTION_LOG:
						getalarmstr (tmp->alarm, astr);
						openupsd_log (upsinfo, LOG_NOTICE, "ALARM activated on device %s: %s (action LOG)", tmp->devname, astr);
						break;
					case UPSD_ACTION_EXEC:
						v = do_exec_alarm (upsinfo, tmp, (openupsd_exec_t *)(tmp->xdata));
						if (v > 0) {
							getalarmstr (tmp->alarm, astr);
							openupsd_log (upsinfo, LOG_NOTICE, "ALARM %s activated on device %s, but %s is already running.",
									astr, tmp->devname, ((openupsd_exec_t *)(tmp->xdata))->path_to_run);
						} else if (!v) {
							/* child process launched, add to the read set of descriptors */
							FD_SET (((openupsd_exec_t *)(tmp->xdata))->env->out_fd, &read_set);
						}
						break;
					}

					/* always delete user alarms */
					sfree (tmp);
					break;
					/*}}}*/
					/*{{{  UPSD_ALARM_POLL*/
				case UPSD_ALARM_POLL:
					{
						openupsd_sdev_t *sdev = (openupsd_sdev_t *)(tmp->xdata);
						openupsd_ds_t upsdata;
						int i;

						if (grab_data (sdev, &upsdata)) {
							openupsd_log (upsinfo, LOG_NOTICE, "failed to read data from device %s, will retry in %d seconds.", sdev->name, sdev->inter_poll_sec);
						} else {
							/*{{{  process "upsdata" from device "sdev"*/

							/* write out to any files */
							for (i = 0; i < DA_CUR(sdev->outfiles); i++) {
								do_dump_devstatus (&upsdata, DA_NTHITEM(sdev->outfiles, i));
							}
							/* enqueue for any connected network clients */
							for (i = 0; i < DA_CUR(sdev->outtcpsvrs); i++) {
								openupsd_netsvr_t *netsvr = DA_NTHITEM(sdev->outtcpsvrs, i);
								int j;

								for (j = 0; j < DA_CUR(netsvr->clients); j++) {
									openupsd_rclient_t *rc = DA_NTHITEM(netsvr->clients, j);
									int k = do_queue_netdata (&upsdata, rc);

									if (k < 0) {
										/* pop, out of room, remove client */
										if (upsinfo->verbose) {
											char netstr[32];

											sprintf (netstr, "%s:%d", inet_ntoa (rc->sin.sin_addr), ntohs (rc->sin.sin_port));
											openupsd_log (upsinfo, LOG_NOTICE, "send queue on remote client %s filled, disconnecting it", netstr);
										}
										if (rc->outbuf) {
											sfree (rc->outbuf);
										}
										FD_CLR (rc->fd, &write_set);
										close (rc->fd);
										dynarray_delitem (netsvr->clients, j);
										sfree (rc);
										j--;
									} else {
										/* put socket in write set */
										FD_SET (rc->fd, &write_set);
										if (rc->fd > high_fd) {
											high_fd = rc->fd;
										}
									}
								}
							}
							/* check for new alarms based on state change */
							alarm_checkdsandadd (sdev->name, &(upsinfo->aalarms), now, &left, DA_CUR(sdev->alarms), DA_PTR(sdev->alarms), DA_PTR(sdev->triggered), &upsdata);
							/*}}}*/
						}

						/* re-schedule alarm for the inter-poll time */
						tmp->time = now + (time_t)sdev->inter_poll_sec;
						alarm_addtolist (&(upsinfo->aalarms), now, &left, tmp);
					}
					break;
					/*}}}*/
					/*{{{  UPSD_ALARM_RECONNECT*/
				case UPSD_ALARM_RECONNECT:
					{
						openupsd_netcli_t *netcli = (openupsd_netcli_t *)(tmp->xdata);
						int r, ser;
						char netstr[32];
						int int_flag = 1;
						int int_size = sizeof (int_flag);

						sprintf (netstr, "%s:%d", inet_ntoa (netcli->ups_host.sin_addr), htons (netcli->ups_host.sin_port));
						if (netcli->fd < 0) {
							/* create a new socket */
							netcli->fd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
							if (netcli->fd < 0) {
								openupsd_log (upsinfo, LOG_ERR, "failed to create new socket for connection to %s, will try again in %d seconds.",
										netstr, netcli->retry);
							} else if (fcntl (netcli->fd, F_SETFL, O_NONBLOCK) < 0) {
								close (netcli->fd);
								netcli->fd = -1;
								openupsd_log (upsinfo, LOG_ERR, "failed to set non-blocking option on socket for connection to %s,  will try again in %d seconds.",
										netstr, netcli->retry);
							}
							if (setsockopt (netcli->fd, SOL_SOCKET, SO_REUSEADDR, (void *)(&int_flag), (socklen_t)int_size)) {
								if (upsinfo->verbose) {
									openupsd_log (upsinfo, LOG_NOTICE, "failed to set SO_REUSEADDR on client socket.");
								}
							}
						}
						if (netcli->fd >= 0) {
							r = connect (netcli->fd, (struct sockaddr *)&(netcli->ups_host), sizeof (struct sockaddr_in));
							ser = errno;
						} else {
							r = -1;
							ser = EPERM;	/* something unlikely */
						}
						if (netcli->fd < 0) {
							/* didn't attempt any connection */
						} else if ((r < 0) && (ser == EINPROGRESS)) {
							netcli->state = UPSD_NETCLI_CONNECTING;
							/* add to the write set */
							FD_SET (netcli->fd, &write_set);
							if (netcli->fd > high_fd) {
								high_fd = netcli->fd;
							}
							if (upsinfo->verbose) {
								openupsd_log (upsinfo, LOG_INFO, "backgrounding remote connection to %s..", netstr);
							}
							sfree (tmp);
						} else if (!r) {
							/* connected */
							netcli->state = UPSD_NETCLI_CONNECTED;
							/* remove from the write set, add to the rest set */
							FD_CLR (netcli->fd, &write_set);
							FD_SET (netcli->fd, &read_set);
							if (netcli->fd > high_fd) {
								high_fd = netcli->fd;
							}
							if (upsinfo->verbose) {
								openupsd_log (upsinfo, LOG_INFO, "connected to remote server %s.", netstr);
							}
							sfree (tmp);
						} else {
							/* failed to connect, for whatever reason, remove socket and reschedule alarm */
							netcli->state = UPSD_NETCLI_INACTIVE;
							FD_CLR (netcli->fd, &write_set);
							FD_CLR (netcli->fd, &read_set);
							close (netcli->fd);
							netcli->fd = -1;
							tmp->time = now + netcli->retry;
							alarm_addtolist (&(upsinfo->aalarms), now, &left, tmp);
							if (upsinfo->verbose) {
								openupsd_log (upsinfo, LOG_INFO, "failed to connect to %s: %s, will try again in %d seconds.", netstr, strerror (ser), netcli->retry);
							}
						}
					}
					break;
					/*}}}*/
				}
				tmp = next;
			}
		}
		/*}}}*/
		/* reset left to the next alarm */
		left = (upsinfo->aalarms ? (int)(upsinfo->aalarms->time - now) : 0);
		/*{{{  process connecting (outgoing) clients in the write set and read set*/
		{
			openupsd_netcli_t *ns;
			char netcli[32];

			for (ns = upsinfo->netcli; sr && ns; ns = ns->next) {
				sprintf (netcli, "%s:%d", inet_ntoa (ns->ups_host.sin_addr), ntohs (ns->ups_host.sin_port));

				if ((ns->fd >= 0) && FD_ISSET (ns->fd, &lw_set)) {
					sr--;
					FD_CLR (ns->fd, &lw_set);
					/*{{{  client ready for writing, must be CONNECTING*/
					if (ns->state == UPSD_NETCLI_CONNECTING) {
						int r, x;
						int xs = sizeof (int);

						/* this means it's completed, possibly with error */
						r = getsockopt (ns->fd, SOL_SOCKET, SO_ERROR, (void *)&x, (socklen_t *)&xs);
						if (!r && !x) {
							/* connected ok, yay, remove from write set, put in read set */
							FD_CLR (ns->fd, &write_set);
							FD_SET (ns->fd, &read_set);
							ns->state = UPSD_NETCLI_CONNECTED;
							if (upsinfo->verbose) {
								openupsd_log (upsinfo, LOG_INFO, "connected to remote server %s.", netcli);
							}
						} else if (!r && x) {
							/* failed to connect, try again in a bit */
							openupsd_alarm_t *tmp;

							ns->state = UPSD_NETCLI_INACTIVE;
							FD_CLR (ns->fd, &write_set);
							FD_CLR (ns->fd, &read_set);
							close (ns->fd);
							ns->fd = -1;
							tmp = alarm_create (now + ns->retry, (openupsd_trigger_t *)UPSD_ALARM_RECONNECT, (void *)ns);
							alarm_addtolist (&(upsinfo->aalarms), now, &left, tmp);
							if (upsinfo->verbose) {
								openupsd_log (upsinfo, LOG_INFO, "failed to connect to %s: %s, will try again in %d seconds.", netcli, strerror (x), ns->retry);
							}
						} else if (r) {
							/* getsockopt() failed.. hmm.. very bad that.. */
							FD_CLR (ns->fd, &write_set);
							openupsd_log (upsinfo, LOG_ERR, "getsockopt() failed with %s.  exiting.", strerror (errno));
							running = 0;
						}
					} else {
						openupsd_log (upsinfo, LOG_ERR, "internal error: in state %d, (outgoing) client ready in write set.  shutting down.", ns->state);
						running = 0;
					}
					/*}}}*/
				} else if ((ns->fd >= 0) && FD_ISSET (ns->fd, &lr_set)) {
					sr--;
					FD_CLR (ns->fd, &lr_set);
					/*{{{  client ready for reading, must be CONNECTED*/
					if (ns->state != UPSD_NETCLI_CONNECTED) {
						openupsd_log (upsinfo, LOG_ERR, "internal error: in state %d, (outgoing) client %s ready in read set.  shutting down.", ns->state, netcli);
						running = 0;
					} else {
						int r, xer;
						char *lbuf;
						int lbufsize;

						/* allocate a modest amount */
						lbufsize = 1024;
						lbuf = (char *)smalloc (lbufsize);
						r = read (ns->fd, lbuf, lbufsize - 1);
						xer = errno;

						if (r <= 0) {
							/* whoopsy -- make inactive */
							openupsd_alarm_t *tmp;

							ns->state = UPSD_NETCLI_INACTIVE;
							FD_CLR (ns->fd, &write_set);
							FD_CLR (ns->fd, &read_set);
							close (ns->fd);
							ns->fd = -1;
							tmp = alarm_create (now + ns->retry, (openupsd_trigger_t *)UPSD_ALARM_RECONNECT, (void *)ns);
							alarm_addtolist (&(upsinfo->aalarms), now, &left, tmp);
							if (upsinfo->verbose) {
								if (r < 0) {
									openupsd_log (upsinfo, LOG_INFO, "read error in connection to %s: %s.  closing and will attempt reconnect in %d seconds.",
											netcli, strerror (xer), ns->retry);
								} else {
									openupsd_log (upsinfo, LOG_INFO, "EOF in connection to %s.  closing and will attempt reconnect in %d seconds.",
											netcli, ns->retry);
								}
							}
						} else {
							openupsd_ds_t upsdata;

							/* good good, stick in client buffer */
							if (!ns->inbuf) {
								ns->inbuf = lbuf;
								ns->bufsize = lbufsize;
								ns->inbytes = r;
							} else {
								if (r >= (ns->bufsize - ns->inbytes)) {
									/* need more buffer space -- be generous */
									ns->inbuf = (char *)srealloc (ns->inbuf, ns->bufsize, ns->bufsize + r);
									ns->bufsize += r;
								}
								/* copy in */
								memcpy (ns->inbuf + ns->inbytes, lbuf, r);
								sfree (lbuf);
								ns->inbytes += r;
							}
							/* right.. see if we've got enough and process if so */
							while ((r = check_extract_netdata (&upsdata, ns)) > 0) {
								/*{{{  process "upsdata" from device "ns"*/
								int i;

								/* write out to any files */
								for (i = 0; i < DA_CUR(ns->outfiles); i++) {
									do_dump_devstatus (&upsdata, DA_NTHITEM(ns->outfiles, i));
								}
								/* enqueue for any connected network clients */
								for (i = 0; i < DA_CUR(ns->outtcpsvrs); i++) {
									openupsd_netsvr_t *netsvr = DA_NTHITEM(ns->outtcpsvrs, i);
									int j;

									for (j = 0; j < DA_CUR(netsvr->clients); j++) {
										openupsd_rclient_t *rc = DA_NTHITEM(netsvr->clients, j);
										int k = do_queue_netdata (&upsdata, rc);

										if (k < 0) {
											/* pop, out of room, remove client */
											if (upsinfo->verbose) {
												char netstr[32];

												sprintf (netstr, "%s:%d", inet_ntoa (rc->sin.sin_addr), ntohs (rc->sin.sin_port));
												openupsd_log (upsinfo, LOG_NOTICE, "send queue on remote client %s filled, disconnecting it", netstr);
											}
											if (rc->outbuf) {
												sfree (rc->outbuf);
											}
											FD_CLR (rc->fd, &write_set);
											close (rc->fd);
											dynarray_delitem (netsvr->clients, j);
											sfree (rc);
											j--;
										} else {
											/* put socket in write set */
											FD_SET (rc->fd, &write_set);
											if (rc->fd > high_fd) {
												high_fd = rc->fd;
											}
										}
									}
								}
								/* check for new alarms based on state change */
								alarm_checkdsandadd (ns->name, &(upsinfo->aalarms), now, &left, DA_CUR(ns->alarms), DA_PTR(ns->alarms), DA_PTR(ns->triggered), &upsdata);
								/*}}}*/
							}
							/* check_extract_netdata returns 0 on "buffer empty" and -1 on error */
							if (r) {
								/* something not right with the data.. make inactive */
								openupsd_alarm_t *tmp;

								ns->state = UPSD_NETCLI_INACTIVE;
								FD_CLR (ns->fd, &write_set);
								FD_CLR (ns->fd, &read_set);
								shutdown (ns->fd, SHUT_RDWR);
								close (ns->fd);
								ns->fd = -1;
								tmp = alarm_create (now + ns->retry, (openupsd_trigger_t *)UPSD_ALARM_RECONNECT, (void *)ns);
								alarm_addtolist (&(upsinfo->aalarms), now, &left, tmp);
								if (upsinfo->verbose) {
									openupsd_log (upsinfo, LOG_INFO, "mangled data in connection with %s.  disconnecting and will attempt reconnect in %d seconds.",
											netcli, ns->retry);
								}
							}
						}
					}
					/*}}}*/
				}
			}
		}
		/*}}}*/
		/*{{{  process listening servers that might have accepted something*/
		{
			openupsd_netsvr_t *vs;

			for (vs = upsinfo->netsvr; sr && vs; vs = vs->next) {
				int i;
				char netstr[32], clistr[32];

				/* this net-server might be trying to write to clients -- see if any were ready (done before checking the listening socket) */
				sprintf (netstr, "%s:%d", inet_ntoa (vs->listen_host.sin_addr), htons (vs->listen_host.sin_port));
				for (i=0; i<DA_CUR(vs->clients); i++) {
					openupsd_rclient_t *rc = DA_NTHITEM(vs->clients, i);

					if (FD_ISSET (rc->fd, &lw_set)) {
						int r;

						sr--;
						FD_CLR (rc->fd, &lw_set);
						/* write some */
						r = write (rc->fd, rc->outbuf + rc->gone, rc->left);
						if ((r < 0) && ((errno == EAGAIN) || (errno == EINTR))) {
							/* oopsy, loop and try again.. */
						} else if (r <= 0) {
							int xer = errno;

							/* other error, disconnect it */
							if (upsinfo->verbose) {
								sprintf (clistr, "%s:%d", inet_ntoa (rc->sin.sin_addr), htons (rc->sin.sin_port));
								openupsd_log (upsinfo, LOG_INFO, "listening server %s disconnected client %s because: %s", netstr, clistr, strerror (xer));
							}
							dynarray_delitem (vs->clients, i);
							FD_CLR (rc->fd, &write_set);
							FD_CLR (rc->fd, &read_set);
							close (rc->fd);
							sfree (rc);
							i--;
						} else {
							/* wrote some out, update stuff */
							rc->left = rc->left - r;
							rc->gone = rc->gone + r;

							if (!rc->left) {
								if (rc->outbuf) {
									sfree (rc->outbuf);
									rc->outbuf = NULL;
								}
								rc->bufsize = 0;
								rc->gone = 0;
								/* remove from global write set */
								FD_CLR (rc->fd, &write_set);
							} /* else still some more to go */
						}
					} else if (FD_ISSET (rc->fd, &lr_set)) {
						/* probably disconnecting, should not be sending data..! */
						int r;
						char lbuf[16];

						sr--;
						FD_CLR (rc->fd, &lr_set);
						r = read (rc->fd, lbuf, 16);
						if (r > 0) {
							sprintf (clistr, "%s:%d", inet_ntoa (rc->sin.sin_addr), htons (rc->sin.sin_port));
							openupsd_log (upsinfo, LOG_NOTICE, "listening server %s has broken client %s (sending data), disconnecting", netstr, clistr);
						} else if (!r) {
							/* EOF as expected */
							if (upsinfo->verbose) {
								sprintf (clistr, "%s:%d", inet_ntoa (rc->sin.sin_addr), htons (rc->sin.sin_port));
								openupsd_log (upsinfo, LOG_INFO, "listening server %s disconnecting client %s (EOF)", netstr, clistr);
							}
						} else {
							/* other error */
							int xer = errno;

							if (upsinfo->verbose) {
								sprintf (clistr, "%s:%d", inet_ntoa (rc->sin.sin_addr), htons (rc->sin.sin_port));
								openupsd_log (upsinfo, LOG_INFO, "listening server %s disconnected client %s because of read error: %s", netstr, clistr, strerror (xer));
							}
						}
						shutdown (rc->fd, SHUT_RDWR);
						dynarray_delitem (vs->clients, i);
						FD_CLR (rc->fd, &write_set);
						FD_CLR (rc->fd, &read_set);
						close (rc->fd);
						sfree (rc);
						i--;
					}
				}

				/* check listening socket */
				if (FD_ISSET (vs->fd, &lr_set)) {
					openupsd_rclient_t *rc;
					struct sockaddr_in sin;
					int sinlen = sizeof (struct sockaddr_in);
					int r;

					sr--;
					FD_CLR (vs->fd, &lr_set);
					/* accept connection */
					r = accept (vs->fd, (struct sockaddr *)&sin, (socklen_t *)&sinlen);
					if ((r < 0) && (errno == EAGAIN)) {
						openupsd_log (upsinfo, LOG_NOTICE, "listening server %s ready, but accept() said EAGAIN.", netstr);
					} else if (r < 0) {
						openupsd_log (upsinfo, LOG_NOTICE, "listening server %s failed to accept connection: %s", netstr, strerror (errno));
					} else if (check_client_connect_allowed (vs, &sin)) {
						sprintf (clistr, "%s:%d", inet_ntoa (sin.sin_addr), htons (sin.sin_port));
						openupsd_log (upsinfo, LOG_NOTICE, "listening server %s disallowing connection from %s", netstr, clistr);
						close (r);
					} else if (fcntl (r, F_SETFL, O_NONBLOCK) < 0) {
						sprintf (clistr, "%s:%d", inet_ntoa (sin.sin_addr), htons (sin.sin_port));
						openupsd_log (upsinfo, LOG_ERR, "listening server %s unable to set non-blocking on connection from %s, dropping it.", netstr, clistr);
						close (r);
					} else {
						int int_flag = 1;
						int int_size = sizeof (int_flag);

						rc = (openupsd_rclient_t *)smalloc (sizeof (openupsd_rclient_t));

						if (setsockopt (r, SOL_SOCKET, SO_REUSEADDR, (void *)(&int_flag), (socklen_t)int_size)) {
							if (upsinfo->verbose) {
								openupsd_log (upsinfo, LOG_NOTICE, "failed to set SO_REUSEADDR on client socket.");
							}
						}
						sprintf (clistr, "%s:%d", inet_ntoa (sin.sin_addr), htons (sin.sin_port));
						memcpy (&(rc->sin), &sin, sizeof (struct sockaddr_in));
						rc->fd = r;
						rc->outbuf = NULL;
						rc->bufsize = 0;
						rc->left = 0;
						rc->gone = 0;
						dynarray_add (vs->clients, rc);
						/* must remember to free these, since they're not anywhere else.. */
						if (upsinfo->verbose) {
							openupsd_log (upsinfo, LOG_INFO, "listening server %s accepted connection from %s", netstr, clistr);
						}

						/* stick in the read set -- how we diagnose loss */
						FD_SET (rc->fd, &read_set);
						if (rc->fd > high_fd) {
							high_fd = rc->fd;
						}
					}
				}
			}
		}
		/*}}}*/
	}
	/*}}}*/
	return result;
}
/*}}}*/

/*{{{  int main (int argc, char **argv)*/
/*
 *	start here
 */
int main (int argc, char **argv)
{
	int i;
	char **walk;
	openupsd_t upsinfo;
	static char *default_config = SYSCONFDIR "/openupsd.conf";
	int errd = 0;
	int ndevs = 0;

	/*{{{  sort out progname*/
	for (progname = *argv + (strlen (*argv) - 1); (progname > *argv) && (*progname != '/'); progname--);
	progname++;
	/*}}}*/
	/*{{{  initialise upsinfo structure*/
	upsinfo.sdev = NULL;
	upsinfo.netcli = NULL;
	upsinfo.netsvr = NULL;
	upsinfo.outfiles = NULL;
	upsinfo.conffile = default_config;
	upsinfo.verbose = 0;
	upsinfo.daemonise = 1;
	upsinfo.use_syslog = 1;
	upsinfo.logfilename = NULL;
	upsinfo.logfile = NULL;
	upsinfo.salarms = NULL;
	upsinfo.aalarms = NULL;
	upsinfo.trigs = NULL;
	upsinfo.invds = NULL;
	upsinfo.proclist = NULL;
	upsinfo.pidfilename = NULL;
	/*}}}*/
	/*{{{  scan command-line for config file and process if found*/
	/* quickly see if there's a config-file specified on the command line */
	for (i=1, walk = argv+1; (i < argc) && *walk; i++, walk++) {
		if (!strcmp (*walk, "-c") || !strcmp (*walk, "--config")) {
			walk++, i++;
			if ((i == argc) || !*walk) {
				fprintf (stderr, "%s: option %s expects an argument.  --help for usage information.\n", progname, walk[-1]);
				exit (EXIT_FAILURE);
			}
			if (access (*walk, R_OK)) {
				fprintf (stderr, "%s: cannot read from config file %s\n", progname, *walk);
				exit (EXIT_FAILURE);
			}
			upsinfo.conffile = *walk;
		}
	}
	/* then try and process it */
	errd = parse_config (&upsinfo, stderr);
	/*}}}*/
	/*{{{  process command-line args*/
	for (i=1, walk = argv+1; (i < argc) && *walk; i++, walk++) {
#ifdef SUPPORT_SYSLOG
		if (!strcmp (*walk, "-s") || !strcmp (*walk, "--syslog")) {
			upsinfo.use_syslog = 1;
			continue;
		}
#endif
		if (!strcmp (*walk, "--help") || !strcmp (*walk, "-h")) {
			show_help (stdout);
			exit (EXIT_SUCCESS);
		}
		if (!strcmp (*walk, "--version") || !strcmp (*walk, "-V")) {
			show_version (stdout);
			exit (EXIT_SUCCESS);
		}
		if (!strcmp (*walk, "--verbose") || !strcmp (*walk, "-v")) {
			upsinfo.verbose = 1;
			continue;
		}
		if (!strcmp (*walk, "-c") || !strcmp (*walk, "--config")) {
			walk++, i++;
			continue;
		}
		if (!strcmp (*walk, "-t") || !strcmp (*walk, "--tty")) {
			upsinfo.daemonise = 0;
			continue;
		}
		if (!strcmp (*walk, "-p") || !strcmp (*walk, "--pidfile")) {
			walk++, i++;
			if (!*walk || (i == argc)) {
				fprintf (stderr, "%s: option %s requires filename argument\n", progname, walk[-1]);
				/* blank abort */
				exit (EXIT_FAILURE);
			}
			if (upsinfo.pidfilename && strcmp (upsinfo.pidfilename, *walk)) {
				fprintf (stderr, "%s: pidfile specified on command-line (%s) overriding config option\n", progname, *walk);
				sfree (upsinfo.pidfilename);
				upsinfo.pidfilename = string_dup (*walk);
			} else if (!upsinfo.pidfilename) {
				upsinfo.pidfilename = string_dup (*walk);
			}
			continue;
		}
		fprintf (stderr, "%s: error: unrecognised option %s.  --help for usage.\n", progname, *walk);
		exit (EXIT_FAILURE);
	}
	/*}}}*/
	/*{{{  if we errored while processing the config file, get out */
	if (errd) {
		fprintf (stderr, "%s: fatal: configuration error.\n", progname);
		exit (EXIT_FAILURE);
	}
	/*}}}*/
	/*{{{  scan output files and make sure they exist/can be written*/
	{
		openupsd_ofile_t *outfiles;
		int done = 0;

		while (!done) {
			if (!upsinfo.outfiles) {
				done = 1;
			}
			for (outfiles = upsinfo.outfiles; outfiles; outfiles = outfiles->next) {
				if (access (outfiles->fname, W_OK)) {
					int fd;

					fd = open (outfiles->fname, O_CREAT | O_WRONLY | O_NOCTTY, 0644);
					if (fd < 0) {
						fprintf (stderr, "%s: unable to write to output file %s, ignoring it.\n", progname, outfiles->fname);
						/* remove outfile from UPS structures */
						remove_outfile (outfiles, upsinfo.sdev, upsinfo.netcli);
						if (outfiles == upsinfo.outfiles) {
							/* remove from list head */
							upsinfo.outfiles = outfiles->next;
							if (upsinfo.outfiles) {
								upsinfo.outfiles->prev = NULL;
							}
						} else if (!outfiles->next) {
							/* remove from list tail */
							outfiles->prev->next = NULL;
						} else {
							/* remove from list middle or tail */
							outfiles->prev->next = outfiles->next;
							outfiles->next->prev = outfiles->prev;
						}
						sfree (outfiles->fname);
						sfree (outfiles);
						break;	/* from for() */
					} else {
						close (fd);
					}
				}
				if (!outfiles->next) {
					done = 1;
				}
			}
		}
	}
	/*}}}*/
	/*{{{  if verbose, display the configuration on stderr before starting (debugging really ;))*/
	if (upsinfo.verbose) {
		openupsd_sdev_t *ts;
		openupsd_netcli_t *ns;
		openupsd_alarm_t *as;
		openupsd_ofile_t *fs;
		openupsd_netsvr_t *vs;
		int i;

		fprintf (stderr, "%s: processed configuration successfully:\n", progname);
		/*{{{  dump clients*/
		if (upsinfo.sdev || upsinfo.netcli) {
			fprintf (stderr, "\n    DEVICE    PORT            INFO                                    \n");
			fprintf (stderr, "    ------------------------------------------------------------------\n");
		}
		if (upsinfo.sdev) {
			for (ts = upsinfo.sdev; ts; ts = ts->next) {
				fprintf (stderr, "    %-10s%-16snof=%d,nalrm=%d,nnetsvr=%d\n", ts->name, ts->device, DA_CUR(ts->outfiles), DA_CUR(ts->alarms), DA_CUR(ts->outtcpsvrs));
			}
		} else {
			fprintf (stderr, "    (no serial devices)\n");
		}
		if (upsinfo.netcli) {
			char netstr[24];

			for (ns = upsinfo.netcli; ns; ns = ns->next) {
				sprintf (netstr, "%s:%d", inet_ntoa (ns->ups_host.sin_addr), ntohs (ns->ups_host.sin_port));
				fprintf (stderr, "    %-10s%-16snof=%d,nalrm=%d,nnetsvr=%d\n", ns->name, netstr, DA_CUR(ns->outfiles), DA_CUR(ns->alarms), DA_CUR(ns->outtcpsvrs));
			}
		} else {
			fprintf (stderr, "    (no network devices)\n");
		}
		/*}}}*/
		/*{{{  dump triggers*/
		if (upsinfo.trigs) {
			openupsd_trigger_t *tt;

			fprintf (stderr, "\n    NAME            FIELD           CMP RHS  (CANCELS)                \n");
			fprintf (stderr, "    ------------------------------------------------------------------\n");
			for (tt = upsinfo.trigs; tt; tt = tt->next) {
				fprintf (stderr, "    %-16s%-16s", tt->name, field_strings[tt->trig]);
				switch (tt->comp) {
				case UPSD_CMP_EQ:	fprintf (stderr, "=   "); break;
				case UPSD_CMP_NE:	fprintf (stderr, "!=  "); break;
				case UPSD_CMP_LT:	fprintf (stderr, "<   "); break;
				case UPSD_CMP_LE:	fprintf (stderr, "<=  "); break;
				case UPSD_CMP_GT:	fprintf (stderr, ">   "); break;
				case UPSD_CMP_GE:	fprintf (stderr, ">=  "); break;
				}
				switch (tt->trig) {
				case UPSD_DS_MODEL:
					fprintf (stderr, "\"%s\"", (char *)(tt->rhs));
					break;
				case UPSD_DS_BAT_COND:
					if ((int)(tt->rhs)) {
						fprintf (stderr, "\"weak\"");
					} else {
						fprintf (stderr, "\"normal\"");
					}
					break;
				case UPSD_DS_BAT_IS:
					if ((int)(tt->rhs)) {
						fprintf (stderr, "\"discharging\"");
					} else {
						fprintf (stderr, "\"charging\"");
					}
					break;
				case UPSD_DS_BAT_VOLTS:
				case UPSD_DS_IN_FREQ:
				case UPSD_DS_IN_VOLTS:
				case UPSD_DS_OUT_FREQ:
				case UPSD_DS_OUT_VOLTS:
					fprintf (stderr, "%.1f", (double)((int)(tt->rhs)) / 10.0);
					break;
				case UPSD_DS_BAT_TEMP:
				case UPSD_DS_BAT_CHARGE:
				case UPSD_DS_OUT_LOAD:
					fprintf (stderr, "%d", (int)(tt->rhs));
					break;
				case UPSD_DS_STATUS:
					{
						int fld, match;

						for (fld = (int)(tt->rhs); fld; fld &= ~match) {
							if ((match = UPSD_ST_OVERHEAT) && (fld & match)) {
								fprintf (stderr, "overheat");
							} else if ((match = UPSD_ST_ONBATTERY) && (fld & match)) {
								fprintf (stderr, "on-battery");
							} else if ((match = UPSD_ST_OUTPUTBAD) && (fld & match)) {
								fprintf (stderr, "output-bad");
							} else if ((match = UPSD_ST_OVERLOAD) && (fld & match)) {
								fprintf (stderr, "overload");
							} else if ((match = UPSD_ST_BYPASSBAD) && (fld & match)) {
								fprintf (stderr, "bypass-bad");
							} else if ((match = UPSD_ST_OUTPUTOFF) && (fld & match)) {
								fprintf (stderr, "output-off");
							} else if ((match = UPSD_ST_CHARGERBAD) && (fld & match)) {
								fprintf (stderr, "charger-bad");
							} else if ((match = UPSD_ST_UPSOFF) && (fld & match)) {
								fprintf (stderr, "ups-off");
							} else if ((match = UPSD_ST_FANFAIL) && (fld & match)) {
								fprintf (stderr, "fan-failure");
							} else if ((match = UPSD_ST_FUSEBREAK) && (fld & match)) {
								fprintf (stderr, "fuse-break");
							} else if ((match = UPSD_ST_FAULT) && (fld & match)) {
								fprintf (stderr, "ups-fault");
							} else if ((match = UPSD_ST_AWAITPOWER) && (fld & match)) {
								fprintf (stderr, "awaiting-power");
							} else if ((match = UPSD_ST_BUZZERON) && (fld & match)) {
								fprintf (stderr, "buzzer-alarm-on");
							} else {
								fprintf (stderr, "<oops?!>");
								match = fld;
							}
							if (fld != match) {
								fprintf (stderr, " ");
							}
						}
					}
					break;
				}
				fprintf (stderr, "  (");
				for (i=0; i<DA_CUR(tt->inv); i++) {
					fprintf (stderr, "%s", (DA_NTHITEM(tt->inv, i))->trash->name);
					if (i != ((DA_CUR (tt->inv)) - 1)) {
						fprintf (stderr, " ");
					}
				}
				fprintf (stderr, ")\n");
			}
		}
		/*}}}*/
		/*{{{  dump alarms*/
		if (upsinfo.salarms) {
			char devstr[32];	/* only 20 really */
			char alarmstr[64];

			fprintf (stderr, "\n    ALARM         DELAY   DEVICES             ACTION                  \n");
			fprintf (stderr, "    ------------------------------------------------------------------\n");
			for (as = upsinfo.salarms; as; as = as->next) {
				int missed = 0;
				char *ch = devstr;
				int left = 16;

				getalarmstr (as->alarm, alarmstr);
				/* expensive -- but not important here -- search through sdev/netcli for devices */
				for (ts = upsinfo.sdev; ts; ts = ts->next) {
					for (i=0; i<DA_CUR(ts->alarms); i++) {
						if (DA_NTHITEM(ts->alarms, i) == as) {
							int slen = strlen (ts->name);

							if (slen >= left) {
								missed++;
							} else {
								strcpy (ch, ts->name);
								left -= slen;
								ch += slen;
								strcpy (ch, " ");
								left--, ch++;
							}
							break;	/* inner for() */
						}
					}
				}
				for (ns = upsinfo.netcli; ns; ns = ns->next) {
					for (i=0; i<DA_CUR(ns->alarms); i++) {
						if (DA_NTHITEM(ns->alarms, i) == as) {
							int slen = strlen (ns->name);

							if (slen >= left) {
								missed++;
							} else {
								strcpy (ch, ns->name);
								left -= slen;
								ch += slen;
								strcpy (ch, " ");
								left--, ch++;
							}
							break;	/* inner for() */
						}
					}
				}
				if (missed) {
					strcpy (ch, "...");
				}
				fprintf (stderr, "    %-14s%-8d%-20s", alarmstr, (int)as->time, devstr);
				switch (as->action) {
				case UPSD_ACTION_LOG:
					fprintf (stderr, "LOG\n");
					break;
				case UPSD_ACTION_EXEC:
					fprintf (stderr, "EXEC %s%s\n", ((openupsd_exec_t *)as->xdata)->path_to_run, (DA_CUR(((openupsd_exec_t *)as->xdata)->args) > 1) ? " ..." : "");
					break;
				default:
					fprintf (stderr, "<unknown %d>\n", as->action);
					break;
				}
			}
		}
		/*}}}*/
		/*{{{  dump output files*/
		if (upsinfo.outfiles) {
			char devstr[64];

			fprintf (stderr, "\n    FILENAME                        DEVICES                           \n");
			fprintf (stderr, "    ------------------------------------------------------------------\n");
			if (upsinfo.pidfilename) {
				fprintf (stderr, "    %-32s(pid)\n", upsinfo.pidfilename);
			}
			for (fs = upsinfo.outfiles; fs; fs = fs->next) {
				int missed = 0;
				char *ch = devstr;
				int left = 48;

				/* expensive -- but not important here -- search through sdev/netcli for devices */
				for (ts = upsinfo.sdev; ts; ts = ts->next) {
					for (i=0; i<DA_CUR(ts->outfiles); i++) {
						if (DA_NTHITEM(ts->outfiles, i) == fs) {
							int slen = strlen (ts->name);

							if (slen >= left) {
								missed++;
							} else {
								strcpy (ch, ts->name);
								left -= slen;
								ch += slen;
								strcpy (ch, " ");
								left--, ch++;
							}
							break;	/* inner for() */
						}
					}
				}
				for (ns = upsinfo.netcli; ns; ns = ns->next) {
					for (i=0; i<DA_CUR(ns->outfiles); i++) {
						if (DA_NTHITEM(ns->outfiles, i) == fs) {
							int slen = strlen (ns->name);

							if (slen >= left) {
								missed++;
							} else {
								strcpy (ch, ns->name);
								left -= slen;
								ch += slen;
								strcpy (ch, " ");
								left--, ch++;
							}
							break;	/* inner for() */
						}
					}
				}
				if (missed) {
					strcpy (ch, "...");
				}
				fprintf (stderr, "    %-32s%s\n", fs->fname, devstr);
			}
		}
		/*}}}*/
		/*{{{  dump network server info*/
		if (upsinfo.netsvr) {
			char devstr[32];	/* only 20 really */
			char listenstr[32];

			fprintf (stderr, "\n    LISTEN                DEVICES             ACCESS                  \n");
			fprintf (stderr, "    ------------------------------------------------------------------\n");
			for (vs = upsinfo.netsvr; vs; vs = vs->next) {
				int missed = 0;
				char *ch = devstr;
				int left = 16;

				sprintf (listenstr, "%s:%d", inet_ntoa (vs->listen_host.sin_addr), (int)ntohs(vs->listen_host.sin_port));
				/* expensive -- but not important here -- search through sdev/netcli for devices */
				for (ts = upsinfo.sdev; ts; ts = ts->next) {
					for (i=0; i<DA_CUR(ts->outtcpsvrs); i++) {
						if (DA_NTHITEM(ts->outtcpsvrs, i) == vs) {
							int slen = strlen (ts->name);

							if (slen >= left) {
								missed++;
							} else {
								strcpy (ch, ts->name);
								left -= slen;
								ch += slen;
								strcpy (ch, " ");
								left--, ch++;
							}
							break;	/* inner for() */
						}
					}
				}
				for (ns = upsinfo.netcli; ns; ns = ns->next) {
					for (i=0; i<DA_CUR(ns->outtcpsvrs); i++) {
						if (DA_NTHITEM(ns->outtcpsvrs, i) == vs) {
							int slen = strlen (ns->name);

							if (slen >= left) {
								missed++;
							} else {
								strcpy (ch, ns->name);
								left -= slen;
								ch += slen;
								strcpy (ch, " ");
								left--, ch++;
							}
							break;	/* inner for() */
						}
					}
				}
				if (missed) {
					strcpy (ch, "...");
				}
				fprintf (stderr, "    %-22s%-20s", listenstr, devstr);
				/* the the access control */
				if (!DA_CUR(vs->allow) && !DA_CUR(vs->disallow)) {
					/* nothing specified, allow anyone to connect */
					fprintf (stderr, "(no access control)\n");
				} else {
					/* DENY comes first if present */
					for (i=0; i<DA_CUR(vs->disallow); i++) {
						struct in_addr tmpaddr;

						tmpaddr.s_addr = (DA_NTHITEM(vs->disallow, i))->allow_net;
						fprintf (stderr, "DENY %s/", inet_ntoa (tmpaddr));
						tmpaddr.s_addr = (DA_NTHITEM(vs->disallow, i))->allow_mask;
						fprintf (stderr, "%s ", inet_ntoa (tmpaddr));
					}
					/* then ALLOW, if any */
					for (i=0; i<DA_CUR(vs->allow); i++) {
						struct in_addr tmpaddr;

						tmpaddr.s_addr = (DA_NTHITEM(vs->allow, i))->allow_net;
						fprintf (stderr, "ALLOW %s/", inet_ntoa (tmpaddr));
						tmpaddr.s_addr = (DA_NTHITEM(vs->allow, i))->allow_mask;
						fprintf (stderr, "%s ", inet_ntoa (tmpaddr));
					}
					/* then policy */
					if (!DA_CUR(vs->allow)) {
						/* default ALLOW if not DENYed */
						fprintf (stderr, "(else ALLOW)\n");
					} else {
						fprintf (stderr, "(else DENY)\n");
					}
				}
			}
		}
		/*}}}*/
	}
	/*}}}*/

	/*{{{  initialise signal handling*/
	sigdata[0] = 0;
	sigdata[1] = 0;
	sigdata[2] = 0;
	sigflag = 0;
	if (init_signal_handlers ()) {
		openupsd_log (&upsinfo, LOG_ERR, "failed to initialise signal handlers.  this is pretty bad, exiting.");
#ifdef SUPPORT_SYSLOG
		if (upsinfo.use_syslog) {
			closelog ();
		}
#endif
		exit (EXIT_FAILURE);
	}
	/*}}}*/

	/* oki, new code proper now.. deamonise first, things are in a semi-sane state (config file parsed OK) */
	/*{{{  daemonise unless told not to*/
	if (upsinfo.daemonise) {
		fflush (stdout);
		fflush (stderr);

		switch (fork ()) {
		case -1:
			fprintf (stderr, "%s: fork() failed with %s\n", progname, strerror (errno));
			exit (EXIT_FAILURE);
			break;
		case 0:
			/* child process, become process group and session leader */
			setsid ();
			break;
		default:
			/* this is the parent process, exit */
			_exit (0);
			break;
		}
		/* fork again to let group leader exit */
		switch (fork ()) {
		case -1:
			fprintf (stderr, "%s: fork() failed with %s\n", progname, strerror (errno));
			exit (EXIT_FAILURE);
			break;
		case 0:
			/* child */
			break;
		default:
			/* parent, exit */
			_exit (0);
			break;
		}
		/* can't ever regain a controlling terminal from here on in */
		chdir ("/");
		umask (0);	/* already checked any files we might need to write to */

		/* get rid of old descriptors */
		close (0);
		close (1);
		close (2);
#ifdef HAVE_SYSCONF
		{
			int i;

			i = (int)sysconf (_SC_OPEN_MAX);
			while (i > 0) {
				i--;
				close (i);
			}
		}
#else
		/* just get rid of stdin, stdout and stderr */
		{
			int i;

			/* paranoia code */
			for (i=0; i<3; i++) {
				close (i);
			}
		}
#endif
	} else {
		/* not a daemon, don't open syslog.. */
#ifdef SUPPORT_SYSLOG
		upsinfo.use_syslog = 0;
		if (upsinfo.logfilename) {
			sfree (upsinfo.logfilename);
			upsinfo.logfilename = NULL;
		}
#endif
	}
	/*}}}*/

#ifdef SUPPORT_SYSLOG
	/*{{{  open syslog if desired */
	if (upsinfo.use_syslog) {
		openlog (progname, LOG_CONS | LOG_NDELAY, LOG_DAEMON);
	}
	/*}}}*/
#endif
	/*{{{  write a PID file if specified*/
	if (upsinfo.pidfilename) {
		FILE *lfp;

		if (!(lfp = fopen (upsinfo.pidfilename, "w"))) {
			openupsd_log (&upsinfo, LOG_WARNING, "unable to open PID file %s for writing.\n", upsinfo.pidfilename);
		} else {
			fprintf (lfp, "%d\n", (int)getpid ());
			fclose (lfp);
		}
	}
	/*}}}*/
	/*{{{  say hello*/
	openupsd_log (&upsinfo, LOG_INFO, "openupsd version " VERSION " starting...");
	/*}}}*/
	/*{{{  go through serial devices and initialise ports*/
	{
		openupsd_sdev_t *ts, *next_ts;

		for (ts = upsinfo.sdev; ts; ts = next_ts) {
			next_ts = ts->next;
			if (set_serial_state (ts)) {
				openupsd_log (&upsinfo, LOG_ERR, "failed to initialise serial-port for device %s, but trying to continue", ts->name);
				restore_serial_state (ts);

				remove_sdev (&upsinfo, ts);
				sfree (ts->name);
				sfree (ts->device);
				dynarray_trash (ts->outfiles);
				dynarray_trash (ts->outtcpsvrs);
				dynarray_trash (ts->alarms);
				sfree (ts);
			} else {
				ndevs++;
			}
		}
	}
	/*}}}*/
	/*{{{  go through network server devices, bind and set listening*/
	{
		openupsd_netsvr_t *ns, *next_ns;
		int int_flag = 1;
		int int_size = sizeof (int_flag);

		for (ns = upsinfo.netsvr; ns; ns = next_ns) {
			next_ns = ns->next;

			ns->fd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
			if (ns->fd < 0) {
				openupsd_log (&upsinfo, LOG_ERR, "failed to create server socket, but trying to continue");
			} else if (fcntl (ns->fd, F_SETFL, O_NONBLOCK) < 0) {
				openupsd_log (&upsinfo, LOG_ERR, "failed to set non-blocking option on server socket, but trying to continue");
				close (ns->fd);
				ns->fd = -1;
			} else if (bind (ns->fd, (struct sockaddr *)&(ns->listen_host), sizeof (struct sockaddr_in)) < 0) {
				openupsd_log (&upsinfo, LOG_ERR, "failed to bind server socket, but trying to continue");
				close (ns->fd);
				ns->fd = -1;
			} else if (listen (ns->fd, 32) < 0) {
				openupsd_log (&upsinfo, LOG_ERR, "failed to listen on server socket, but trying to continue");
				close (ns->fd);
				ns->fd = -1;
			}
			if (ns->fd < 0) {
				/* remove this one */
				remove_netsvr (&upsinfo, ns);
				dynarray_trash (ns->allow);
				dynarray_trash (ns->disallow);
				dynarray_trash (ns->clients);
				sfree (ns);
			} else if (setsockopt (ns->fd, SOL_SOCKET, SO_REUSEADDR, (void *)(&int_flag), (socklen_t)int_size)) {
				if (upsinfo.verbose) {
					openupsd_log (&upsinfo, LOG_NOTICE, "failed to set SO_REUSEADDR on server socket.");
				}
			}
		}
	}
	/*}}}*/
	/*{{{  go through network client devices, start connection*/
	{
		openupsd_netcli_t *ns, *next_ns;

		for (ns = upsinfo.netcli; ns; ns = next_ns) {
			next_ns = ns->next;

			ns->fd = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
			if (ns->fd < 0) {
				openupsd_log (&upsinfo, LOG_ERR, "failed to create client socket, but trying to continue");
			} else if (fcntl (ns->fd, F_SETFL, O_NONBLOCK) < 0) {
				openupsd_log (&upsinfo, LOG_ERR, "failed to set non-blocking option on client socket, but trying to continue");
				close (ns->fd);
				ns->fd = -1;
			} else {
				/* attempt initial connection */
				int r;
				int int_flag = 1;
				int int_size = sizeof (int_flag);
				
				if (setsockopt (ns->fd, SOL_SOCKET, SO_REUSEADDR, (void *)(&int_flag), (socklen_t)int_size)) {
					if (upsinfo.verbose) {
						openupsd_log (&upsinfo, LOG_NOTICE, "failed to set SO_REUSEADDR on client socket.");
					}
				}
				r = connect (ns->fd, (struct sockaddr *)&(ns->ups_host), sizeof (struct sockaddr_in));
				if ((r < 0) && (errno == EINPROGRESS)) {
					ns->state = UPSD_NETCLI_CONNECTING;
					if (upsinfo.verbose) {
						openupsd_log (&upsinfo, LOG_INFO, "backgrounding remote connection..");
					}
				} else if (!r) {
					ns->state = UPSD_NETCLI_CONNECTED;
					if (upsinfo.verbose) {
						openupsd_log (&upsinfo, LOG_INFO, "connected to remote server.");
					}
				} else {
					openupsd_log (&upsinfo, LOG_NOTICE, "failed to connect, will try again..");
				}
			}
			if (ns->fd < 0) {
				/* remove this one */
				remove_netcli (&upsinfo, ns);
				sfree (ns->name);
				dynarray_trash (ns->outfiles);
				dynarray_trash (ns->outtcpsvrs);
				dynarray_trash (ns->alarms);
				sfree (ns);
			} else {
				ndevs++;	/* perhaps speculative, but heyho.. */
			}
		}
	}
	/*}}}*/
	/*{{{  exit if no available devices*/
	if (!ndevs) {
		openupsd_log (&upsinfo, LOG_ERR, "no devices available, exiting.");
#ifdef SUPPORT_SYSLOG
		if (upsinfo.use_syslog) {
			closelog ();
		}
#endif
		if (upsinfo.pidfilename) {
			/* remove PID file (or try to) */
			unlink (upsinfo.pidfilename);
		}
		exit (EXIT_FAILURE);
	}
	/*}}}*/
	/*{{{  short delay*/
	if (upsinfo.verbose) {
		openupsd_log (&upsinfo, LOG_INFO, "%d devices initialised.  pausing for things to settle.", ndevs);
	}
	microdelay (100000);
	/*}}}*/

	/* do the magic initialisation stuff on the serial devices */
	/*{{{  this bit was extracted from strace output */
	{
		openupsd_sdev_t *ts, *next_ts;

		for (ts = upsinfo.sdev; ts; ts = next_ts) {
			next_ts = ts->next;
			if (ts->fd > -1) {
				int v;
				int ierr = 0;

				v = TIOCM_DTR;
				ioctl (ts->fd, TIOCMBIC, &v);
				v = TIOCM_RTS;
				ioctl (ts->fd, TIOCMBIS, &v);
				tcflush (ts->fd, TCIOFLUSH);
				microdelay (100000);
				tcflush (ts->fd, TCIFLUSH);
				microdelay (100000);

				/* do some peculiar initialisation song+dance */
				if (init_loops (&upsinfo, ts, 8, 4)) {
					ierr = 1;
				} else {
					tcflush (ts->fd, TCIFLUSH);
					microdelay (100000);
					null_read (ts->fd, 127);
					tcflush (ts->fd, TCIFLUSH);
					microdelay (100000);

					if (stock_initialisation (&upsinfo, ts)) {
						ierr = 2;
					}
				}
				if (ierr) {
					ndevs--;
					openupsd_log (&upsinfo, LOG_NOTICE, "failed to %s device %s, but trying to continue", (ierr == 1) ? "detect" : "initialise", ts->name);
					restore_serial_state (ts);

					remove_sdev (&upsinfo, ts);
					sfree (ts->name);
					sfree (ts->device);
					dynarray_trash (ts->outfiles);
					sfree (ts);
				}
			}
		}

	}
	/*}}}*/
	/*{{{  exit if no available devices*/
	if (!ndevs) {
		openupsd_log (&upsinfo, LOG_ERR, "no devices available, exiting.");
#ifdef SUPPORT_SYSLOG
		if (upsinfo.use_syslog) {
			closelog ();
		}
#endif
		if (upsinfo.pidfilename) {
			/* remove PID file (or try to) */
			unlink (upsinfo.pidfilename);
		}
		exit (EXIT_FAILURE);
	}
	/*}}}*/

	/* right, run main program thing */
	errd = openupsd_mainprog (&upsinfo);

	/*{{{  shutdown and exit */
	{
		openupsd_sdev_t *ts, *next_ts;

		restore_signal_handlers ();
		if (upsinfo.verbose) {
			openupsd_log (&upsinfo, LOG_NOTICE, "server shutting down...");
		}
		for (ts = upsinfo.sdev; ts; ts = next_ts) {
			next_ts = ts->next;

			tcflush (ts->fd, TCIFLUSH);
			microdelay (100000);
			restore_serial_state (ts);
			sfree (ts->name);
			sfree (ts->device);
			dynarray_trash (ts->outfiles);
			sfree (ts);		/* almost silly, but not quite.. */
		}
		if (upsinfo.pidfilename) {
			/* remove PID file (or try to) */
			unlink (upsinfo.pidfilename);
		}
	}
	/*}}}*/
	if (errd) {
		return EXIT_FAILURE;
	}
	return EXIT_SUCCESS;
}
/*}}}*/




syntax highlighted by Code2HTML, v. 0.9.1