From 88b93973fe1248b64af9f661d9766dc890f05421 Mon Sep 17 00:00:00 2001 From: "Todd C. Miller" Date: Sun, 11 Oct 2009 21:41:36 +0000 Subject: [PATCH] Run command in its own pgrp (like the shell does) for easier signalling. No need to relay SIGINT or SIGQUIT to parent, just send to grandchild. Don't want grandchild stopped events in the child (only termination). Flush output after suspending grandchild before signalling parent. --- script.c | 151 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 102 insertions(+), 49 deletions(-) diff --git a/script.c b/script.c index e27aba0a7..12fdc3b0c 100644 --- a/script.c +++ b/script.c @@ -75,9 +75,16 @@ __unused static const char rcsid[] = "$Sudo$"; #define SFD_OUTPUT 3 #define SFD_TIMING 4 -int script_fds[5]; +struct script_buf { + int len; /* buffer length (how much read in) */ + int off; /* write position (how much already consumed) */ + char buf[16 * 1024]; +}; + +static int script_fds[5]; static sig_atomic_t alive = 1; +static sig_atomic_t suspended = 0; static sig_atomic_t signo; static pid_t parent, grandchild; @@ -92,22 +99,19 @@ static char slavename[] = "/dev/ptyXX"; static void script_child __P((char *path, char *argv[])); static void script_grandchild __P((char *path, char *argv[], int)); static void sync_winsize __P((int src, int dst)); -static void handler __P((int signo)); -static void sigchild __P((int signo)); -static void sigwinch __P((int signo)); -static void sigrelay __P((int signo)); +static void handler __P((int s)); +static void sigchild __P((int s)); +static void sigwinch __P((int s)); +static void sigtstp __P((int s)); +static void sigrepost __P((int s)); static int get_pty __P((int *master, int *slave)); +static void flush_output __P((struct script_buf *output, struct timeval *then, + struct timeval *now, FILE *ofile, FILE *tfile)); /* * TODO: run monitor as root? */ -struct script_buf { - int len; /* buffer length (how much read in) */ - int off; /* write position (how much already consumed) */ - char buf[16 * 1024]; -}; - static int fdcompar(v1, v2) const void *v1; @@ -435,7 +439,7 @@ script_execv(path, argv) (void) sigaction(signo, &sa, NULL); if (!term_raw(STDIN_FILENO, 1) && errno == EINTR) goto check_sig; - killpg(child, SIGCONT); + kill(child, SIGCONT); } term_restore(STDIN_FILENO); @@ -507,13 +511,22 @@ script_child(path, argv) zero_bytes(&sa, sizeof(sa)); sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; - sa.sa_handler = sigchild; - sigaction(SIGCHLD, &sa, NULL); - sa.sa_handler = sigrelay; + + /* These tty-related signals get relayed back to the parent. */ + sa.sa_handler = sigtstp; sigaction(SIGTSTP, &sa, NULL); sigaction(SIGTTIN, &sa, NULL); sigaction(SIGTTOU, &sa, NULL); + + /* These signals just get posted to the grandchilds pgrp. */ + sa.sa_handler = sigrepost; sigaction(SIGQUIT, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + + /* Do not want child stop notifications. */ + sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; + sa.sa_handler = sigchild; + sigaction(SIGCHLD, &sa, NULL); /* Start grandchild and begin reading from pty master. */ grandchild = fork(); @@ -521,6 +534,7 @@ script_child(path, argv) log_error(USE_ERRNO, "Can't fork"); if (grandchild == 0) { /* setup tty and exec command */ + setpgid(grandchild, grandchild); script_grandchild(path, argv, rbac_enabled); warning("unable to execute %s", path); _exit(127); @@ -581,6 +595,16 @@ script_child(path, argv) if (input.len > input.off) FD_SET(script_fds[SFD_MASTER], fdsw); + if (suspended) { + /* Flush any remaining output to master tty. */ + flush_output(&output, &then, &now, ofile, tfile); + + /* Relay signal back to parent for its tty and suspend ourself. */ + kill(parent, signo); + kill(getpid(), SIGSTOP); + suspended = 0; + killpg(grandchild, SIGCONT); + } nready = select(script_fds[SFD_MASTER] + 1, fdsr, fdsw, NULL, NULL); if (nready == -1) { if (errno == EINTR) @@ -652,30 +676,7 @@ script_child(path, argv) n &= ~O_NONBLOCK; (void) fcntl(STDOUT_FILENO, F_SETFL, n); } - while (output.len > output.off) { - n = write(STDOUT_FILENO, output.buf + output.off, - output.len - output.off); - if (n <= 0) - break; - output.off += n; - } - - /* Make sure there is no output remaining on the master pty. */ - for (;;) { - n = read(script_fds[SFD_MASTER], output.buf, sizeof(output.buf)); - if (n <= 0) - break; - log_output(output.buf, n, &then, &now, ofile, tfile); - output.off = 0; - output.len = n; - do { - n = write(STDOUT_FILENO, output.buf + output.off, - output.len - output.off); - if (n <= 0) - break; - output.off += n; - } while (output.len > output.off); - } + flush_output(&output, &then, &now, ofile, tfile); if (WIFEXITED(grandchild_status)) exit(WEXITSTATUS(grandchild_status)); @@ -684,12 +685,51 @@ script_child(path, argv) exit(1); } +static void +flush_output(output, then, now, ofile, tfile) + struct script_buf *output; + struct timeval *then; + struct timeval *now; + FILE *ofile; + FILE *tfile; +{ + int n; + + while (output->len > output->off) { + n = write(STDOUT_FILENO, output->buf + output->off, + output->len - output->off); + if (n <= 0) + break; + output->off += n; + } + + /* Make sure there is no output remaining on the master pty. */ + for (;;) { + n = read(script_fds[SFD_MASTER], output->buf, sizeof(output->buf)); + if (n <= 0) + break; + log_output(output->buf, n, &then, &now, ofile, tfile); + output->off = 0; + output->len = n; + do { + n = write(STDOUT_FILENO, output->buf + output->off, + output->len - output->off); + if (n <= 0) + break; + output->off += n; + } while (output->len > output->off); + } +} + static void script_grandchild(path, argv, rbac_enabled) char *path; char *argv[]; int rbac_enabled; { + /* Also set process group here to avoid a race condition. */ + setpgid(0, getpid()); + dup2(script_fds[SFD_SLAVE], STDIN_FILENO); dup2(script_fds[SFD_SLAVE], STDOUT_FILENO); dup2(script_fds[SFD_SLAVE], STDERR_FILENO); @@ -752,7 +792,7 @@ sigchild(signo) #ifdef sudo_waitpid do { pid = sudo_waitpid(grandchild, &grandchild_status, WNOHANG); - if (pid == grandchild) { + if (pid == grandchild) alive = 0; break; } @@ -768,23 +808,36 @@ sigchild(signo) } static void -sigrelay(signo) - int signo; +sigrepost(s) + int s; { int serrno = errno; - /* Relay signal back to parent for its tty. */ - kill(parent, signo); - - /* Suspend self and command, parent will continue us when it is time. */ - killpg(getpid(), SIGSTOP); + /* Re-post signal to child via its process group. */ + killpg(grandchild, s); errno = serrno; } static void -sigwinch(signo) - int signo; +sigtstp(s) + int s; +{ + int serrno = errno; + + /* Event loop needs to know which signal to relay to parent. */ + signo = s; + + /* Suspend the command we are running and set state. */ + killpg(grandchild, SIGSTOP); + suspended = 1; + + errno = serrno; +} + +static void +sigwinch(s) + int s; { int serrno = errno;