2 * waitpid(1) - wait for process termination
4 * Copyright (C) 2022 Thomas Weißschuh <thomas@t-8ch.de>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it would be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 #include <sys/epoll.h>
22 #include <sys/timerfd.h>
31 #include "pidfd-utils.h"
36 #include "exitcodes.h"
37 #include "timeutils.h"
40 #define EXIT_TIMEOUT_EXPIRED 3
42 #define TIMEOUT_SOCKET_IDX UINT64_MAX
44 #define err_nosys(exitcode, ...) \
45 err(errno == ENOSYS ? EXIT_NOTSUPP : exitcode, __VA_ARGS__)
47 static bool verbose
= false;
48 static struct timespec timeout
;
49 static bool allow_exited
= false;
52 static pid_t
*parse_pids(size_t n_strings
, char * const *strings
)
54 pid_t
*pids
= xcalloc(n_strings
, sizeof(*pids
));
56 for (size_t i
= 0; i
< n_strings
; i
++)
57 pids
[i
] = strtopid_or_err(strings
[i
], _("failed to parse pid"));
62 static int *open_pidfds(size_t n_pids
, pid_t
*pids
)
64 int *pidfds
= xcalloc(n_pids
, sizeof(*pidfds
));
66 for (size_t i
= 0; i
< n_pids
; i
++) {
67 pidfds
[i
] = pidfd_open(pids
[i
], 0);
68 if (pidfds
[i
] == -1) {
69 if (allow_exited
&& errno
== ESRCH
) {
70 warnx(_("PID %d has exited, skipping"), pids
[i
]);
73 err_nosys(EXIT_FAILURE
, _("could not open pid %u"), pids
[i
]);
80 static int open_timeoutfd(void)
83 struct itimerspec timer
= {};
85 if (!timeout
.tv_sec
&& !timeout
.tv_nsec
)
88 timer
.it_value
= timeout
;
90 fd
= timerfd_create(CLOCK_MONOTONIC
, TFD_CLOEXEC
);
92 err_nosys(EXIT_FAILURE
, _("could not create timerfd"));
94 if (timerfd_settime(fd
, 0, &timer
, NULL
))
95 err_nosys(EXIT_FAILURE
, _("could not set timer"));
101 static size_t add_listeners(int epll
, size_t n_pids
, int * const pidfds
, int timeoutfd
)
104 struct epoll_event evt
= {
108 if (timeoutfd
!= -1) {
109 evt
.data
.u64
= TIMEOUT_SOCKET_IDX
;
110 if (epoll_ctl(epll
, EPOLL_CTL_ADD
, timeoutfd
, &evt
))
111 err_nosys(EXIT_FAILURE
, _("could not add timerfd"));
114 for (size_t i
= 0; i
< n_pids
; i
++) {
115 if (pidfds
[i
] == -1) {
120 if (epoll_ctl(epll
, EPOLL_CTL_ADD
, pidfds
[i
], &evt
))
121 err_nosys(EXIT_FAILURE
, _("could not add listener"));
124 return n_pids
- skipped
;
127 static void wait_for_exits(int epll
, size_t active_pids
, pid_t
* const pids
,
130 while (active_pids
) {
131 struct epoll_event evt
;
134 ret
= epoll_wait(epll
, &evt
, 1, -1);
139 err_nosys(EXIT_FAILURE
, _("failure during wait"));
141 if (evt
.data
.u64
== TIMEOUT_SOCKET_IDX
) {
143 printf(_("Timeout expired\n"));
144 exit(EXIT_TIMEOUT_EXPIRED
);
147 printf(_("PID %d finished\n"), pids
[evt
.data
.u64
]);
148 assert((size_t) ret
<= active_pids
);
149 fd
= pidfds
[evt
.data
.u64
];
150 epoll_ctl(epll
, EPOLL_CTL_DEL
, fd
, NULL
);
155 static void __attribute__((__noreturn__
)) usage(void)
159 fputs(USAGE_HEADER
, out
);
160 fprintf(out
, _(" %s [options] pid...\n"), program_invocation_short_name
);
162 fputs(USAGE_OPTIONS
, out
);
163 fputs(_(" -v, --verbose be more verbose\n"), out
);
164 fputs(_(" -t, --timeout=<timeout> wait at most timeout seconds\n"), out
);
165 fputs(_(" -e, --exited allow exited PIDs\n"), out
);
166 fputs(_(" -c, --count=<count> number of process exits to wait for\n"), out
);
168 fputs(USAGE_SEPARATOR
, out
);
169 fprintf(out
, USAGE_HELP_OPTIONS(25));
171 fprintf(out
, USAGE_MAN_TAIL("waitpid(1)"));
176 static int parse_options(int argc
, char **argv
)
179 static const struct option longopts
[] = {
180 { "verbose", no_argument
, NULL
, 'v' },
181 { "timeout", required_argument
, NULL
, 't' },
182 { "exited", no_argument
, NULL
, 'e' },
183 { "count", required_argument
, NULL
, 'c' },
184 { "version", no_argument
, NULL
, 'V' },
185 { "help", no_argument
, NULL
, 'h' },
188 static const ul_excl_t excl
[] = { /* rows and cols in ASCII order */
192 int excl_st
[ARRAY_SIZE(excl
)] = UL_EXCL_STATUS_INIT
;
194 while ((c
= getopt_long (argc
, argv
, "vVht:c:e", longopts
, NULL
)) != -1) {
196 err_exclusive_options(c
, longopts
, excl
, excl_st
);
203 strtotimespec_or_err(optarg
, &timeout
,
204 _("Could not parse timeout"));
210 count
= str2num_or_err(optarg
, 10, _("Invalid count"),
214 print_version(EXIT_SUCCESS
);
218 errtryhelp(EXIT_FAILURE
);
225 int main(int argc
, char **argv
)
227 int pid_idx
, epoll
, timeoutfd
, *pidfds
;
228 size_t n_pids
, active_pids
;
230 setlocale(LC_ALL
, "");
231 bindtextdomain(PACKAGE
, LOCALEDIR
);
234 pid_idx
= parse_options(argc
, argv
);
235 n_pids
= argc
- pid_idx
;
237 errx(EXIT_FAILURE
, _("no PIDs specified"));
239 if (count
&& count
> n_pids
)
241 _("can't want for %zu of %zu PIDs"), count
, n_pids
);
243 pid_t
*pids
= parse_pids(argc
- pid_idx
, argv
+ pid_idx
);
245 pidfds
= open_pidfds(n_pids
, pids
);
246 timeoutfd
= open_timeoutfd();
247 epoll
= epoll_create(n_pids
);
249 err_nosys(EXIT_FAILURE
, _("could not create epoll"));
251 active_pids
= add_listeners(epoll
, n_pids
, pidfds
, timeoutfd
);
253 active_pids
= min(active_pids
, count
);
254 wait_for_exits(epoll
, active_pids
, pids
, pidfds
);