xref: /freebsd/bin/pwait/pwait.c (revision a259b98fa211ed87bfee58c575de4e2de94ee0fa)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2004-2009, Jilles Tjoelker
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with
8  * or without modification, are permitted provided that the
9  * following conditions are met:
10  *
11  * 1. Redistributions of source code must retain the above
12  *    copyright notice, this list of conditions and the
13  *    following disclaimer.
14  * 2. Redistributions in binary form must reproduce the
15  *    above copyright notice, this list of conditions and
16  *    the following disclaimer in the documentation and/or
17  *    other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
20  * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
21  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
23  * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
25  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
27  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
28  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
31  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
32  * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
33  * OF SUCH DAMAGE.
34  */
35 
36 #include <sys/types.h>
37 #include <sys/event.h>
38 #include <sys/sysctl.h>
39 #include <sys/time.h>
40 #include <sys/tree.h>
41 #include <sys/wait.h>
42 
43 #include <assert.h>
44 #include <err.h>
45 #include <errno.h>
46 #include <signal.h>
47 #include <stdbool.h>
48 #include <stdio.h>
49 #include <stdlib.h>
50 #include <string.h>
51 #include <sysexits.h>
52 #include <unistd.h>
53 
54 struct pid {
55 	RB_ENTRY(pid) entry;
56 	pid_t pid;
57 };
58 
59 static int
60 pidcmp(const struct pid *a, const struct pid *b)
61 {
62 	return (a->pid > b->pid ? 1 : a->pid < b->pid ? -1 : 0);
63 }
64 
65 RB_HEAD(pidtree, pid);
66 static struct pidtree pids = RB_INITIALIZER(&pids);
67 RB_GENERATE_STATIC(pidtree, pid, entry, pidcmp);
68 
69 static void
70 usage(void)
71 {
72 	fprintf(stderr, "usage: pwait [-oprv] [-t timeout] pid ...\n");
73 	exit(EX_USAGE);
74 }
75 
76 /*
77  * pwait - wait for processes to terminate
78  */
79 int
80 main(int argc, char *argv[])
81 {
82 	struct itimerval itv;
83 	struct kevent *e;
84 	struct pid k, *p;
85 	char *end, *s;
86 	double timeout;
87 	size_t sz;
88 	long pid;
89 	pid_t mypid;
90 	int i, kq, n, ndone, nev, nleft, notes, opt, pid_max, ret, status;
91 	bool oflag, pflag, rflag, tflag, verbose;
92 
93 	oflag = false;
94 	pflag = false;
95 	rflag = false;
96 	tflag = false;
97 	verbose = false;
98 	memset(&itv, 0, sizeof(itv));
99 
100 	while ((opt = getopt(argc, argv, "oprt:v")) != -1) {
101 		switch (opt) {
102 		case 'o':
103 			oflag = true;
104 			break;
105 		case 'p':
106 			pflag = true;
107 			break;
108 		case 'r':
109 			rflag = true;
110 			break;
111 		case 't':
112 			tflag = true;
113 			errno = 0;
114 			timeout = strtod(optarg, &end);
115 			if (end == optarg || errno == ERANGE || timeout < 0) {
116 				errx(EX_DATAERR, "timeout value");
117 			}
118 			switch (*end) {
119 			case '\0':
120 				break;
121 			case 's':
122 				end++;
123 				break;
124 			case 'h':
125 				timeout *= 60;
126 				/* FALLTHROUGH */
127 			case 'm':
128 				timeout *= 60;
129 				end++;
130 				break;
131 			default:
132 				errx(EX_DATAERR, "timeout unit");
133 			}
134 			if (*end != '\0') {
135 				errx(EX_DATAERR, "timeout unit");
136 			}
137 			if (timeout > 100000000L) {
138 				errx(EX_DATAERR, "timeout value");
139 			}
140 			itv.it_value.tv_sec = (time_t)timeout;
141 			timeout -= (time_t)timeout;
142 			itv.it_value.tv_usec =
143 			    (suseconds_t)(timeout * 1000000UL);
144 			break;
145 		case 'v':
146 			verbose = true;
147 			break;
148 		default:
149 			usage();
150 			/* NOTREACHED */
151 		}
152 	}
153 
154 	argc -= optind;
155 	argv += optind;
156 
157 	if (argc == 0) {
158 		usage();
159 	}
160 
161 	if ((kq = kqueue()) < 0)
162 		err(EX_OSERR, "kqueue");
163 
164 	sz = sizeof(pid_max);
165 	if (sysctlbyname("kern.pid_max", &pid_max, &sz, NULL, 0) != 0) {
166 		pid_max = 99999;
167 	}
168 	if ((e = malloc((argc + 1 + tflag) * sizeof(*e))) == NULL) {
169 		err(EX_OSERR, "malloc");
170 	}
171 	ndone = nev = nleft = 0;
172 	mypid = getpid();
173 	notes = rflag ? NOTE_REAP : NOTE_EXIT;
174 	if (verbose)
175 		notes |= NOTE_EXIT;
176 	for (n = 0; n < argc; n++) {
177 		s = argv[n];
178 		/* Undocumented Solaris compat */
179 		if (strncmp(s, "/proc/", 6) == 0) {
180 			s += 6;
181 		}
182 		errno = 0;
183 		pid = strtol(s, &end, 10);
184 		if (pid < 0 || pid > pid_max || *end != '\0' || errno != 0) {
185 			warnx("%s: bad process id", s);
186 			continue;
187 		}
188 		if (pid == mypid) {
189 			warnx("%s: skipping my own pid", s);
190 			continue;
191 		}
192 		if ((p = malloc(sizeof(*p))) == NULL) {
193 			err(EX_OSERR, NULL);
194 		}
195 		p->pid = pid;
196 		if (RB_INSERT(pidtree, &pids, p) != NULL) {
197 			/* Duplicate. */
198 			free(p);
199 			continue;
200 		}
201 		EV_SET(e + nleft, pid, EVFILT_PROC, EV_ADD, notes, 0, NULL);
202 		if (kevent(kq, e + nleft, 1, NULL, 0, NULL) == -1) {
203 			if (errno != ESRCH)
204 				err(EX_OSERR, "kevent()");
205 			warn("%ld", pid);
206 			RB_REMOVE(pidtree, &pids, p);
207 			free(p);
208 			ndone++;
209 		} else {
210 			nleft++;
211 			nev++;
212 		}
213 	}
214 
215 	/*
216 	 * Detect SIGINFO so we can print a status.
217 	 */
218 	EV_SET(e + nev, SIGINFO, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
219 	if (kevent(kq, e + nev, 1, NULL, 0, NULL) == -1) {
220 		err(EX_OSERR, "kevent");
221 	}
222 	nev++;
223 	if ((ndone == 0 || !oflag) && nleft > 0 && tflag) {
224 		/*
225 		 * Explicitly detect SIGALRM so that an exit status of 124
226 		 * can be returned rather than 142.
227 		 */
228 		EV_SET(e + nev, SIGALRM, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
229 		if (kevent(kq, e + nev, 1, NULL, 0, NULL) == -1) {
230 			err(EX_OSERR, "kevent");
231 		}
232 		nev++;
233 		/* Ignore SIGALRM to not interrupt kevent(2). */
234 		signal(SIGALRM, SIG_IGN);
235 		if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {
236 			err(EX_OSERR, "setitimer");
237 		}
238 	}
239 	ret = EX_OK;
240 	setvbuf(stderr, NULL, _IOLBF, 0);
241 	while ((ndone == 0 || !oflag) && ret == EX_OK && nleft > 0) {
242 		n = kevent(kq, NULL, 0, e, nev, NULL);
243 		if (n == -1) {
244 			err(EX_OSERR, "kevent");
245 		}
246 		for (i = 0; i < n; i++) {
247 			if (e[i].filter == EVFILT_SIGNAL &&
248 			    e[i].ident == SIGINFO) {
249 				p = RB_MIN(pidtree, &pids);
250 				fprintf(stderr, "%d", p->pid);
251 				while ((p = RB_NEXT(pidtree, &pids, p)) != NULL) {
252 					fprintf(stderr, " %d", p->pid);
253 				}
254 				fprintf(stderr, "\n");
255 				continue;
256 			}
257 			if (e[i].filter == EVFILT_SIGNAL &&
258 			    e[i].ident == SIGALRM) {
259 				if (verbose) {
260 					printf("timeout\n");
261 				}
262 				ret = 124;
263 				continue;
264 			}
265 			assert(e[i].filter == EVFILT_PROC);
266 			pid = e[i].ident;
267 			if ((e[i].fflags & NOTE_EXIT) && verbose) {
268 				status = e[i].data;
269 				if (WIFEXITED(status)) {
270 					printf("%ld: exited with status %d.\n",
271 					    pid, WEXITSTATUS(status));
272 				} else if (WIFSIGNALED(status)) {
273 					printf("%ld: killed by signal %d.\n",
274 					    pid, WTERMSIG(status));
275 				} else {
276 					printf("%ld: terminated.\n", pid);
277 				}
278 			}
279 			if ((e[i].fflags & NOTE_REAP) && verbose) {
280 				printf("%ld: reaped.\n", pid);
281 			}
282 			if ((e[i].fflags & NOTE_REAP) ||
283 			    (!rflag && (e[i].fflags & NOTE_EXIT))) {
284 				/* this process is done */
285 				k.pid = pid;
286 				if ((p = RB_FIND(pidtree, &pids, &k)) != NULL) {
287 					RB_REMOVE(pidtree, &pids, p);
288 					free(p);
289 					ndone++;
290 				}
291 				--nleft;
292 			}
293 		}
294 	}
295 	if (pflag) {
296 		RB_FOREACH(p, pidtree, &pids) {
297 			printf("%d\n", p->pid);
298 		}
299 	}
300 	exit(ret);
301 }
302