timeout(1): only start the child command after the parent is fully set up

Since the default disposition for SIGCHLD is ignore, the prematurely
exited child would cause SIGCHLD dropped.  This makes timeout(1) hang,
because REAP_STATUS reports a zombie not waited for, but SIGCHLD for it
was already lost, so the main loop cannot exit, instead calling into
sigsuspend().

Reported and tested by:	pho
Reviewed by:	markj
Sponsored by:	The FreeBSD Foundation
MFC after:	1 week
Differential revision:	https://reviews.freebsd.org/D50752
This commit is contained in:
Konstantin Belousov
2025-06-08 18:47:49 +03:00
parent bff05e8a8c
commit aa8cdb7cae
+16 -1
View File
@@ -26,7 +26,7 @@
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <sys/cdefs.h>
#include <sys/fcntl.h>
#include <sys/procctl.h>
#include <sys/resource.h>
#include <sys/time.h>
@@ -283,6 +283,8 @@ main(int argc, char **argv)
int ch, status, sig;
int pstat = 0;
pid_t pid, cpid;
int pp[2], error;
char c;
double first_kill;
double second_kill = 0;
bool foreground = false;
@@ -351,6 +353,9 @@ main(int argc, char **argv)
if (sigprocmask(SIG_BLOCK, &allmask, &oldmask) == -1)
err(EXIT_FAILURE, "sigprocmask()");
if (pipe2(pp, O_CLOEXEC) == -1)
err(EXIT_FAILURE, "pipe2");
pid = fork();
if (pid == -1) {
err(EXIT_FAILURE, "fork()");
@@ -366,6 +371,11 @@ main(int argc, char **argv)
if (sigprocmask(SIG_SETMASK, &oldmask, NULL) == -1)
err(EXIT_FAILURE, "sigprocmask(oldmask)");
error = read(pp[0], &c, 1);
if (error == -1)
err(EXIT_FAILURE, "read from control pipe");
if (error == 0)
errx(EXIT_FAILURE, "eof from control pipe");
execvp(argv[0], argv);
warn("exec(%s)", argv[0]);
_exit(errno == ENOENT ? EXIT_CMD_NOENT : EXIT_CMD_ERROR);
@@ -391,6 +401,11 @@ main(int argc, char **argv)
signal(SIGTTOU, SIG_IGN);
set_interval(first_kill);
error = write(pp[1], "a", 1);
if (error == -1)
err(EXIT_FAILURE, "write to control pipe");
if (error == 0)
errx(EXIT_FAILURE, "short write to control pipe");
sigemptyset(&zeromask);
for (;;) {