/* upsdrvctl.c - UPS driver controller Copyright (C) 2001 Russell Kroll <rkroll@exploits.org> 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 <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/stat.h> #include <sys/wait.h> #include "config.h" #include "proto.h" #include "version.h" #include "common.h" #include "upsconf.h" typedef struct { char *upsname; char *driver; char *port; int sdorder; int maxstartdelay; void *next; } ups_t; static ups_t *upstable = NULL; static int verbose = 0, maxsdorder = 0, testmode = 0, exec_error = 0; /* timer - keeps us from getting stuck if a driver hangs */ static int maxstartdelay = 45; /* Directory where driver executables live */ static char *driverpath = NULL; /* passthrough to the drivers: chroot path and new user name */ static char *pt_root = NULL, *pt_user = NULL; static sigset_t nut_upsdrvctl_sigmask; static struct sigaction sa; void do_upsconf_args(char *upsname, char *var, char *val) { ups_t *tmp, *last; /* handle global declarations */ if (!upsname) { if (!strcmp(var, "maxstartdelay")) maxstartdelay = atoi(val); if (!strcmp(var, "driverpath")) { if (driverpath) free(driverpath); driverpath = xstrdup(val); } /* ignore anything else - it's probably for main */ return; } last = tmp = upstable; while (tmp) { last = tmp; if (!strcmp(tmp->upsname, upsname)) { if (!strcmp(var, "driver")) tmp->driver = xstrdup(val); if (!strcmp(var, "port")) tmp->port = xstrdup(val); if (!strcmp(var, "maxstartdelay")) tmp->maxstartdelay = atoi(val); if (!strcmp(var, "sdorder")) { tmp->sdorder = atoi(val); if (tmp->sdorder > maxsdorder) maxsdorder = tmp->sdorder; } return; } tmp = tmp->next; } tmp = xmalloc(sizeof(ups_t)); tmp->upsname = xstrdup(upsname); tmp->driver = NULL; tmp->port = NULL; tmp->next = NULL; tmp->sdorder = 0; tmp->maxstartdelay = -1; /* use global value by default */ if (!strcmp(var, "driver")) tmp->driver = xstrdup(val); if (!strcmp(var, "port")) tmp->port = xstrdup(val); if (last) last->next = tmp; else upstable = tmp; } /* handle sending the signal */ static void send_term(char *upsname, char *driver, char *port) { char pidfn[SMALLBUF], buf[SMALLBUF]; int ret, pid; struct stat fs; FILE *pidf; printf("Stopping UPS: %s\n", upsname); snprintf(pidfn, sizeof(pidfn), "%s/%s-%s.pid", altpidpath(), driver, xbasename(port)); ret = stat(pidfn, &fs); if (ret != 0) { upslog(LOG_ERR, "Can't open %s", pidfn); exec_error++; return; } pidf = fopen(pidfn, "r"); if (!pidf) { upslog(LOG_ERR, "Can't open %s", pidfn); exec_error++; return; } fgets(buf, sizeof(buf), pidf); buf[strlen(buf)-1] = '\0'; pid = strtol(buf, (char **)NULL, 10); if (pid < 2) { upslogx(LOG_NOTICE, "Ignoring invalid pid %d in %s", pid, pidfn); exec_error++; return; } if (verbose) printf("Sending signal: kill -TERM %d\n", pid); if (testmode) return; ret = kill(pid, SIGTERM); if (ret < 0) { upslog(LOG_ERR, "kill pid %d failed", pid); exec_error++; return; } } /* stop user-selected driver */ static void stop_one_driver(char *upsname) { ups_t *tmp = upstable; if (!tmp) fatalx("Error: no UPS definitions found in ups.conf!\n"); while (tmp) { if (!strcmp(tmp->upsname, upsname)) { send_term(tmp->upsname, tmp->driver, tmp->port); return; } tmp = tmp->next; } fatalx("UPS %s not found in ups.conf", upsname); } /* walk ups table, but stop drivers instead */ static void stop_all_drivers(void) { ups_t *tmp = upstable; if (!tmp) fatalx("Error: no UPS definitions found in ups.conf!\n"); while (tmp) { send_term(tmp->upsname, tmp->driver, tmp->port); tmp = tmp->next; } } static void waitpid_timeout(int sig) { /* do nothing */ return; } static void forkexec(const char *prog, char **argv, ups_t *ups) { int ret; pid_t pid; pid = fork(); if (pid < 0) fatal("fork"); if (pid != 0) { /* parent */ int wstat; sigemptyset(&nut_upsdrvctl_sigmask); sa.sa_mask = nut_upsdrvctl_sigmask; sa.sa_flags = 0; sa.sa_handler = waitpid_timeout; sigaction(SIGALRM, &sa, NULL); if (ups->maxstartdelay != -1) alarm(ups->maxstartdelay); else alarm(maxstartdelay); ret = waitpid(pid, &wstat, 0); signal(SIGALRM, SIG_IGN); alarm(0); if (ret == -1) { upslogx(LOG_WARNING, "Startup timer elapsed, continuing..."); exec_error++; return; } if (WIFEXITED(wstat) == 0) { upslogx(LOG_WARNING, "Driver exited abnormally"); exec_error++; return; } if (WEXITSTATUS(wstat) != 0) { upslogx(LOG_WARNING, "Driver failed to start" " (exit status=%d)", WEXITSTATUS(wstat)); exec_error++; return; } /* the rest only work when WIFEXITED is nonzero */ if (WIFSIGNALED(wstat)) { upslog(LOG_WARNING, "Driver died after signal %d", WTERMSIG(wstat)); exec_error++; } return; } /* child */ ret = execv(prog, argv); /* shouldn't get here */ fatal("execv"); } static void start_driver(ups_t *ups) { char dfn[SMALLBUF], *argv[8]; int ret, arg = 0; struct stat fs; snprintf(dfn, sizeof(dfn), "%s/%s", driverpath, ups->driver); ret = stat(dfn, &fs); if (ret < 0) fatal("Can't start %s", dfn); if (verbose) printf("exec: %s -a %s", dfn, ups->upsname); argv[arg++] = xstrdup(dfn); argv[arg++] = "-a"; argv[arg++] = ups->upsname; /* stick on the chroot / user args if given to us */ if (pt_root) { argv[arg++] = "-r"; argv[arg++] = pt_root; if (verbose) printf(" -r %s", pt_root); } if (pt_user) { argv[arg++] = "-u"; argv[arg++] = pt_user; if (verbose) printf(" -u %s", pt_user); } if (verbose) printf("\n"); if (testmode) return; /* tie it off */ argv[arg++] = NULL; forkexec(dfn, argv, ups); } /* start user-selected driver */ static void start_one_driver(char *upsname) { ups_t *tmp = upstable; if (!tmp) fatalx("Error: no UPS definitions found in ups.conf!\n"); while (tmp) { if (!strcmp(tmp->upsname, upsname)) { start_driver(tmp); return; } tmp = tmp->next; } fatalx("UPS %s not found in ups.conf", upsname); } /* walk ups table and invoke drivers */ static void start_all_drivers(void) { ups_t *tmp = upstable; if (!tmp) fatalx("Error: no UPS definitions found in ups.conf!\n"); while (tmp) { start_driver(tmp); tmp = tmp->next; } } static void help(const char *progname) { printf("Starts and stops UPS drivers via ups.conf.\n\n"); printf("usage: %s [OPTIONS] (start | stop | shutdown) [<ups>]\n\n", progname); printf(" -h display this help\n"); printf(" -r <path> drivers will chroot to <path>\n"); printf(" -t testing mode - prints actions without doing them\n"); printf(" -u <user> drivers started will switch from root to <user>\n"); printf(" -v enable verbose messages\n"); printf(" start start all UPS drivers in ups.conf\n"); printf(" start <ups> only start driver for UPS <ups>\n"); printf(" stop stop all UPS drivers in ups.conf\n"); printf(" stop <ups> only stop driver for UPS <ups>\n"); printf(" shutdown shutdown all UPS drivers in ups.conf\n"); printf(" shutdown <ups> only shutdown UPS <ups>\n"); exit(EXIT_SUCCESS); } static void shutdown_driver(ups_t *ups) { char *argv[7], dfn[SMALLBUF]; snprintf(dfn, sizeof(dfn), "%s/%s", driverpath, ups->driver); if (verbose) printf("exec: %s -a %s -k\n", dfn, ups->upsname); if (testmode) return; argv[0] = dfn; argv[1] = "-a"; argv[2] = xstrdup(ups->upsname); argv[3] = "-k"; argv[4] = NULL; forkexec(dfn, argv, ups); } static void shutdown_one_driver(char *upsname) { ups_t *tmp = upstable; if (!tmp) fatalx("Error: no UPS definitions found in ups.conf!\n"); while (tmp) { if (!strcmp(tmp->upsname, upsname)) { shutdown_driver(tmp); return; } tmp = tmp->next; } fatalx("UPS %s not found in ups.conf", upsname); } /* walk UPS table and shut down all UPSes according to sdorder */ static void shutdown_all_drivers(void) { ups_t *tmp; int i; if (!upstable) fatalx("Error: no UPS definitions found in ups.conf"); for (i = 0; i <= maxsdorder; i++) { tmp = upstable; while (tmp) { if (tmp->sdorder == i) shutdown_driver(tmp); tmp = tmp->next; } } } static void exit_cleanup(void) { ups_t *tmp, *next; tmp = upstable; while (tmp) { next = tmp->next; if (tmp->driver) free(tmp->driver); if (tmp->port) free(tmp->port); if (tmp->upsname) free(tmp->upsname); free(tmp); tmp = next; } if (driverpath) free(driverpath); } int main(int argc, char **argv) { int i; char *prog; printf("Network UPS Tools - UPS driver controller %s\n", UPS_VERSION); prog = argv[0]; while ((i = getopt(argc, argv, "+htvu:r:V")) != EOF) { switch(i) { case 'r': pt_root = optarg; break; case 't': testmode = 1; /* force verbose mode while testing */ if (verbose == 0) verbose = 1; break; case 'u': pt_user = optarg; break; case 'v': verbose++; break; case 'V': exit(EXIT_SUCCESS); case 'h': default: help(prog); break; } } argc -= optind; argv += optind; if (argc < 1) help(prog); if (testmode) printf("*** Testing mode: not calling exec/kill\n"); driverpath = xstrdup(DRVPATH); /* set default */ atexit(exit_cleanup); if (!strcmp(argv[0], "start")) { read_upsconf(); if (argc == 1) start_all_drivers(); else start_one_driver(argv[1]); if (exec_error) exit(EXIT_FAILURE); exit(EXIT_SUCCESS); } if (!strcmp(argv[0], "stop")) { read_upsconf(); if (argc == 1) stop_all_drivers(); else stop_one_driver(argv[1]); if (exec_error) exit(EXIT_FAILURE); exit(EXIT_SUCCESS); } if (!strcmp(argv[0], "shutdown")) { read_upsconf(); if (argc == 1) shutdown_all_drivers(); else shutdown_one_driver(argv[1]); if (exec_error) exit(EXIT_FAILURE); exit(EXIT_SUCCESS); } fatalx("Error: unrecognized command [%s]\n", argv[0]); /* NOTREACHED */ exit(EXIT_FAILURE); }