1 /* dummy-ups.c - NUT simulation and device repeater driver
2
3 Copyright (C)
4 2005 - 2015 Arnaud Quette <http://arnaud.quette.free.fr/contact.html>
5
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.
10
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.
15
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
19 */
20
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
30 */
31
32 #include <netdb.h>
33 #include <netinet/in.h>
34 #include <sys/socket.h>
35 #include <string.h>
36
37 #include "main.h"
38 #include "parseconf.h"
39 #include "upsclient.h"
40 #include "dummy-ups.h"
41
42 #define DRIVER_NAME "Device simulation and repeater driver"
43 #define DRIVER_VERSION "0.14"
44
45 /* driver description structure */
46 upsdrv_info_t upsdrv_info =
47 {
48 DRIVER_NAME,
49 DRIVER_VERSION,
50 "Arnaud Quette <arnaud.quette@gmail.com>",
51 DRV_STABLE,
52 { NULL }
53 };
54
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) */
59
60 int mode=MODE_NONE;
61
62 /* parseconf context, for dummy mode using a file */
63 PCONF_CTX_t *ctx=NULL;
64 time_t next_update = -1;
65
66 #define MAX_STRING_SIZE 128
67
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);
76
77 /* connection information */
78 static char *client_upsname = NULL, *hostname = NULL;
79 static UPSCONN_t *ups = NULL;
80 static int port;
81
82 /* Driver functions */
83
upsdrv_initinfo(void)84 void upsdrv_initinfo(void)
85 {
86 dummy_info_t *item;
87
88 switch (mode)
89 {
90 case MODE_DUMMY:
91 /* Initialise basic essential variables */
92 for ( item = nut_data ; item->info_type != NULL ; item++ )
93 {
94 if (item->drv_flags & DU_FLAG_INIT)
95 {
96 dstate_setinfo(item->info_type, "%s", item->default_value);
97 dstate_setflags(item->info_type, item->info_flags);
98
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);
102 }
103 }
104
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);
108
109 /* Initialize handler */
110 upsh.setvar = setvar;
111
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)
118 {
119 fatalx(EXIT_FAILURE, "Error: invalid UPS definition.\nRequired format: upsname[@hostname[:port]]");
120 }
121 /* Connect to the target */
122 ups = xmalloc(sizeof(*ups));
123 if (upscli_connect(ups, hostname, port, UPSCLI_CONN_TRYSSL) < 0)
124 {
125 fatalx(EXIT_FAILURE, "Error: %s", upscli_strerror(ups));
126 }
127 else
128 {
129 upsdebugx(1, "Connected to %s@%s", client_upsname, hostname);
130 }
131 if (upsclient_update_vars() < 0)
132 {
133 /* check for an old upsd */
134 if (upscli_upserror(ups) == UPSCLI_ERR_UNKCOMMAND)
135 {
136 fatalx(EXIT_FAILURE, "Error: upsd is too old to support this query");
137 }
138 fatalx(EXIT_FAILURE, "Error: %s", upscli_strerror(ups));
139 }
140 /* FIXME: commands and settable variable! */
141 break;
142 default:
143 case MODE_NONE:
144 fatalx(EXIT_FAILURE, "no suitable definition found!");
145 break;
146 }
147 upsh.instcmd = instcmd;
148
149 dstate_addcmd("load.off");
150 }
151
upsdrv_updateinfo(void)152 void upsdrv_updateinfo(void)
153 {
154 upsdebugx(1, "upsdrv_updateinfo...");
155
156 sleep(1);
157
158 switch (mode)
159 {
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)
168 {
169 dstate_dataok();
170 }
171 else
172 {
173 /* try to reconnect */
174 upscli_disconnect(ups);
175 if (upscli_connect(ups, hostname, port, UPSCLI_CONN_TRYSSL) < 0)
176 {
177 upsdebugx(1, "Error reconnecting: %s", upscli_strerror(ups));
178 }
179 else
180 {
181 upsdebugx(1, "Reconnected");
182 }
183 }
184 break;
185 case MODE_NONE:
186 default:
187 break;
188 }
189 }
190
upsdrv_shutdown(void)191 void upsdrv_shutdown(void)
192 {
193 fatalx(EXIT_FAILURE, "shutdown not supported");
194 }
195
instcmd(const char * cmdname,const char * extra)196 static int instcmd(const char *cmdname, const char *extra)
197 {
198 /*
199 if (!strcasecmp(cmdname, "test.battery.stop")) {
200 ser_send_buf(upsfd, ...);
201 return STAT_INSTCMD_HANDLED;
202 }
203 */
204 /* FIXME: the below is only valid if (mode == MODE_DUMMY)
205 * if (mode == MODE_REPEATER) => forward
206 * if (mode == MODE_META) => ?
207 */
208
209 upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname);
210 return STAT_INSTCMD_UNKNOWN;
211 }
212
upsdrv_help(void)213 void upsdrv_help(void)
214 {
215 }
216
upsdrv_makevartable(void)217 void upsdrv_makevartable(void)
218 {
219 }
220
upsdrv_initups(void)221 void upsdrv_initups(void)
222 {
223 /* check the running mode... */
224 if (strchr(device_path, '@'))
225 {
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... */
230 }
231 else
232 {
233 upsdebugx(1, "Dummy (simulation) mode");
234 mode = MODE_DUMMY;
235 dstate_setinfo("driver.parameter.mode", "dummy");
236 }
237 }
238
upsdrv_cleanup(void)239 void upsdrv_cleanup(void)
240 {
241 if ( (mode == MODE_META) || (mode == MODE_REPEATER) )
242 {
243 if (ups)
244 {
245 upscli_disconnect(ups);
246 }
247
248 if (ctx)
249 {
250 pconf_finish(ctx);
251 free(ctx);
252 }
253
254 free(client_upsname);
255 free(hostname);
256 free(ups);
257 }
258 }
259
setvar(const char * varname,const char * val)260 static int setvar(const char *varname, const char *val)
261 {
262 dummy_info_t *item;
263
264 upsdebugx(2, "entering setvar(%s, %s)", varname, val);
265
266 /* FIXME: the below is only valid if (mode == MODE_DUMMY)
267 * if (mode == MODE_REPEATER) => forward
268 * if (mode == MODE_META) => ?
269 */
270 if (!strncmp(varname, "ups.status", 10))
271 {
272 status_init();
273 /* FIXME: split and check values (support multiple values), à la usbhid-ups */
274 status_set(val);
275 status_commit();
276
277 return STAT_SET_HANDLED;
278 }
279
280 /* Check variable validity */
281 if (!is_valid_data(varname))
282 {
283 upsdebugx(2, "setvar: invalid variable name (%s)", varname);
284 return STAT_SET_UNKNOWN;
285 }
286
287 /* Check value validity */
288 if (!is_valid_value(varname, val))
289 {
290 upsdebugx(2, "setvar: invalid value (%s) for variable (%s)", val, varname);
291 return STAT_SET_UNKNOWN;
292 }
293
294 /* If value is empty, remove the variable (FIXME: do we need
295 * a magic word?) */
296 if (strlen(val) == 0)
297 {
298 dstate_delinfo(varname);
299 }
300 else
301 {
302 dstate_setinfo(varname, "%s", val);
303
304 if ( (item = find_info(varname)) != NULL)
305 {
306 dstate_setflags(item->info_type, item->info_flags);
307
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);
311 }
312 else
313 {
314 upsdebugx(2, "setvar: unknown variable (%s), using default flags", varname);
315 dstate_setflags(varname, ST_FLAG_STRING | ST_FLAG_RW);
316 dstate_setaux(varname, 32);
317 }
318 }
319 return STAT_SET_HANDLED;
320 }
321
322 /*************************************************/
323 /* Support functions */
324 /*************************************************/
325
upsclient_update_vars(void)326 static int upsclient_update_vars(void)
327 {
328 int ret;
329 unsigned int numq, numa;
330 const char *query[4];
331 char **answer;
332
333 query[0] = "VAR";
334 query[1] = client_upsname;
335 numq = 2;
336
337 ret = upscli_list_start(ups, numq, query);
338
339 if (ret < 0)
340 {
341 upsdebugx(1, "Error: %s (%i)", upscli_strerror(ups), upscli_upserror(ups));
342 return ret;
343 }
344
345 while (upscli_list_next(ups, numq, query, &numa, &answer) == 1)
346 {
347 /* VAR <upsname> <varname> <val> */
348 if (numa < 4)
349 {
350 upsdebugx(1, "Error: insufficient data (got %d args, need at least 4)", numa);
351 }
352
353 upsdebugx(5, "Received: %s %s %s %s",
354 answer[0], answer[1], answer[2], answer[3]);
355
356 /* do not override the driver collection */
357 if (strncmp(answer[2], "driver.", 7))
358 setvar(answer[2], answer[3]);
359 }
360 return 1;
361 }
362
363 /* find info element definition in info array */
find_info(const char * varname)364 static dummy_info_t *find_info(const char *varname)
365 {
366 dummy_info_t *item;
367
368 for ( item = nut_data ; item->info_type != NULL ; item++ )
369 {
370 if (!strcasecmp(item->info_type, varname))
371 return item;
372 }
373
374 upsdebugx(2, "find_info: unknown variable: %s\n", varname);
375
376 return NULL;
377 }
378
379 /* check if data exists in our data table */
is_valid_data(const char * varname)380 static int is_valid_data(const char* varname)
381 {
382 dummy_info_t *item;
383
384 if ( (item = find_info(varname)) != NULL)
385 {
386 return 1;
387 }
388
389 /* FIXME: we need to have the full data set before
390 * enforcing controls! We also need a way to automate
391 * the update / sync process (with cmdvartab?!) */
392
393 upsdebugx(1, "Unknown data. Committing anyway...");
394 return 1;
395 /* return 0;*/
396 }
397
398 /* check if data's value validity */
is_valid_value(const char * varname,const char * value)399 static int is_valid_value(const char* varname, const char *value)
400 {
401 dummy_info_t *item;
402
403 if ( (item = find_info(varname)) != NULL)
404 {
405 /* FIXME: test enum or bound against value */
406 return 1;
407 }
408
409 /* FIXME: we need to have the full data set before
410 * enforcing controls! We also need a way to automate
411 * the update / sync process (with cmdvartab?) */
412
413 upsdebugx(1, "Unknown data. Committing value anyway...");
414 return 1;
415 /* return 0;*/
416 }
417
418 /* called for fatal errors in parseconf like malloc failures */
upsconf_err(const char * errmsg)419 static void upsconf_err(const char *errmsg)
420 {
421 upslogx(LOG_ERR, "Fatal error in parseconf(ups.conf): %s", errmsg);
422 }
423
424 /* for dummy mode
425 * parse the definition file and process its content
426 */
parse_data_file(int upsfd)427 static int parse_data_file(int upsfd)
428 {
429 char fn[SMALLBUF];
430 char *ptr, var_value[MAX_STRING_SIZE];
431 int value_args = 0, counter;
432 time_t now;
433
434 time(&now);
435
436 upsdebugx(1, "entering parse_data_file()");
437
438 if (now < next_update)
439 {
440 upsdebugx(1, "leaving (paused)...");
441 return 1;
442 }
443
444 /* initialise everything, to loop back at the beginning of the file */
445 if (ctx == NULL)
446 {
447 ctx = (PCONF_CTX_t *)xmalloc(sizeof(PCONF_CTX_t));
448
449 if (device_path[0] == '/')
450 snprintf(fn, sizeof(fn), "%s", device_path);
451 else
452 snprintf(fn, sizeof(fn), "%s/%s", confpath(), device_path);
453
454 pconf_init(ctx, upsconf_err);
455
456 if (!pconf_file_begin(ctx, fn))
457 fatalx(EXIT_FAILURE, "Can't open dummy-ups definition file %s: %s",
458 fn, ctx->errmsg);
459 }
460
461 /* Reset the next call time, so that we can loop back on the file
462 * if there is no blocking action (ie TIMER) until the end of the file */
463 next_update = -1;
464
465 /* Now start or continue parsing... */
466 while (pconf_file_next(ctx))
467 {
468 if (pconf_parse_error(ctx))
469 {
470 upsdebugx(2, "Parse error: %s:%d: %s",
471 fn, ctx->linenum, ctx->errmsg);
472 continue;
473 }
474
475 /* Check if we have something to process */
476 if (ctx->numargs < 1)
477 continue;
478
479 /* Process actions (only "TIMER" ATM) */
480 if (!strncmp(ctx->arglist[0], "TIMER", 5))
481 {
482 /* TIMER <seconds> will wait "seconds" before
483 * continuing the parsing */
484 int delay = atoi (ctx->arglist[1]);
485 time(&next_update);
486 next_update += delay;
487 upsdebugx(1, "suspending execution for %i seconds...", delay);
488 break;
489 }
490
491 /* Remove ":" suffix, after the variable name */
492 if ((ptr = strchr(ctx->arglist[0], ':')) != NULL)
493 *ptr = '\0';
494
495 upsdebugx(3, "parse_data_file: variable \"%s\" with %d args",
496 ctx->arglist[0], (int)ctx->numargs);
497
498 /* Skip the driver.* collection data */
499 if (!strncmp(ctx->arglist[0], "driver.", 7))
500 {
501 upsdebugx(2, "parse_data_file: skipping %s", ctx->arglist[0]);
502 continue;
503 }
504
505 /* From there, we get varname in arg[0], and values in other arg[1...x] */
506 /* special handler for status */
507 if (!strncmp( ctx->arglist[0], "ups.status", 10))
508 {
509 status_init();
510 for (counter = 1, value_args = ctx->numargs ;
511 counter < value_args ; counter++)
512 {
513 status_set(ctx->arglist[counter]);
514 }
515 status_commit();
516 }
517 else
518 {
519 for (counter = 1, value_args = ctx->numargs ;
520 counter < value_args ; counter++)
521 {
522 if (counter == 1) /* don't append the first space separator */
523 snprintf(var_value, sizeof(var_value), "%s", ctx->arglist[counter]);
524 else
525 snprintfcat(var_value, sizeof(var_value), " %s", ctx->arglist[counter]);
526 }
527
528 if (setvar(ctx->arglist[0], var_value) == STAT_SET_UNKNOWN)
529 {
530 upsdebugx(2, "parse_data_file: can't add \"%s\" with value \"%s\"\nError: %s",
531 ctx->arglist[0], var_value, ctx->errmsg);
532 }
533 else
534 {
535 upsdebugx(3, "parse_data_file: added \"%s\" with value \"%s\"",
536 ctx->arglist[0], var_value);
537 }
538 }
539 }
540
541 /* Cleanup parseconf if there is no pending action */
542 if (next_update == -1)
543 {
544 pconf_finish(ctx);
545 free(ctx);
546 ctx=NULL;
547 }
548 return 1;
549 }
550