Sync with FreeBSD (adds reload).
[dragonfly.git] / usr.sbin / battd / battd.c
blob7ce0995cdcdb07336ffe31aee6e6705c7659aee0
1 /*
2 * Copyright (c) 2003, 2005 The DragonFly Project. All rights reserved.
4 * This code is derived from software contributed to The DragonFly Project
5 * by Liam J. Foy <liamfoy@dragonflybsd.org>
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in
15 * the documentation and/or other materials provided with the
16 * distribution.
17 * 3. Neither the name of The DragonFly Project nor the names of its
18 * contributors may be used to endorse or promote products derived
19 * from this software without specific, prior written permission.
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 * SUCH DAMAGE.
34 * $DragonFly: src/usr.sbin/battd/battd.c,v 1.12 2008/02/22 04:30:34 swildner Exp $
36 * Dedicated to my grandfather Peter Foy. Goodnight...
39 #include <sys/file.h>
40 #include <sys/ioctl.h>
41 #include <sys/types.h>
42 #include <sys/wait.h>
44 #include <machine/apm_bios.h>
45 #include <limits.h>
47 #include <err.h>
48 #include <errno.h>
49 #include <libutil.h>
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <string.h>
53 #include <syslog.h>
54 #include <unistd.h>
56 #define APMUNKNOWN 255 /* Unknown value. */
57 #define AC_LINE_IN 1 /* AC Line Status values. */
58 #define AC_OFFLINE 0
59 #define SECONDS 60
60 #define APM_DEVICE "/dev/apm" /* Default APM Device. */
61 #define ERR_OUT 1 /* Values for error writing. */
62 #define SYSLOG_OUT 0
63 #define DEFAULT_ALERT 10 /* Default alert is 10%. */
65 struct battd_conf {
66 int alert_per; /* Percentage to alert user on */
67 int alert_time; /* User can also alert when there is only X amount of minutes left */
68 int alert_status; /* Alert when battery is either high, low, critical */
69 const char *apm_dev; /* APM Device */
70 const char *exec_cmd; /* Command to execute if desired */
73 #ifdef DEBUG
74 static int f_debug;
75 #endif
77 static int check_percent(int);
78 static int check_stat(int);
79 static int check_time(int);
80 static int get_apm_info(struct apm_info *, int, const char *, int);
81 static void execute_cmd(const char *, int *);
82 static void write_emerg(const char *, int *);
83 static void usage(void); __dead2
85 static void
86 usage(void)
88 #ifdef DEBUG
89 fprintf(stderr, "usage: battd [-dEhT] [-c seconds] [-e command] [-f device]\n"
90 " [-p percentage] [-s status] [-t minutes]\n");
91 #else
92 fprintf(stderr, "usage: battd [-EhT] [-c seconds] [-e command] [-f device]\n"
93 " [-p percentage] [-s status] [-t minutes]\n");
94 #endif
95 exit(EXIT_FAILURE);
98 static int
99 check_percent(int apm_per)
101 if (apm_per < 0 || apm_per >= APMUNKNOWN || apm_per > 100)
102 return(1);
104 return(0);
107 static int
108 check_time(int apm_time)
110 if (apm_time == -1)
111 return(1);
113 return(0);
116 static int
117 check_stat(int apm_stat)
119 if (apm_stat > 3 || apm_stat < 0)
120 return(1);
122 return(0);
125 /* Fetch battery information */
126 static int
127 get_apm_info(struct apm_info *ai, int fp_dev, const char *apm_dev, int err_to)
129 if (ioctl(fp_dev, APMIO_GETINFO, ai) == -1) {
130 if (err_to)
131 err(1, "ioctl(APMIO_GETINFO) device: %s", apm_dev);
132 else
133 syslog(LOG_ERR, "ioctl(APMIO_GETINFO) device: %s: %m ",
134 apm_dev);
136 return(1);
139 return(0);
142 /* Execute command. */
143 static void
144 execute_cmd(const char *exec_cmd, int *exec_cont)
146 pid_t pid;
147 int status;
149 if (exec_cmd != NULL) {
150 if ((pid = fork()) == -1) {
151 /* Here fork failed */
152 #ifdef DEBUG
153 if (f_debug)
154 warn("fork failed");
155 else
156 #endif
157 syslog(LOG_ERR, "fork failed: %m");
158 } else if (pid == 0) {
159 execl("/bin/sh", "/bin/sh", "-c", exec_cmd, NULL);
160 _exit(EXIT_FAILURE);
161 } else {
162 while (waitpid(pid, &status, 0) != pid)
164 if (WEXITSTATUS(status)) {
165 #ifdef DEBUG
166 if (f_debug)
167 warnx("child exited with code %d", status);
168 else
169 #endif
170 syslog(LOG_ERR, "child exited with code %d", status);
172 if (*exec_cont)
173 exec_cmd = NULL;
178 /* Write warning. */
179 static void
180 write_emerg(const char *eme_msg, int *warn_cont)
182 if (*warn_cont == 0) {
183 openlog("battd", LOG_EMERG, LOG_CONSOLE);
184 syslog(LOG_ERR, "%s\n", eme_msg);
185 *warn_cont = 1;
189 /* Check given numerical arguments. */
190 static int
191 getnum(const char *str)
193 long val;
194 char *ep;
196 errno = 0;
197 val = strtol(str, &ep, 10);
198 if (errno)
199 err(1, "strtol failed: %s", str);
201 if (str == ep || *ep != '\0')
202 errx(1, "invalid value: %s", str);
204 if (val > INT_MAX || val < INT_MIN) {
205 errno = ERANGE;
206 errc(1, errno, "getnum failed:");
209 return((int)val);
213 main(int argc, char **argv)
215 struct battd_conf battd_options, *opts;
216 struct apm_info ai;
217 int fp_device, exec_cont;
218 int per_warn_cont, time_warn_cont, stat_warn_cont;
219 int check_sec, time_def_alert, def_warn_cont;
220 int c, tmp;
221 char msg[80];
223 opts = &battd_options;
226 * As default, we sleep for 30 seconds before
227 * we next call get_apm_info and do the rounds.
228 * The lower the value, the more accurate. Very
229 * low values could cause a decrease in system
230 * performance. We recommend about 30 seconds.
233 check_sec = 30;
235 exec_cont = per_warn_cont = stat_warn_cont = 0;
236 time_warn_cont = time_def_alert = def_warn_cont = 0;
238 opts->alert_per = 0;
239 opts->alert_time = 0;
240 opts->alert_status = -1;
241 opts->exec_cmd = NULL;
242 opts->apm_dev = APM_DEVICE;
244 while ((c = getopt(argc, argv, "de:Ep:s:c:f:ht:T")) != -1) {
245 switch (c) {
246 case 'c':
247 /* Parse the check battery interval. */
248 check_sec = getnum(optarg);
249 if (check_sec <= 0)
250 errx(1, "the interval for checking battery"
251 "status must be greater than 0.");
252 break;
253 #ifdef DEBUG
254 case 'd':
255 /* Debug mode. */
256 f_debug = 1;
257 break;
258 #endif
259 case 'e':
260 /* Command to execute. */
261 opts->exec_cmd = optarg;
262 break;
263 case 'E':
264 /* Only execute once when any condition has been met. */
265 exec_cont = 1;
266 break;
267 case 'f':
268 /* Don't use /dev/apm use optarg. */
269 opts->apm_dev = optarg;
270 break;
271 case 'h':
272 /* Print usage and be done! */
273 usage();
274 break;
275 case 'p':
277 * Parse percentage to alert on and enable
278 * battd to monitor the battery percentage.
280 opts->alert_per = getnum(optarg);
281 if (opts->alert_per <= 0 || opts->alert_per > 100)
282 errx(1, "Battery percentage to alert on must be "
283 "greater than 0 and less than 100.");
284 break;
285 case 's':
287 * Parse status to alert on and enable
288 * battd to monitor the battery status.
289 * We also accept 'high', 'HIGH' or 'HiGh'.
291 if (strcasecmp(optarg, "high") == 0)
292 opts->alert_status = 0; /* High */
293 else if (strcasecmp(optarg, "low") == 0)
294 opts->alert_status = 1; /* Low */
295 else if (strcasecmp(optarg, "critical") == 0)
296 opts->alert_status = 2; /* Critical (mental) */
297 else {
298 /* No idea, see what we have. */
299 opts->alert_status = getnum(optarg);
300 if (opts->alert_status < 0 || opts->alert_status > 2)
301 errx(1, "Alert status must be between 0 and 2.");
303 break;
304 case 't':
306 * Parse time to alert on and enable
307 * battd to monitor the time percentage.
309 opts->alert_time = getnum(optarg);
310 if (opts->alert_time <= 0)
311 errx(1, "Alert time must be greater than 0 minutes.");
312 break;
313 case 'T':
314 time_def_alert = 1;
315 break;
316 default:
317 usage();
321 if ((fp_device = open(opts->apm_dev, O_RDONLY)) < 0)
322 err(1, "open failed: %s", opts->apm_dev);
325 * Before we become a daemon, first check whether
326 * the actual function requested is supported. If
327 * not, exit and let the user know.
330 /* Start test */
331 get_apm_info(&ai, fp_device, opts->apm_dev, ERR_OUT);
333 if (opts->alert_per >= 0)
334 if (check_percent(ai.ai_batt_life))
335 errx(1, "invalid/unknown percentage(%d) returned from %s",
336 ai.ai_batt_life, opts->apm_dev);
338 if (opts->alert_time || time_def_alert)
339 if (check_time(ai.ai_batt_time))
340 errx(1, "invalid/unknown time(%d) returned from %s",
341 ai.ai_batt_time, opts->apm_dev);
343 if (opts->alert_status)
344 if (check_stat(ai.ai_batt_stat))
345 errx(1, "invalid/unknown status(%d) returned from %s",
346 ai.ai_batt_stat, opts->apm_dev);
347 /* End test */
349 #ifdef DEBUG
350 if (f_debug == 0) {
351 #endif
352 if (daemon(0, 0) == -1)
353 err(1, "daemon failed");
354 pidfile(NULL);
355 #ifdef DEBUG
357 #endif
359 for (;;) {
360 if (get_apm_info(&ai, fp_device, opts->apm_dev,
361 #ifdef DEBUG
362 f_debug ? ERR_OUT : SYSLOG_OUT))
363 #else
364 SYSLOG_OUT))
365 #endif
366 /* Recoverable - sleep for check_sec seconds */
367 goto sleepy_time;
369 /* If we have power, reset the warning values. */
370 if (ai.ai_acline == AC_LINE_IN) {
371 per_warn_cont = 0;
372 time_warn_cont = 0;
373 stat_warn_cont = 0;
374 def_warn_cont = 0;
378 * If the battery has main power (AC lead is plugged in)
379 * we skip and sleep for check_sec seconds.
381 if (ai.ai_acline != AC_LINE_IN) {
383 * Battery has no mains power. Time to do
384 * our job!
388 * Main Processing loop
389 * --------------------
390 * 1. Check battery percentage if enabled.
391 * 2. Check battery time remaining if enabled.
392 * 3. Check battery status if enabled.
393 * 4. Deal with default alerts.
396 /* 1. Check battery percentage if enabled */
397 if (opts->alert_per) {
398 if (check_percent(ai.ai_batt_life)) {
399 #ifdef DEBUG
400 if (f_debug) {
401 printf("Invalid percentage (%d) received from %s.\n",
402 ai.ai_batt_life, opts->apm_dev);
403 } else {
404 #endif
405 syslog(LOG_ERR, "Invalid percentage received from %s.",
406 opts->apm_dev);
407 #ifdef DEBUG
409 #endif
410 continue;
413 if (ai.ai_batt_life <= (u_int)opts->alert_per) {
414 tmp = (ai.ai_batt_life == (u_int)opts->alert_per);
415 snprintf(msg, sizeof(msg), "battery has %s %d%%\n",
416 tmp ? "reached" : "fallen below",
417 opts->alert_per);
418 execute_cmd(opts->exec_cmd, &exec_cont);
419 write_emerg(msg, &per_warn_cont);
423 /* 2. Check battery time remaining if enabled */
424 if (opts->alert_time) {
425 if (check_time(ai.ai_batt_time)) {
426 #ifdef DEBUG
427 if (f_debug) {
428 printf("Invalid time value (%d) received from %s.\n",
429 ai.ai_batt_time, opts->apm_dev);
430 } else {
431 #endif
432 syslog(LOG_ERR, "Invalid time value received from %s.",
433 opts->apm_dev);
434 #ifdef DEBUG
436 #endif
437 continue;
440 if (ai.ai_batt_time <= (opts->alert_time * SECONDS)) {
441 int h, m, s;
442 char tmp_time[sizeof "tt:tt:tt" + 1];
443 h = ai.ai_batt_time;
444 s = h % 60;
445 h /= 60;
446 m = h % 60;
447 h /= 60;
448 snprintf(tmp_time, sizeof(tmp_time), "%d:%d:%d\n", h, m, s);
449 tmp = (ai.ai_batt_time == opts->alert_time);
450 snprintf(msg, sizeof(msg), "battery has %s %d(%s) minutes"
451 "remaining\n", tmp ? "reached" : "fallen below",
452 ai.ai_batt_time / SECONDS, tmp_time);
453 execute_cmd(opts->exec_cmd, &exec_cont);
454 write_emerg(msg, &time_warn_cont);
458 /* 3. Check battery status if enabled */
459 if (opts->alert_status != -1) {
460 if (check_stat(ai.ai_batt_stat)) {
461 #ifdef DEBUG
462 if (f_debug) {
463 printf("Invalid status value (%d) received from %s.\n",
464 ai.ai_batt_life, opts->apm_dev);
465 } else {
466 #endif
467 syslog(LOG_ERR, "Invalid status value received from %s.",
468 opts->apm_dev);
469 #ifdef DEBUG
471 #endif
472 continue;
475 if (ai.ai_batt_stat >= (u_int)opts->alert_status) {
476 const char *batt_status[] = {"high", "low", "critical"};
478 tmp = (ai.ai_batt_stat == (u_int)opts->alert_status);
479 snprintf(msg, sizeof(msg), "battery has %s '%s' status\n",
480 tmp ? "reached" : "fallen below",
481 batt_status[ai.ai_batt_stat]);
482 execute_cmd(opts->exec_cmd, &exec_cont);
483 write_emerg(msg, &stat_warn_cont);
487 /* 4. Deal with default alerts. */
488 if (time_def_alert) {
489 if (check_time(ai.ai_batt_time)) {
490 if (ai.ai_batt_time <= DEFAULT_ALERT * SECONDS) {
491 snprintf(msg, sizeof(msg), "WARNING! battery only"
492 "has roughly %d minutes remaining!\n",
493 ai.ai_batt_time / SECONDS);
494 write_emerg(msg, NULL);
499 if (ai.ai_batt_life <= DEFAULT_ALERT) {
500 tmp = (ai.ai_batt_life == DEFAULT_ALERT);
501 snprintf(msg, sizeof(msg), "WARNING! battery has %s %d%%\n",
502 tmp ? "reached" : "fallen below",
503 DEFAULT_ALERT);
504 if (!def_warn_cont)
505 execute_cmd(opts->exec_cmd, &exec_cont);
506 write_emerg(msg, &def_warn_cont);
510 sleepy_time:
511 /* Sleep time! Default is 30 seconds */
512 sleep(check_sec);
514 return(0);