- Do not depend on stat_warn_cont when executing commands.
[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.2 2005/02/01 18:32:01 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 <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
63 struct 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
72 static int      check_percent(int);
73 static int      check_stat(int);
74 static int      check_time(int);
75 static int      get_apm_info(struct apm_info *, int, const char *, int);
76 static void     execute_cmd(const char *, int *);
77 static void     write_emerg(const char *, int *);
78 static void     usage(void); __dead2
79
80 static void
81 usage(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
88 static int
89 check_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
97 static int
98 check_time(int apm_time)
99 {
100         if (apm_time == -1)
101                 return(1);
102
103         return(0);
104 }
105
106 static int
107 check_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 */
116 static int
117 get_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. */  
133 static void
134 execute_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. */
158 static void
159 write_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. */
169 static int
170 getnum(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
191 int
192 main(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);
416                                         execute_cmd(opts->exec_cmd, &exec_cont);
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]);
442                                         execute_cmd(opts->exec_cmd, &exec_cont);
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                 }
470 sleepy_time:
471                 /* Sleep time! Default is 30 seconds */
472                 sleep(check_sec);
473         }
474         return(0);
475 }