1 /* dummy-ups.c - NUT simulation and device repeater driver
4 2005 - 2010 Arnaud Quette <http://arnaud.quette.free.fr/contact.html>
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 * - separate the code between dummy and repeater/meta
23 * - for repeater/meta:
24 * * add support for instant commands and setvar
26 * * variable/value enforcement using cmdvartab for testing
27 * the variable existance, and possible values
28 * * allow variable creation on the fly (using upsrw)
29 * * poll the "port" file for change
33 #include <netinet/in.h>
34 #include <sys/socket.h>
38 #include "parseconf.h"
39 #include "upsclient.h"
40 #include "dummy-ups.h"
42 #define DRIVER_NAME "Device simulation and repeater driver"
43 #define DRIVER_VERSION "0.13"
45 /* driver description structure */
46 upsdrv_info_t upsdrv_info
=
50 "Arnaud Quette <arnaud.quette@gmail.com>",
56 #define MODE_DUMMY 1 /* use the embedded defintion or a definition file */
57 #define MODE_REPEATER 2 /* use libupsclient to repeat an UPS */
58 #define MODE_META 3 /* consolidate data from several UPSs (TBS) */
62 /* parseconf context, for dummy mode using a file */
63 PCONF_CTX_t
*ctx
=NULL
;
64 time_t next_update
= -1;
66 #define MAX_STRING_SIZE 128
68 static int setvar(const char *varname
, const char *val
);
69 static int instcmd(const char *cmdname
, const char *extra
);
70 static int parse_data_file(int upsfd
);
71 static dummy_info_t
*find_info(const char *varname
);
72 static int is_valid_data(const char* varname
);
73 static int is_valid_value(const char* varname
, const char *value
);
74 /* libupsclient update */
75 static int upsclient_update_vars(void);
77 /* connection information */
78 static char *client_upsname
= NULL
, *hostname
= NULL
;
79 static UPSCONN_t
*ups
= NULL
;
82 /* Driver functions */
84 void upsdrv_initinfo(void)
91 /* Initialise basic essential variables */
92 for ( item
= nut_data
; item
->info_type
!= NULL
; item
++ )
94 if (item
->drv_flags
& DU_FLAG_INIT
)
96 dstate_setinfo(item
->info_type
, "%s", item
->default_value
);
97 dstate_setflags(item
->info_type
, item
->info_flags
);
99 /* Set max length for strings, if needed */
100 if (item
->info_flags
& ST_FLAG_STRING
)
101 dstate_setaux(item
->info_type
, item
->info_len
);
105 /* Now get user's defined variables */
106 if (parse_data_file(upsfd
) < 0)
107 upslogx(LOG_NOTICE
, "Unable to parse the definition file %s", device_path
);
109 /* Initialize handler */
110 upsh
.setvar
= setvar
;
116 /* Obtain the target name */
117 if (upscli_splitname(device_path
, &client_upsname
, &hostname
, &port
) != 0)
119 fatalx(EXIT_FAILURE
, "Error: invalid UPS definition.\nRequired format: upsname[@hostname[:port]]");
121 /* Connect to the target */
122 ups
= xmalloc(sizeof(*ups
));
123 if (upscli_connect(ups
, hostname
, port
, UPSCLI_CONN_TRYSSL
) < 0)
125 fatalx(EXIT_FAILURE
, "Error: %s", upscli_strerror(ups
));
129 upsdebugx(1, "Connected to %s@%s", client_upsname
, hostname
);
131 if (upsclient_update_vars() < 0)
133 /* check for an old upsd */
134 if (upscli_upserror(ups
) == UPSCLI_ERR_UNKCOMMAND
)
136 fatalx(EXIT_FAILURE
, "Error: upsd is too old to support this query");
138 fatalx(EXIT_FAILURE
, "Error: %s", upscli_strerror(ups
));
140 /* FIXME: commands and settable variable! */
144 fatalx(EXIT_FAILURE
, "no suitable definition found!");
147 upsh
.instcmd
= instcmd
;
149 dstate_addcmd("load.off");
152 void upsdrv_updateinfo(void)
154 upsdebugx(1, "upsdrv_updateinfo...");
161 /* Now get user's defined variables */
162 if (parse_data_file(upsfd
) >= 0)
167 if (upsclient_update_vars() > 0)
173 /* try to reconnect */
174 upscli_disconnect(ups
);
175 if (upscli_connect(ups
, hostname
, port
, UPSCLI_CONN_TRYSSL
) < 0)
177 upsdebugx(1, "Error reconnecting: %s", upscli_strerror(ups
));
181 upsdebugx(1, "Reconnected");
191 void upsdrv_shutdown(void)
193 fatalx(EXIT_FAILURE
, "shutdown not supported");
196 static int instcmd(const char *cmdname
, const char *extra
)
199 if (!strcasecmp(cmdname, "test.battery.stop")) {
200 ser_send_buf(upsfd, ...);
201 return STAT_INSTCMD_HANDLED;
204 /* FIXME: the below is only valid if (mode == MODE_DUMMY)
205 * if (mode == MODE_REPEATER) => forward
206 * if (mode == MODE_META) => ?
209 upslogx(LOG_NOTICE
, "instcmd: unknown command [%s]", cmdname
);
210 return STAT_INSTCMD_UNKNOWN
;
213 void upsdrv_help(void)
217 void upsdrv_makevartable(void)
221 void upsdrv_initups(void)
223 /* check the running mode... */
224 if (strchr(device_path
, '@'))
226 upsdebugx(1, "Repeater mode");
227 mode
= MODE_REPEATER
;
228 dstate_setinfo("driver.parameter.mode", "repeater");
229 /* FIXME: if there is at least one more => MODE_META... */
233 upsdebugx(1, "Dummy (simulation) mode");
235 dstate_setinfo("driver.parameter.mode", "dummy");
239 void upsdrv_cleanup(void)
241 if ( (mode
== MODE_META
) || (mode
== MODE_REPEATER
) )
245 upscli_disconnect(ups
);
254 free(client_upsname
);
260 static int setvar(const char *varname
, const char *val
)
264 upsdebugx(2, "entering setvar(%s, %s)", varname
, val
);
266 /* FIXME: the below is only valid if (mode == MODE_DUMMY)
267 * if (mode == MODE_REPEATER) => forward
268 * if (mode == MODE_META) => ?
270 if (!strncmp(varname
, "ups.status", 10))
273 /* FIXME: split and check values (support multiple values), Ã la usbhid-ups */
277 return STAT_SET_HANDLED
;
280 /* Check variable validity */
281 if (!is_valid_data(varname
))
283 upsdebugx(2, "setvar: invalid variable name (%s)", varname
);
284 return STAT_SET_UNKNOWN
;
287 /* Check value validity */
288 if (!is_valid_value(varname
, val
))
290 upsdebugx(2, "setvar: invalid value (%s) for variable (%s)", val
, varname
);
291 return STAT_SET_UNKNOWN
;
294 /* If value is empty, remove the variable (FIXME: do we need
296 if (strlen(val
) == 0)
298 dstate_delinfo(varname
);
302 dstate_setinfo(varname
, "%s", val
);
304 if ( (item
= find_info(varname
)) != NULL
)
306 dstate_setflags(item
->info_type
, item
->info_flags
);
308 /* Set max length for strings, if needed */
309 if (item
->info_flags
& ST_FLAG_STRING
)
310 dstate_setaux(item
->info_type
, item
->info_len
);
313 return STAT_SET_HANDLED
;
316 /*************************************************/
317 /* Support functions */
318 /*************************************************/
320 static int upsclient_update_vars(void)
323 unsigned int numq
, numa
;
324 const char *query
[4];
328 query
[1] = client_upsname
;
331 ret
= upscli_list_start(ups
, numq
, query
);
335 upsdebugx(1, "Error: %s (%i)", upscli_strerror(ups
), upscli_upserror(ups
));
339 while (upscli_list_next(ups
, numq
, query
, &numa
, &answer
) == 1)
341 /* VAR <upsname> <varname> <val> */
344 upsdebugx(1, "Error: insufficient data (got %d args, need at least 4)", numa
);
347 upsdebugx(5, "Received: %s %s %s %s",
348 answer
[0], answer
[1], answer
[2], answer
[3]);
350 /* do not override the driver collection */
351 if (strncmp(answer
[2], "driver.", 7))
352 setvar(answer
[2], answer
[3]);
357 /* find info element definition in info array */
358 static dummy_info_t
*find_info(const char *varname
)
362 for ( item
= nut_data
; item
->info_type
!= NULL
; item
++ )
364 if (!strcasecmp(item
->info_type
, varname
))
368 upsdebugx(2, "find_info: unknown variable: %s\n", varname
);
373 /* check if data exists in our data table */
374 static int is_valid_data(const char* varname
)
378 if ( (item
= find_info(varname
)) != NULL
)
383 /* FIXME: we need to have the full data set before
384 * enforcing controls! We also need a way to automate
385 * the update / sync process (with cmdvartab?!) */
387 upsdebugx(1, "Unknown data. Committing anyway...");
392 /* check if data's value validity */
393 static int is_valid_value(const char* varname
, const char *value
)
397 if ( (item
= find_info(varname
)) != NULL
)
399 /* FIXME: test enum or bound against value */
403 /* FIXME: we need to have the full data set before
404 * enforcing controls! We also need a way to automate
405 * the update / sync process (with cmdvartab?) */
407 upsdebugx(1, "Unknown data. Committing value anyway...");
412 /* called for fatal errors in parseconf like malloc failures */
413 static void upsconf_err(const char *errmsg
)
415 upslogx(LOG_ERR
, "Fatal error in parseconf(ups.conf): %s", errmsg
);
419 * parse the definition file and process its content
421 static int parse_data_file(int upsfd
)
424 char *ptr
, var_value
[MAX_STRING_SIZE
];
425 int value_args
= 0, counter
;
430 upsdebugx(1, "entering parse_data_file()");
432 if (now
< next_update
)
434 upsdebugx(1, "leaving (paused)...");
438 /* initialise everything, to loop back at the beginning of the file */
441 ctx
= (PCONF_CTX_t
*)xmalloc(sizeof(PCONF_CTX_t
));
443 if (device_path
[0] == '/')
444 snprintf(fn
, sizeof(fn
), "%s", device_path
);
446 snprintf(fn
, sizeof(fn
), "%s/%s", confpath(), device_path
);
448 pconf_init(ctx
, upsconf_err
);
450 if (!pconf_file_begin(ctx
, fn
))
451 fatalx(EXIT_FAILURE
, "Can't open dummy-ups definition file %s: %s",
455 /* Reset the next call time, so that we can loop back on the file
456 * if there is no blocking action (ie TIMER) until the end of the file */
459 /* Now start or continue parsing... */
460 while (pconf_file_next(ctx
))
462 if (pconf_parse_error(ctx
))
464 upsdebugx(2, "Parse error: %s:%d: %s",
465 fn
, ctx
->linenum
, ctx
->errmsg
);
469 /* Check if we have something to process */
470 if (ctx
->numargs
< 1)
473 /* Process actions (only "TIMER" ATM) */
474 if (!strncmp(ctx
->arglist
[0], "TIMER", 5))
476 /* TIMER <seconds> will wait "seconds" before
477 * continuing the parsing */
478 int delay
= atoi (ctx
->arglist
[1]);
480 next_update
+= delay
;
481 upsdebugx(1, "suspending execution for %i seconds...", delay
);
485 /* Remove ":" suffix, after the variable name */
486 if ((ptr
= strchr(ctx
->arglist
[0], ':')) != NULL
)
489 upsdebugx(3, "parse_data_file: variable \"%s\" with %d args",
490 ctx
->arglist
[0], (int)ctx
->numargs
);
492 /* Skip the driver.* collection data */
493 if (!strncmp(ctx
->arglist
[0], "driver.", 7))
495 upsdebugx(2, "parse_data_file: skipping %s", ctx
->arglist
[0]);
499 /* From there, we get varname in arg[0], and values in other arg[1...x] */
500 /* special handler for status */
501 if (!strncmp( ctx
->arglist
[0], "ups.status", 10))
504 for (counter
= 1, value_args
= ctx
->numargs
;
505 counter
< value_args
; counter
++)
507 status_set(ctx
->arglist
[counter
]);
513 for (counter
= 1, value_args
= ctx
->numargs
;
514 counter
< value_args
; counter
++)
516 if (counter
== 1) /* don't append the first space separator */
517 snprintf(var_value
, sizeof(var_value
), "%s", ctx
->arglist
[counter
]);
519 snprintfcat(var_value
, sizeof(var_value
), " %s", ctx
->arglist
[counter
]);
522 if (setvar(ctx
->arglist
[0], var_value
) == STAT_SET_UNKNOWN
)
524 upsdebugx(2, "parse_data_file: can't add \"%s\" with value \"%s\"\nError: %s",
525 ctx
->arglist
[0], var_value
, ctx
->errmsg
);
529 upsdebugx(3, "parse_data_file: added \"%s\" with value \"%s\"",
530 ctx
->arglist
[0], var_value
);
535 /* Cleanup parseconf if there is no pending action */
536 if (next_update
== -1)