2011-01-26 09:35:08 +00:00
|
|
|
/* dummy-ups.c - NUT simulation and device repeater driver
|
2010-03-25 23:20:59 +00:00
|
|
|
|
|
|
|
Copyright (C)
|
2016-07-18 00:11:41 +00:00
|
|
|
2005 - 2015 Arnaud Quette <http://arnaud.quette.free.fr/contact.html>
|
2010-03-25 23:20:59 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* TODO list:
|
|
|
|
* - separate the code between dummy and repeater/meta
|
|
|
|
* - for repeater/meta:
|
|
|
|
* * add support for instant commands and setvar
|
|
|
|
* - for dummy:
|
|
|
|
* * variable/value enforcement using cmdvartab for testing
|
|
|
|
* the variable existance, and possible values
|
|
|
|
* * allow variable creation on the fly (using upsrw)
|
|
|
|
* * poll the "port" file for change
|
|
|
|
*/
|
|
|
|
|
2022-06-29 10:37:36 +00:00
|
|
|
#include "config.h" /* must be the first header */
|
|
|
|
|
2010-03-25 23:20:59 +00:00
|
|
|
#include <netdb.h>
|
|
|
|
#include <netinet/in.h>
|
|
|
|
#include <sys/socket.h>
|
2022-06-29 10:37:36 +00:00
|
|
|
#include <sys/stat.h>
|
2010-03-25 23:20:59 +00:00
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include "main.h"
|
|
|
|
#include "parseconf.h"
|
2022-06-29 10:37:36 +00:00
|
|
|
#include "nut_stdint.h"
|
2010-03-25 23:20:59 +00:00
|
|
|
#include "upsclient.h"
|
|
|
|
#include "dummy-ups.h"
|
|
|
|
|
2011-01-26 09:35:08 +00:00
|
|
|
#define DRIVER_NAME "Device simulation and repeater driver"
|
2022-06-29 10:37:36 +00:00
|
|
|
#define DRIVER_VERSION "0.15"
|
2010-03-25 23:20:59 +00:00
|
|
|
|
|
|
|
/* driver description structure */
|
2011-01-26 09:35:08 +00:00
|
|
|
upsdrv_info_t upsdrv_info =
|
|
|
|
{
|
2010-03-25 23:20:59 +00:00
|
|
|
DRIVER_NAME,
|
|
|
|
DRIVER_VERSION,
|
|
|
|
"Arnaud Quette <arnaud.quette@gmail.com>",
|
|
|
|
DRV_STABLE,
|
|
|
|
{ NULL }
|
|
|
|
};
|
|
|
|
|
2022-06-29 10:37:36 +00:00
|
|
|
enum drivermode {
|
|
|
|
MODE_NONE = 0,
|
|
|
|
|
|
|
|
/* use the embedded defintion or a definition file, parsed in a
|
|
|
|
* loop again and again (often with TIMER lines to delay changes)
|
|
|
|
* Default mode for files with *.seq naming pattern
|
|
|
|
* (legacy-compatibility note: and other patterns except *.dev)
|
|
|
|
*/
|
|
|
|
MODE_DUMMY_LOOP,
|
|
|
|
|
|
|
|
/* use the embedded defintion or a definition file, parsed once
|
|
|
|
*
|
|
|
|
* This allows to spin up a dummy device with initial readings
|
|
|
|
* and retain in memory whatever SET VAR was sent by clients later.
|
|
|
|
* This is also less stressful on system resources to run the dummy.
|
|
|
|
*
|
|
|
|
* Default mode for files with *.dev naming pattern
|
|
|
|
*/
|
|
|
|
MODE_DUMMY_ONCE,
|
|
|
|
|
|
|
|
/* use libupsclient to repeat another UPS */
|
|
|
|
MODE_REPEATER,
|
|
|
|
|
|
|
|
/* consolidate data from several UPSs (TBS) */
|
|
|
|
MODE_META
|
|
|
|
};
|
|
|
|
typedef enum drivermode drivermode_t;
|
2010-03-25 23:20:59 +00:00
|
|
|
|
2022-06-29 10:37:36 +00:00
|
|
|
static drivermode_t mode = MODE_NONE;
|
2010-03-25 23:20:59 +00:00
|
|
|
|
|
|
|
/* parseconf context, for dummy mode using a file */
|
2022-06-29 10:37:36 +00:00
|
|
|
static PCONF_CTX_t *ctx = NULL;
|
|
|
|
static time_t next_update = -1;
|
|
|
|
static struct stat datafile_stat;
|
2010-03-25 23:20:59 +00:00
|
|
|
|
|
|
|
#define MAX_STRING_SIZE 128
|
|
|
|
|
|
|
|
static int setvar(const char *varname, const char *val);
|
|
|
|
static int instcmd(const char *cmdname, const char *extra);
|
|
|
|
static int parse_data_file(int upsfd);
|
|
|
|
static dummy_info_t *find_info(const char *varname);
|
|
|
|
static int is_valid_data(const char* varname);
|
|
|
|
static int is_valid_value(const char* varname, const char *value);
|
|
|
|
/* libupsclient update */
|
|
|
|
static int upsclient_update_vars(void);
|
|
|
|
|
|
|
|
/* connection information */
|
|
|
|
static char *client_upsname = NULL, *hostname = NULL;
|
|
|
|
static UPSCONN_t *ups = NULL;
|
2022-06-29 10:37:36 +00:00
|
|
|
static uint16_t port;
|
2010-03-25 23:20:59 +00:00
|
|
|
|
|
|
|
/* Driver functions */
|
|
|
|
|
|
|
|
void upsdrv_initinfo(void)
|
|
|
|
{
|
|
|
|
dummy_info_t *item;
|
|
|
|
|
2011-01-26 09:35:08 +00:00
|
|
|
switch (mode)
|
|
|
|
{
|
2022-06-29 10:37:36 +00:00
|
|
|
case MODE_DUMMY_ONCE:
|
|
|
|
case MODE_DUMMY_LOOP:
|
2010-03-25 23:20:59 +00:00
|
|
|
/* Initialise basic essential variables */
|
2011-01-26 09:35:08 +00:00
|
|
|
for ( item = nut_data ; item->info_type != NULL ; item++ )
|
|
|
|
{
|
|
|
|
if (item->drv_flags & DU_FLAG_INIT)
|
|
|
|
{
|
2010-03-25 23:20:59 +00:00
|
|
|
dstate_setinfo(item->info_type, "%s", item->default_value);
|
|
|
|
dstate_setflags(item->info_type, item->info_flags);
|
|
|
|
|
|
|
|
/* Set max length for strings, if needed */
|
|
|
|
if (item->info_flags & ST_FLAG_STRING)
|
|
|
|
dstate_setaux(item->info_type, item->info_len);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Now get user's defined variables */
|
|
|
|
if (parse_data_file(upsfd) < 0)
|
|
|
|
upslogx(LOG_NOTICE, "Unable to parse the definition file %s", device_path);
|
|
|
|
|
|
|
|
/* Initialize handler */
|
|
|
|
upsh.setvar = setvar;
|
|
|
|
|
|
|
|
dstate_dataok();
|
|
|
|
break;
|
2022-06-29 10:37:36 +00:00
|
|
|
|
2010-03-25 23:20:59 +00:00
|
|
|
case MODE_META:
|
|
|
|
case MODE_REPEATER:
|
|
|
|
/* Obtain the target name */
|
2011-01-26 09:35:08 +00:00
|
|
|
if (upscli_splitname(device_path, &client_upsname, &hostname, &port) != 0)
|
|
|
|
{
|
2010-03-25 23:20:59 +00:00
|
|
|
fatalx(EXIT_FAILURE, "Error: invalid UPS definition.\nRequired format: upsname[@hostname[:port]]");
|
|
|
|
}
|
|
|
|
/* Connect to the target */
|
|
|
|
ups = xmalloc(sizeof(*ups));
|
2011-01-26 09:35:08 +00:00
|
|
|
if (upscli_connect(ups, hostname, port, UPSCLI_CONN_TRYSSL) < 0)
|
|
|
|
{
|
2010-03-25 23:20:59 +00:00
|
|
|
fatalx(EXIT_FAILURE, "Error: %s", upscli_strerror(ups));
|
|
|
|
}
|
2011-01-26 09:35:08 +00:00
|
|
|
else
|
|
|
|
{
|
2010-03-25 23:20:59 +00:00
|
|
|
upsdebugx(1, "Connected to %s@%s", client_upsname, hostname);
|
|
|
|
}
|
2011-01-26 09:35:08 +00:00
|
|
|
if (upsclient_update_vars() < 0)
|
|
|
|
{
|
2010-03-25 23:20:59 +00:00
|
|
|
/* check for an old upsd */
|
2011-01-26 09:35:08 +00:00
|
|
|
if (upscli_upserror(ups) == UPSCLI_ERR_UNKCOMMAND)
|
|
|
|
{
|
2010-03-25 23:20:59 +00:00
|
|
|
fatalx(EXIT_FAILURE, "Error: upsd is too old to support this query");
|
|
|
|
}
|
|
|
|
fatalx(EXIT_FAILURE, "Error: %s", upscli_strerror(ups));
|
|
|
|
}
|
2011-01-26 09:35:08 +00:00
|
|
|
/* FIXME: commands and settable variable! */
|
2010-03-25 23:20:59 +00:00
|
|
|
break;
|
2022-06-29 10:37:36 +00:00
|
|
|
|
2011-01-26 09:35:08 +00:00
|
|
|
case MODE_NONE:
|
2022-06-29 10:37:36 +00:00
|
|
|
#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) )
|
|
|
|
# pragma GCC diagnostic push
|
|
|
|
#endif
|
|
|
|
#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT
|
|
|
|
# pragma GCC diagnostic ignored "-Wcovered-switch-default"
|
|
|
|
#endif
|
|
|
|
#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE
|
|
|
|
# pragma GCC diagnostic ignored "-Wunreachable-code"
|
|
|
|
#endif
|
|
|
|
/* Older CLANG (e.g. clang-3.4) seems to not support the GCC pragmas above */
|
|
|
|
#ifdef __clang__
|
|
|
|
# pragma clang diagnostic push
|
|
|
|
# pragma clang diagnostic ignored "-Wunreachable-code"
|
|
|
|
# pragma clang diagnostic ignored "-Wcovered-switch-default"
|
|
|
|
#endif
|
|
|
|
/* All enum cases defined as of the time of coding
|
|
|
|
* have been covered above. Handle later definitions,
|
|
|
|
* memory corruptions and buggy inputs below...
|
|
|
|
*/
|
|
|
|
default:
|
2010-03-25 23:20:59 +00:00
|
|
|
fatalx(EXIT_FAILURE, "no suitable definition found!");
|
2022-06-29 10:37:36 +00:00
|
|
|
#ifdef __clang__
|
|
|
|
# pragma clang diagnostic pop
|
|
|
|
#endif
|
|
|
|
#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) )
|
|
|
|
# pragma GCC diagnostic pop
|
|
|
|
#endif
|
2010-03-25 23:20:59 +00:00
|
|
|
}
|
|
|
|
upsh.instcmd = instcmd;
|
|
|
|
|
|
|
|
dstate_addcmd("load.off");
|
|
|
|
}
|
|
|
|
|
|
|
|
void upsdrv_updateinfo(void)
|
|
|
|
{
|
|
|
|
upsdebugx(1, "upsdrv_updateinfo...");
|
|
|
|
|
|
|
|
sleep(1);
|
|
|
|
|
2011-01-26 09:35:08 +00:00
|
|
|
switch (mode)
|
|
|
|
{
|
2022-06-29 10:37:36 +00:00
|
|
|
case MODE_DUMMY_LOOP:
|
2010-03-25 23:20:59 +00:00
|
|
|
/* Now get user's defined variables */
|
|
|
|
if (parse_data_file(upsfd) >= 0)
|
|
|
|
dstate_dataok();
|
|
|
|
break;
|
2022-06-29 10:37:36 +00:00
|
|
|
|
|
|
|
case MODE_DUMMY_ONCE:
|
|
|
|
/* less stress on the sys */
|
|
|
|
if (ctx == NULL && next_update == -1) {
|
|
|
|
struct stat fs;
|
|
|
|
char fn[SMALLBUF];
|
|
|
|
|
|
|
|
if (device_path[0] == '/')
|
|
|
|
snprintf(fn, sizeof(fn), "%s", device_path);
|
|
|
|
else
|
|
|
|
snprintf(fn, sizeof(fn), "%s/%s", confpath(), device_path);
|
|
|
|
|
|
|
|
if (0 != fstat (upsfd, &fs) && 0 != stat (fn, &fs)) {
|
|
|
|
upsdebugx(2, "Can't open %s currently", fn);
|
|
|
|
/* retry ASAP until we get a file */
|
|
|
|
memset(&datafile_stat, 0, sizeof(struct stat));
|
|
|
|
next_update = 1;
|
|
|
|
} else {
|
|
|
|
if (datafile_stat.st_mtim.tv_sec != fs.st_mtim.tv_sec) {
|
|
|
|
upsdebugx(2, "upsdrv_updateinfo: input file was already read once to the end, but changed later - re-reading");
|
|
|
|
/* updated file => retry ASAP */
|
|
|
|
next_update = 1;
|
|
|
|
datafile_stat = fs;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ctx == NULL && next_update == -1) {
|
|
|
|
upsdebugx(2, "upsdrv_updateinfo: NO-OP: input file was already read once to the end");
|
|
|
|
dstate_dataok();
|
|
|
|
} else {
|
|
|
|
/* initial parsing interrupted by e.g. TIMER line */
|
|
|
|
if (parse_data_file(upsfd) >= 0)
|
|
|
|
dstate_dataok();
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2010-03-25 23:20:59 +00:00
|
|
|
case MODE_META:
|
|
|
|
case MODE_REPEATER:
|
|
|
|
if (upsclient_update_vars() > 0)
|
2011-01-26 09:35:08 +00:00
|
|
|
{
|
2010-03-25 23:20:59 +00:00
|
|
|
dstate_dataok();
|
2011-01-26 09:35:08 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2010-03-25 23:20:59 +00:00
|
|
|
/* try to reconnect */
|
|
|
|
upscli_disconnect(ups);
|
2011-01-26 09:35:08 +00:00
|
|
|
if (upscli_connect(ups, hostname, port, UPSCLI_CONN_TRYSSL) < 0)
|
|
|
|
{
|
2010-03-25 23:20:59 +00:00
|
|
|
upsdebugx(1, "Error reconnecting: %s", upscli_strerror(ups));
|
|
|
|
}
|
2011-01-26 09:35:08 +00:00
|
|
|
else
|
|
|
|
{
|
2010-03-25 23:20:59 +00:00
|
|
|
upsdebugx(1, "Reconnected");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
2022-06-29 10:37:36 +00:00
|
|
|
|
2011-01-26 09:35:08 +00:00
|
|
|
case MODE_NONE:
|
2022-06-29 10:37:36 +00:00
|
|
|
#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) )
|
|
|
|
# pragma GCC diagnostic push
|
|
|
|
#endif
|
|
|
|
#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT
|
|
|
|
# pragma GCC diagnostic ignored "-Wcovered-switch-default"
|
|
|
|
#endif
|
|
|
|
#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE
|
|
|
|
# pragma GCC diagnostic ignored "-Wunreachable-code"
|
|
|
|
#endif
|
|
|
|
/* Older CLANG (e.g. clang-3.4) seems to not support the GCC pragmas above */
|
|
|
|
#ifdef __clang__
|
|
|
|
# pragma clang diagnostic push
|
|
|
|
# pragma clang diagnostic ignored "-Wunreachable-code"
|
|
|
|
# pragma clang diagnostic ignored "-Wcovered-switch-default"
|
|
|
|
#endif
|
|
|
|
/* All enum cases defined as of the time of coding
|
|
|
|
* have been covered above. Handle later definitions,
|
|
|
|
* memory corruptions and buggy inputs below...
|
|
|
|
*/
|
2011-01-26 09:35:08 +00:00
|
|
|
default:
|
|
|
|
break;
|
2022-06-29 10:37:36 +00:00
|
|
|
#ifdef __clang__
|
|
|
|
# pragma clang diagnostic pop
|
|
|
|
#endif
|
|
|
|
#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) )
|
|
|
|
# pragma GCC diagnostic pop
|
|
|
|
#endif
|
2010-03-25 23:20:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-29 10:37:36 +00:00
|
|
|
void upsdrv_shutdown(void)
|
|
|
|
__attribute__((noreturn));
|
|
|
|
|
2010-03-25 23:20:59 +00:00
|
|
|
void upsdrv_shutdown(void)
|
|
|
|
{
|
|
|
|
fatalx(EXIT_FAILURE, "shutdown not supported");
|
|
|
|
}
|
|
|
|
|
|
|
|
static int instcmd(const char *cmdname, const char *extra)
|
2022-06-29 10:37:36 +00:00
|
|
|
{
|
2010-03-25 23:20:59 +00:00
|
|
|
/*
|
|
|
|
if (!strcasecmp(cmdname, "test.battery.stop")) {
|
|
|
|
ser_send_buf(upsfd, ...);
|
|
|
|
return STAT_INSTCMD_HANDLED;
|
|
|
|
}
|
|
|
|
*/
|
2012-01-24 10:22:33 +00:00
|
|
|
/* FIXME: the below is only valid if (mode == MODE_DUMMY)
|
|
|
|
* if (mode == MODE_REPEATER) => forward
|
|
|
|
* if (mode == MODE_META) => ?
|
|
|
|
*/
|
|
|
|
|
2022-06-29 10:37:36 +00:00
|
|
|
upslogx(LOG_NOTICE, "instcmd: unknown command [%s] [%s]", cmdname, extra);
|
2010-03-25 23:20:59 +00:00
|
|
|
return STAT_INSTCMD_UNKNOWN;
|
|
|
|
}
|
|
|
|
|
|
|
|
void upsdrv_help(void)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void upsdrv_makevartable(void)
|
|
|
|
{
|
2022-06-29 10:37:36 +00:00
|
|
|
addvar(VAR_VALUE, "mode", "Specify mode instead of guessing it from port value (dummy = dummy-loop, dummy-once, repeater)"); /* meta */
|
2010-03-25 23:20:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void upsdrv_initups(void)
|
|
|
|
{
|
2022-06-29 10:37:36 +00:00
|
|
|
const char *val;
|
|
|
|
|
|
|
|
val = dstate_getinfo("driver.parameter.mode");
|
|
|
|
if (val) {
|
|
|
|
if (!strcmp(val, "dummy-loop")
|
|
|
|
&& !strcmp(val, "dummy-once")
|
|
|
|
&& !strcmp(val, "dummy")
|
|
|
|
&& !strcmp(val, "repeater")
|
|
|
|
/* && !strcmp(val, "meta") */
|
|
|
|
) {
|
|
|
|
fatalx(EXIT_FAILURE, "Unsupported mode was specified: %s", val);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-03-25 23:20:59 +00:00
|
|
|
/* check the running mode... */
|
2022-06-29 10:37:36 +00:00
|
|
|
if ( (!val && strchr(device_path, '@'))
|
|
|
|
|| (val && !strcmp(val, "repeater"))
|
|
|
|
/*|| (val && !strcmp(val, "meta")) */
|
|
|
|
) {
|
2010-03-25 23:20:59 +00:00
|
|
|
upsdebugx(1, "Repeater mode");
|
|
|
|
mode = MODE_REPEATER;
|
|
|
|
dstate_setinfo("driver.parameter.mode", "repeater");
|
2011-01-26 09:35:08 +00:00
|
|
|
/* FIXME: if there is at least one more => MODE_META... */
|
2010-03-25 23:20:59 +00:00
|
|
|
}
|
2011-01-26 09:35:08 +00:00
|
|
|
else
|
|
|
|
{
|
2022-06-29 10:37:36 +00:00
|
|
|
char fn[SMALLBUF];
|
|
|
|
mode = MODE_NONE;
|
|
|
|
|
|
|
|
if (val) {
|
|
|
|
if (!strcmp(val, "dummy-loop")) {
|
|
|
|
upsdebugx(2, "Dummy (simulation) mode looping infinitely was explicitly requested");
|
|
|
|
mode = MODE_DUMMY_LOOP;
|
|
|
|
} else
|
|
|
|
if (!strcmp(val, "dummy-once")) {
|
|
|
|
upsdebugx(2, "Dummy (simulation) mode with data read once was explicitly requested");
|
|
|
|
mode = MODE_DUMMY_ONCE;
|
|
|
|
} else
|
|
|
|
if (!strcmp(val, "dummy")) {
|
|
|
|
upsdebugx(2, "Dummy (simulation) mode default (looping infinitely) was explicitly requested");
|
|
|
|
mode = MODE_DUMMY_LOOP;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (mode == MODE_NONE) {
|
|
|
|
if (str_ends_with(device_path, ".seq")) {
|
|
|
|
upsdebugx(2, "Dummy (simulation) mode with a sequence file name pattern (looping infinitely)");
|
|
|
|
mode = MODE_DUMMY_LOOP;
|
|
|
|
} else if (str_ends_with(device_path, ".dev")) {
|
|
|
|
upsdebugx(2, "Dummy (simulation) mode with a device data dump file name pattern (read once)");
|
|
|
|
mode = MODE_DUMMY_ONCE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Report decisions similar to those above,
|
|
|
|
* just a bit shorter and at another level */
|
|
|
|
switch (mode) {
|
|
|
|
case MODE_DUMMY_ONCE:
|
|
|
|
upsdebugx(1, "Dummy (simulation) mode using data read once");
|
|
|
|
dstate_setinfo("driver.parameter.mode", "dummy-once");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MODE_DUMMY_LOOP:
|
|
|
|
upsdebugx(1, "Dummy (simulation) mode looping infinitely");
|
|
|
|
dstate_setinfo("driver.parameter.mode", "dummy-loop");
|
|
|
|
break;
|
|
|
|
|
|
|
|
case MODE_NONE:
|
|
|
|
case MODE_REPEATER:
|
|
|
|
case MODE_META:
|
|
|
|
#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) )
|
|
|
|
# pragma GCC diagnostic push
|
|
|
|
#endif
|
|
|
|
#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT
|
|
|
|
# pragma GCC diagnostic ignored "-Wcovered-switch-default"
|
|
|
|
#endif
|
|
|
|
#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE
|
|
|
|
# pragma GCC diagnostic ignored "-Wunreachable-code"
|
|
|
|
#endif
|
|
|
|
/* Older CLANG (e.g. clang-3.4) seems to not support the GCC pragmas above */
|
|
|
|
#ifdef __clang__
|
|
|
|
# pragma clang diagnostic push
|
|
|
|
# pragma clang diagnostic ignored "-Wunreachable-code"
|
|
|
|
# pragma clang diagnostic ignored "-Wcovered-switch-default"
|
|
|
|
#endif
|
|
|
|
/* All enum cases defined as of the time of coding
|
|
|
|
* have been covered above. Handle later definitions,
|
|
|
|
* memory corruptions and buggy inputs below...
|
|
|
|
*/
|
|
|
|
default:
|
|
|
|
/* This was the only mode until MODE_DUMMY_LOOP
|
|
|
|
* got split from MODE_DUMMY_ONCE in NUT v2.8.0
|
|
|
|
* so we keep the previously known mode string
|
|
|
|
* and it remains default when we are not sure
|
|
|
|
*/
|
|
|
|
upsdebugx(1, "Dummy (simulation) mode default (looping infinitely)");
|
|
|
|
mode = MODE_DUMMY_LOOP;
|
|
|
|
dstate_setinfo("driver.parameter.mode", "dummy");
|
|
|
|
break;
|
|
|
|
#ifdef __clang__
|
|
|
|
# pragma clang diagnostic pop
|
|
|
|
#endif
|
|
|
|
#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) )
|
|
|
|
# pragma GCC diagnostic pop
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
if (device_path[0] == '/')
|
|
|
|
snprintf(fn, sizeof(fn), "%s", device_path);
|
|
|
|
else
|
|
|
|
snprintf(fn, sizeof(fn), "%s/%s", confpath(), device_path);
|
|
|
|
|
|
|
|
if (0 != fstat (upsfd, &datafile_stat) && 0 != stat (device_path, &datafile_stat)) {
|
|
|
|
upsdebugx(2, "Can't open %s currently", device_path);
|
|
|
|
}
|
2010-03-25 23:20:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void upsdrv_cleanup(void)
|
|
|
|
{
|
2011-01-26 09:35:08 +00:00
|
|
|
if ( (mode == MODE_META) || (mode == MODE_REPEATER) )
|
|
|
|
{
|
|
|
|
if (ups)
|
|
|
|
{
|
2010-03-25 23:20:59 +00:00
|
|
|
upscli_disconnect(ups);
|
|
|
|
}
|
|
|
|
|
2011-01-26 09:35:08 +00:00
|
|
|
if (ctx)
|
|
|
|
{
|
2010-03-25 23:20:59 +00:00
|
|
|
pconf_finish(ctx);
|
|
|
|
free(ctx);
|
|
|
|
}
|
|
|
|
|
|
|
|
free(client_upsname);
|
|
|
|
free(hostname);
|
|
|
|
free(ups);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int setvar(const char *varname, const char *val)
|
|
|
|
{
|
|
|
|
dummy_info_t *item;
|
|
|
|
|
|
|
|
upsdebugx(2, "entering setvar(%s, %s)", varname, val);
|
|
|
|
|
2012-01-24 10:22:33 +00:00
|
|
|
/* FIXME: the below is only valid if (mode == MODE_DUMMY)
|
|
|
|
* if (mode == MODE_REPEATER) => forward
|
|
|
|
* if (mode == MODE_META) => ?
|
|
|
|
*/
|
2011-01-26 09:35:08 +00:00
|
|
|
if (!strncmp(varname, "ups.status", 10))
|
|
|
|
{
|
2010-03-25 23:20:59 +00:00
|
|
|
status_init();
|
|
|
|
/* FIXME: split and check values (support multiple values), à la usbhid-ups */
|
|
|
|
status_set(val);
|
|
|
|
status_commit();
|
|
|
|
|
|
|
|
return STAT_SET_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check variable validity */
|
2011-01-26 09:35:08 +00:00
|
|
|
if (!is_valid_data(varname))
|
|
|
|
{
|
2010-03-25 23:20:59 +00:00
|
|
|
upsdebugx(2, "setvar: invalid variable name (%s)", varname);
|
|
|
|
return STAT_SET_UNKNOWN;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check value validity */
|
2011-01-26 09:35:08 +00:00
|
|
|
if (!is_valid_value(varname, val))
|
|
|
|
{
|
2010-03-25 23:20:59 +00:00
|
|
|
upsdebugx(2, "setvar: invalid value (%s) for variable (%s)", val, varname);
|
|
|
|
return STAT_SET_UNKNOWN;
|
|
|
|
}
|
|
|
|
|
2011-01-26 09:35:08 +00:00
|
|
|
/* If value is empty, remove the variable (FIXME: do we need
|
|
|
|
* a magic word?) */
|
|
|
|
if (strlen(val) == 0)
|
|
|
|
{
|
|
|
|
dstate_delinfo(varname);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
dstate_setinfo(varname, "%s", val);
|
2010-03-25 23:20:59 +00:00
|
|
|
|
2011-01-26 09:35:08 +00:00
|
|
|
if ( (item = find_info(varname)) != NULL)
|
|
|
|
{
|
|
|
|
dstate_setflags(item->info_type, item->info_flags);
|
2010-03-25 23:20:59 +00:00
|
|
|
|
2011-01-26 09:35:08 +00:00
|
|
|
/* Set max length for strings, if needed */
|
|
|
|
if (item->info_flags & ST_FLAG_STRING)
|
|
|
|
dstate_setaux(item->info_type, item->info_len);
|
|
|
|
}
|
2016-07-18 00:11:41 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
upsdebugx(2, "setvar: unknown variable (%s), using default flags", varname);
|
|
|
|
dstate_setflags(varname, ST_FLAG_STRING | ST_FLAG_RW);
|
|
|
|
dstate_setaux(varname, 32);
|
|
|
|
}
|
2010-03-25 23:20:59 +00:00
|
|
|
}
|
|
|
|
return STAT_SET_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*************************************************/
|
|
|
|
/* Support functions */
|
|
|
|
/*************************************************/
|
|
|
|
|
|
|
|
static int upsclient_update_vars(void)
|
|
|
|
{
|
|
|
|
int ret;
|
2022-06-29 10:37:36 +00:00
|
|
|
size_t numq, numa;
|
2010-03-25 23:20:59 +00:00
|
|
|
const char *query[4];
|
|
|
|
char **answer;
|
|
|
|
|
|
|
|
query[0] = "VAR";
|
|
|
|
query[1] = client_upsname;
|
|
|
|
numq = 2;
|
|
|
|
|
|
|
|
ret = upscli_list_start(ups, numq, query);
|
|
|
|
|
2011-01-26 09:35:08 +00:00
|
|
|
if (ret < 0)
|
|
|
|
{
|
2010-03-25 23:20:59 +00:00
|
|
|
upsdebugx(1, "Error: %s (%i)", upscli_strerror(ups), upscli_upserror(ups));
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2011-01-26 09:35:08 +00:00
|
|
|
while (upscli_list_next(ups, numq, query, &numa, &answer) == 1)
|
|
|
|
{
|
2010-03-25 23:20:59 +00:00
|
|
|
/* VAR <upsname> <varname> <val> */
|
2011-01-26 09:35:08 +00:00
|
|
|
if (numa < 4)
|
|
|
|
{
|
2022-06-29 10:37:36 +00:00
|
|
|
upsdebugx(1, "Error: insufficient data (got %zu args, need at least 4)", numa);
|
2010-03-25 23:20:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
upsdebugx(5, "Received: %s %s %s %s",
|
|
|
|
answer[0], answer[1], answer[2], answer[3]);
|
|
|
|
|
|
|
|
/* do not override the driver collection */
|
|
|
|
if (strncmp(answer[2], "driver.", 7))
|
|
|
|
setvar(answer[2], answer[3]);
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* find info element definition in info array */
|
|
|
|
static dummy_info_t *find_info(const char *varname)
|
|
|
|
{
|
|
|
|
dummy_info_t *item;
|
|
|
|
|
2011-01-26 09:35:08 +00:00
|
|
|
for ( item = nut_data ; item->info_type != NULL ; item++ )
|
|
|
|
{
|
2010-03-25 23:20:59 +00:00
|
|
|
if (!strcasecmp(item->info_type, varname))
|
|
|
|
return item;
|
|
|
|
}
|
|
|
|
|
|
|
|
upsdebugx(2, "find_info: unknown variable: %s\n", varname);
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* check if data exists in our data table */
|
|
|
|
static int is_valid_data(const char* varname)
|
|
|
|
{
|
|
|
|
dummy_info_t *item;
|
|
|
|
|
2011-01-26 09:35:08 +00:00
|
|
|
if ( (item = find_info(varname)) != NULL)
|
|
|
|
{
|
|
|
|
return 1;
|
2010-03-25 23:20:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* FIXME: we need to have the full data set before
|
2011-01-26 09:35:08 +00:00
|
|
|
* enforcing controls! We also need a way to automate
|
|
|
|
* the update / sync process (with cmdvartab?!) */
|
|
|
|
|
2011-06-01 20:31:49 +00:00
|
|
|
upsdebugx(1, "Unknown data. Committing anyway...");
|
2010-03-25 23:20:59 +00:00
|
|
|
return 1;
|
|
|
|
/* return 0;*/
|
|
|
|
}
|
|
|
|
|
|
|
|
/* check if data's value validity */
|
|
|
|
static int is_valid_value(const char* varname, const char *value)
|
|
|
|
{
|
|
|
|
dummy_info_t *item;
|
2022-06-29 10:37:36 +00:00
|
|
|
NUT_UNUSED_VARIABLE(value);
|
2010-03-25 23:20:59 +00:00
|
|
|
|
2011-01-26 09:35:08 +00:00
|
|
|
if ( (item = find_info(varname)) != NULL)
|
|
|
|
{
|
2010-03-25 23:20:59 +00:00
|
|
|
/* FIXME: test enum or bound against value */
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* FIXME: we need to have the full data set before
|
2011-01-26 09:35:08 +00:00
|
|
|
* enforcing controls! We also need a way to automate
|
|
|
|
* the update / sync process (with cmdvartab?) */
|
|
|
|
|
2011-06-01 20:31:49 +00:00
|
|
|
upsdebugx(1, "Unknown data. Committing value anyway...");
|
2010-03-25 23:20:59 +00:00
|
|
|
return 1;
|
|
|
|
/* return 0;*/
|
|
|
|
}
|
|
|
|
|
|
|
|
/* called for fatal errors in parseconf like malloc failures */
|
|
|
|
static void upsconf_err(const char *errmsg)
|
|
|
|
{
|
|
|
|
upslogx(LOG_ERR, "Fatal error in parseconf(ups.conf): %s", errmsg);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* for dummy mode
|
|
|
|
* parse the definition file and process its content
|
2022-06-29 10:37:36 +00:00
|
|
|
*/
|
|
|
|
static int parse_data_file(int arg_upsfd)
|
2010-03-25 23:20:59 +00:00
|
|
|
{
|
|
|
|
char fn[SMALLBUF];
|
2011-06-01 20:31:49 +00:00
|
|
|
char *ptr, var_value[MAX_STRING_SIZE];
|
2022-06-29 10:37:36 +00:00
|
|
|
size_t value_args = 0, counter;
|
2010-03-25 23:20:59 +00:00
|
|
|
time_t now;
|
2022-06-29 10:37:36 +00:00
|
|
|
NUT_UNUSED_VARIABLE(arg_upsfd);
|
2010-03-25 23:20:59 +00:00
|
|
|
|
|
|
|
time(&now);
|
|
|
|
|
|
|
|
upsdebugx(1, "entering parse_data_file()");
|
|
|
|
|
2011-01-26 09:35:08 +00:00
|
|
|
if (now < next_update)
|
|
|
|
{
|
2010-03-25 23:20:59 +00:00
|
|
|
upsdebugx(1, "leaving (paused)...");
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2011-01-26 09:35:08 +00:00
|
|
|
/* initialise everything, to loop back at the beginning of the file */
|
|
|
|
if (ctx == NULL)
|
|
|
|
{
|
2010-03-25 23:20:59 +00:00
|
|
|
ctx = (PCONF_CTX_t *)xmalloc(sizeof(PCONF_CTX_t));
|
|
|
|
|
|
|
|
if (device_path[0] == '/')
|
|
|
|
snprintf(fn, sizeof(fn), "%s", device_path);
|
|
|
|
else
|
|
|
|
snprintf(fn, sizeof(fn), "%s/%s", confpath(), device_path);
|
|
|
|
|
|
|
|
pconf_init(ctx, upsconf_err);
|
|
|
|
|
|
|
|
if (!pconf_file_begin(ctx, fn))
|
|
|
|
fatalx(EXIT_FAILURE, "Can't open dummy-ups definition file %s: %s",
|
|
|
|
fn, ctx->errmsg);
|
|
|
|
}
|
|
|
|
|
2011-01-26 09:35:08 +00:00
|
|
|
/* Reset the next call time, so that we can loop back on the file
|
2010-03-25 23:20:59 +00:00
|
|
|
* if there is no blocking action (ie TIMER) until the end of the file */
|
|
|
|
next_update = -1;
|
|
|
|
|
2011-01-26 09:35:08 +00:00
|
|
|
/* Now start or continue parsing... */
|
|
|
|
while (pconf_file_next(ctx))
|
|
|
|
{
|
|
|
|
if (pconf_parse_error(ctx))
|
|
|
|
{
|
2010-03-25 23:20:59 +00:00
|
|
|
upsdebugx(2, "Parse error: %s:%d: %s",
|
|
|
|
fn, ctx->linenum, ctx->errmsg);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check if we have something to process */
|
|
|
|
if (ctx->numargs < 1)
|
|
|
|
continue;
|
|
|
|
|
2011-01-26 09:35:08 +00:00
|
|
|
/* Process actions (only "TIMER" ATM) */
|
|
|
|
if (!strncmp(ctx->arglist[0], "TIMER", 5))
|
|
|
|
{
|
2010-03-25 23:20:59 +00:00
|
|
|
/* TIMER <seconds> will wait "seconds" before
|
|
|
|
* continuing the parsing */
|
|
|
|
int delay = atoi (ctx->arglist[1]);
|
|
|
|
time(&next_update);
|
|
|
|
next_update += delay;
|
|
|
|
upsdebugx(1, "suspending execution for %i seconds...", delay);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-01-26 09:35:08 +00:00
|
|
|
/* Remove ":" suffix, after the variable name */
|
2010-03-25 23:20:59 +00:00
|
|
|
if ((ptr = strchr(ctx->arglist[0], ':')) != NULL)
|
|
|
|
*ptr = '\0';
|
|
|
|
|
2011-01-26 09:35:08 +00:00
|
|
|
upsdebugx(3, "parse_data_file: variable \"%s\" with %d args",
|
|
|
|
ctx->arglist[0], (int)ctx->numargs);
|
2010-03-25 23:20:59 +00:00
|
|
|
|
2011-01-26 09:35:08 +00:00
|
|
|
/* Skip the driver.* collection data */
|
|
|
|
if (!strncmp(ctx->arglist[0], "driver.", 7))
|
|
|
|
{
|
2010-03-25 23:20:59 +00:00
|
|
|
upsdebugx(2, "parse_data_file: skipping %s", ctx->arglist[0]);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* From there, we get varname in arg[0], and values in other arg[1...x] */
|
|
|
|
/* special handler for status */
|
2011-01-26 09:35:08 +00:00
|
|
|
if (!strncmp( ctx->arglist[0], "ups.status", 10))
|
|
|
|
{
|
2010-03-25 23:20:59 +00:00
|
|
|
status_init();
|
2011-01-26 09:35:08 +00:00
|
|
|
for (counter = 1, value_args = ctx->numargs ;
|
|
|
|
counter < value_args ; counter++)
|
|
|
|
{
|
|
|
|
status_set(ctx->arglist[counter]);
|
|
|
|
}
|
2010-03-25 23:20:59 +00:00
|
|
|
status_commit();
|
|
|
|
}
|
2011-01-26 09:35:08 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
for (counter = 1, value_args = ctx->numargs ;
|
|
|
|
counter < value_args ; counter++)
|
|
|
|
{
|
2011-06-01 20:31:49 +00:00
|
|
|
if (counter == 1) /* don't append the first space separator */
|
|
|
|
snprintf(var_value, sizeof(var_value), "%s", ctx->arglist[counter]);
|
|
|
|
else
|
|
|
|
snprintfcat(var_value, sizeof(var_value), " %s", ctx->arglist[counter]);
|
2011-01-26 09:35:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (setvar(ctx->arglist[0], var_value) == STAT_SET_UNKNOWN)
|
|
|
|
{
|
2010-03-25 23:20:59 +00:00
|
|
|
upsdebugx(2, "parse_data_file: can't add \"%s\" with value \"%s\"\nError: %s",
|
|
|
|
ctx->arglist[0], var_value, ctx->errmsg);
|
|
|
|
}
|
2011-01-26 09:35:08 +00:00
|
|
|
else
|
2022-06-29 10:37:36 +00:00
|
|
|
{
|
2010-03-25 23:20:59 +00:00
|
|
|
upsdebugx(3, "parse_data_file: added \"%s\" with value \"%s\"",
|
|
|
|
ctx->arglist[0], var_value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-01-26 09:35:08 +00:00
|
|
|
/* Cleanup parseconf if there is no pending action */
|
|
|
|
if (next_update == -1)
|
|
|
|
{
|
2010-03-25 23:20:59 +00:00
|
|
|
pconf_finish(ctx);
|
|
|
|
free(ctx);
|
|
|
|
ctx=NULL;
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|