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