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
pidcmp(const struct pid * a,const struct pid * b)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
usage(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
main(int argc,char * argv[])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