/*
* 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