summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Herrmann <dh.herrmann@gmail.com>2014-04-24 17:49:35 +0200
committerDavid Herrmann <dh.herrmann@gmail.com>2014-04-24 18:11:52 +0200
commit694b3158d0342698fb48678ee263acf3e2ffd4a5 (patch)
treed9b7aa0571f4ed63a456df3cb97f101ec780ebdb
parent4320f05168b47315383bd5439ff78202842cf37a (diff)
shared: add macro, pty and ring helpers
Few more shl helpers that we later need for TSM examples. Note that --gc-sections strips them from normal libtsm builds. Signed-off-by: David Herrmann <dh.herrmann@gmail.com>
-rw-r--r--Makefile.am5
-rw-r--r--src/shared/shl-macro.h243
-rw-r--r--src/shared/shl-pty.c552
-rw-r--r--src/shared/shl-pty.h69
-rw-r--r--src/shared/shl-ring.c188
-rw-r--r--src/shared/shl-ring.h52
6 files changed, 1108 insertions, 1 deletions
diff --git a/Makefile.am b/Makefile.am
index b9eb4d8..70d159b 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -108,7 +108,10 @@ libshl_la_SOURCES = \
src/shared/shl-array.h \
src/shared/shl-htable.h \
src/shared/shl-htable.c \
- src/shared/shl-llog.h
+ src/shared/shl-llog.h \
+ src/shared/shl-macro.h \
+ src/shared/shl-ring.h \
+ src/shared/shl-ring.c
libshl_la_CPPFLAGS = $(AM_CPPFLAGS)
libshl_la_LDFLAGS = $(AM_LDFLAGS)
libshl_la_LIBADD = $(AM_LIBADD)
diff --git a/src/shared/shl-macro.h b/src/shared/shl-macro.h
new file mode 100644
index 0000000..fa1c011
--- /dev/null
+++ b/src/shared/shl-macro.h
@@ -0,0 +1,243 @@
+/*
+ * SHL - Macros
+ *
+ * Copyright (c) 2011-2014 David Herrmann <dh.herrmann@gmail.com>
+ * Dedicated to the Public Domain
+ */
+
+/*
+ * Macros
+ */
+
+#ifndef SHL_MACRO_H
+#define SHL_MACRO_H
+
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/* sanity checks required for some macros */
+#if __SIZEOF_POINTER__ != 4 && __SIZEOF_POINTER__ != 8
+#error "Pointer size is neither 4 nor 8 bytes"
+#endif
+
+/* gcc attributes; look them up for more information */
+#define _shl_printf_(_a, _b) __attribute__((__format__(printf, _a, _b)))
+#define _shl_alloc_(...) __attribute__((__alloc_size__(__VA_ARGS__)))
+#define _shl_sentinel_ __attribute__((__sentinel__))
+#define _shl_noreturn_ __attribute__((__noreturn__))
+#define _shl_unused_ __attribute__((__unused__))
+#define _shl_pure_ __attribute__((__pure__))
+#define _shl_const_ __attribute__((__const__))
+#define _shl_deprecated_ __attribute__((__deprecated__))
+#define _shl_packed_ __attribute__((__packed__))
+#define _shl_malloc_ __attribute__((__malloc__))
+#define _shl_weak_ __attribute__((__weak__))
+#define _shl_likely_(_val) (__builtin_expect(!!(_val), 1))
+#define _shl_unlikely_(_val) (__builtin_expect(!!(_val), 0))
+#define _shl_public_ __attribute__((__visibility__("default")))
+#define _shl_hidden_ __attribute__((__visibility__("hidden")))
+#define _shl_weakref_(_val) __attribute__((__weakref__(#_val)))
+#define _shl_cleanup_(_val) __attribute__((__cleanup__(_val)))
+
+static inline void shl_freep(void *p)
+{
+ free(*(void**)p);
+}
+
+#define _shl_free_ _shl_cleanup_(shl_freep)
+
+static inline void shl_closep(int *p)
+{
+ if (*p >= 0)
+ close(*p);
+}
+
+#define _shl_close_ _shl_cleanup_(shl_closep)
+
+static inline void shl_set_errno(int *r)
+{
+ errno = *r;
+}
+
+#define SHL_PROTECT_ERRNO \
+ _shl_cleanup_(shl_set_errno) _shl_unused_ int shl__errno = errno
+
+/* 2-level stringify helper */
+#define SHL__STRINGIFY(_val) #_val
+#define SHL_STRINGIFY(_val) SHL__STRINGIFY(_val)
+
+/* 2-level concatenate helper */
+#define SHL__CONCATENATE(_a, _b) _a ## _b
+#define SHL_CONCATENATE(_a, _b) SHL__CONCATENATE(_a, _b)
+
+/* unique identifier with prefix */
+#define SHL_UNIQUE(_prefix) SHL_CONCATENATE(_prefix, __COUNTER__)
+
+/* array element count */
+#define SHL_ARRAY_LENGTH(_array) (sizeof(_array)/sizeof(*(_array)))
+
+/* get parent pointer by container-type, member and member-pointer */
+#define shl_container_of(_ptr, _type, _member) \
+ ({ \
+ const typeof( ((_type *)0)->_member ) *__mptr = (_ptr); \
+ (_type *)( (char *)__mptr - offsetof(_type, _member) ); \
+ })
+
+/* return maximum of two values and do strict type checking */
+#define shl_max(_a, _b) \
+ ({ \
+ typeof(_a) __a = (_a); \
+ typeof(_b) __b = (_b); \
+ (void) (&__a == &__b); \
+ __a > __b ? __a : __b; \
+ })
+
+/* same as shl_max() but perform explicit cast beforehand */
+#define shl_max_t(_type, _a, _b) \
+ ({ \
+ _type __a = (_type)(_a); \
+ _type __b = (_type)(_b); \
+ __a > __b ? __a : __b; \
+ })
+
+/* return minimum of two values and do strict type checking */
+#define shl_min(_a, _b) \
+ ({ \
+ typeof(_a) __a = (_a); \
+ typeof(_b) __b = (_b); \
+ (void) (&__a == &__b); \
+ __a < __b ? __a : __b; \
+ })
+
+/* same as shl_min() but perform explicit cast beforehand */
+#define shl_min_t(_type, _a, _b) \
+ ({ \
+ _type __a = (_type)(_a); \
+ _type __b = (_type)(_b); \
+ __a < __b ? __a : __b; \
+ })
+
+/* clamp value between low and high barriers */
+#define shl_clamp(_val, _low, _high) \
+ ({ \
+ typeof(_val) __v = (_val); \
+ typeof(_low) __l = (_low); \
+ typeof(_high) __h = (_high); \
+ (void) (&__v == &__l); \
+ (void) (&__v == &__h); \
+ ((__v > __h) ? __h : ((__v < __l) ? __l : __v)); \
+ })
+
+/* align to next higher power-of-2 (except for: 0 => 0, overflow => 0) */
+static inline size_t SHL_ALIGN_POWER2(size_t u)
+{
+ return 1ULL << ((sizeof(u) * 8ULL) - __builtin_clzll(u - 1ULL));
+}
+
+/* zero memory or type */
+#define shl_memzero(_ptr, _size) (memset((_ptr), 0, (_size)))
+#define shl_zero(_ptr) (shl_memzero(&(_ptr), sizeof(_ptr)))
+
+/* ptr <=> uint casts */
+#define SHL_PTR_TO_TYPE(_type, _ptr) ((_type)((uintptr_t)(_ptr)))
+#define SHL_TYPE_TO_PTR(_type, _int) ((void*)((uintptr_t)(_int)))
+#define SHL_PTR_TO_INT(_ptr) SHL_PTR_TO_TYPE(int, (_ptr))
+#define SHL_INT_TO_PTR(_ptr) SHL_TYPE_TO_PTR(int, (_ptr))
+#define SHL_PTR_TO_UINT(_ptr) SHL_PTR_TO_TYPE(unsigned int, (_ptr))
+#define SHL_UINT_TO_PTR(_ptr) SHL_TYPE_TO_PTR(unsigned int, (_ptr))
+#define SHL_PTR_TO_LONG(_ptr) SHL_PTR_TO_TYPE(long, (_ptr))
+#define SHL_LONG_TO_PTR(_ptr) SHL_TYPE_TO_PTR(long, (_ptr))
+#define SHL_PTR_TO_ULONG(_ptr) SHL_PTR_TO_TYPE(unsigned long, (_ptr))
+#define SHL_ULONG_TO_PTR(_ptr) SHL_TYPE_TO_PTR(unsigned long, (_ptr))
+#define SHL_PTR_TO_S32(_ptr) SHL_PTR_TO_TYPE(int32_t, (_ptr))
+#define SHL_S32_TO_PTR(_ptr) SHL_TYPE_TO_PTR(int32_t, (_ptr))
+#define SHL_PTR_TO_U32(_ptr) SHL_PTR_TO_TYPE(uint32_t, (_ptr))
+#define SHL_U32_TO_PTR(_ptr) SHL_TYPE_TO_PTR(uint32_t, (_ptr))
+#define SHL_PTR_TO_S64(_ptr) SHL_PTR_TO_TYPE(int64_t, (_ptr))
+#define SHL_S64_TO_PTR(_ptr) SHL_TYPE_TO_PTR(int64_t, (_ptr))
+#define SHL_PTR_TO_U64(_ptr) SHL_PTR_TO_TYPE(uint64_t, (_ptr))
+#define SHL_U64_TO_PTR(_ptr) SHL_TYPE_TO_PTR(uint64_t, (_ptr))
+
+/* compile-time assertions */
+#define shl_assert_cc(_expr) static_assert(_expr, #_expr)
+
+/*
+ * Safe Multiplications
+ * Multiplications are subject to overflows. These helpers guarantee that the
+ * multiplication can be done safely and return -ERANGE if not.
+ *
+ * Note: This is horribly slow for ull/uint64_t as we need a division to test
+ * for overflows. Take that into account when using these. For smaller integers,
+ * we can simply use an upcast-multiplication which gcc should be smart enough
+ * to optimize.
+ */
+
+#define SHL__REAL_MULT(_max, _val, _factor) \
+ ({ \
+ (_factor == 0 || *(_val) <= (_max) / (_factor)) ? \
+ ((*(_val) *= (_factor)), 0) : \
+ -ERANGE; \
+ })
+
+#define SHL__UPCAST_MULT(_type, _max, _val, _factor) \
+ ({ \
+ _type v = *(_val) * (_type)(_factor); \
+ (v <= (_max)) ? \
+ ((*(_val) = v), 0) : \
+ -ERANGE; \
+ })
+
+static inline int shl_mult_ull(unsigned long long *val,
+ unsigned long long factor)
+{
+ return SHL__REAL_MULT(ULLONG_MAX, val, factor);
+}
+
+static inline int shl_mult_ul(unsigned long *val, unsigned long factor)
+{
+#if ULONG_MAX < ULLONG_MAX
+ return SHL__UPCAST_MULT(unsigned long long, ULONG_MAX, val, factor);
+#else
+ shl_assert_cc(sizeof(unsigned long) == sizeof(unsigned long long));
+ return shl_mult_ull((unsigned long long*)val, factor);
+#endif
+}
+
+static inline int shl_mult_u(unsigned int *val, unsigned int factor)
+{
+#if UINT_MAX < ULONG_MAX
+ return SHL__UPCAST_MULT(unsigned long, UINT_MAX, val, factor);
+#elif UINT_MAX < ULLONG_MAX
+ return SHL__UPCAST_MULT(unsigned long long, UINT_MAX, val, factor);
+#else
+ shl_assert_cc(sizeof(unsigned int) == sizeof(unsigned long long));
+ return shl_mult_ull(val, factor);
+#endif
+}
+
+static inline int shl_mult_u64(uint64_t *val, uint64_t factor)
+{
+ return SHL__REAL_MULT(UINT64_MAX, val, factor);
+}
+
+static inline int shl_mult_u32(uint32_t *val, uint32_t factor)
+{
+ return SHL__UPCAST_MULT(uint_fast64_t, UINT32_MAX, val, factor);
+}
+
+static inline int shl_mult_u16(uint16_t *val, uint16_t factor)
+{
+ return SHL__UPCAST_MULT(uint_fast32_t, UINT16_MAX, val, factor);
+}
+
+static inline int shl_mult_u8(uint8_t *val, uint8_t factor)
+{
+ return SHL__UPCAST_MULT(uint_fast16_t, UINT8_MAX, val, factor);
+}
+
+#endif /* SHL_MACRO_H */
diff --git a/src/shared/shl-pty.c b/src/shared/shl-pty.c
new file mode 100644
index 0000000..5dfe977
--- /dev/null
+++ b/src/shared/shl-pty.c
@@ -0,0 +1,552 @@
+/*
+ * SHL - PTY Helpers
+ *
+ * Copyright (c) 2011-2014 David Herrmann <dh.herrmann@gmail.com>
+ * Dedicated to the Public Domain
+ */
+
+/*
+ * PTY Helpers
+ */
+
+#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 "shl-macro.h"
+#include "shl-pty.h"
+#include "shl-ring.h"
+
+#define SHL_PTY_BUFSIZE 16384
+
+/*
+ * 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. This has to be taken into account by
+ * the user of shl_pty. As many event-loops don't support edge-triggered
+ * behavior, you can use the shl_pty_bridge interface.
+ *
+ * Note that shl_pty does not track SIGHUP, you need to do that yourself
+ * and call shl_pty_close() once the client exited.
+ */
+
+struct shl_pty {
+ unsigned long ref;
+ int fd;
+ pid_t child;
+ char in_buf[SHL_PTY_BUFSIZE];
+ struct shl_ring out_buf;
+
+ shl_pty_input_fn fn_input;
+ void *fn_input_data;
+};
+
+enum shl_pty_msg {
+ SHL_PTY_FAILED,
+ SHL_PTY_SETUP,
+};
+
+static char pty_recv(int fd)
+{
+ int r;
+ char d;
+
+ do {
+ r = read(fd, &d, 1);
+ } while (r < 0 && (errno == EINTR || errno == EAGAIN));
+
+ return (r <= 0) ? SHL_PTY_FAILED : d;
+}
+
+static int pty_send(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;
+
+ /* get terminal attributes */
+ if (tcgetattr(slave, &attr) < 0)
+ return -errno;
+
+ /* erase character should be normal backspace, PLEASEEE! */
+ attr.c_cc[VERASE] = 010;
+ /* always set UTF8 flag */
+ attr.c_iflag |= IUTF8;
+
+ /* set changed terminal attributes */
+ 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 shl_pty_open(struct shl_pty **out,
+ shl_pty_input_fn fn_input,
+ void *fn_input_data,
+ unsigned short term_width,
+ unsigned short term_height)
+{
+ _shl_pty_unref_ struct shl_pty *pty = NULL;
+ _shl_close_ int fd = -1;
+ int slave, r, comm[2];
+ pid_t pid;
+ char d;
+
+ if (!out)
+ return -EINVAL;
+
+ pty = calloc(1, sizeof(*pty));
+ if (!pty)
+ return -ENOMEM;
+
+ pty->ref = 1;
+ pty->fd = -1;
+ pty->fn_input = fn_input;
+ pty->fn_input_data = fn_input_data;
+
+ fd = posix_openpt(O_RDWR | O_NOCTTY | O_CLOEXEC | O_NONBLOCK);
+ if (fd < 0)
+ return -errno;
+
+ r = pipe2(comm, O_CLOEXEC);
+ if (r < 0)
+ return -errno;
+
+ pid = fork();
+ if (pid < 0) {
+ /* error */
+ pid = -errno;
+ close(comm[0]);
+ close(comm[1]);
+ return pid;
+ } else if (!pid) {
+ /* child */
+ slave = pty_init_child(fd);
+ if (slave < 0)
+ exit(1);
+
+ close(comm[0]);
+ close(fd);
+ fd = -1;
+ free(pty);
+ pty = NULL;
+
+ 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_send(comm[1], SHL_PTY_SETUP);
+ close(comm[1]);
+
+ *out = NULL;
+ return pid;
+ }
+
+ /* parent */
+ pty->fd = fd;
+ pty->child = pid;
+
+ close(comm[1]);
+ fd = -1;
+
+ /* wait for child setup */
+ d = pty_recv(comm[0]);
+ close(comm[0]);
+ if (d != SHL_PTY_SETUP)
+ return -EINVAL;
+
+ *out = pty;
+ pty = NULL;
+ return pid;
+}
+
+void shl_pty_ref(struct shl_pty *pty)
+{
+ if (!pty || !pty->ref)
+ return;
+
+ ++pty->ref;
+}
+
+void shl_pty_unref(struct shl_pty *pty)
+{
+ if (!pty || !pty->ref || --pty->ref)
+ return;
+
+ shl_pty_close(pty);
+ shl_ring_clear(&pty->out_buf);
+ free(pty);
+}
+
+void shl_pty_close(struct shl_pty *pty)
+{
+ if (!pty || pty->fd < 0)
+ return;
+
+ close(pty->fd);
+ pty->fd = -1;
+}
+
+bool shl_pty_is_open(struct shl_pty *pty)
+{
+ return pty && pty->fd >= 0;
+}
+
+int shl_pty_get_fd(struct shl_pty *pty)
+{
+ if (!pty)
+ return -EINVAL;
+
+ return pty->fd >= 0 ? pty->fd : -EPIPE;
+}
+
+pid_t shl_pty_get_child(struct shl_pty *pty)
+{
+ if (!pty)
+ return -EINVAL;
+
+ return pty->child > 0 ? pty->child : -ECHILD;
+}
+
+static int pty_write(struct shl_pty *pty)
+{
+ struct iovec vec[2];
+ unsigned int i;
+ size_t num;
+ ssize_t r;
+
+ /*
+ * Same as pty_read(), we're edge-triggered so we need to call write()
+ * until either all data is written or it return EAGAIN. We call it
+ * twice and if it still writes successfully, we return EAGAIN. If we
+ * bail out early, we also return EAGAIN if there's still data.
+ */
+
+ for (i = 0; i < 2; ++i) {
+ num = shl_ring_peek(&pty->out_buf, vec);
+ if (!num)
+ return 0;
+
+ r = writev(pty->fd, vec, (int)num);
+ if (r < 0) {
+ if (errno == EAGAIN)
+ return 0;
+ if (errno == EINTR)
+ return -EAGAIN;
+
+ return -errno;
+ } else if (!r) {
+ return -EPIPE;
+ } else {
+ shl_ring_pull(&pty->out_buf, (size_t)r);
+ }
+ }
+
+ return shl_ring_get_size(&pty->out_buf) > 0 ? -EAGAIN : 0;
+}
+
+static int pty_read(struct shl_pty *pty)
+{
+ unsigned int i;
+ ssize_t len;
+
+ /*
+ * 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 read twice and if the second read still returned
+ * data, we return -EAGAIN and let the caller deal with rescheduling the
+ * dispatcher.
+ */
+
+ for (i = 0; i < 2; ++i) {
+ len = read(pty->fd, pty->in_buf, sizeof(pty->in_buf) - 1);
+ if (len < 0) {
+ if (errno == EAGAIN)
+ return 0;
+ if (errno == EINTR)
+ return -EAGAIN;
+
+ return -errno;
+ } else if (!len) {
+ return -EPIPE;
+ } else if (len > 0 && pty->fn_input) {
+ /* set terminating zero for debugging safety */
+ pty->in_buf[len] = 0;
+ pty->fn_input(pty,
+ pty->fn_input_data,
+ pty->in_buf,
+ len);
+ }
+ }
+
+ return -EAGAIN;
+}
+
+int shl_pty_dispatch(struct shl_pty *pty)
+{
+ int r;
+
+ if (!shl_pty_is_open(pty))
+ return -ENODEV;
+
+ r = pty_read(pty);
+ pty_write(pty);
+ return r;
+}
+
+int shl_pty_write(struct shl_pty *pty, const char *u8, size_t len)
+{
+ if (!shl_pty_is_open(pty))
+ return -ENODEV;
+
+ return shl_ring_push(&pty->out_buf, u8, len);
+}
+
+int shl_pty_signal(struct shl_pty *pty, int sig)
+{
+ if (!shl_pty_is_open(pty))
+ return -ENODEV;
+
+ return ioctl(pty->fd, TIOCSIG, sig) < 0 ? -errno : 0;
+}
+
+int shl_pty_resize(struct shl_pty *pty,
+ unsigned short term_width,
+ unsigned short term_height)
+{
+ struct winsize ws;
+
+ if (!shl_pty_is_open(pty))
+ return -ENODEV;
+
+ 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.
+ */
+ return ioctl(pty->fd, TIOCSWINSZ, &ws) < 0 ? -errno : 0;
+}
+
+/*
+ * PTY Bridge
+ * The PTY bridge wraps multiple ptys in a single file-descriptor. It is
+ * enough for the caller to listen for read-events on the fd.
+ *
+ * This interface is provided to allow integration of PTYs into event-loops
+ * that do not support edge-triggered interfaces. There is no other reason
+ * to use this bridge.
+ */
+
+int shl_pty_bridge_new(void)
+{
+ int fd;
+
+ fd = epoll_create1(EPOLL_CLOEXEC);
+ if (fd < 0)
+ return -errno;
+
+ return fd;
+}
+
+void shl_pty_bridge_free(int bridge)
+{
+ if (bridge < 0)
+ return;
+
+ close(bridge);
+}
+
+int shl_pty_bridge_dispatch_pty(int bridge, struct shl_pty *pty)
+{
+ struct epoll_event up;
+ int r;
+
+ if (bridge < 0 || !pty)
+ return -EINVAL;
+
+ r = shl_pty_dispatch(pty);
+ if (r == -EAGAIN) {
+ /* EAGAIN means we couldn't dispatch data fast enough. Modify
+ * the fd in the epoll-set so we get edge-triggered events
+ * next round. */
+ memset(&up, 0, sizeof(up));
+ up.events = EPOLLHUP | EPOLLERR | EPOLLIN | EPOLLOUT | EPOLLET;
+ up.data.ptr = pty;
+ epoll_ctl(bridge,
+ EPOLL_CTL_ADD,
+ shl_pty_get_fd(pty),
+ &up);
+ }
+
+ return 0;
+}
+
+int shl_pty_bridge_dispatch(int bridge, int timeout)
+{
+ struct epoll_event ev;
+ struct shl_pty *pty;
+ int r;
+
+ if (bridge < 0)
+ return -EINVAL;
+
+ r = epoll_wait(bridge, &ev, 1, timeout);
+ if (r < 0) {
+ if (errno == EAGAIN || errno == EINTR)
+ return 0;
+
+ return -errno;
+ }
+
+ if (!r)
+ return 0;
+
+ pty = ev.data.ptr;
+ return shl_pty_bridge_dispatch_pty(bridge, pty);
+}
+
+int shl_pty_bridge_add(int bridge, struct shl_pty *pty)
+{
+ struct epoll_event ev;
+ int r;
+
+ if (bridge < 0)
+ return -EINVAL;
+ if (!shl_pty_is_open(pty))
+ return -ENODEV;
+
+ memset(&ev, 0, sizeof(ev));
+ ev.events = EPOLLHUP | EPOLLERR | EPOLLIN | EPOLLOUT | EPOLLET;
+ ev.data.ptr = pty;
+
+ r = epoll_ctl(bridge,
+ EPOLL_CTL_ADD,
+ shl_pty_get_fd(pty),
+ &ev);
+ if (r < 0)
+ return -errno;
+
+ return 0;
+}
+
+void shl_pty_bridge_remove(int bridge, struct shl_pty *pty)
+{
+ if (bridge < 0 || !shl_pty_is_open(pty))
+ return;
+
+ epoll_ctl(bridge,
+ EPOLL_CTL_DEL,
+ shl_pty_get_fd(pty),
+ NULL);
+}
diff --git a/src/shared/shl-pty.h b/src/shared/shl-pty.h
new file mode 100644
index 0000000..e5e4ed2
--- /dev/null
+++ b/src/shared/shl-pty.h
@@ -0,0 +1,69 @@
+/*
+ * SHL - PTY Helpers
+ *
+ * Copyright (c) 2011-2014 David Herrmann <dh.herrmann@gmail.com>
+ * Dedicated to the Public Domain
+ */
+
+/*
+ * PTY Helpers
+ */
+
+#ifndef SHL_PTY_H
+#define SHL_PTY_H
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "shl-macro.h"
+
+/* pty */
+
+struct shl_pty;
+
+typedef void (*shl_pty_input_fn) (struct shl_pty *pty,
+ void *data,
+ char *u8,
+ size_t len);
+
+pid_t shl_pty_open(struct shl_pty **out,
+ shl_pty_input_fn fn_input,
+ void *fn_input_data,
+ unsigned short term_width,
+ unsigned short term_height);
+void shl_pty_ref(struct shl_pty *pty);
+void shl_pty_unref(struct shl_pty *pty);
+void shl_pty_close(struct shl_pty *pty);
+
+static inline void shl_pty_unref_p(struct shl_pty **pty)
+{
+ shl_pty_unref(*pty);
+}
+
+#define _shl_pty_unref_ _shl_cleanup_(shl_pty_unref_p)
+
+bool shl_pty_is_open(struct shl_pty *pty);
+int shl_pty_get_fd(struct shl_pty *pty);
+pid_t shl_pty_get_child(struct shl_pty *pty);
+
+int shl_pty_dispatch(struct shl_pty *pty);
+int shl_pty_write(struct shl_pty *pty, const char *u8, size_t len);
+int shl_pty_signal(struct shl_pty *pty, int sig);
+int shl_pty_resize(struct shl_pty *pty,
+ unsigned short term_width,
+ unsigned short term_height);
+
+/* pty bridge */
+
+int shl_pty_bridge_new(void);
+void shl_pty_bridge_free(int bridge);
+
+int shl_pty_bridge_dispatch_pty(int bridge, struct shl_pty *pty);
+int shl_pty_bridge_dispatch(int bridge, int timeout);
+int shl_pty_bridge_add(int bridge, struct shl_pty *pty);
+void shl_pty_bridge_remove(int bridge, struct shl_pty *pty);
+
+#endif /* SHL_PTY_H */
diff --git a/src/shared/shl-ring.c b/src/shared/shl-ring.c
new file mode 100644
index 0000000..2d72e97
--- /dev/null
+++ b/src/shared/shl-ring.c
@@ -0,0 +1,188 @@
+/*
+ * SHL - Ring buffer
+ *
+ * Copyright (c) 2011-2014 David Herrmann <dh.herrmann@gmail.com>
+ * Dedicated to the Public Domain
+ */
+
+/*
+ * Ring buffer
+ */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/uio.h>
+#include "shl-macro.h"
+#include "shl-ring.h"
+
+#define RING_MASK(_r, _v) ((_v) & ((_r)->size - 1))
+
+void shl_ring_flush(struct shl_ring *r)
+{
+ r->start = 0;
+ r->used = 0;
+}
+
+void shl_ring_clear(struct shl_ring *r)
+{
+ free(r->buf);
+ memset(r, 0, sizeof(*r));
+}
+
+/*
+ * Get data pointers for current ring-buffer data. @vec must be an array of 2
+ * iovec objects. They are filled according to the data available in the
+ * ring-buffer. 0, 1 or 2 is returned according to the number of iovec objects
+ * that were filled (0 meaning buffer is empty).
+ *
+ * Hint: "struct iovec" is defined in <sys/uio.h> and looks like this:
+ * struct iovec {
+ * void *iov_base;
+ * size_t iov_len;
+ * };
+ */
+size_t shl_ring_peek(struct shl_ring *r, struct iovec *vec)
+{
+ if (r->used == 0) {
+ return 0;
+ } else if (r->start + r->used <= r->size) {
+ if (vec) {
+ vec[0].iov_base = &r->buf[r->start];
+ vec[0].iov_len = r->used;
+ }
+ return 1;
+ } else {
+ if (vec) {
+ vec[0].iov_base = &r->buf[r->start];
+ vec[0].iov_len = r->size - r->start;
+ vec[1].iov_base = r->buf;
+ vec[1].iov_len = r->used - (r->size - r->start);
+ }
+ return 2;
+ }
+}
+
+/*
+ * Copy data from the ring buffer into the linear external buffer @buf. Copy
+ * at most @size bytes. If the ring buffer size is smaller, copy less bytes and
+ * return the number of bytes copied.
+ */
+size_t shl_ring_copy(struct shl_ring *r, void *buf, size_t size)
+{
+ size_t l;
+
+ if (size > r->used)
+ size = r->used;
+
+ if (size > 0) {
+ l = r->size - r->start;
+ if (size <= l) {
+ memcpy(buf, &r->buf[r->start], size);
+ } else {
+ memcpy(buf, &r->buf[r->start], l);
+ memcpy((uint8_t*)buf + l, r->buf, size - l);
+ }
+ }
+
+ return size;
+}
+
+/*
+ * Resize ring-buffer to size @nsize. @nsize must be a power-of-2, otherwise
+ * ring operations will behave incorrectly.
+ */
+static int ring_resize(struct shl_ring *r, size_t nsize)
+{
+ uint8_t *buf;
+ size_t l;
+
+ buf = malloc(nsize);
+ if (!buf)
+ return -ENOMEM;
+
+ if (r->used > 0) {
+ l = r->size - r->start;
+ if (r->used <= l) {
+ memcpy(buf, &r->buf[r->start], r->used);
+ } else {
+ memcpy(buf, &r->buf[r->start], l);
+ memcpy(&buf[l], r->buf, r->used - l);
+ }
+ }
+
+ free(r->buf);
+ r->buf = buf;
+ r->size = nsize;
+ r->start = 0;
+
+ return 0;
+}
+
+/*
+ * Resize ring-buffer to provide enough room for @add bytes of new data. This
+ * resizes the buffer if it is too small. It returns -ENOMEM on OOM and 0 on
+ * success.
+ */
+static int ring_grow(struct shl_ring *r, size_t add)
+{
+ size_t need;
+
+ if (r->size - r->used >= add)
+ return 0;
+
+ need = r->used + add;
+ if (need <= r->used)
+ return -ENOMEM;
+ else if (need < 4096)
+ need = 4096;
+
+ need = SHL_ALIGN_POWER2(need);
+ if (need == 0)
+ return -ENOMEM;
+
+ return ring_resize(r, need);
+}
+
+/*
+ * Push @len bytes from @u8 into the ring buffer. The buffer is resized if it
+ * is too small. -ENOMEM is returned on OOM, 0 on success.
+ */
+int shl_ring_push(struct shl_ring *r, const void *u8, size_t size)
+{
+ int err;
+ size_t pos, l;
+
+ if (size == 0)
+ return 0;
+
+ err = ring_grow(r, size);
+ if (err < 0)
+ return err;
+
+ pos = RING_MASK(r, r->start + r->used);
+ l = r->size - pos;
+ if (l >= size) {
+ memcpy(&r->buf[pos], u8, size);
+ } else {
+ memcpy(&r->buf[pos], u8, l);
+ memcpy(r->buf, (const uint8_t*)u8 + l, size - l);
+ }
+
+ r->used += size;
+
+ return 0;
+}
+
+/*
+ * Remove @len bytes from the start of the ring-buffer. Note that we protect
+ * against overflows so removing more bytes than available is safe.
+ */
+void shl_ring_pull(struct shl_ring *r, size_t size)
+{
+ if (size > r->used)
+ size = r->used;
+
+ r->start = RING_MASK(r, r->start + size);
+ r->used -= size;
+}
diff --git a/src/shared/shl-ring.h b/src/shared/shl-ring.h
new file mode 100644
index 0000000..c98f084
--- /dev/null
+++ b/src/shared/shl-ring.h
@@ -0,0 +1,52 @@
+/*
+ * SHL - Ring buffer
+ *
+ * Copyright (c) 2011-2014 David Herrmann <dh.herrmann@gmail.com>
+ * Dedicated to the Public Domain
+ */
+
+/*
+ * Ring buffer
+ */
+
+#ifndef SHL_RING_H
+#define SHL_RING_H
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/uio.h>
+
+struct shl_ring {
+ uint8_t *buf; /* buffer or NULL */
+ size_t size; /* actual size of @buf */
+ size_t start; /* start position of ring */
+ size_t used; /* number of actually used bytes */
+};
+
+/* flush buffer so it is empty again */
+void shl_ring_flush(struct shl_ring *r);
+
+/* flush buffer, free allocated data and reset to initial state */
+void shl_ring_clear(struct shl_ring *r);
+
+/* get pointers to buffer data and their length */
+size_t shl_ring_peek(struct shl_ring *r, struct iovec *vec);
+
+/* copy data into external linear buffer */
+size_t shl_ring_copy(struct shl_ring *r, void *buf, size_t size);
+
+/* push data to the end of the buffer */
+int shl_ring_push(struct shl_ring *r, const void *u8, size_t size);
+
+/* pull data from the front of the buffer */
+void shl_ring_pull(struct shl_ring *r, size_t size);
+
+/* return size of occupied buffer in bytes */
+static inline size_t shl_ring_get_size(struct shl_ring *r)
+{
+ return r->used;
+}
+
+#endif /* SHL_RING_H */