xref: /freebsd/usr.sbin/apmd/apmd.c (revision eba230afba4932f02a1ca44efc797cf7499a5cb0)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * APM (Advanced Power Management) Event Dispatcher
5  *
6  * Copyright (c) 1999 Mitsuru IWASAKI <iwasaki@FreeBSD.org>
7  * Copyright (c) 1999 KOIE Hidetaka <koie@suri.co.jp>
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 *
event_cmd_default_clone(void * this)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
event_cmd_exec_act(void * this)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
event_cmd_exec_dump(void * this,FILE * fp)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 *
event_cmd_exec_clone(void * this)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
event_cmd_exec_free(void * this)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
event_cmd_reject_act(void * this __unused)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 *
clone_event_cmd_list(struct event_cmd * p)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
free_event_cmd_list(struct event_cmd * p)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
register_battery_handlers(int level,int direction,struct event_cmd * cmdlist)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
register_apm_event_handlers(bitstr_t bit_decl (evlist,EVENT_MAX),struct event_cmd * cmdlist)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
exec_run_cmd(struct event_cmd * p)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
exec_event_cmd(struct event_config * ev)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
read_config(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
dump_config(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
destroy_config(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
restart(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
write_pid(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
enque_signal(int sig)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
wait_child(void)453 wait_child(void)
454 {
455 	int status;
456 	while (waitpid(-1, &status, WNOHANG) > 0)
457 		;
458 }
459 
460 int
proc_signal(int fd)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
proc_apmevent(int fd)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
check_battery(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
event_loop(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
main(int ac,char * av[])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