1 /* upsdrvctl.c - UPS driver controller
2
3 Copyright (C) 2001 Russell Kroll <rkroll@exploits.org>
4
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.
9
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.
14
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
18 */
19
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <unistd.h>
24 #include <sys/stat.h>
25 #include <sys/wait.h>
26
27 #include "config.h"
28 #include "proto.h"
29 #include "common.h"
30 #include "upsconf.h"
31 #include "attribute.h"
32
33 typedef struct {
34 char *upsname;
35 char *driver;
36 char *port;
37 int sdorder;
38 int maxstartdelay;
39 void *next;
40 } ups_t;
41
42 static ups_t *upstable = NULL;
43
44 static int maxsdorder = 0, testmode = 0, exec_error = 0;
45
46 /* Should we wait for driver (1) or "parallelize" drivers start (0) */
47 static int waitfordrivers = 1;
48
49 /* timer - keeps us from getting stuck if a driver hangs */
50 static int maxstartdelay = 45;
51
52 /* counter - retry that many time(s) to start the driver if it fails to */
53 static int maxretry = 1;
54
55 /* timer - delay between each restart attempt of the driver(s) */
56 static int retrydelay = 5;
57
58 /* Directory where driver executables live */
59 static char *driverpath = NULL;
60
61 /* passthrough to the drivers: chroot path and new user name */
62 static char *pt_root = NULL, *pt_user = NULL;
63
do_upsconf_args(char * upsname,char * var,char * val)64 void do_upsconf_args(char *upsname, char *var, char *val)
65 {
66 ups_t *tmp, *last;
67
68 /* handle global declarations */
69 if (!upsname) {
70 if (!strcmp(var, "maxstartdelay"))
71 maxstartdelay = atoi(val);
72
73 if (!strcmp(var, "driverpath")) {
74 free(driverpath);
75 driverpath = xstrdup(val);
76 }
77
78 if (!strcmp(var, "maxretry"))
79 maxretry = atoi(val);
80
81 if (!strcmp(var, "retrydelay"))
82 retrydelay = atoi(val);
83
84 if (!strcmp(var, "nowait"))
85 waitfordrivers = 0;
86
87 /* ignore anything else - it's probably for main */
88
89 return;
90 }
91
92 last = tmp = upstable;
93
94 while (tmp) {
95 last = tmp;
96
97 if (!strcmp(tmp->upsname, upsname)) {
98 if (!strcmp(var, "driver"))
99 tmp->driver = xstrdup(val);
100
101 if (!strcmp(var, "port"))
102 tmp->port = xstrdup(val);
103
104 if (!strcmp(var, "maxstartdelay"))
105 tmp->maxstartdelay = atoi(val);
106
107 if (!strcmp(var, "sdorder")) {
108 tmp->sdorder = atoi(val);
109
110 if (tmp->sdorder > maxsdorder)
111 maxsdorder = tmp->sdorder;
112 }
113
114 return;
115 }
116
117 tmp = tmp->next;
118 }
119
120 tmp = xmalloc(sizeof(ups_t));
121 tmp->upsname = xstrdup(upsname);
122 tmp->driver = NULL;
123 tmp->port = NULL;
124 tmp->next = NULL;
125 tmp->sdorder = 0;
126 tmp->maxstartdelay = -1; /* use global value by default */
127
128 if (!strcmp(var, "driver"))
129 tmp->driver = xstrdup(val);
130
131 if (!strcmp(var, "port"))
132 tmp->port = xstrdup(val);
133
134 if (last)
135 last->next = tmp;
136 else
137 upstable = tmp;
138 }
139
140 /* handle sending the signal */
stop_driver(const ups_t * ups)141 static void stop_driver(const ups_t *ups)
142 {
143 char pidfn[SMALLBUF];
144 int ret;
145 struct stat fs;
146
147 upsdebugx(1, "Stopping UPS: %s", ups->upsname);
148
149 snprintf(pidfn, sizeof(pidfn), "%s/%s-%s.pid", altpidpath(),
150 ups->driver, ups->upsname);
151 ret = stat(pidfn, &fs);
152
153 if ((ret != 0) && (ups->port != NULL)) {
154 upslog_with_errno(LOG_ERR, "Can't open %s", pidfn);
155 snprintf(pidfn, sizeof(pidfn), "%s/%s-%s.pid", altpidpath(),
156 ups->driver, xbasename(ups->port));
157 ret = stat(pidfn, &fs);
158 }
159
160 if (ret != 0) {
161 upslog_with_errno(LOG_ERR, "Can't open %s either", pidfn);
162 exec_error++;
163 return;
164 }
165
166 upsdebugx(2, "Sending signal to %s", pidfn);
167
168 if (testmode)
169 return;
170
171 ret = sendsignalfn(pidfn, SIGTERM);
172
173 if (ret < 0) {
174 upslog_with_errno(LOG_ERR, "Stopping %s failed", pidfn);
175 exec_error++;
176 return;
177 }
178 }
179
waitpid_timeout(const int sig)180 static void waitpid_timeout(const int sig)
181 {
182 NUT_UNUSED_VARIABLE(sig);
183
184 /* do nothing */
185 return;
186 }
187
188 /* print out a command line at the given debug level. */
debugcmdline(int level,const char * msg,char * const argv[])189 static void debugcmdline(int level, const char *msg, char *const argv[])
190 {
191 char cmdline[LARGEBUF];
192
193 snprintf(cmdline, sizeof(cmdline), "%s", msg);
194
195 while (*argv) {
196 snprintfcat(cmdline, sizeof(cmdline), " %s", *argv++);
197 }
198
199 upsdebugx(level, "%s", cmdline);
200 }
201
forkexec(char * const argv[],const ups_t * ups)202 static void forkexec(char *const argv[], const ups_t *ups)
203 {
204 int ret;
205 pid_t pid;
206
207 pid = fork();
208
209 if (pid < 0)
210 fatal_with_errno(EXIT_FAILURE, "fork");
211
212 if (pid != 0) { /* parent */
213 int wstat;
214 struct sigaction sa;
215
216 /* Handle "parallel" drivers startup */
217 if (waitfordrivers == 0) {
218 upsdebugx(2, "'nowait' set, continuing...");
219 return;
220 }
221
222 sigemptyset(&sa.sa_mask);
223 sa.sa_flags = 0;
224 sa.sa_handler = waitpid_timeout;
225 sigaction(SIGALRM, &sa, NULL);
226
227 /* Use the local maxstartdelay, if available */
228 if (ups->maxstartdelay != -1) {
229 if (ups->maxstartdelay >= 0)
230 alarm((unsigned int)ups->maxstartdelay);
231 } else { /* Otherwise, use the global (or default) value */
232 if (maxstartdelay >= 0)
233 alarm((unsigned int)maxstartdelay);
234 }
235
236 ret = waitpid(pid, &wstat, 0);
237
238 alarm(0);
239
240 if (ret == -1) {
241 upslogx(LOG_WARNING, "Startup timer elapsed, continuing...");
242 exec_error++;
243 return;
244 }
245
246 if (WIFEXITED(wstat) == 0) {
247 upslogx(LOG_WARNING, "Driver exited abnormally");
248 exec_error++;
249 return;
250 }
251
252 if (WEXITSTATUS(wstat) != 0) {
253 upslogx(LOG_WARNING, "Driver failed to start"
254 " (exit status=%d)", WEXITSTATUS(wstat));
255 exec_error++;
256 return;
257 }
258
259 /* the rest only work when WIFEXITED is nonzero */
260
261 if (WIFSIGNALED(wstat)) {
262 upslog_with_errno(LOG_WARNING, "Driver died after signal %d",
263 WTERMSIG(wstat));
264 exec_error++;
265 }
266
267 return;
268 }
269
270 /* child */
271
272 ret = execv(argv[0], argv);
273
274 /* shouldn't get here */
275 fatal_with_errno(EXIT_FAILURE, "execv");
276 }
277
start_driver(const ups_t * ups)278 static void start_driver(const ups_t *ups)
279 {
280 char *argv[8];
281 char dfn[SMALLBUF];
282 int ret, arg = 0;
283 int initial_exec_error = exec_error, drv_maxretry = maxretry;
284 struct stat fs;
285
286 upsdebugx(1, "Starting UPS: %s", ups->upsname);
287
288 snprintf(dfn, sizeof(dfn), "%s/%s", driverpath, ups->driver);
289 ret = stat(dfn, &fs);
290
291 if (ret < 0)
292 fatal_with_errno(EXIT_FAILURE, "Can't start %s", dfn);
293
294 argv[arg++] = dfn;
295 argv[arg++] = (char *)"-a"; /* FIXME: cast away const */
296 argv[arg++] = ups->upsname;
297
298 /* stick on the chroot / user args if given to us */
299 if (pt_root) {
300 argv[arg++] = (char *)"-r"; /* FIXME: cast away const */
301 argv[arg++] = pt_root;
302 }
303
304 if (pt_user) {
305 argv[arg++] = (char *)"-u"; /* FIXME: cast away const */
306 argv[arg++] = pt_user;
307 }
308
309 /* tie it off */
310 argv[arg++] = NULL;
311
312
313 while (drv_maxretry > 0) {
314 int cur_exec_error = exec_error;
315
316 upsdebugx(2, "%i remaining attempts", drv_maxretry);
317 debugcmdline(2, "exec: ", argv);
318 drv_maxretry--;
319
320 if (!testmode) {
321 forkexec(argv, ups);
322 }
323
324 /* driver command succeeded */
325 if (cur_exec_error == exec_error) {
326 drv_maxretry = 0;
327 exec_error = initial_exec_error;
328 }
329 else {
330 /* otherwise, retry if still needed */
331 if (drv_maxretry > 0)
332 if (retrydelay >= 0)
333 sleep ((unsigned int)retrydelay);
334 }
335 }
336 }
337
338 static void help(const char *progname)
339 __attribute__((noreturn));
340
help(const char * progname)341 static void help(const char *progname)
342 {
343 printf("Starts and stops UPS drivers via ups.conf.\n\n");
344 printf("usage: %s [OPTIONS] (start | stop | shutdown) [<ups>]\n\n", progname);
345
346 printf(" -h display this help\n");
347 printf(" -r <path> drivers will chroot to <path>\n");
348 printf(" -t testing mode - prints actions without doing them\n");
349 printf(" -u <user> drivers started will switch from root to <user>\n");
350 printf(" -D raise debugging level\n");
351 printf(" start start all UPS drivers in ups.conf\n");
352 printf(" start <ups> only start driver for UPS <ups>\n");
353 printf(" stop stop all UPS drivers in ups.conf\n");
354 printf(" stop <ups> only stop driver for UPS <ups>\n");
355 printf(" shutdown shutdown all UPS drivers in ups.conf\n");
356 printf(" shutdown <ups> only shutdown UPS <ups>\n");
357
358 exit(EXIT_SUCCESS);
359 }
360
shutdown_driver(const ups_t * ups)361 static void shutdown_driver(const ups_t *ups)
362 {
363 char *argv[9];
364 char dfn[SMALLBUF];
365 int arg = 0;
366
367 upsdebugx(1, "Shutdown UPS: %s", ups->upsname);
368
369 snprintf(dfn, sizeof(dfn), "%s/%s", driverpath, ups->driver);
370
371 argv[arg++] = dfn;
372 argv[arg++] = (char *)"-a"; /* FIXME: cast away const */
373 argv[arg++] = ups->upsname;
374 argv[arg++] = (char *)"-k"; /* FIXME: cast away const */
375
376 /* stick on the chroot / user args if given to us */
377 if (pt_root) {
378 argv[arg++] = (char *)"-r"; /* FIXME: cast away const */
379 argv[arg++] = pt_root;
380 }
381
382 if (pt_user) {
383 argv[arg++] = (char *)"-u"; /* FIXME: cast away const */
384 argv[arg++] = pt_user;
385 }
386
387 argv[arg++] = NULL;
388
389 debugcmdline(2, "exec: ", argv);
390
391 if (!testmode) {
392 forkexec(argv, ups);
393 }
394 }
395
send_one_driver(void (* command)(const ups_t *),const char * upsname)396 static void send_one_driver(void (*command)(const ups_t *), const char *upsname)
397 {
398 ups_t *ups = upstable;
399
400 if (!ups)
401 fatalx(EXIT_FAILURE, "Error: no UPS definitions found in ups.conf!\n");
402
403 while (ups) {
404 if (!strcmp(ups->upsname, upsname)) {
405 command(ups);
406 return;
407 }
408
409 ups = ups->next;
410 }
411
412 fatalx(EXIT_FAILURE, "UPS %s not found in ups.conf", upsname);
413 }
414
415 /* walk UPS table and send command to all UPSes according to sdorder */
send_all_drivers(void (* command)(const ups_t *))416 static void send_all_drivers(void (*command)(const ups_t *))
417 {
418 ups_t *ups;
419 int i;
420
421 if (!upstable)
422 fatalx(EXIT_FAILURE, "Error: no UPS definitions found in ups.conf");
423
424 if (command != &shutdown_driver) {
425 ups = upstable;
426
427 while (ups) {
428 command(ups);
429
430 ups = ups->next;
431 }
432
433 return;
434 }
435
436 for (i = 0; i <= maxsdorder; i++) {
437 ups = upstable;
438
439 while (ups) {
440 if (ups->sdorder == i)
441 command(ups);
442
443 ups = ups->next;
444 }
445 }
446 }
447
exit_cleanup(void)448 static void exit_cleanup(void)
449 {
450 ups_t *tmp, *next;
451
452 tmp = upstable;
453
454 while (tmp) {
455 next = tmp->next;
456
457 free(tmp->driver);
458 free(tmp->port);
459 free(tmp->upsname);
460 free(tmp);
461
462 tmp = next;
463 }
464
465 free(driverpath);
466 }
467
main(int argc,char ** argv)468 int main(int argc, char **argv)
469 {
470 int i;
471 char *prog;
472 void (*command)(const ups_t *) = NULL;
473
474 printf("Network UPS Tools - UPS driver controller %s\n",
475 UPS_VERSION);
476
477 prog = argv[0];
478 while ((i = getopt(argc, argv, "+htu:r:DV")) != -1) {
479 switch(i) {
480 case 'r':
481 pt_root = optarg;
482 break;
483
484 case 't':
485 testmode = 1;
486 break;
487
488 case 'u':
489 pt_user = optarg;
490 break;
491
492 case 'V':
493 exit(EXIT_SUCCESS);
494
495 case 'D':
496 nut_debug_level++;
497 break;
498
499 case 'h':
500 default:
501 help(prog);
502 }
503 }
504
505 argc -= optind;
506 argv += optind;
507
508 if (argc < 1)
509 help(prog);
510
511 if (testmode) {
512 printf("*** Testing mode: not calling exec/kill\n");
513
514 if (nut_debug_level < 2)
515 nut_debug_level = 2;
516 }
517
518 upsdebugx(2, "\n"
519 "If you're not a NUT core developer, chances are that you're told to enable debugging\n"
520 "to see why a driver isn't working for you. We're sorry for the confusion, but this is\n"
521 "the 'upsdrvctl' wrapper, not the driver you're interested in.\n\n"
522 "Below you'll find one or more lines starting with 'exec:' followed by an absolute\n"
523 "path to the driver binary and some command line option. This is what the driver\n"
524 "starts and you need to copy and paste that line and append the debug flags to that\n"
525 "line (less the 'exec:' prefix).\n");
526
527 if (!strcmp(argv[0], "start"))
528 command = &start_driver;
529
530 if (!strcmp(argv[0], "stop"))
531 command = &stop_driver;
532
533 if (!strcmp(argv[0], "shutdown"))
534 command = &shutdown_driver;
535
536 if (!command)
537 fatalx(EXIT_FAILURE, "Error: unrecognized command [%s]", argv[0]);
538
539 driverpath = xstrdup(DRVPATH); /* set default */
540
541 atexit(exit_cleanup);
542
543 read_upsconf();
544
545 if (argc == 1)
546 send_all_drivers(command);
547 else
548 send_one_driver(command, argv[1]);
549
550 if (exec_error)
551 exit(EXIT_FAILURE);
552
553 exit(EXIT_SUCCESS);
554 }
555