GitHub issues can also be used to report HCL updates
[networkupstools/kirr.git] / drivers / main.c
blob7c2fc55f17768a86f2a4ce968ff83eb2eaf3d2b6
1 /* main.c - Network UPS Tools driver core
3 Copyright (C) 1999 Russell Kroll <rkroll@exploits.org>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 #include "main.h"
21 #include "dstate.h"
23 /* data which may be useful to the drivers */
24 int upsfd = -1;
25 char *device_path = NULL;
26 const char *progname = NULL, *upsname = NULL, *device_name = NULL;
28 /* may be set by the driver to wake up while in dstate_poll_fds */
29 int extrafd = -1;
31 /* for ser_open */
32 int do_lock_port = 1;
34 /* for detecting -a values that don't match anything */
35 static int upsname_found = 0;
37 static vartab_t *vartab_h = NULL;
39 /* variables possibly set by the global part of ups.conf */
40 unsigned int poll_interval = 2;
41 static char *chroot_path = NULL, *user = NULL;
43 /* signal handling */
44 int exit_flag = 0;
46 /* everything else */
47 static char *pidfn = NULL;
49 /* print the driver banner */
50 void upsdrv_banner (void)
52 int i;
54 printf("Network UPS Tools - %s %s (%s)\n", upsdrv_info.name, upsdrv_info.version, UPS_VERSION);
56 /* process sub driver(s) information */
57 for (i = 0; upsdrv_info.subdrv_info[i]; i++) {
59 if (!upsdrv_info.subdrv_info[i]->name) {
60 continue;
63 if (!upsdrv_info.subdrv_info[i]->version) {
64 continue;
67 printf("%s %s\n", upsdrv_info.subdrv_info[i]->name,
68 upsdrv_info.subdrv_info[i]->version);
72 /* power down the attached load immediately */
73 static void forceshutdown(void)
75 upslogx(LOG_NOTICE, "Initiating UPS shutdown");
77 /* the driver must not block in this function */
78 upsdrv_shutdown();
79 exit(EXIT_SUCCESS);
82 /* this function only prints the usage message; it does not call exit() */
83 static void help_msg(void)
85 vartab_t *tmp;
87 printf("\nusage: %s -a <id> [OPTIONS]\n", progname);
89 printf(" -a <id> - autoconfig using ups.conf section <id>\n");
90 printf(" - note: -x after -a overrides ups.conf settings\n\n");
92 printf(" -V - print version, then exit\n");
93 printf(" -L - print parseable list of driver variables\n");
94 printf(" -D - raise debugging level\n");
95 printf(" -q - raise log level threshold\n");
96 printf(" -h - display this help\n");
97 printf(" -k - force shutdown\n");
98 printf(" -i <int> - poll interval\n");
99 printf(" -r <dir> - chroot to <dir>\n");
100 printf(" -u <user> - switch to <user> (if started as root)\n");
101 printf(" -x <var>=<val> - set driver variable <var> to <val>\n");
102 printf(" - example: -x cable=940-0095B\n\n");
104 if (vartab_h) {
105 tmp = vartab_h;
107 printf("Acceptable values for -x or ups.conf in this driver:\n\n");
109 while (tmp) {
110 if (tmp->vartype == VAR_VALUE)
111 printf("%40s : -x %s=<value>\n",
112 tmp->desc, tmp->var);
113 else
114 printf("%40s : -x %s\n", tmp->desc, tmp->var);
115 tmp = tmp->next;
119 upsdrv_help();
122 /* store these in dstate as driver.(parameter|flag) */
123 static void dparam_setinfo(const char *var, const char *val)
125 char vtmp[SMALLBUF];
127 /* store these in dstate for debugging and other help */
128 if (val) {
129 snprintf(vtmp, sizeof(vtmp), "driver.parameter.%s", var);
130 dstate_setinfo(vtmp, "%s", val);
131 return;
134 /* no value = flag */
136 snprintf(vtmp, sizeof(vtmp), "driver.flag.%s", var);
137 dstate_setinfo(vtmp, "enabled");
140 /* cram var [= <val>] data into storage */
141 static void storeval(const char *var, char *val)
143 vartab_t *tmp, *last;
145 if (!strncasecmp(var, "override.", 9)) {
146 dstate_setinfo(var+9, "%s", val);
147 dstate_setflags(var+9, ST_FLAG_IMMUTABLE);
148 return;
151 if (!strncasecmp(var, "default.", 8)) {
152 dstate_setinfo(var+8, "%s", val);
153 return;
156 tmp = last = vartab_h;
158 while (tmp) {
159 last = tmp;
161 /* sanity check */
162 if (!tmp->var) {
163 tmp = tmp->next;
164 continue;
167 /* later definitions overwrite earlier ones */
168 if (!strcasecmp(tmp->var, var)) {
169 free(tmp->val);
171 if (val)
172 tmp->val = xstrdup(val);
174 /* don't keep things like SNMP community strings */
175 if ((tmp->vartype & VAR_SENSITIVE) == 0)
176 dparam_setinfo(var, val);
178 tmp->found = 1;
179 return;
182 tmp = tmp->next;
185 /* try to help them out */
186 printf("\nFatal error: '%s' is not a valid %s for this driver.\n", var,
187 val ? "variable name" : "flag");
188 printf("\n");
189 printf("Look in the man page or call this driver with -h for a list of\n");
190 printf("valid variable names and flags.\n");
192 exit(EXIT_SUCCESS);
195 /* retrieve the value of variable <var> if possible */
196 char *getval(const char *var)
198 vartab_t *tmp = vartab_h;
200 while (tmp) {
201 if (!strcasecmp(tmp->var, var))
202 return(tmp->val);
203 tmp = tmp->next;
206 return NULL;
209 /* see if <var> has been defined, even if no value has been given to it */
210 int testvar(const char *var)
212 vartab_t *tmp = vartab_h;
214 while (tmp) {
215 if (!strcasecmp(tmp->var, var))
216 return tmp->found;
217 tmp = tmp->next;
220 return 0; /* not found */
223 /* callback from driver - create the table for -x/conf entries */
224 void addvar(int vartype, const char *name, const char *desc)
226 vartab_t *tmp, *last;
228 tmp = last = vartab_h;
230 while (tmp) {
231 last = tmp;
232 tmp = tmp->next;
235 tmp = xmalloc(sizeof(vartab_t));
237 tmp->vartype = vartype;
238 tmp->var = xstrdup(name);
239 tmp->val = NULL;
240 tmp->desc = xstrdup(desc);
241 tmp->found = 0;
242 tmp->next = NULL;
244 if (last)
245 last->next = tmp;
246 else
247 vartab_h = tmp;
250 /* handle -x / ups.conf config details that are for this part of the code */
251 static int main_arg(char *var, char *val)
253 /* flags for main: just 'nolock' for now */
255 if (!strcmp(var, "nolock")) {
256 do_lock_port = 0;
257 dstate_setinfo("driver.flag.nolock", "enabled");
258 return 1; /* handled */
261 if (!strcmp(var, "ignorelb")) {
262 dstate_setinfo("driver.flag.ignorelb", "enabled");
263 return 1; /* handled */
266 /* any other flags are for the driver code */
267 if (!val)
268 return 0;
270 /* variables for main: port */
272 if (!strcmp(var, "port")) {
273 device_path = xstrdup(val);
274 device_name = xbasename(device_path);
275 dstate_setinfo("driver.parameter.port", "%s", val);
276 return 1; /* handled */
279 if (!strcmp(var, "sddelay")) {
280 upslogx(LOG_INFO, "Obsolete value sddelay found in ups.conf");
281 return 1; /* handled */
284 /* only for upsdrvctl - ignored here */
285 if (!strcmp(var, "sdorder"))
286 return 1; /* handled */
288 /* only for upsd (at the moment) - ignored here */
289 if (!strcmp(var, "desc"))
290 return 1; /* handled */
292 return 0; /* unhandled, pass it through to the driver */
295 static void do_global_args(const char *var, const char *val)
297 if (!strcmp(var, "pollinterval")) {
298 poll_interval = atoi(val);
299 return;
302 if (!strcmp(var, "chroot")) {
303 free(chroot_path);
304 chroot_path = xstrdup(val);
307 if (!strcmp(var, "user")) {
308 free(user);
309 user = xstrdup(val);
313 /* unrecognized */
316 void do_upsconf_args(char *confupsname, char *var, char *val)
318 char tmp[SMALLBUF];
320 /* handle global declarations */
321 if (!confupsname) {
322 do_global_args(var, val);
323 return;
326 /* no match = not for us */
327 if (strcmp(confupsname, upsname) != 0)
328 return;
330 upsname_found = 1;
332 if (main_arg(var, val))
333 return;
335 /* flags (no =) now get passed to the driver-level stuff */
336 if (!val) {
338 /* also store this, but it's a bit different */
339 snprintf(tmp, sizeof(tmp), "driver.flag.%s", var);
340 dstate_setinfo(tmp, "enabled");
342 storeval(var, NULL);
343 return;
346 /* don't let the user shoot themselves in the foot */
347 if (!strcmp(var, "driver")) {
348 if (strcmp(val, progname) != 0)
349 fatalx(EXIT_FAILURE, "Error: UPS [%s] is for driver %s, but I'm %s!\n",
350 confupsname, val, progname);
351 return;
354 /* allow per-driver overrides of the global setting */
355 if (!strcmp(var, "pollinterval")) {
356 poll_interval = atoi(val);
357 return;
360 /* everything else must be for the driver */
361 storeval(var, val);
364 /* split -x foo=bar into 'foo' and 'bar' */
365 static void splitxarg(char *inbuf)
367 char *eqptr, *val, *buf;
369 /* make our own copy - avoid changing argv */
370 buf = xstrdup(inbuf);
372 eqptr = strchr(buf, '=');
374 if (!eqptr)
375 val = NULL;
376 else {
377 *eqptr++ = '\0';
378 val = eqptr;
381 /* see if main handles this first */
382 if (main_arg(buf, val))
383 return;
385 /* otherwise store it for later */
386 storeval(buf, val);
389 /* dump the list from the vartable for external parsers */
390 static void listxarg(void)
392 vartab_t *tmp;
394 tmp = vartab_h;
396 if (!tmp)
397 return;
399 while (tmp) {
401 switch (tmp->vartype) {
402 case VAR_VALUE: printf("VALUE"); break;
403 case VAR_FLAG: printf("FLAG"); break;
404 default: printf("UNKNOWN"); break;
407 printf(" %s \"%s\"\n", tmp->var, tmp->desc);
409 tmp = tmp->next;
413 static void vartab_free(void)
415 vartab_t *tmp, *next;
417 tmp = vartab_h;
419 while (tmp) {
420 next = tmp->next;
422 free(tmp->var);
423 free(tmp->val);
424 free(tmp->desc);
425 free(tmp);
427 tmp = next;
431 static void exit_cleanup(void)
433 free(chroot_path);
434 free(device_path);
435 free(user);
437 if (pidfn) {
438 unlink(pidfn);
439 free(pidfn);
442 dstate_free();
443 vartab_free();
446 static void set_exit_flag(int sig)
448 exit_flag = sig;
451 static void setup_signals(void)
453 struct sigaction sa;
455 sigemptyset(&sa.sa_mask);
456 sa.sa_flags = 0;
458 sa.sa_handler = set_exit_flag;
459 sigaction(SIGTERM, &sa, NULL);
460 sigaction(SIGINT, &sa, NULL);
461 sigaction(SIGQUIT, &sa, NULL);
463 sa.sa_handler = SIG_IGN;
464 sigaction(SIGHUP, &sa, NULL);
465 sigaction(SIGPIPE, &sa, NULL);
468 int main(int argc, char **argv)
470 struct passwd *new_uid = NULL;
471 int i, do_forceshutdown = 0;
473 atexit(exit_cleanup);
475 /* pick up a default from configure --with-user */
476 user = xstrdup(RUN_AS_USER); /* xstrdup: this gets freed at exit */
478 progname = xbasename(argv[0]);
479 open_syslog(progname);
481 upsdrv_banner();
483 if (upsdrv_info.status == DRV_EXPERIMENTAL) {
484 printf("Warning: This is an experimental driver.\n");
485 printf("Some features may not function correctly.\n\n");
488 /* build the driver's extra (-x) variable table */
489 upsdrv_makevartable();
491 while ((i = getopt(argc, argv, "+a:kDhx:Lqr:u:Vi:")) != -1) {
492 switch (i) {
493 case 'a':
494 upsname = optarg;
496 read_upsconf();
498 if (!upsname_found)
499 fatalx(EXIT_FAILURE, "Error: Section %s not found in ups.conf",
500 optarg);
501 break;
502 case 'D':
503 nut_debug_level++;
504 break;
505 case 'i':
506 poll_interval = atoi(optarg);
507 break;
508 case 'k':
509 do_lock_port = 0;
510 do_forceshutdown = 1;
511 break;
512 case 'L':
513 listxarg();
514 exit(EXIT_SUCCESS);
515 case 'q':
516 nut_log_level++;
517 break;
518 case 'r':
519 chroot_path = xstrdup(optarg);
520 break;
521 case 'u':
522 user = xstrdup(optarg);
523 break;
524 case 'V':
525 /* already printed the banner, so exit */
526 exit(EXIT_SUCCESS);
527 case 'x':
528 splitxarg(optarg);
529 break;
530 case 'h':
531 help_msg();
532 exit(EXIT_SUCCESS);
533 default:
534 fatalx(EXIT_FAILURE,
535 "Error: unknown option -%c. Try -h for help.", i);
539 argc -= optind;
540 argv += optind;
542 if (argc > 0) {
543 fatalx(EXIT_FAILURE,
544 "Error: too many non-option arguments. Try -h for help.");
547 if (!upsname_found) {
548 fatalx(EXIT_FAILURE,
549 "Error: specifying '-a id' is now mandatory. Try -h for help.");
552 /* we need to get the port from somewhere */
553 if (!device_path) {
554 fatalx(EXIT_FAILURE,
555 "Error: you must specify a port name in ups.conf. Try -h for help.");
558 upsdebugx(1, "debug level is '%d'", nut_debug_level);
560 new_uid = get_user_pwent(user);
562 if (chroot_path)
563 chroot_start(chroot_path);
565 become_user(new_uid);
567 /* Only switch to statepath if we're not powering off */
568 /* This avoid case where ie /var is umounted */
569 if ((!do_forceshutdown) && (chdir(dflt_statepath())))
570 fatal_with_errno(EXIT_FAILURE, "Can't chdir to %s", dflt_statepath());
572 /* Setup signals to communicate with driver once backgrounded. */
573 if ((nut_debug_level == 0) && (!do_forceshutdown)) {
574 char buffer[SMALLBUF];
576 setup_signals();
578 snprintf(buffer, sizeof(buffer), "%s/%s-%s.pid", altpidpath(), progname, upsname);
580 /* Try to prevent that driver is started multiple times. If a PID file */
581 /* already exists, send a TERM signal to the process and try if it goes */
582 /* away. If not, retry a couple of times. */
583 for (i = 0; i < 3; i++) {
584 struct stat st;
586 if (stat(buffer, &st) != 0) {
587 /* PID file not found */
588 break;
591 if (sendsignalfn(buffer, SIGTERM) != 0) {
592 /* Can't send signal to PID, assume invalid file */
593 break;
596 upslogx(LOG_WARNING, "Duplicate driver instance detected! Terminating other driver!");
598 /* Allow driver some time to quit */
599 sleep(5);
602 pidfn = xstrdup(buffer);
603 writepid(pidfn); /* before backgrounding */
606 /* clear out callback handler data */
607 memset(&upsh, '\0', sizeof(upsh));
609 upsdrv_initups();
611 /* UPS is detected now, cleanup upon exit */
612 atexit(upsdrv_cleanup);
614 /* now see if things are very wrong out there */
615 if (upsdrv_info.status == DRV_BROKEN) {
616 fatalx(EXIT_FAILURE, "Fatal error: broken driver. It probably needs to be converted.\n");
619 if (do_forceshutdown)
620 forceshutdown();
622 /* note: device.type is set early to be overriden by the driver
623 * when its a pdu! */
624 dstate_setinfo("device.type", "ups");
626 /* publish the top-level data: version numbers, driver name */
627 dstate_setinfo("driver.version", "%s", UPS_VERSION);
628 dstate_setinfo("driver.version.internal", "%s", upsdrv_info.version);
629 dstate_setinfo("driver.name", "%s", progname);
631 /* get the base data established before allowing connections */
632 upsdrv_initinfo();
633 upsdrv_updateinfo();
635 if (dstate_getinfo("driver.flag.ignorelb")) {
636 int have_lb_method = 0;
638 if (dstate_getinfo("battery.charge") && dstate_getinfo("battery.charge.low")) {
639 upslogx(LOG_INFO, "using 'battery.charge' to set battery low state");
640 have_lb_method++;
643 if (dstate_getinfo("battery.runtime") && dstate_getinfo("battery.runtime.low")) {
644 upslogx(LOG_INFO, "using 'battery.runtime' to set battery low state");
645 have_lb_method++;
648 if (!have_lb_method) {
649 fatalx(EXIT_FAILURE,
650 "The 'ignorelb' flag is set, but there is no way to determine the\n"
651 "battery state of charge.\n\n"
652 "Only set this flag if both 'battery.charge' and 'battery.charge.low'\n"
653 "and/or 'battery.runtime' and 'battery.runtime.low' are available.\n");
657 /* now we can start servicing requests */
658 dstate_init(progname, upsname);
660 /* The poll_interval may have been changed from the default */
661 dstate_setinfo("driver.parameter.pollinterval", "%d", poll_interval);
663 /* remap the device.* info from ups.* for the transition period */
664 if (dstate_getinfo("ups.mfr") != NULL)
665 dstate_setinfo("device.mfr", "%s", dstate_getinfo("ups.mfr"));
666 if (dstate_getinfo("ups.model") != NULL)
667 dstate_setinfo("device.model", "%s", dstate_getinfo("ups.model"));
668 if (dstate_getinfo("ups.serial") != NULL)
669 dstate_setinfo("device.serial", "%s", dstate_getinfo("ups.serial"));
671 if (nut_debug_level == 0) {
672 background();
673 writepid(pidfn); /* PID changes when backgrounding */
676 while (!exit_flag) {
678 struct timeval timeout;
680 gettimeofday(&timeout, NULL);
681 timeout.tv_sec += poll_interval;
683 upsdrv_updateinfo();
685 while (!dstate_poll_fds(timeout, extrafd) && !exit_flag) {
686 /* repeat until time is up or extrafd has data */
690 /* if we get here, the exit flag was set by a signal handler */
691 upslogx(LOG_INFO, "Signal %d: exiting", exit_flag);
693 exit(EXIT_SUCCESS);