diff options
author | David Herrmann <dh.herrmann@gmail.com> | 2014-04-24 17:49:35 +0200 |
---|---|---|
committer | David Herrmann <dh.herrmann@gmail.com> | 2014-04-24 18:11:52 +0200 |
commit | 694b3158d0342698fb48678ee263acf3e2ffd4a5 (patch) | |
tree | d9b7aa0571f4ed63a456df3cb97f101ec780ebdb | |
parent | 4320f05168b47315383bd5439ff78202842cf37a (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.am | 5 | ||||
-rw-r--r-- | src/shared/shl-macro.h | 243 | ||||
-rw-r--r-- | src/shared/shl-pty.c | 552 | ||||
-rw-r--r-- | src/shared/shl-pty.h | 69 | ||||
-rw-r--r-- | src/shared/shl-ring.c | 188 | ||||
-rw-r--r-- | src/shared/shl-ring.h | 52 |
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 */ |