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.
This commit is contained in:
Todd C. Miller
2009-10-11 21:41:36 +00:00
parent a3185d3e25
commit 88b93973fe

151
script.c
View File

@@ -75,9 +75,16 @@ __unused static const char rcsid[] = "$Sudo$";
#define SFD_OUTPUT 3 #define SFD_OUTPUT 3
#define SFD_TIMING 4 #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 alive = 1;
static sig_atomic_t suspended = 0;
static sig_atomic_t signo; static sig_atomic_t signo;
static pid_t parent, grandchild; 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_child __P((char *path, char *argv[]));
static void script_grandchild __P((char *path, char *argv[], int)); static void script_grandchild __P((char *path, char *argv[], int));
static void sync_winsize __P((int src, int dst)); static void sync_winsize __P((int src, int dst));
static void handler __P((int signo)); static void handler __P((int s));
static void sigchild __P((int signo)); static void sigchild __P((int s));
static void sigwinch __P((int signo)); static void sigwinch __P((int s));
static void sigrelay __P((int signo)); static void sigtstp __P((int s));
static void sigrepost __P((int s));
static int get_pty __P((int *master, int *slave)); 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? * 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 static int
fdcompar(v1, v2) fdcompar(v1, v2)
const void *v1; const void *v1;
@@ -435,7 +439,7 @@ script_execv(path, argv)
(void) sigaction(signo, &sa, NULL); (void) sigaction(signo, &sa, NULL);
if (!term_raw(STDIN_FILENO, 1) && errno == EINTR) if (!term_raw(STDIN_FILENO, 1) && errno == EINTR)
goto check_sig; goto check_sig;
killpg(child, SIGCONT); kill(child, SIGCONT);
} }
term_restore(STDIN_FILENO); term_restore(STDIN_FILENO);
@@ -507,13 +511,22 @@ script_child(path, argv)
zero_bytes(&sa, sizeof(sa)); zero_bytes(&sa, sizeof(sa));
sigemptyset(&sa.sa_mask); sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART; sa.sa_flags = SA_RESTART;
sa.sa_handler = sigchild;
sigaction(SIGCHLD, &sa, NULL); /* These tty-related signals get relayed back to the parent. */
sa.sa_handler = sigrelay; sa.sa_handler = sigtstp;
sigaction(SIGTSTP, &sa, NULL); sigaction(SIGTSTP, &sa, NULL);
sigaction(SIGTTIN, &sa, NULL); sigaction(SIGTTIN, &sa, NULL);
sigaction(SIGTTOU, &sa, NULL); sigaction(SIGTTOU, &sa, NULL);
/* These signals just get posted to the grandchilds pgrp. */
sa.sa_handler = sigrepost;
sigaction(SIGQUIT, &sa, NULL); 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. */ /* Start grandchild and begin reading from pty master. */
grandchild = fork(); grandchild = fork();
@@ -521,6 +534,7 @@ script_child(path, argv)
log_error(USE_ERRNO, "Can't fork"); log_error(USE_ERRNO, "Can't fork");
if (grandchild == 0) { if (grandchild == 0) {
/* setup tty and exec command */ /* setup tty and exec command */
setpgid(grandchild, grandchild);
script_grandchild(path, argv, rbac_enabled); script_grandchild(path, argv, rbac_enabled);
warning("unable to execute %s", path); warning("unable to execute %s", path);
_exit(127); _exit(127);
@@ -581,6 +595,16 @@ script_child(path, argv)
if (input.len > input.off) if (input.len > input.off)
FD_SET(script_fds[SFD_MASTER], fdsw); 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); nready = select(script_fds[SFD_MASTER] + 1, fdsr, fdsw, NULL, NULL);
if (nready == -1) { if (nready == -1) {
if (errno == EINTR) if (errno == EINTR)
@@ -652,30 +676,7 @@ script_child(path, argv)
n &= ~O_NONBLOCK; n &= ~O_NONBLOCK;
(void) fcntl(STDOUT_FILENO, F_SETFL, n); (void) fcntl(STDOUT_FILENO, F_SETFL, n);
} }
while (output.len > output.off) { flush_output(&output, &then, &now, ofile, tfile);
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);
}
if (WIFEXITED(grandchild_status)) if (WIFEXITED(grandchild_status))
exit(WEXITSTATUS(grandchild_status)); exit(WEXITSTATUS(grandchild_status));
@@ -684,12 +685,51 @@ script_child(path, argv)
exit(1); 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 static void
script_grandchild(path, argv, rbac_enabled) script_grandchild(path, argv, rbac_enabled)
char *path; char *path;
char *argv[]; char *argv[];
int rbac_enabled; 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], STDIN_FILENO);
dup2(script_fds[SFD_SLAVE], STDOUT_FILENO); dup2(script_fds[SFD_SLAVE], STDOUT_FILENO);
dup2(script_fds[SFD_SLAVE], STDERR_FILENO); dup2(script_fds[SFD_SLAVE], STDERR_FILENO);
@@ -752,7 +792,7 @@ sigchild(signo)
#ifdef sudo_waitpid #ifdef sudo_waitpid
do { do {
pid = sudo_waitpid(grandchild, &grandchild_status, WNOHANG); pid = sudo_waitpid(grandchild, &grandchild_status, WNOHANG);
if (pid == grandchild) { if (pid == grandchild)
alive = 0; alive = 0;
break; break;
} }
@@ -768,23 +808,36 @@ sigchild(signo)
} }
static void static void
sigrelay(signo) sigrepost(s)
int signo; int s;
{ {
int serrno = errno; int serrno = errno;
/* Relay signal back to parent for its tty. */ /* Re-post signal to child via its process group. */
kill(parent, signo); killpg(grandchild, s);
/* Suspend self and command, parent will continue us when it is time. */
killpg(getpid(), SIGSTOP);
errno = serrno; errno = serrno;
} }
static void static void
sigwinch(signo) sigtstp(s)
int signo; 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; int serrno = errno;