From 60a9a3fbfeb9280b85e7ca17853ba5d6b8225c1c Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 13 Jan 2012 12:42:13 +0200 Subject: [PATCH 1/3] Add pseudo terminal support This commit adds a new pty object. The pty object takes care of all pseudo terminal handling, reading and writing. It can be opened and closed, and notify through callbacks when input arrives or the child process exits/dies. It can also receive input and pass it along to the child process. There is not yet any real VTE processing, so we display raw escape codes and so on. However, this should provide immediate feedback for any further vte development, as we start to act like a real terminal emulator. Signed-off-by: Ran Benita --- Makefile.am | 3 +- src/pty.c | 421 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/pty.h | 72 +++++++++ 3 files changed, 495 insertions(+), 1 deletion(-) create mode 100644 src/pty.c create mode 100644 src/pty.h diff --git a/Makefile.am b/Makefile.am index 21ca61e..0c52720 100644 --- a/Makefile.am +++ b/Makefile.am @@ -31,7 +31,8 @@ libkmscon_core_la_SOURCES = \ src/input_xkb.c src/input_xkb.h \ external/imKStoUCS.c external\imKStoUCS.h \ src/vte.c src/vte.h \ - src/terminal.c src/terminal.h + src/terminal.c src/terminal.h \ + src/pty.c src/pty.h if USE_PANGO libkmscon_core_la_SOURCES += \ diff --git a/src/pty.c b/src/pty.c new file mode 100644 index 0000000..4f53170 --- /dev/null +++ b/src/pty.c @@ -0,0 +1,421 @@ +/* + * kmscon - Pseudo Terminal Handling + * + * Copyright (c) 2012 Ran Benita + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* for pty functions */ +#define _XOPEN_SOURCE 700 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "log.h" +#include "pty.h" + +struct kmscon_pty { + unsigned long ref; + struct kmscon_eloop *eloop; + + int fd; + struct kmscon_fd *efd; + + kmscon_pty_output_cb output_cb; + void *output_data; + + kmscon_pty_closed_cb closed_cb; + void *closed_data; +}; + +int kmscon_pty_new(struct kmscon_pty **out, + kmscon_pty_output_cb output_cb, void *data) +{ + struct kmscon_pty *pty; + + if (!out) + return -EINVAL; + + log_debug("pty: new pty object\n"); + + pty = malloc(sizeof(*pty)); + if (!pty) + return -ENOMEM; + + memset(pty, 0, sizeof(*pty)); + pty->fd = -1; + pty->ref = 1; + + pty->output_cb = output_cb; + pty->output_data = data; + *out = pty; + return 0; +} + +void kmscon_pty_ref(struct kmscon_pty *pty) +{ + if (!pty) + return; + + pty->ref++; +} + +void kmscon_pty_unref(struct kmscon_pty *pty) +{ + if (!pty || !pty->ref) + return; + + if (--pty->ref) + return; + + kmscon_pty_close(pty); + free(pty); + log_debug("pty: destroying pty object\n"); +} + +/* + * TODO: + * - Decide which terminal we're emulating and set TERM accordingly. + * - Decide what to exec here: login, some getty equivalent, a shell... + * - Might also need to update some details in utmp wtmp and friends. + */ +static void __attribute__((noreturn)) +exec_child(int pty_master) +{ + const char *sh; + + setenv("TERM", "linux", 1); + + sh = getenv("SHELL") ?: _PATH_BSHELL; + execlp(sh, sh, "-i", NULL); + + log_err("pty: failed to exec child: %m\n"); + + _exit(EXIT_FAILURE); +} + +static int fork_pty_child(int master, struct winsize *ws) +{ + int ret, saved_errno; + pid_t pid; + const char *slave_name; + int slave = -1; + + /* This doesn't actually do anything on linux. */ + ret = grantpt(master); + if (ret < 0) { + log_err("pty: grantpt failed: %m"); + goto err_out; + } + + ret = unlockpt(master); + if (ret < 0) { + log_err("pty: cannot unlock pty: %m"); + goto err_out; + } + + slave_name = ptsname(master); + if (!slave_name) { + log_err("pty: cannot find slave name: %m"); + goto err_out; + } + + /* This also loses our controlling tty. */ + pid = setsid(); + if (pid < 0) { + log_err("pty: cannot start a new session: %m"); + goto err_out; + } + + /* And the slave pty becomes our controlling tty. */ + slave = open(slave_name, O_RDWR | O_CLOEXEC); + if (slave < 0) { + log_err("pty: cannot open slave: %m"); + goto err_out; + } + + if (ws) { + ret = ioctl(slave, TIOCSWINSZ, ws); + if (ret) + log_warning("pty: cannot set slave window size: %m"); + } + + if (dup2(slave, STDIN_FILENO) != STDIN_FILENO || + dup2(slave, STDOUT_FILENO) != STDOUT_FILENO || + dup2(slave, STDERR_FILENO) != STDERR_FILENO) { + log_err("pty: cannot duplicate slave: %m"); + goto err_out; + } + + close(master); + close(slave); + return 0; + +err_out: + saved_errno = errno; + if (slave > 0) + close(slave); + close(master); + return -saved_errno; +} + +/* + * This is functionally equivalent to forkpty(3). We do it manually to obtain + * a little bit more control of the process, and as a bonus avoid linking to + * the libutil library in glibc. + */ +static pid_t fork_pty(int *pty_out, struct winsize *ws) +{ + int ret; + pid_t pid; + int master; + + master = posix_openpt(O_RDWR | O_NOCTTY | O_CLOEXEC | O_NONBLOCK); + if (master < 0) { + ret = -errno; + log_err("pty: cannot open master: %m"); + goto err_out; + } + + pid = fork(); + switch (pid) { + case -1: + log_err("pty: cannot fork: %m"); + ret = -errno; + goto err_master; + case 0: + ret = fork_pty_child(master, ws); + if (ret) + goto err_master; + *pty_out = -1; + return 0; + default: + *pty_out = master; + return pid; + } + +err_master: + close(master); +err_out: + *pty_out = -1; + errno = -ret; + return -1; +} + +static int pty_spawn(struct kmscon_pty *pty, + unsigned short width, unsigned short height) +{ + struct winsize ws; + pid_t pid; + + if (pty->fd >= 0) + return -EALREADY; + + memset(&ws, 0, sizeof(ws)); + ws.ws_col = width; + ws.ws_row = height; + + pid = fork_pty(&pty->fd, &ws); + switch (pid) { + case -1: + log_err("pty: cannot fork or open pty pair: %m"); + return -errno; + case 0: + exec_child(pty->fd); + default: + break; + } + + return 0; +} + +static void pty_output(struct kmscon_fd *fd, int mask, void *data) +{ + int ret, nread; + ssize_t len; + struct kmscon_pty *pty = data; + + if (!pty || pty->fd < 0) + return; + + /* + * If we get a hangup or an error, but the pty is still readable, we + * read what's left and deal with the rest on the next dispatch. + */ + if (!(mask & KMSCON_READABLE)) { + if (mask & KMSCON_ERR) + log_warning("pty: error condition happened on pty\n"); + kmscon_pty_close(pty); + return; + } + + ret = ioctl(pty->fd, FIONREAD, &nread); + if (ret) { + log_warning("pty: cannot peek into pty input buffer: %m"); + return; + } else if (nread <= 0) { + return; + } + + char u8[nread]; + len = read(pty->fd, u8, nread); + if (len == -1) { + if (errno == EWOULDBLOCK) + return; + /* EIO is hangup, although we should have caught it above. */ + if (errno != EIO) + log_err("pty: cannot read from pty: %m"); + kmscon_pty_close(pty); + return; + } else if (len == 0) { + kmscon_pty_close(pty); + return; + } + + if (pty->output_cb) + pty->output_cb(pty, u8, len, pty->output_data); +} + +static int connect_eloop(struct kmscon_pty *pty, struct kmscon_eloop *eloop) +{ + int ret; + + if (pty->eloop) + return -EALREADY; + + ret = kmscon_eloop_new_fd(eloop, &pty->efd, pty->fd, + KMSCON_READABLE, pty_output, pty); + if (ret) + return ret; + + kmscon_eloop_ref(eloop); + pty->eloop = eloop; + return 0; +} + +static void disconnect_eloop(struct kmscon_pty *pty) +{ + kmscon_eloop_rm_fd(pty->efd); + kmscon_eloop_unref(pty->eloop); + pty->efd = NULL; + pty->eloop = NULL; +} + +int kmscon_pty_open(struct kmscon_pty *pty, struct kmscon_eloop *eloop, + unsigned short width, unsigned short height, + kmscon_pty_closed_cb closed_cb, void *data) +{ + int ret; + + if (!pty || !eloop) + return -EINVAL; + + if (pty->fd >= 0) + return -EALREADY; + + ret = pty_spawn(pty, width, height); + if (ret) + return ret; + + ret = connect_eloop(pty, eloop); + if (ret == -EALREADY) { + disconnect_eloop(pty); + ret = connect_eloop(pty, eloop); + } + if (ret) { + close(pty->fd); + pty->fd = -1; + return ret; + } + + pty->closed_cb = closed_cb; + pty->closed_data = data; + return 0; +} + +void kmscon_pty_close(struct kmscon_pty *pty) +{ + kmscon_pty_closed_cb cb; + void *data; + + if (!pty || pty->fd < 0) + return; + + disconnect_eloop(pty); + + close(pty->fd); + pty->fd = -1; + + cb = pty->closed_cb; + data = pty->closed_data; + pty->closed_cb = NULL; + pty->closed_data = NULL; + + if (cb) + cb(pty, data); +} + +void kmscon_pty_input(struct kmscon_pty *pty, const char *u8, size_t len) +{ + if (!pty || pty->fd < 0) + return; + + /* FIXME: In EWOULDBLOCK we would lose input! Need to buffer. */ + len = write(pty->fd, u8, len); + if (len <= 0) { + if (errno != EWOULDBLOCK) + kmscon_pty_close(pty); + return; + } +} + +void kmscon_pty_resize(struct kmscon_pty *pty, + unsigned short width, unsigned short height) +{ + int ret; + struct winsize ws; + + if (!pty || pty->fd < 0) + return; + + memset(&ws, 0, sizeof(ws)); + ws.ws_col = width; + ws.ws_row = height; + + /* + * This will send SIGWINCH to the pty slave foreground process group. + * We will also get one, but we don't need it. + */ + ret = ioctl(pty->fd, TIOCSWINSZ, &ws); + if (ret) { + log_warning("pty: cannot set window size\n"); + return; + } + + log_debug("pty: window size set to %hdx%hd\n", ws.ws_col, ws.ws_row); +} diff --git a/src/pty.h b/src/pty.h new file mode 100644 index 0000000..6cb0877 --- /dev/null +++ b/src/pty.h @@ -0,0 +1,72 @@ +/* + * kmscon - Pseudo Terminal Handling + * + * Copyright (c) 2012 Ran Benita + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The pty object provides an interface for communicating with a child process + * over a pseudo terminal. The child is the host, we act as the TTY terminal, + * and the kernel is the driver. + * + * To use this, create a new pty object and open it. You will start receiving + * output notifications through the output_cb callback. To communicate with + * the other end of the terminal, use the kmscon_pty_input method. All + * communication is done using byte streams (presumably UTF-8). + * + * The pty can be closed voluntarily using the kmson_pty_close method. The + * child process can also exit at will; this will be communicated through the + * closed_cb callback. The pty object does not wait on the child processes it + * spawns; this is the responsibility of the object's user. + */ + +#ifndef KMSCON_PTY_H +#define KMSCON_PTY_H + +#include "eloop.h" + +struct kmscon_pty; + +typedef void (*kmscon_pty_output_cb) + (struct kmscon_pty *pty, char *u8, size_t len, void *data); +typedef void (*kmscon_pty_closed_cb) (struct kmscon_pty *pty, void *data); + +int kmscon_pty_new(struct kmscon_pty **out, + kmscon_pty_output_cb output_cb, void *data); +void kmscon_pty_ref(struct kmscon_pty *pty); +void kmscon_pty_unref(struct kmscon_pty *pty); + +int kmscon_pty_open(struct kmscon_pty *pty, struct kmscon_eloop *eloop, + unsigned short width, unsigned short height, + kmscon_pty_closed_cb closed_cb, void *data); +void kmscon_pty_close(struct kmscon_pty *pty); + +void kmscon_pty_input(struct kmscon_pty *pty, const char *u8, size_t len); + +/* + * Call this whenever the size of the screen (rows or columns) changes. The + * kernel and child process need to be notified. + */ +void kmscon_pty_resize(struct kmscon_pty *pty, + unsigned short width, unsigned short height); + +#endif /* KMSCON_PTY_H */ From ea3177c6151eccb69a438e9469ea8f62a2939ec6 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 13 Jan 2012 12:53:46 +0200 Subject: [PATCH 2/3] terminal: use new pty object Add a new terminal_open/terminal_close methods to correspond to the pty ones, and notify when the terminal is closed. Signed-off-by: Ran Benita --- src/terminal.c | 105 ++++++++++++++++++++++++++++++++++-------- src/terminal.h | 10 ++-- tests/test_terminal.c | 12 ++++- 3 files changed, 104 insertions(+), 23 deletions(-) diff --git a/src/terminal.c b/src/terminal.c index ec61bfd..5dfbbcb 100644 --- a/src/terminal.c +++ b/src/terminal.c @@ -39,7 +39,9 @@ #include "console.h" #include "eloop.h" #include "font.h" +#include "input.h" #include "log.h" +#include "pty.h" #include "terminal.h" #include "unicode.h" #include "vte.h" @@ -59,6 +61,10 @@ struct kmscon_terminal { struct kmscon_console *console; struct kmscon_idle *redraw; struct kmscon_vte *vte; + struct kmscon_pty *pty; + + kmscon_terminal_closed_cb closed_cb; + void *closed_data; }; static void draw_all(struct kmscon_idle *idle, void *data) @@ -101,20 +107,17 @@ static void schedule_redraw(struct kmscon_terminal *term) log_warning("terminal: cannot schedule redraw\n"); } -static const char help_text[] = -"terminal subsystem - KMS based console test\n" -"This is some default text to test the drawing operations.\n\n"; - -static void print_help(struct kmscon_terminal *term) +static void pty_output(struct kmscon_pty *pty, char *u8, size_t len, void *data) { - unsigned int i, len; - kmscon_symbol_t ch; + size_t i; + struct kmscon_terminal *term = data; - len = sizeof(help_text) - 1; - for (i = 0; i < len; ++i) { - ch = kmscon_symbol_make(help_text[i]); - kmscon_terminal_input(term, ch); - } + /* FIXME: UTF-8. */ + for (i=0; i < len; i++) + if (u8[i] < 128) + kmscon_vte_input(term->vte, u8[i]); + + schedule_redraw(term); } int kmscon_terminal_new(struct kmscon_terminal **out, @@ -147,11 +150,16 @@ int kmscon_terminal_new(struct kmscon_terminal **out, if (ret) goto err_con; kmscon_vte_bind(term->vte, term->console); - print_help(term); + + ret = kmscon_pty_new(&term->pty, pty_output, term); + if (ret) + goto err_vte; *out = term; return 0; +err_vte: + kmscon_vte_unref(term->vte); err_con: kmscon_console_unref(term->console); err_idle: @@ -177,16 +185,17 @@ void kmscon_terminal_unref(struct kmscon_terminal *term) if (--term->ref) return; + term->closed_cb = NULL; + kmscon_terminal_close(term); kmscon_terminal_rm_all_outputs(term); + kmscon_pty_unref(term->pty); kmscon_vte_unref(term->vte); kmscon_console_unref(term->console); - kmscon_terminal_disconnect_eloop(term); free(term); log_debug("terminal: destroying terminal object\n"); } -int kmscon_terminal_connect_eloop(struct kmscon_terminal *term, - struct kmscon_eloop *eloop) +int connect_eloop(struct kmscon_terminal *term, struct kmscon_eloop *eloop) { if (!term || !eloop) return -EINVAL; @@ -200,7 +209,7 @@ int kmscon_terminal_connect_eloop(struct kmscon_terminal *term, return 0; } -void kmscon_terminal_disconnect_eloop(struct kmscon_terminal *term) +void disconnect_eloop(struct kmscon_terminal *term) { if (!term) return; @@ -209,6 +218,63 @@ void kmscon_terminal_disconnect_eloop(struct kmscon_terminal *term) term->eloop = NULL; } +static void pty_closed(struct kmscon_pty *pty, void *data) +{ + struct kmscon_terminal *term = data; + kmscon_terminal_close(term); +} + +int kmscon_terminal_open(struct kmscon_terminal *term, + struct kmscon_eloop *eloop, + kmscon_terminal_closed_cb closed_cb, void *data) +{ + int ret; + unsigned short width, height; + + if (!term) + return -EINVAL; + + ret = connect_eloop(term, eloop); + if (ret == -EALREADY) { + disconnect_eloop(term); + ret = connect_eloop(term, eloop); + } + if (ret) + return ret; + + width = kmscon_console_get_width(term->console); + height = kmscon_console_get_height(term->console); + ret = kmscon_pty_open(term->pty, eloop, width, height, pty_closed, term); + if (ret) { + disconnect_eloop(term); + return ret; + } + + term->closed_cb = closed_cb; + term->closed_data = data; + return 0; +} + +void kmscon_terminal_close(struct kmscon_terminal *term) +{ + kmscon_terminal_closed_cb cb; + void *data; + + if (!term) + return; + + cb = term->closed_cb; + data = term->closed_data; + term->closed_data = NULL; + term->closed_cb = NULL; + + disconnect_eloop(term); + kmscon_pty_close(term->pty); + + if (cb) + cb(term, data); +} + int kmscon_terminal_add_output(struct kmscon_terminal *term, struct kmscon_output *output) { @@ -263,6 +329,7 @@ void kmscon_terminal_rm_all_outputs(struct kmscon_terminal *term) void kmscon_terminal_input(struct kmscon_terminal *term, kmscon_symbol_t ch) { - kmscon_vte_input(term->vte, ch); - schedule_redraw(term); + /* FIXME: UTF-8. */ + if (ch < 128) + kmscon_pty_input(term->pty, (char *)&ch, 1); } diff --git a/src/terminal.h b/src/terminal.h index 0168b5f..70c7e12 100644 --- a/src/terminal.h +++ b/src/terminal.h @@ -41,14 +41,18 @@ struct kmscon_terminal; +typedef void (*kmscon_terminal_closed_cb) (struct kmscon_terminal *term, + void *data); + int kmscon_terminal_new(struct kmscon_terminal **out, struct kmscon_font_factory *ff); void kmscon_terminal_ref(struct kmscon_terminal *term); void kmscon_terminal_unref(struct kmscon_terminal *term); -int kmscon_terminal_connect_eloop(struct kmscon_terminal *term, - struct kmscon_eloop *eloop); -void kmscon_terminal_disconnect_eloop(struct kmscon_terminal *term); +int kmscon_terminal_open(struct kmscon_terminal *term, + struct kmscon_eloop *eloop, + kmscon_terminal_closed_cb closed_cb, void *data); +void kmscon_terminal_close(struct kmscon_terminal *term); int kmscon_terminal_add_output(struct kmscon_terminal *term, struct kmscon_output *output); diff --git a/tests/test_terminal.c b/tests/test_terminal.c index 48d30ba..a606ce5 100644 --- a/tests/test_terminal.c +++ b/tests/test_terminal.c @@ -63,6 +63,15 @@ static void sig_term(struct kmscon_signal *sig, int signum, void *data) terminate = 1; } +static void terminal_closed(struct kmscon_terminal *term, void *data) +{ + /* + * Alternativly, we could spwan a new login/shell here, like what + * happens when the user exits the shell in a linux console. + */ + terminate = 1; +} + static void read_input(struct kmscon_input *input, struct kmscon_input_event *ev, void *data) { @@ -186,7 +195,8 @@ static int setup_app(struct app *app) if (ret) goto err_loop; - ret = kmscon_terminal_connect_eloop(app->term, app->eloop); + ret = kmscon_terminal_open(app->term, app->eloop, + terminal_closed, NULL); if (ret) goto err_loop; From b8210461a295ff90f3fcb66d70cdbc75c57136b9 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 10 Jan 2012 02:52:42 +0200 Subject: [PATCH 3/3] test_terminal: wait on children to avoid zombies Unfortunately, there is no clean way I see to hook this up from the pty object. We can (and will) have more than one pty object opened at a time, but the semantics of signalfd make it impossible to deliver each SIGCHLD to its rightful owner without complicating things. [ From what I tested: - If you have two signalfd's listening to the same signal, they will be dispatched in some round-robin manner. - Also, if more than one child exits before we read signalfd (possibly beloging to different terminals), they will be compressed to one event. ] We therefore need to do the reaping from a central location, and need to remember to copy this snippet over to main.c in the future. Signed-off-by: Ran Benita --- tests/test_terminal.c | 68 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 6 deletions(-) diff --git a/tests/test_terminal.c b/tests/test_terminal.c index a606ce5..b30c8e1 100644 --- a/tests/test_terminal.c +++ b/tests/test_terminal.c @@ -36,6 +36,7 @@ #include #include #include +#include #include "eloop.h" #include "input.h" @@ -48,6 +49,7 @@ struct app { struct kmscon_eloop *eloop; struct kmscon_signal *sig_term; struct kmscon_signal *sig_int; + struct kmscon_signal *sig_chld; struct kmscon_symbol_table *st; struct kmscon_font_factory *ff; struct kmscon_compositor *comp; @@ -63,13 +65,61 @@ static void sig_term(struct kmscon_signal *sig, int signum, void *data) terminate = 1; } +static void sig_chld(struct kmscon_signal *sig, int signum, void *data) +{ + pid_t pid; + int status; + + /* + * If multiple children exit at the same time, signalfd would put them + * all in one event. So we reap in a loop. + */ + while (1) { + pid = waitpid(-1, &status, WNOHANG); + if (pid == -1) { + if (errno != ECHILD) + log_warning("test: cannot wait on child: %m\n"); + break; + } else if (pid == 0) { + break; + } else if (WIFEXITED(status)) { + if (WEXITSTATUS(status) != 0) + log_info("test: child %d exited with status " + "%d\n", pid, WEXITSTATUS(status)); + else + log_debug("test: child %d exited " + "successfully\n", pid); + } else if (WIFSIGNALED(status)) { + log_debug("test: child %d exited by signal %d\n", pid, + WTERMSIG(status)); + } + } +} + static void terminal_closed(struct kmscon_terminal *term, void *data) { - /* - * Alternativly, we could spwan a new login/shell here, like what - * happens when the user exits the shell in a linux console. - */ - terminate = 1; +#if 0 + /* + * Alternativly, we could spwan a new login/shell here, like what + * happens when the user exits the shell in a linux console: + */ + + int ret; + struct app *app = data; + + if (!app) + goto err_out; + + ret = kmscon_terminal_open(app->term, app->eloop, + terminal_closed, app); + if (ret) + goto err_out; + + return; + +err_out: +#endif + terminate = 1; } static void read_input(struct kmscon_input *input, @@ -140,6 +190,7 @@ static void destroy_app(struct app *app) kmscon_compositor_unref(app->comp); kmscon_font_factory_unref(app->ff); kmscon_symbol_table_unref(app->st); + kmscon_eloop_rm_signal(app->sig_chld); kmscon_eloop_rm_signal(app->sig_int); kmscon_eloop_rm_signal(app->sig_term); kmscon_eloop_unref(app->eloop); @@ -163,6 +214,11 @@ static int setup_app(struct app *app) if (ret) goto err_loop; + ret = kmscon_eloop_new_signal(app->eloop, &app->sig_chld, SIGCHLD, + sig_chld, NULL); + if (ret) + goto err_loop; + ret = kmscon_symbol_table_new(&app->st); if (ret) goto err_loop; @@ -196,7 +252,7 @@ static int setup_app(struct app *app) goto err_loop; ret = kmscon_terminal_open(app->term, app->eloop, - terminal_closed, NULL); + terminal_closed, app); if (ret) goto err_loop;