+/*
+ * Read signals on fd written to by handler().
+ * Returns -1 on error, 0 on child exit, else 1.
+ */
+static int
+handle_signals(fd, child, cstat)
+ int fd;
+ pid_t child;
+ struct command_status *cstat;
+{
+ unsigned char signo;
+ ssize_t nread;
+ int status;
+ pid_t pid;
+
+ for (;;) {
+ /* read signal pipe */
+ nread = read(signal_pipe[0], &signo, sizeof(signo));
+ if (nread <= 0) {
+ /* It should not be possible to get EOF but just in case. */
+ if (nread == 0)
+ errno = ECONNRESET;
+ /* Restart if interrupted by signal so the pipe doesn't fill. */
+ if (errno == EINTR)
+ continue;
+ /* If pipe is empty, we are done. */
+ if (errno == EAGAIN)
+ break;
+ cstat->type = CMD_ERRNO;
+ cstat->val = errno;
+ return -1;
+ }
+ if (signo == SIGCHLD) {
+ /*
+ * If logging I/O, child is the intermediate process,
+ * otherwise it is the command itself.
+ */
+ do {
+#ifdef sudo_waitpid
+ pid = sudo_waitpid(child, &status, WUNTRACED|WNOHANG);
+#else
+ pid = wait(&status);
+#endif
+ } while (pid == -1 && errno == EINTR);
+ if (pid == child) {
+ /* If not logging I/O and child has exited we are done. */
+#ifdef _PATH_SUDO_IO_LOGDIR
+ if (!log_io)
+#endif
+ {
+ if (WIFSTOPPED(status)) {
+ /*
+ * Save the controlling terminal's process group
+ * so we can restore it after we resume.
+ */
+#ifdef HAVE_TCSETPGRP
+ pid_t saved_pgrp = (pid_t)-1;
+ int fd = open(_PATH_TTY, O_RDWR|O_NOCTTY, 0);
+ if (fd != -1)
+ saved_pgrp = tcgetpgrp(fd);
+#endif /* HAVE_TCSETPGRP */
+ if (kill(getpid(), WSTOPSIG(status)) != 0)
+ warning("kill(%d, %d)", getpid(), WSTOPSIG(status));
+#ifdef HAVE_TCSETPGRP
+ if (fd != -1) {
+ if (saved_pgrp != (pid_t)-1)
+ (void)tcsetpgrp(fd, saved_pgrp);
+ close(fd);
+ }
+#endif /* HAVE_TCSETPGRP */
+ } else {
+ /* Child has exited, we are done. */
+ cstat->type = CMD_WSTATUS;
+ cstat->val = status;
+ return 0;
+ }
+ }
+ /* Else we get ECONNRESET on sv[0] if child dies. */
+ }
+ } else {
+#ifdef _PATH_SUDO_IO_LOGDIR
+ if (log_io) {
+ /* Schedule signo to be forwared to the child. */
+ schedule_signal(signo);
+ } else
+#endif
+ {
+ /* Nothing listening on sv[0], send directly. */
+ if (kill(child, signo) != 0)
+ warning("kill(%d, %d)", child, signo);
+ }
+ }
+ }
+ return 1;
+}
+
+#ifdef _PATH_SUDO_IO_LOGDIR
+/*
+ * Forward signals in sigfwd_list to child listening on fd.
+ */
+static void
+forward_signals(sock)
+ int sock;
+{
+ struct sigforward *sigfwd;
+ struct command_status cstat;
+ ssize_t nsent;
+
+ while (!tq_empty(&sigfwd_list)) {
+ sigfwd = tq_first(&sigfwd_list);
+ cstat.type = CMD_SIGNO;
+ cstat.val = sigfwd->signo;
+ do {
+ nsent = send(sock, &cstat, sizeof(cstat), 0);
+ } while (nsent == -1 && errno == EINTR);
+ tq_remove(&sigfwd_list, sigfwd);
+ efree(sigfwd);
+ if (nsent != sizeof(cstat)) {
+ if (errno == EPIPE) {
+ /* Other end of socket gone, empty out sigfwd_list. */
+ while (!tq_empty(&sigfwd_list)) {
+ sigfwd = tq_first(&sigfwd_list);
+ tq_remove(&sigfwd_list, sigfwd);
+ efree(sigfwd);
+ }
+ }
+ break;
+ }
+ }
+}
+
+/*
+ * Schedule a signal to be forwared.
+ */
+static void
+schedule_signal(signo)
+ int signo;
+{
+ struct sigforward *sigfwd;
+
+ sigfwd = emalloc(sizeof(*sigfwd));
+ sigfwd->prev = sigfwd;
+ sigfwd->next = NULL;
+ sigfwd->signo = signo;
+ tq_append(&sigfwd_list, sigfwd);
+}
+#endif /* _PATH_SUDO_IO_LOGDIR */
+