2 * APM (Advanced Power Management) Event Dispatcher
4 * Copyright (c) 1999 Mitsuru IWASAKI <iwasaki@FreeBSD.org>
5 * Copyright (c) 1999 KOIE Hidetaka <koie@suri.co.jp>
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
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.
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
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.7 2008/07/10 18:29:52 swildner Exp $
34 #include <bitstring.h>
46 #include <sys/ioctl.h>
47 #include <sys/types.h>
50 #include <machine/apm_bios.h>
57 const char *apmd_configfile
= APMD_CONFIGFILE
;
58 int apmctl_fd
= -1, apmnorm_fd
= -1;
61 * table of event handlers
63 #define EVENT_CONFIG_INITIALIZER(EV,R) { #EV, NULL, R },
64 struct event_config events
[EVENT_MAX
] = {
65 EVENT_CONFIG_INITIALIZER(NOEVENT
, 0)
66 EVENT_CONFIG_INITIALIZER(STANDBYREQ
, 1)
67 EVENT_CONFIG_INITIALIZER(SUSPENDREQ
, 1)
68 EVENT_CONFIG_INITIALIZER(NORMRESUME
, 0)
69 EVENT_CONFIG_INITIALIZER(CRITRESUME
, 0)
70 EVENT_CONFIG_INITIALIZER(BATTERYLOW
, 0)
71 EVENT_CONFIG_INITIALIZER(POWERSTATECHANGE
, 0)
72 EVENT_CONFIG_INITIALIZER(UPDATETIME
, 0)
73 EVENT_CONFIG_INITIALIZER(CRITSUSPEND
, 1)
74 EVENT_CONFIG_INITIALIZER(USERSTANDBYREQ
, 1)
75 EVENT_CONFIG_INITIALIZER(USERSUSPENDREQ
, 1)
76 EVENT_CONFIG_INITIALIZER(STANDBYRESUME
, 0)
77 EVENT_CONFIG_INITIALIZER(CAPABILITIESCHANGE
, 0)
81 * List of battery events
83 struct battery_watch_event
*battery_watch_list
= NULL
;
85 #define BATT_CHK_INTV 10 /* how many seconds between battery state checks? */
91 event_cmd_default_clone(void *this)
93 struct event_cmd
* oldone
= this;
94 struct event_cmd
* newone
= malloc(oldone
->len
);
97 newone
->len
= oldone
->len
;
98 newone
->name
= oldone
->name
;
99 newone
->op
= oldone
->op
;
107 event_cmd_exec_act(void *this)
109 struct event_cmd_exec
* p
= this;
113 switch ((pid
= fork())) {
119 execl(_PATH_BSHELL
, "sh", "-c", p
->line
, NULL
);
124 pid
= waitpid(pid
, &status
, 0);
125 } while (pid
== -1 && errno
== EINTR
);
132 event_cmd_exec_dump(void *this, FILE *fp
)
134 fprintf(fp
, " \"%s\"", ((struct event_cmd_exec
*)this)->line
);
137 event_cmd_exec_clone(void *this)
139 struct event_cmd_exec
* newone
= (struct event_cmd_exec
*) event_cmd_default_clone(this);
140 struct event_cmd_exec
* oldone
= this;
142 newone
->evcmd
.next
= NULL
;
143 newone
->evcmd
.len
= oldone
->evcmd
.len
;
144 newone
->evcmd
.name
= oldone
->evcmd
.name
;
145 newone
->evcmd
.op
= oldone
->evcmd
.op
;
146 if ((newone
->line
= strdup(oldone
->line
)) == NULL
)
147 err(1, "out of memory");
148 return (struct event_cmd
*) newone
;
151 event_cmd_exec_free(void *this)
153 free(((struct event_cmd_exec
*)this)->line
);
155 struct event_cmd_op event_cmd_exec_ops
= {
158 event_cmd_exec_clone
,
166 event_cmd_reject_act(void *this)
170 if (ioctl(apmctl_fd
, APMIO_REJECTLASTREQ
, NULL
)) {
171 syslog(LOG_NOTICE
, "fail to reject\n");
178 struct event_cmd_op event_cmd_reject_ops
= {
179 event_cmd_reject_act
,
181 event_cmd_default_clone
,
186 * manipulate event_config
189 clone_event_cmd_list(struct event_cmd
*p
)
191 struct event_cmd dummy
;
192 struct event_cmd
*q
= &dummy
;
193 for ( ;p
; p
= p
->next
) {
194 assert(p
->op
->clone
);
195 if ((q
->next
= p
->op
->clone(p
)) == NULL
)
196 err(1, "out of memory");
203 free_event_cmd_list(struct event_cmd
*p
)
205 struct event_cmd
* q
;
214 register_battery_handlers(
215 int level
, int direction
,
216 struct event_cmd
*cmdlist
)
219 * level is negative if it's in "minutes", non-negative if
222 * direction =1 means we care about this level when charging,
223 * direction =-1 means we care about it when discharging.
225 if (level
>100) /* percentage > 100 */
227 if (abs(direction
) != 1) /* nonsense direction value */
231 struct battery_watch_event
*we
;
233 if ((we
= malloc(sizeof(struct battery_watch_event
))) == NULL
)
234 err(1, "out of memory");
236 we
->next
= battery_watch_list
; /* starts at NULL */
237 battery_watch_list
= we
;
238 we
->level
= abs(level
);
239 we
->type
= (level
<0)?BATTERY_MINUTES
:BATTERY_PERCENT
;
240 we
->direction
= (direction
<0)?BATTERY_DISCHARGING
:
243 we
->cmdlist
= clone_event_cmd_list(cmdlist
);
248 register_apm_event_handlers(
249 bitstr_t
bit_decl(evlist
, EVENT_MAX
),
250 struct event_cmd
*cmdlist
)
253 bitstr_t
bit_decl(tmp
, EVENT_MAX
);
254 memcpy(&tmp
, evlist
, bitstr_size(EVENT_MAX
));
260 bit_ffs(tmp
, EVENT_MAX
, &n
);
263 p
= events
[n
].cmdlist
;
264 if ((q
= clone_event_cmd_list(cmdlist
)) == NULL
)
265 err(1, "out of memory");
267 while (p
->next
!= NULL
)
271 events
[n
].cmdlist
= q
;
283 exec_run_cmd(struct event_cmd
*p
)
287 for (; p
; p
= p
->next
) {
290 syslog(LOG_INFO
, "action: %s", p
->name
);
291 status
= p
->op
->act(p
);
293 syslog(LOG_NOTICE
, "command finished with %d\n", status
);
301 * execute command -- the event version
304 exec_event_cmd(struct event_config
*ev
)
308 status
= exec_run_cmd(ev
->cmdlist
);
309 if (status
&& ev
->rejectable
) {
310 syslog(LOG_ERR
, "canceled");
311 event_cmd_reject_act(NULL
);
327 if ((yyin
= fopen(apmd_configfile
, "r")) == NULL
) {
328 err(1, "cannot open config file");
332 yydebug
= debug_level
;
336 err(1, "cannot parse config file");
341 for (i
= 0; i
< EVENT_MAX
; i
++) {
342 if (events
[i
].cmdlist
) {
343 u_int event_type
= i
;
344 if (write(apmctl_fd
, &event_type
, sizeof(u_int
)) == -1) {
345 err(1, "cannot enable event 0x%x", event_type
);
355 struct battery_watch_event
*q
;
357 for (i
= 0; i
< EVENT_MAX
; i
++) {
358 struct event_cmd
* p
;
359 if ((p
= events
[i
].cmdlist
)) {
360 fprintf(stderr
, "apm_event %s {\n", events
[i
].name
);
361 for ( ; p
; p
= p
->next
) {
362 fprintf(stderr
, "\t%s", p
->name
);
364 p
->op
->dump(p
, stderr
);
365 fprintf(stderr
, ";\n");
367 fprintf(stderr
, "}\n");
370 for (q
= battery_watch_list
; q
!= NULL
; q
= q
-> next
) {
371 struct event_cmd
* p
;
372 fprintf(stderr
, "apm_battery %d%s %s {\n",
374 (q
-> type
== BATTERY_PERCENT
)?"%":"m",
375 (q
-> direction
== BATTERY_CHARGING
)?"charging":
377 for ( p
= q
-> cmdlist
; p
; p
= p
->next
) {
378 fprintf(stderr
, "\t%s", p
->name
);
380 p
->op
->dump(p
, stderr
);
381 fprintf(stderr
, ";\n");
383 fprintf(stderr
, "}\n");
391 struct battery_watch_event
*q
;
394 for (i
= 0; i
< EVENT_MAX
; i
++) {
395 if (events
[i
].cmdlist
) {
396 u_int event_type
= i
;
397 if (write(apmctl_fd
, &event_type
, sizeof(u_int
)) == -1) {
398 err(1, "cannot disable event 0x%x", event_type
);
403 for (i
= 0; i
< EVENT_MAX
; i
++) {
404 struct event_cmd
* p
;
405 if ((p
= events
[i
].cmdlist
))
406 free_event_cmd_list(p
);
407 events
[i
].cmdlist
= NULL
;
410 for( ; battery_watch_list
; battery_watch_list
= battery_watch_list
-> next
) {
411 free_event_cmd_list(battery_watch_list
->cmdlist
);
412 q
= battery_watch_list
->next
;
413 free(battery_watch_list
);
414 battery_watch_list
= q
;
430 static int signal_fd
[2];
433 enque_signal(int sig
)
435 if (write(signal_fd
[1], &sig
, sizeof sig
) != sizeof sig
)
436 err(1, "cannot process signal.");
443 while (waitpid(-1, &status
, WNOHANG
) > 0)
453 while (read(fd
, &sig
, sizeof sig
) == sizeof sig
) {
454 syslog(LOG_INFO
, "caught signal: %d", sig
);
457 syslog(LOG_NOTICE
, "restart by SIG");
461 syslog(LOG_NOTICE
, "going down on signal %d", sig
);
468 warn("unexpected signal(%d) received.", sig
);
477 proc_apmevent(int fd
)
479 struct apm_event_info apmevent
;
481 while (ioctl(fd
, APMIO_NEXTEVENT
, &apmevent
) == 0) {
483 syslog(LOG_NOTICE
, "apmevent %04x index %d\n",
484 apmevent
.type
, apmevent
.index
);
485 syslog(LOG_INFO
, "apm event: %s", events
[apmevent
.type
].name
);
487 status
= exec_event_cmd(&events
[apmevent
.type
]);
493 #define AC_POWER_STATE ((pw_info.ai_acline == 1) ? BATTERY_CHARGING :\
500 static int first_time
=1, last_state
;
502 struct apm_info pw_info
;
503 struct battery_watch_event
*p
;
505 /* If we don't care, don't bother */
506 if (battery_watch_list
== NULL
)
510 if ( ioctl(apmnorm_fd
, APMIO_GETINFO
, &pw_info
) < 0)
511 err(1, "cannot check battery state.");
513 * This next statement isn't entirely true. The spec does not tie AC
514 * line state to battery charging or not, but this is a bit lazier to do.
516 last_state
= AC_POWER_STATE
;
518 return; /* We can't process events, we have no baseline */
522 * XXX - should we do this a bunch of times and perform some sort
523 * of smoothing or correction?
525 if ( ioctl(apmnorm_fd
, APMIO_GETINFO
, &pw_info
) < 0)
526 err(1, "cannot check battery state.");
529 * If we're not in the state now that we were in last time,
530 * then it's a transition, which means we must clean out
531 * the event-caught state.
533 if (last_state
!= AC_POWER_STATE
) {
534 last_state
= AC_POWER_STATE
;
535 for (p
= battery_watch_list
; p
!=NULL
; p
= p
-> next
)
538 for (p
= battery_watch_list
; p
!= NULL
; p
= p
-> next
)
539 if (p
-> direction
== AC_POWER_STATE
&&
541 ((p
-> type
== BATTERY_PERCENT
&&
542 p
-> level
== (int)pw_info
.ai_batt_life
) ||
543 (p
-> type
== BATTERY_MINUTES
&&
544 p
-> level
== (pw_info
.ai_batt_time
/ 60)))) {
547 syslog(LOG_NOTICE
, "Caught battery event: %s, %d%s",
548 (p
-> direction
== BATTERY_CHARGING
)?"charging":"discharging",
550 (p
-> type
== BATTERY_PERCENT
)?"%":" minutes");
553 status
= exec_run_cmd(p
-> cmdlist
);
562 struct sigaction nsa
;
564 sigset_t sigmask
, osigmask
;
566 FD_ZERO(&master_rfds
);
567 FD_SET(apmctl_fd
, &master_rfds
);
568 fdmax
= apmctl_fd
> fdmax
? apmctl_fd
: fdmax
;
570 FD_SET(signal_fd
[0], &master_rfds
);
571 fdmax
= signal_fd
[0] > fdmax
? signal_fd
[0] : fdmax
;
573 memset(&nsa
, 0, sizeof nsa
);
574 nsa
.sa_handler
= enque_signal
;
575 sigfillset(&nsa
.sa_mask
);
576 nsa
.sa_flags
= SA_RESTART
;
577 sigaction(SIGHUP
, &nsa
, NULL
);
578 sigaction(SIGCHLD
, &nsa
, NULL
);
579 sigaction(SIGTERM
, &nsa
, NULL
);
581 sigemptyset(&sigmask
);
582 sigaddset(&sigmask
, SIGHUP
);
583 sigaddset(&sigmask
, SIGCHLD
);
584 sigaddset(&sigmask
, SIGTERM
);
585 sigprocmask(SIG_SETMASK
, &sigmask
, &osigmask
);
592 to
.tv_sec
= BATT_CHK_INTV
;
595 memcpy(&rfds
, &master_rfds
, sizeof rfds
);
596 sigprocmask(SIG_SETMASK
, &osigmask
, NULL
);
597 if ((res
=select(fdmax
+ 1, &rfds
, 0, 0, &to
)) < 0) {
601 sigprocmask(SIG_SETMASK
, &sigmask
, NULL
);
603 if (res
== 0) { /* time to check the battery */
608 if (FD_ISSET(signal_fd
[0], &rfds
)) {
609 if (proc_signal(signal_fd
[0]) < 0)
613 if (FD_ISSET(apmctl_fd
, &rfds
))
614 proc_apmevent(apmctl_fd
);
621 main(int ac
, char* av
[])
626 int logopt
= LOG_NDELAY
| LOG_PID
;
628 while ((ch
= getopt(ac
, av
, "df:v")) != -1) {
635 apmd_configfile
= optarg
;
641 err(1, "unknown option `%c'", ch
);
653 logopt
|= LOG_PERROR
;
655 prog
= strrchr(av
[0], '/');
656 openlog(prog
? prog
+1 : av
[0], logopt
, LOG_DAEMON
);
658 syslog(LOG_NOTICE
, "start");
660 if (pipe(signal_fd
) < 0)
662 if (fcntl(signal_fd
[0], F_SETFL
, O_NONBLOCK
) < 0)
665 if ((apmnorm_fd
= open(APM_NORM_DEVICEFILE
, O_RDWR
)) == -1) {
666 err(1, "cannot open device file `%s'", APM_NORM_DEVICEFILE
);
669 if ((apmctl_fd
= open(APM_CTL_DEVICEFILE
, O_RDWR
)) == -1) {
670 err(1, "cannot open device file `%s'", APM_CTL_DEVICEFILE
);