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 <err.h>
44 #include <errno.h>
45 #include <signal.h>
46 #include <stdbool.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <sysexits.h>
51 #include <unistd.h>
52
53 struct pid {
54 RB_ENTRY(pid) entry;
55 pid_t pid;
56 };
57
58 static int
pidcmp(const struct pid * a,const struct pid * b)59 pidcmp(const struct pid *a, const struct pid *b)
60 {
61 return (a->pid > b->pid ? 1 : a->pid < b->pid ? -1 : 0);
62 }
63
64 RB_HEAD(pidtree, pid);
65 static struct pidtree pids = RB_INITIALIZER(&pids);
66 RB_GENERATE_STATIC(pidtree, pid, entry, pidcmp);
67
68 static void
usage(void)69 usage(void)
70 {
71 fprintf(stderr, "usage: pwait [-t timeout] [-opv] pid ...\n");
72 exit(EX_USAGE);
73 }
74
75 /*
76 * pwait - wait for processes to terminate
77 */
78 int
main(int argc,char * argv[])79 main(int argc, char *argv[])
80 {
81 struct itimerval itv;
82 struct kevent *e;
83 struct pid k, *p;
84 char *end, *s;
85 double timeout;
86 size_t sz;
87 long pid;
88 pid_t mypid;
89 int i, kq, n, ndone, nleft, opt, pid_max, ret, status;
90 bool oflag, pflag, tflag, verbose;
91
92 oflag = false;
93 pflag = false;
94 tflag = false;
95 verbose = false;
96 memset(&itv, 0, sizeof(itv));
97
98 while ((opt = getopt(argc, argv, "opt:v")) != -1) {
99 switch (opt) {
100 case 'o':
101 oflag = true;
102 break;
103 case 'p':
104 pflag = true;
105 break;
106 case 't':
107 tflag = true;
108 errno = 0;
109 timeout = strtod(optarg, &end);
110 if (end == optarg || errno == ERANGE || timeout < 0) {
111 errx(EX_DATAERR, "timeout value");
112 }
113 switch (*end) {
114 case '\0':
115 break;
116 case 's':
117 end++;
118 break;
119 case 'h':
120 timeout *= 60;
121 /* FALLTHROUGH */
122 case 'm':
123 timeout *= 60;
124 end++;
125 break;
126 default:
127 errx(EX_DATAERR, "timeout unit");
128 }
129 if (*end != '\0') {
130 errx(EX_DATAERR, "timeout unit");
131 }
132 if (timeout > 100000000L) {
133 errx(EX_DATAERR, "timeout value");
134 }
135 itv.it_value.tv_sec = (time_t)timeout;
136 timeout -= (time_t)timeout;
137 itv.it_value.tv_usec =
138 (suseconds_t)(timeout * 1000000UL);
139 break;
140 case 'v':
141 verbose = true;
142 break;
143 default:
144 usage();
145 /* NOTREACHED */
146 }
147 }
148
149 argc -= optind;
150 argv += optind;
151
152 if (argc == 0) {
153 usage();
154 }
155
156 if ((kq = kqueue()) < 0)
157 err(EX_OSERR, "kqueue");
158
159 sz = sizeof(pid_max);
160 if (sysctlbyname("kern.pid_max", &pid_max, &sz, NULL, 0) != 0) {
161 pid_max = 99999;
162 }
163 if ((e = malloc((argc + tflag) * sizeof(*e))) == NULL) {
164 err(EX_OSERR, "malloc");
165 }
166 ndone = nleft = 0;
167 mypid = getpid();
168 for (n = 0; n < argc; n++) {
169 s = argv[n];
170 /* Undocumented Solaris compat */
171 if (strncmp(s, "/proc/", 6) == 0) {
172 s += 6;
173 }
174 errno = 0;
175 pid = strtol(s, &end, 10);
176 if (pid < 0 || pid > pid_max || *end != '\0' || errno != 0) {
177 warnx("%s: bad process id", s);
178 continue;
179 }
180 if (pid == mypid) {
181 warnx("%s: skipping my own pid", s);
182 continue;
183 }
184 if ((p = malloc(sizeof(*p))) == NULL) {
185 err(EX_OSERR, NULL);
186 }
187 p->pid = pid;
188 if (RB_INSERT(pidtree, &pids, p) != NULL) {
189 /* Duplicate. */
190 free(p);
191 continue;
192 }
193 EV_SET(e + nleft, pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL);
194 if (kevent(kq, e + nleft, 1, NULL, 0, NULL) == -1) {
195 if (errno != ESRCH)
196 err(EX_OSERR, "kevent()");
197 warn("%ld", pid);
198 RB_REMOVE(pidtree, &pids, p);
199 free(p);
200 ndone++;
201 } else {
202 nleft++;
203 }
204 }
205
206 if ((ndone == 0 || !oflag) && nleft > 0 && tflag) {
207 /*
208 * Explicitly detect SIGALRM so that an exit status of 124
209 * can be returned rather than 142.
210 */
211 EV_SET(e + nleft, SIGALRM, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
212 if (kevent(kq, e + nleft, 1, NULL, 0, NULL) == -1) {
213 err(EX_OSERR, "kevent");
214 }
215 /* Ignore SIGALRM to not interrupt kevent(2). */
216 signal(SIGALRM, SIG_IGN);
217 if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {
218 err(EX_OSERR, "setitimer");
219 }
220 }
221 ret = EX_OK;
222 while ((ndone == 0 || !oflag) && ret == EX_OK && nleft > 0) {
223 n = kevent(kq, NULL, 0, e, nleft + tflag, NULL);
224 if (n == -1) {
225 err(EX_OSERR, "kevent");
226 }
227 for (i = 0; i < n; i++) {
228 if (e[i].filter == EVFILT_SIGNAL) {
229 if (verbose) {
230 printf("timeout\n");
231 }
232 ret = 124;
233 }
234 pid = e[i].ident;
235 if (verbose) {
236 status = e[i].data;
237 if (WIFEXITED(status)) {
238 printf("%ld: exited with status %d.\n",
239 pid, WEXITSTATUS(status));
240 } else if (WIFSIGNALED(status)) {
241 printf("%ld: killed by signal %d.\n",
242 pid, WTERMSIG(status));
243 } else {
244 printf("%ld: terminated.\n", pid);
245 }
246 }
247 k.pid = pid;
248 if ((p = RB_FIND(pidtree, &pids, &k)) != NULL) {
249 RB_REMOVE(pidtree, &pids, p);
250 free(p);
251 ndone++;
252 }
253 --nleft;
254 }
255 }
256 if (pflag) {
257 RB_FOREACH(p, pidtree, &pids) {
258 printf("%d\n", p->pid);
259 }
260 }
261 exit(ret);
262 }
263