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
23 /* data which may be useful to the drivers */
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 */
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
;
47 static char *pidfn
= NULL
;
49 /* print the driver banner */
50 void upsdrv_banner (void)
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
) {
63 if (!upsdrv_info
.subdrv_info
[i
]->version
) {
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 */
82 /* this function only prints the usage message; it does not call exit() */
83 static void help_msg(void)
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");
107 printf("Acceptable values for -x or ups.conf in this driver:\n\n");
110 if (tmp
->vartype
== VAR_VALUE
)
111 printf("%40s : -x %s=<value>\n",
112 tmp
->desc
, tmp
->var
);
114 printf("%40s : -x %s\n", tmp
->desc
, tmp
->var
);
122 /* store these in dstate as driver.(parameter|flag) */
123 static void dparam_setinfo(const char *var
, const char *val
)
127 /* store these in dstate for debugging and other help */
129 snprintf(vtmp
, sizeof(vtmp
), "driver.parameter.%s", var
);
130 dstate_setinfo(vtmp
, "%s", val
);
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
);
151 if (!strncasecmp(var
, "default.", 8)) {
152 dstate_setinfo(var
+8, "%s", val
);
156 tmp
= last
= vartab_h
;
167 /* later definitions overwrite earlier ones */
168 if (!strcasecmp(tmp
->var
, var
)) {
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
);
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");
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");
195 /* retrieve the value of variable <var> if possible */
196 char *getval(const char *var
)
198 vartab_t
*tmp
= vartab_h
;
201 if (!strcasecmp(tmp
->var
, var
))
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
;
215 if (!strcasecmp(tmp
->var
, var
))
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
;
235 tmp
= xmalloc(sizeof(vartab_t
));
237 tmp
->vartype
= vartype
;
238 tmp
->var
= xstrdup(name
);
240 tmp
->desc
= xstrdup(desc
);
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")) {
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 */
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
);
302 if (!strcmp(var
, "chroot")) {
304 chroot_path
= xstrdup(val
);
307 if (!strcmp(var
, "user")) {
316 void do_upsconf_args(char *confupsname
, char *var
, char *val
)
320 /* handle global declarations */
322 do_global_args(var
, val
);
326 /* no match = not for us */
327 if (strcmp(confupsname
, upsname
) != 0)
332 if (main_arg(var
, val
))
335 /* flags (no =) now get passed to the driver-level stuff */
338 /* also store this, but it's a bit different */
339 snprintf(tmp
, sizeof(tmp
), "driver.flag.%s", var
);
340 dstate_setinfo(tmp
, "enabled");
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
);
354 /* allow per-driver overrides of the global setting */
355 if (!strcmp(var
, "pollinterval")) {
356 poll_interval
= atoi(val
);
360 /* everything else must be for the driver */
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
, '=');
381 /* see if main handles this first */
382 if (main_arg(buf
, val
))
385 /* otherwise store it for later */
389 /* dump the list from the vartable for external parsers */
390 static void listxarg(void)
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
);
413 static void vartab_free(void)
415 vartab_t
*tmp
, *next
;
431 static void exit_cleanup(void)
446 static void set_exit_flag(int sig
)
451 static void setup_signals(void)
455 sigemptyset(&sa
.sa_mask
);
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
);
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) {
499 fatalx(EXIT_FAILURE
, "Error: Section %s not found in ups.conf",
506 poll_interval
= atoi(optarg
);
510 do_forceshutdown
= 1;
519 chroot_path
= xstrdup(optarg
);
522 user
= xstrdup(optarg
);
525 /* already printed the banner, so exit */
535 "Error: unknown option -%c. Try -h for help.", i
);
544 "Error: too many non-option arguments. Try -h for help.");
547 if (!upsname_found
) {
549 "Error: specifying '-a id' is now mandatory. Try -h for help.");
552 /* we need to get the port from somewhere */
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
);
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
];
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
++) {
586 if (stat(buffer
, &st
) != 0) {
587 /* PID file not found */
591 if (sendsignalfn(buffer
, SIGTERM
) != 0) {
592 /* Can't send signal to PID, assume invalid file */
596 upslogx(LOG_WARNING
, "Duplicate driver instance detected! Terminating other driver!");
598 /* Allow driver some time to quit */
602 pidfn
= xstrdup(buffer
);
603 writepid(pidfn
); /* before backgrounding */
606 /* clear out callback handler data */
607 memset(&upsh
, '\0', sizeof(upsh
));
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
)
622 /* note: device.type is set early to be overriden by the driver
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 */
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");
643 if (dstate_getinfo("battery.runtime") && dstate_getinfo("battery.runtime.low")) {
644 upslogx(LOG_INFO
, "using 'battery.runtime' to set battery low state");
648 if (!have_lb_method
) {
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) {
673 writepid(pidfn
); /* PID changes when backgrounding */
678 struct timeval timeout
;
680 gettimeofday(&timeout
, NULL
);
681 timeout
.tv_sec
+= poll_interval
;
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
);