GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.sbin/apmd/apmd.c Lines: 0 293 0.0 %
Date: 2017-11-13 Branches: 0 182 0.0 %

Line Branch Exec Source
1
/*	$OpenBSD: apmd.c,v 1.81 2017/10/15 15:14:49 jca Exp $	*/
2
3
/*
4
 *  Copyright (c) 1995, 1996 John T. Kohl
5
 *  All rights reserved.
6
 *
7
 *  Redistribution and use in source and binary forms, with or without
8
 *  modification, are permitted provided that the following conditions
9
 *  are met:
10
 *  1. Redistributions of source code must retain the above copyright
11
 *     notice, this list of conditions and the following disclaimer.
12
 *  2. Redistributions in binary form must reproduce the above copyright
13
 *     notice, this list of conditions and the following disclaimer in the
14
 *     documentation and/or other materials provided with the distribution.
15
 *  3. The name of the author may not be used to endorse or promote products
16
 *     derived from this software without specific prior written permission.
17
 *
18
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR
19
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
 * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
22
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
27
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
 * POSSIBILITY OF SUCH DAMAGE.
29
 *
30
 */
31
32
#include <sys/stat.h>
33
#include <sys/ioctl.h>
34
#include <sys/socket.h>
35
#include <sys/un.h>
36
#include <sys/wait.h>
37
#include <sys/event.h>
38
#include <sys/time.h>
39
#include <sys/sched.h>
40
#include <sys/sysctl.h>
41
#include <stdio.h>
42
#include <syslog.h>
43
#include <fcntl.h>
44
#include <unistd.h>
45
#include <stdlib.h>
46
#include <string.h>
47
#include <signal.h>
48
#include <errno.h>
49
#include <err.h>
50
#include <limits.h>
51
#include <machine/apmvar.h>
52
53
#include "pathnames.h"
54
#include "apm-proto.h"
55
56
#define AUTO_SUSPEND 1
57
#define AUTO_HIBERNATE 2
58
59
const char apmdev[] = _PATH_APM_CTLDEV;
60
const char sockfile[] = _PATH_APM_SOCKET;
61
62
int debug = 0;
63
64
int doperf = PERF_NONE;
65
66
extern char *__progname;
67
68
void usage(void);
69
int power_status(int fd, int force, struct apm_power_info *pinfo);
70
int bind_socket(const char *sn);
71
enum apm_state handle_client(int sock_fd, int ctl_fd);
72
int  get_avg_idle_mp(int ncpu);
73
int  get_avg_idle_up(void);
74
void perf_status(struct apm_power_info *pinfo, int ncpu);
75
void suspend(int ctl_fd);
76
void stand_by(int ctl_fd);
77
void hibernate(int ctl_fd);
78
void setperfpolicy(char *policy);
79
void sigexit(int signo);
80
void do_etc_file(const char *file);
81
void sockunlink(void);
82
void error(const char *fmt, const char *arg);
83
void set_driver_messages(int fd, int mode);
84
85
/* ARGSUSED */
86
void
87
sigexit(int signo)
88
{
89
	sockunlink();
90
	_exit(1);
91
}
92
93
void
94
usage(void)
95
{
96
	fprintf(stderr,
97
	    "usage: %s [-AadHLs] [-f devname] [-S sockname] [-t seconds] "
98
		"[-Z percent] [-z percent]\n", __progname);
99
	exit(1);
100
}
101
102
void
103
error(const char *fmt, const char *arg)
104
{
105
	char buf[128];
106
107
	if (debug)
108
		err(1, fmt, arg);
109
	else {
110
		strlcpy(buf, fmt, sizeof(buf));
111
		strlcat(buf, ": %m", sizeof(buf));
112
		syslog(LOG_ERR, buf, arg);
113
		exit(1);
114
	}
115
}
116
117
118
/*
119
 * tell the driver if it should display messages or not.
120
 */
121
void
122
set_driver_messages(int fd, int mode)
123
{
124
	if (ioctl(fd, APM_IOC_PRN_CTL, &mode) == -1)
125
		syslog(LOG_DEBUG, "can't disable driver messages, error: %m");
126
}
127
128
int
129
power_status(int fd, int force, struct apm_power_info *pinfo)
130
{
131
	struct apm_power_info bstate;
132
	static struct apm_power_info last;
133
	int acon = 0;
134
135
	if (fd == -1) {
136
		if (pinfo) {
137
			bstate.battery_state = 255;
138
			bstate.ac_state = 255;
139
			bstate.battery_life = 0;
140
			bstate.minutes_left = -1;
141
			*pinfo = bstate;
142
		}
143
144
		return 0;
145
	}
146
147
	if (ioctl(fd, APM_IOC_GETPOWER, &bstate) == 0) {
148
	/* various conditions under which we report status:  something changed
149
	 * enough since last report, or asked to force a print */
150
		if (bstate.ac_state == APM_AC_ON)
151
			acon = 1;
152
		if (force ||
153
		    bstate.ac_state != last.ac_state ||
154
		    bstate.battery_state != last.battery_state ||
155
		    (bstate.minutes_left && bstate.minutes_left < 15) ||
156
		    abs(bstate.battery_life - last.battery_life) >= 10) {
157
#ifdef __powerpc__
158
			/*
159
			 * When the battery is charging, the estimated life
160
			 * time is in fact the estimated remaining charge time
161
			 * on Apple machines, so lie in the stats.
162
			 * We still want an useful message if the battery or
163
			 * ac status changes, however.
164
			 */
165
			if (bstate.minutes_left != 0 &&
166
			    bstate.battery_state != APM_BATT_CHARGING)
167
#else
168
			if ((int)bstate.minutes_left > 0)
169
#endif
170
				syslog(LOG_NOTICE, "battery status: %s. "
171
				    "external power status: %s. "
172
				    "estimated battery life %d%% (%u minutes)",
173
				    battstate(bstate.battery_state),
174
				    ac_state(bstate.ac_state),
175
				    bstate.battery_life,
176
				    bstate.minutes_left);
177
			else
178
				syslog(LOG_NOTICE, "battery status: %s. "
179
				    "external power status: %s. "
180
				    "estimated battery life %d%%",
181
				    battstate(bstate.battery_state),
182
				    ac_state(bstate.ac_state),
183
				    bstate.battery_life);
184
			last = bstate;
185
		}
186
		if (pinfo)
187
			*pinfo = bstate;
188
	} else
189
		syslog(LOG_ERR, "cannot fetch power status: %m");
190
191
	return acon;
192
}
193
194
char socketname[PATH_MAX];
195
196
void
197
sockunlink(void)
198
{
199
	if (socketname[0])
200
		remove(socketname);
201
}
202
203
int
204
bind_socket(const char *sockname)
205
{
206
	struct sockaddr_un s_un;
207
	mode_t old_umask;
208
	int sock;
209
210
	sock = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
211
	if (sock == -1)
212
		error("cannot create local socket", NULL);
213
214
	s_un.sun_family = AF_UNIX;
215
	strlcpy(s_un.sun_path, sockname, sizeof(s_un.sun_path));
216
217
	/* remove it if present, we're moving in */
218
	(void) remove(sockname);
219
220
	old_umask = umask(077);
221
	if (bind(sock, (struct sockaddr *)&s_un, sizeof(s_un)) == -1)
222
		error("cannot bind on APM socket", NULL);
223
	umask(old_umask);
224
	if (chmod(sockname, 0660) == -1 || chown(sockname, 0, 0) == -1)
225
		error("cannot set socket mode/owner/group to 660/0/0", NULL);
226
227
	listen(sock, 1);
228
	strlcpy(socketname, sockname, sizeof socketname);
229
	atexit(sockunlink);
230
231
	return sock;
232
}
233
234
enum apm_state
235
handle_client(int sock_fd, int ctl_fd)
236
{
237
	/* accept a handle from the client, process it, then clean up */
238
	int cli_fd;
239
	struct sockaddr_un from;
240
	socklen_t fromlen;
241
	struct apm_command cmd;
242
	struct apm_reply reply;
243
	int cpuspeed_mib[] = {CTL_HW, HW_CPUSPEED};
244
	int cpuspeed = 0;
245
	size_t cpuspeed_sz = sizeof(cpuspeed);
246
247
	fromlen = sizeof(from);
248
	cli_fd = accept(sock_fd, (struct sockaddr *)&from, &fromlen);
249
	if (cli_fd == -1) {
250
		syslog(LOG_INFO, "client accept failure: %m");
251
		return NORMAL;
252
	}
253
254
	if (recv(cli_fd, &cmd, sizeof(cmd), 0) != sizeof(cmd)) {
255
		(void) close(cli_fd);
256
		syslog(LOG_INFO, "client size botch");
257
		return NORMAL;
258
	}
259
260
	if (cmd.vno != APMD_VNO) {
261
		close(cli_fd);			/* terminate client */
262
		/* no error message, just drop it. */
263
		return NORMAL;
264
	}
265
266
	power_status(ctl_fd, 0, &reply.batterystate);
267
	switch (cmd.action) {
268
	case SUSPEND:
269
		reply.newstate = SUSPENDING;
270
		break;
271
	case STANDBY:
272
		reply.newstate = STANDING_BY;
273
		break;
274
	case HIBERNATE:
275
		reply.newstate = HIBERNATING;
276
		break;
277
	case SETPERF_LOW:
278
		doperf = PERF_MANUAL;
279
		reply.newstate = NORMAL;
280
		syslog(LOG_NOTICE, "setting hw.perfpolicy to low");
281
		setperfpolicy("low");
282
		break;
283
	case SETPERF_HIGH:
284
		doperf = PERF_MANUAL;
285
		reply.newstate = NORMAL;
286
		syslog(LOG_NOTICE, "setting hw.perfpolicy to high");
287
		setperfpolicy("high");
288
		break;
289
	case SETPERF_AUTO:
290
	case SETPERF_COOL:
291
		doperf = PERF_AUTO;
292
		reply.newstate = NORMAL;
293
		syslog(LOG_NOTICE, "setting hw.perfpolicy to auto");
294
		setperfpolicy("auto");
295
		break;
296
	default:
297
		reply.newstate = NORMAL;
298
		break;
299
	}
300
301
	if (sysctl(cpuspeed_mib, 2, &cpuspeed, &cpuspeed_sz, NULL, 0) < 0)
302
		syslog(LOG_INFO, "cannot read hw.cpuspeed");
303
304
	reply.cpuspeed = cpuspeed;
305
	reply.perfmode = doperf;
306
	reply.vno = APMD_VNO;
307
	if (send(cli_fd, &reply, sizeof(reply), 0) != sizeof(reply))
308
		syslog(LOG_INFO, "reply to client botched");
309
	close(cli_fd);
310
311
	return reply.newstate;
312
}
313
314
void
315
suspend(int ctl_fd)
316
{
317
	syslog(LOG_NOTICE, "system suspending");
318
	do_etc_file(_PATH_APM_ETC_SUSPEND);
319
	sync();
320
	sleep(1);
321
	ioctl(ctl_fd, APM_IOC_SUSPEND, 0);
322
}
323
324
void
325
stand_by(int ctl_fd)
326
{
327
	syslog(LOG_NOTICE, "system entering standby");
328
	do_etc_file(_PATH_APM_ETC_STANDBY);
329
	sync();
330
	sleep(1);
331
	ioctl(ctl_fd, APM_IOC_STANDBY, 0);
332
}
333
334
void
335
hibernate(int ctl_fd)
336
{
337
	syslog(LOG_NOTICE, "system hibernating");
338
	do_etc_file(_PATH_APM_ETC_HIBERNATE);
339
	sync();
340
	sleep(1);
341
	ioctl(ctl_fd, APM_IOC_HIBERNATE, 0);
342
}
343
344
#define TIMO (10*60)			/* 10 minutes */
345
346
int
347
main(int argc, char *argv[])
348
{
349
	const char *fname = apmdev;
350
	int ctl_fd, sock_fd, ch, suspends, standbys, hibernates, resumes;
351
	int autoaction = 0;
352
	int autolimit = 0;
353
	int statonly = 0;
354
	int powerstatus = 0, powerbak = 0, powerchange = 0;
355
	int noacsleep = 0;
356
	struct timespec ts = {TIMO, 0}, sts = {0, 0};
357
	struct apm_power_info pinfo;
358
	time_t apmtimeout = 0;
359
	const char *sockname = sockfile;
360
	const char *errstr;
361
	int kq, nchanges;
362
	struct kevent ev[2];
363
	int ncpu_mib[2] = { CTL_HW, HW_NCPU };
364
	int ncpu;
365
	size_t ncpu_sz = sizeof(ncpu);
366
367
	while ((ch = getopt(argc, argv, "aACdHLsf:t:S:z:Z:")) != -1)
368
		switch(ch) {
369
		case 'a':
370
			noacsleep = 1;
371
			break;
372
		case 'd':
373
			debug = 1;
374
			break;
375
		case 'f':
376
			fname = optarg;
377
			break;
378
		case 'S':
379
			sockname = optarg;
380
			break;
381
		case 't':
382
			ts.tv_sec = strtoul(optarg, NULL, 0);
383
			if (ts.tv_sec == 0)
384
				usage();
385
			break;
386
		case 's':	/* status only */
387
			statonly = 1;
388
			break;
389
		case 'A':
390
		case 'C':
391
			if (doperf != PERF_NONE)
392
				usage();
393
			doperf = PERF_AUTO;
394
			setperfpolicy("auto");
395
			break;
396
		case 'L':
397
			if (doperf != PERF_NONE)
398
				usage();
399
			doperf = PERF_MANUAL;
400
			setperfpolicy("low");
401
			break;
402
		case 'H':
403
			if (doperf != PERF_NONE)
404
				usage();
405
			doperf = PERF_MANUAL;
406
			setperfpolicy("high");
407
			break;
408
		case 'Z':
409
			autoaction = AUTO_HIBERNATE;
410
			autolimit = strtonum(optarg, 1, 100, &errstr);
411
			if (errstr != NULL)
412
				errc(1, EINVAL, "%s percentage: %s", errstr,
413
				    optarg);
414
			break;
415
		case 'z':
416
			autoaction = AUTO_SUSPEND;
417
			autolimit = strtonum(optarg, 1, 100, &errstr);
418
			if (errstr != NULL)
419
				errc(1, EINVAL, "%s percentage: %s", errstr,
420
				    optarg);
421
			break;
422
		case '?':
423
		default:
424
			usage();
425
		}
426
427
	argc -= optind;
428
	argv += optind;
429
430
	if (argc != 0)
431
		usage();
432
433
	if (doperf == PERF_NONE)
434
		doperf = PERF_MANUAL;
435
436
	if (debug)
437
		openlog(__progname, LOG_CONS, LOG_LOCAL1);
438
	else {
439
		if (daemon(0, 0) < 0)
440
			error("failed to daemonize", NULL);
441
		openlog(__progname, LOG_CONS, LOG_DAEMON);
442
		setlogmask(LOG_UPTO(LOG_NOTICE));
443
	}
444
445
	(void) signal(SIGTERM, sigexit);
446
	(void) signal(SIGHUP, sigexit);
447
	(void) signal(SIGINT, sigexit);
448
449
	if ((ctl_fd = open(fname, O_RDWR | O_CLOEXEC)) == -1) {
450
		if (errno != ENXIO && errno != ENOENT)
451
			error("cannot open device file `%s'", fname);
452
	}
453
454
	sock_fd = bind_socket(sockname);
455
456
	power_status(ctl_fd, 1, &pinfo);
457
458
	if (statonly)
459
		exit(0);
460
461
	set_driver_messages(ctl_fd, APM_PRINT_OFF);
462
463
	kq = kqueue();
464
	if (kq <= 0)
465
		error("kqueue", NULL);
466
467
	EV_SET(&ev[0], sock_fd, EVFILT_READ, EV_ADD | EV_ENABLE | EV_CLEAR,
468
	    0, 0, NULL);
469
	if (ctl_fd == -1)
470
		nchanges = 1;
471
	else {
472
		EV_SET(&ev[1], ctl_fd, EVFILT_READ, EV_ADD | EV_ENABLE |
473
		    EV_CLEAR, 0, 0, NULL);
474
		nchanges = 2;
475
	}
476
	if (kevent(kq, ev, nchanges, NULL, 0, &sts) < 0)
477
		error("kevent", NULL);
478
479
	if (sysctl(ncpu_mib, 2, &ncpu, &ncpu_sz, NULL, 0) < 0)
480
		error("cannot read hw.ncpu", NULL);
481
482
	for (;;) {
483
		int rv;
484
485
		sts = ts;
486
487
		apmtimeout += 1;
488
		if ((rv = kevent(kq, NULL, 0, ev, 1, &sts)) < 0)
489
			break;
490
491
		if (apmtimeout >= ts.tv_sec) {
492
			apmtimeout = 0;
493
494
			/* wakeup for timeout: take status */
495
			powerbak = power_status(ctl_fd, 0, &pinfo);
496
			if (powerstatus != powerbak) {
497
				powerstatus = powerbak;
498
				powerchange = 1;
499
			}
500
501
			if (!powerstatus && autoaction &&
502
			    autolimit > (int)pinfo.battery_life) {
503
				syslog(LOG_NOTICE,
504
				    "estimated battery life %d%%, "
505
				    "autoaction limit set to %d%% .",
506
				    pinfo.battery_life,
507
				    autolimit
508
				);
509
510
				if (autoaction == AUTO_SUSPEND)
511
					suspend(ctl_fd);
512
				else
513
					hibernate(ctl_fd);
514
			}
515
		}
516
517
		if (!rv)
518
			continue;
519
520
		if (ev->ident == ctl_fd) {
521
			suspends = standbys = hibernates = resumes = 0;
522
			syslog(LOG_DEBUG, "apmevent %04x index %d",
523
			    (int)APM_EVENT_TYPE(ev->data),
524
			    (int)APM_EVENT_INDEX(ev->data));
525
526
			switch (APM_EVENT_TYPE(ev->data)) {
527
			case APM_SUSPEND_REQ:
528
			case APM_USER_SUSPEND_REQ:
529
			case APM_CRIT_SUSPEND_REQ:
530
			case APM_BATTERY_LOW:
531
				suspends++;
532
				break;
533
			case APM_USER_STANDBY_REQ:
534
			case APM_STANDBY_REQ:
535
				standbys++;
536
				break;
537
			case APM_USER_HIBERNATE_REQ:
538
				hibernates++;
539
				break;
540
#if 0
541
			case APM_CANCEL:
542
				suspends = standbys = 0;
543
				break;
544
#endif
545
			case APM_NORMAL_RESUME:
546
			case APM_CRIT_RESUME:
547
			case APM_SYS_STANDBY_RESUME:
548
				powerbak = power_status(ctl_fd, 0, &pinfo);
549
				if (powerstatus != powerbak) {
550
					powerstatus = powerbak;
551
					powerchange = 1;
552
				}
553
				resumes++;
554
				break;
555
			case APM_POWER_CHANGE:
556
				powerbak = power_status(ctl_fd, 0, &pinfo);
557
				if (powerstatus != powerbak) {
558
					powerstatus = powerbak;
559
					powerchange = 1;
560
				}
561
				break;
562
			default:
563
				;
564
			}
565
566
			if ((standbys || suspends) && noacsleep &&
567
			    power_status(ctl_fd, 0, &pinfo))
568
				syslog(LOG_DEBUG, "no! sleep! till brooklyn!");
569
			else if (suspends)
570
				suspend(ctl_fd);
571
			else if (standbys)
572
				stand_by(ctl_fd);
573
			else if (hibernates)
574
				hibernate(ctl_fd);
575
			else if (resumes) {
576
				do_etc_file(_PATH_APM_ETC_RESUME);
577
				syslog(LOG_NOTICE,
578
				    "system resumed from sleep");
579
			}
580
581
			if (powerchange) {
582
				if (powerstatus)
583
					do_etc_file(_PATH_APM_ETC_POWERUP);
584
				else
585
					do_etc_file(_PATH_APM_ETC_POWERDOWN);
586
				powerchange = 0;
587
			}
588
589
		} else if (ev->ident == sock_fd)
590
			switch (handle_client(sock_fd, ctl_fd)) {
591
			case NORMAL:
592
				break;
593
			case SUSPENDING:
594
				suspend(ctl_fd);
595
				break;
596
			case STANDING_BY:
597
				stand_by(ctl_fd);
598
				break;
599
			case HIBERNATING:
600
				hibernate(ctl_fd);
601
				break;
602
			}
603
	}
604
	error("kevent loop", NULL);
605
606
	return 1;
607
}
608
609
void
610
setperfpolicy(char *policy)
611
{
612
	int hw_perfpol_mib[] = { CTL_HW, HW_PERFPOLICY };
613
	char oldpolicy[32];
614
	size_t oldsz = sizeof(oldpolicy);
615
	int setlo = 0;
616
617
	if (strcmp(policy, "low") == 0) {
618
		policy = "manual";
619
		setlo = 1;
620
	}
621
622
	if (sysctl(hw_perfpol_mib, 2, oldpolicy, &oldsz, policy, strlen(policy) + 1) < 0)
623
		syslog(LOG_INFO, "cannot set hw.perfpolicy");
624
625
	if (setlo == 1) {
626
		int hw_perf_mib[] = {CTL_HW, HW_SETPERF};
627
		int perf;
628
		int new_perf = 0;
629
		size_t perf_sz = sizeof(perf);
630
		if (sysctl(hw_perf_mib, 2, &perf, &perf_sz, &new_perf, perf_sz) < 0)
631
			syslog(LOG_INFO, "cannot set hw.setperf");
632
	}
633
}
634
635
void
636
do_etc_file(const char *file)
637
{
638
	pid_t pid;
639
	int status;
640
	const char *prog;
641
642
	/* If file doesn't exist, do nothing. */
643
	if (access(file, X_OK|R_OK)) {
644
		syslog(LOG_DEBUG, "do_etc_file(): cannot access file %s", file);
645
		return;
646
	}
647
648
	prog = strrchr(file, '/');
649
	if (prog)
650
		prog++;
651
	else
652
		prog = file;
653
654
	pid = fork();
655
	switch (pid) {
656
	case -1:
657
		syslog(LOG_ERR, "failed to fork(): %m");
658
		return;
659
	case 0:
660
		/* We are the child. */
661
		execl(file, prog, (char *)NULL);
662
		syslog(LOG_ERR, "failed to exec %s: %m", file);
663
		_exit(1);
664
		/* NOTREACHED */
665
	default:
666
		/* We are the parent. */
667
		wait4(pid, &status, 0, 0);
668
		if (WIFEXITED(status))
669
			syslog(LOG_DEBUG, "%s exited with status %d", file,
670
			    WEXITSTATUS(status));
671
		else
672
			syslog(LOG_ERR, "%s exited abnormally.", file);
673
	}
674
}