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