/*
* UPS daemon for MGE Pulsar
* At least on my ES5+ works fine
* Copyright (c) Stanislav Voronyi <stas@esc.kharkov.com>
* Version 0.3 28.12.1998
*/
#include <stdio.h>
#include <sys/types.h>
#include <termios.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <fcntl.h>
#include <signal.h>
#include <syslog.h>
#include <sysexits.h>
#include <stdlib.h>
/* some defaults */
#define DEFAULT_INTERVAL 10
#define DEFAULT_L_INTERVAL 30
int RTS = TIOCM_RTS, use_syslog = 0;
/* only bits I need for */
#define SS_BATTERY_LOW 4
#define SS_POWER_FAIL 5
#define SS_OVERLOAD 6
#define BS_POWER_FAIL 0
#define BS_POWER_OK 1
#define BS_BAT 2
#define BS_PWS 11
#define BS_TDP 12
/* status file for init */
#define PWRFILE "/var/run/powerstatus"
/* Linux usual */
#define LOCKDIR "/var/run"
#define RUNDIR "/var/run"
char lockfile[40] = {0,};
char pidfile[40] = {0,};
char progname[40] = {0,};
void
sigexit (int ignore)
{
if (*lockfile)
unlink (lockfile);
if (*pidfile)
unlink (pidfile);
if (use_syslog)
syslog (LOG_NOTICE, "UPS daemon terminated");
exit (EX_OK);
}
int
main (int argc, char **argv)
{
int tty, fd;
FILE *trace = NULL;
char iline[40], Status[20], *av[3], *tfile = NULL, *p;
char portname[40], *pwrfile = PWRFILE, *run_path = NULL;
struct termios ts;
struct timeval timeout;
fd_set fds;
int interval = DEFAULT_INTERVAL, lowinterval = DEFAULT_L_INTERVAL;
int c, i, mode = 0, nr_count = 0, start = 1, prev = '0', ckbat = 0;
int tdp = 0, pws = 0; /* don't touch this flags by default */
int before_pwroff = 0, pwr_on_wait = 0;
time_t last_low_rep = 0, overload_time = 0;
pid_t init_pid = 1;
if ((p = strrchr (argv[0], '/')) != NULL)
strcpy (progname, p + 1);
else
strcpy (progname, argv[0]);
/* now parsing argumets
We may have
mgeupsd [-i check_interval] [-q] port
port may be as /dev/ttyxxx or ttyxx
or you can do ln -s /dev/ttyXxx /dev/mgeups
and use /dev/mgeups */
for (i = 1; i < argc; i++)
{
if (strcmp (argv[i], "-pwroff") == 0)
{
mode = 1;
continue;
}
else if (strcmp (argv[i], "-q") == 0)
{ /* for comatibility with standart powerd */
mode = 1;
continue;
}
else if (strcmp (argv[i], "-l") == 0) /* use syslog for reporting */
{
use_syslog = 1;
continue;
}
else if (strcmp (argv[i], "-i") == 0)
{
if ((interval = atoi (argv[++i])) == 0)
interval = DEFAULT_INTERVAL; /* my default - check ups every 10 seconds */
continue;
}
else if (strcmp (argv[i], "-li") == 0)
{
if ((lowinterval = atoi (argv[++i])) == 0)
lowinterval = DEFAULT_L_INTERVAL; /* my default - report twice a minute */
continue;
}
else if (strcmp (argv[i], "-t") == 0) /* trace commands, huge output - only for debbuging */
{
tfile = strdup (argv[++i]);
continue;
}
else if (strcmp (argv[i], "-pon") == 0) /* total discharge protection on */
{
tdp_on:
tdp = '0';
continue;
}
else if (strcmp (argv[i], "-poff") == 0) /* total discharge protection off */
{
tdp_off:
tdp = '1';
continue;
}
else if (strcmp (argv[i], "-p") == 0)
{
if (strcmp (argv[++i], "on") == 0)
goto tdp_on;
else if (strcmp (argv[i], "off") == 0)
goto tdp_off;
else
{
fprintf (stderr, "Invalid option -p %s\n", argv[i]);
break;
}
}
else if (strcmp (argv[i], "-son") == 0) /* power saving mode on */
{
pws_on:
pws = '1';
continue;
}
else if (strcmp (argv[i], "-soff") == 0) /* power saving mode off */
{
pws_off:
pws = '0';
continue;
}
else if (strcmp (argv[i], "-s") == 0)
{
if (strcmp (argv[++i], "on") == 0)
goto pws_on;
else if (strcmp (argv[i], "off") == 0)
goto pws_off;
else
{
fprintf (stderr, "Invalid option -s %s\n", argv[i]);
break;
}
}
/* -pwrfile, -process & -run three options that allow monitoring not only machine
but any other equipment as well */
else if (strcmp (argv[i], "-pwrfile") == 0) /* change name of power status file */
{
pwrfile = argv[++i];
/* make some verification */
if ((fd = open (pwrfile, O_CREAT | O_WRONLY, 0644)) >= 0)
{
close (fd);
unlink (pwrfile);
}
else
{
fprintf (stderr, "Can't create file %s\n", argv[i]);
break;
}
continue;
}
else if (strcmp (argv[i], "-process") == 0) /* change pid of process for send SIGPWR to */
{
if ((init_pid = atoi (argv[++i])) == 0)
{
fprintf (stderr, "Invalid option -process %s\n", argv[i]);
break;
}
if (kill (init_pid, 0))
{ /* process not running */
fprintf (stderr, "Process %u does not running\n", init_pid);
break;
}
continue;
}
else if (strcmp (argv[i], "-run") == 0) /* run specified command & give powerfile as argv[1] */
{
/* must be full pathname */
run_path = argv[++i];
if (*run_path != '/')
{
fprintf (stderr, "You must specify full pathname for -run\n");
break;
}
if (access (run_path, X_OK))
{
fprintf (stderr, "Haven't permission to run %s\n", run_path);
break;
}
continue;
}
else if (strcmp (argv[i], "-swpwr") == 0) /* may have two parameters time_off,time_before_off */
{
mode = 2;
pwr_on_wait = (int) strtol (argv[++i], &p, 10);
if (*p == ',')
before_pwroff = atoi (++p);
continue;
}
else
{ /* this must be portname */
if (argv[i][0] == '-')
{ /* portname can't start from '-' - this is wrong option */
fprintf (stderr, "Invalid option %s\n", argv[i]);
break;
}
if (argv[i][0] == '/')
strcpy (portname, argv[i]);
else
sprintf (portname, "/dev/%s", argv[i]);
/* check for port locking & lock port */
p = strrchr (portname, '/') + 1;
sprintf (lockfile, "%s/LCK..%s", LOCKDIR, p);
if ((trace = fopen (lockfile, "r")) == NULL)
{
newlock:
if (!mode) /* don't make lock in poweroff mode - filesystems possible ro already */
{
#ifndef TEST
if (fork ())
exit (EX_OK);
#endif
if ((trace = fopen (lockfile, "w")) == NULL)
{
fprintf (stderr, "Unable to create lock file, exiting!\n");
exit (EX_CANTCREAT);
}
fprintf (trace, "%u", getpid ());
fclose (trace);
trace = NULL;
}
}
else
{
fscanf (trace, "%d", &c);
fclose (trace);
trace = NULL;
if (kill ((pid_t) c, 0) == 0)
{
fprintf (stderr, "Device %s already locked by process %u\n", portname, c);
exit (EX_NOPERM);
}
else
goto newlock;
}
tty = open (portname, O_RDWR);
if (tty == -1 || isatty (tty) != 1)
{
fprintf (stderr, "mgeupsd: %s not a tty\n", portname);
exit (EX_USAGE);
}
else
{
close (tty); /* I open it for real later */
goto operate;
}
}
}
/* if we here something wrong */
fprintf (stderr, "Usage: mgeupsd [-i check_interval] [-l] [-q] [-p on|off] [-s on|off] port\n");
exit (EX_USAGE);
operate:
/* we don't need stdxxx and control tty */
fclose (stderr);
fclose (stdout);
fclose (stdin);
setsid ();
signal (SIGTERM, sigexit);
signal (SIGCHLD, SIG_IGN);
/* setup port */
tty = open (portname, O_RDWR | O_NOCTTY);
ioctl (tty, TIOCMBIC, &RTS);
tcgetattr (tty, &ts);
cfmakeraw (&ts);
cfsetospeed (&ts, B2400);
cfsetispeed (&ts, B2400);
ts.c_cflag &= ~(CRTSCTS);
ts.c_cc[VMIN] = 0;
ts.c_cc[VTIME] = 5;
tcsetattr (tty, TCSANOW, &ts);
/* end setup */
if (mode)
{ /* turn power off */
rz:
write (tty, "Z\r\n", 3);
c = read (tty, iline, sizeof iline);
if (c != 0)
goto rz;
rm:
c = sprintf (iline, "Sm %u\r\n", pwr_on_wait);
write (tty, iline, c);
for (c = 0, i = 0; (i = read (tty, iline + c, (sizeof iline) - c)); c += i);
if (c != 4 || iline[0] != 'O' || iline[1] != 'K')
goto rm;
rn:
c = sprintf (iline, "Sn %u\r\n", before_pwroff);
write (tty, iline, c);
for (c = 0, i = 0; (i = read (tty, iline + c, (sizeof iline) - c)); c += i);
if (c != 4 || iline[0] != 'O' || iline[1] != 'K')
goto rn;
rx:
write (tty, "Sx0\r\n", 5);
for (c = 0, i = 0; (i = read (tty, iline + c, (sizeof iline) - c)); c += i);
if (c != 4 || iline[0] != 'O' || iline[1] != 'K')
goto rx;
/* power must be turned off now, just wait for it */
if (mode == 1)
pause ();
else
exit (EX_OK); /* or I must waitng to turn power back to on? write me if anyone need this */
}
else
{ /* normal operation */
/* create pid-file first */
sprintf (pidfile, "%s/%s.pid", RUNDIR, progname);
if ((trace = fopen (pidfile, "w")) != NULL)
{
fprintf (trace, "%u\n", getpid ());
fclose (trace);
trace = NULL;
}
if (use_syslog)
{
openlog (progname, LOG_PID, LOG_DAEMON);
syslog (LOG_INFO, "UPS daemon started");
}
if (tfile) /* trace mode */
trace = fopen (tfile, "a"); /* I don't check for error
if trace == NULL just no trace */
if (trace)
setvbuf (trace, NULL, _IONBF, 0);
/* init UPS */
nr_count = -1;
ri:
if (nr_count++ > 60)
{
nr_count = 0;
if (use_syslog)
syslog (LOG_NOTICE, "Unable to %s communication with UPS", start ? "start" : "restore");
}
write (tty, "Z\r\n", 3);
if (trace)
fprintf (trace, "Command: Z\n");
c = read (tty, iline, sizeof iline);
if (c != 0)
{
if (trace)
{
iline[c] = 0;
fprintf (trace, "Answer: %s\n", iline); /* junk ? */
}
goto ri;
}
write (tty, "Si 1\r\n", 6);
if (trace)
fprintf (trace, "Command: Si 1\n");
c = read (tty, iline, sizeof iline);
if (c == 0)
goto ri;
while (c < 10)
{
i = read (tty, iline + c, sizeof iline);
if (i)
c += i;
else
goto ri;
}
if (strncmp (iline, "Pulsar", 6) != 0)
goto ri;
if (trace || use_syslog)
{
iline[c] = 0;
if ((p = strchr (iline, '\n')) != NULL)
*p = 0;
if ((p = strchr (iline, '\r')) != NULL)
*p = 0; /* remove CR LF for report */
}
if (trace)
fprintf (trace, "Device report: %s\n", iline);
if (use_syslog)
{
if (!start)
syslog (LOG_NOTICE, "Communication with UPS restored");
syslog (LOG_INFO, "Device: %s", iline);
}
while (c != 0)
{
c = read (tty, iline, sizeof iline);
if (trace && c)
fprintf (trace, "more: %s", iline);
}
/* check battery status first */
nr_count = 0;
rbs:
write (tty, "Bs\r\n", 4);
if (trace)
fprintf (trace, "Command: Bs\n");
/* answer must be 19 bytes long */
for (c = 0, i = 0; (i = read (tty, Status + c, (sizeof Status) - c)); c += i);
if (c != 19)
{
if (trace)
{
Status[c] = 0;
fprintf (trace, "Answer: %s\n", Status); /* somethig wrong */
}
if (nr_count++ < 3)
goto rbs;
else
goto ri;
}
else
Status[17] = 0;
if (trace)
fprintf (trace, "Answer: %s\n", Status);
if (Status[BS_POWER_FAIL] == '1')
{ /* very strange - we start from battery */
if (use_syslog)
syslog (LOG_WARNING, "UPS started without main supply");
}
if (use_syslog)
if (Status[BS_POWER_OK] == '1')
{
if (Status[BS_BAT] == '1')
{ /* battery not fully charged */
ckbat = 1;
syslog (LOG_INFO, "UPS battery not fully charged");
}
else
syslog (LOG_INFO, "UPS battery completely charged");
}
if (tdp)
{
if (Status[BS_TDP] != tdp)
nr_count = 0;
if (tdp == '1')
{
rbx3:
write (tty, "Bx3\r\n", 5);
if (trace)
fprintf (trace, "Command: Bx3\n");
for (c = 0, i = 0; (i = read (tty, iline + c, (sizeof iline) - c)); c += i);
if (trace)
{
iline[c] = 0;
fprintf (trace, "Answer: %s\n", iline);
}
if (c != 4 || iline[0] != 'O' || iline[1] != 'K')
if (nr_count++ < 3)
goto rbx3;
else if (use_syslog)
syslog (LOG_WARNING, "Can't turn off total discharge protection");
}
else
{
rbx4:
write (tty, "Bx4\r\n", 5);
if (trace)
fprintf (trace, "Command: Bx4\n");
for (c = 0, i = 0; (i = read (tty, iline + c, (sizeof iline) - c)); c += i);
if (trace)
{
iline[c] = 0;
fprintf (trace, "Answer: %s\n", iline);
}
if (c != 4 || iline[0] != 'O' || iline[1] != 'K')
if (nr_count++ < 3)
goto rbx4;
else if (use_syslog)
syslog (LOG_WARNING, "Can't turn on total discharge protection");
}
}
if (pws)
{
if (Status[BS_PWS] != pws)
nr_count = 0;
if (pws == '1')
{
rsx11:
write (tty, "Sx11\r\n", 6);
if (trace)
fprintf (trace, "Command: Sx11\n");
for (c = 0, i = 0; (i = read (tty, iline + c, (sizeof iline) - c)); c += i);
if (trace)
{
iline[c] = 0;
fprintf (trace, "Answer: %s\n", iline);
}
if (c != 4 || iline[0] != 'O' || iline[1] != 'K')
if (nr_count++ < 3)
goto rsx11;
else if (use_syslog)
syslog (LOG_WARNING, "Can't turn on power save mode");
}
else
{
rsx10:
write (tty, "Sx10\r\n", 6);
if (trace)
fprintf (trace, "Command: Sx10\n");
for (c = 0, i = 0; (i = read (tty, iline + c, (sizeof iline) - c)); c += i);
if (trace)
{
iline[c] = 0;
fprintf (trace, "Answer: %s\n", iline);
}
if (c != 4 || iline[0] != 'O' || iline[1] != 'K')
if (nr_count++ < 3)
goto rsx10;
else if (use_syslog)
syslog (LOG_WARNING, "Can't turn off power save mode");
}
}
FD_ZERO (&fds);
FD_SET (tty, &fds);
start = 0;
while (1)
{
timeout.tv_sec = interval;
timeout.tv_usec = 0;
rsel:
FD_SET (tty, &fds);
if (select (tty + 1, &fds, 0, 0, &timeout) != 0)
{
if (FD_ISSET (tty, &fds))
{
for (c = 0, i = 0; (i = read (tty, iline + c, (sizeof iline) - c)); c += i);
if (trace)
{
iline[c] = 0;
fprintf (trace, "Unexpected answer: %s", iline); /* answer usualy have \n at end */
}
}
goto rsel;
}
/* now read status */
nr_count = 0;
rs:
write (tty, "Ss\r\n", 4);
if (trace)
fprintf (trace, "Command: Ss\n");
/* read status string */
for (c = 0, i = 0; (i = read (tty, Status + c, (sizeof Status) - c)); c += i);
if (c == 0)
{
if (nr_count++ < 3)
goto rs;
else
{
if (use_syslog)
syslog (LOG_NOTICE, "Communication with UPS lost!");
goto ri;
}
}
if (c != 10)
{
if (trace)
{
Status[c] = 0;
fprintf (trace, "Short answer: %s\n", Status);
}
goto rs;
}
if (trace)
{
Status[8] = 0;
fprintf (trace, "State: %s\n", Status);
}
/* check status */
if (Status[SS_OVERLOAD] == '1')
{ /* UPS overloaded */
if (use_syslog)
syslog (LOG_WARNING, "UPS overloaded!");
/* UPS can operate no more 5 minutes, so do immediate reboot after 4 minutes */
if (!overload_time)
(void) time (&overload_time);
else if (overload_time + 4 * 60 < time ((time_t *) NULL)) /* immediate shutdown */
/* just set SS_BATTERY_LOW */
Status[SS_BATTERY_LOW] = '1';
}
else if (overload_time)
{
if (use_syslog)
syslog (LOG_INFO, "UPS overload obviated");
overload_time = 0;
}
if (Status[SS_BATTERY_LOW] == '1')
{ /* very bad, we must immidiate halt */
if (last_low_rep + lowinterval > time ((time_t *) NULL))
continue;
if (use_syslog)
syslog (LOG_CRIT, "UPS battery low! Shutdown immediately!");
unlink (pwrfile);
if ((fd = open (pwrfile, O_CREAT | O_WRONLY, 0644)) >= 0)
{
write (fd, "LOWBATT\n", 8);
close (fd);
if (run_path)
{
if (fork () == 0)
{
p = strrchr (run_path, '/') + 1;
av[0] = p;
av[1] = pwrfile;
av[2] = NULL;
close (tty);
execv (run_path, av);
exit (1);
}
}
else
{
#ifndef TEST
if (kill (init_pid, SIGUSR2))
syslog (LOG_CRIT, "Process %u doesn not exist! Can't send SIGPWR", init_pid);
#endif
}
time (&last_low_rep);
continue;
}
}
if (Status[SS_POWER_FAIL] == '1')
{ /* mains suply absent */
if (prev == Status[SS_POWER_FAIL]) /* nothing changed */
continue;
else
{ /* report to init */
if (use_syslog)
syslog (LOG_WARNING, "Power fail!");
unlink (pwrfile);
if ((fd = open (pwrfile, O_CREAT | O_WRONLY, 0644)) >= 0)
{
write (fd, "FAIL\n", 5);
close (fd);
if (run_path)
{
if (fork () == 0)
{
p = strrchr (run_path, '/') + 1;
av[0] = p;
av[1] = pwrfile;
av[2] = NULL;
close (tty);
execv (run_path, av);
exit (1);
}
}
else
{
#ifndef TEST
if (kill (init_pid, SIGINT))
syslog (LOG_CRIT, "Process %u doesn not exist! Can't send SIGPWR", init_pid);
#endif
}
prev = Status[SS_POWER_FAIL];
continue;
}
}
}
if (prev == '1')
{ /* mains suply return, report to init */
if (use_syslog)
syslog (LOG_NOTICE, "Power restored!");
unlink (pwrfile);
if ((fd = open (pwrfile, O_CREAT | O_WRONLY, 0644)) >= 0)
{
write (fd, "OK\n", 3);
close (fd);
if (run_path)
{
if (fork () == 0)
{
p = strrchr (run_path, '/') + 1;
av[0] = p;
av[1] = pwrfile;
av[2] = NULL;
close (tty);
execv (run_path, av);
exit (1);
}
}
else
{
#ifndef TEST
if (kill (init_pid, SIGHUP))
syslog (LOG_CRIT, "Process %u doesn not exist! Can't send SIGPWR", init_pid);
#endif
}
prev = Status[SS_POWER_FAIL];
}
if (use_syslog)
ckbat = 1;
}
if (ckbat && use_syslog)
{
rckbat:
write (tty, "Bs\r\n", 4);
if (trace)
fprintf (trace, "Command: Bs\n");
/* answer must be 19 bytes long */
for (c = 0, i = 0; (i = read (tty, Status + c, (sizeof Status) - c)); c += i);
if (c != 19)
{
if (trace)
{
Status[c] = 0;
fprintf (trace, "Answer: %s\n", Status); /* somethig wrong */
}
if (nr_count++ < 3)
goto rckbat;
else
{
if (use_syslog)
syslog (LOG_NOTICE, "Communication with UPS lost!");
goto ri;
}
}
else
Status[17] = 0;
if (trace)
fprintf (trace, "Answer: %s\n", Status);
if (Status[BS_POWER_FAIL] == '1')
ckbat = 0;
if (Status[BS_POWER_OK] == '1' && Status[BS_BAT] == '0')
{
ckbat = 0; /* don't check in future */
syslog (LOG_INFO, "UPS battery completely recharged");
}
}
} /* while(1) */
}
return 0;
}
syntax highlighted by Code2HTML, v. 0.9.1