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