xref: /openbsd-src/usr.sbin/apmd/apmd.c (revision 6753c042c48abc51ce9e77a1b06a3924e6903df5)
1*6753c042Skn /*	$OpenBSD: apmd.c,v 1.112 2023/04/27 10:51:27 kn Exp $	*/
2a831c31eSmickey 
3054c7b81Shvozda /*
4054c7b81Shvozda  *  Copyright (c) 1995, 1996 John T. Kohl
5054c7b81Shvozda  *  All rights reserved.
6054c7b81Shvozda  *
7054c7b81Shvozda  *  Redistribution and use in source and binary forms, with or without
8054c7b81Shvozda  *  modification, are permitted provided that the following conditions
9054c7b81Shvozda  *  are met:
10054c7b81Shvozda  *  1. Redistributions of source code must retain the above copyright
11054c7b81Shvozda  *     notice, this list of conditions and the following disclaimer.
12054c7b81Shvozda  *  2. Redistributions in binary form must reproduce the above copyright
13054c7b81Shvozda  *     notice, this list of conditions and the following disclaimer in the
14054c7b81Shvozda  *     documentation and/or other materials provided with the distribution.
15054c7b81Shvozda  *  3. The name of the author may not be used to endorse or promote products
16054c7b81Shvozda  *     derived from this software without specific prior written permission.
17054c7b81Shvozda  *
18054c7b81Shvozda  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR
19054c7b81Shvozda  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20054c7b81Shvozda  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21054c7b81Shvozda  * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
22054c7b81Shvozda  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23054c7b81Shvozda  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24054c7b81Shvozda  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25054c7b81Shvozda  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26054c7b81Shvozda  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
27054c7b81Shvozda  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28054c7b81Shvozda  * POSSIBILITY OF SUCH DAMAGE.
29054c7b81Shvozda  *
30054c7b81Shvozda  */
31054c7b81Shvozda 
32b543b397Smickey #include <sys/stat.h>
33b543b397Smickey #include <sys/ioctl.h>
34b543b397Smickey #include <sys/socket.h>
35b543b397Smickey #include <sys/un.h>
36b543b397Smickey #include <sys/wait.h>
37b543b397Smickey #include <sys/event.h>
38b543b397Smickey #include <sys/time.h>
39f6da2e61Ssturm #include <sys/sysctl.h>
40fea49c57Sjca #include <assert.h>
412a1df9f6Stedu #include <stdarg.h>
42054c7b81Shvozda #include <stdio.h>
43054c7b81Shvozda #include <syslog.h>
44054c7b81Shvozda #include <fcntl.h>
45054c7b81Shvozda #include <unistd.h>
46054c7b81Shvozda #include <stdlib.h>
47054c7b81Shvozda #include <string.h>
48054c7b81Shvozda #include <signal.h>
49ba4c8bcaSmickey #include <errno.h>
50b38f1a8fSmickey #include <err.h>
51b9fc9a72Sderaadt #include <limits.h>
52ba4c8bcaSmickey #include <machine/apmvar.h>
53b9fc9a72Sderaadt 
54054c7b81Shvozda #include "pathnames.h"
55054c7b81Shvozda #include "apm-proto.h"
56054c7b81Shvozda 
5795c85f17Stedu #define AUTO_SUSPEND 1
5895c85f17Stedu #define AUTO_HIBERNATE 2
5995c85f17Stedu 
60ba4c8bcaSmickey int debug = 0;
61054c7b81Shvozda 
62054c7b81Shvozda extern char *__progname;
63054c7b81Shvozda 
64054c7b81Shvozda void usage(void);
65054c7b81Shvozda int power_status(int fd, int force, struct apm_power_info *pinfo);
66054c7b81Shvozda int bind_socket(const char *sn);
67836803b5Sjca void handle_client(int sock_fd, int ctl_fd);
68c7cff24eSkn int suspend(int ctl_fd);
69c7cff24eSkn int stand_by(int ctl_fd);
70c7cff24eSkn int hibernate(int ctl_fd);
71882cc764Stedu void resumed(int ctl_fd);
726149d02eStedu void setperfpolicy(char *policy);
73054c7b81Shvozda void sigexit(int signo);
74054c7b81Shvozda void do_etc_file(const char *file);
758768ce35Sderaadt void error(const char *fmt, const char *arg);
768768ce35Sderaadt void set_driver_messages(int fd, int mode);
77054c7b81Shvozda 
78054c7b81Shvozda void
sigexit(int signo)79054c7b81Shvozda sigexit(int signo)
80054c7b81Shvozda {
81cd91c6e6Sderaadt 	_exit(1);
82054c7b81Shvozda }
83054c7b81Shvozda 
84054c7b81Shvozda void
logmsg(int prio,const char * msg,...)852a1df9f6Stedu logmsg(int prio, const char *msg, ...)
862a1df9f6Stedu {
872a1df9f6Stedu 	va_list ap;
882a1df9f6Stedu 
892a1df9f6Stedu 	va_start(ap, msg);
902a1df9f6Stedu 	if (debug) {
91e6311ecfSjca 		vfprintf(stderr, msg, ap);
92e6311ecfSjca 		fprintf(stderr, "\n");
932a1df9f6Stedu 	} else {
942a1df9f6Stedu 		vsyslog(prio, msg, ap);
952a1df9f6Stedu 	}
962a1df9f6Stedu 	va_end(ap);
972a1df9f6Stedu }
982a1df9f6Stedu 
992a1df9f6Stedu void
usage(void)100054c7b81Shvozda usage(void)
101054c7b81Shvozda {
1021ad7c21cSmarc 	fprintf(stderr,
10395c85f17Stedu 	    "usage: %s [-AadHLs] [-f devname] [-S sockname] [-t seconds] "
10495c85f17Stedu 		"[-Z percent] [-z percent]\n", __progname);
105054c7b81Shvozda 	exit(1);
106054c7b81Shvozda }
107054c7b81Shvozda 
10852cef1c2Smickey void
error(const char * fmt,const char * arg)10952cef1c2Smickey error(const char *fmt, const char *arg)
11052cef1c2Smickey {
11152cef1c2Smickey 	char buf[128];
11252cef1c2Smickey 
11352cef1c2Smickey 	if (debug)
11452cef1c2Smickey 		err(1, fmt, arg);
11552cef1c2Smickey 	else {
11652cef1c2Smickey 		strlcpy(buf, fmt, sizeof(buf));
11752cef1c2Smickey 		strlcat(buf, ": %m", sizeof(buf));
11852cef1c2Smickey 		syslog(LOG_ERR, buf, arg);
11952cef1c2Smickey 		exit(1);
12052cef1c2Smickey 	}
12152cef1c2Smickey }
12252cef1c2Smickey 
123054c7b81Shvozda 
124bea40608Smarc /*
125bea40608Smarc  * tell the driver if it should display messages or not.
126bea40608Smarc  */
127ba4c8bcaSmickey void
set_driver_messages(int fd,int mode)128bea40608Smarc set_driver_messages(int fd, int mode)
129bea40608Smarc {
130bea40608Smarc 	if (ioctl(fd, APM_IOC_PRN_CTL, &mode) == -1)
1312a1df9f6Stedu 		logmsg(LOG_DEBUG, "can't disable driver messages, error: %s",
1322a1df9f6Stedu 		    strerror(errno));
133bea40608Smarc }
134bea40608Smarc 
135054c7b81Shvozda int
power_status(int fd,int force,struct apm_power_info * pinfo)136054c7b81Shvozda power_status(int fd, int force, struct apm_power_info *pinfo)
137054c7b81Shvozda {
138054c7b81Shvozda 	struct apm_power_info bstate;
139054c7b81Shvozda 	static struct apm_power_info last;
140fb00f44fSbluhm 	int acon = 0, priority = LOG_NOTICE;
141054c7b81Shvozda 
142f6da2e61Ssturm 	if (fd == -1) {
143f6da2e61Ssturm 		if (pinfo) {
144f6da2e61Ssturm 			bstate.battery_state = 255;
145f6da2e61Ssturm 			bstate.ac_state = 255;
146f6da2e61Ssturm 			bstate.battery_life = 0;
147f6da2e61Ssturm 			bstate.minutes_left = -1;
148f6da2e61Ssturm 			*pinfo = bstate;
149f6da2e61Ssturm 		}
150f6da2e61Ssturm 
151f6da2e61Ssturm 		return 0;
152f6da2e61Ssturm 	}
153f6da2e61Ssturm 
154054c7b81Shvozda 	if (ioctl(fd, APM_IOC_GETPOWER, &bstate) == 0) {
155054c7b81Shvozda 	/* various conditions under which we report status:  something changed
156ba4c8bcaSmickey 	 * enough since last report, or asked to force a print */
157054c7b81Shvozda 		if (bstate.ac_state == APM_AC_ON)
158054c7b81Shvozda 			acon = 1;
159fb00f44fSbluhm 		if (bstate.battery_state == APM_BATT_CRITICAL &&
160fb00f44fSbluhm 		    bstate.battery_state != last.battery_state)
161fb00f44fSbluhm 			priority = LOG_EMERG;
162054c7b81Shvozda 		if (force ||
163054c7b81Shvozda 		    bstate.ac_state != last.ac_state ||
164054c7b81Shvozda 		    bstate.battery_state != last.battery_state ||
165da1d6147Srobert 		    ((bstate.battery_state != APM_BATT_CHARGING) &&
166da1d6147Srobert 		     (bstate.minutes_left && bstate.minutes_left < 15)) ||
1679ac46695Stedu 		    abs(bstate.battery_life - last.battery_life) >= 10) {
168966c1e6cSmiod 			if ((int)bstate.minutes_left > 0)
169fb00f44fSbluhm 				logmsg(priority, "battery status: %s. "
170ba4c8bcaSmickey 				    "external power status: %s. "
171b4c1455dSrobert 				    "estimated battery life %d%% "
172b4c1455dSrobert 				    "(%u minutes %s time estimate)",
173054c7b81Shvozda 				    battstate(bstate.battery_state),
174ba4c8bcaSmickey 				    ac_state(bstate.ac_state),
175ba4c8bcaSmickey 				    bstate.battery_life,
176b4c1455dSrobert 				    bstate.minutes_left,
177b4c1455dSrobert 				    (bstate.battery_state == APM_BATT_CHARGING)
178b4c1455dSrobert 					? "recharge" : "life");
179054c7b81Shvozda 			else
180fb00f44fSbluhm 				logmsg(priority, "battery status: %s. "
181ba4c8bcaSmickey 				    "external power status: %s. "
182054c7b81Shvozda 				    "estimated battery life %d%%",
183054c7b81Shvozda 				    battstate(bstate.battery_state),
184ba4c8bcaSmickey 				    ac_state(bstate.ac_state),
185ba4c8bcaSmickey 				    bstate.battery_life);
186054c7b81Shvozda 			last = bstate;
187054c7b81Shvozda 		}
188054c7b81Shvozda 		if (pinfo)
189054c7b81Shvozda 			*pinfo = bstate;
190054c7b81Shvozda 	} else
1919008c63fSkn 		logmsg(LOG_ERR, "cannot fetch power status: %s", strerror(errno));
192ba4c8bcaSmickey 
193054c7b81Shvozda 	return acon;
194054c7b81Shvozda }
195054c7b81Shvozda 
196054c7b81Shvozda int
bind_socket(const char * sockname)197054c7b81Shvozda bind_socket(const char *sockname)
198054c7b81Shvozda {
199054c7b81Shvozda 	struct sockaddr_un s_un;
20004b87f3aSderaadt 	mode_t old_umask;
201ba4c8bcaSmickey 	int sock;
202054c7b81Shvozda 
203b3a5c146Sguenther 	sock = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
204b38f1a8fSmickey 	if (sock == -1)
20552cef1c2Smickey 		error("cannot create local socket", NULL);
206054c7b81Shvozda 
207054c7b81Shvozda 	s_un.sun_family = AF_UNIX;
208e9bd1503Sguenther 	strlcpy(s_un.sun_path, sockname, sizeof(s_un.sun_path));
209ba4c8bcaSmickey 
210054c7b81Shvozda 	/* remove it if present, we're moving in */
211054c7b81Shvozda 	(void) remove(sockname);
212ba4c8bcaSmickey 
21304b87f3aSderaadt 	old_umask = umask(077);
214e9bd1503Sguenther 	if (bind(sock, (struct sockaddr *)&s_un, sizeof(s_un)) == -1)
2150f155b55Sjca 		error("cannot bind on APM socket", NULL);
21604b87f3aSderaadt 	umask(old_umask);
217bac616c8Sderaadt 	if (chmod(sockname, 0660) == -1 || chown(sockname, 0, 0) == -1)
218bac616c8Sderaadt 		error("cannot set socket mode/owner/group to 660/0/0", NULL);
219ba4c8bcaSmickey 
220054c7b81Shvozda 	listen(sock, 1);
221ba4c8bcaSmickey 
222054c7b81Shvozda 	return sock;
223054c7b81Shvozda }
224054c7b81Shvozda 
225836803b5Sjca void
handle_client(int sock_fd,int ctl_fd)226054c7b81Shvozda handle_client(int sock_fd, int ctl_fd)
227054c7b81Shvozda {
228054c7b81Shvozda 	/* accept a handle from the client, process it, then clean up */
229054c7b81Shvozda 	int cli_fd;
230054c7b81Shvozda 	struct sockaddr_un from;
23108e0da89Sderaadt 	socklen_t fromlen;
232054c7b81Shvozda 	struct apm_command cmd;
233054c7b81Shvozda 	struct apm_reply reply;
234af3b61a4Sjca 	int perfpol_mib[] = { CTL_HW, HW_PERFPOLICY };
235af3b61a4Sjca 	char perfpol[32];
236af3b61a4Sjca 	size_t perfpol_sz = sizeof(perfpol);
237cc5d5fa6Ssturm 	int cpuspeed_mib[] = { CTL_HW, HW_CPUSPEED };
23868f4a19fSderaadt 	int cpuspeed = 0;
239cc5d5fa6Ssturm 	size_t cpuspeed_sz = sizeof(cpuspeed);
240054c7b81Shvozda 
241bb89a20fSderaadt 	fromlen = sizeof(from);
242054c7b81Shvozda 	cli_fd = accept(sock_fd, (struct sockaddr *)&from, &fromlen);
243054c7b81Shvozda 	if (cli_fd == -1) {
2442a1df9f6Stedu 		logmsg(LOG_INFO, "client accept failure: %s", strerror(errno));
245836803b5Sjca 		return;
246054c7b81Shvozda 	}
247ba4c8bcaSmickey 
248054c7b81Shvozda 	if (recv(cli_fd, &cmd, sizeof(cmd), 0) != sizeof(cmd)) {
249054c7b81Shvozda 		(void) close(cli_fd);
2502a1df9f6Stedu 		logmsg(LOG_INFO, "client size botch");
251836803b5Sjca 		return;
252054c7b81Shvozda 	}
253ba4c8bcaSmickey 
254054c7b81Shvozda 	if (cmd.vno != APMD_VNO) {
255054c7b81Shvozda 		close(cli_fd);			/* terminate client */
256054c7b81Shvozda 		/* no error message, just drop it. */
257836803b5Sjca 		return;
258054c7b81Shvozda 	}
259ba4c8bcaSmickey 
260c7cff24eSkn 	bzero(&reply, sizeof(reply));
261054c7b81Shvozda 	power_status(ctl_fd, 0, &reply.batterystate);
262054c7b81Shvozda 	switch (cmd.action) {
2630693b59fSderaadt 	case SUSPEND:
2640693b59fSderaadt 		reply.newstate = SUSPENDING;
265c186fadcSjca 		reply.error = suspend(ctl_fd);
2660693b59fSderaadt 		break;
2670693b59fSderaadt 	case STANDBY:
2680693b59fSderaadt 		reply.newstate = STANDING_BY;
269c186fadcSjca 		reply.error = stand_by(ctl_fd);
2700693b59fSderaadt 		break;
27129a085afSderaadt 	case HIBERNATE:
27229a085afSderaadt 		reply.newstate = HIBERNATING;
273c186fadcSjca 		reply.error = hibernate(ctl_fd);
27429a085afSderaadt 		break;
275f6da2e61Ssturm 	case SETPERF_LOW:
276f6da2e61Ssturm 		reply.newstate = NORMAL;
2772a1df9f6Stedu 		logmsg(LOG_NOTICE, "setting hw.perfpolicy to low");
2786149d02eStedu 		setperfpolicy("low");
279f6da2e61Ssturm 		break;
280f6da2e61Ssturm 	case SETPERF_HIGH:
281f6da2e61Ssturm 		reply.newstate = NORMAL;
2822a1df9f6Stedu 		logmsg(LOG_NOTICE, "setting hw.perfpolicy to high");
2836149d02eStedu 		setperfpolicy("high");
284f6da2e61Ssturm 		break;
285f6da2e61Ssturm 	case SETPERF_AUTO:
286f6da2e61Ssturm 		reply.newstate = NORMAL;
2872a1df9f6Stedu 		logmsg(LOG_NOTICE, "setting hw.perfpolicy to auto");
2886149d02eStedu 		setperfpolicy("auto");
289ab844ba2Sbeck 		break;
2900693b59fSderaadt 	default:
2910693b59fSderaadt 		reply.newstate = NORMAL;
2920693b59fSderaadt 		break;
293054c7b81Shvozda 	}
294ba4c8bcaSmickey 
295af3b61a4Sjca 	reply.perfmode = PERF_NONE;
296af3b61a4Sjca 	if (sysctl(perfpol_mib, 2, perfpol, &perfpol_sz, NULL, 0) == -1)
297af3b61a4Sjca 		logmsg(LOG_INFO, "cannot read hw.perfpolicy");
298af3b61a4Sjca 	else {
299af3b61a4Sjca 		if (strcmp(perfpol, "manual") == 0 ||
300af3b61a4Sjca 		    strcmp(perfpol, "high") == 0) {
301af3b61a4Sjca 			reply.perfmode = PERF_MANUAL;
302af3b61a4Sjca 		} else if (strcmp(perfpol, "auto") == 0)
303af3b61a4Sjca 			reply.perfmode = PERF_AUTO;
304af3b61a4Sjca 	}
305cc5d5fa6Ssturm 
306af3b61a4Sjca 	if (sysctl(cpuspeed_mib, 2, &cpuspeed, &cpuspeed_sz, NULL, 0) == -1) {
307af3b61a4Sjca 		logmsg(LOG_INFO, "cannot read hw.cpuspeed");
308af3b61a4Sjca 		cpuspeed = 0;
309af3b61a4Sjca 	}
310cc5d5fa6Ssturm 	reply.cpuspeed = cpuspeed;
311054c7b81Shvozda 	reply.vno = APMD_VNO;
312ba4c8bcaSmickey 	if (send(cli_fd, &reply, sizeof(reply), 0) != sizeof(reply))
3132a1df9f6Stedu 		logmsg(LOG_INFO, "reply to client botched");
314054c7b81Shvozda 	close(cli_fd);
315054c7b81Shvozda }
316054c7b81Shvozda 
3175c5d853cSderaadt /*
3185c5d853cSderaadt  * Refresh the random file read by the bootblocks, and remove the +t bit
3195c5d853cSderaadt  * which the bootblock use to track "reuse of the file".
3205c5d853cSderaadt  */
3215c5d853cSderaadt void
fixrandom(void)3225c5d853cSderaadt fixrandom(void)
3235c5d853cSderaadt {
3245c5d853cSderaadt 	char buf[512];
3255c5d853cSderaadt 	int fd;
3265c5d853cSderaadt 
3275c5d853cSderaadt 	fd = open("/etc/random.seed", O_WRONLY);
3285c5d853cSderaadt 	if (fd != -1) {
3295c5d853cSderaadt 		arc4random_buf(buf, sizeof buf);
3305c5d853cSderaadt 		write(fd, buf, sizeof buf);
3315c5d853cSderaadt 		fchmod(fd, 0600);
3325c5d853cSderaadt 		close(fd);
3335c5d853cSderaadt 	}
3345c5d853cSderaadt }
3355c5d853cSderaadt 
336c7cff24eSkn int
suspend(int ctl_fd)337054c7b81Shvozda suspend(int ctl_fd)
338054c7b81Shvozda {
339c186fadcSjca 	int error = 0;
340c7cff24eSkn 
3412a1df9f6Stedu 	logmsg(LOG_NOTICE, "system suspending");
342882cc764Stedu 	power_status(ctl_fd, 1, NULL);
3435c5d853cSderaadt 	fixrandom();
344054c7b81Shvozda 	do_etc_file(_PATH_APM_ETC_SUSPEND);
345054c7b81Shvozda 	sync();
346054c7b81Shvozda 	sleep(1);
347c186fadcSjca 
348c186fadcSjca 	if (ioctl(ctl_fd, APM_IOC_SUSPEND, 0) == -1) {
349c186fadcSjca 		error = errno;
3502f1f646eSkn 		logmsg(LOG_WARNING, "%s: %s", __func__, strerror(errno));
351c186fadcSjca 	}
352c186fadcSjca 
353c186fadcSjca 	return error;
354054c7b81Shvozda }
355054c7b81Shvozda 
356c7cff24eSkn int
stand_by(int ctl_fd)357054c7b81Shvozda stand_by(int ctl_fd)
358054c7b81Shvozda {
359c186fadcSjca 	int error = 0;
360c7cff24eSkn 
3612a1df9f6Stedu 	logmsg(LOG_NOTICE, "system entering standby");
362882cc764Stedu 	power_status(ctl_fd, 1, NULL);
3635c5d853cSderaadt 	fixrandom();
364054c7b81Shvozda 	do_etc_file(_PATH_APM_ETC_STANDBY);
365054c7b81Shvozda 	sync();
366054c7b81Shvozda 	sleep(1);
367c186fadcSjca 
368c186fadcSjca 	if (ioctl(ctl_fd, APM_IOC_STANDBY, 0) == -1) {
369c186fadcSjca 		error = errno;
3702f1f646eSkn 		logmsg(LOG_WARNING, "%s: %s", __func__, strerror(errno));
371c186fadcSjca 	}
372c186fadcSjca 
373c186fadcSjca 	return error;
374054c7b81Shvozda }
375054c7b81Shvozda 
376c7cff24eSkn int
hibernate(int ctl_fd)37729a085afSderaadt hibernate(int ctl_fd)
37829a085afSderaadt {
379c186fadcSjca 	int error = 0;
380c7cff24eSkn 
3812a1df9f6Stedu 	logmsg(LOG_NOTICE, "system hibernating");
382882cc764Stedu 	power_status(ctl_fd, 1, NULL);
3835c5d853cSderaadt 	fixrandom();
38429a085afSderaadt 	do_etc_file(_PATH_APM_ETC_HIBERNATE);
38529a085afSderaadt 	sync();
38629a085afSderaadt 	sleep(1);
387c186fadcSjca 
388c186fadcSjca 	if (ioctl(ctl_fd, APM_IOC_HIBERNATE, 0) == -1) {
389c186fadcSjca 		error = errno;
3902f1f646eSkn 		logmsg(LOG_WARNING, "%s: %s", __func__, strerror(errno));
391c186fadcSjca 	}
392c186fadcSjca 
393c186fadcSjca 	return error;
39429a085afSderaadt }
39529a085afSderaadt 
396882cc764Stedu void
resumed(int ctl_fd)397882cc764Stedu resumed(int ctl_fd)
398882cc764Stedu {
399882cc764Stedu 	do_etc_file(_PATH_APM_ETC_RESUME);
4002a1df9f6Stedu 	logmsg(LOG_NOTICE, "system resumed from sleep");
401882cc764Stedu 	power_status(ctl_fd, 1, NULL);
402882cc764Stedu }
403882cc764Stedu 
4048c74e916Smickey #define TIMO (10*60)			/* 10 minutes */
40532fd24c3Sjca #define AUTOACTION_GRACE_PERIOD (60)	/* 1mn after resume */
406054c7b81Shvozda 
407bea40608Smarc int
main(int argc,char * argv[])408054c7b81Shvozda main(int argc, char *argv[])
409054c7b81Shvozda {
410d28e4965Skn 	const char *fname = _PATH_APM_CTLDEV;
4118d4bc9d5Sdcoppa 	int ctl_fd, sock_fd, ch, suspends, standbys, hibernates, resumes;
41232fd24c3Sjca 	int autoaction = 0, autoaction_inflight = 0;
41395c85f17Stedu 	int autolimit = 0;
414054c7b81Shvozda 	int statonly = 0;
415a831c31eSmickey 	int powerstatus = 0, powerbak = 0, powerchange = 0;
416ba4c8bcaSmickey 	int noacsleep = 0;
417b543b397Smickey 	struct timespec ts = {TIMO, 0}, sts = {0, 0};
41832fd24c3Sjca 	struct timespec last_resume = { 0, 0 };
419ab844ba2Sbeck 	struct apm_power_info pinfo;
420d28e4965Skn 	const char *sockname = _PATH_APM_SOCKET;
42195c85f17Stedu 	const char *errstr;
422f6da2e61Ssturm 	int kq, nchanges;
423b543b397Smickey 	struct kevent ev[2];
424af3b61a4Sjca 	int doperf = PERF_NONE;
425054c7b81Shvozda 
42695c85f17Stedu 	while ((ch = getopt(argc, argv, "aACdHLsf:t:S:z:Z:")) != -1)
427054c7b81Shvozda 		switch(ch) {
428054c7b81Shvozda 		case 'a':
429054c7b81Shvozda 			noacsleep = 1;
430054c7b81Shvozda 			break;
431054c7b81Shvozda 		case 'd':
432054c7b81Shvozda 			debug = 1;
433054c7b81Shvozda 			break;
434054c7b81Shvozda 		case 'f':
435054c7b81Shvozda 			fname = optarg;
436054c7b81Shvozda 			break;
437054c7b81Shvozda 		case 'S':
438054c7b81Shvozda 			sockname = optarg;
439054c7b81Shvozda 			break;
440054c7b81Shvozda 		case 't':
441550f9dfaSkn 			ts.tv_sec = strtonum(optarg, 1, LLONG_MAX, &errstr);
442550f9dfaSkn 			if (errstr != NULL)
443550f9dfaSkn 				errx(1, "number of seconds is %s: %s", errstr,
444550f9dfaSkn 				    optarg);
445054c7b81Shvozda 			break;
446054c7b81Shvozda 		case 's':	/* status only */
447054c7b81Shvozda 			statonly = 1;
448054c7b81Shvozda 			break;
449f6da2e61Ssturm 		case 'A':
450ab844ba2Sbeck 		case 'C':
451ab844ba2Sbeck 			if (doperf != PERF_NONE)
452ab844ba2Sbeck 				usage();
4536149d02eStedu 			doperf = PERF_AUTO;
4546149d02eStedu 			setperfpolicy("auto");
455ab844ba2Sbeck 			break;
456f6da2e61Ssturm 		case 'L':
457f6da2e61Ssturm 			if (doperf != PERF_NONE)
458f6da2e61Ssturm 				usage();
459a99a7ef6Ssturm 			doperf = PERF_MANUAL;
4606149d02eStedu 			setperfpolicy("low");
461f6da2e61Ssturm 			break;
462f6da2e61Ssturm 		case 'H':
463f6da2e61Ssturm 			if (doperf != PERF_NONE)
464f6da2e61Ssturm 				usage();
465a99a7ef6Ssturm 			doperf = PERF_MANUAL;
4666149d02eStedu 			setperfpolicy("high");
467f6da2e61Ssturm 			break;
46895c85f17Stedu 		case 'Z':
46995c85f17Stedu 			autoaction = AUTO_HIBERNATE;
47095c85f17Stedu 			autolimit = strtonum(optarg, 1, 100, &errstr);
47195c85f17Stedu 			if (errstr != NULL)
472550f9dfaSkn 				errx(1, "battery percentage is %s: %s", errstr,
47395c85f17Stedu 				    optarg);
47495c85f17Stedu 			break;
47595c85f17Stedu 		case 'z':
47695c85f17Stedu 			autoaction = AUTO_SUSPEND;
47795c85f17Stedu 			autolimit = strtonum(optarg, 1, 100, &errstr);
47895c85f17Stedu 			if (errstr != NULL)
479550f9dfaSkn 				errx(1, "battery percentage is %s: %s", errstr,
48095c85f17Stedu 				    optarg);
48195c85f17Stedu 			break;
482054c7b81Shvozda 		default:
483054c7b81Shvozda 			usage();
484054c7b81Shvozda 		}
485ba4c8bcaSmickey 
486054c7b81Shvozda 	argc -= optind;
487054c7b81Shvozda 	argv += optind;
488ba4c8bcaSmickey 
489f7fcee6dSderaadt 	if (argc != 0)
490f7fcee6dSderaadt 		usage();
491f7fcee6dSderaadt 
492a99a7ef6Ssturm 	if (doperf == PERF_NONE)
493a99a7ef6Ssturm 		doperf = PERF_MANUAL;
494a99a7ef6Ssturm 
4952a1df9f6Stedu 	if (debug == 0) {
496df69c215Sderaadt 		if (daemon(0, 0) == -1)
4974dbc74fcSjasper 			error("failed to daemonize", NULL);
498054c7b81Shvozda 		openlog(__progname, LOG_CONS, LOG_DAEMON);
499054c7b81Shvozda 		setlogmask(LOG_UPTO(LOG_NOTICE));
500054c7b81Shvozda 	}
501ba4c8bcaSmickey 
502b38f1a8fSmickey 	(void) signal(SIGTERM, sigexit);
503b38f1a8fSmickey 	(void) signal(SIGHUP, sigexit);
504b38f1a8fSmickey 	(void) signal(SIGINT, sigexit);
505b38f1a8fSmickey 
5060205f8e6Sguenther 	if ((ctl_fd = open(fname, O_RDWR | O_CLOEXEC)) == -1) {
50734ee9765Ssturm 		if (errno != ENXIO && errno != ENOENT)
50852cef1c2Smickey 			error("cannot open device file `%s'", fname);
5090205f8e6Sguenther 	}
5105b2b631eSmickey 
51152cef1c2Smickey 	sock_fd = bind_socket(sockname);
51252cef1c2Smickey 
513ab844ba2Sbeck 	power_status(ctl_fd, 1, &pinfo);
514ba4c8bcaSmickey 
515054c7b81Shvozda 	if (statonly)
516054c7b81Shvozda 		exit(0);
517ba4c8bcaSmickey 
5182ecd7ab2Skn 	if (unveil(_PATH_APM_ETC_DIR, "rx") == -1)
519bc5a8259Sbeck 		err(1, "unveil %s", _PATH_APM_ETC_DIR);
5205c5d853cSderaadt 	if (unveil("/etc/random.seed", "w") == -1)
5215c5d853cSderaadt 		err(1, "unveil /etc/random.seed");
5222ecd7ab2Skn 	if (unveil(NULL, NULL) == -1)
5232ecd7ab2Skn 		err(1, "unveil");
5242ecd7ab2Skn 
525bea40608Smarc 	set_driver_messages(ctl_fd, APM_PRINT_OFF);
526ba4c8bcaSmickey 
527b543b397Smickey 	kq = kqueue();
52852cef1c2Smickey 	if (kq <= 0)
52952cef1c2Smickey 		error("kqueue", NULL);
530ba4c8bcaSmickey 
531f6da2e61Ssturm 	EV_SET(&ev[0], sock_fd, EVFILT_READ, EV_ADD | EV_ENABLE | EV_CLEAR,
532b543b397Smickey 	    0, 0, NULL);
533f6da2e61Ssturm 	if (ctl_fd == -1)
534f6da2e61Ssturm 		nchanges = 1;
535f6da2e61Ssturm 	else {
536f6da2e61Ssturm 		EV_SET(&ev[1], ctl_fd, EVFILT_READ, EV_ADD | EV_ENABLE |
537f6da2e61Ssturm 		    EV_CLEAR, 0, 0, NULL);
538f6da2e61Ssturm 		nchanges = 2;
539f6da2e61Ssturm 	}
540df69c215Sderaadt 	if (kevent(kq, ev, nchanges, NULL, 0, &sts) == -1)
54152cef1c2Smickey 		error("kevent", NULL);
542054c7b81Shvozda 
54377661748Sderaadt 	for (;;) {
544fea49c57Sjca 		int rv, event, index;
54577661748Sderaadt 
546b543b397Smickey 		sts = ts;
54777661748Sderaadt 
548df69c215Sderaadt 		if ((rv = kevent(kq, NULL, 0, ev, 1, &sts)) == -1)
549b543b397Smickey 			break;
550b543b397Smickey 
551d7bc99aeSjca 		if (rv == 1 && ev->ident == sock_fd) {
552836803b5Sjca 			handle_client(sock_fd, ctl_fd);
553d7bc99aeSjca 			continue;
554d7bc99aeSjca 		}
555d7bc99aeSjca 
556d7bc99aeSjca 		suspends = standbys = hibernates = resumes = 0;
557d7bc99aeSjca 
558d7bc99aeSjca 		if (rv == 0 && ctl_fd == -1) {
559d7bc99aeSjca 			/* timeout and no way to query status */
560d7bc99aeSjca 			continue;
561d7bc99aeSjca 		} else if (rv == 0) {
562054c7b81Shvozda 			/* wakeup for timeout: take status */
563fea49c57Sjca 			event = APM_POWER_CHANGE;
564fea49c57Sjca 			index = -1;
565fea49c57Sjca 		} else {
566fea49c57Sjca 			assert(rv == 1 && ev->ident == ctl_fd);
567fea49c57Sjca 			event = APM_EVENT_TYPE(ev->data);
568fea49c57Sjca 			index = APM_EVENT_INDEX(ev->data);
569a831c31eSmickey 		}
57095c85f17Stedu 
571fea49c57Sjca 		logmsg(LOG_DEBUG, "apmevent %04x index %d", event, index);
57295c85f17Stedu 
573fea49c57Sjca 		switch (event) {
574054c7b81Shvozda 		case APM_SUSPEND_REQ:
575054c7b81Shvozda 		case APM_USER_SUSPEND_REQ:
576054c7b81Shvozda 		case APM_CRIT_SUSPEND_REQ:
577054c7b81Shvozda 		case APM_BATTERY_LOW:
578054c7b81Shvozda 			suspends++;
579054c7b81Shvozda 			break;
580054c7b81Shvozda 		case APM_USER_STANDBY_REQ:
581054c7b81Shvozda 		case APM_STANDBY_REQ:
582054c7b81Shvozda 			standbys++;
583054c7b81Shvozda 			break;
5848d4bc9d5Sdcoppa 		case APM_USER_HIBERNATE_REQ:
5858d4bc9d5Sdcoppa 			hibernates++;
5868d4bc9d5Sdcoppa 			break;
587054c7b81Shvozda 		case APM_NORMAL_RESUME:
588054c7b81Shvozda 		case APM_CRIT_RESUME:
589054c7b81Shvozda 		case APM_SYS_STANDBY_RESUME:
590ab844ba2Sbeck 			powerbak = power_status(ctl_fd, 0, &pinfo);
591a831c31eSmickey 			if (powerstatus != powerbak) {
592a831c31eSmickey 				powerstatus = powerbak;
593a831c31eSmickey 				powerchange = 1;
594a831c31eSmickey 			}
59532fd24c3Sjca 			clock_gettime(CLOCK_MONOTONIC, &last_resume);
59632fd24c3Sjca 			autoaction_inflight = 0;
597054c7b81Shvozda 			resumes++;
598054c7b81Shvozda 			break;
599054c7b81Shvozda 		case APM_POWER_CHANGE:
600ab844ba2Sbeck 			powerbak = power_status(ctl_fd, 0, &pinfo);
601a831c31eSmickey 			if (powerstatus != powerbak) {
602a831c31eSmickey 				powerstatus = powerbak;
603a831c31eSmickey 				powerchange = 1;
604a831c31eSmickey 			}
605add3467cSjca 
606add3467cSjca 			if (!powerstatus && autoaction &&
607add3467cSjca 			    autolimit > (int)pinfo.battery_life) {
60832fd24c3Sjca 				struct timespec graceperiod, now;
60932fd24c3Sjca 
61032fd24c3Sjca 				graceperiod = last_resume;
61132fd24c3Sjca 				graceperiod.tv_sec += AUTOACTION_GRACE_PERIOD;
61232fd24c3Sjca 				clock_gettime(CLOCK_MONOTONIC, &now);
61332fd24c3Sjca 
614add3467cSjca 				logmsg(LOG_NOTICE,
615add3467cSjca 				    "estimated battery life %d%%"
61632fd24c3Sjca 				    " below configured limit %d%%%s%s",
61732fd24c3Sjca 				    pinfo.battery_life, autolimit,
61832fd24c3Sjca 				    !autoaction_inflight ? "" : ", in flight",
61932fd24c3Sjca 				    timespeccmp(&now, &graceperiod, >) ?
62032fd24c3Sjca 				        "" : ", grace period"
621fea49c57Sjca 				);
622add3467cSjca 
62332fd24c3Sjca 				if (!autoaction_inflight &&
62432fd24c3Sjca 				    timespeccmp(&now, &graceperiod, >)) {
625add3467cSjca 					if (autoaction == AUTO_SUSPEND)
626add3467cSjca 						suspends++;
627add3467cSjca 					else
628add3467cSjca 						hibernates++;
62932fd24c3Sjca 					/* Block autoaction until next resume */
63032fd24c3Sjca 					autoaction_inflight = 1;
63132fd24c3Sjca 				}
632add3467cSjca 			}
633054c7b81Shvozda 			break;
634054c7b81Shvozda 		default:
6350693b59fSderaadt 			;
636054c7b81Shvozda 		}
637ba4c8bcaSmickey 
638054c7b81Shvozda 		if ((standbys || suspends) && noacsleep &&
639ab844ba2Sbeck 		    power_status(ctl_fd, 0, &pinfo))
6402a1df9f6Stedu 			logmsg(LOG_DEBUG, "no! sleep! till brooklyn!");
641ba4c8bcaSmickey 		else if (suspends)
642054c7b81Shvozda 			suspend(ctl_fd);
643ba4c8bcaSmickey 		else if (standbys)
644054c7b81Shvozda 			stand_by(ctl_fd);
6458d4bc9d5Sdcoppa 		else if (hibernates)
6468d4bc9d5Sdcoppa 			hibernate(ctl_fd);
647ba4c8bcaSmickey 		else if (resumes) {
648882cc764Stedu 			resumed(ctl_fd);
6495432be34Sangelos 		}
650ba4c8bcaSmickey 
651a831c31eSmickey 		if (powerchange) {
652a831c31eSmickey 			if (powerstatus)
653ba4c8bcaSmickey 				do_etc_file(_PATH_APM_ETC_POWERUP);
654a831c31eSmickey 			else
655ba4c8bcaSmickey 				do_etc_file(_PATH_APM_ETC_POWERDOWN);
656a831c31eSmickey 			powerchange = 0;
657a831c31eSmickey 		}
658054c7b81Shvozda 	}
65952cef1c2Smickey 	error("kevent loop", NULL);
660ba4c8bcaSmickey 
661bea40608Smarc 	return 1;
662054c7b81Shvozda }
663054c7b81Shvozda 
664054c7b81Shvozda void
setperfpolicy(char * policy)6656149d02eStedu setperfpolicy(char *policy)
666f6da2e61Ssturm {
6676149d02eStedu 	int hw_perfpol_mib[] = { CTL_HW, HW_PERFPOLICY };
668d229da1cSjca 	int hw_perf_mib[] = { CTL_HW, HW_SETPERF };
669d229da1cSjca 	int new_perf = -1;
6706149d02eStedu 
6716149d02eStedu 	if (strcmp(policy, "low") == 0) {
6726149d02eStedu 		policy = "manual";
673d229da1cSjca 		new_perf = 0;
674d229da1cSjca 	} else if (strcmp(policy, "high") == 0) {
675d229da1cSjca 		policy = "manual";
676d229da1cSjca 		new_perf = 100;
6776149d02eStedu 	}
6786149d02eStedu 
679d229da1cSjca 	if (sysctl(hw_perfpol_mib, 2, NULL, NULL,
680df69c215Sderaadt 	    policy, strlen(policy) + 1) == -1)
6812a1df9f6Stedu 		logmsg(LOG_INFO, "cannot set hw.perfpolicy");
6826149d02eStedu 
683d229da1cSjca 	if (new_perf == -1)
684d229da1cSjca 		return;
685d229da1cSjca 
686d229da1cSjca 	if (sysctl(hw_perf_mib, 2, NULL, NULL,
687d229da1cSjca 	    &new_perf, sizeof(new_perf)) == -1)
6882a1df9f6Stedu 		logmsg(LOG_INFO, "cannot set hw.setperf");
689f6da2e61Ssturm }
690f6da2e61Ssturm 
691f6da2e61Ssturm void
do_etc_file(const char * file)692054c7b81Shvozda do_etc_file(const char *file)
693054c7b81Shvozda {
694054c7b81Shvozda 	pid_t pid;
695054c7b81Shvozda 	int status;
696054c7b81Shvozda 	const char *prog;
697054c7b81Shvozda 
698054c7b81Shvozda 	/* If file doesn't exist, do nothing. */
699054c7b81Shvozda 	if (access(file, X_OK|R_OK)) {
7002a1df9f6Stedu 		logmsg(LOG_DEBUG, "do_etc_file(): cannot access file %s", file);
701054c7b81Shvozda 		return;
702054c7b81Shvozda 	}
703054c7b81Shvozda 
704054c7b81Shvozda 	prog = strrchr(file, '/');
705054c7b81Shvozda 	if (prog)
706054c7b81Shvozda 		prog++;
707054c7b81Shvozda 	else
708054c7b81Shvozda 		prog = file;
709054c7b81Shvozda 
710054c7b81Shvozda 	pid = fork();
711054c7b81Shvozda 	switch (pid) {
712054c7b81Shvozda 	case -1:
7132a1df9f6Stedu 		logmsg(LOG_ERR, "failed to fork(): %s", strerror(errno));
714054c7b81Shvozda 		return;
715054c7b81Shvozda 	case 0:
716054c7b81Shvozda 		/* We are the child. */
717c96f6a27Sderaadt 		execl(file, prog, (char *)NULL);
718600f892eSkn 		logmsg(LOG_ERR, "failed to exec %s: %s", file, strerror(errno));
71943ae5fd5Sderaadt 		_exit(1);
720054c7b81Shvozda 		/* NOTREACHED */
721054c7b81Shvozda 	default:
722054c7b81Shvozda 		/* We are the parent. */
723054c7b81Shvozda 		wait4(pid, &status, 0, 0);
724054c7b81Shvozda 		if (WIFEXITED(status))
7252a1df9f6Stedu 			logmsg(LOG_DEBUG, "%s exited with status %d", file,
726054c7b81Shvozda 			    WEXITSTATUS(status));
727ba4c8bcaSmickey 		else
7282a1df9f6Stedu 			logmsg(LOG_ERR, "%s exited abnormally.", file);
729054c7b81Shvozda 	}
730054c7b81Shvozda }
731