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"
39 #define EXIT_TIMEOUT_EXPIRED 3
41 #define TIMEOUT_SOCKET_IDX UINT64_MAX
43 #define err_nosys(exitcode, ...) \
44 err(errno == ENOSYS ? EXIT_NOTSUPP : exitcode, __VA_ARGS__)
46 static bool verbose
= false;
47 static struct timespec timeout
;
48 static bool allow_exited
= false;
50 static pid_t
*parse_pids(size_t n_strings
, char * const *strings
)
52 pid_t
*pids
= xcalloc(n_strings
, sizeof(*pids
));
54 for (size_t i
= 0; i
< n_strings
; i
++)
55 pids
[i
] = strtopid_or_err(strings
[i
], _("failed to parse pid"));
60 static int *open_pidfds(size_t n_pids
, pid_t
*pids
)
62 int *pidfds
= xcalloc(n_pids
, sizeof(*pidfds
));
64 for (size_t i
= 0; i
< n_pids
; i
++) {
65 pidfds
[i
] = pidfd_open(pids
[i
], 0);
66 if (pidfds
[i
] == -1) {
67 if (allow_exited
&& errno
== ESRCH
) {
68 warnx(_("PID %d has exited, skipping"), pids
[i
]);
71 err_nosys(EXIT_FAILURE
, _("could not open pid %u"), pids
[i
]);
78 static int open_timeoutfd(void)
81 struct itimerspec timer
= {};
83 if (!timeout
.tv_sec
&& !timeout
.tv_nsec
)
86 timer
.it_value
= timeout
;
88 fd
= timerfd_create(CLOCK_MONOTONIC
, TFD_CLOEXEC
);
90 err_nosys(EXIT_FAILURE
, _("could not create timerfd"));
92 if (timerfd_settime(fd
, 0, &timer
, NULL
))
93 err_nosys(EXIT_FAILURE
, _("could not set timer"));
99 static size_t add_listeners(int epll
, size_t n_pids
, int * const pidfds
, int timeoutfd
)
102 struct epoll_event evt
= {
106 if (timeoutfd
!= -1) {
107 evt
.data
.u64
= TIMEOUT_SOCKET_IDX
;
108 if (epoll_ctl(epll
, EPOLL_CTL_ADD
, timeoutfd
, &evt
))
109 err_nosys(EXIT_FAILURE
, _("could not add timerfd"));
112 for (size_t i
= 0; i
< n_pids
; i
++) {
113 if (pidfds
[i
] == -1) {
118 if (epoll_ctl(epll
, EPOLL_CTL_ADD
, pidfds
[i
], &evt
))
119 err_nosys(EXIT_FAILURE
, _("could not add listener"));
122 return n_pids
- skipped
;
125 static void wait_for_exits(int epll
, size_t active_pids
, pid_t
* const pids
,
128 while (active_pids
) {
129 struct epoll_event evt
;
132 ret
= epoll_wait(epll
, &evt
, 1, -1);
137 err_nosys(EXIT_FAILURE
, _("failure during wait"));
139 if (evt
.data
.u64
== TIMEOUT_SOCKET_IDX
) {
141 printf(_("Timeout expired\n"));
142 exit(EXIT_TIMEOUT_EXPIRED
);
145 printf(_("PID %d finished\n"), pids
[evt
.data
.u64
]);
146 assert((size_t) ret
<= active_pids
);
147 fd
= pidfds
[evt
.data
.u64
];
148 epoll_ctl(epll
, EPOLL_CTL_DEL
, fd
, NULL
);
153 static void __attribute__((__noreturn__
)) usage(void)
157 fputs(USAGE_HEADER
, out
);
158 fprintf(out
, _(" %s [options] pid...\n"), program_invocation_short_name
);
160 fputs(USAGE_OPTIONS
, out
);
161 fputs(_(" -v, --verbose be more verbose\n"), out
);
162 fputs(_(" -t, --timeout=<timeout> wait at most timeout seconds\n"), out
);
163 fputs(_(" -e, --exited allow exited PIDs\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 { "version", no_argument
, NULL
, 'V' },
181 { "help", no_argument
, NULL
, 'h' },
182 { NULL
, 0, NULL
, 0 },
185 while ((c
= getopt_long (argc
, argv
, "vVht:e", longopts
, NULL
)) != -1) {
191 strtotimespec_or_err(optarg
, &timeout
,
192 _("Could not parse timeout"));
198 print_version(EXIT_SUCCESS
);
202 errtryhelp(EXIT_FAILURE
);
209 int main(int argc
, char **argv
)
211 int pid_idx
, epoll
, timeoutfd
, *pidfds
;
212 size_t n_pids
, active_pids
;
214 setlocale(LC_ALL
, "");
215 bindtextdomain(PACKAGE
, LOCALEDIR
);
218 pid_idx
= parse_options(argc
, argv
);
219 n_pids
= argc
- pid_idx
;
221 errx(EXIT_FAILURE
, _("no PIDs specified"));
223 pid_t
*pids
= parse_pids(argc
- pid_idx
, argv
+ pid_idx
);
225 pidfds
= open_pidfds(n_pids
, pids
);
226 timeoutfd
= open_timeoutfd();
227 epoll
= epoll_create(n_pids
);
229 err_nosys(EXIT_FAILURE
, _("could not create epoll"));
231 active_pids
= add_listeners(epoll
, n_pids
, pidfds
, timeoutfd
);
232 wait_for_exits(epoll
, active_pids
, pids
, pidfds
);