/*
* cfg.c -- config file handling for openupsd
* Copyright (C) 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.
*/
/*{{{ includes, defines, etc.*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <termios.h>
#include <errno.h>
#include <netdb.h>
#include "support.h"
#include "openupsd.h"
/*}}}*/
/*{{{ strings table*/
static char *trig_strings[] = UPSD_DS_STRINGS;
/*}}}*/
/*{{{ static openupsd_netsvr_t *makelinktcpnetsvr (openupsd_netsvr_t **svrlist, char *dnames, char *hostport, openupsd_sdev_t *sdevs, openupsd_netcli_t *netclis, FILE *errstream)*/
/*
* this processes a NETSERVER TCP directive, linking the server info into
* the `sdevs' and `netclis' as appropriate. note, might trash contents of "dnames"..
* returns the new/existing entry on success, NULL on failure
*/
static openupsd_netsvr_t *makelinktcpnetsvr (openupsd_netsvr_t **svrlist, char *dnames, char *hostport, openupsd_sdev_t *sdevs, openupsd_netcli_t *netclis, FILE *errstream)
{
openupsd_netsvr_t *tmp;
int gotdev = 0;
int port;
char *ch = strchr (hostport, ':');
struct hostent *hp;
struct sockaddr_in sin;
if (!ch) {
if (errstream) {
fprintf (errstream, "%s: malformed host:port: %s\n", progname, hostport);
}
return NULL;
}
if (sscanf (ch + 1, "%d", &port) != 1) {
if (errstream) {
fprintf (errstream, "%s: malformed port: %s\n", progname, ch + 1);
}
return NULL;
}
*ch = '\0';
if (strcmp (hostport, "*")) {
hp = gethostbyname (hostport);
if (!hp) {
if (errstream) {
fprintf (errstream, "%s: failed to resolve %s\n", progname, hostport);
}
return NULL;
}
if (hp->h_addrtype != AF_INET) {
if (errstream) {
fprintf (errstream, "%s: only INET address type supported (for %s)\n", progname, hostport);
}
return NULL;
}
memcpy (&(sin.sin_addr), hp->h_addr, sizeof (struct in_addr));
} else {
sin.sin_addr.s_addr = INADDR_ANY;
}
sin.sin_family = AF_INET;
sin.sin_port = htons (port);
/* see if we're listening here already */
for (tmp = *svrlist; tmp; tmp = tmp->next) {
if ((tmp->listen_host.sin_addr.s_addr == sin.sin_addr.s_addr) && (tmp->listen_host.sin_port == sin.sin_port)) {
break;
}
}
if (!tmp) {
/* create one and add to the list */
tmp = (openupsd_netsvr_t *)smalloc (sizeof (openupsd_netsvr_t));
tmp->next = tmp->prev = NULL;
tmp->listen_host.sin_addr.s_addr = sin.sin_addr.s_addr;
tmp->listen_host.sin_port = sin.sin_port;
tmp->listen_host.sin_family = sin.sin_family;
tmp->fd = -1;
dynarray_init (tmp->allow);
dynarray_init (tmp->disallow);
dynarray_init (tmp->clients);
if (*svrlist) {
tmp->next = *svrlist;
(*svrlist)->prev = tmp;
}
*svrlist = tmp;
}
/* then deal with dnames and make the links */
if (!strcmp (dnames, "*")) {
/* link to all things */
openupsd_sdev_t *ts;
openupsd_netcli_t *ns;
for (ts = sdevs; ts; ts = ts->next) {
dynarray_maybeadd (ts->outtcpsvrs, tmp);
gotdev++;
}
for (ns = netclis; ns; ns = ns->next) {
dynarray_maybeadd (ns->outtcpsvrs, tmp);
gotdev++;
}
} else if (!strchr (dnames, ',')) {
/* just one thing, go looking for it */
openupsd_sdev_t *ts;
openupsd_netcli_t *ns;
for (ts = sdevs; !gotdev && ts; ts = ts->next) {
if (!strcmp (dnames, ts->name)) {
dynarray_maybeadd (ts->outtcpsvrs, tmp);
gotdev++;
}
}
for (ns = netclis; !gotdev && ns; ns = ns->next) {
if (!strcmp (dnames, ns->name)) {
dynarray_maybeadd (ns->outtcpsvrs, tmp);
gotdev++;
}
}
} else {
/* got comma, so a list of things */
char *ch, *dh;
openupsd_sdev_t *ts;
openupsd_netcli_t *ns;
for (ch = dnames; *ch != '\0'; ch++) {
char *name = ch;
int lgotdev = 0;
for (dh = ch; (*dh != ',') && (*dh != '\0'); dh++);
if (*dh == ',') {
*dh = '\0';
ch = dh;
} else {
ch = dh - 1;
}
/* name valid here */
for (ts = sdevs; !lgotdev && ts; ts = ts->next) {
if (!strcmp (name, ts->name)) {
dynarray_maybeadd (ts->outtcpsvrs, tmp);
lgotdev++;
}
}
for (ns = netclis; !lgotdev && ns; ns = ns->next) {
if (!strcmp (name, ns->name)) {
dynarray_maybeadd (ns->outtcpsvrs, tmp);
lgotdev++;
}
}
if (lgotdev) {
gotdev++;
}
}
}
if (!gotdev) {
/* no devices */
if (errstream) {
fprintf (stderr, "%s: no devices found for OUTPUT\n", progname);
}
return NULL;
}
return tmp;
}
/*}}}*/
/*{{{ static int linkalarm (openupsd_alarm_t *alrm, char *dnames, openupsd_sdev_t *sdevs, openupsd_netcli_t *netclis, FILE *errstream)*/
static int linkalarm (openupsd_alarm_t *alrm, char *dnames, openupsd_sdev_t *sdevs, openupsd_netcli_t *netclis, FILE *errstream)
{
int gotdev = 0;
if (!strcmp (dnames, "*")) {
/* link to all things */
openupsd_sdev_t *ts;
openupsd_netcli_t *ns;
for (ts = sdevs; ts; ts = ts->next) {
if (dynarray_maybeadd (ts->alarms, alrm)) {
dynarray_add (ts->triggered, -1);
}
gotdev++;
}
for (ns = netclis; ns; ns = ns->next) {
if (dynarray_maybeadd (ns->alarms, alrm)) {
dynarray_add (ns->triggered, -1);
}
gotdev++;
}
} else if (!strchr (dnames, ',')) {
/* just one thing, go looking for it */
openupsd_sdev_t *ts;
openupsd_netcli_t *ns;
for (ts = sdevs; !gotdev && ts; ts = ts->next) {
if (!strcmp (dnames, ts->name)) {
if (dynarray_maybeadd (ts->alarms, alrm)) {
dynarray_add (ts->triggered, -1);
}
gotdev++;
}
}
for (ns = netclis; !gotdev && ns; ns = ns->next) {
if (!strcmp (dnames, ns->name)) {
if (dynarray_maybeadd (ns->alarms, alrm)) {
dynarray_add (ns->triggered, -1);
}
gotdev++;
}
}
} else {
/* got comma, so a list of things */
char *ch, *dh;
openupsd_sdev_t *ts;
openupsd_netcli_t *ns;
for (ch = dnames; *ch != '\0'; ch++) {
char *name = ch;
int lgotdev = 0;
for (dh = ch; (*dh != ',') && (*dh != '\0'); dh++);
if (*dh == ',') {
*dh = '\0';
ch = dh;
} else {
ch = dh - 1;
}
/* name valid here */
for (ts = sdevs; !lgotdev && ts; ts = ts->next) {
if (!strcmp (name, ts->name)) {
if (dynarray_maybeadd (ts->alarms, alrm)) {
dynarray_add (ts->triggered, -1);
}
lgotdev++;
}
}
for (ns = netclis; !lgotdev && ns; ns = ns->next) {
if (!strcmp (name, ns->name)) {
if (dynarray_maybeadd (ns->alarms, alrm)) {
dynarray_add (ns->triggered, -1);
}
lgotdev++;
}
}
if (lgotdev) {
gotdev++;
}
}
}
if (!gotdev) {
/* no devices */
if (errstream) {
fprintf (stderr, "%s: no devices found for ALARM\n", progname);
}
return -1;
}
return 0;
}
/*}}}*/
/*{{{ static openupsd_netcli_t *maketcpnetclidev (char *name, char *hostport)*/
static openupsd_netcli_t *maketcpnetclidev (char *name, char *hostport)
{
openupsd_netcli_t *tmp;
char *ch;
int port;
struct hostent *hp;
ch = strchr (hostport, ':');
if (!ch) {
return NULL;
}
if (sscanf (ch+1, "%d", &port) != 1) {
/* make sure we haven't trashed the string by this point ;) */
return NULL;
}
*ch = '\0';
hp = gethostbyname (hostport);
if (!hp) {
return NULL;
}
if (hp->h_addrtype != AF_INET) {
return NULL;
}
tmp = (openupsd_netcli_t *)smalloc (sizeof (openupsd_netcli_t));
tmp->next = tmp->prev = NULL;
tmp->name = string_dup (name);
tmp->fd = -1;
tmp->retry = 60;
tmp->state = UPSD_NETCLI_INACTIVE;
memcpy (&(tmp->ups_host.sin_addr), hp->h_addr, sizeof (struct in_addr));
tmp->ups_host.sin_family = AF_INET;
tmp->ups_host.sin_port = htons (port);
tmp->inbuf = NULL;
tmp->bufsize = 0;
tmp->inbytes = 0;
dynarray_init (tmp->outfiles);
dynarray_init (tmp->outtcpsvrs);
dynarray_init (tmp->alarms);
dynarray_init (tmp->triggered);
return tmp;
}
/*}}}*/
/*{{{ static int decode_alarm_trigger (char *trigger)*/
static int decode_alarm_trigger (char *trigger)
{
int i;
for (i=0; i<UPSD_DS_NUMFIELDS; i++) {
if (!strcasecmp (trigger, trig_strings[i])) {
return i;
}
}
return -1;
}
/*}}}*/
/*{{{ static int decode_alarm_cmp (char *cmp)*/
static int decode_alarm_cmp (char *cmp)
{
int i = -1;
switch (*cmp) {
case '!':
cmp++;
if (*cmp == '=') {
i = UPSD_CMP_NE;
cmp++;
}
break;
case '=':
cmp++;
i = UPSD_CMP_EQ;
if (*cmp == '=') {
cmp++;
}
break;
case '<':
cmp++;
if (*cmp == '=') {
cmp++;
i = UPSD_CMP_LE;
} else {
i = UPSD_CMP_LT;
}
break;
case '>':
cmp++;
if (*cmp == '=') {
cmp++;
i = UPSD_CMP_GE;
} else {
i = UPSD_CMP_GT;
}
break;
}
if (*cmp != '\0') {
return -1;
}
return i;
}
/*}}}*/
/*{{{ static openupsd_alarm_t *makelogalarm (openupsd_trigger_t *trigger, char *delay)*/
static openupsd_alarm_t *makelogalarm (openupsd_trigger_t *trigger, char *delay)
{
openupsd_alarm_t *tmp;
int td;
if (delay) {
if (sscanf (delay, "%d", &td) != 1) {
return NULL;
}
} else {
td = 0;
}
tmp = (openupsd_alarm_t *)smalloc (sizeof (openupsd_alarm_t));
tmp->next = tmp->prev = NULL;
tmp->alarm = trigger;
tmp->time = (time_t)td;
tmp->action = UPSD_ACTION_LOG;
tmp->devname = NULL;
tmp->initial = 0;
tmp->xdata = NULL;
return tmp;
}
/*}}}*/
/*{{{ static openupsd_alarm_t *makeexecalarm (openupsd_trigger_t *trigger, char *delay)*/
static openupsd_alarm_t *makeexecalarm (openupsd_trigger_t *trigger, char *delay)
{
openupsd_alarm_t *tmp;
openupsd_exec_t *etmp;
int td;
if (delay) {
if (sscanf (delay, "%d", &td) != 1) {
return NULL;
}
} else {
td = 0;
}
etmp = (openupsd_exec_t *)smalloc (sizeof (openupsd_exec_t));
etmp->prev = etmp->next = NULL;
etmp->path_to_run = NULL;
etmp->alarm = NULL;
etmp->env = NULL;
dynarray_init (etmp->args);
tmp = (openupsd_alarm_t *)smalloc (sizeof (openupsd_alarm_t));
tmp->next = tmp->prev = NULL;
tmp->alarm = trigger;
tmp->time = (time_t)td;
tmp->action = UPSD_ACTION_EXEC;
tmp->devname = NULL;
tmp->initial = 0;
tmp->xdata = (void *)etmp;
return tmp;
}
/*}}}*/
/*{{{ static openupsd_trigger_t *openupsd_find_trigger (char *str, openupsd_trigger_t *tlist)*/
static openupsd_trigger_t *openupsd_find_trigger (char *str, openupsd_trigger_t *tlist)
{
while (tlist) {
if (!strcasecmp (tlist->name, str)) {
return tlist;
}
tlist = tlist->next;
}
return NULL;
}
/*}}}*/
/*{{{ static openupsd_sdev_t *makeserialdev (char *name, char *device, char *params, char *polltime)*/
static openupsd_sdev_t *makeserialdev (char *name, char *device, char *params, char *polltime)
{
openupsd_sdev_t *tmp;
int sbaud, sdata, sstop;
char sparity;
int pt;
if (sscanf (polltime, "%d", &pt) != 1) {
return NULL;
}
if ((pt < 5) || (pt > 86400)) {
/* min 5 secs, max 1 day */
return NULL;
}
if (sscanf (params, "%d-%d-%c-%d", &sbaud, &sdata, &sparity, &sstop) != 4) {
return NULL;
}
/* check sanity of data/parity/stop -- baud checked when the device is opened */
switch (sdata) {
case 6:
case 7:
case 8:
break;
default:
return NULL;
}
switch (sparity) {
case 'N':
case 'n':
case 'O':
case 'o':
case 'E':
case 'e':
case 'M':
case 'm':
case 'S':
case 's':
break;
default:
return NULL;
}
switch (sstop) {
case 1:
case 2:
break;
default:
return NULL;
}
tmp = (openupsd_sdev_t *)smalloc (sizeof (openupsd_sdev_t));
tmp->next = tmp->prev = NULL;
tmp->name = string_dup (name);
tmp->device = string_dup (device);
tmp->fd = -1;
tmp->inter_poll_sec = pt;
tmp->s_baud = sbaud;
tmp->s_data = sdata;
tmp->s_parity = sparity;
tmp->s_stop = sstop;
dynarray_init (tmp->outfiles);
dynarray_init (tmp->outtcpsvrs);
dynarray_init (tmp->alarms);
dynarray_init (tmp->triggered);
return tmp;
}
/*}}}*/
/*{{{ static int makelinkofile (openupsd_ofile_t **oflist, char *dnames, char *fname, openupsd_sdev_t *sdevs, openupsd_netcli_t *netclis, FILE *errstream)*/
/*
* this processes an OUTPUT directive, linking the various output files
* into the `sdevs' and `netclis' as appropriate. note, might trash contents of "dnames"..
* returns 0 on success, anything else on failure.
*/
static int makelinkofile (openupsd_ofile_t **oflist, char *dnames, char *fname, openupsd_sdev_t *sdevs, openupsd_netcli_t *netclis, FILE *errstream)
{
openupsd_ofile_t *tmp;
int gotdev = 0;
/* see if we've already got a file of this name */
for (tmp=*oflist; tmp; tmp = tmp->next) {
if (!strcmp (tmp->fname, fname)) {
break;
}
}
if (!tmp) {
/* create one and add to the list */
tmp = (openupsd_ofile_t *)smalloc (sizeof (openupsd_ofile_t));
tmp->next = tmp->prev = NULL;
tmp->fname = (char *)string_dup (fname);
if (*oflist) {
tmp->next = *oflist;
(*oflist)->prev = tmp;
}
*oflist = tmp;
}
/* then pick apart dnames */
if (!strcmp (dnames, "*")) {
/* link to all things */
openupsd_sdev_t *ts;
openupsd_netcli_t *ns;
for (ts = sdevs; ts; ts = ts->next) {
dynarray_maybeadd (ts->outfiles, tmp);
gotdev++;
}
for (ns = netclis; ns; ns = ns->next) {
dynarray_maybeadd (ns->outfiles, tmp);
gotdev++;
}
} else if (!strchr (dnames, ',')) {
/* just one thing, go looking for it */
openupsd_sdev_t *ts;
openupsd_netcli_t *ns;
for (ts = sdevs; !gotdev && ts; ts = ts->next) {
if (!strcmp (dnames, ts->name)) {
dynarray_maybeadd (ts->outfiles, tmp);
gotdev++;
}
}
for (ns = netclis; !gotdev && ns; ns = ns->next) {
if (!strcmp (dnames, ns->name)) {
dynarray_maybeadd (ns->outfiles, tmp);
gotdev++;
}
}
} else {
/* got a comma, so a list of things */
char *ch, *dh;
openupsd_sdev_t *ts;
openupsd_netcli_t *ns;
for (ch = dnames; *ch != '\0'; ch++) {
char *name = ch;
int lgotdev = 0;
for (dh = ch; (*dh != ',') && (*dh != '\0'); dh++);
if (*dh == ',') {
*dh = '\0';
ch = dh;
} else {
ch = dh - 1;
}
/* name valid here */
for (ts = sdevs; !lgotdev && ts; ts = ts->next) {
if (!strcmp (name, ts->name)) {
dynarray_maybeadd (ts->outfiles, tmp);
lgotdev++;
}
}
for (ns = netclis; !lgotdev && ns; ns = ns->next) {
if (!strcmp (name, ns->name)) {
dynarray_maybeadd (ns->outfiles, tmp);
lgotdev++;
}
}
if (lgotdev) {
gotdev++;
}
}
}
if (!gotdev) {
/* no devices */
if (errstream) {
fprintf (stderr, "%s: no devices found for OUTPUT\n", progname);
}
return -1;
}
return 0;
}
/*}}}*/
/*{{{ static openupsd_trigger_t *makeaddtrigger (openupsd_trigger_t **tlist, char *name, int field, int cmp, char *rhs)*/
static openupsd_trigger_t *makeaddtrigger (openupsd_trigger_t **tlist, char *name, int field, int cmp, char *rhs)
{
openupsd_trigger_t *tmp;
void *nrhs;
char *ch;
int x, y;
/* make sure we don't have one already */
for (tmp = *tlist; tmp; tmp = tmp->next) {
if (!strcmp (tmp->name, name)) {
return NULL;
}
}
/* look at field, check cmp and rhs */
switch (field) {
case UPSD_DS_MODEL: /* model */
if ((cmp != UPSD_CMP_EQ) && (cmp != UPSD_CMP_NE)) {
return NULL;
}
/* RHS must be a _quoted_ string */
if (*rhs != '\"') {
return NULL;
}
ch = rhs + 1;
if ((ch = strchr (ch, '\"')) == NULL) {
return NULL;
}
*ch = '\0';
nrhs = (void *)string_dup (rhs + 1);
break;
case UPSD_DS_BAT_COND: /* battery-condition (weak/normal) */
if ((cmp != UPSD_CMP_EQ) && (cmp != UPSD_CMP_NE)) {
return NULL;
}
/* RHS must be a _quoted_ string */
if (*rhs != '\"') {
return NULL;
}
ch = rhs + 1;
if ((ch = strchr (ch, '\"')) == NULL) {
return NULL;
}
*ch = '\0';
nrhs = (void *)0;
for (ch = rhs + 1; *ch != '\0'; ch++) {
char *dh = strchr (ch, ' ');
if (dh) {
*dh = '\0';
}
/* pick field from ch */
if (!strcmp (ch, "weak")) {
nrhs = (void *)1;
} else if (!strcmp (ch, "normal")) {
nrhs = (void *)0;
} else {
return NULL;
}
if (dh) {
ch = dh;
} else {
ch += (strlen (ch) - 1);
}
}
break;
case UPSD_DS_BAT_IS: /* battery-is (charging/discharging) */
if ((cmp != UPSD_CMP_EQ) && (cmp != UPSD_CMP_NE)) {
return NULL;
}
/* RHS must be a _quoted_ string */
if (*rhs != '\"') {
return NULL;
}
ch = rhs + 1;
if ((ch = strchr (ch, '\"')) == NULL) {
return NULL;
}
*ch = '\0';
nrhs = (void *)0;
for (ch = rhs + 1; *ch != '\0'; ch++) {
char *dh = strchr (ch, ' ');
if (dh) {
*dh = '\0';
}
/* pick field from ch */
if (!strcmp (ch, "discharging")) {
nrhs = (void *)1;
} else if (!strcmp (ch, "charging")) {
nrhs = (void *)0;
} else {
return NULL;
}
if (dh) {
ch = dh;
} else {
ch += (strlen (ch) - 1);
}
}
break;
case UPSD_DS_BAT_VOLTS: /* battery-volts (.1f) */
case UPSD_DS_IN_FREQ: /* input-frequency (.1f) */
case UPSD_DS_IN_VOLTS: /* input-voltage (.1f) */
case UPSD_DS_OUT_FREQ: /* output-frequency (.1f) */
case UPSD_DS_OUT_VOLTS: /* output-voltage (.1f) */
/* RHS is a number, factor of 10 out */
ch = strchr (rhs, '.');
if (ch) {
if (sscanf (rhs, "%d.%d", &x, &y) != 2) {
return NULL;
} else if ((y < 0) || (y > 9)) {
return NULL;
}
} else {
if (sscanf (rhs, "%d", &x) != 1) {
return NULL;
}
y = 0;
}
nrhs = (void *)((x * 10) + y);
break;
case UPSD_DS_BAT_CHARGE: /* battery-charge (d) */
case UPSD_DS_BAT_TEMP: /* battery-temperature (d) */
case UPSD_DS_OUT_LOAD: /* output-load (d) */
/* RHS is a simple INT */
if (sscanf (rhs, "%d", &x) != 1) {
return NULL;
}
nrhs = (void *)x;
break;
case UPSD_DS_STATUS: /* status-field */
if ((cmp != UPSD_CMP_EQ) && (cmp != UPSD_CMP_NE)) {
return NULL;
}
/* RHS must be a _quoted_ string */
if (*rhs != '\"') {
return NULL;
}
ch = rhs + 1;
if ((ch = strchr (ch, '\"')) == NULL) {
return NULL;
}
*ch = '\0';
nrhs = (void *)0; /* add flags to this */
for (ch = rhs + 1; *ch != '\0'; ch++) {
char *dh = strchr (ch, ' ');
if (dh) {
*dh = '\0';
}
/* pick field from ch */
if (!strcmp (ch, "overheat")) {
nrhs = (void *)((int)nrhs | UPSD_ST_OVERHEAT);
} else if (!strcmp (ch, "on-battery")) {
nrhs = (void *)((int)nrhs | UPSD_ST_ONBATTERY);
} else if (!strcmp (ch, "output-bad")) {
nrhs = (void *)((int)nrhs | UPSD_ST_OUTPUTBAD);
} else if (!strcmp (ch, "overload")) {
nrhs = (void *)((int)nrhs | UPSD_ST_OVERLOAD);
} else if (!strcmp (ch, "bypass-bad")) {
nrhs = (void *)((int)nrhs | UPSD_ST_BYPASSBAD);
} else if (!strcmp (ch, "output-off")) {
nrhs = (void *)((int)nrhs | UPSD_ST_OUTPUTOFF);
} else if (!strcmp (ch, "charger-bad")) {
nrhs = (void *)((int)nrhs | UPSD_ST_CHARGERBAD);
} else if (!strcmp (ch, "ups-off")) {
nrhs = (void *)((int)nrhs | UPSD_ST_UPSOFF);
} else if (!strcmp (ch, "fan-failure")) {
nrhs = (void *)((int)nrhs | UPSD_ST_FANFAIL);
} else if (!strcmp (ch, "fuse-break")) {
nrhs = (void *)((int)nrhs | UPSD_ST_FUSEBREAK);
} else if (!strcmp (ch, "ups-fault")) {
nrhs = (void *)((int)nrhs | UPSD_ST_FAULT);
} else if (!strcmp (ch, "awaiting-power")) {
nrhs = (void *)((int)nrhs | UPSD_ST_AWAITPOWER);
} else if (!strcmp (ch, "buzzer-alarm-on")) {
nrhs = (void *)((int)nrhs | UPSD_ST_BUZZERON);
} else {
return NULL;
}
if (dh) {
ch = dh;
} else {
ch += (strlen (ch) - 1);
}
}
break;
default:
return NULL;
}
/* right, build new one and add to the list */
tmp = (openupsd_trigger_t *)smalloc (sizeof (openupsd_trigger_t));
tmp->prev = tmp->next = NULL;
tmp->name = string_dup (name);
tmp->trig = field;
tmp->comp = cmp;
tmp->rhs = nrhs;
dynarray_init (tmp->inv);
if (*tlist) {
tmp->next = *tlist;
(*tlist)->prev = tmp;
}
*tlist = tmp;
return tmp;
}
/*}}}*/
/*{{{ static int add_network_netmask (int allow, openupsd_netsvr_t *svr, char *netspec)*/
/*
* adds allow/deny stuff to network servers
* returns 0 on success, -1 on error
*/
static int add_network_netmask (int allow, openupsd_netsvr_t *svr, char *netspec)
{
char *ch;
unsigned long netaddr = 0;
unsigned long netbits = 0xffffffff;
int a, b, c, d, i;
openupsd_netnet_t *tmp;
ch = strchr (netspec, '/');
if (ch) {
int l;
*ch = '\0';
ch++;
if ((sscanf (ch, "%d", &l) != 1) || (l < 0) || (l > 32)) {
return -1;
}
for (l = (31 - l); l >= 0; netbits <<= 1, l--);
}
if (sscanf (netspec, "%d.%d.%d.%d", &a, &b, &c, &d) != 4) {
return -1;
}
netaddr = ((unsigned long)(a & 0xff) << 24) | ((unsigned long)(b & 0xff) << 16) | ((unsigned long)(c & 0xff) << 8) | (unsigned long)(d & 0xff);
/* network byte-order them */
netaddr = htonl (netaddr);
netbits = htonl (netbits);
/* see if already here.. */
for (i = 0; i < (allow ? DA_CUR(svr->allow) : DA_CUR(svr->disallow)); i++) {
openupsd_netnet_t *item = (allow ? DA_NTHITEM(svr->allow, i) : DA_NTHITEM(svr->disallow, i));
if ((item->allow_mask == netbits) && ((item->allow_net & item->allow_mask) == (netaddr & netbits))) {
/* already here */
return 0;
}
}
/* not here, create a new one and add it */
tmp = (openupsd_netnet_t *)smalloc (sizeof (openupsd_netnet_t));
tmp->allow_net = netaddr;
tmp->allow_mask = netbits;
if (allow) {
dynarray_add(svr->allow, tmp);
} else {
dynarray_add(svr->disallow, tmp);
}
return 0;
}
/*}}}*/
/*{{{ static char *getnextarg (char **buf, int *nextarg)*/
static char *getnextarg (char **buf, int *nextarg)
{
char *ch, *arg;
arg = *buf;
for (ch=*buf; (*ch != '\0') && (*ch != ' ') && (*ch != '\t'); ch++);
if (*ch == '\0') {
*nextarg = 0;
*buf = ch;
} else {
*ch = '\0';
for (ch++; (*ch == ' ') || (*ch == '\t'); ch++);
*buf = ch;
*nextarg = *nextarg + 1;
}
return arg;
}
/*}}}*/
/*{{{ int parse_config (openupsd_t *upsinfo)*/
/*
* reads the configuration file and populates parts of upsinfo
* this is relaxed -- spouts configuration errors to `errstream' if not null
* returns 0 on success, number of errors encountered on failure
*/
int parse_config (openupsd_t *upsinfo, FILE *errstream)
{
FILE *fp;
char pbuf[1024];
openupsd_sdev_t *sdevlist = NULL;
openupsd_alarm_t *alarms = NULL;
openupsd_netcli_t *netclis = NULL;
openupsd_trigger_t *trigs = NULL;
openupsd_inv_t *invds = NULL;
int cline = 0;
int errcount = 0;
if (!upsinfo->conffile) {
return -1;
} else if (access (upsinfo->conffile, R_OK)) {
return -1;
}
fp = fopen (upsinfo->conffile, "r");
if (!fp) {
return -1;
}
/* oki, go parse! */
while (!feof (fp)) {
char *ch, *dh;
char odh;
int nextarg = 0;
fgets (pbuf, sizeof (pbuf) - 1, fp);
cline++;
/*{{{ tidy up line*/
for (ch = pbuf; (*ch == ' ') || (*ch == '\t'); ch++);
if (*ch == '#') {
continue;
}
for (dh = ch; (*dh != '\0') && (*dh != '#'); dh++);
for (dh--; (dh >= ch) && (*dh <= ' '); dh--) {
*dh = '\0';
}
if ((*ch == '\0') || (ch == dh)) {
continue;
}
/*}}}*/
/* process line */
for (dh=ch; (*dh != ' ') && (*dh != '\t') && (*dh != '\0'); dh++);
odh = *dh;
*dh = '\0';
if (odh != '\0') {
for (dh++; (*dh == ' ') || (*dh == '\t'); dh++);
nextarg = 1;
}
/* ch has this arg, dh has the next if nextarg > 0 */
if (!strcasecmp (ch, "log")) {
/*{{{ LOG [SYSLOG|filename] */
if (nextarg) {
char *arg = getnextarg (&dh, &nextarg);
if (!strcasecmp (arg, "syslog")) {
upsinfo->use_syslog = 1;
} else {
upsinfo->use_syslog = 0;
upsinfo->logfilename = string_dup (arg);
}
} else {
/* turn logging off */
if (upsinfo->logfilename) {
sfree (upsinfo->logfilename);
upsinfo->logfilename = NULL;
}
upsinfo->use_syslog = 0;
}
if (nextarg) {
if (errstream) {
fprintf (errstream, "%s: error in config file %s:%d: ignoring trailing rubbish on line\n",
progname, upsinfo->conffile, cline);
}
errcount++;
}
continue;
/*}}}*/
} else if (!strcasecmp (ch, "pidfile")) {
/*{{{ PIDFILE filename*/
char *fname = nextarg ? getnextarg (&dh, &nextarg) : NULL;
if (nextarg) {
if (errstream) {
fprintf (errstream, "%s: error in config file %s:%d: ignoring trailing rubbish on line\n",
progname, upsinfo->conffile, cline);
}
errcount++;
}
if (!fname) {
if (errstream) {
fprintf (errstream, "%s: error in config file %s:%d: %s requires a filename argument\n",
progname, upsinfo->conffile, cline, ch);
}
errcount++;
} else {
if (upsinfo->pidfilename) {
sfree (upsinfo->pidfilename);
}
upsinfo->pidfilename = string_dup (fname);
}
/*}}}*/
} else if (!strcasecmp (ch, "device")) {
/*{{{ DEVICE <name> <sdev> <baud-data-parity-stop> <interpoll>*/
/* next arguments should be a device name, serial device, parameters and inter-poll time */
char *dname = nextarg ? getnextarg (&dh, &nextarg) : NULL;
char *sdev = nextarg ? getnextarg (&dh, &nextarg) : NULL;
char *sparams = nextarg ? getnextarg (&dh, &nextarg) : NULL;
char *polltime = nextarg ? getnextarg (&dh, &nextarg) : NULL;
if (nextarg) {
if (errstream) {
fprintf (errstream, "%s: error in config file %s:%d: ignoring trailing rubbish on line\n",
progname, upsinfo->conffile, cline);
}
errcount++;
}
if (!polltime) {
if (errstream) {
fprintf (errstream, "%s: error in config file %s:%d: %s requires more arguments\n",
progname, upsinfo->conffile, cline, ch);
}
errcount++;
} else if (access (sdev, R_OK | W_OK)) {
if (errstream) {
fprintf (errstream, "%s: error in config file %s:%d: cannot access serial device %s\n",
progname, upsinfo->conffile, cline, sdev);
}
errcount++;
} else {
openupsd_sdev_t *tsdev = makeserialdev (dname, sdev, sparams, polltime);
if (tsdev) {
if (!sdevlist) {
sdevlist = tsdev;
} else {
tsdev->next = sdevlist;
sdevlist->prev = tsdev;
sdevlist = tsdev;
}
} else {
if (errstream) {
fprintf (errstream, "%s: error in config file %s:%d: failed to parse %s parameters\n",
progname, upsinfo->conffile, cline, ch);
}
errcount++;
}
}
continue;
/*}}}*/
} else if (!strcasecmp (ch, "output")) {
/*{{{ OUTPUT <name> <filename>*/
/* next arguments will be a name (possibly * or a list) and a filename */
char *dnames = nextarg ? getnextarg (&dh, &nextarg) : NULL;
char *fname = nextarg ? getnextarg (&dh, &nextarg) : NULL;
if (nextarg) {
if (errstream) {
fprintf (errstream, "%s: error in config file %s:%d: ignoring trailing rubbish on line\n",
progname, upsinfo->conffile, cline);
}
errcount++;
}
if (!fname) {
if (errstream) {
fprintf (errstream, "%s: error in config file %s:%d: %s requires more arguments\n",
progname, upsinfo->conffile, cline, ch);
}
errcount++;
} else if (makelinkofile (&(upsinfo->outfiles), dnames, fname, sdevlist, netclis, errstream)) {
errcount++;
}
/*}}}*/
} else if (!strcasecmp (ch, "netclient")) {
/*{{{ NETCLIENT <mode> <name> <host>:<port> [RETRY time]*/
char *mode = nextarg ? getnextarg (&dh, &nextarg) : NULL;
if (!mode) {
if (errstream) {
fprintf (errstream, "%s: error in config file %s:%d: %s requires more arguments\n",
progname, upsinfo->conffile, cline, ch);
}
errcount++;
} else if (!strcasecmp (mode, "tcp")) {
char *name = nextarg ? getnextarg (&dh, &nextarg) : NULL;
char *hostport = nextarg ? getnextarg (&dh, &nextarg) : NULL;
time_t retry_time = (time_t)60;
if (nextarg) {
/* probably a RETRY spec */
char *targ = getnextarg (&dh, &nextarg);
char *value = nextarg ? getnextarg (&dh, &nextarg) : NULL;
if (value && !strcasecmp (targ, "retry")) {
int tme = 0;
if (sscanf (value, "%d", &tme) != 1) {
if (errstream) {
fprintf (errstream, "%s: error in config file %s:%d: mangled %s value %s\n",
progname, upsinfo->conffile, cline, targ, value);
}
errcount++;
}
retry_time = (time_t)tme;
} else {
if (errstream) {
fprintf (errstream, "%s: error in config file %s:%d: ignoring trailing rubbish on line\n",
progname, upsinfo->conffile, cline);
}
errcount++;
}
}
if (!hostport) {
if (errstream) {
fprintf (errstream, "%s: error in config file %s:%d: %s %s requires more arguments\n",
progname, upsinfo->conffile, cline, ch, mode);
}
errcount++;
} else {
openupsd_netcli_t *nsdev = maketcpnetclidev (name, hostport);
nsdev->retry = retry_time;
if (nsdev) {
if (!netclis) {
netclis = nsdev;
} else {
nsdev->next = netclis;
netclis->prev = nsdev;
netclis = nsdev;
}
} else {
if (errstream) {
fprintf (errstream, "%s: error in config file %s:%d: failed to resolve %s\n",
progname, upsinfo->conffile, cline, hostport);
}
errcount++;
}
}
} else {
if (errstream) {
fprintf (errstream, "%s: error in config file %s:%d: unknown %s mode %s\n",
progname, upsinfo->conffile, cline, ch, mode);
}
errcount++;
}
/*}}}*/
} else if (!strcasecmp (ch, "netserver")) {
/*{{{ NETSERVER <mode> <names> <host:port> [ALLOW network/netmask [ALLOW ...]] [DENY network/netmask [DENY ...]]*/
char *mode = nextarg ? getnextarg (&dh, &nextarg) : NULL;
if (!mode) {
if (errstream) {
fprintf (errstream, "%s: error in config file %s:%d: %s requires more arguments\n",
progname, upsinfo->conffile, cline, ch);
}
errcount++;
} else if (!strcasecmp (mode, "tcp")) {
char *dnames = nextarg ? getnextarg (&dh, &nextarg) : NULL;
char *hostport = nextarg ? getnextarg (&dh, &nextarg) : NULL;
openupsd_netsvr_t *tns;
if (!hostport) {
if (errstream) {
fprintf (errstream, "%s: error in config file %s:%d: %s requires more arguments\n",
progname, upsinfo->conffile, cline, ch);
}
errcount++;
} else {
tns = makelinktcpnetsvr (&(upsinfo->netsvr), dnames, hostport, sdevlist, netclis, errstream);
if (!tns) {
if (errstream) {
fprintf (errstream, "%s: error in config file %s:%d: %s %s error\n",
progname, upsinfo->conffile, cline, ch, mode);
}
errcount++;
} else {
int lerr = 0;
/* process ALLOW/DENY stuff */
while (nextarg) {
char *option = getnextarg (&dh, &nextarg);
char *value = nextarg ? getnextarg (&dh, &nextarg) : NULL;
if (!value) {
if (errstream) {
fprintf (errstream, "%s: error in config file %s:%d: expecting ALLOW/DENY pairs\n",
progname, upsinfo->conffile, cline);
}
errcount++;
} else if (!strcasecmp (option, "allow")) {
if (add_network_netmask (1, tns, value)) {
lerr++;
}
} else if (!strcasecmp (option, "deny")) {
if (add_network_netmask (0, tns, value)) {
lerr++;
}
} else {
if (errstream) {
fprintf (errstream, "%s: error in config file %s:%d: unknown option %s\n",
progname, upsinfo->conffile, cline, option);
}
errcount++;
}
}
if (lerr) {
if (errstream) {
fprintf (errstream, "%s: error in config file %s:%d: error processing network/netmask\n",
progname, upsinfo->conffile, cline);
}
errcount++;
}
}
}
}
/*}}}*/
} else if (!strcasecmp (ch, "trigger")) {
/*{{{ TRIGGER <name> "<field>" <comparison> <rhs>*/
char *tname = nextarg ? getnextarg (&dh, &nextarg) : NULL;
char *tfield = nextarg ? getnextarg (&dh, &nextarg) : NULL;
char *tcmp = nextarg ? getnextarg (&dh, &nextarg) : NULL;
char *trhs = nextarg ? getnextarg (&dh, &nextarg) : NULL;
openupsd_trigger_t *tmp = NULL;
if (!trhs) {
if (errstream) {
fprintf (errstream, "%s: error in config file %s:%d: %s requires more arguments\n",
progname, upsinfo->conffile, cline, ch);
}
errcount++;
} else {
int ifield = -1;
int icmp = -1;
if (*tfield == '\"') {
char *eh = (tfield + 1);
if ((eh = strchr (eh, '\"')) != NULL) {
*eh = '\0';
ifield = decode_alarm_trigger (tfield + 1);
}
} else {
ifield = decode_alarm_trigger (tfield);
}
icmp = decode_alarm_cmp (tcmp);
if ((ifield >= 0) && (icmp >= 0)) {
/* attempt to create a new trigger (handles the fudging of RHS) */
tmp = makeaddtrigger (&trigs, tname, ifield, icmp, trhs);
}
if (!tmp) {
if (errstream) {
fprintf (errstream, "%s: error in config file %s:%d: bad %s directive\n",
progname, upsinfo->conffile, cline, ch);
}
errcount++;
} else if (nextarg) {
/* spurious rubbish */
if (errstream) {
fprintf (errstream, "%s: error in config file %s:%d: ignoring rubbish at end of %s directive\n",
progname, upsinfo->conffile, cline, ch);
}
errcount++;
} /* else already in the linked list */
}
/*}}}*/
} else if (!strcasecmp (ch, "invalidate")) {
/*{{{ INVALIDATE <this> WHEN <that>*/
char *thisname = nextarg ? getnextarg (&dh, &nextarg) : NULL;
char *when = nextarg ? getnextarg (&dh, &nextarg) : NULL;
char *thatname = nextarg ? getnextarg (&dh, &nextarg) : NULL;
if (!thatname) {
if (errstream) {
fprintf (errstream, "%s: error in config file %s:%d: %s requires more arguments\n",
progname, upsinfo->conffile, cline, ch);
}
errcount++;
} else {
openupsd_trigger_t *thistrig, *thattrig;
for (thistrig = trigs; thistrig; thistrig = thistrig->next) {
if (!strcmp (thistrig->name, thisname)) {
break;
}
}
for (thattrig = trigs; thattrig; thattrig = thattrig->next) {
if (!strcmp (thattrig->name, thatname)) {
break;
}
}
if (strcasecmp (when, "WHEN")) {
if (errstream) {
fprintf (errstream, "%s: error in config file %s:%d: mangled trigger definition\n",
progname, upsinfo->conffile, cline);
}
errcount++;
} else if (!thistrig || !thattrig) {
if (errstream) {
fprintf (errstream, "%s: error in config file %s:%d: trigger %s not found\n",
progname, upsinfo->conffile, cline, thattrig ? thisname : thatname);
}
errcount++;
} else {
/* add "thattrig" to invalidators for "thistrig", and pop in linked list (invds) */
openupsd_inv_t *tmp;
tmp = (openupsd_inv_t *)smalloc (sizeof (openupsd_inv_t));
tmp->next = tmp->prev = NULL;
tmp->when = thattrig;
tmp->trash = thistrig;
dynarray_maybeadd (thattrig->inv, tmp);
if (invds) {
invds->prev = tmp;
tmp->next = invds;
}
invds = tmp;
}
}
/*}}}*/
} else if (!strcasecmp (ch, "alarm")) {
/*{{{ ALARM <names> <trigger> [INITIAL] [AFTER delay] <action> */
char *dnames = nextarg ? getnextarg (&dh, &nextarg) : NULL;
char *trigger = nextarg ? getnextarg (&dh, &nextarg) : NULL;
if (!trigger) {
if (errstream) {
fprintf (errstream, "%s: error in config file %s:%d: %s requires more arguments\n",
progname, upsinfo->conffile, cline, ch);
}
errcount++;
} else {
char *aa = nextarg ? getnextarg (&dh, &nextarg) : NULL;
char *delay = NULL;
openupsd_alarm_t *tmp = NULL;
int ialrm = 0;
openupsd_trigger_t *trig = NULL;
/* locate trigger */
trig = openupsd_find_trigger (trigger, trigs);
/* aa is either INITIAL, AFTER or <action>. (in that order) */
if (aa && !strcasecmp (aa, "INITIAL")) {
aa = nextarg ? getnextarg (&dh, &nextarg) : NULL;
ialrm = 1;
}
if (aa && nextarg && !strcasecmp (aa, "AFTER")) {
delay = getnextarg (&dh, &nextarg);
aa = nextarg ? getnextarg (&dh, &nextarg) : NULL;
}
/* aa is action, or NULL */
if (!aa) {
if (errstream) {
fprintf (errstream, "%s: error in config file %s:%d: error processing %s directive\n",
progname, upsinfo->conffile, cline, ch);
}
errcount++;
} else if (!strcasecmp (aa, "LOG")) {
tmp = makelogalarm (trig, delay);
if (!tmp) {
if (errstream) {
fprintf (errstream, "%s: error in config file %s:%d: %s %s error\n",
progname, upsinfo->conffile, cline, ch, aa);
}
errcount++;
}
} else if (!strcasecmp (aa, "EXEC")) {
tmp = makeexecalarm (trig, delay);
if (!nextarg) {
if (errstream) {
fprintf (errstream, "%s: error in config file %s:%d: %s %s requires more arguments\n",
progname, upsinfo->conffile, cline, ch, aa);
}
errcount++;
} else if (!tmp) {
if (errstream) {
fprintf (errstream, "%s: error in config file %s:%d: %s %s error\n",
progname, upsinfo->conffile, cline, ch, aa);
}
errcount++;
} else {
char *arg = getnextarg (&dh, &nextarg);
char *copy;
openupsd_exec_t *texec = (openupsd_exec_t *)(tmp->xdata);
texec->prev = texec->next = NULL;
texec->alarm = tmp;
texec->path_to_run = string_dup (arg);
copy = string_dup (arg);
dynarray_add (texec->args, copy);
while (nextarg) {
arg = getnextarg (&dh, &nextarg);
copy = string_dup (arg);
dynarray_add (texec->args, copy);
}
}
} else {
if (errstream) {
fprintf (errstream, "%s: error in config file %s:%d: unknown %s %s\n",
progname, upsinfo->conffile, cline, ch, aa);
}
errcount++;
}
if (tmp) {
/* add it to the list of alarms and link into named devices */
tmp->initial = ialrm;
if (!alarms) {
alarms = tmp;
} else {
alarms->prev = tmp;
tmp->next = alarms;
alarms = tmp;
}
linkalarm (tmp, dnames, sdevlist, netclis, errstream);
}
}
/*}}}*/
} else {
/*{{{ anything else*/
if (errstream) {
fprintf (errstream, "%s: error in config file %s:%d: unknown directive: %s\n",
progname, upsinfo->conffile, cline, ch);
}
errcount++;
/*}}}*/
}
}
fclose (fp);
upsinfo->sdev = sdevlist;
upsinfo->salarms = alarms;
upsinfo->netcli = netclis;
upsinfo->trigs = trigs;
upsinfo->invds = invds;
return errcount;
}
/*}}}*/
syntax highlighted by Code2HTML, v. 0.9.1