c295d55ac6fd670b238d721f88f63bd9fa8c2d89
[dragonfly.git] / usr.sbin / apmd / apmd.c
1 /*-
2  * APM (Advanced Power Management) Event Dispatcher
3  *
4  * Copyright (c) 1999 Mitsuru IWASAKI <iwasaki@FreeBSD.org>
5  * Copyright (c) 1999 KOIE Hidetaka <koie@suri.co.jp>
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
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 the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  *
29  * $FreeBSD: src/usr.sbin/apmd/apmd.c,v 1.3.2.1 2001/08/13 17:30:30 nsayer Exp $
30  * $DragonFly: src/usr.sbin/apmd/apmd.c,v 1.3 2003/11/15 20:33:42 eirikn Exp $
31  */
32
33 #include <assert.h>
34 #include <bitstring.h>
35 #include <err.h>
36 #include <errno.h>
37 #include <fcntl.h>
38 #include <paths.h>
39 #include <signal.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <syslog.h>
44 #include <unistd.h>
45 #include <sys/ioctl.h>
46 #include <sys/types.h>
47 #include <sys/time.h>
48 #include <sys/wait.h>
49 #include <machine/apm_bios.h>
50
51 #include "apmd.h"
52
53 extern int      yyparse(void);
54
55 int             debug_level = 0;
56 int             verbose = 0;
57 const char      *apmd_configfile = APMD_CONFIGFILE;
58 const char      *apmd_pidfile = APMD_PIDFILE;
59 int             apmctl_fd = -1, apmnorm_fd = -1;
60
61 /*
62  * table of event handlers
63  */
64 #define EVENT_CONFIG_INITIALIZER(EV,R) { #EV, NULL, R },
65 struct event_config events[EVENT_MAX] = {
66         EVENT_CONFIG_INITIALIZER(NOEVENT, 0)
67         EVENT_CONFIG_INITIALIZER(STANDBYREQ, 1)
68         EVENT_CONFIG_INITIALIZER(SUSPENDREQ, 1)
69         EVENT_CONFIG_INITIALIZER(NORMRESUME, 0)
70         EVENT_CONFIG_INITIALIZER(CRITRESUME, 0)
71         EVENT_CONFIG_INITIALIZER(BATTERYLOW, 0)
72         EVENT_CONFIG_INITIALIZER(POWERSTATECHANGE, 0)
73         EVENT_CONFIG_INITIALIZER(UPDATETIME, 0)
74         EVENT_CONFIG_INITIALIZER(CRITSUSPEND, 1)
75         EVENT_CONFIG_INITIALIZER(USERSTANDBYREQ, 1)
76         EVENT_CONFIG_INITIALIZER(USERSUSPENDREQ, 1)
77         EVENT_CONFIG_INITIALIZER(STANDBYRESUME, 0)
78         EVENT_CONFIG_INITIALIZER(CAPABILITIESCHANGE, 0)
79 };
80
81 /*
82  * List of battery events
83  */
84 struct battery_watch_event *battery_watch_list = NULL;
85
86 #define BATT_CHK_INTV 10 /* how many seconds between battery state checks? */
87
88 /*
89  * default procedure
90  */
91 struct event_cmd *
92 event_cmd_default_clone(void *this)
93 {
94         struct event_cmd * oldone = this;
95         struct event_cmd * newone = malloc(oldone->len);
96
97         newone->next = NULL;
98         newone->len = oldone->len;
99         newone->name = oldone->name;
100         newone->op = oldone->op;
101         return newone;
102 }
103
104 /*
105  * exec command
106  */
107 int
108 event_cmd_exec_act(void *this)
109 {
110         struct event_cmd_exec * p = this;
111         int status = -1;
112         pid_t pid;
113
114         switch ((pid = fork())) {
115         case -1:
116                 (void) warn("cannot fork");
117                 goto out;
118         case 0:
119                 /* child process */
120                 execl(_PATH_BSHELL, "sh", "-c", p->line, (char *)NULL);
121                 _exit(127);
122         default:
123                 /* parent process */
124                 do {
125                         pid = waitpid(pid, &status, 0);
126                 } while (pid == -1 && errno == EINTR);
127                 break;
128         }
129  out:
130         return status;
131 }
132 void
133 event_cmd_exec_dump(void *this, FILE *fp)
134 {
135         fprintf(fp, " \"%s\"", ((struct event_cmd_exec *)this)->line);
136 }
137 struct event_cmd *
138 event_cmd_exec_clone(void *this)
139 {
140         struct event_cmd_exec * newone = (struct event_cmd_exec *) event_cmd_default_clone(this);
141         struct event_cmd_exec * oldone = this;
142
143         newone->evcmd.next = NULL;
144         newone->evcmd.len = oldone->evcmd.len;
145         newone->evcmd.name = oldone->evcmd.name;
146         newone->evcmd.op = oldone->evcmd.op;
147         if ((newone->line = strdup(oldone->line)) == NULL)
148                 err(1, "out of memory");
149         return (struct event_cmd *) newone;
150 }
151 void
152 event_cmd_exec_free(void *this)
153 {
154         free(((struct event_cmd_exec *)this)->line);
155 }
156 struct event_cmd_op event_cmd_exec_ops = {
157         event_cmd_exec_act,
158         event_cmd_exec_dump,
159         event_cmd_exec_clone,
160         event_cmd_exec_free
161 };
162
163 /*
164  * reject commad
165  */
166 int
167 event_cmd_reject_act(void *this)
168 {
169         int rc = -1;
170
171         if (ioctl(apmctl_fd, APMIO_REJECTLASTREQ, NULL)) {
172                 syslog(LOG_NOTICE, "fail to reject\n");
173                 goto out;
174         }
175         rc = 0;
176  out:
177         return rc;
178 }
179 struct event_cmd_op event_cmd_reject_ops = {
180         event_cmd_reject_act,
181         NULL,
182         event_cmd_default_clone,
183         NULL
184 };
185
186 /*
187  * manipulate event_config
188  */
189 struct event_cmd *
190 clone_event_cmd_list(struct event_cmd *p)
191 {
192         struct event_cmd dummy;
193         struct event_cmd *q = &dummy;
194         for ( ;p; p = p->next) {
195                 assert(p->op->clone);
196                 if ((q->next = p->op->clone(p)) == NULL)
197                         (void) err(1, "out of memory");
198                 q = q->next;
199         }
200         q->next = NULL;
201         return dummy.next;
202 }
203 void
204 free_event_cmd_list(struct event_cmd *p)
205 {
206         struct event_cmd * q;
207         for ( ; p ; p = q) {
208                 q = p->next;
209                 if (p->op->free)
210                         p->op->free(p);
211                 free(p);
212         }
213 }
214 int
215 register_battery_handlers(
216         int level, int direction,
217         struct event_cmd *cmdlist)
218 {
219         /*
220          * level is negative if it's in "minutes", non-negative if
221          * percentage.
222          *
223          * direction =1 means we care about this level when charging,
224          * direction =-1 means we care about it when discharging.
225          */
226         if (level>100) /* percentage > 100 */
227                 return -1;
228         if (abs(direction) != 1) /* nonsense direction value */
229                 return -1;
230
231         if (cmdlist) {
232                 struct battery_watch_event *we;
233                 
234                 if ((we = malloc(sizeof(struct battery_watch_event))) == NULL)
235                         (void) err(1, "out of memory");
236
237                 we->next = battery_watch_list; /* starts at NULL */
238                 battery_watch_list = we;
239                 we->level = abs(level);
240                 we->type = (level<0)?BATTERY_MINUTES:BATTERY_PERCENT;
241                 we->direction = (direction<0)?BATTERY_DISCHARGING:
242                         BATTERY_CHARGING;
243                 we->done = 0;
244                 we->cmdlist = clone_event_cmd_list(cmdlist);
245         }
246         return 0;
247 }
248 int
249 register_apm_event_handlers(
250         bitstr_t bit_decl(evlist, EVENT_MAX),
251         struct event_cmd *cmdlist)
252 {
253         if (cmdlist) {
254                 bitstr_t bit_decl(tmp, EVENT_MAX);
255                 memcpy(&tmp, evlist, bitstr_size(EVENT_MAX));
256
257                 for (;;) {
258                         int n;
259                         struct event_cmd *p;
260                         struct event_cmd *q;
261                         bit_ffs(tmp, EVENT_MAX, &n);
262                         if (n < 0)
263                                 break;
264                         p = events[n].cmdlist;
265                         if ((q = clone_event_cmd_list(cmdlist)) == NULL)
266                                 (void) err(1, "out of memory");
267                         if (p) {
268                                 while (p->next != NULL)
269                                         p = p->next;
270                                 p->next = q;
271                         } else {
272                                 events[n].cmdlist = q;
273                         }
274                         bit_clear(tmp, n);
275                 }
276         }
277         return 0;
278 }
279
280 /*
281  * execute command
282  */
283 int
284 exec_run_cmd(struct event_cmd *p)
285 {
286         int status = 0;
287
288         for (; p; p = p->next) {
289                 assert(p->op->act);
290                 if (verbose)
291                         syslog(LOG_INFO, "action: %s", p->name);
292                 status = p->op->act(p);
293                 if (status) {
294                         syslog(LOG_NOTICE, "command finished with %d\n", status);
295                         break;
296                 }
297         }
298         return status;
299 }
300
301 /*
302  * execute command -- the event version
303  */
304 int
305 exec_event_cmd(struct event_config *ev)
306 {
307         int status = 0;
308
309         status = exec_run_cmd(ev->cmdlist);
310         if (status && ev->rejectable) {
311                 syslog(LOG_ERR, "canceled");
312                 (void) event_cmd_reject_act(NULL);
313         }
314         return status;
315 }
316
317 /*
318  * read config file
319  */
320 extern FILE * yyin;
321 extern int yydebug;
322
323 void
324 read_config(void)
325 {
326         int i;
327
328         if ((yyin = fopen(apmd_configfile, "r")) == NULL) {
329                 (void) err(1, "cannot open config file");
330         }
331
332 #ifdef DEBUG
333         yydebug = debug_level;
334 #endif
335
336         if (yyparse() != 0)
337                 (void) err(1, "cannot parse config file");
338
339         fclose(yyin);
340
341         /* enable events */
342         for (i = 0; i < EVENT_MAX; i++) {
343                 if (events[i].cmdlist) {
344                         u_int event_type = i;
345                         if (write(apmctl_fd, &event_type, sizeof(u_int)) == -1) {
346                                 (void) err(1, "cannot enable event 0x%x", event_type);
347                         }
348                 }
349         }
350 }
351
352 void
353 dump_config(void)
354 {
355         int i;
356         struct battery_watch_event *q;
357
358         for (i = 0; i < EVENT_MAX; i++) {
359                 struct event_cmd * p;
360                 if ((p = events[i].cmdlist)) {
361                         fprintf(stderr, "apm_event %s {\n", events[i].name);
362                         for ( ; p ; p = p->next) {
363                                 fprintf(stderr, "\t%s", p->name);
364                                 if (p->op->dump)
365                                         p->op->dump(p, stderr);
366                                 fprintf(stderr, ";\n");
367                         }
368                         fprintf(stderr, "}\n");
369                 }
370         }
371         for (q = battery_watch_list ; q != NULL ; q = q -> next) {
372                 struct event_cmd * p;
373                 fprintf(stderr, "apm_battery %d%s %s {\n",
374                         q -> level,
375                         (q -> type == BATTERY_PERCENT)?"%":"m",
376                         (q -> direction == BATTERY_CHARGING)?"charging":
377                                 "discharging");
378                 for ( p = q -> cmdlist; p ; p = p->next) {
379                         fprintf(stderr, "\t%s", p->name);
380                         if (p->op->dump)
381                                 p->op->dump(p, stderr);
382                         fprintf(stderr, ";\n");
383                 }
384                 fprintf(stderr, "}\n");
385         }
386 }
387
388 void
389 destroy_config(void)
390 {
391         int i;
392         struct battery_watch_event *q;
393
394         /* disable events */
395         for (i = 0; i < EVENT_MAX; i++) {
396                 if (events[i].cmdlist) {
397                         u_int event_type = i;
398                         if (write(apmctl_fd, &event_type, sizeof(u_int)) == -1) {
399                                 (void) err(1, "cannot disable event 0x%x", event_type);
400                         }
401                 }
402         }
403
404         for (i = 0; i < EVENT_MAX; i++) {
405                 struct event_cmd * p;
406                 if ((p = events[i].cmdlist))
407                         free_event_cmd_list(p);
408                 events[i].cmdlist = NULL;
409         }
410
411         for( ; battery_watch_list; battery_watch_list = battery_watch_list -> next) {
412                 free_event_cmd_list(battery_watch_list->cmdlist);
413                 q = battery_watch_list->next;
414                 free(battery_watch_list);
415                 battery_watch_list = q;
416         }
417 }
418
419 void
420 restart(void)
421 {
422         destroy_config();
423         read_config();
424         if (verbose)
425                 dump_config();
426 }
427
428 /*
429  * write pid file
430  */
431 static void
432 write_pid(void)
433 {
434         FILE *fp = fopen(apmd_pidfile, "w");
435
436         if (fp) {
437                 fprintf(fp, "%d\n", getpid());
438                 fclose(fp);
439         }
440 }
441
442 /*
443  * handle signals
444  */
445 static int signal_fd[2];
446
447 void
448 enque_signal(int sig)
449 {
450         if (write(signal_fd[1], &sig, sizeof sig) != sizeof sig)
451                 (void) err(1, "cannot process signal.");
452 }
453
454 void
455 wait_child(void)
456 {
457         int status;
458         while (waitpid(-1, &status, WNOHANG) > 0)
459                 ;
460 }
461
462 int
463 proc_signal(int fd)
464 {
465         int rc = -1;
466         int sig;
467
468         while (read(fd, &sig, sizeof sig) == sizeof sig) {
469                 syslog(LOG_INFO, "caught signal: %d", sig);
470                 switch (sig) {
471                 case SIGHUP:
472                         syslog(LOG_NOTICE, "restart by SIG");
473                         restart();
474                         break;
475                 case SIGTERM:
476                         syslog(LOG_NOTICE, "going down on signal %d", sig);
477                         rc = 1;
478                         goto out;
479                 case SIGCHLD:
480                         wait_child();
481                         break;
482                 default:
483                         (void) warn("unexpected signal(%d) received.", sig);
484                         break;
485                 }
486         }
487         rc = 0;
488  out:
489         return rc;
490 }
491 void
492 proc_apmevent(int fd)
493 {
494         struct apm_event_info apmevent;
495
496         while (ioctl(fd, APMIO_NEXTEVENT, &apmevent) == 0) {
497                 int status;
498                 syslog(LOG_NOTICE, "apmevent %04x index %d\n",
499                         apmevent.type, apmevent.index);
500                 syslog(LOG_INFO, "apm event: %s", events[apmevent.type].name);
501                 if (fork() == 0) {
502                         status = exec_event_cmd(&events[apmevent.type]);
503                         exit(status);
504                 }
505         }
506 }
507
508 #define AC_POWER_STATE ((pw_info.ai_acline == 1) ? BATTERY_CHARGING :\
509         BATTERY_DISCHARGING)
510
511 void
512 check_battery(void)
513 {
514
515         static int first_time=1, last_state;
516
517         struct apm_info pw_info;
518         struct battery_watch_event *p;
519
520         /* If we don't care, don't bother */
521         if (battery_watch_list == NULL)
522                 return;
523
524         if (first_time) {
525                 if ( ioctl(apmnorm_fd, APMIO_GETINFO, &pw_info) < 0)
526                         (void) err(1, "cannot check battery state.");
527 /*
528  * This next statement isn't entirely true. The spec does not tie AC
529  * line state to battery charging or not, but this is a bit lazier to do.
530  */
531                 last_state = AC_POWER_STATE;
532                 first_time = 0;
533                 return; /* We can't process events, we have no baseline */
534         }
535
536         /*
537          * XXX - should we do this a bunch of times and perform some sort
538          * of smoothing or correction?
539          */
540         if ( ioctl(apmnorm_fd, APMIO_GETINFO, &pw_info) < 0)
541                 (void) err(1, "cannot check battery state.");
542
543         /*
544          * If we're not in the state now that we were in last time,
545          * then it's a transition, which means we must clean out
546          * the event-caught state.
547          */
548         if (last_state != AC_POWER_STATE) {
549                 last_state = AC_POWER_STATE;
550                 for (p = battery_watch_list ; p!=NULL ; p = p -> next)
551                         p->done = 0;
552         }
553         for (p = battery_watch_list ; p != NULL ; p = p -> next)
554                 if (p -> direction == AC_POWER_STATE &&
555                         !(p -> done) &&
556                         ((p -> type == BATTERY_PERCENT && 
557                                 p -> level == pw_info.ai_batt_life) ||
558                         (p -> type == BATTERY_MINUTES &&
559                                 p -> level == (pw_info.ai_batt_time / 60)))) {
560                         p -> done++;
561                         if (verbose)
562                                 syslog(LOG_NOTICE, "Caught battery event: %s, %d%s",
563                                         (p -> direction == BATTERY_CHARGING)?"charging":"discharging",
564                                         p -> level,
565                                         (p -> type == BATTERY_PERCENT)?"%":" minutes");
566                         if (fork() == 0) {
567                                 int status;
568                                 status = exec_run_cmd(p -> cmdlist);
569                                 exit(status);
570                         }
571                 }
572 }
573 void
574 event_loop(void)
575 {
576         int             fdmax = 0;
577         struct sigaction nsa;
578         fd_set          master_rfds;
579         sigset_t        sigmask, osigmask;
580
581         FD_ZERO(&master_rfds);
582         FD_SET(apmctl_fd, &master_rfds);
583         fdmax = apmctl_fd > fdmax ? apmctl_fd : fdmax;
584
585         FD_SET(signal_fd[0], &master_rfds);
586         fdmax = signal_fd[0] > fdmax ? signal_fd[0] : fdmax;
587
588         memset(&nsa, 0, sizeof nsa);
589         nsa.sa_handler = enque_signal;
590         sigfillset(&nsa.sa_mask);
591         nsa.sa_flags = SA_RESTART;
592         sigaction(SIGHUP, &nsa, NULL);
593         sigaction(SIGCHLD, &nsa, NULL);
594         sigaction(SIGTERM, &nsa, NULL);
595
596         sigemptyset(&sigmask);
597         sigaddset(&sigmask, SIGHUP);
598         sigaddset(&sigmask, SIGCHLD);
599         sigaddset(&sigmask, SIGTERM);
600         sigprocmask(SIG_SETMASK, &sigmask, &osigmask);
601
602         while (1) {
603                 fd_set rfds;
604                 int res;
605                 struct timeval to;
606
607                 to.tv_sec = BATT_CHK_INTV;
608                 to.tv_usec = 0;
609
610                 memcpy(&rfds, &master_rfds, sizeof rfds);
611                 sigprocmask(SIG_SETMASK, &osigmask, NULL);
612                 if ((res=select(fdmax + 1, &rfds, 0, 0, &to)) < 0) {
613                         if (errno != EINTR)
614                                 (void) err(1, "select");
615                 }
616                 sigprocmask(SIG_SETMASK, &sigmask, NULL);
617
618                 if (res == 0) { /* time to check the battery */
619                         check_battery();
620                         continue;
621                 }
622
623                 if (FD_ISSET(signal_fd[0], &rfds)) {
624                         if (proc_signal(signal_fd[0]) < 0)
625                                 goto out;
626                 }
627
628                 if (FD_ISSET(apmctl_fd, &rfds))
629                         proc_apmevent(apmctl_fd);
630         }
631 out:
632         return;
633 }
634
635 int
636 main(int ac, char* av[])
637 {
638         int     ch;
639         int     daemonize = 1;
640         char    *prog;
641         int     logopt = LOG_NDELAY | LOG_PID;
642
643         while ((ch = getopt(ac, av, "df:v")) != EOF) {
644                 switch (ch) {
645                 case 'd':
646                         daemonize = 0;
647                         debug_level++;
648                         break;
649                 case 'f':
650                         apmd_configfile = optarg;
651                         break;
652                 case 'v':
653                         verbose = 1;
654                         break;
655                 default:
656                         (void) err(1, "unknown option `%c'", ch);
657                 }
658         }
659
660         if (daemonize)
661                 daemon(0, 0);
662
663 #ifdef NICE_INCR
664         (void) nice(NICE_INCR);
665 #endif
666
667         if (!daemonize)
668                 logopt |= LOG_PERROR;
669
670         prog = strrchr(av[0], '/');
671         openlog(prog ? prog+1 : av[0], logopt, LOG_DAEMON);
672
673         syslog(LOG_NOTICE, "start");
674
675         if (pipe(signal_fd) < 0)
676                 (void) err(1, "pipe");
677         if (fcntl(signal_fd[0], F_SETFL, O_NONBLOCK) < 0)
678                 (void) err(1, "fcntl");
679
680         if ((apmnorm_fd = open(APM_NORM_DEVICEFILE, O_RDWR)) == -1) {
681                 (void) err(1, "cannot open device file `%s'", APM_NORM_DEVICEFILE);
682         }
683
684         if ((apmctl_fd = open(APM_CTL_DEVICEFILE, O_RDWR)) == -1) {
685                 (void) err(1, "cannot open device file `%s'", APM_CTL_DEVICEFILE);
686         }
687
688         restart();
689         write_pid();
690         event_loop();
691         exit(EXIT_SUCCESS);
692 }
693