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