Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/usr.sbin/apmd/apmd.c
103478 views
1
/*-
2
* SPDX-License-Identifier: BSD-2-Clause
3
*
4
* APM (Advanced Power Management) Event Dispatcher
5
*
6
* Copyright (c) 1999 Mitsuru IWASAKI <[email protected]>
7
* Copyright (c) 1999 KOIE Hidetaka <[email protected]>
8
* All rights reserved.
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
*
19
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
20
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
23
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29
* SUCH DAMAGE.
30
*/
31
32
#include <sys/types.h>
33
#include <assert.h>
34
#include <bitstring.h>
35
#include <err.h>
36
#include <errno.h>
37
#include <fcntl.h>
38
#include <paths.h>
39
#include <signal.h>
40
#include <stdio.h>
41
#include <stdlib.h>
42
#include <string.h>
43
#include <syslog.h>
44
#include <unistd.h>
45
#include <sys/ioctl.h>
46
#include <sys/time.h>
47
#include <sys/wait.h>
48
#include <machine/apm_bios.h>
49
50
#include "apmd.h"
51
52
int debug_level = 0;
53
int verbose = 0;
54
int soft_power_state_change = 0;
55
const char *apmd_configfile = APMD_CONFIGFILE;
56
const char *apmd_pidfile = APMD_PIDFILE;
57
int apmctl_fd = -1, apmnorm_fd = -1;
58
59
/*
60
* table of event handlers
61
*/
62
#define EVENT_CONFIG_INITIALIZER(EV,R) { #EV, NULL, R },
63
struct event_config events[EVENT_MAX] = {
64
EVENT_CONFIG_INITIALIZER(NOEVENT, 0)
65
EVENT_CONFIG_INITIALIZER(STANDBYREQ, 1)
66
EVENT_CONFIG_INITIALIZER(SUSPENDREQ, 1)
67
EVENT_CONFIG_INITIALIZER(NORMRESUME, 0)
68
EVENT_CONFIG_INITIALIZER(CRITRESUME, 0)
69
EVENT_CONFIG_INITIALIZER(BATTERYLOW, 0)
70
EVENT_CONFIG_INITIALIZER(POWERSTATECHANGE, 0)
71
EVENT_CONFIG_INITIALIZER(UPDATETIME, 0)
72
EVENT_CONFIG_INITIALIZER(CRITSUSPEND, 1)
73
EVENT_CONFIG_INITIALIZER(USERSTANDBYREQ, 1)
74
EVENT_CONFIG_INITIALIZER(USERSUSPENDREQ, 1)
75
EVENT_CONFIG_INITIALIZER(STANDBYRESUME, 0)
76
EVENT_CONFIG_INITIALIZER(CAPABILITIESCHANGE, 0)
77
};
78
79
/*
80
* List of battery events
81
*/
82
struct battery_watch_event *battery_watch_list = NULL;
83
84
#define BATT_CHK_INTV 10 /* how many seconds between battery state checks? */
85
86
/*
87
* default procedure
88
*/
89
struct event_cmd *
90
event_cmd_default_clone(void *this)
91
{
92
struct event_cmd * oldone = this;
93
struct event_cmd * newone = malloc(oldone->len);
94
95
newone->next = NULL;
96
newone->len = oldone->len;
97
newone->name = oldone->name;
98
newone->op = oldone->op;
99
return newone;
100
}
101
102
/*
103
* exec command
104
*/
105
int
106
event_cmd_exec_act(void *this)
107
{
108
struct event_cmd_exec * p = this;
109
int status = -1;
110
pid_t pid;
111
112
switch ((pid = fork())) {
113
case -1:
114
warn("cannot fork");
115
break;
116
case 0:
117
/* child process */
118
signal(SIGHUP, SIG_DFL);
119
signal(SIGCHLD, SIG_DFL);
120
signal(SIGTERM, SIG_DFL);
121
execl(_PATH_BSHELL, "sh", "-c", p->line, (char *)NULL);
122
_exit(127);
123
default:
124
/* parent process */
125
do {
126
pid = waitpid(pid, &status, 0);
127
} while (pid == -1 && errno == EINTR);
128
break;
129
}
130
return status;
131
}
132
void
133
event_cmd_exec_dump(void *this, FILE *fp)
134
{
135
fprintf(fp, " \"%s\"", ((struct event_cmd_exec *)this)->line);
136
}
137
struct event_cmd *
138
event_cmd_exec_clone(void *this)
139
{
140
struct event_cmd_exec * newone = (struct event_cmd_exec *) event_cmd_default_clone(this);
141
struct event_cmd_exec * oldone = this;
142
143
newone->evcmd.next = NULL;
144
newone->evcmd.len = oldone->evcmd.len;
145
newone->evcmd.name = oldone->evcmd.name;
146
newone->evcmd.op = oldone->evcmd.op;
147
if ((newone->line = strdup(oldone->line)) == NULL)
148
err(1, "out of memory");
149
return (struct event_cmd *) newone;
150
}
151
void
152
event_cmd_exec_free(void *this)
153
{
154
free(((struct event_cmd_exec *)this)->line);
155
}
156
struct event_cmd_op event_cmd_exec_ops = {
157
event_cmd_exec_act,
158
event_cmd_exec_dump,
159
event_cmd_exec_clone,
160
event_cmd_exec_free
161
};
162
163
/*
164
* reject command
165
*/
166
int
167
event_cmd_reject_act(void *this __unused)
168
{
169
int rc = 0;
170
171
if (ioctl(apmctl_fd, APMIO_REJECTLASTREQ, NULL)) {
172
syslog(LOG_NOTICE, "fail to reject\n");
173
rc = -1;
174
}
175
return rc;
176
}
177
struct event_cmd_op event_cmd_reject_ops = {
178
event_cmd_reject_act,
179
NULL,
180
event_cmd_default_clone,
181
NULL
182
};
183
184
/*
185
* manipulate event_config
186
*/
187
struct event_cmd *
188
clone_event_cmd_list(struct event_cmd *p)
189
{
190
struct event_cmd dummy;
191
struct event_cmd *q = &dummy;
192
for ( ;p; p = p->next) {
193
assert(p->op->clone);
194
if ((q->next = p->op->clone(p)) == NULL)
195
err(1, "out of memory");
196
q = q->next;
197
}
198
q->next = NULL;
199
return dummy.next;
200
}
201
void
202
free_event_cmd_list(struct event_cmd *p)
203
{
204
struct event_cmd * q;
205
for ( ; p ; p = q) {
206
q = p->next;
207
if (p->op->free)
208
p->op->free(p);
209
free(p);
210
}
211
}
212
int
213
register_battery_handlers(
214
int level, int direction,
215
struct event_cmd *cmdlist)
216
{
217
/*
218
* level is negative if it's in "minutes", non-negative if
219
* percentage.
220
*
221
* direction =1 means we care about this level when charging,
222
* direction =-1 means we care about it when discharging.
223
*/
224
if (level>100) /* percentage > 100 */
225
return -1;
226
if (abs(direction) != 1) /* nonsense direction value */
227
return -1;
228
229
if (cmdlist) {
230
struct battery_watch_event *we;
231
232
if ((we = malloc(sizeof(struct battery_watch_event))) == NULL)
233
err(1, "out of memory");
234
235
we->next = battery_watch_list; /* starts at NULL */
236
battery_watch_list = we;
237
we->level = abs(level);
238
we->type = (level<0)?BATTERY_MINUTES:BATTERY_PERCENT;
239
we->direction = (direction<0)?BATTERY_DISCHARGING:
240
BATTERY_CHARGING;
241
we->done = 0;
242
we->cmdlist = clone_event_cmd_list(cmdlist);
243
}
244
return 0;
245
}
246
int
247
register_apm_event_handlers(
248
bitstr_t bit_decl(evlist, EVENT_MAX),
249
struct event_cmd *cmdlist)
250
{
251
if (cmdlist) {
252
bitstr_t bit_decl(tmp, EVENT_MAX);
253
memcpy(&tmp, evlist, bitstr_size(EVENT_MAX));
254
255
for (;;) {
256
int n;
257
struct event_cmd *p;
258
struct event_cmd *q;
259
bit_ffs(tmp, EVENT_MAX, &n);
260
if (n < 0)
261
break;
262
p = events[n].cmdlist;
263
if ((q = clone_event_cmd_list(cmdlist)) == NULL)
264
err(1, "out of memory");
265
if (p) {
266
while (p->next != NULL)
267
p = p->next;
268
p->next = q;
269
} else {
270
events[n].cmdlist = q;
271
}
272
bit_clear(tmp, n);
273
}
274
}
275
return 0;
276
}
277
278
/*
279
* execute command
280
*/
281
int
282
exec_run_cmd(struct event_cmd *p)
283
{
284
int status = 0;
285
286
for (; p; p = p->next) {
287
assert(p->op->act);
288
if (verbose)
289
syslog(LOG_INFO, "action: %s", p->name);
290
status = p->op->act(p);
291
if (status) {
292
syslog(LOG_NOTICE, "command finished with %d\n", status);
293
break;
294
}
295
}
296
return status;
297
}
298
299
/*
300
* execute command -- the event version
301
*/
302
int
303
exec_event_cmd(struct event_config *ev)
304
{
305
int status = 0;
306
307
status = exec_run_cmd(ev->cmdlist);
308
if (status && ev->rejectable) {
309
syslog(LOG_ERR, "canceled");
310
event_cmd_reject_act(NULL);
311
}
312
return status;
313
}
314
315
/*
316
* read config file
317
*/
318
extern FILE * yyin;
319
extern int yydebug;
320
321
void
322
read_config(void)
323
{
324
int i;
325
326
if ((yyin = fopen(apmd_configfile, "r")) == NULL) {
327
err(1, "cannot open config file");
328
}
329
330
#ifdef DEBUG
331
yydebug = debug_level;
332
#endif
333
334
if (yyparse() != 0)
335
err(1, "cannot parse config file");
336
337
fclose(yyin);
338
339
/* enable events */
340
for (i = 0; i < EVENT_MAX; i++) {
341
if (events[i].cmdlist) {
342
u_int event_type = i;
343
if (write(apmctl_fd, &event_type, sizeof(u_int)) == -1) {
344
err(1, "cannot enable event 0x%x", event_type);
345
}
346
}
347
}
348
}
349
350
void
351
dump_config(void)
352
{
353
int i;
354
struct battery_watch_event *q;
355
356
for (i = 0; i < EVENT_MAX; i++) {
357
struct event_cmd * p;
358
if ((p = events[i].cmdlist)) {
359
fprintf(stderr, "apm_event %s {\n", events[i].name);
360
for ( ; p ; p = p->next) {
361
fprintf(stderr, "\t%s", p->name);
362
if (p->op->dump)
363
p->op->dump(p, stderr);
364
fprintf(stderr, ";\n");
365
}
366
fprintf(stderr, "}\n");
367
}
368
}
369
for (q = battery_watch_list ; q != NULL ; q = q -> next) {
370
struct event_cmd * p;
371
fprintf(stderr, "apm_battery %d%s %s {\n",
372
q -> level,
373
(q -> type == BATTERY_PERCENT)?"%":"m",
374
(q -> direction == BATTERY_CHARGING)?"charging":
375
"discharging");
376
for ( p = q -> cmdlist; p ; p = p->next) {
377
fprintf(stderr, "\t%s", p->name);
378
if (p->op->dump)
379
p->op->dump(p, stderr);
380
fprintf(stderr, ";\n");
381
}
382
fprintf(stderr, "}\n");
383
}
384
}
385
386
void
387
destroy_config(void)
388
{
389
int i;
390
struct battery_watch_event *q;
391
392
/* disable events */
393
for (i = 0; i < EVENT_MAX; i++) {
394
if (events[i].cmdlist) {
395
u_int event_type = i;
396
if (write(apmctl_fd, &event_type, sizeof(u_int)) == -1) {
397
err(1, "cannot disable event 0x%x", event_type);
398
}
399
}
400
}
401
402
for (i = 0; i < EVENT_MAX; i++) {
403
struct event_cmd * p;
404
if ((p = events[i].cmdlist))
405
free_event_cmd_list(p);
406
events[i].cmdlist = NULL;
407
}
408
409
for( ; battery_watch_list; battery_watch_list = battery_watch_list -> next) {
410
free_event_cmd_list(battery_watch_list->cmdlist);
411
q = battery_watch_list->next;
412
free(battery_watch_list);
413
battery_watch_list = q;
414
}
415
}
416
417
void
418
restart(void)
419
{
420
destroy_config();
421
read_config();
422
if (verbose)
423
dump_config();
424
}
425
426
/*
427
* write pid file
428
*/
429
static void
430
write_pid(void)
431
{
432
FILE *fp = fopen(apmd_pidfile, "w");
433
434
if (fp) {
435
fprintf(fp, "%ld\n", (long)getpid());
436
fclose(fp);
437
}
438
}
439
440
/*
441
* handle signals
442
*/
443
static int signal_fd[2];
444
445
void
446
enque_signal(int sig)
447
{
448
if (write(signal_fd[1], &sig, sizeof sig) != sizeof sig)
449
err(1, "cannot process signal.");
450
}
451
452
void
453
wait_child(void)
454
{
455
int status;
456
while (waitpid(-1, &status, WNOHANG) > 0)
457
;
458
}
459
460
int
461
proc_signal(int fd)
462
{
463
int rc = 0;
464
int sig;
465
466
while (read(fd, &sig, sizeof sig) == sizeof sig) {
467
syslog(LOG_INFO, "caught signal: %d", sig);
468
switch (sig) {
469
case SIGHUP:
470
syslog(LOG_NOTICE, "restart by SIG");
471
restart();
472
break;
473
case SIGTERM:
474
syslog(LOG_NOTICE, "going down on signal %d", sig);
475
rc = -1;
476
return rc;
477
case SIGCHLD:
478
wait_child();
479
break;
480
default:
481
warn("unexpected signal(%d) received.", sig);
482
break;
483
}
484
}
485
return rc;
486
}
487
void
488
proc_apmevent(int fd)
489
{
490
struct apm_event_info apmevent;
491
492
while (ioctl(fd, APMIO_NEXTEVENT, &apmevent) == 0) {
493
int status;
494
syslog(LOG_NOTICE, "apmevent %04x index %d\n",
495
apmevent.type, apmevent.index);
496
syslog(LOG_INFO, "apm event: %s", events[apmevent.type].name);
497
if (fork() == 0) {
498
status = exec_event_cmd(&events[apmevent.type]);
499
exit(status);
500
}
501
}
502
}
503
504
#define AC_POWER_STATE ((pw_info.ai_acline == 1) ? BATTERY_CHARGING :\
505
BATTERY_DISCHARGING)
506
507
void
508
check_battery(void)
509
{
510
511
static int first_time=1, last_state;
512
int status;
513
514
struct apm_info pw_info;
515
struct battery_watch_event *p;
516
517
/* If we don't care, don't bother */
518
if (battery_watch_list == NULL)
519
return;
520
521
if (first_time) {
522
if ( ioctl(apmnorm_fd, APMIO_GETINFO, &pw_info) < 0)
523
err(1, "cannot check battery state.");
524
/*
525
* This next statement isn't entirely true. The spec does not tie AC
526
* line state to battery charging or not, but this is a bit lazier to do.
527
*/
528
last_state = AC_POWER_STATE;
529
first_time = 0;
530
return; /* We can't process events, we have no baseline */
531
}
532
533
/*
534
* XXX - should we do this a bunch of times and perform some sort
535
* of smoothing or correction?
536
*/
537
if ( ioctl(apmnorm_fd, APMIO_GETINFO, &pw_info) < 0)
538
err(1, "cannot check battery state.");
539
540
/*
541
* If we're not in the state now that we were in last time,
542
* then it's a transition, which means we must clean out
543
* the event-caught state.
544
*/
545
if (last_state != AC_POWER_STATE) {
546
if (soft_power_state_change && fork() == 0) {
547
status = exec_event_cmd(&events[PMEV_POWERSTATECHANGE]);
548
exit(status);
549
}
550
last_state = AC_POWER_STATE;
551
for (p = battery_watch_list ; p!=NULL ; p = p -> next)
552
p->done = 0;
553
}
554
for (p = battery_watch_list ; p != NULL ; p = p -> next)
555
if (p -> direction == AC_POWER_STATE &&
556
!(p -> done) &&
557
((p -> type == BATTERY_PERCENT &&
558
p -> level == (int)pw_info.ai_batt_life) ||
559
(p -> type == BATTERY_MINUTES &&
560
p -> level == (pw_info.ai_batt_time / 60)))) {
561
p -> done++;
562
if (verbose)
563
syslog(LOG_NOTICE, "Caught battery event: %s, %d%s",
564
(p -> direction == BATTERY_CHARGING)?"charging":"discharging",
565
p -> level,
566
(p -> type == BATTERY_PERCENT)?"%":" minutes");
567
if (fork() == 0) {
568
status = exec_run_cmd(p -> cmdlist);
569
exit(status);
570
}
571
}
572
}
573
void
574
event_loop(void)
575
{
576
int fdmax = 0;
577
struct sigaction nsa;
578
fd_set master_rfds;
579
sigset_t sigmask, osigmask;
580
581
FD_ZERO(&master_rfds);
582
FD_SET(apmctl_fd, &master_rfds);
583
fdmax = apmctl_fd > fdmax ? apmctl_fd : fdmax;
584
585
FD_SET(signal_fd[0], &master_rfds);
586
fdmax = signal_fd[0] > fdmax ? signal_fd[0] : fdmax;
587
588
memset(&nsa, 0, sizeof nsa);
589
nsa.sa_handler = enque_signal;
590
sigfillset(&nsa.sa_mask);
591
nsa.sa_flags = SA_RESTART;
592
sigaction(SIGHUP, &nsa, NULL);
593
sigaction(SIGCHLD, &nsa, NULL);
594
sigaction(SIGTERM, &nsa, NULL);
595
596
sigemptyset(&sigmask);
597
sigaddset(&sigmask, SIGHUP);
598
sigaddset(&sigmask, SIGCHLD);
599
sigaddset(&sigmask, SIGTERM);
600
sigprocmask(SIG_SETMASK, &sigmask, &osigmask);
601
602
while (1) {
603
fd_set rfds;
604
int res;
605
struct timeval to;
606
607
to.tv_sec = BATT_CHK_INTV;
608
to.tv_usec = 0;
609
610
memcpy(&rfds, &master_rfds, sizeof rfds);
611
sigprocmask(SIG_SETMASK, &osigmask, NULL);
612
if ((res=select(fdmax + 1, &rfds, 0, 0, &to)) < 0) {
613
if (errno != EINTR)
614
err(1, "select");
615
}
616
sigprocmask(SIG_SETMASK, &sigmask, NULL);
617
618
if (res == 0) { /* time to check the battery */
619
check_battery();
620
continue;
621
}
622
623
if (FD_ISSET(signal_fd[0], &rfds)) {
624
if (proc_signal(signal_fd[0]) < 0)
625
return;
626
}
627
628
if (FD_ISSET(apmctl_fd, &rfds))
629
proc_apmevent(apmctl_fd);
630
}
631
}
632
633
int
634
main(int ac, char* av[])
635
{
636
int ch;
637
int daemonize = 1;
638
char *prog;
639
int logopt = LOG_NDELAY | LOG_PID;
640
641
while ((ch = getopt(ac, av, "df:sv")) != -1) {
642
switch (ch) {
643
case 'd':
644
daemonize = 0;
645
debug_level++;
646
break;
647
case 'f':
648
apmd_configfile = optarg;
649
break;
650
case 's':
651
soft_power_state_change = 1;
652
break;
653
case 'v':
654
verbose = 1;
655
break;
656
default:
657
err(1, "unknown option `%c'", ch);
658
}
659
}
660
661
if (daemonize)
662
daemon(0, 0);
663
664
#ifdef NICE_INCR
665
nice(NICE_INCR);
666
#endif
667
668
if (!daemonize)
669
logopt |= LOG_PERROR;
670
671
prog = strrchr(av[0], '/');
672
openlog(prog ? prog+1 : av[0], logopt, LOG_DAEMON);
673
674
syslog(LOG_NOTICE, "start");
675
676
if (pipe(signal_fd) < 0)
677
err(1, "pipe");
678
if (fcntl(signal_fd[0], F_SETFL, O_NONBLOCK) < 0)
679
err(1, "fcntl");
680
681
if ((apmnorm_fd = open(APM_NORM_DEVICEFILE, O_RDWR)) == -1) {
682
err(1, "cannot open device file `%s'", APM_NORM_DEVICEFILE);
683
}
684
685
if (fcntl(apmnorm_fd, F_SETFD, 1) == -1) {
686
err(1, "cannot set close-on-exec flag for device file '%s'", APM_NORM_DEVICEFILE);
687
}
688
689
if ((apmctl_fd = open(APM_CTL_DEVICEFILE, O_RDWR)) == -1) {
690
err(1, "cannot open device file `%s'", APM_CTL_DEVICEFILE);
691
}
692
693
if (fcntl(apmctl_fd, F_SETFD, 1) == -1) {
694
err(1, "cannot set close-on-exec flag for device file '%s'", APM_CTL_DEVICEFILE);
695
}
696
697
restart();
698
write_pid();
699
event_loop();
700
exit(EXIT_SUCCESS);
701
}
702
703
704