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