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