return pty->master;
}
+pid_t ul_pty_get_child(struct ul_pty *pty)
+{
+ assert(pty);
+ return pty->child;
+}
+
/* it's active when signals are redurected to sigfd */
int ul_pty_is_running(struct ul_pty *pty)
{
}
}
+static void pty_signals_cleanup(struct ul_pty *pty)
+{
+ if (pty->sigfd != -1)
+ close(pty->sigfd);
+ pty->sigfd = -1;
+
+ /* restore original setting */
+ sigprocmask(SIG_SETMASK, &pty->orgsig, NULL);
+}
+
/* call me before fork() */
int ul_pty_setup(struct ul_pty *pty)
{
struct termios slave_attrs;
- int rc;
+ sigset_t ourset;
+ int rc = 0;
+
+ assert(pty->sigfd == -1);
/* save the current signals setting */
sigprocmask(0, NULL, &pty->orgsig);
DBG(SETUP, ul_debugobj(pty, "create for terminal"));
/* original setting of the current terminal */
- if (tcgetattr(STDIN_FILENO, &pty->stdin_attrs) != 0)
- return -errno;
+ if (tcgetattr(STDIN_FILENO, &pty->stdin_attrs) != 0) {
+ rc = -errno;
+ goto done;
+ }
ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&pty->win);
/* create master+slave */
rc = openpty(&pty->master, &pty->slave, NULL, &pty->stdin_attrs, &pty->win);
+ if (rc)
+ goto done;
/* set the current terminal to raw mode; pty_cleanup() reverses this change on exit */
slave_attrs = pty->stdin_attrs;
tcgetattr(pty->slave, &slave_attrs);
slave_attrs.c_lflag &= ~ECHO;
tcsetattr(pty->slave, TCSANOW, &slave_attrs);
- }
+ } else
+ goto done;
}
+ sigfillset(&ourset);
+ if (sigprocmask(SIG_BLOCK, &ourset, NULL)) {
+ rc = -errno;
+ goto done;
+ }
+
+ sigemptyset(&ourset);
+ sigaddset(&ourset, SIGCHLD);
+ sigaddset(&ourset, SIGWINCH);
+ sigaddset(&ourset, SIGALRM);
+ sigaddset(&ourset, SIGTERM);
+ sigaddset(&ourset, SIGINT);
+ sigaddset(&ourset, SIGQUIT);
+
+ if ((pty->sigfd = signalfd(-1, &ourset, SFD_CLOEXEC)) < 0)
+ rc = -errno;
+done:
+ if (rc)
+ ul_pty_cleanup(pty);
+
DBG(SETUP, ul_debugobj(pty, "pty setup done [master=%d, slave=%d, rc=%d]",
pty->master, pty->slave, rc));
return rc;
{
struct termios rtt;
+ pty_signals_cleanup(pty);
+
if (pty->master == -1 || !pty->isterm)
return;
{
char buf[BUFSIZ];
ssize_t bytes;
+ int rc = 0;
- DBG(IO, ul_debugobj(pty, "%d FD active", fd));
+ DBG(IO, ul_debugobj(pty, " handle I/O on fd=%d", fd));
*eof = 0;
/* read from active FD */
write_output(buf, bytes);
}
- return 0;
+ if (pty->callbacks.log_stream_activity)
+ rc = pty->callbacks.log_stream_activity(
+ pty->callback_data, fd, buf, bytes);
+
+ return rc;
+}
+
+void ul_pty_wait_for_child(struct ul_pty *pty)
+{
+ int status;
+ pid_t pid;
+ int options = 0;
+
+ if (pty->child == (pid_t) -1)
+ return;
+
+ DBG(SIG, ul_debug("waiting for child [child=%d]", (int) pty->child));
+
+ if (ul_pty_is_running(pty)) {
+ /* wait for specific child */
+ options = WNOHANG;
+ for (;;) {
+ pid = waitpid(pty->child, &status, options);
+ DBG(SIG, ul_debug(" waitpid done [rc=%d]", (int) pid));
+ if (pid != (pid_t) - 1) {
+ if (pty->callbacks.child_die)
+ pty->callbacks.child_die(
+ pty->callback_data,
+ pty->child, status);
+ ul_pty_set_child(pty, (pid_t) -1);
+ } else
+ break;
+ }
+ } else {
+ /* final wait */
+ while ((pid = wait3(&status, options, NULL)) > 0) {
+ DBG(SIG, ul_debug(" wait3 done [rc=%d]", (int) pid));
+ if (pid == pty->child) {
+ if (pty->callbacks.child_die)
+ pty->callbacks.child_die(
+ pty->callback_data,
+ pty->child, status);
+ ul_pty_set_child(pty, (pid_t) -1);
+ }
+ }
+ }
}
static int handle_signal(struct ul_pty *pty, int fd)
{
struct signalfd_siginfo info;
ssize_t bytes;
+ int rc = 0;
- DBG(SIG, ul_debugobj(pty, "signal FD %d active", fd));
+ DBG(SIG, ul_debugobj(pty, " handle signal on fd=%d", fd));
bytes = read(fd, &info, sizeof(info));
if (bytes != sizeof(info)) {
if (info.ssi_code == CLD_EXITED
|| info.ssi_code == CLD_KILLED
- || info.ssi_code == CLD_DUMPED)
- pty->callbacks.child_wait(pty->callback_data);
+ || info.ssi_code == CLD_DUMPED) {
- else if (info.ssi_status == SIGSTOP && pty->child > 0)
- pty->callbacks.child_sigstop(pty->callback_data);
+ if (pty->callbacks.child_wait)
+ pty->callbacks.child_wait(pty->callback_data,
+ pty->child);
+ else
+ ul_pty_wait_for_child(pty);
+
+ } else if (info.ssi_status == SIGSTOP && pty->child > 0)
+ pty->callbacks.child_sigstop(pty->callback_data,
+ pty->child);
if (pty->child <= 0) {
+ DBG(SIG, ul_debugobj(pty, " no child, setting leaving timeout"));
pty->poll_timeout = 10;
timerclear(&pty->next_callback_time);
}
if (pty->isterm) {
ioctl(STDIN_FILENO, TIOCGWINSZ, (char *)&pty->win);
ioctl(pty->slave, TIOCSWINSZ, (char *)&pty->win);
+
+ if (pty->callbacks.log_signal)
+ rc = pty->callbacks.log_signal(pty->callback_data,
+ &info, (void *) &pty->win);
}
break;
case SIGTERM:
/* Child termination is going to generate SIGCHILD (see above) */
if (pty->child > 0)
kill(pty->child, SIGTERM);
+
+ if (pty->callbacks.log_signal)
+ rc = pty->callbacks.log_signal(pty->callback_data,
+ &info, (void *) &pty->win);
break;
default:
abort();
}
- return 0;
+ return rc;
}
/* loop in parent */
int ul_pty_proxy_master(struct ul_pty *pty)
{
- sigset_t ourset;
int rc = 0, ret, eof = 0;
enum {
POLLFD_SIGNAL = 0,
/* We use signalfd and standard signals by handlers are blocked
* at all
*/
- sigfillset(&ourset);
- if (sigprocmask(SIG_BLOCK, &ourset, NULL))
- return -errno;
-
- sigemptyset(&ourset);
- sigaddset(&ourset, SIGCHLD);
- sigaddset(&ourset, SIGWINCH);
- sigaddset(&ourset, SIGALRM);
- sigaddset(&ourset, SIGTERM);
- sigaddset(&ourset, SIGINT);
- sigaddset(&ourset, SIGQUIT);
-
- if ((pty->sigfd = signalfd(-1, &ourset, SFD_CLOEXEC)) < 0) {
- rc = -errno;
- goto done;
- }
+ assert(pty->sigfd >= 0);
pfd[POLLFD_SIGNAL].fd = pty->sigfd;
pty->poll_timeout = -1;
}
}
-done:
- if (pty->sigfd != -1)
- close(pty->sigfd);
- pty->sigfd = -1;
-
- /* restore original setting */
- sigprocmask(SIG_SETMASK, &pty->orgsig, NULL);
+ pty_signals_cleanup(pty);
DBG(IO, ul_debug("poll() done [signal=%d, rc=%d]", pty->delivered_signal, rc));
return rc;
*
* ... and see for example tty(1) or "ps afu"
*/
-struct ptytest {
- pid_t child;
- int childstatus;
-
- struct ul_pty *pty;
-};
-
-/* on child exit/dump/... */
-static void wait_for_child(void *data)
+static void child_sigstop(void *data __attribute__((__unused__)), pid_t child)
{
- struct ptytest *ss = (struct ptytest *) data;
- int status;
- pid_t pid;
- int options = 0;
-
- if (ss->child == (pid_t) -1)
- return;
-
- if (ul_pty_is_running(ss->pty)) {
- /* wait for specific child */
- options = WNOHANG;
- for (;;) {
- pid = waitpid(ss->child, &status, options);
- if (pid != (pid_t) - 1) {
- ss->childstatus = status;
- ss->child = (pid_t) -1;
- ul_pty_set_child(ss->pty, (pid_t) -1);
- } else
- break;
- }
- } else {
- /* final wait */
- while ((pid = wait3(&status, options, NULL)) > 0) {
- if (pid == ss->child) {
- ss->childstatus = status;
- ss->child = (pid_t) -1;
- ul_pty_set_child(ss->pty, (pid_t) -1);
- }
- }
- }
-}
-
-static void child_sigstop(void *data)
-{
- struct ptytest *ss = (struct ptytest *) data;
kill(getpid(), SIGSTOP);
- kill(ss->child, SIGCONT);
+ kill(child, SIGCONT);
}
int main(int argc, char *argv[])
{
- struct ptytest ss = { .child = (pid_t) -1 };
struct ul_pty_callbacks *cb;
const char *shell, *command = NULL, *shname = NULL;
int caught_signal = 0;
+ pid_t child;
+ struct ul_pty *pty;
shell = getenv("SHELL");
if (shell == NULL)
ul_pty_init_debug(0);
- ss.pty = ul_new_pty(isatty(STDIN_FILENO));
- if (!ss.pty)
+ pty = ul_new_pty(isatty(STDIN_FILENO));
+ if (!pty)
err(EXIT_FAILURE, "failed to allocate PTY handler");
- ul_pty_set_callback_data(ss.pty, (void *) &ss);
- cb = ul_pty_get_callbacks(ss.pty);
- cb->child_wait = wait_for_child;
+ cb = ul_pty_get_callbacks(pty);
cb->child_sigstop = child_sigstop;
- if (ul_pty_setup(ss.pty))
+ if (ul_pty_setup(pty))
err(EXIT_FAILURE, "failed to create pseudo-terminal");
fflush(stdout); /* ??? */
- switch ((int) (ss.child = fork())) {
+ switch ((int) (child = fork())) {
case -1: /* error */
- ul_pty_cleanup(ss.pty);
+ ul_pty_cleanup(pty);
err(EXIT_FAILURE, "cannot create child process");
break;
case 0: /* child */
- ul_pty_init_slave(ss.pty);
+ ul_pty_init_slave(pty);
signal(SIGTERM, SIG_DFL); /* because /etc/csh.login */
}
/* parent */
- ul_pty_set_child(ss.pty, ss.child);
+ ul_pty_set_child(pty, child);
/* this is the main loop */
- ul_pty_proxy_master(ss.pty);
+ ul_pty_proxy_master(pty);
/* all done; cleanup and kill */
- caught_signal = ul_pty_get_delivered_signal(ss.pty);
+ caught_signal = ul_pty_get_delivered_signal(pty);
- if (!caught_signal && ss.child != (pid_t)-1)
- wait_for_child(&ss); /* final wait */
+ if (!caught_signal && ul_pty_get_child(pty) != (pid_t)-1)
+ ul_pty_wait_for_child(pty); /* final wait */
- if (caught_signal && ss.child != (pid_t)-1) {
+ if (caught_signal && ul_pty_get_child(pty) != (pid_t)-1) {
fprintf(stderr, "\nSession terminated, killing shell...");
- kill(ss.child, SIGTERM);
+ kill(child, SIGTERM);
sleep(2);
- kill(ss.child, SIGKILL);
+ kill(child, SIGKILL);
fprintf(stderr, " ...killed.\n");
}
- ul_pty_cleanup(ss.pty);
+ ul_pty_cleanup(pty);
+ ul_free_pty(pty);
return EXIT_SUCCESS;
}