1 /* $NetBSD: apmd.c,v 1.30 2006/10/07 17:27:57 elad Exp $ */
4 * Copyright (c) 1996, 2000 The NetBSD Foundation, Inc.
7 * This code is derived from software contributed to The NetBSD Foundation
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
41 #include <sys/types.h>
45 #include <sys/ioctl.h>
47 #include <sys/socket.h>
51 #include <machine/apmvar.h>
53 #include "pathnames.h"
54 #include "apm-proto.h"
59 #define POWER_STATUS_ACON 0x1
60 #define POWER_STATUS_LOWBATTNOW 0x2
62 const char apmdev
[] = _PATH_APM_CTLDEV
;
63 const char sockfile
[] = _PATH_APM_SOCKET
;
66 static int verbose
= 0;
69 int power_status (int fd
, int force
, struct apm_power_info
*pinfo
);
70 int bind_socket (const char *sn
, mode_t mode
, uid_t uid
, gid_t gid
);
71 enum apm_state
handle_client(int sock_fd
, int ctl_fd
);
72 void suspend(int ctl_fd
);
73 void stand_by(int ctl_fd
);
74 void resume(int ctl_fd
);
75 void sigexit(int signo
);
76 void make_noise(int howmany
);
77 void do_etc_file(const char *file
);
78 void do_ac_state(int state
);
89 fprintf(stderr
,"usage: %s [-adlqsv] [-t seconds] [-S sockname]\n\t[-m sockmode] [-o sockowner:sockgroup] [-f devname]\n", getprogname());
95 power_status(int fd
, int force
, struct apm_power_info
*pinfo
)
97 struct apm_power_info bstate
;
98 static struct apm_power_info last
;
102 memset(&bstate
, 0, sizeof(bstate
));
103 if (ioctl(fd
, APM_IOC_GETPOWER
, &bstate
) == 0) {
104 /* various conditions under which we report status: something changed
105 enough since last report, or asked to force a print */
106 if (bstate
.ac_state
== APM_AC_ON
)
108 if (bstate
.battery_state
!= last
.battery_state
&&
109 bstate
.battery_state
== APM_BATT_LOW
)
112 bstate
.ac_state
!= last
.ac_state
||
113 bstate
.battery_state
!= last
.battery_state
||
114 (bstate
.minutes_left
&& bstate
.minutes_left
< 15) ||
115 abs(bstate
.battery_life
- last
.battery_life
) > 20) {
117 if (bstate
.minutes_left
)
119 "battery status: %s. external power status: %s. "
120 "estimated battery life %d%% (%d minutes)",
121 battstate(bstate
.battery_state
),
122 ac_state(bstate
.ac_state
), bstate
.battery_life
,
123 bstate
.minutes_left
);
126 "battery status: %s. external power status: %s. "
127 "estimated battery life %d%%",
128 battstate(bstate
.battery_state
),
129 ac_state(bstate
.ac_state
), bstate
.battery_life
);
136 syslog(LOG_ERR
, "cannot fetch power status: %m");
137 return ((acon
?POWER_STATUS_ACON
:0) |
138 (lowbattnow
?POWER_STATUS_LOWBATTNOW
:0));
141 static char *socketname
;
143 static void sockunlink(void);
149 (void) remove(socketname
);
153 bind_socket(const char *sockname
, mode_t mode
, uid_t uid
, gid_t gid
)
156 struct sockaddr_un s_un
;
158 sock
= socket(AF_LOCAL
, SOCK_STREAM
, 0);
160 err(1, "cannot create local socket");
162 s_un
.sun_family
= AF_LOCAL
;
163 strncpy(s_un
.sun_path
, sockname
, sizeof(s_un
.sun_path
));
164 s_un
.sun_len
= SUN_LEN(&s_un
);
165 /* remove it if present, we're moving in */
166 (void) remove(sockname
);
167 if (bind(sock
, (struct sockaddr
*)&s_un
, s_un
.sun_len
) == -1)
168 err(1, "cannot create APM socket");
169 if (chmod(sockname
, mode
) == -1 || chown(sockname
, uid
, gid
) == -1)
170 err(1, "cannot set socket mode/owner/group to %o/%d/%d",
173 socketname
= strdup(sockname
);
179 handle_client(int sock_fd
, int ctl_fd
)
181 /* accept a handle from the client, process it, then clean up */
183 struct sockaddr_un from
;
184 socklen_t fromlen
= sizeof(from
);
185 struct apm_command cmd
;
186 struct apm_reply reply
;
188 cli_fd
= accept(sock_fd
, (struct sockaddr
*)&from
, &fromlen
);
190 syslog(LOG_INFO
, "client accept failure: %m");
193 if (recv(cli_fd
, &cmd
, sizeof(cmd
), 0) != sizeof(cmd
)) {
194 (void) close(cli_fd
);
195 syslog(LOG_INFO
, "client size botch");
198 if (cmd
.vno
!= APMD_VNO
) {
199 close(cli_fd
); /* terminate client */
200 /* no error message, just drop it. */
203 power_status(ctl_fd
, 0, &reply
.batterystate
);
204 switch (cmd
.action
) {
206 reply
.newstate
= NORMAL
;
209 reply
.newstate
= SUSPENDING
;
212 reply
.newstate
= STANDING_BY
;
215 reply
.vno
= APMD_VNO
;
216 if (send(cli_fd
, &reply
, sizeof(reply
), 0) != sizeof(reply
)) {
217 syslog(LOG_INFO
, "client reply botch");
220 return reply
.newstate
;
223 static int speaker_ok
= TRUE
;
232 if (!speaker_ok
) /* don't bother after sticky errors */
235 for (trycnt
= 0; trycnt
< 3; trycnt
++) {
236 spkrfd
= open(_PATH_DEV_SPEAKER
, O_WRONLY
);
249 "speaker device " _PATH_DEV_SPEAKER
" unavailable: %m");
257 syslog(LOG_WARNING
, "cannot open " _PATH_DEV_SPEAKER
": %m");
260 syslog(LOG_DEBUG
, "sending %d tones to speaker\n", howmany
);
261 write (spkrfd
, "o4cc", 2 + howmany
);
270 do_etc_file(_PATH_APM_ETC_SUSPEND
);
276 ioctl(ctl_fd
, APM_IOC_SUSPEND
, 0);
282 do_etc_file(_PATH_APM_ETC_STANDBY
);
288 ioctl(ctl_fd
, APM_IOC_STANDBY
, 0);
291 #define TIMO (10*60) /* 10 minutes */
296 do_etc_file(_PATH_APM_ETC_RESUME
);
300 main(int argc
, char *argv
[])
302 const char *fname
= apmdev
;
303 int ctl_fd
, sock_fd
, ch
, ready
;
305 struct pollfd set
[2];
306 struct apm_event_info apmevent
;
307 int suspends
, standbys
, resumes
;
310 int lowbattsleep
= 0;
312 unsigned long timeout
= TIMO
;
313 const char *sockname
= sockfile
;
316 uid_t uid
= 2; /* operator */
317 gid_t gid
= 5; /* operator */
321 while ((ch
= getopt(argc
, argv
, "adlqsvf:t:S:m:o:")) != -1)
345 timeout
= strtoul(optarg
, 0, 0);
350 mode
= strtoul(optarg
, 0, 8);
357 group
= strchr(user
, ':');
361 uid
= strtoul(user
, &scratch
, 0);
362 if (*scratch
!= '\0') {
367 errx(1, "user name `%s' unknown", user
);
370 if (group
&& *group
) {
371 gid
= strtoul(group
, &scratch
, 0);
372 if (*scratch
!= '\0') {
373 gr
= getgrnam(group
);
377 errx(1, "group name `%s' unknown", group
);
381 case 's': /* status only */
392 openlog("apmd", 0, LOG_LOCAL1
);
395 openlog("apmd", 0, LOG_DAEMON
);
396 setlogmask(LOG_UPTO(LOG_NOTICE
));
399 if ((ctl_fd
= open(fname
, O_RDWR
)) == -1) {
400 syslog(LOG_ERR
, "cannot open device file `%s'", fname
);
405 power_status(ctl_fd
, 1, 0);
408 struct apm_power_info pinfo
;
409 power_status(ctl_fd
, 1, &pinfo
);
410 do_ac_state(pinfo
.ac_state
);
411 ac_is_off
= (pinfo
.ac_state
== APM_AC_OFF
);
414 (void) signal(SIGTERM
, sigexit
);
415 (void) signal(SIGHUP
, sigexit
);
416 (void) signal(SIGINT
, sigexit
);
417 (void) signal(SIGPIPE
, SIG_IGN
);
420 sock_fd
= bind_socket(sockname
, mode
, uid
, gid
);
423 set
[0].events
= POLLIN
;
425 set
[1].events
= POLLIN
;
429 (ready
= poll(set
, 2, timeout
* 1000)) >= 0 || errno
== EINTR
;
435 /* wakeup for timeout: take status */
436 status
= power_status(ctl_fd
, 0, 0);
437 if (lowbattsleep
&& status
&POWER_STATUS_LOWBATTNOW
) {
438 if (noacsleep
&& status
&POWER_STATUS_ACON
) {
441 "not sleeping because "
447 if (set
[0].revents
& POLLIN
) {
448 suspends
= standbys
= resumes
= 0;
449 while (ioctl(ctl_fd
, APM_IOC_NEXTEVENT
, &apmevent
) == 0) {
451 syslog(LOG_DEBUG
, "apmevent %04x index %d", apmevent
.type
,
453 switch (apmevent
.type
) {
454 case APM_SUSPEND_REQ
:
455 case APM_USER_SUSPEND_REQ
:
456 case APM_CRIT_SUSPEND_REQ
:
459 case APM_BATTERY_LOW
:
463 case APM_USER_STANDBY_REQ
:
464 case APM_STANDBY_REQ
:
469 suspends
= standbys
= 0;
472 case APM_NORMAL_RESUME
:
473 case APM_CRIT_RESUME
:
474 case APM_SYS_STANDBY_RESUME
:
477 case APM_POWER_CHANGE
:
479 struct apm_power_info pinfo
;
480 power_status(ctl_fd
, 0, &pinfo
);
481 /* power status can change without ac status changing */
482 if (ac_is_off
!= (pinfo
.ac_state
== APM_AC_OFF
)) {
483 do_ac_state(pinfo
.ac_state
);
484 ac_is_off
= (pinfo
.ac_state
== APM_AC_OFF
);
492 if ((standbys
|| suspends
) && noacsleep
&&
493 (power_status(ctl_fd
, 0, 0) & POWER_STATUS_ACON
)) {
495 syslog(LOG_DEBUG
, "not sleeping because AC is connected");
496 } else if (suspends
) {
498 } else if (standbys
) {
500 } else if (resumes
) {
503 syslog(LOG_NOTICE
, "system resumed from APM sleep");
509 if (set
[1].revents
& POLLIN
) {
510 switch (handle_client(sock_fd
, ctl_fd
)) {
522 syslog(LOG_ERR
, "poll failed: %m");
527 do_etc_file(const char *file
)
533 /* If file doesn't exist, do nothing. */
534 if (access(file
, X_OK
|R_OK
)) {
536 syslog(LOG_DEBUG
, "do_etc_file(): cannot access file %s", file
);
540 prog
= strrchr(file
, '/');
549 syslog(LOG_ERR
, "failed to fork(): %m");
552 /* We are the child. */
553 if (execl(file
, prog
, NULL
) == -1)
554 syslog(LOG_ERR
, "could not execute \"%s\": %m", file
);
558 /* We are the parent. */
559 wait4(pid
, &status
, 0, 0);
560 if (WIFEXITED(status
)) {
562 syslog(LOG_DEBUG
, "%s exited with status %d", file
,
563 WEXITSTATUS(status
));
565 syslog(LOG_ERR
, "%s exited abnormally.", file
);
571 do_ac_state(int state
)
575 do_etc_file(_PATH_APM_ETC_BATTERY
);
579 do_etc_file(_PATH_APM_ETC_LINE
);
582 /* Silently ignore */ ;