(root)/
util-linux-2.39/
misc-utils/
waitpid.c
       1  /*
       2   * waitpid(1) - wait for process termination
       3   *
       4   * Copyright (C) 2022 Thomas Weißschuh <thomas@t-8ch.de>
       5   *
       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.
      10   *
      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.
      15   *
      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.
      19   */
      20  
      21  #include <sys/epoll.h>
      22  #include <sys/timerfd.h>
      23  #include <assert.h>
      24  #include <stdlib.h>
      25  #include <errno.h>
      26  #include <string.h>
      27  #include <unistd.h>
      28  #include <stdbool.h>
      29  #include <getopt.h>
      30  
      31  #include "pidfd-utils.h"
      32  #include "c.h"
      33  #include "nls.h"
      34  #include "xalloc.h"
      35  #include "strutils.h"
      36  #include "exitcodes.h"
      37  #include "timeutils.h"
      38  #include "optutils.h"
      39  
      40  #define EXIT_TIMEOUT_EXPIRED 3
      41  
      42  #define TIMEOUT_SOCKET_IDX UINT64_MAX
      43  
      44  #define err_nosys(exitcode, ...) \
      45  	err(errno == ENOSYS ? EXIT_NOTSUPP : exitcode, __VA_ARGS__)
      46  
      47  static bool verbose = false;
      48  static struct timespec timeout;
      49  static bool allow_exited = false;
      50  static size_t count;
      51  
      52  static pid_t *parse_pids(size_t n_strings, char * const *strings)
      53  {
      54  	pid_t *pids = xcalloc(n_strings, sizeof(*pids));
      55  
      56  	for (size_t i = 0; i < n_strings; i++)
      57  		pids[i] = strtopid_or_err(strings[i], _("failed to parse pid"));
      58  
      59  	return pids;
      60  }
      61  
      62  static int *open_pidfds(size_t n_pids, pid_t *pids)
      63  {
      64  	int *pidfds = xcalloc(n_pids, sizeof(*pidfds));
      65  
      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]);
      71  				continue;
      72  			}
      73  			err_nosys(EXIT_FAILURE, _("could not open pid %u"), pids[i]);
      74  		}
      75  	}
      76  
      77  	return pidfds;
      78  }
      79  
      80  static int open_timeoutfd(void)
      81  {
      82  	int fd;
      83  	struct itimerspec timer = {};
      84  
      85  	if (!timeout.tv_sec && !timeout.tv_nsec)
      86  		return -1;
      87  
      88  	timer.it_value = timeout;
      89  
      90  	fd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
      91  	if (fd == -1)
      92  		err_nosys(EXIT_FAILURE, _("could not create timerfd"));
      93  
      94  	if (timerfd_settime(fd, 0, &timer, NULL))
      95  		err_nosys(EXIT_FAILURE, _("could not set timer"));
      96  
      97  	return fd;
      98  
      99  }
     100  
     101  static size_t add_listeners(int epll, size_t n_pids, int * const pidfds, int timeoutfd)
     102  {
     103  	size_t skipped = 0;
     104  	struct epoll_event evt = {
     105  		.events = EPOLLIN,
     106  	};
     107  
     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"));
     112  	}
     113  
     114  	for (size_t i = 0; i < n_pids; i++) {
     115  		if (pidfds[i] == -1) {
     116  			skipped++;
     117  			continue;
     118  		}
     119  		evt.data.u64 = i;
     120  		if (epoll_ctl(epll, EPOLL_CTL_ADD, pidfds[i], &evt))
     121  			err_nosys(EXIT_FAILURE, _("could not add listener"));
     122  	}
     123  
     124  	return n_pids - skipped;
     125  }
     126  
     127  static void wait_for_exits(int epll, size_t active_pids, pid_t * const pids,
     128  			   int * const pidfds)
     129  {
     130  	while (active_pids) {
     131  		struct epoll_event evt;
     132  		int ret, fd;
     133  
     134  		ret = epoll_wait(epll, &evt, 1, -1);
     135  		if (ret == -1) {
     136  			if (errno == EINTR)
     137  				continue;
     138  			else
     139  				err_nosys(EXIT_FAILURE, _("failure during wait"));
     140  		}
     141  		if (evt.data.u64 == TIMEOUT_SOCKET_IDX) {
     142  			if (verbose)
     143  				printf(_("Timeout expired\n"));
     144  			exit(EXIT_TIMEOUT_EXPIRED);
     145  		}
     146  		if (verbose)
     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);
     151  		active_pids -= ret;
     152  	}
     153  }
     154  
     155  static void __attribute__((__noreturn__)) usage(void)
     156  {
     157  	FILE *out = stdout;
     158  
     159  	fputs(USAGE_HEADER, out);
     160  	fprintf(out, _(" %s [options] pid...\n"), program_invocation_short_name);
     161  
     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);
     167  
     168  	fputs(USAGE_SEPARATOR, out);
     169  	fprintf(out, USAGE_HELP_OPTIONS(25));
     170  
     171  	fprintf(out, USAGE_MAN_TAIL("waitpid(1)"));
     172  
     173  	exit(EXIT_SUCCESS);
     174  }
     175  
     176  static int parse_options(int argc, char **argv)
     177  {
     178  	int c;
     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' },
     186  		{ 0 }
     187  	};
     188  	static const ul_excl_t excl[] = {       /* rows and cols in ASCII order */
     189  		{ 'c', 'e' },
     190  		{ 0 }
     191  	};
     192  	int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
     193  
     194  	while ((c = getopt_long (argc, argv, "vVht:c:e", longopts, NULL)) != -1) {
     195  
     196  		err_exclusive_options(c, longopts, excl, excl_st);
     197  
     198  		switch (c) {
     199  		case 'v':
     200  			verbose = true;
     201  			break;
     202  		case 't':
     203  			strtotimespec_or_err(optarg, &timeout,
     204  					     _("Could not parse timeout"));
     205  			break;
     206  		case 'e':
     207  			allow_exited = true;
     208  			break;
     209  		case 'c':
     210  			count = str2num_or_err(optarg, 10, _("Invalid count"),
     211  					       1, INT64_MAX);
     212  			break;
     213  		case 'V':
     214  			print_version(EXIT_SUCCESS);
     215  		case 'h':
     216  			usage();
     217  		default:
     218  			errtryhelp(EXIT_FAILURE);
     219  		}
     220  	}
     221  
     222  	return optind;
     223  }
     224  
     225  int main(int argc, char **argv)
     226  {
     227  	int pid_idx, epoll, timeoutfd, *pidfds;
     228  	size_t n_pids, active_pids;
     229  
     230  	setlocale(LC_ALL, "");
     231  	bindtextdomain(PACKAGE, LOCALEDIR);
     232  	textdomain(PACKAGE);
     233  
     234  	pid_idx = parse_options(argc, argv);
     235  	n_pids = argc - pid_idx;
     236  	if (!n_pids)
     237  		errx(EXIT_FAILURE, _("no PIDs specified"));
     238  
     239  	if (count && count > n_pids)
     240  		errx(EXIT_FAILURE,
     241  		     _("can't want for %zu of %zu PIDs"), count, n_pids);
     242  
     243  	pid_t *pids = parse_pids(argc - pid_idx, argv + pid_idx);
     244  
     245  	pidfds = open_pidfds(n_pids, pids);
     246  	timeoutfd = open_timeoutfd();
     247  	epoll = epoll_create(n_pids);
     248  	if (epoll == -1)
     249  		err_nosys(EXIT_FAILURE, _("could not create epoll"));
     250  
     251  	active_pids = add_listeners(epoll, n_pids, pidfds, timeoutfd);
     252  	if (count)
     253  		active_pids = min(active_pids, count);
     254  	wait_for_exits(epoll, active_pids, pids, pidfds);
     255  }