/* * openupsd.c -- Belkin UPS status getter (serial port version) * Copyright (C) 2002-2003 Fred Barnes * * 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 #include #include #include #ifdef HAVE_SYS_SELECT_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "support.h" #include "openupsd.h" #ifdef SUPPORT_SYSLOG #include #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 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 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, ""); } 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] ++ */ 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; jinv); 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; ialarm, 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; idisallow); 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; iallow); 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; (ienv->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; iclients); 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, ""); match = fld; } if (fld != match) { fprintf (stderr, " "); } } } break; } fprintf (stderr, " ("); for (i=0; iinv); 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; ialarms); 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; ialarms); 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, "\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; ioutfiles); 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; ioutfiles); 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; iouttcpsvrs); 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; iouttcpsvrs); 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; idisallow); 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; iallow); 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; } /*}}}*/