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 static bool verbose
= false;
45 static struct timespec timeout
;
46 static bool allow_exited
= false;
49 static pid_t
*parse_pids(size_t n_strings
, char * const *strings
)
51 pid_t
*pids
= xcalloc(n_strings
, sizeof(*pids
));
53 for (size_t i
= 0; i
< n_strings
; i
++)
54 pids
[i
] = strtopid_or_err(strings
[i
], _("failed to parse pid"));
59 static int *open_pidfds(size_t n_pids
, pid_t
*pids
)
61 int *pidfds
= xcalloc(n_pids
, sizeof(*pidfds
));
63 for (size_t i
= 0; i
< n_pids
; i
++) {
64 pidfds
[i
] = pidfd_open(pids
[i
], 0);
65 if (pidfds
[i
] == -1) {
66 if (allow_exited
&& errno
== ESRCH
) {
67 warnx(_("PID %d has exited, skipping"), pids
[i
]);
70 err_nosys(EXIT_FAILURE
, _("could not open pid %u"), pids
[i
]);
77 static int open_timeoutfd(void)
80 struct itimerspec timer
= {};
82 if (!timeout
.tv_sec
&& !timeout
.tv_nsec
)
85 timer
.it_value
= timeout
;
87 fd
= timerfd_create(CLOCK_MONOTONIC
, TFD_CLOEXEC
);
89 err_nosys(EXIT_FAILURE
, _("could not create timerfd"));
91 if (timerfd_settime(fd
, 0, &timer
, NULL
))
92 err_nosys(EXIT_FAILURE
, _("could not set timer"));
98 static size_t add_listeners(int epll
, size_t n_pids
, int * const pidfds
, int timeoutfd
)
101 struct epoll_event evt
= {
105 if (timeoutfd
!= -1) {
106 evt
.data
.u64
= TIMEOUT_SOCKET_IDX
;
107 if (epoll_ctl(epll
, EPOLL_CTL_ADD
, timeoutfd
, &evt
))
108 err_nosys(EXIT_FAILURE
, _("could not add timerfd"));
111 for (size_t i
= 0; i
< n_pids
; i
++) {
112 if (pidfds
[i
] == -1) {
117 if (epoll_ctl(epll
, EPOLL_CTL_ADD
, pidfds
[i
], &evt
))
118 err_nosys(EXIT_FAILURE
, _("could not add listener"));
121 return n_pids
- skipped
;
124 static void wait_for_exits(int epll
, size_t active_pids
, pid_t
* const pids
,
127 while (active_pids
) {
128 struct epoll_event evt
;
131 ret
= epoll_wait(epll
, &evt
, 1, -1);
136 err_nosys(EXIT_FAILURE
, _("failure during wait"));
138 if (evt
.data
.u64
== TIMEOUT_SOCKET_IDX
) {
140 printf(_("Timeout expired\n"));
141 exit(EXIT_TIMEOUT_EXPIRED
);
144 printf(_("PID %d finished\n"), pids
[evt
.data
.u64
]);
145 assert((size_t) ret
<= active_pids
);
146 fd
= pidfds
[evt
.data
.u64
];
147 epoll_ctl(epll
, EPOLL_CTL_DEL
, fd
, NULL
);
152 static void __attribute__((__noreturn__
)) usage(void)
156 fputs(USAGE_HEADER
, out
);
157 fprintf(out
, _(" %s [options] pid...\n"), program_invocation_short_name
);
159 fputs(USAGE_OPTIONS
, out
);
160 fputs(_(" -v, --verbose be more verbose\n"), out
);
161 fputs(_(" -t, --timeout=<timeout> wait at most timeout seconds\n"), out
);
162 fputs(_(" -e, --exited allow exited PIDs\n"), out
);
163 fputs(_(" -c, --count=<count> number of process exits to wait for\n"), out
);
165 fputs(USAGE_SEPARATOR
, out
);
166 fprintf(out
, USAGE_HELP_OPTIONS(25));
168 fprintf(out
, USAGE_MAN_TAIL("waitpid(1)"));
173 static int parse_options(int argc
, char **argv
)
176 static const struct option longopts
[] = {
177 { "verbose", no_argument
, NULL
, 'v' },
178 { "timeout", required_argument
, NULL
, 't' },
179 { "exited", no_argument
, NULL
, 'e' },
180 { "count", required_argument
, NULL
, 'c' },
181 { "version", no_argument
, NULL
, 'V' },
182 { "help", no_argument
, NULL
, 'h' },
185 static const ul_excl_t excl
[] = { /* rows and cols in ASCII order */
189 int excl_st
[ARRAY_SIZE(excl
)] = UL_EXCL_STATUS_INIT
;
191 while ((c
= getopt_long (argc
, argv
, "vVht:c:e", longopts
, NULL
)) != -1) {
193 err_exclusive_options(c
, longopts
, excl
, excl_st
);
200 strtotimespec_or_err(optarg
, &timeout
,
201 _("Could not parse timeout"));
207 count
= str2num_or_err(optarg
, 10, _("Invalid count"),
211 print_version(EXIT_SUCCESS
);
215 errtryhelp(EXIT_FAILURE
);
222 int main(int argc
, char **argv
)
224 int pid_idx
, epoll
, timeoutfd
, *pidfds
;
225 size_t n_pids
, active_pids
;
227 setlocale(LC_ALL
, "");
228 bindtextdomain(PACKAGE
, LOCALEDIR
);
231 pid_idx
= parse_options(argc
, argv
);
232 n_pids
= argc
- pid_idx
;
234 errx(EXIT_FAILURE
, _("no PIDs specified"));
236 if (count
&& count
> n_pids
)
238 _("can't want for %zu of %zu PIDs"), count
, n_pids
);
240 pid_t
*pids
= parse_pids(argc
- pid_idx
, argv
+ pid_idx
);
242 pidfds
= open_pidfds(n_pids
, pids
);
243 timeoutfd
= open_timeoutfd();
244 epoll
= epoll_create(n_pids
);
246 err_nosys(EXIT_FAILURE
, _("could not create epoll"));
248 active_pids
= add_listeners(epoll
, n_pids
, pidfds
, timeoutfd
);
250 active_pids
= min(active_pids
, count
);
251 wait_for_exits(epoll
, active_pids
, pids
, pidfds
);