Merge from vendor branch OPENSSL:
[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.10 2005/08/28 20:15:38 liamfoy Exp $
35  *
36  * Dedicated to my grandfather Peter Foy. Goodnight... 
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>
49 #include <libutil.h>
50 #include <stdio.h>
51 #include <stdlib.h>
52 #include <string.h>
53 #include <syslog.h>
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%. */
64
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 */     
71 };
72
73 #ifdef DEBUG
74 static int f_debug;
75 #endif
76
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
84
85 static void
86 usage(void)
87 {
88 #ifdef DEBUG
89         fprintf(stderr, "usage: battd [-dEhT] [-p percent] [-t minutes] [-s status]\n"
90                         "             [-f device] [-e command] [-c seconds]\n");
91 #else
92         fprintf(stderr, "usage: battd [-EhT] [-p percent] [-t minutes] [-s status]\n"
93                         "             [-f device] [-e command] [-c seconds]\n");
94 #endif
95         exit(EXIT_FAILURE);
96 }
97
98 static int
99 check_percent(int apm_per)
100 {
101         if (apm_per < 0 || apm_per >= APMUNKNOWN || apm_per > 100)
102                 return(1);
103
104         return(0);
105 }
106
107 static int
108 check_time(int apm_time)
109 {
110         if (apm_time == -1)
111                 return(1);
112
113         return(0);
114 }
115
116 static int
117 check_stat(int apm_stat)
118 {
119         if (apm_stat > 3 || apm_stat < 0)
120                 return(1);
121
122         return(0);
123 }
124
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)
128 {
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);
135
136                 return(1);
137         }
138
139         return(0);
140 }
141
142 /* Execute command. */
143 static void
144 execute_cmd(const char *exec_cmd, int *exec_cont)
145 {
146         pid_t pid;
147         int status;
148                 
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)
163                                 ;
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);
171                         }
172                         if (*exec_cont)
173                                 exec_cmd = NULL;
174                 }
175         }
176 }
177
178 /* Write warning. */
179 static void
180 write_emerg(const char *eme_msg, int *warn_cont)
181 {
182         if (*warn_cont == 0) {
183                 openlog("battd", LOG_EMERG, LOG_CONSOLE);
184                 syslog(LOG_ERR, "%s\n", eme_msg);
185                 *warn_cont = 1;
186         }
187 }
188
189 /* Check given numerical arguments. */
190 static int
191 getnum(const char *str)
192 {
193         long val;
194         char *ep;
195
196         errno = 0;
197         val = strtol(str, &ep, 10);
198         if (errno)
199                 err(1, "strtol failed: %s", str);
200
201         if (str == ep || *ep != '\0')
202                 errx(1, "invalid value: %s", str);
203
204         if (val > INT_MAX || val < INT_MIN) {
205                 errno = ERANGE;
206                 errc(1, errno, "getnum failed:");
207         }
208
209         return((int)val);
210 }
211
212 int
213 main(int argc, char **argv)
214 {
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];
222
223         opts = &battd_options;
224
225         /*
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.
231          */
232
233         check_sec = 30;
234
235         exec_cont = per_warn_cont = stat_warn_cont = 0;
236         time_warn_cont = time_def_alert = def_warn_cont = 0;
237
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;
243
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':
276                         /*
277                          * Parse percentage to alert on and enable
278                          * battd to monitor the battery percentage.
279                          */
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':
286                         /*
287                          * Parse status to alert on and enable
288                          * battd to monitor the battery status.
289                          * We also accept 'high', 'HIGH' or 'HiGh'.
290                          */
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.");
302                         }
303                         break;
304                 case 't':
305                         /*
306                          * Parse time to alert on and enable
307                          * battd to monitor the time percentage.
308                          */
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();
318                 }
319         }
320
321         if ((fp_device = open(opts->apm_dev, O_RDONLY)) < 0)
322                 err(1, "open failed: %s", opts->apm_dev);
323
324         /* 
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.
328          */
329
330         /* Start test */
331         get_apm_info(&ai, fp_device, opts->apm_dev, ERR_OUT);
332
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);
337
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);
342
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 */
348
349 #ifdef DEBUG
350         if (f_debug == 0) {
351 #endif
352                 if (daemon(0, 0) == -1)
353                         err(1, "daemon failed");
354                 pidfile(getprogname());
355 #ifdef DEBUG
356         }
357 #endif
358
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;
368
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;
375                 }
376
377                 /*
378                  * If the battery has main power (AC lead is plugged in)
379                  * we skip and sleep for check_sec seconds.
380                  */
381                 if (ai.ai_acline != AC_LINE_IN) {
382                         /*
383                          * Battery has no mains power. Time to do
384                          * our job!
385                          */
386
387                         /*
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.
394                          */ 
395
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
408                                         }
409 #endif
410                                         continue;
411                                 }
412
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);
420                                 }
421                         }
422
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
435                                         }
436 #endif
437                                         continue;
438                                 }
439         
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);
455                                 }
456                         }
457
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
470                                         }
471 #endif
472                                         continue;
473                                 }
474
475                                 if (ai.ai_batt_stat >= (u_int)opts->alert_status) {
476                                         const char *batt_status[] = {"high", "low", "critical"};
477
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);
484                                 }
485                         }
486
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);
495                                                 }
496                                 }
497                         }
498
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);
507                         }
508
509                 }
510 sleepy_time:
511                 /* Sleep time! Default is 30 seconds */
512                 sleep(check_sec);
513         }
514         return(0);
515 }