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