apmd.c revision 1.1 1 /* $NetBSD: apmd.c,v 1.1 1996/08/25 23:41:03 jtk Exp $ */
2 /*-
3 * Copyright (c) 1995,1996 John T. Kohl. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. All advertising materials mentioning features or use of this software
14 * must display the following acknowledgement:
15 * This product includes software developed by the University of
16 * California, Berkeley and its contributors.
17 * 4. Neither the name of the University nor the names of its contributors
18 * may be used to endorse or promote products derived from this software
19 * without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR
22 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
25 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
29 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 * POSSIBILITY OF SUCH DAMAGE.
32 *
33 */
34
35 #include <stdio.h>
36 #include <errno.h>
37 #include <syslog.h>
38 #include <fcntl.h>
39 #include <unistd.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <signal.h>
43 #include <sys/types.h>
44 #include <sys/stat.h>
45 #include <sys/ioctl.h>
46 #include <sys/time.h>
47 #include <sys/socket.h>
48 #include <sys/un.h>
49 #include <sys/wait.h>
50 #include <machine/apmvar.h>
51 #include <err.h>
52 #include "pathnames.h"
53 #include "apm-proto.h"
54
55 #define MAX(a,b) (a > b ? a : b)
56 #define TRUE 1
57 #define FALSE 0
58
59 const char apmdev[] = _PATH_APM_CTLDEV;
60 const char sockfile[] = _PATH_APM_SOCKET;
61
62 static int debug = 0;
63
64 extern char *__progname;
65 extern char *optarg;
66 extern int optind;
67 extern int optopt;
68 extern int opterr;
69 extern int optreset;
70
71 void usage (void);
72 int power_status (int fd, int force, struct apm_power_info *pinfo);
73 int bind_socket (const char *sn);
74 enum apm_state handle_client(int sock_fd, int ctl_fd);
75 void suspend(int ctl_fd);
76 void stand_by(int ctl_fd);
77 void resume(int ctl_fd);
78 void sigexit(int signo);
79 void make_noise(int howmany);
80 void do_etc_file(const char *file);
81
82 void
83 sigexit(int signo)
84 {
85 exit(1);
86 }
87
88 void
89 usage(void)
90 {
91 fprintf(stderr,"usage: %s [-d] [-t timo] [-s] [-a] [-f devfile] [-S sockfile]\n", __progname);
92 exit(1);
93 }
94
95
96 int
97 power_status(int fd, int force, struct apm_power_info *pinfo)
98 {
99 struct apm_power_info bstate;
100 static struct apm_power_info last;
101 int acon = 0;
102
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)
107 acon = 1;
108 if (force ||
109 bstate.ac_state != last.ac_state ||
110 bstate.battery_state != last.battery_state ||
111 (bstate.minutes_left && bstate.minutes_left < 15) ||
112 abs(bstate.battery_life - last.battery_life) > 20) {
113 if (bstate.minutes_left)
114 syslog(LOG_NOTICE,
115 "battery status: %s. external power status: %s. "
116 "estimated battery life %d%% (%d minutes)",
117 battstate(bstate.battery_state),
118 ac_state(bstate.ac_state), bstate.battery_life,
119 bstate.minutes_left);
120 else
121 syslog(LOG_NOTICE,
122 "battery status: %s. external power status: %s. "
123 "estimated battery life %d%%",
124 battstate(bstate.battery_state),
125 ac_state(bstate.ac_state), bstate.battery_life);
126 last = bstate;
127 }
128 if (pinfo)
129 *pinfo = bstate;
130 } else
131 syslog(LOG_ERR, "cannot fetch power status: %m");
132 return acon;
133 }
134
135 static char *socketname;
136
137 static void sockunlink(void);
138
139 static void
140 sockunlink(void)
141 {
142 if (socketname)
143 (void) remove(socketname);
144 }
145
146 int
147 bind_socket(const char *sockname)
148 {
149 int sock;
150 struct sockaddr_un s_un;
151
152 sock = socket(AF_UNIX, SOCK_STREAM, 0);
153 if (sock == -1)
154 err(1, "cannot create local socket");
155
156 s_un.sun_family = AF_UNIX;
157 strncpy(s_un.sun_path, sockname, sizeof(s_un.sun_path));
158 s_un.sun_len = SUN_LEN(&s_un);
159 /* remove it if present, we're moving in */
160 (void) remove(sockname);
161 if (bind(sock, (struct sockaddr *)&s_un, s_un.sun_len) == -1)
162 err(1, "cannot connect to APM socket");
163 if (chmod(sockname, 0660) == -1 || chown(sockname, 0, 0) == -1)
164 err(1, "cannot set socket mode/owner/group to 666/0/0");
165 listen(sock, 1);
166 socketname = strdup(sockname);
167 atexit(sockunlink);
168 return sock;
169 }
170
171 enum apm_state
172 handle_client(int sock_fd, int ctl_fd)
173 {
174 /* accept a handle from the client, process it, then clean up */
175 int cli_fd;
176 struct sockaddr_un from;
177 int fromlen;
178 struct apm_command cmd;
179 struct apm_reply reply;
180
181 cli_fd = accept(sock_fd, (struct sockaddr *)&from, &fromlen);
182 if (cli_fd == -1) {
183 syslog(LOG_INFO, "client accept failure: %m");
184 return NORMAL;
185 }
186 if (recv(cli_fd, &cmd, sizeof(cmd), 0) != sizeof(cmd)) {
187 (void) close(cli_fd);
188 syslog(LOG_INFO, "client size botch");
189 return NORMAL;
190 }
191 if (cmd.vno != APMD_VNO) {
192 close(cli_fd); /* terminate client */
193 /* no error message, just drop it. */
194 return NORMAL;
195 }
196 power_status(ctl_fd, 0, &reply.batterystate);
197 switch (cmd.action) {
198 default:
199 reply.newstate = NORMAL;
200 break;
201 case SUSPEND:
202 reply.newstate = SUSPENDING;
203 break;
204 case STANDBY:
205 reply.newstate = STANDING_BY;
206 break;
207 }
208 reply.vno = APMD_VNO;
209 if (send(cli_fd, &reply, sizeof(reply), 0) != sizeof(reply)) {
210 syslog(LOG_INFO, "client reply botch");
211 }
212 close(cli_fd);
213 return reply.newstate;
214 }
215
216 static int speaker_ok = TRUE;
217
218 void
219 make_noise(howmany)
220 int howmany;
221 {
222 int spkrfd;
223 int trycnt;
224
225 if (!speaker_ok) /* don't bother after sticky errors */
226 return;
227
228 for (trycnt = 0; trycnt < 3; trycnt++) {
229 spkrfd = open(_PATH_DEV_SPEAKER, O_WRONLY);
230 if (spkrfd == -1) {
231 switch (errno) {
232 case EBUSY:
233 usleep(500000);
234 errno = EBUSY;
235 continue;
236 case ENOENT:
237 case ENODEV:
238 case ENXIO:
239 case EPERM:
240 case EACCES:
241 syslog(LOG_INFO,
242 "speaker device " _PATH_DEV_SPEAKER " unavailable: %m");
243 speaker_ok = FALSE;
244 return;
245 }
246 } else
247 break;
248 }
249 if (spkrfd == -1) {
250 syslog(LOG_WARNING, "cannot open " _PATH_DEV_SPEAKER ": %m");
251 return;
252 }
253 syslog(LOG_DEBUG, "sending %d tones to speaker\n", howmany);
254 write (spkrfd, "o4cc", 2 + howmany);
255 close(spkrfd);
256 return;
257 }
258
259
260 void
261 suspend(int ctl_fd)
262 {
263 do_etc_file(_PATH_APM_ETC_SUSPEND);
264 sync();
265 make_noise(2);
266 sync();
267 sync();
268 sleep(1);
269 ioctl(ctl_fd, APM_IOC_SUSPEND, 0);
270 }
271
272 void
273 stand_by(int ctl_fd)
274 {
275 do_etc_file(_PATH_APM_ETC_STANDBY);
276 sync();
277 make_noise(1);
278 sync();
279 sync();
280 sleep(1);
281 ioctl(ctl_fd, APM_IOC_STANDBY, 0);
282 }
283
284 #define TIMO (10*60) /* 10 minutes */
285
286 void
287 resume(int ctl_fd)
288 {
289 do_etc_file(_PATH_APM_ETC_RESUME);
290 }
291
292 void
293 main(int argc, char *argv[])
294 {
295 const char *fname = apmdev;
296 int ctl_fd, sock_fd, ch, ready;
297 int statonly = 0;
298 fd_set devfds;
299 fd_set selcopy;
300 struct apm_event_info apmevent;
301 int suspends, standbys, resumes;
302 int noacsleep = 0;
303 struct timeval tv = {TIMO, 0}, stv;
304 const char *sockname = sockfile;
305
306 while ((ch = getopt(argc, argv, "qadsf:t:S:")) != -1)
307 switch(ch) {
308 case 'q':
309 speaker_ok = FALSE;
310 break;
311 case 'a':
312 noacsleep = 1;
313 break;
314 case 'd':
315 debug = 1;
316 break;
317 case 'f':
318 fname = optarg;
319 break;
320 case 'S':
321 sockname = optarg;
322 break;
323 case 't':
324 tv.tv_sec = strtoul(optarg, 0, 0);
325 if (tv.tv_sec == 0)
326 usage();
327 break;
328 case 's': /* status only */
329 statonly = 1;
330 break;
331 case '?':
332
333 default:
334 usage();
335 }
336 argc -= optind;
337 argv += optind;
338 if ((ctl_fd = open(fname, O_RDWR)) == -1) {
339 (void)err(1, "cannot open device file `%s'", fname);
340 }
341 if (debug) {
342 openlog(__progname, LOG_CONS, LOG_LOCAL1);
343 } else {
344 openlog(__progname, LOG_CONS, LOG_DAEMON);
345 setlogmask(LOG_UPTO(LOG_NOTICE));
346 daemon(0, 0);
347 }
348 power_status(ctl_fd, 1, 0);
349 if (statonly)
350 exit(0);
351 (void) signal(SIGTERM, sigexit);
352 (void) signal(SIGHUP, sigexit);
353 (void) signal(SIGINT, sigexit);
354
355 sock_fd = bind_socket(sockname);
356
357 FD_ZERO(&devfds);
358 FD_SET(ctl_fd, &devfds);
359 FD_SET(sock_fd, &devfds);
360
361 for (selcopy = devfds, errno = 0, stv = tv;
362 (ready = select(MAX(ctl_fd,sock_fd)+1, &selcopy, 0, 0, &stv)) >= 0 ||
363 errno == EINTR;
364 selcopy = devfds, errno = 0, stv = tv) {
365 if (errno == EINTR)
366 continue;
367 if (ready == 0) {
368 /* wakeup for timeout: take status */
369 power_status(ctl_fd, 0, 0);
370 }
371 if (FD_ISSET(ctl_fd, &selcopy)) {
372 suspends = standbys = resumes = 0;
373 while (ioctl(ctl_fd, APM_IOC_NEXTEVENT, &apmevent) == 0) {
374 syslog(LOG_DEBUG, "apmevent %04x index %d", apmevent.type,
375 apmevent.index);
376 switch (apmevent.type) {
377 case APM_SUSPEND_REQ:
378 case APM_USER_SUSPEND_REQ:
379 case APM_CRIT_SUSPEND_REQ:
380 case APM_BATTERY_LOW:
381 suspends++;
382 break;
383 case APM_USER_STANDBY_REQ:
384 case APM_STANDBY_REQ:
385 standbys++;
386 break;
387 #if 0
388 case APM_CANCEL:
389 suspends = standbys = 0;
390 break;
391 #endif
392 case APM_NORMAL_RESUME:
393 case APM_CRIT_RESUME:
394 case APM_SYS_STANDBY_RESUME:
395 resumes++;
396 break;
397 case APM_POWER_CHANGE:
398 power_status(ctl_fd, 1, 0);
399 break;
400 default:
401 break;
402 }
403 }
404 if ((standbys || suspends) && noacsleep &&
405 power_status(ctl_fd, 0, 0)) {
406 syslog(LOG_DEBUG, "not sleeping cuz AC is connected");
407 } else if (suspends) {
408 suspend(ctl_fd);
409 } else if (standbys) {
410 stand_by(ctl_fd);
411 } else if (resumes) {
412 resume(ctl_fd);
413 syslog(LOG_NOTICE, "system resumed from APM sleep");
414 }
415 ready--;
416 }
417 if (ready == 0)
418 continue;
419 if (FD_ISSET(sock_fd, &selcopy)) {
420 switch (handle_client(sock_fd, ctl_fd)) {
421 case NORMAL:
422 break;
423 case SUSPENDING:
424 suspend(ctl_fd);
425 break;
426 case STANDING_BY:
427 stand_by(ctl_fd);
428 break;
429 }
430 }
431 }
432 syslog(LOG_ERR, "select failed: %m");
433 exit(1);
434 }
435
436 void
437 do_etc_file(const char *file)
438 {
439 pid_t pid;
440 int status;
441 const char *prog;
442
443 /* If file doesn't exist, do nothing. */
444 if (access(file, X_OK|R_OK)) {
445 syslog(LOG_DEBUG, "do_etc_file(): cannot access file %s", file);
446 return;
447 }
448
449 prog = strrchr(file, '/');
450 if (prog)
451 prog++;
452 else
453 prog = file;
454
455 pid = fork();
456 switch (pid) {
457 case -1:
458 syslog(LOG_ERR, "failed to fork(): %m");
459 return;
460 case 0:
461 /* We are the child. */
462 execl(file, prog, NULL);
463 _exit(-1);
464 /* NOTREACHED */
465 default:
466 /* We are the parent. */
467 wait4(pid, &status, 0, 0);
468 if (WIFEXITED(status))
469 syslog(LOG_DEBUG, "%s exited with status %d", file,
470 WEXITSTATUS(status));
471 else {
472 syslog(LOG_ERR, "%s exited abnormally.", file);
473 }
474 break;
475 }
476 }
477