[systemd-devel] [RFC 12/12] console: add systemd-consoled

David Herrmann dh.herrmann at gmail.com
Wed Nov 27 10:48:47 PST 2013


systemd-consoled is a very basic terminal-emulator to replace the
in-kernel VT layer. It is based on libtsm as emulation layer (which itself
has no external dependencies).

systemd-consoled expects to be run in a logind-session. The caller must
have already setup the session and prepared it for systemd-consoled. This
is usually done by login-managers.
---
 .gitignore                      |   1 +
 Makefile.am                     |  24 +++
 configure.ac                    |  17 ++
 src/console/Makefile            |   1 +
 src/console/consoled-pty.c      | 391 ++++++++++++++++++++++++++++++++++++++++
 src/console/consoled-screen.c   | 170 +++++++++++++++++
 src/console/consoled-terminal.c | 371 ++++++++++++++++++++++++++++++++++++++
 src/console/consoled.c          | 278 ++++++++++++++++++++++++++++
 src/console/consoled.h          | 128 +++++++++++++
 9 files changed, 1381 insertions(+)
 create mode 120000 src/console/Makefile
 create mode 100644 src/console/consoled-pty.c
 create mode 100644 src/console/consoled-screen.c
 create mode 100644 src/console/consoled-terminal.c
 create mode 100644 src/console/consoled.c
 create mode 100644 src/console/consoled.h

diff --git a/.gitignore b/.gitignore
index c856412..0f563c3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -36,6 +36,7 @@
 /systemd-cgls
 /systemd-cgroups-agent
 /systemd-cgtop
+/systemd-consoled
 /systemd-coredump
 /systemd-coredumpctl
 /systemd-cryptsetup
diff --git a/Makefile.am b/Makefile.am
index 1e8aeed..b5f1fd9 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3937,6 +3937,30 @@ update-unifont: src/libsystemd-gfx/unifont.bin src/libsystemd-gfx/unifont.cmp
 endif
 
 # ------------------------------------------------------------------------------
+if ENABLE_CONSOLED
+rootlibexec_PROGRAMS += \
+	systemd-consoled
+
+systemd_consoled_SOURCES = \
+	src/console/consoled.h \
+	src/console/consoled.c \
+	src/console/consoled-pty.c \
+	src/console/consoled-screen.c \
+	src/console/consoled-terminal.c
+
+systemd_consoled_CFLAGS = \
+	$(AM_CFLAGS) \
+	$(CONSOLED_CFLAGS)
+
+systemd_consoled_LDADD = \
+	$(CONSOLED_LIBS) \
+	libsystemd-bus.la \
+	libsystemd-daemon.la \
+	libsystemd-gfx.la \
+	libsystemd-shared.la
+endif
+
+# ------------------------------------------------------------------------------
 if ENABLE_NETWORKD
 rootlibexec_PROGRAMS += \
 	systemd-networkd
diff --git a/configure.ac b/configure.ac
index bf3fce3..924637c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -306,6 +306,22 @@ AM_CONDITIONAL(HAVE_GFX, [test "$have_gfx" = "yes"])
 AM_CONDITIONAL(HAVE_GFX_GL, [test "$have_gfx_gl" = "yes"])
 
 # ------------------------------------------------------------------------------
+have_consoled=no
+AC_ARG_ENABLE(consoled, AS_HELP_STRING([--disable-consoled], [disable system console]))
+if test "x$enable_consoled" != "xno"; then
+        if test "x$have_gfx" = xno -a "x$enable_consoled" = xyes; then
+                AC_MSG_ERROR([*** consoled support requested, but libraries not found])
+        elif test "x$have_gfx" = xyes ; then
+                PKG_CHECK_MODULES(CONSOLED, [ libtsm >= 3 ],
+                        [AC_DEFINE(ENABLE_CONSOLED, 1, [Define if system console is built]) have_consoled=yes], have_consoled=no)
+                if test "x$have_consoled" = xno -a "x$enable_consoled" = xyes; then
+                        AC_MSG_ERROR([*** consoled support requested, but libraries not found])
+                fi
+        fi
+fi
+AM_CONDITIONAL(ENABLE_CONSOLED, [test "$have_consoled" = "yes"])
+
+# ------------------------------------------------------------------------------
 have_blkid=no
 AC_ARG_ENABLE(blkid, AS_HELP_STRING([--disable-blkid], [disable blkid support]))
 if test "x$enable_blkid" != "xno"; then
@@ -1095,6 +1111,7 @@ AC_MSG_RESULT([
         efi:                     ${have_efi}
         kmod:                    ${have_kmod}
         sd-gfx:                  ${have_gfx}
+        consoled:                ${have_consoled}
         blkid:                   ${have_blkid}
         nss-myhostname:          ${have_myhostname}
         gudev:                   ${enable_gudev}
diff --git a/src/console/Makefile b/src/console/Makefile
new file mode 120000
index 0000000..d0b0e8e
--- /dev/null
+++ b/src/console/Makefile
@@ -0,0 +1 @@
+../Makefile
\ No newline at end of file
diff --git a/src/console/consoled-pty.c b/src/console/consoled-pty.c
new file mode 100644
index 0000000..c3defc1
--- /dev/null
+++ b/src/console/consoled-pty.c
@@ -0,0 +1,391 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2013 David Herrmann <dh.herrmann at gmail.com>
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <pty.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/epoll.h>
+#include <sys/ioctl.h>
+#include <sys/uio.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "consoled.h"
+#include "ring.h"
+#include "sd-event.h"
+
+/*
+ * PTY
+ * A PTY object represents a single PTY connection between a master and a
+ * child. The child process is fork()ed so the caller controls what program
+ * will be run.
+ *
+ * Programs like /bin/login tend to perform a vhangup() on their TTY
+ * before running the login procedure. This also causes the pty master
+ * to get a EPOLLHUP event as long as no client has the TTY opened.
+ * This means, we cannot use the TTY connection as reliable way to track
+ * the client. Instead, we _must_ rely on the PID of the client to track
+ * them.
+ * However, this has the side effect that if the client forks and the
+ * parent exits, we loose them and restart the client. But this seems to
+ * be the expected behavior so we implement it here.
+ *
+ * Unfortunately, epoll always polls for EPOLLHUP so as long as the
+ * vhangup() is ongoing, we will _always_ get EPOLLHUP and cannot sleep.
+ * This gets worse if the client closes the TTY but doesn't exit.
+ * Therefore, the fd must be edge-triggered in the epoll-set so we
+ * only get the events once they change.
+ */
+
+static void pty_dispatch_write(Pty *pty) {
+        struct iovec vec[2];
+        size_t num;
+        ssize_t r;
+
+        num = ring_peek(&pty->out_buf, vec);
+        if (!num)
+                return;
+
+        /* ignore errors in favor of SIGCHLD; (we're edge-triggered, anyway) */
+        r = writev(pty->fd, vec, (int)num);
+        if (r > 0)
+                ring_pull(&pty->out_buf, (size_t)r);
+}
+
+static int pty_dispatch_read(Pty *pty) {
+        ssize_t len, num;
+
+        /* We're edge-triggered, means we need to read the whole queue. This,
+         * however, might cause us to stall if the writer is faster than we
+         * are. Therefore, we have some rather arbitrary limit on how fast
+         * we read. If we reach it, we simply return EAGAIN to the caller and
+         * let them schedule an idle-event. */
+
+        num = 4;
+        do {
+                len = read(pty->fd, pty->in_buf, sizeof(pty->in_buf));
+                if (len > 0)
+                        terminal_from_pty(pty->t, pty->in_buf, len);
+        } while (len > 0 && --num);
+
+        return !num ? -EAGAIN : 0;
+}
+
+static int pty_dispatch(Pty *pty) {
+        int r;
+
+        r = pty_dispatch_read(pty);
+        pty_dispatch_write(pty);
+        return r;
+}
+
+static int pty_idle_fn(sd_event_source *source, void *data) {
+        Pty *pty = data;
+        int r;
+
+        r = pty_dispatch(pty);
+        if (r == -EAGAIN)
+                sd_event_source_set_enabled(pty->idle_source, SD_EVENT_ONESHOT);
+
+        return 0;
+}
+
+static int pty_io_fn(sd_event_source *source, int fd, uint32_t ev, void *data) {
+        Pty *pty = data;
+        int r;
+
+        r = pty_dispatch(pty);
+        if (r == -EAGAIN)
+                sd_event_source_set_enabled(pty->idle_source, SD_EVENT_ONESHOT);
+
+        return 0;
+}
+
+enum {
+        PTY_FAILED,
+        PTY_SETUP,
+};
+
+static char pty_wait(int fd) {
+        int r;
+        char d;
+
+        do {
+                r = read(fd, &d, 1);
+        } while (r < 0 && (errno == EINTR || errno == EAGAIN));
+
+        return (r <= 0) ? PTY_FAILED : d;
+}
+
+static int pty_wakeup(int fd, char d) {
+        int r;
+
+        do {
+                r = write(fd, &d, 1);
+        } while (r < 0 && (errno == EINTR || errno == EAGAIN));
+
+        return (r == 1) ? 0 : -EINVAL;
+}
+
+static int pty_setup_child(int slave, unsigned short term_width, unsigned short term_height) {
+        struct termios attr;
+        struct winsize ws;
+
+        if (tcgetattr(slave, &attr) < 0)
+                return -errno;
+
+        /* erase character should be normal backspace, PLEASEEE! */
+        attr.c_cc[VERASE] = 010;
+
+        if (tcsetattr(slave, TCSANOW, &attr) < 0)
+                return -errno;
+
+        memset(&ws, 0, sizeof(ws));
+        ws.ws_col = term_width;
+        ws.ws_row = term_height;
+
+        if (ioctl(slave, TIOCSWINSZ, &ws) < 0)
+                return -errno;
+
+        if (dup2(slave, STDIN_FILENO) != STDIN_FILENO ||
+            dup2(slave, STDOUT_FILENO) != STDOUT_FILENO ||
+            dup2(slave, STDERR_FILENO) != STDERR_FILENO)
+                return -errno;
+
+        return 0;
+}
+
+static int pty_init_child(int fd) {
+        int r;
+        sigset_t sigset;
+        char *slave_name;
+        int slave, i;
+        pid_t pid;
+
+        /* unlockpt() requires unset signal-handlers */
+        sigemptyset(&sigset);
+        r = sigprocmask(SIG_SETMASK, &sigset, NULL);
+        if (r < 0)
+                return -errno;
+
+        for (i = 1; i < SIGUNUSED; ++i)
+                signal(i, SIG_DFL);
+
+        r = grantpt(fd);
+        if (r < 0)
+                return -errno;
+
+        r = unlockpt(fd);
+        if (r < 0)
+                return -errno;
+
+        slave_name = ptsname(fd);
+        if (!slave_name)
+                return -errno;
+
+        /* open slave-TTY */
+        slave = open(slave_name, O_RDWR | O_CLOEXEC | O_NOCTTY);
+        if (slave < 0)
+                return -errno;
+
+        /* open session so we loose our controlling TTY */
+        pid = setsid();
+        if (pid < 0) {
+                close(slave);
+                return -errno;
+        }
+
+        /* set controlling TTY */
+        r = ioctl(slave, TIOCSCTTY, 0);
+        if (r < 0) {
+                close(slave);
+                return -errno;
+        }
+
+        return slave;
+}
+
+pid_t pty_new(unsigned short term_width, unsigned short term_height, Terminal *t, Pty **out) {
+        Pty *pty;
+        pid_t pid;
+        int fd, comm[2], slave, r;
+        char d;
+
+        pty = calloc(1, sizeof(*pty));
+        if (!pty)
+                return -ENOMEM;
+
+        fd = posix_openpt(O_RDWR | O_NOCTTY | O_CLOEXEC | O_NONBLOCK);
+        if (fd < 0) {
+                free(pty);
+                return -errno;
+        }
+
+        r = sd_event_add_io(t->m->event,
+                            fd,
+                            EPOLLHUP | EPOLLERR | EPOLLIN | EPOLLOUT | EPOLLET,
+                            pty_io_fn,
+                            pty,
+                            &pty->fd_source);
+        if (r < 0) {
+                close(fd);
+                free(pty);
+                return r;
+        }
+
+        r = sd_event_add_defer(t->m->event, pty_idle_fn, pty, &pty->idle_source);
+        if (r < 0) {
+                sd_event_source_set_enabled(pty->fd_source, SD_EVENT_OFF);
+                sd_event_source_unref(pty->fd_source);
+                close(fd);
+                free(pty);
+                return r;
+        }
+
+        r = pipe2(comm, O_CLOEXEC);
+        if (r < 0) {
+                r = -errno;
+                sd_event_source_set_enabled(pty->idle_source, SD_EVENT_OFF);
+                sd_event_source_unref(pty->idle_source);
+                sd_event_source_set_enabled(pty->fd_source, SD_EVENT_OFF);
+                sd_event_source_unref(pty->fd_source);
+                close(fd);
+                free(pty);
+                return r;
+        }
+
+        pid = fork();
+        if (pid < 0) {
+                /* error */
+                pid = -errno;
+                close(comm[0]);
+                close(comm[1]);
+                sd_event_source_set_enabled(pty->idle_source, SD_EVENT_OFF);
+                sd_event_source_unref(pty->idle_source);
+                sd_event_source_set_enabled(pty->fd_source, SD_EVENT_OFF);
+                sd_event_source_unref(pty->fd_source);
+                close(fd);
+                free(pty);
+                return pid;
+        } else if (!pid) {
+                /* child */
+                close(comm[0]);
+                free(pty);
+
+                slave = pty_init_child(fd);
+                close(fd);
+
+                if (slave < 0)
+                        _exit(1);
+
+                r = pty_setup_child(slave, term_width, term_height);
+                if (r < 0)
+                        _exit(1);
+
+                /* close slave if it's not one of the std-fds */
+                if (slave > 2)
+                        close(slave);
+
+                /* wake parent */
+                pty_wakeup(comm[1], PTY_SETUP);
+                close(comm[1]);
+
+                *out = NULL;
+                return pid;
+        }
+
+        /* parent */
+        close(comm[1]);
+
+        pty->fd = fd;
+        pty->child = pid;
+        pty->t = t;
+
+        /* Wait for child setup. We need to do that to guarantee that any
+         * following signals are really delivered. Maybe this is too pedantic,
+         * but.. it's already implemented. */
+        d = pty_wait(comm[0]);
+        if (d != PTY_SETUP) {
+                close(comm[0]);
+                sd_event_source_set_enabled(pty->idle_source, SD_EVENT_OFF);
+                sd_event_source_unref(pty->idle_source);
+                sd_event_source_set_enabled(pty->fd_source, SD_EVENT_OFF);
+                sd_event_source_unref(pty->fd_source);
+                close(fd);
+                free(pty);
+                return -EINVAL;
+        }
+
+        close(comm[0]);
+        *out = pty;
+        return pid;
+}
+
+void pty_free(Pty *pty) {
+        if (!pty)
+                return;
+
+        close(pty->fd);
+        sd_event_source_set_enabled(pty->idle_source, SD_EVENT_OFF);
+        sd_event_source_unref(pty->idle_source);
+        sd_event_source_set_enabled(pty->fd_source, SD_EVENT_OFF);
+        sd_event_source_unref(pty->fd_source);
+        ring_clear(&pty->out_buf);
+        free(pty);
+}
+
+int pty_write(Pty *pty, const char *u8, size_t len) {
+        int r;
+
+        r = ring_push(&pty->out_buf, u8, len);
+        if (r < 0)
+                return r;
+
+        return sd_event_source_set_enabled(pty->idle_source, SD_EVENT_ONESHOT);
+}
+
+int pty_resize(Pty *pty, unsigned short term_width, unsigned short term_height) {
+        struct winsize ws;
+        int r;
+
+        memset(&ws, 0, sizeof(ws));
+        ws.ws_col = term_width;
+        ws.ws_row = term_height;
+
+        /* This will send SIGWINCH to the pty slave foreground process group.
+         * We will also get one, but we don't need it. */
+        r = ioctl(pty->fd, TIOCSWINSZ, &ws);
+        return (r < 0) ? -errno : 0;
+}
+
+int pty_signal(Pty *pty, int sig) {
+        int r;
+
+        r = ioctl(pty->fd, TIOCSIG, sig);
+        return (r < 0) ? -errno : 0;
+}
diff --git a/src/console/consoled-screen.c b/src/console/consoled-screen.c
new file mode 100644
index 0000000..00c26d3
--- /dev/null
+++ b/src/console/consoled-screen.c
@@ -0,0 +1,170 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2013 David Herrmann <dh.herrmann at gmail.com>
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include "build.h"
+#include "consoled.h"
+#include "def.h"
+#include "list.h"
+#include "log.h"
+#include "macro.h"
+#include "missing.h"
+#include "sd-event.h"
+#include "sd-gfx.h"
+#include "util.h"
+
+int screen_new(Terminal *t, sd_gfx_pipe *pipe, Screen **out) {
+        Screen *s;
+
+        s = calloc(1, sizeof(*s));
+        if (!s)
+                return log_oom();
+
+        s->t = t;
+        s->pipe = pipe;
+        s->plane = sd_gfx_pipe_get_primary_plane(pipe);
+
+        LIST_PREPEND(screen, t->screens, s);
+        *out = s;
+        return 0;
+}
+
+void screen_free(Screen *s) {
+        LIST_REMOVE(screen, s->t->screens, s);
+        free(s);
+}
+
+struct screen_data {
+        Screen *s;
+        sd_gfx_fb *fb;
+};
+
+static int screen_render_cell_fn(struct tsm_screen *screen,
+                                 uint32_t id,
+                                 const uint32_t *ch,
+                                 size_t len,
+                                 unsigned int cwidth,
+                                 unsigned int posx,
+                                 unsigned int posy,
+                                 const struct tsm_screen_attr *attr,
+                                 tsm_age_t age,
+                                 void *data) {
+        struct screen_data *d = data;
+        Screen *s = d->s;
+        sd_gfx_fb *fb = d->fb;
+        sd_gfx_buffer *buf;
+        unsigned int x, y;
+        uint32_t fc, bc, tc;
+
+        x = posx * s->t->cell_width;
+        y = posy * s->t->cell_height;
+
+        fc = (0xff << 24) | (attr->fr << 16) | (attr->fg << 8) | attr->fb;
+        bc = (0xff << 24) | (attr->br << 16) | (attr->bg << 8) | attr->bb;
+        if (attr->inverse) {
+                tc = fc;
+                fc = bc;
+                bc = tc;
+        }
+
+        if (!len) {
+                sd_gfx_fb_fill(fb, bc, x, y, s->t->cell_width, s->t->cell_height);
+        } else {
+                sd_gfx_font_render(s->t->font, id, ch, len, &buf);
+                sd_gfx_fb_blend_bichrome(fb, fc, bc, x, y, buf);
+        }
+
+        return 0;
+}
+
+static void screen_render(Screen *s, sd_gfx_fb *fb) {
+        struct screen_data d;
+
+        d.s = s;
+        d.fb = fb;
+        tsm_screen_draw(s->t->screen, screen_render_cell_fn, (void*)&d);
+}
+
+void screen_redraw(Screen *s) {
+        sd_gfx_fb *fb, *front = NULL;
+        int r;
+
+        if (!s->active)
+                return;
+
+        if (s->swapping) {
+                s->need_redraw = 1;
+                return;
+        }
+
+        fb = sd_gfx_plane_get_back(s->plane);
+        if (!fb) {
+                front = sd_gfx_plane_get_front(s->plane);
+                fb = front;
+        }
+
+        s->need_redraw = 0;
+        screen_render(s, fb);
+
+        if (fb == front)
+                return;
+
+        sd_gfx_plane_swap_to(s->plane, fb);
+        r = sd_gfx_pipe_commit(s->pipe);
+        if (r < 0) {
+                log_error("screen: cannot swap primary plane: %d", r);
+        } else {
+                s->swapping = 1;
+        }
+}
+
+void screen_wake_up(Screen *s) {
+        sd_gfx_fb *fb;
+
+        fb = sd_gfx_plane_get_front(s->plane);
+        s->width = sd_gfx_fb_get_width(fb);
+        s->height = sd_gfx_fb_get_height(fb);
+        s->active = 1;
+}
+
+void screen_sleep(Screen *s) {
+        s->width = 0;
+        s->height = 0;
+        s->active = 0;
+}
+
+void screen_swap(Screen *s) {
+        if (!s->swapping)
+                return;
+
+        s->swapping = 0;
+        if (s->need_redraw)
+                screen_redraw(s);
+}
diff --git a/src/console/consoled-terminal.c b/src/console/consoled-terminal.c
new file mode 100644
index 0000000..963266d
--- /dev/null
+++ b/src/console/consoled-terminal.c
@@ -0,0 +1,371 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2013 David Herrmann <dh.herrmann at gmail.com>
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <libtsm.h>
+#include <paths.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include "build.h"
+#include "consoled.h"
+#include "def.h"
+#include "log.h"
+#include "macro.h"
+#include "missing.h"
+#include "sd-event.h"
+#include "sd-gfx.h"
+#include "util.h"
+
+static void terminal_tsm_log_fn(void *data,
+                                const char *file,
+                                int line,
+                                const char *fn,
+                                const char *subs,
+                                unsigned int sev,
+                                const char *format,
+                                va_list args) {
+        char *msg;
+        int r;
+
+        r = vasprintf(&msg, format, args);
+        if (r < 0)
+                return;
+
+        log_meta(sev, file, line, fn, "%s: %s", subs, msg);
+
+        free(msg);
+}
+
+static void terminal_tsm_write_fn(struct tsm_vte *vte,
+                                  const char *u8,
+                                  size_t len,
+                                  void *data) {
+        Terminal *t = data;
+        int r;
+
+        if (!t->pty)
+                return;
+
+        r = pty_write(t->pty, u8, len);
+        if (r < 0)
+                log_error("cannot write message to PTY: %d", r);
+}
+
+int terminal_new(Manager *m, Terminal **out) {
+        Terminal *t;
+        int r;
+
+        t = calloc(1, sizeof(*t));
+        if (!t)
+                return log_oom();
+
+        t->m = m;
+
+        r = sd_gfx_font_new(&t->font, 90);
+        if (r < 0)
+                goto error;
+
+        r = tsm_screen_new(&t->screen, terminal_tsm_log_fn, NULL);
+        if (r < 0) {
+                log_error("cannot allocate TSM screen: %d", r);
+                goto error;
+        }
+
+        r = tsm_vte_new(&t->vte,
+                        t->screen,
+                        terminal_tsm_write_fn,
+                        t,
+                        terminal_tsm_log_fn,
+                        NULL);
+        if (r < 0) {
+                log_error("cannot allocate TSM VTE: %d", r);
+                goto error;
+        }
+
+        *out = t;
+        return 0;
+
+error:
+        tsm_vte_unref(t->vte);
+        tsm_screen_unref(t->screen);
+        sd_gfx_font_free(t->font);
+        free(t);
+        return r;
+}
+
+void terminal_free(Terminal *t) {
+        Screen *s;
+
+        if (!t)
+                return;
+
+        if (t->pty) {
+                sd_event_source_set_enabled(t->pty_source, SD_EVENT_OFF);
+                sd_event_source_unref(t->pty_source);
+                pty_signal(t->pty, SIGHUP);
+                pty_free(t->pty);
+                t->pty = NULL;
+        }
+
+        while ((s = t->screens))
+                screen_free(s);
+
+        tsm_vte_unref(t->vte);
+        tsm_screen_unref(t->screen);
+        sd_gfx_font_free(t->font);
+        free(t);
+}
+
+static void _noreturn_ terminal_run_child(Terminal *t) {
+        char **argv = (char**)(const char*[]) {
+                getenv("SHELL") ? : _PATH_BSHELL,
+                "-il",
+                NULL
+        };
+
+        setenv("TERM", "xterm-256color", 1);
+        execve(argv[0], argv, environ);
+        _exit(1);
+}
+
+static int terminal_child_fn(sd_event_source *source, const siginfo_t *s, void *data) {
+        Terminal *t = data;
+
+        log_warning("child process died");
+
+        pty_signal(t->pty, SIGHUP);
+        pty_free(t->pty);
+        t->pty = NULL;
+        sd_event_request_quit(t->m->event);
+
+        return 0;
+}
+
+void terminal_start(Terminal *t) {
+        pid_t pid;
+        int r;
+
+        pid = pty_new(t->cols, t->rows, t, &t->pty);
+        if (pid < 0)
+                goto error;
+        else if (!pid)
+                terminal_run_child(t);
+
+        r = sd_event_add_child(t->m->event,
+                               t->pty->child,
+                               WEXITED,
+                               terminal_child_fn,
+                               t,
+                               &t->pty_source);
+        if (r < 0)
+                goto error;
+
+        terminal_redraw(t);
+        return;
+
+error:
+        log_error("cannot spawn PTY: %d/%d", (int)pid, r);
+        if (t->pty) {
+                pty_signal(t->pty, SIGHUP);
+                pty_free(t->pty);
+                t->pty = NULL;
+        }
+        sd_event_request_quit(t->m->event);
+}
+
+void terminal_redraw(Terminal *t) {
+        Screen *s;
+
+        LIST_FOREACH(screen, s, t->screens)
+                screen_redraw(s);
+}
+
+static void terminal_resize(Terminal *t) {
+        int r;
+
+        t->cell_width = sd_gfx_font_get_width(t->font) ? : 1;
+        t->cols = t->min_width / t->cell_width;
+        if (!t->cols)
+                t->cols = 1;
+
+        t->cell_height = sd_gfx_font_get_height(t->font) ? : 1;
+        t->rows = t->min_height / t->cell_height;
+        if (!t->rows)
+                t->rows = 1;
+
+        log_debug("terminal: new size is %ux%u / %ux%u",
+                  t->min_width, t->min_height,
+                  t->cols, t->rows);
+
+        r = tsm_screen_resize(t->screen, t->cols, t->rows);
+        if (r < 0) {
+                log_error("cannot resize screen: %d", r);
+        } else if (t->pty) {
+                pty_resize(t->pty, t->cols, t->rows);
+        }
+}
+
+void terminal_pipe_add(Terminal *t, sd_gfx_pipe *pipe) {
+        Screen *s;
+        int r;
+
+        r = screen_new(t, pipe, &s);
+        if (r < 0)
+                return;
+
+        sd_gfx_pipe_set_data(pipe, s);
+}
+
+void terminal_pipe_remove(Terminal *t, sd_gfx_pipe *pipe) {
+        Screen *s = sd_gfx_pipe_get_data(pipe);
+
+        if (!s)
+                return;
+
+        screen_free(s);
+}
+
+void terminal_pipe_wake_up(Terminal *t, sd_gfx_pipe *pipe) {
+        Screen *s = sd_gfx_pipe_get_data(pipe);
+        bool resize = false;
+
+        if (!s)
+                return;
+
+        screen_wake_up(s);
+
+        if (s->width < t->min_width || !t->min_width) {
+                t->min_width = s->width;
+                resize = true;
+        }
+        if (s->height < t->min_height || !t->min_height) {
+                t->min_height = s->height;
+                resize = true;
+        }
+
+        if (resize) {
+                terminal_resize(t);
+                terminal_redraw(t);
+        } else {
+                screen_redraw(s);
+        }
+}
+
+void terminal_pipe_sleep(Terminal *t, sd_gfx_pipe *pipe) {
+        Screen *s = sd_gfx_pipe_get_data(pipe);
+        unsigned int min_width, min_height;
+        bool resize = false;
+        Screen *i;
+
+        if (!s)
+                return;
+
+        if (s->width <= t->min_width || s->height <= t->min_height) {
+                min_width = 0;
+                min_height = 0;
+                LIST_FOREACH(screen, i, t->screens) {
+                        if (!i->active)
+                                continue;
+                        if (i->width < min_width || !min_width)
+                                min_width = i->width;
+                        if (i->height < min_height || !min_height)
+                                min_height = i->height;
+                }
+
+                if (min_width != t->min_width || min_height != t->min_height)
+                        resize = true;
+        }
+
+        screen_sleep(s);
+
+        if (!resize)
+                return;
+
+        t->min_width = min_width;
+        t->min_height = min_height;
+        terminal_resize(t);
+        terminal_redraw(t);
+}
+
+void terminal_pipe_swap(Terminal *t, sd_gfx_pipe *pipe) {
+        Screen *s = sd_gfx_pipe_get_data(pipe);
+
+        if (!s)
+                return;
+
+        screen_swap(s);
+}
+
+static void terminal_kbd_fn(sd_gfx_kbd *kbd, void *fn_data, struct sd_gfx_kbd_event *ev) {
+        Terminal *t = fn_data;
+        unsigned int mods;
+        uint32_t ucs4;
+
+        if (ev->sym_count != 1)
+                return;
+
+        mods = 0;
+        if (ev->mods & SD_GFX_SHIFT)
+                mods |= TSM_SHIFT_MASK;
+        if (ev->mods & SD_GFX_CAPSL)
+                mods |= TSM_LOCK_MASK;
+        if (ev->mods & SD_GFX_CTRL)
+                mods |= TSM_CONTROL_MASK;
+        if (ev->mods & SD_GFX_ALT)
+                mods |= TSM_ALT_MASK;
+        if (ev->mods & SD_GFX_LOGO)
+                mods |= TSM_LOGO_MASK;
+
+        ucs4 = ev->codepoints[0];
+        if (ev->codepoints[0] == 0xffffffff)
+                ucs4 = TSM_VTE_INVALID;
+
+        if (tsm_vte_handle_keyboard(t->vte,
+                                    ev->keysyms[0],
+                                    ev->ascii,
+                                    mods,
+                                    ucs4)) {
+                tsm_screen_sb_reset(t->screen);
+        }
+}
+
+void terminal_kbd_add(Terminal *t, sd_gfx_kbd *kbd) {
+        sd_gfx_kbd_set_event_fn(kbd, terminal_kbd_fn);
+        sd_gfx_kbd_set_fn_data(kbd, t);
+}
+
+void terminal_kbd_remove(Terminal *t, sd_gfx_kbd *kbd) {
+        sd_gfx_kbd_set_event_fn(kbd, NULL);
+        sd_gfx_kbd_set_fn_data(kbd, NULL);
+}
+
+void terminal_from_pty(Terminal *t, const char *u8, size_t len) {
+        tsm_vte_input(t->vte, u8, len);
+        terminal_redraw(t);
+}
diff --git a/src/console/consoled.c b/src/console/consoled.c
new file mode 100644
index 0000000..b42b9aa
--- /dev/null
+++ b/src/console/consoled.c
@@ -0,0 +1,278 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2013 David Herrmann <dh.herrmann at gmail.com>
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <unistd.h>
+
+#include "build.h"
+#include "consoled.h"
+#include "def.h"
+#include "log.h"
+#include "macro.h"
+#include "missing.h"
+#include "sd-bus.h"
+#include "sd-daemon.h"
+#include "sd-event.h"
+#include "sd-gfx.h"
+#include "util.h"
+
+static void manager_card_fn(sd_gfx_card *card, void *fn_data, sd_gfx_card_event *ev) {
+        Manager *m = fn_data;
+        Terminal *t;
+
+        switch (ev->type) {
+        case SD_GFX_CARD_PIPE_CREATE:
+                /* add all pipes to default terminal */
+                t = m->terminal;
+                terminal_pipe_add(t, ev->pipe);
+                sd_gfx_pipe_set_fn_data(ev->pipe, t);
+                break;
+        case SD_GFX_CARD_PIPE_DESTROY:
+                t = sd_gfx_pipe_get_fn_data(ev->pipe);
+                terminal_pipe_remove(t, ev->pipe);
+                break;
+        case SD_GFX_CARD_PIPE_WAKE_UP:
+                t = sd_gfx_pipe_get_fn_data(ev->pipe);
+                terminal_pipe_wake_up(t, ev->pipe);
+                break;
+        case SD_GFX_CARD_PIPE_SLEEP:
+                t = sd_gfx_pipe_get_fn_data(ev->pipe);
+                terminal_pipe_sleep(t, ev->pipe);
+                break;
+        case SD_GFX_CARD_PIPE_SWAP:
+                t = sd_gfx_pipe_get_fn_data(ev->pipe);
+                terminal_pipe_swap(t, ev->pipe);
+                break;
+        }
+}
+
+static void manager_add_card(Manager *m, sd_gfx_card *card) {
+        sd_gfx_card_set_event_fn(card, manager_card_fn);
+        sd_gfx_card_set_fn_data(card, m);
+}
+
+static void manager_event_fn(sd_gfx_monitor *mon, void *fn_data, sd_gfx_monitor_event *ev) {
+        Manager *m = fn_data;
+
+        switch (ev->type) {
+        case SD_GFX_MONITOR_RUN:
+                terminal_start(m->terminal);
+                break;
+        case SD_GFX_MONITOR_CREATE:
+                switch (ev->devtype) {
+                case SD_GFX_DEV_CARD:
+                        manager_add_card(m, ev->card);
+                        break;
+                case SD_GFX_DEV_KBD:
+                        terminal_kbd_add(m->terminal, ev->kbd);
+                        break;
+                }
+                break;
+        case SD_GFX_MONITOR_DESTROY:
+                switch (ev->devtype) {
+                case SD_GFX_DEV_CARD:
+                        /* gets destroyed by gfx-monitor */
+                        break;
+                case SD_GFX_DEV_KBD:
+                        terminal_kbd_remove(m->terminal, ev->kbd);
+                        break;
+                }
+                break;
+        }
+}
+
+static int manager_signal_fn(sd_event_source *s, const struct signalfd_siginfo *ssi, void *data) {
+        Manager *m = data;
+
+        log_notice("catched signal %d, exiting..", (int)ssi->ssi_signo);
+        sd_event_request_quit(m->event);
+
+        return 0;
+}
+
+static int manager_new(Manager **out) {
+        static const int sigs[] = {
+                SIGINT, SIGTERM, SIGQUIT, SIGHUP, SIGPIPE, SIGCHLD, 0
+        };
+        unsigned int i;
+        sigset_t mask;
+        Manager *m;
+        int r;
+
+        m = calloc(1, sizeof(*m));
+        if (!m)
+                return log_oom();
+
+        r = sd_event_default(&m->event);
+        if (r < 0) {
+                log_error("cannot get default event-loop: %d", r);
+                goto error;
+        }
+
+        sigemptyset(&mask);
+        for (i = 0; sigs[i]; ++i) {
+                sigaddset(&mask, sigs[i]);
+                r = sd_event_add_signal(m->event,
+                                        sigs[i],
+                                        manager_signal_fn,
+                                        m,
+                                        &m->sigs[i]);
+                if (r < 0) {
+                        log_error("cannot block signal %d: %d",
+                                  sigs[i], r);
+                        goto error;
+                }
+        }
+        sigprocmask(SIG_BLOCK, &mask, NULL);
+
+        r = sd_gfx_monitor_new(&m->mon,
+                               SD_GFX_DEV_KBD | SD_GFX_DEV_CARD,
+                               SD_GFX_MONITOR_DEFAULT,
+                               m->event);
+        if (r < 0)
+                goto error;
+
+        sd_gfx_monitor_set_fn_data(m->mon, m);
+        sd_gfx_monitor_set_event_fn(m->mon, manager_event_fn);
+        sd_gfx_monitor_parse_cmdline(m->mon);
+
+        r = terminal_new(m, &m->terminal);
+        if (r < 0)
+                goto error;
+
+        *out = m;
+        return 0;
+
+error:
+        sd_gfx_monitor_free(m->mon);
+        for (i = 0; m->sigs[i]; ++i)
+                sd_event_source_unref(m->sigs[i]);
+        sd_event_unref(m->event);
+        free(m);
+        return r;
+}
+
+static void manager_free(Manager *m) {
+        unsigned int i;
+
+        if (!m)
+                return;
+
+        sd_gfx_monitor_free(m->mon);
+        terminal_free(m->terminal);
+        for (i = 0; m->sigs[i]; ++i)
+                sd_event_source_unref(m->sigs[i]);
+        sd_event_unref(m->event);
+        free(m);
+}
+
+static int manager_run(Manager *m) {
+        return sd_event_loop(m->event);
+}
+
+static int help(void) {
+        printf("%s [OPTIONS...] ...\n\n"
+               "System console with integrated terminal emulator.\n\n"
+               "  -h --help           Show this help\n"
+               "     --version        Show package version\n"
+               , program_invocation_short_name);
+
+        return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+        enum {
+                ARG_VERSION = 0x100,
+        };
+        static const struct option options[] = {
+                { "help",    no_argument,       NULL, 'h'         },
+                { "version", no_argument,       NULL, ARG_VERSION },
+                {}
+        };
+        int c;
+
+
+        assert(argc >= 0);
+        assert(argv);
+
+        while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) {
+                switch(c) {
+                case 'h':
+                        return help();
+                case ARG_VERSION:
+                        puts(PACKAGE_STRING);
+                        puts(SYSTEMD_FEATURES);
+                        return 0;
+                case '?':
+                        return -EINVAL;
+                default:
+                        assert_not_reached("Unhandled option");
+                }
+        }
+
+        if (optind < argc) {
+                log_error("This program does not take arguments.");
+                return -EINVAL;
+        }
+
+        return 1;
+}
+
+int main(int argc, char *argv[]) {
+        Manager *m = NULL;
+        int r;
+
+        log_set_target(LOG_TARGET_AUTO);
+        log_parse_environment();
+        log_open();
+
+        umask(0022);
+
+        r = parse_argv(argc, argv);
+        if (r < 0)
+                return EXIT_FAILURE;
+        if (r == 0)
+                return EXIT_SUCCESS;
+
+        r = manager_new(&m);
+        if (r < 0)
+                goto finish;
+
+        sd_notify(false, "READY=1\nSTATUS=Running...");
+
+        r = manager_run(m);
+
+finish:
+        sd_notify(false, "STATUS=Shutting down...");
+
+        if (m)
+                manager_free(m);
+
+        log_debug("exiting..");
+        return abs(r);
+}
diff --git a/src/console/consoled.h b/src/console/consoled.h
new file mode 100644
index 0000000..edc327d
--- /dev/null
+++ b/src/console/consoled.h
@@ -0,0 +1,128 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2013 David Herrmann <dh.herrmann at gmail.com>
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#pragma once
+
+#include <errno.h>
+#include <libtsm.h>
+#include <stdlib.h>
+
+#include "list.h"
+#include "ring.h"
+#include "sd-event.h"
+#include "sd-gfx.h"
+
+typedef struct Manager Manager;
+typedef struct Terminal Terminal;
+typedef struct Screen Screen;
+typedef struct Pty Pty;
+
+struct Manager {
+        sd_event *event;
+        sd_event_source *sigs[_NSIG];
+        sd_gfx_monitor *mon;
+
+        Terminal *terminal;
+};
+
+struct Terminal {
+        Manager *m;
+        sd_gfx_font *font;
+        struct tsm_screen *screen;
+        struct tsm_vte *vte;
+
+        Pty *pty;
+        sd_event_source *pty_source;
+
+        unsigned int min_width;
+        unsigned int min_height;
+        unsigned int cols;
+        unsigned int rows;
+        unsigned int cell_width;
+        unsigned int cell_height;
+
+        LIST_HEAD(Screen, screens);
+};
+
+struct Screen {
+        Terminal *t;
+        sd_gfx_pipe *pipe;
+        sd_gfx_plane *plane;
+
+        unsigned int width;
+        unsigned int height;
+
+        LIST_FIELDS(Screen, screen);
+
+        unsigned int active : 1;
+        unsigned int swapping : 1;
+        unsigned int need_redraw : 1;
+};
+
+#define PTY_BUFSIZE 16384
+
+struct Pty {
+        Terminal *t;
+        int fd;
+        sd_event_source *fd_source;
+        sd_event_source *idle_source;
+        pid_t child;
+        char in_buf[PTY_BUFSIZE];
+        Ring out_buf;
+};
+
+/* terminal */
+
+int terminal_new(Manager *m, Terminal **out);
+void terminal_free(Terminal *t);
+
+void terminal_start(Terminal *t);
+void terminal_redraw(Terminal *t);
+
+void terminal_pipe_add(Terminal *t, sd_gfx_pipe *pipe);
+void terminal_pipe_remove(Terminal *t, sd_gfx_pipe *pipe);
+void terminal_pipe_wake_up(Terminal *t, sd_gfx_pipe *pipe);
+void terminal_pipe_sleep(Terminal *t, sd_gfx_pipe *pipe);
+void terminal_pipe_swap(Terminal *t, sd_gfx_pipe *pipe);
+
+void terminal_kbd_add(Terminal *t, sd_gfx_kbd *kbd);
+void terminal_kbd_remove(Terminal *t, sd_gfx_kbd *kbd);
+
+void terminal_from_pty(Terminal *t, const char *u8, size_t len);
+
+/* screen */
+
+int screen_new(Terminal *t, sd_gfx_pipe *pipe, Screen **out);
+void screen_free(Screen *s);
+
+void screen_redraw(Screen *s);
+void screen_wake_up(Screen *s);
+void screen_sleep(Screen *s);
+void screen_swap(Screen *s);
+
+/* pty */
+
+pid_t pty_new(unsigned short term_width, unsigned short term_height, Terminal *t, Pty **out);
+void pty_free(Pty *pty);
+
+int pty_write(Pty *pty, const char *u8, size_t len);
+int pty_resize(Pty *pty, unsigned short term_width, unsigned short term_height);
+int pty_signal(Pty *pty, int sig);
-- 
1.8.4.2



More information about the systemd-devel mailing list