apcupsd-ups: ignore generated files
[networkupstools/kirr.git] / drivers / dummy-ups.c
blob7dbb9910d22b3ba2594b1978d90ecc1084f3ef40
1 /* dummy-ups.c - NUT simulation and device repeater driver
3 Copyright (C)
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
21 /* TODO list:
22 * - separate the code between dummy and repeater/meta
23 * - for repeater/meta:
24 * * add support for instant commands and setvar
25 * - for dummy:
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
32 #include <netdb.h>
33 #include <netinet/in.h>
34 #include <sys/socket.h>
35 #include <string.h>
37 #include "main.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 =
48 DRIVER_NAME,
49 DRIVER_VERSION,
50 "Arnaud Quette <arnaud.quette@gmail.com>",
51 DRV_STABLE,
52 { NULL }
55 #define MODE_NONE 0
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) */
60 int mode=MODE_NONE;
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;
80 static int port;
82 /* Driver functions */
84 void upsdrv_initinfo(void)
86 dummy_info_t *item;
88 switch (mode)
90 case MODE_DUMMY:
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;
112 dstate_dataok();
113 break;
114 case MODE_META:
115 case MODE_REPEATER:
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));
127 else
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! */
141 break;
142 default:
143 case MODE_NONE:
144 fatalx(EXIT_FAILURE, "no suitable definition found!");
145 break;
147 upsh.instcmd = instcmd;
149 dstate_addcmd("load.off");
152 void upsdrv_updateinfo(void)
154 upsdebugx(1, "upsdrv_updateinfo...");
156 sleep(1);
158 switch (mode)
160 case MODE_DUMMY:
161 /* Now get user's defined variables */
162 if (parse_data_file(upsfd) >= 0)
163 dstate_dataok();
164 break;
165 case MODE_META:
166 case MODE_REPEATER:
167 if (upsclient_update_vars() > 0)
169 dstate_dataok();
171 else
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));
179 else
181 upsdebugx(1, "Reconnected");
184 break;
185 case MODE_NONE:
186 default:
187 break;
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... */
231 else
233 upsdebugx(1, "Dummy (simulation) mode");
234 mode = MODE_DUMMY;
235 dstate_setinfo("driver.parameter.mode", "dummy");
239 void upsdrv_cleanup(void)
241 if ( (mode == MODE_META) || (mode == MODE_REPEATER) )
243 if (ups)
245 upscli_disconnect(ups);
248 if (ctx)
250 pconf_finish(ctx);
251 free(ctx);
254 free(client_upsname);
255 free(hostname);
256 free(ups);
260 static int setvar(const char *varname, const char *val)
262 dummy_info_t *item;
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))
272 status_init();
273 /* FIXME: split and check values (support multiple values), à la usbhid-ups */
274 status_set(val);
275 status_commit();
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
295 * a magic word?) */
296 if (strlen(val) == 0)
298 dstate_delinfo(varname);
300 else
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)
322 int ret;
323 unsigned int numq, numa;
324 const char *query[4];
325 char **answer;
327 query[0] = "VAR";
328 query[1] = client_upsname;
329 numq = 2;
331 ret = upscli_list_start(ups, numq, query);
333 if (ret < 0)
335 upsdebugx(1, "Error: %s (%i)", upscli_strerror(ups), upscli_upserror(ups));
336 return ret;
339 while (upscli_list_next(ups, numq, query, &numa, &answer) == 1)
341 /* VAR <upsname> <varname> <val> */
342 if (numa < 4)
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]);
354 return 1;
357 /* find info element definition in info array */
358 static dummy_info_t *find_info(const char *varname)
360 dummy_info_t *item;
362 for ( item = nut_data ; item->info_type != NULL ; item++ )
364 if (!strcasecmp(item->info_type, varname))
365 return item;
368 upsdebugx(2, "find_info: unknown variable: %s\n", varname);
370 return NULL;
373 /* check if data exists in our data table */
374 static int is_valid_data(const char* varname)
376 dummy_info_t *item;
378 if ( (item = find_info(varname)) != NULL)
380 return 1;
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...");
388 return 1;
389 /* return 0;*/
392 /* check if data's value validity */
393 static int is_valid_value(const char* varname, const char *value)
395 dummy_info_t *item;
397 if ( (item = find_info(varname)) != NULL)
399 /* FIXME: test enum or bound against value */
400 return 1;
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...");
408 return 1;
409 /* return 0;*/
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);
418 /* for dummy mode
419 * parse the definition file and process its content
421 static int parse_data_file(int upsfd)
423 char fn[SMALLBUF];
424 char *ptr, var_value[MAX_STRING_SIZE];
425 int value_args = 0, counter;
426 time_t now;
428 time(&now);
430 upsdebugx(1, "entering parse_data_file()");
432 if (now < next_update)
434 upsdebugx(1, "leaving (paused)...");
435 return 1;
438 /* initialise everything, to loop back at the beginning of the file */
439 if (ctx == NULL)
441 ctx = (PCONF_CTX_t *)xmalloc(sizeof(PCONF_CTX_t));
443 if (device_path[0] == '/')
444 snprintf(fn, sizeof(fn), "%s", device_path);
445 else
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",
452 fn, ctx->errmsg);
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 */
457 next_update = -1;
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);
466 continue;
469 /* Check if we have something to process */
470 if (ctx->numargs < 1)
471 continue;
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]);
479 time(&next_update);
480 next_update += delay;
481 upsdebugx(1, "suspending execution for %i seconds...", delay);
482 break;
485 /* Remove ":" suffix, after the variable name */
486 if ((ptr = strchr(ctx->arglist[0], ':')) != NULL)
487 *ptr = '\0';
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]);
496 continue;
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))
503 status_init();
504 for (counter = 1, value_args = ctx->numargs ;
505 counter < value_args ; counter++)
507 status_set(ctx->arglist[counter]);
509 status_commit();
511 else
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]);
518 else
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);
527 else
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)
538 pconf_finish(ctx);
539 free(ctx);
540 ctx=NULL;
542 return 1;