/* main.c - Network UPS Tools driver core Copyright (C) 1999 Russell Kroll This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "main.h" #include "dstate.h" /* data which may be useful to the drivers */ int upsfd = -1; char *device_path = NULL; const char *progname = NULL, *upsname = NULL, *device_name = NULL; /* may be set by the driver to wake up while in dstate_poll_fds */ int extrafd = -1; /* for ser_open */ int do_lock_port = 1; /* for dstate->sock_connect, default to asynchronous */ int do_synchronous = 0; /* for detecting -a values that don't match anything */ static int upsname_found = 0; static vartab_t *vartab_h = NULL; /* variables possibly set by the global part of ups.conf */ unsigned int poll_interval = 2; static char *chroot_path = NULL, *user = NULL; /* signal handling */ int exit_flag = 0; /* everything else */ static char *pidfn = NULL; /* print the driver banner */ void upsdrv_banner (void) { int i; printf("Network UPS Tools - %s %s (%s)\n", upsdrv_info.name, upsdrv_info.version, UPS_VERSION); /* process sub driver(s) information */ for (i = 0; upsdrv_info.subdrv_info[i]; i++) { if (!upsdrv_info.subdrv_info[i]->name) { continue; } if (!upsdrv_info.subdrv_info[i]->version) { continue; } printf("%s %s\n", upsdrv_info.subdrv_info[i]->name, upsdrv_info.subdrv_info[i]->version); } } /* power down the attached load immediately */ static void forceshutdown(void) { upslogx(LOG_NOTICE, "Initiating UPS shutdown"); /* the driver must not block in this function */ upsdrv_shutdown(); exit(EXIT_SUCCESS); } /* this function only prints the usage message; it does not call exit() */ static void help_msg(void) { vartab_t *tmp; printf("\nusage: %s -a [OPTIONS]\n", progname); printf(" -a - autoconfig using ups.conf section \n"); printf(" - note: -x after -a overrides ups.conf settings\n\n"); printf(" -V - print version, then exit\n"); printf(" -L - print parseable list of driver variables\n"); printf(" -D - raise debugging level\n"); printf(" -q - raise log level threshold\n"); printf(" -h - display this help\n"); printf(" -k - force shutdown\n"); printf(" -i - poll interval\n"); printf(" -r - chroot to \n"); printf(" -u - switch to (if started as root)\n"); printf(" -x = - set driver variable to \n"); printf(" - example: -x cable=940-0095B\n\n"); if (vartab_h) { tmp = vartab_h; printf("Acceptable values for -x or ups.conf in this driver:\n\n"); while (tmp) { if (tmp->vartype == VAR_VALUE) printf("%40s : -x %s=\n", tmp->desc, tmp->var); else printf("%40s : -x %s\n", tmp->desc, tmp->var); tmp = tmp->next; } } upsdrv_help(); } /* store these in dstate as driver.(parameter|flag) */ static void dparam_setinfo(const char *var, const char *val) { char vtmp[SMALLBUF]; /* store these in dstate for debugging and other help */ if (val) { snprintf(vtmp, sizeof(vtmp), "driver.parameter.%s", var); dstate_setinfo(vtmp, "%s", val); return; } /* no value = flag */ snprintf(vtmp, sizeof(vtmp), "driver.flag.%s", var); dstate_setinfo(vtmp, "enabled"); } /* cram var [= ] data into storage */ static void storeval(const char *var, char *val) { vartab_t *tmp, *last; if (!strncasecmp(var, "override.", 9)) { dstate_setinfo(var+9, "%s", val); dstate_setflags(var+9, ST_FLAG_IMMUTABLE); return; } if (!strncasecmp(var, "default.", 8)) { dstate_setinfo(var+8, "%s", val); return; } tmp = last = vartab_h; while (tmp) { last = tmp; /* sanity check */ if (!tmp->var) { tmp = tmp->next; continue; } /* later definitions overwrite earlier ones */ if (!strcasecmp(tmp->var, var)) { free(tmp->val); if (val) tmp->val = xstrdup(val); /* don't keep things like SNMP community strings */ if ((tmp->vartype & VAR_SENSITIVE) == 0) dparam_setinfo(var, val); tmp->found = 1; return; } tmp = tmp->next; } /* try to help them out */ printf("\nFatal error: '%s' is not a valid %s for this driver.\n", var, val ? "variable name" : "flag"); printf("\n"); printf("Look in the man page or call this driver with -h for a list of\n"); printf("valid variable names and flags.\n"); exit(EXIT_SUCCESS); } /* retrieve the value of variable if possible */ char *getval(const char *var) { vartab_t *tmp = vartab_h; while (tmp) { if (!strcasecmp(tmp->var, var)) return(tmp->val); tmp = tmp->next; } return NULL; } /* see if has been defined, even if no value has been given to it */ int testvar(const char *var) { vartab_t *tmp = vartab_h; while (tmp) { if (!strcasecmp(tmp->var, var)) return tmp->found; tmp = tmp->next; } return 0; /* not found */ } /* callback from driver - create the table for -x/conf entries */ void addvar(int vartype, const char *name, const char *desc) { vartab_t *tmp, *last; tmp = last = vartab_h; while (tmp) { last = tmp; tmp = tmp->next; } tmp = xmalloc(sizeof(vartab_t)); tmp->vartype = vartype; tmp->var = xstrdup(name); tmp->val = NULL; tmp->desc = xstrdup(desc); tmp->found = 0; tmp->next = NULL; if (last) last->next = tmp; else vartab_h = tmp; } /* handle -x / ups.conf config details that are for this part of the code */ static int main_arg(char *var, char *val) { /* flags for main */ if (!strcmp(var, "nolock")) { do_lock_port = 0; dstate_setinfo("driver.flag.nolock", "enabled"); return 1; /* handled */ } if (!strcmp(var, "ignorelb")) { dstate_setinfo("driver.flag.ignorelb", "enabled"); return 1; /* handled */ } /* any other flags are for the driver code */ if (!val) return 0; /* variables for main: port */ if (!strcmp(var, "port")) { device_path = xstrdup(val); device_name = xbasename(device_path); dstate_setinfo("driver.parameter.port", "%s", val); return 1; /* handled */ } if (!strcmp(var, "sddelay")) { upslogx(LOG_INFO, "Obsolete value sddelay found in ups.conf"); return 1; /* handled */ } /* allow per-driver overrides of the global setting */ if (!strcmp(var, "synchronous")) { if (!strcmp(val, "yes")) do_synchronous=1; else do_synchronous=0; return 1; /* handled */ } /* only for upsdrvctl - ignored here */ if (!strcmp(var, "sdorder")) return 1; /* handled */ /* only for upsd (at the moment) - ignored here */ if (!strcmp(var, "desc")) return 1; /* handled */ return 0; /* unhandled, pass it through to the driver */ } static void do_global_args(const char *var, const char *val) { if (!strcmp(var, "pollinterval")) { poll_interval = atoi(val); return; } if (!strcmp(var, "chroot")) { free(chroot_path); chroot_path = xstrdup(val); } if (!strcmp(var, "user")) { free(user); user = xstrdup(val); } if (!strcmp(var, "synchronous")) { if (!strcmp(val, "yes")) do_synchronous=1; else do_synchronous=0; } /* unrecognized */ } void do_upsconf_args(char *confupsname, char *var, char *val) { char tmp[SMALLBUF]; /* handle global declarations */ if (!confupsname) { do_global_args(var, val); return; } /* no match = not for us */ if (strcmp(confupsname, upsname) != 0) return; upsname_found = 1; if (main_arg(var, val)) return; /* flags (no =) now get passed to the driver-level stuff */ if (!val) { /* also store this, but it's a bit different */ snprintf(tmp, sizeof(tmp), "driver.flag.%s", var); dstate_setinfo(tmp, "enabled"); storeval(var, NULL); return; } /* don't let the user shoot themselves in the foot */ if (!strcmp(var, "driver")) { if (strcmp(val, progname) != 0) fatalx(EXIT_FAILURE, "Error: UPS [%s] is for driver %s, but I'm %s!\n", confupsname, val, progname); return; } /* allow per-driver overrides of the global setting */ if (!strcmp(var, "pollinterval")) { poll_interval = atoi(val); return; } /* everything else must be for the driver */ storeval(var, val); } /* split -x foo=bar into 'foo' and 'bar' */ static void splitxarg(char *inbuf) { char *eqptr, *val, *buf; /* make our own copy - avoid changing argv */ buf = xstrdup(inbuf); eqptr = strchr(buf, '='); if (!eqptr) val = NULL; else { *eqptr++ = '\0'; val = eqptr; } /* see if main handles this first */ if (main_arg(buf, val)) return; /* otherwise store it for later */ storeval(buf, val); } /* dump the list from the vartable for external parsers */ static void listxarg(void) { vartab_t *tmp; tmp = vartab_h; if (!tmp) return; while (tmp) { switch (tmp->vartype) { case VAR_VALUE: printf("VALUE"); break; case VAR_FLAG: printf("FLAG"); break; default: printf("UNKNOWN"); break; } printf(" %s \"%s\"\n", tmp->var, tmp->desc); tmp = tmp->next; } } static void vartab_free(void) { vartab_t *tmp, *next; tmp = vartab_h; while (tmp) { next = tmp->next; free(tmp->var); free(tmp->val); free(tmp->desc); free(tmp); tmp = next; } } static void exit_cleanup(void) { free(chroot_path); free(device_path); free(user); if (pidfn) { unlink(pidfn); free(pidfn); } dstate_free(); vartab_free(); } static void set_exit_flag(int sig) { exit_flag = sig; } static void setup_signals(void) { struct sigaction sa; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sa.sa_handler = set_exit_flag; sigaction(SIGTERM, &sa, NULL); sigaction(SIGINT, &sa, NULL); sigaction(SIGQUIT, &sa, NULL); sa.sa_handler = SIG_IGN; sigaction(SIGHUP, &sa, NULL); sigaction(SIGPIPE, &sa, NULL); } int main(int argc, char **argv) { struct passwd *new_uid = NULL; int i, do_forceshutdown = 0; atexit(exit_cleanup); /* pick up a default from configure --with-user */ user = xstrdup(RUN_AS_USER); /* xstrdup: this gets freed at exit */ progname = xbasename(argv[0]); open_syslog(progname); upsdrv_banner(); if (upsdrv_info.status == DRV_EXPERIMENTAL) { printf("Warning: This is an experimental driver.\n"); printf("Some features may not function correctly.\n\n"); } /* build the driver's extra (-x) variable table */ upsdrv_makevartable(); while ((i = getopt(argc, argv, "+a:kDhx:Lqr:u:Vi:")) != -1) { switch (i) { case 'a': upsname = optarg; read_upsconf(); if (!upsname_found) fatalx(EXIT_FAILURE, "Error: Section %s not found in ups.conf", optarg); break; case 'D': nut_debug_level++; break; case 'i': poll_interval = atoi(optarg); break; case 'k': do_lock_port = 0; do_forceshutdown = 1; break; case 'L': listxarg(); exit(EXIT_SUCCESS); case 'q': nut_log_level++; break; case 'r': chroot_path = xstrdup(optarg); break; case 'u': free(user); user = xstrdup(optarg); break; case 'V': /* already printed the banner, so exit */ exit(EXIT_SUCCESS); case 'x': splitxarg(optarg); break; case 'h': help_msg(); exit(EXIT_SUCCESS); default: fatalx(EXIT_FAILURE, "Error: unknown option -%c. Try -h for help.", i); } } argc -= optind; argv += optind; if (argc > 0) { fatalx(EXIT_FAILURE, "Error: too many non-option arguments. Try -h for help."); } if (!upsname_found) { fatalx(EXIT_FAILURE, "Error: specifying '-a id' is now mandatory. Try -h for help."); } /* we need to get the port from somewhere */ if (!device_path) { fatalx(EXIT_FAILURE, "Error: you must specify a port name in ups.conf. Try -h for help."); } upsdebugx(1, "debug level is '%d'", nut_debug_level); new_uid = get_user_pwent(user); if (chroot_path) chroot_start(chroot_path); become_user(new_uid); /* Only switch to statepath if we're not powering off */ /* This avoid case where ie /var is umounted */ if ((!do_forceshutdown) && (chdir(dflt_statepath()))) fatal_with_errno(EXIT_FAILURE, "Can't chdir to %s", dflt_statepath()); /* Setup signals to communicate with driver once backgrounded. */ if ((nut_debug_level == 0) && (!do_forceshutdown)) { char buffer[SMALLBUF]; setup_signals(); snprintf(buffer, sizeof(buffer), "%s/%s-%s.pid", altpidpath(), progname, upsname); /* Try to prevent that driver is started multiple times. If a PID file */ /* already exists, send a TERM signal to the process and try if it goes */ /* away. If not, retry a couple of times. */ for (i = 0; i < 3; i++) { struct stat st; if (stat(buffer, &st) != 0) { /* PID file not found */ break; } if (sendsignalfn(buffer, SIGTERM) != 0) { /* Can't send signal to PID, assume invalid file */ break; } upslogx(LOG_WARNING, "Duplicate driver instance detected! Terminating other driver!"); /* Allow driver some time to quit */ sleep(5); } pidfn = xstrdup(buffer); writepid(pidfn); /* before backgrounding */ } /* clear out callback handler data */ memset(&upsh, '\0', sizeof(upsh)); upsdrv_initups(); /* UPS is detected now, cleanup upon exit */ atexit(upsdrv_cleanup); /* now see if things are very wrong out there */ if (upsdrv_info.status == DRV_BROKEN) { fatalx(EXIT_FAILURE, "Fatal error: broken driver. It probably needs to be converted.\n"); } if (do_forceshutdown) forceshutdown(); /* note: device.type is set early to be overriden by the driver * when its a pdu! */ dstate_setinfo("device.type", "ups"); /* publish the top-level data: version numbers, driver name */ dstate_setinfo("driver.version", "%s", UPS_VERSION); dstate_setinfo("driver.version.internal", "%s", upsdrv_info.version); dstate_setinfo("driver.name", "%s", progname); /* get the base data established before allowing connections */ upsdrv_initinfo(); upsdrv_updateinfo(); if (dstate_getinfo("driver.flag.ignorelb")) { int have_lb_method = 0; if (dstate_getinfo("battery.charge") && dstate_getinfo("battery.charge.low")) { upslogx(LOG_INFO, "using 'battery.charge' to set battery low state"); have_lb_method++; } if (dstate_getinfo("battery.runtime") && dstate_getinfo("battery.runtime.low")) { upslogx(LOG_INFO, "using 'battery.runtime' to set battery low state"); have_lb_method++; } if (!have_lb_method) { fatalx(EXIT_FAILURE, "The 'ignorelb' flag is set, but there is no way to determine the\n" "battery state of charge.\n\n" "Only set this flag if both 'battery.charge' and 'battery.charge.low'\n" "and/or 'battery.runtime' and 'battery.runtime.low' are available.\n"); } } /* now we can start servicing requests */ dstate_init(progname, upsname); /* The poll_interval may have been changed from the default */ dstate_setinfo("driver.parameter.pollinterval", "%d", poll_interval); /* The synchronous option may have been changed from the default */ dstate_setinfo("driver.parameter.synchronous", "%s", (do_synchronous==1)?"yes":"no"); /* remap the device.* info from ups.* for the transition period */ if (dstate_getinfo("ups.mfr") != NULL) dstate_setinfo("device.mfr", "%s", dstate_getinfo("ups.mfr")); if (dstate_getinfo("ups.model") != NULL) dstate_setinfo("device.model", "%s", dstate_getinfo("ups.model")); if (dstate_getinfo("ups.serial") != NULL) dstate_setinfo("device.serial", "%s", dstate_getinfo("ups.serial")); if (nut_debug_level == 0) { background(); writepid(pidfn); /* PID changes when backgrounding */ } while (!exit_flag) { struct timeval timeout; gettimeofday(&timeout, NULL); timeout.tv_sec += poll_interval; upsdrv_updateinfo(); while (!dstate_poll_fds(timeout, extrafd) && !exit_flag) { /* repeat until time is up or extrafd has data */ } } /* if we get here, the exit flag was set by a signal handler */ upslogx(LOG_INFO, "Signal %d: exiting", exit_flag); exit(EXIT_SUCCESS); }