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