nut/drivers/main.c
2022-06-29 12:37:36 +02:00

1087 lines
28 KiB
C

/* main.c - Network UPS Tools driver core
Copyright (C)
1999 Russell Kroll <rkroll@exploits.org>
2005 - 2017 Arnaud Quette <arnaud.quette@free.fr>
2017 Eaton (author: Emilien Kia <EmilienKia@Eaton.com>)
2017 - 2022 Jim Klimov <jimklimov+nut@gmail.com>
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "common.h"
#include "main.h"
#include "nut_stdint.h"
#include "dstate.h"
#include "attribute.h"
#include <grp.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
/* data which may be useful to the drivers */
int upsfd = -1;
char *device_path = NULL;
const char *progname = NULL, *upsname = NULL, *device_name = NULL;
/* may be set by the driver to wake up while in dstate_poll_fds */
int extrafd = -1;
/* for ser_open */
int do_lock_port = 1;
/* for dstate->sock_connect, default to effectively
* asynchronous (0) with fallback to synchronous (1) */
int do_synchronous = -1;
/* for detecting -a values that don't match anything */
static int upsname_found = 0;
static vartab_t *vartab_h = NULL;
/* variables possibly set by the global part of ups.conf
* user and group may be set globally or per-driver
*/
time_t poll_interval = 2;
static char *chroot_path = NULL, *user = NULL, *group = NULL;
static int user_from_cmdline = 0, group_from_cmdline = 0;
/* signal handling */
int exit_flag = 0;
/* should this driver instance go to background (default)
* or stay foregrounded (default if -D/-d options are set on
* command line)?
* Value is tri-state:
* -1 (default) Background the driver process
* 0 User required to not background explicitly,
* or passed -D (or -d) and current value was -1
* 1 User required to background even if with -D or dump_mode
*/
static int background_flag = -1;
/* Users can pass a -D[...] option to enable debugging.
* For the service tracing purposes, also the ups.conf
* can define a debug_min value in the global or device
* section, to set the minimal debug level (CLI provided
* value less than that would not have effect, can only
* have more).
*/
static int nut_debug_level_global = -1;
static int nut_debug_level_driver = -1;
/* everything else */
static char *pidfn = NULL;
static int dump_data = 0; /* Store the update_count requested */
/* print the driver banner */
void upsdrv_banner (void)
{
int i;
printf("Network UPS Tools - %s %s (%s)\n", upsdrv_info.name, upsdrv_info.version, UPS_VERSION);
/* process sub driver(s) information */
for (i = 0; upsdrv_info.subdrv_info[i]; i++) {
if (!upsdrv_info.subdrv_info[i]->name) {
continue;
}
if (!upsdrv_info.subdrv_info[i]->version) {
continue;
}
printf("%s %s\n", upsdrv_info.subdrv_info[i]->name,
upsdrv_info.subdrv_info[i]->version);
}
}
/* power down the attached load immediately */
static void forceshutdown(void)
__attribute__((noreturn));
static void forceshutdown(void)
{
upslogx(LOG_NOTICE, "Initiating UPS shutdown");
/* the driver must not block in this function */
upsdrv_shutdown();
exit(EXIT_SUCCESS);
}
/* this function only prints the usage message; it does not call exit() */
static void help_msg(void)
{
vartab_t *tmp;
printf("\nusage: %s (-a <id>|-s <id>) [OPTIONS]\n", progname);
printf(" -a <id> - autoconfig using ups.conf section <id>\n");
printf(" - note: -x after -a overrides ups.conf settings\n\n");
printf(" -s <id> - configure directly from cmd line arguments\n");
printf(" - note: must specify all driver parameters with successive -x\n");
printf(" - note: at least 'port' variable should be set\n");
printf(" - note: to explore the current values on a device from an\n");
printf(" unprivileged user account (with sufficient media access in\n");
printf(" the OS - e.g. to query networked devices), you can specify\n");
printf(" '-d 1' argument and `export NUT_STATEPATH=/tmp` beforehand\n\n");
printf(" -V - print version, then exit\n");
printf(" -L - print parseable list of driver variables\n");
printf(" -D - raise debugging level (and stay foreground by default)\n");
printf(" -d <count> - dump data to stdout after 'count' updates loop and exit\n");
printf(" -F - stay foregrounded even if no debugging is enabled\n");
printf(" -B - stay backgrounded even if debugging is bumped\n");
printf(" -q - raise log level threshold\n");
printf(" -h - display this help\n");
printf(" -k - force shutdown\n");
printf(" -i <int> - poll interval\n");
printf(" -r <dir> - chroot to <dir>\n");
printf(" -u <user> - switch to <user> (if started as root)\n");
printf(" -g <group> - set pipe access to <group> (if started as root)\n");
printf(" -x <var>=<val> - set driver variable <var> to <val>\n");
printf(" - example: -x cable=940-0095B\n\n");
if (vartab_h) {
tmp = vartab_h;
printf("Acceptable values for -x or ups.conf in this driver:\n\n");
while (tmp) {
if (tmp->vartype == VAR_VALUE)
printf("%40s : -x %s=<value>\n",
tmp->desc, tmp->var);
else
printf("%40s : -x %s\n", tmp->desc, tmp->var);
tmp = tmp->next;
}
}
upsdrv_help();
}
/* store these in dstate as driver.(parameter|flag) */
static void dparam_setinfo(const char *var, const char *val)
{
char vtmp[SMALLBUF];
/* store these in dstate for debugging and other help */
if (val) {
snprintf(vtmp, sizeof(vtmp), "driver.parameter.%s", var);
dstate_setinfo(vtmp, "%s", val);
return;
}
/* no value = flag */
snprintf(vtmp, sizeof(vtmp), "driver.flag.%s", var);
dstate_setinfo(vtmp, "enabled");
}
/* cram var [= <val>] data into storage */
static void storeval(const char *var, char *val)
{
vartab_t *tmp, *last;
if (!strncasecmp(var, "override.", 9)) {
dstate_setinfo(var+9, "%s", val);
dstate_setflags(var+9, ST_FLAG_IMMUTABLE);
return;
}
if (!strncasecmp(var, "default.", 8)) {
dstate_setinfo(var+8, "%s", val);
return;
}
tmp = last = vartab_h;
while (tmp) {
last = tmp;
/* sanity check */
if (!tmp->var) {
tmp = tmp->next;
continue;
}
/* later definitions overwrite earlier ones */
if (!strcasecmp(tmp->var, var)) {
free(tmp->val);
if (val)
tmp->val = xstrdup(val);
/* don't keep things like SNMP community strings */
if ((tmp->vartype & VAR_SENSITIVE) == 0)
dparam_setinfo(var, val);
tmp->found = 1;
return;
}
tmp = tmp->next;
}
/* try to help them out */
printf("\nFatal error: '%s' is not a valid %s for this driver.\n", var,
val ? "variable name" : "flag");
printf("\n");
printf("Look in the man page or call this driver with -h for a list of\n");
printf("valid variable names and flags.\n");
exit(EXIT_SUCCESS);
}
/* retrieve the value of variable <var> if possible */
char *getval(const char *var)
{
vartab_t *tmp = vartab_h;
while (tmp) {
if (!strcasecmp(tmp->var, var))
return(tmp->val);
tmp = tmp->next;
}
return NULL;
}
/* see if <var> has been defined, even if no value has been given to it */
int testvar(const char *var)
{
vartab_t *tmp = vartab_h;
while (tmp) {
if (!strcasecmp(tmp->var, var))
return tmp->found;
tmp = tmp->next;
}
return 0; /* not found */
}
/* callback from driver - create the table for -x/conf entries */
void addvar(int vartype, const char *name, const char *desc)
{
vartab_t *tmp, *last;
tmp = last = vartab_h;
while (tmp) {
last = tmp;
tmp = tmp->next;
}
tmp = xmalloc(sizeof(vartab_t));
tmp->vartype = vartype;
tmp->var = xstrdup(name);
tmp->val = NULL;
tmp->desc = xstrdup(desc);
tmp->found = 0;
tmp->next = NULL;
if (last)
last->next = tmp;
else
vartab_h = tmp;
}
/* handle -x / ups.conf config details that are for this part of the code */
static int main_arg(char *var, char *val)
{
/* flags for main */
upsdebugx(3, "%s: var='%s' val='%s'",
__func__,
var ? var : "<null>", /* null should not happen... but... */
val ? val : "<null>");
if (!strcmp(var, "nolock")) {
do_lock_port = 0;
dstate_setinfo("driver.flag.nolock", "enabled");
return 1; /* handled */
}
if (!strcmp(var, "ignorelb")) {
dstate_setinfo("driver.flag.ignorelb", "enabled");
return 1; /* handled */
}
/* any other flags are for the driver code */
if (!val)
return 0; /* unhandled, pass it through to the driver */
/* variables for main: port */
if (!strcmp(var, "port")) {
device_path = xstrdup(val);
device_name = xbasename(device_path);
dstate_setinfo("driver.parameter.port", "%s", val);
return 1; /* handled */
}
/* user specified at the driver level overrides that on global level
* or the built-in default
*/
if (!strcmp(var, "user")) {
if (user_from_cmdline) {
upsdebugx(0, "User '%s' specified in driver section "
"was ignored due to '%s' specified on command line",
val, user);
} else {
upsdebugx(1, "Overriding previously specified user '%s' "
"with '%s' specified for driver section",
user, val);
free(user);
user = xstrdup(val);
}
return 1; /* handled */
}
if (!strcmp(var, "group")) {
if (group_from_cmdline) {
upsdebugx(0, "Group '%s' specified in driver section "
"was ignored due to '%s' specified on command line",
val, group);
} else {
upsdebugx(1, "Overriding previously specified group '%s' "
"with '%s' specified for driver section",
group, val);
free(group);
group = xstrdup(val);
}
return 1; /* handled */
}
if (!strcmp(var, "sddelay")) {
upslogx(LOG_INFO, "Obsolete value sddelay found in ups.conf");
return 1; /* handled */
}
/* allow per-driver overrides of the global setting */
if (!strcmp(var, "synchronous")) {
if (!strcmp(val, "yes"))
do_synchronous=1;
else
if (!strcmp(val, "auto"))
do_synchronous=-1;
else
do_synchronous=0;
return 1; /* handled */
}
/* only for upsdrvctl - ignored here */
if (!strcmp(var, "sdorder"))
return 1; /* handled */
/* only for upsd (at the moment) - ignored here */
if (!strcmp(var, "desc"))
return 1; /* handled */
/* Allow each driver to specify its minimal debugging level -
* admins can set more with command-line args, but can't set
* less without changing config. Should help debug of services. */
if (!strcmp(var, "debug_min")) {
int lvl = -1; // typeof common/common.c: int nut_debug_level
if ( str_to_int (val, &lvl, 10) && lvl >= 0 ) {
nut_debug_level_driver = lvl;
} else {
upslogx(LOG_INFO, "WARNING : Invalid debug_min value found in ups.conf for the driver");
}
return 1; /* handled */
}
return 0; /* unhandled, pass it through to the driver */
}
static void do_global_args(const char *var, const char *val)
{
upsdebugx(3, "%s: var='%s' val='%s'",
__func__,
var ? var : "<null>", /* null should not happen... but... */
val ? val : "<null>");
if (!strcmp(var, "pollinterval")) {
int ipv = atoi(val);
if (ipv > 0) {
poll_interval = (time_t)ipv;
} else {
fatalx(EXIT_FAILURE, "Error: invalid pollinterval: %d", ipv);
}
return;
}
if (!strcmp(var, "chroot")) {
free(chroot_path);
chroot_path = xstrdup(val);
}
if (!strcmp(var, "user")) {
if (user_from_cmdline) {
upsdebugx(0, "User specified in global section '%s' "
"was ignored due to '%s' specified on command line",
val, user);
} else {
upsdebugx(1, "Overriding previously specified user '%s' "
"with '%s' specified in global section",
user, val);
free(user);
user = xstrdup(val);
}
}
if (!strcmp(var, "group")) {
if (group_from_cmdline) {
upsdebugx(0, "Group specified in global section '%s' "
"was ignored due to '%s' specified on command line",
val, group);
} else {
upsdebugx(1, "Overriding previously specified group '%s' "
"with '%s' specified in global section",
group, val);
free(group);
group = xstrdup(val);
}
}
if (!strcmp(var, "synchronous")) {
if (!strcmp(val, "yes"))
do_synchronous=1;
else
if (!strcmp(val, "auto"))
do_synchronous=-1;
else
do_synchronous=0;
}
/* Allow to specify its minimal debugging level for all drivers -
* admins can set more with command-line args, but can't set
* less without changing config. Should help debug of services. */
if (!strcmp(var, "debug_min")) {
int lvl = -1; // typeof common/common.c: int nut_debug_level
if ( str_to_int (val, &lvl, 10) && lvl >= 0 ) {
nut_debug_level_global = lvl;
} else {
upslogx(LOG_INFO, "WARNING : Invalid debug_min value found in ups.conf global settings");
}
}
/* unrecognized */
}
void do_upsconf_args(char *confupsname, char *var, char *val)
{
char tmp[SMALLBUF];
/* handle global declarations */
if (!confupsname) {
do_global_args(var, val);
return;
}
/* no match = not for us */
if (strcmp(confupsname, upsname) != 0)
return;
upsname_found = 1;
if (main_arg(var, val))
return;
/* flags (no =) now get passed to the driver-level stuff */
if (!val) {
/* also store this, but it's a bit different */
snprintf(tmp, sizeof(tmp), "driver.flag.%s", var);
dstate_setinfo(tmp, "enabled");
storeval(var, NULL);
return;
}
/* don't let the user shoot themselves in the foot */
if (!strcmp(var, "driver")) {
/* Accomodate for libtool wrapped developer iterations
* running e.g. `drivers/.libs/lt-dummy-ups` filenames
*/
size_t tmplen = strlen("lt-");
if (strncmp("lt-", progname, tmplen) == 0
&& strcmp(val, progname + tmplen) == 0) {
/* debug level may be not initialized yet, and situation
* should not happen in end-user builds, so ok to yell: */
upsdebugx(0, "Seems this driver binary %s is a libtool "
"wrapped build for driver %s", progname, val);
/* progname points to xbasename(argv[0]) in-place;
* roll the pointer forward a bit, we know we can:
*/
progname = progname + tmplen;
}
if (strcmp(val, progname) != 0) {
fatalx(EXIT_FAILURE, "Error: UPS [%s] is for driver %s, but I'm %s!\n",
confupsname, val, progname);
}
return;
}
/* allow per-driver overrides of the global setting */
if (!strcmp(var, "pollinterval")) {
int ipv = atoi(val);
if (ipv > 0) {
poll_interval = (time_t)ipv;
} else {
fatalx(EXIT_FAILURE, "Error: UPS [%s]: invalid pollinterval: %d",
confupsname, ipv);
}
return;
}
/* everything else must be for the driver */
storeval(var, val);
}
/* split -x foo=bar into 'foo' and 'bar' */
static void splitxarg(char *inbuf)
{
char *eqptr, *val, *buf;
/* make our own copy - avoid changing argv */
buf = xstrdup(inbuf);
eqptr = strchr(buf, '=');
if (!eqptr)
val = NULL;
else {
*eqptr++ = '\0';
val = eqptr;
}
/* see if main handles this first */
if (main_arg(buf, val))
return;
/* otherwise store it for later */
storeval(buf, val);
}
/* dump the list from the vartable for external parsers */
static void listxarg(void)
{
vartab_t *tmp;
tmp = vartab_h;
if (!tmp)
return;
while (tmp) {
switch (tmp->vartype) {
case VAR_VALUE: printf("VALUE"); break;
case VAR_FLAG: printf("FLAG"); break;
default: printf("UNKNOWN"); break;
}
printf(" %s \"%s\"\n", tmp->var, tmp->desc);
tmp = tmp->next;
}
}
static void vartab_free(void)
{
vartab_t *tmp, *next;
tmp = vartab_h;
while (tmp) {
next = tmp->next;
free(tmp->var);
free(tmp->val);
free(tmp->desc);
free(tmp);
tmp = next;
}
}
static void exit_cleanup(void)
{
free(chroot_path);
free(device_path);
free(user);
free(group);
if (pidfn) {
unlink(pidfn);
free(pidfn);
}
dstate_free();
vartab_free();
}
static void set_exit_flag(int sig)
{
exit_flag = sig;
}
static void setup_signals(void)
{
struct sigaction sa;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sa.sa_handler = set_exit_flag;
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGINT, &sa, NULL);
sigaction(SIGQUIT, &sa, NULL);
#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_STRICT_PROTOTYPES)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wstrict-prototypes"
#endif
sa.sa_handler = SIG_IGN;
#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_STRICT_PROTOTYPES)
# pragma GCC diagnostic pop
#endif
sigaction(SIGHUP, &sa, NULL);
sigaction(SIGPIPE, &sa, NULL);
}
int main(int argc, char **argv)
{
struct passwd *new_uid = NULL;
int i, do_forceshutdown = 0;
int update_count = 0;
atexit(exit_cleanup);
/* pick up a default from configure --with-user */
user = xstrdup(RUN_AS_USER); /* xstrdup: this gets freed at exit */
/* pick up a default from configure --with-group */
group = xstrdup(RUN_AS_GROUP); /* xstrdup: this gets freed at exit */
progname = xbasename(argv[0]);
open_syslog(progname);
upsdrv_banner();
if (upsdrv_info.status == DRV_EXPERIMENTAL) {
printf("Warning: This is an experimental driver.\n");
printf("Some features may not function correctly.\n\n");
}
/* build the driver's extra (-x) variable table */
upsdrv_makevartable();
while ((i = getopt(argc, argv, "+a:s:kFBDd:hx:Lqr:u:g:Vi:")) != -1) {
switch (i) {
case 'a':
upsname = optarg;
read_upsconf();
if (!upsname_found)
fatalx(EXIT_FAILURE, "Error: Section %s not found in ups.conf",
optarg);
break;
case 's':
upsname = optarg;
upsname_found = 1;
break;
case 'F':
background_flag = 0;
break;
case 'B':
background_flag = 1;
break;
case 'D':
nut_debug_level++;
break;
case 'd':
dump_data = atoi(optarg);
break;
case 'i': { /* scope */
int ipv = atoi(optarg);
if (ipv > 0) {
poll_interval = (time_t)ipv;
} else {
fatalx(EXIT_FAILURE, "Error: command-line: invalid pollinterval: %d",
ipv);
}
}
break;
case 'k':
do_lock_port = 0;
do_forceshutdown = 1;
break;
case 'L':
listxarg();
exit(EXIT_SUCCESS);
case 'q':
nut_log_level++;
break;
case 'r':
chroot_path = xstrdup(optarg);
break;
case 'u':
if (user_from_cmdline) {
upsdebugx(1, "Previously specified user for drivers '%s' "
"was ignored due to '%s' specified on command line"
" (again?)",
user, optarg);
} else {
upsdebugx(1, "Built-in default or configured user "
"for drivers '%s' was ignored due to '%s' "
"specified on command line",
user, optarg);
}
free(user);
user = xstrdup(optarg);
user_from_cmdline = 1;
break;
case 'g':
if (group_from_cmdline) {
upsdebugx(1, "Previously specified group for drivers '%s' "
"was ignored due to '%s' specified on command line"
" (again?)",
group, optarg);
} else {
upsdebugx(1, "Built-in default or configured group "
"for drivers '%s' was ignored due to '%s' "
"specified on command line",
group, optarg);
}
free(group);
group = xstrdup(optarg);
group_from_cmdline = 1;
break;
case 'V':
/* already printed the banner, so exit */
exit(EXIT_SUCCESS);
case 'x':
splitxarg(optarg);
break;
case 'h':
help_msg();
exit(EXIT_SUCCESS);
default:
fatalx(EXIT_FAILURE,
"Error: unknown option -%c. Try -h for help.", i);
}
}
if (nut_debug_level > 0 || dump_data) {
if ( background_flag < 0 ) {
/* Only flop from default - stay foreground with debug on */
background_flag = 0;
} else {
upsdebugx (0,
"Debug level is %d, dump data count is %s, "
"but backgrounding mode requested as %s",
nut_debug_level,
dump_data ? "on" : "off",
background_flag ? "on" : "off"
);
}
} /* else: default remains `background_flag==-1` where nonzero is true */
argc -= optind;
argv += optind;
if (argc > 0) {
fatalx(EXIT_FAILURE,
"Error: too many non-option arguments. Try -h for help.");
}
if (!upsname_found) {
fatalx(EXIT_FAILURE,
"Error: specifying '-a id' or '-s id' is now mandatory. Try -h for help.");
}
/* we need to get the port from somewhere */
if (!device_path) {
fatalx(EXIT_FAILURE,
"Error: you must specify a port name in ups.conf or in '-x port=...' argument.\n"
"Try -h for help.");
}
/* CLI debug level can not be smaller than debug_min specified
* in ups.conf, and value specified for a driver config section
* overrides the global one. Note that non-zero debug_min does
* not impact foreground running mode.
*/
{
int nut_debug_level_upsconf = -1 ;
if ( nut_debug_level_global >= 0 && nut_debug_level_driver >= 0 ) {
nut_debug_level_upsconf = nut_debug_level_driver;
} else {
if ( nut_debug_level_global >= 0 ) nut_debug_level_upsconf = nut_debug_level_global;
if ( nut_debug_level_driver >= 0 ) nut_debug_level_upsconf = nut_debug_level_driver;
}
if ( nut_debug_level_upsconf > nut_debug_level )
nut_debug_level = nut_debug_level_upsconf;
}
upsdebugx(1, "debug level is '%d'", nut_debug_level);
new_uid = get_user_pwent(user);
if (chroot_path)
chroot_start(chroot_path);
become_user(new_uid);
/* Only switch to statepath if we're not powering off
* or not just dumping data (for discovery) */
/* This avoids case where ie /var is unmounted already */
if ((!do_forceshutdown) && (!dump_data) && (chdir(dflt_statepath())))
fatal_with_errno(EXIT_FAILURE, "Can't chdir to %s", dflt_statepath());
/* Setup signals to communicate with driver once backgrounded. */
if ((background_flag != 0) && (!do_forceshutdown)) {
char buffer[SMALLBUF];
setup_signals();
snprintf(buffer, sizeof(buffer), "%s/%s-%s.pid", altpidpath(), progname, upsname);
/* Try to prevent that driver is started multiple times. If a PID file */
/* already exists, send a TERM signal to the process and try if it goes */
/* away. If not, retry a couple of times. */
for (i = 0; i < 3; i++) {
struct stat st;
if (stat(buffer, &st) != 0) {
/* PID file not found */
break;
}
if (sendsignalfn(buffer, SIGTERM) != 0) {
/* Can't send signal to PID, assume invalid file */
break;
}
upslogx(LOG_WARNING, "Duplicate driver instance detected (PID file %s exists)! Terminating other driver!", buffer);
/* Allow driver some time to quit */
sleep(5);
}
/* Only write pid if we're not just dumping data, for discovery */
if (!dump_data) {
pidfn = xstrdup(buffer);
writepid(pidfn); /* before backgrounding */
}
}
/* clear out callback handler data */
memset(&upsh, '\0', sizeof(upsh));
/* note: device.type is set early to be overridden by the driver
* when its a pdu! */
dstate_setinfo("device.type", "ups");
upsdrv_initups();
/* UPS is detected now, cleanup upon exit */
atexit(upsdrv_cleanup);
/* now see if things are very wrong out there */
if (upsdrv_info.status == DRV_BROKEN) {
fatalx(EXIT_FAILURE, "Fatal error: broken driver. It probably needs to be converted.\n");
}
if (do_forceshutdown)
forceshutdown();
/* publish the top-level data: version numbers, driver name */
dstate_setinfo("driver.version", "%s", UPS_VERSION);
dstate_setinfo("driver.version.internal", "%s", upsdrv_info.version);
dstate_setinfo("driver.name", "%s", progname);
/*
* If we are not debugging, send the early startup logs generated by
* upsdrv_initinfo() and upsdrv_updateinfo() to syslog, not just stderr.
* Otherwise these logs are lost.
*/
if ((nut_debug_level == 0) && (!dump_data))
syslogbit_set();
/* get the base data established before allowing connections */
upsdrv_initinfo();
upsdrv_updateinfo();
if (dstate_getinfo("driver.flag.ignorelb")) {
int have_lb_method = 0;
if (dstate_getinfo("battery.charge") && dstate_getinfo("battery.charge.low")) {
upslogx(LOG_INFO, "using 'battery.charge' to set battery low state");
have_lb_method++;
}
if (dstate_getinfo("battery.runtime") && dstate_getinfo("battery.runtime.low")) {
upslogx(LOG_INFO, "using 'battery.runtime' to set battery low state");
have_lb_method++;
}
if (!have_lb_method) {
fatalx(EXIT_FAILURE,
"The 'ignorelb' flag is set, but there is no way to determine the\n"
"battery state of charge.\n\n"
"Only set this flag if both 'battery.charge' and 'battery.charge.low'\n"
"and/or 'battery.runtime' and 'battery.runtime.low' are available.\n");
}
}
/* now we can start servicing requests */
/* Only write pid if we're not just dumping data, for discovery */
if (!dump_data) {
char * sockname = dstate_init(progname, upsname);
/* Normally we stick to the built-in account info,
* so if they were not over-ridden - no-op here:
*/
if (strcmp(group, RUN_AS_GROUP)
|| strcmp(user, RUN_AS_USER)
) {
int allOk = 1;
/* Tune group access permission to the pipe,
* so that upsd can access it (using the
* specified or retained default group):
*/
struct group *grp = getgrnam(group);
upsdebugx(1, "Group and/or user account for this driver "
"was customized ('%s:%s') compared to built-in "
"defaults. Fixing socket '%s' ownership/access.",
user, group, sockname);
if (grp == NULL) {
upsdebugx(1, "WARNING: could not resolve "
"group name '%s': %s",
group, strerror(errno)
);
allOk = 0;
} else {
struct stat statbuf;
mode_t mode;
if (chown(sockname, -1, grp->gr_gid)) {
upsdebugx(1, "WARNING: chown failed: %s",
strerror(errno)
);
allOk = 0;
}
if (stat(sockname, &statbuf)) {
/* Logically we'd fail chown above if file
* does not exist or is not accessible, but
* practically we only need stat for chmod
*/
upsdebugx(1, "WARNING: stat failed: %s",
strerror(errno)
);
allOk = 0;
} else {
/* chmod g+rw sockname */
mode = statbuf.st_mode;
mode |= S_IWGRP;
mode |= S_IRGRP;
if (chmod(sockname, mode)) {
upsdebugx(1, "WARNING: chmod failed: %s",
strerror(errno)
);
allOk = 0;
}
}
}
if (allOk) {
upsdebugx(1, "Group access for this driver successfully fixed");
} else {
upsdebugx(0, "WARNING: Needed to fix group access "
"to filesystem socket of this driver, but failed; "
"run the driver with more debugging to see how exactly.\n"
"Consumers of the socket, such as upsd data server, "
"can fail to interact with the driver and represent "
"the device: %s",
sockname);
}
}
free(sockname);
}
/* The poll_interval may have been changed from the default */
dstate_setinfo("driver.parameter.pollinterval", "%jd", (intmax_t)poll_interval);
/* The synchronous option may have been changed from the default */
dstate_setinfo("driver.parameter.synchronous", "%s",
(do_synchronous==1)?"yes":((do_synchronous==0)?"no":"auto"));
/* remap the device.* info from ups.* for the transition period */
if (dstate_getinfo("ups.mfr") != NULL)
dstate_setinfo("device.mfr", "%s", dstate_getinfo("ups.mfr"));
if (dstate_getinfo("ups.model") != NULL)
dstate_setinfo("device.model", "%s", dstate_getinfo("ups.model"));
if (dstate_getinfo("ups.serial") != NULL)
dstate_setinfo("device.serial", "%s", dstate_getinfo("ups.serial"));
if (background_flag != 0) {
background();
writepid(pidfn); /* PID changes when backgrounding */
}
while (!exit_flag) {
struct timeval timeout;
gettimeofday(&timeout, NULL);
timeout.tv_sec += poll_interval;
upsdrv_updateinfo();
/* Dump the data tree (in upsc-like format) to stdout and exit */
if (dump_data) {
/* Wait for 'dump_data' update loops to ensure data completion */
if (update_count == dump_data) {
dstate_dump();
exit_flag = 1;
}
else
update_count++;
}
else {
while (!dstate_poll_fds(timeout, extrafd) && !exit_flag) {
/* repeat until time is up or extrafd has data */
}
}
}
/* if we get here, the exit flag was set by a signal handler */
/* however, avoid to "pollute" data dump output! */
if (!dump_data)
upslogx(LOG_INFO, "Signal %d: exiting", exit_flag);
exit(EXIT_SUCCESS);
}