[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