kernel/syscons - Fix excessive cpu latency during scrolling
[dragonfly.git] / usr.sbin / battd / battd.c
CommitLineData
7e73274e
LF
1/*
2 * Copyright (c) 2003, 2005 The DragonFly Project. All rights reserved.
6ce6e6d3 3 *
7e73274e
LF
4 * This code is derived from software contributed to The DragonFly Project
5 * by Liam J. Foy <liamfoy@dragonflybsd.org>
6ce6e6d3 6 *
7e73274e
LF
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
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.
6ce6e6d3 20 *
7e73274e
LF
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.
6ce6e6d3 33 *
a57da6de 34 * $DragonFly: src/usr.sbin/battd/battd.c,v 1.12 2008/02/22 04:30:34 swildner Exp $
6b600946
LF
35 *
36 * Dedicated to my grandfather Peter Foy. Goodnight...
7e73274e
LF
37 */
38
39#include <sys/file.h>
40#include <sys/ioctl.h>
41#include <sys/types.h>
42#include <sys/wait.h>
43
44#include <machine/apm_bios.h>
45#include <limits.h>
46
47#include <err.h>
48#include <errno.h>
8bbed431 49#include <libutil.h>
7e73274e
LF
50#include <stdio.h>
51#include <stdlib.h>
52#include <string.h>
53#include <syslog.h>
7e73274e
LF
54#include <unistd.h>
55
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%. */
c65d676d
TN
64#define EXEC_ALL 0
65#define EXEC_ONCE 1
66#define EXEC_DONE 2
7e73274e
LF
67
68struct battd_conf {
69 int alert_per; /* Percentage to alert user on */
70 int alert_time; /* User can also alert when there is only X amount of minutes left */
71 int alert_status; /* Alert when battery is either high, low, critical */
7e73274e
LF
72 const char *apm_dev; /* APM Device */
73 const char *exec_cmd; /* Command to execute if desired */
74};
75
523117aa
LF
76#ifdef DEBUG
77static int f_debug;
78#endif
79
7e73274e
LF
80static int check_percent(int);
81static int check_stat(int);
82static int check_time(int);
83static int get_apm_info(struct apm_info *, int, const char *, int);
84static void execute_cmd(const char *, int *);
85static void write_emerg(const char *, int *);
c65d676d 86static void usage(void) __dead2;
7e73274e
LF
87
88static void
89usage(void)
90{
523117aa 91#ifdef DEBUG
a57da6de 92 fprintf(stderr, "usage: battd [-dEhT] [-c seconds] [-e command] [-f device]\n"
c65d676d 93 " [-p percent] [-s status] [-t minutes]\n");
523117aa 94#else
a57da6de 95 fprintf(stderr, "usage: battd [-EhT] [-c seconds] [-e command] [-f device]\n"
c65d676d 96 " [-p percent] [-s status] [-t minutes]\n");
523117aa 97#endif
6ce6e6d3 98 exit(EXIT_FAILURE);
7e73274e
LF
99}
100
101static int
102check_percent(int apm_per)
103{
104 if (apm_per < 0 || apm_per >= APMUNKNOWN || apm_per > 100)
105 return(1);
106
107 return(0);
108}
109
110static int
111check_time(int apm_time)
112{
c65d676d 113 if (apm_time <= -1)
7e73274e
LF
114 return(1);
115
116 return(0);
117}
118
119static int
120check_stat(int apm_stat)
121{
122 if (apm_stat > 3 || apm_stat < 0)
123 return(1);
124
125 return(0);
126}
127
128/* Fetch battery information */
129static int
130get_apm_info(struct apm_info *ai, int fp_dev, const char *apm_dev, int err_to)
131{
132 if (ioctl(fp_dev, APMIO_GETINFO, ai) == -1) {
133 if (err_to)
6ce6e6d3 134 err(1, "ioctl(APMIO_GETINFO) device: %s", apm_dev);
7e73274e 135 else
6ce6e6d3 136 syslog(LOG_ERR, "ioctl(APMIO_GETINFO) device: %s: %m ",
7e73274e
LF
137 apm_dev);
138
139 return(1);
140 }
141
142 return(0);
143}
144
6ce6e6d3 145/* Execute command. */
7e73274e
LF
146static void
147execute_cmd(const char *exec_cmd, int *exec_cont)
148{
149 pid_t pid;
150 int status;
151
c65d676d 152 if (*exec_cont != EXEC_DONE) {
7e73274e
LF
153 if ((pid = fork()) == -1) {
154 /* Here fork failed */
523117aa
LF
155#ifdef DEBUG
156 if (f_debug)
157 warn("fork failed");
158 else
159#endif
160 syslog(LOG_ERR, "fork failed: %m");
7e73274e
LF
161 } else if (pid == 0) {
162 execl("/bin/sh", "/bin/sh", "-c", exec_cmd, NULL);
163 _exit(EXIT_FAILURE);
164 } else {
165 while (waitpid(pid, &status, 0) != pid)
166 ;
523117aa
LF
167 if (WEXITSTATUS(status)) {
168#ifdef DEBUG
169 if (f_debug)
170 warnx("child exited with code %d", status);
171 else
172#endif
173 syslog(LOG_ERR, "child exited with code %d", status);
174 }
c65d676d
TN
175 if (*exec_cont == EXEC_ONCE)
176 *exec_cont = EXEC_DONE;
7e73274e
LF
177 }
178 }
179}
180
181/* Write warning. */
182static void
183write_emerg(const char *eme_msg, int *warn_cont)
184{
185 if (*warn_cont == 0) {
186 openlog("battd", LOG_EMERG, LOG_CONSOLE);
187 syslog(LOG_ERR, "%s\n", eme_msg);
188 *warn_cont = 1;
189 }
190}
191
192/* Check given numerical arguments. */
193static int
194getnum(const char *str)
195{
c65d676d
TN
196 long val;
197 char *ep;
7e73274e
LF
198
199 errno = 0;
c65d676d
TN
200 val = strtol(str, &ep, 10);
201 if (errno)
202 err(1, "strtol failed: %s", str);
7e73274e 203
c65d676d
TN
204 if (str == ep || *ep != '\0')
205 errx(1, "invalid value: %s", str);
7e73274e
LF
206
207 if (val > INT_MAX || val < INT_MIN) {
208 errno = ERANGE;
209 errc(1, errno, "getnum failed:");
210 }
211
c65d676d 212 return((int)val);
7e73274e
LF
213}
214
215int
216main(int argc, char **argv)
217{
218 struct battd_conf battd_options, *opts;
219 struct apm_info ai;
523117aa 220 int fp_device, exec_cont;
7e73274e
LF
221 int per_warn_cont, time_warn_cont, stat_warn_cont;
222 int check_sec, time_def_alert, def_warn_cont;
223 int c, tmp;
f89b9ed2 224 char msg[80];
7e73274e
LF
225
226 opts = &battd_options;
227
228 /*
229 * As default, we sleep for 30 seconds before
230 * we next call get_apm_info and do the rounds.
231 * The lower the value, the more accurate. Very
232 * low values could cause a decrease in system
233 * performance. We recommend about 30 seconds.
234 */
235
236 check_sec = 30;
237
c65d676d
TN
238 exec_cont = EXEC_ALL;
239 per_warn_cont = stat_warn_cont = 0;
7e73274e
LF
240 time_warn_cont = time_def_alert = def_warn_cont = 0;
241
c65d676d 242 opts->alert_per = DEFAULT_ALERT;
7e73274e
LF
243 opts->alert_time = 0;
244 opts->alert_status = -1;
245 opts->exec_cmd = NULL;
254cf936 246 opts->apm_dev = APM_DEVICE;
7e73274e
LF
247
248 while ((c = getopt(argc, argv, "de:Ep:s:c:f:ht:T")) != -1) {
249 switch (c) {
250 case 'c':
251 /* Parse the check battery interval. */
252 check_sec = getnum(optarg);
253 if (check_sec <= 0)
c65d676d 254 errx(1, "the interval for checking battery "
7e73274e
LF
255 "status must be greater than 0.");
256 break;
523117aa 257#ifdef DEBUG
7e73274e
LF
258 case 'd':
259 /* Debug mode. */
260 f_debug = 1;
261 break;
523117aa 262#endif
7e73274e
LF
263 case 'e':
264 /* Command to execute. */
265 opts->exec_cmd = optarg;
266 break;
267 case 'E':
6ce6e6d3 268 /* Only execute once when any condition has been met. */
c65d676d 269 exec_cont = EXEC_ONCE;
6ce6e6d3 270 break;
7e73274e
LF
271 case 'f':
272 /* Don't use /dev/apm use optarg. */
273 opts->apm_dev = optarg;
7e73274e
LF
274 break;
275 case 'h':
276 /* Print usage and be done! */
277 usage();
278 break;
279 case 'p':
280 /*
281 * Parse percentage to alert on and enable
282 * battd to monitor the battery percentage.
c65d676d 283 * A value of 0 disables battery percentage monitoring.
7e73274e
LF
284 */
285 opts->alert_per = getnum(optarg);
c65d676d 286 if (opts->alert_per < 0 || opts->alert_per > 99)
7e73274e 287 errx(1, "Battery percentage to alert on must be "
c65d676d 288 "between 0 and 99.");
7e73274e
LF
289 break;
290 case 's':
291 /*
292 * Parse status to alert on and enable
293 * battd to monitor the battery status.
294 * We also accept 'high', 'HIGH' or 'HiGh'.
295 */
296 if (strcasecmp(optarg, "high") == 0)
297 opts->alert_status = 0; /* High */
298 else if (strcasecmp(optarg, "low") == 0)
299 opts->alert_status = 1; /* Low */
300 else if (strcasecmp(optarg, "critical") == 0)
301 opts->alert_status = 2; /* Critical (mental) */
302 else {
303 /* No idea, see what we have. */
304 opts->alert_status = getnum(optarg);
305 if (opts->alert_status < 0 || opts->alert_status > 2)
306 errx(1, "Alert status must be between 0 and 2.");
307 }
308 break;
309 case 't':
310 /*
311 * Parse time to alert on and enable
312 * battd to monitor the time percentage.
313 */
314 opts->alert_time = getnum(optarg);
315 if (opts->alert_time <= 0)
523117aa 316 errx(1, "Alert time must be greater than 0 minutes.");
7e73274e
LF
317 break;
318 case 'T':
319 time_def_alert = 1;
320 break;
321 default:
322 usage();
323 }
324 }
325
254cf936
LF
326 if ((fp_device = open(opts->apm_dev, O_RDONLY)) < 0)
327 err(1, "open failed: %s", opts->apm_dev);
7e73274e
LF
328
329 /*
330 * Before we become a daemon, first check whether
331 * the actual function requested is supported. If
332 * not, exit and let the user know.
333 */
334
335 /* Start test */
336 get_apm_info(&ai, fp_device, opts->apm_dev, ERR_OUT);
337
c65d676d 338 if (opts->alert_per > 0)
7e73274e 339 if (check_percent(ai.ai_batt_life))
f89b9ed2
LF
340 errx(1, "invalid/unknown percentage(%d) returned from %s",
341 ai.ai_batt_life, opts->apm_dev);
7e73274e 342
523117aa 343 if (opts->alert_time || time_def_alert)
c65d676d 344 if (check_time(ai.ai_batt_time) && ai.ai_batt_time != -1)
f89b9ed2
LF
345 errx(1, "invalid/unknown time(%d) returned from %s",
346 ai.ai_batt_time, opts->apm_dev);
7e73274e
LF
347
348 if (opts->alert_status)
349 if (check_stat(ai.ai_batt_stat))
f89b9ed2
LF
350 errx(1, "invalid/unknown status(%d) returned from %s",
351 ai.ai_batt_stat, opts->apm_dev);
7e73274e
LF
352 /* End test */
353
523117aa 354#ifdef DEBUG
7e73274e 355 if (f_debug == 0) {
523117aa 356#endif
d316f7c9
JM
357 struct pidfh *pfh = NULL;
358
359 pfh = pidfile_open(NULL, 600, NULL);
6ce6e6d3 360 if (daemon(0, 0) == -1)
7e73274e 361 err(1, "daemon failed");
d316f7c9 362 pidfile_write(pfh);
523117aa 363#ifdef DEBUG
7e73274e 364 }
523117aa 365#endif
7e73274e 366
6ce6e6d3 367 for (;;) {
7e73274e 368 if (get_apm_info(&ai, fp_device, opts->apm_dev,
523117aa 369#ifdef DEBUG
7e73274e 370 f_debug ? ERR_OUT : SYSLOG_OUT))
523117aa
LF
371#else
372 SYSLOG_OUT))
373#endif
7e73274e
LF
374 /* Recoverable - sleep for check_sec seconds */
375 goto sleepy_time;
376
377 /* If we have power, reset the warning values. */
378 if (ai.ai_acline == AC_LINE_IN) {
379 per_warn_cont = 0;
380 time_warn_cont = 0;
381 stat_warn_cont = 0;
382 def_warn_cont = 0;
383 }
384
385 /*
386 * If the battery has main power (AC lead is plugged in)
387 * we skip and sleep for check_sec seconds.
388 */
389 if (ai.ai_acline != AC_LINE_IN) {
390 /*
391 * Battery has no mains power. Time to do
392 * our job!
393 */
394
395 /*
396 * Main Processing loop
397 * --------------------
398 * 1. Check battery percentage if enabled.
399 * 2. Check battery time remaining if enabled.
400 * 3. Check battery status if enabled.
c65d676d 401 * 4. Deal with time default alert.
7e73274e
LF
402 */
403
404 /* 1. Check battery percentage if enabled */
405 if (opts->alert_per) {
406 if (check_percent(ai.ai_batt_life)) {
523117aa 407#ifdef DEBUG
7e73274e 408 if (f_debug) {
487c4d01 409 printf("Invalid percentage (%d) received from %s.\n",
254cf936 410 ai.ai_batt_life, opts->apm_dev);
7e73274e 411 } else {
523117aa 412#endif
487c4d01 413 syslog(LOG_ERR, "Invalid percentage received from %s.",
254cf936 414 opts->apm_dev);
523117aa 415#ifdef DEBUG
7e73274e 416 }
523117aa 417#endif
7e73274e
LF
418 continue;
419 }
6ce6e6d3 420
7e73274e
LF
421 if (ai.ai_batt_life <= (u_int)opts->alert_per) {
422 tmp = (ai.ai_batt_life == (u_int)opts->alert_per);
423 snprintf(msg, sizeof(msg), "battery has %s %d%%\n",
487c4d01 424 tmp ? "reached" : "fallen below",
7e73274e
LF
425 opts->alert_per);
426 execute_cmd(opts->exec_cmd, &exec_cont);
427 write_emerg(msg, &per_warn_cont);
428 }
6ce6e6d3 429 }
7e73274e
LF
430
431 /* 2. Check battery time remaining if enabled */
432 if (opts->alert_time) {
433 if (check_time(ai.ai_batt_time)) {
523117aa 434#ifdef DEBUG
7e73274e 435 if (f_debug) {
487c4d01 436 printf("Invalid time value (%d) received from %s.\n",
254cf936 437 ai.ai_batt_time, opts->apm_dev);
7e73274e 438 } else {
523117aa 439#endif
487c4d01 440 syslog(LOG_ERR, "Invalid time value received from %s.",
254cf936 441 opts->apm_dev);
523117aa 442#ifdef DEBUG
7e73274e 443 }
523117aa 444#endif
7e73274e
LF
445 continue;
446 }
6ce6e6d3 447
7e73274e
LF
448 if (ai.ai_batt_time <= (opts->alert_time * SECONDS)) {
449 int h, m, s;
450 char tmp_time[sizeof "tt:tt:tt" + 1];
451 h = ai.ai_batt_time;
452 s = h % 60;
453 h /= 60;
454 m = h % 60;
455 h /= 60;
456 snprintf(tmp_time, sizeof(tmp_time), "%d:%d:%d\n", h, m, s);
457 tmp = (ai.ai_batt_time == opts->alert_time);
c65d676d 458 snprintf(msg, sizeof(msg), "battery has %s %d(%s) minutes "
487c4d01 459 "remaining\n", tmp ? "reached" : "fallen below",
7e73274e 460 ai.ai_batt_time / SECONDS, tmp_time);
50146dc0 461 execute_cmd(opts->exec_cmd, &exec_cont);
7e73274e 462 write_emerg(msg, &time_warn_cont);
6ce6e6d3 463 }
7e73274e
LF
464 }
465
466 /* 3. Check battery status if enabled */
467 if (opts->alert_status != -1) {
468 if (check_stat(ai.ai_batt_stat)) {
523117aa 469#ifdef DEBUG
7e73274e 470 if (f_debug) {
487c4d01 471 printf("Invalid status value (%d) received from %s.\n",
254cf936 472 ai.ai_batt_life, opts->apm_dev);
6ce6e6d3 473 } else {
523117aa 474#endif
487c4d01 475 syslog(LOG_ERR, "Invalid status value received from %s.",
254cf936 476 opts->apm_dev);
523117aa 477#ifdef DEBUG
7e73274e 478 }
523117aa 479#endif
7e73274e
LF
480 continue;
481 }
482
483 if (ai.ai_batt_stat >= (u_int)opts->alert_status) {
f89b9ed2 484 const char *batt_status[] = {"high", "low", "critical"};
7e73274e
LF
485
486 tmp = (ai.ai_batt_stat == (u_int)opts->alert_status);
487 snprintf(msg, sizeof(msg), "battery has %s '%s' status\n",
487c4d01 488 tmp ? "reached" : "fallen below",
7e73274e 489 batt_status[ai.ai_batt_stat]);
50146dc0 490 execute_cmd(opts->exec_cmd, &exec_cont);
7e73274e
LF
491 write_emerg(msg, &stat_warn_cont);
492 }
493 }
494
c65d676d 495 /* 4. Deal with time default alert. */
7e73274e
LF
496 if (time_def_alert) {
497 if (check_time(ai.ai_batt_time)) {
c65d676d
TN
498#ifdef DEBUG
499 if (f_debug) {
500 printf("Invalid time value (%d) received from %s.\n",
501 ai.ai_batt_time, opts->apm_dev);
502 } else {
503#endif
504 syslog(LOG_ERR, "Invalid time value received from %s.",
505 opts->apm_dev);
506#ifdef DEBUG
507 }
508#endif
509 continue;
7e73274e 510 }
7e73274e 511
c65d676d
TN
512 if (ai.ai_batt_time <= DEFAULT_ALERT * SECONDS) {
513 snprintf(msg, sizeof(msg), "WARNING! battery has "
514 "only roughly %d minutes remaining!\n",
515 ai.ai_batt_time / SECONDS);
516 write_emerg(msg, &def_warn_cont);
517 }
7e73274e
LF
518 }
519
520 }
521sleepy_time:
522 /* Sleep time! Default is 30 seconds */
523 sleep(check_sec);
524 }
525 return(0);
526}