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

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