xref: /src/bin/pwait/pwait.c (revision 0a1fd13e73200756b61d06c949622b4f6bba7dad)
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