diff options
-rw-r--r-- | Makefile.am | 18 | ||||
-rw-r--r-- | src/basic/ring.c | 209 | ||||
-rw-r--r-- | src/basic/ring.h | 55 | ||||
-rw-r--r-- | src/shared/pty.c | 633 | ||||
-rw-r--r-- | src/shared/pty.h | 72 | ||||
-rw-r--r-- | src/test/test-pty.c | 142 | ||||
-rw-r--r-- | src/test/test-ring.c | 130 |
7 files changed, 0 insertions, 1259 deletions
diff --git a/Makefile.am b/Makefile.am index 0583742232..c395840759 100644 --- a/Makefile.am +++ b/Makefile.am @@ -865,8 +865,6 @@ libbasic_la_SOURCES = \ src/basic/xml.h \ src/basic/json.c \ src/basic/json.h \ - src/basic/ring.c \ - src/basic/ring.h \ src/basic/barrier.c \ src/basic/barrier.h \ src/basic/async.c \ @@ -954,8 +952,6 @@ libshared_la_SOURCES = \ src/shared/ima-util.h \ src/shared/ptyfwd.c \ src/shared/ptyfwd.h \ - src/shared/pty.c \ - src/shared/pty.h \ src/shared/base-filesystem.c \ src/shared/base-filesystem.h \ src/shared/uid-range.c \ @@ -1402,9 +1398,7 @@ tests += \ test-process-util \ test-terminal-util \ test-path-lookup \ - test-ring \ test-barrier \ - test-pty \ test-tmpfiles \ test-namespace \ test-date \ @@ -1721,24 +1715,12 @@ test_socket_util_SOURCES = \ test_socket_util_LDADD = \ libshared.la -test_ring_SOURCES = \ - src/test/test-ring.c - -test_ring_LDADD = \ - libshared.la - test_barrier_SOURCES = \ src/test/test-barrier.c test_barrier_LDADD = \ libshared.la -test_pty_SOURCES = \ - src/test/test-pty.c - -test_pty_LDADD = \ - libcore.la - test_tmpfiles_SOURCES = \ src/test/test-tmpfiles.c diff --git a/src/basic/ring.c b/src/basic/ring.c deleted file mode 100644 index 6814918464..0000000000 --- a/src/basic/ring.c +++ /dev/null @@ -1,209 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2014 David Herrmann <dh.herrmann@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 <stdlib.h> -#include <string.h> -#include <sys/uio.h> -#include "macro.h" -#include "ring.h" - -#define RING_MASK(_r, _v) ((_v) & ((_r)->size - 1)) - -void ring_flush(Ring *r) { - assert(r); - - r->start = 0; - r->used = 0; -} - -void ring_clear(Ring *r) { - assert(r); - - free(r->buf); - zero(*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 ring_peek(Ring *r, struct iovec *vec) { - assert(r); - - 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 ring_copy(Ring *r, void *buf, size_t size) { - size_t l; - - assert(r); - assert(buf); - - 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(Ring *r, size_t nsize) { - uint8_t *buf; - size_t l; - - assert(r); - assert(nsize > 0); - - 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(Ring *r, size_t add) { - size_t need; - - assert(r); - - 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 = 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 ring_push(Ring *r, const void *u8, size_t size) { - int err; - size_t pos, l; - - assert(r); - assert(u8); - - 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 ring_pull(Ring *r, size_t size) { - assert(r); - - if (size > r->used) - size = r->used; - - r->start = RING_MASK(r, r->start + size); - r->used -= size; -} diff --git a/src/basic/ring.h b/src/basic/ring.h deleted file mode 100644 index dbd6296384..0000000000 --- a/src/basic/ring.h +++ /dev/null @@ -1,55 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 David Herrmann <dh.herrmann@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/>. -***/ - - -typedef struct Ring Ring; - -struct 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 ring_flush(Ring *r); - -/* flush buffer, free allocated data and reset to initial state */ -void ring_clear(Ring *r); - -/* get pointers to buffer data and their length */ -size_t ring_peek(Ring *r, struct iovec *vec); - -/* copy data into external linear buffer */ -size_t ring_copy(Ring *r, void *buf, size_t size); - -/* push data to the end of the buffer */ -int ring_push(Ring *r, const void *u8, size_t size); - -/* pull data from the front of the buffer */ -void ring_pull(Ring *r, size_t size); - -/* return size of occupied buffer in bytes */ -static inline size_t ring_get_size(Ring *r) { - return r->used; -} diff --git a/src/shared/pty.c b/src/shared/pty.c deleted file mode 100644 index 35d9ff5f4d..0000000000 --- a/src/shared/pty.c +++ /dev/null @@ -1,633 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2014 David Herrmann <dh.herrmann@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/>. -***/ - -/* - * 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. - */ - -#include <errno.h> -#include <fcntl.h> -#include <signal.h> -#include <stdbool.h> -#include <stdint.h> -#include <stdlib.h> -#include <sys/epoll.h> -#include <sys/ioctl.h> -#include <sys/uio.h> -#include <sys/wait.h> -#include <termios.h> -#include <unistd.h> - -#include "barrier.h" -#include "macro.h" -#include "ring.h" -#include "util.h" -#include "signal-util.h" -#include "pty.h" - -#define PTY_BUFSIZE 4096 - -enum { - PTY_ROLE_UNKNOWN, - PTY_ROLE_PARENT, - PTY_ROLE_CHILD, -}; - -struct Pty { - unsigned long ref; - Barrier barrier; - int fd; - pid_t child; - sd_event_source *fd_source; - sd_event_source *child_source; - - char in_buf[PTY_BUFSIZE]; - Ring out_buf; - - pty_event_t event_fn; - void *event_fn_userdata; - - bool needs_requeue : 1; - unsigned int role : 2; -}; - -int pty_new(Pty **out) { - _pty_unref_ Pty *pty = NULL; - int r; - - assert_return(out, -EINVAL); - - pty = new0(Pty, 1); - if (!pty) - return -ENOMEM; - - pty->ref = 1; - pty->fd = -1; - pty->barrier = (Barrier) BARRIER_NULL; - - pty->fd = posix_openpt(O_RDWR | O_NOCTTY | O_CLOEXEC | O_NONBLOCK); - if (pty->fd < 0) - return -errno; - - /* - * The slave-node is initialized to uid/gid of the caller of - * posix_openpt(). Only if devpts is mounted with fixed uid/gid this is - * skipped. In that case, grantpt() can overwrite these, but then you - * have to be root to use chown() (or a pt_chown helper has to be - * present). In those cases grantpt() really does something, - * otherwise it's a no-op. We call grantpt() here to try supporting - * those cases, even though no-one uses that, I guess. If you need other - * access-rights, set them yourself after this call returns (no, this is - * not racy, it looks racy, but races regarding your own UID are never - * important as an attacker could ptrace you; and the slave-pty is also - * still locked). - */ - r = grantpt(pty->fd); - if (r < 0) - return -errno; - - r = barrier_create(&pty->barrier); - if (r < 0) - return r; - - *out = pty; - pty = NULL; - return 0; -} - -Pty *pty_ref(Pty *pty) { - if (!pty || pty->ref < 1) - return NULL; - - ++pty->ref; - return pty; -} - -Pty *pty_unref(Pty *pty) { - if (!pty || pty->ref < 1 || --pty->ref > 0) - return NULL; - - pty_close(pty); - pty->child_source = sd_event_source_unref(pty->child_source); - barrier_destroy(&pty->barrier); - ring_clear(&pty->out_buf); - free(pty); - - return NULL; -} - -Barrier *pty_get_barrier(Pty *pty) { - assert(pty); - return &pty->barrier; -} - -bool pty_is_unknown(Pty *pty) { - return pty && pty->role == PTY_ROLE_UNKNOWN; -} - -bool pty_is_parent(Pty *pty) { - return pty && pty->role == PTY_ROLE_PARENT; -} - -bool pty_is_child(Pty *pty) { - return pty && pty->role == PTY_ROLE_CHILD; -} - -bool pty_has_child(Pty *pty) { - return pty_is_parent(pty) && pty->child > 0; -} - -pid_t pty_get_child(Pty *pty) { - return pty_has_child(pty) ? pty->child : -ECHILD; -} - -bool pty_is_open(Pty *pty) { - return pty && pty->fd >= 0; -} - -int pty_get_fd(Pty *pty) { - assert_return(pty, -EINVAL); - - return pty_is_open(pty) ? pty->fd : -EPIPE; -} - -int pty_make_child(Pty *pty) { - _cleanup_free_ char *slave_name = NULL; - int r, fd; - - assert_return(pty, -EINVAL); - assert_return(pty_is_unknown(pty), -EALREADY); - - r = ptsname_malloc(pty->fd, &slave_name); - if (r < 0) - return -errno; - - fd = open(slave_name, O_RDWR | O_CLOEXEC | O_NOCTTY); - if (fd < 0) - return -errno; - - safe_close(pty->fd); - pty->fd = fd; - pty->child = getpid(); - pty->role = PTY_ROLE_CHILD; - barrier_set_role(&pty->barrier, BARRIER_CHILD); - - return 0; -} - -int pty_make_parent(Pty *pty, pid_t child) { - assert_return(pty, -EINVAL); - assert_return(pty_is_unknown(pty), -EALREADY); - - pty->child = child; - pty->role = PTY_ROLE_PARENT; - - return 0; -} - -int pty_unlock(Pty *pty) { - assert_return(pty, -EINVAL); - assert_return(pty_is_unknown(pty) || pty_is_parent(pty), -EINVAL); - assert_return(pty_is_open(pty), -ENODEV); - - return unlockpt(pty->fd) < 0 ? -errno : 0; -} - -int pty_setup_child(Pty *pty) { - struct termios attr; - pid_t pid; - int r; - - assert_return(pty, -EINVAL); - assert_return(pty_is_child(pty), -EINVAL); - assert_return(pty_is_open(pty), -EALREADY); - - r = reset_signal_mask(); - if (r < 0) - return r; - - r = reset_all_signal_handlers(); - if (r < 0) - return r; - - pid = setsid(); - if (pid < 0 && errno != EPERM) - return -errno; - - r = ioctl(pty->fd, TIOCSCTTY, 0); - if (r < 0) - return -errno; - - r = tcgetattr(pty->fd, &attr); - if (r < 0) - return -errno; - - /* erase character should be normal backspace, PLEASEEE! */ - attr.c_cc[VERASE] = 010; - /* always set UTF8 flag */ - attr.c_iflag |= IUTF8; - - r = tcsetattr(pty->fd, TCSANOW, &attr); - if (r < 0) - return -errno; - - if (dup2(pty->fd, STDIN_FILENO) != STDIN_FILENO || - dup2(pty->fd, STDOUT_FILENO) != STDOUT_FILENO || - dup2(pty->fd, STDERR_FILENO) != STDERR_FILENO) - return -errno; - - /* only close FD if it's not a std-fd */ - pty->fd = (pty->fd > 2) ? safe_close(pty->fd) : -1; - - return 0; -} - -void pty_close(Pty *pty) { - if (!pty_is_open(pty)) - return; - - pty->fd_source = sd_event_source_unref(pty->fd_source); - pty->fd = safe_close(pty->fd); -} - -/* - * Drain input-queue and dispatch data via the event-handler. Returns <0 on - * error, 0 if queue is empty and 1 if we couldn't empty the input queue fast - * enough and there's still data left. - */ -static int pty_dispatch_read(Pty *pty) { - unsigned int i; - ssize_t len; - int r; - - /* - * 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, try reading as much as 8 times (32KiB) and only - * bail out then. - */ - - for (i = 0; i < 8; ++i) { - len = read(pty->fd, pty->in_buf, sizeof(pty->in_buf) - 1); - if (len < 0) { - if (errno == EINTR) - continue; - - return (errno == EAGAIN) ? 0 : -errno; - } else if (len == 0) - continue; - - /* set terminating zero for debugging safety */ - pty->in_buf[len] = 0; - r = pty->event_fn(pty, pty->event_fn_userdata, PTY_DATA, pty->in_buf, len); - if (r < 0) - return r; - } - - /* still data left, make sure we're queued again */ - pty->needs_requeue = true; - - return 1; -} - -/* - * Drain output-queue by writing data to the pty. Returns <0 on error, 0 if the - * output queue is empty now and 1 if we couldn't empty the output queue fast - * enough and there's still data left. - */ -static int pty_dispatch_write(Pty *pty) { - struct iovec vec[2]; - unsigned int i; - ssize_t len; - size_t num; - - /* - * Same as pty_dispatch_read(), we're edge-triggered so we need to call - * write() until either all data is written or it returns EAGAIN. We - * call it twice and if it still writes successfully, we reschedule. - */ - - for (i = 0; i < 2; ++i) { - num = ring_peek(&pty->out_buf, vec); - if (num < 1) - return 0; - - len = writev(pty->fd, vec, (int)num); - if (len < 0) { - if (errno == EINTR) - continue; - - return (errno == EAGAIN) ? 1 : -errno; - } else if (len == 0) - continue; - - ring_pull(&pty->out_buf, (size_t)len); - } - - /* still data left, make sure we're queued again */ - if (ring_get_size(&pty->out_buf) > 0) { - pty->needs_requeue = true; - return 1; - } - - return 0; -} - -static int pty_fd_fn(sd_event_source *source, int fd, uint32_t revents, void *userdata) { - Pty *pty = userdata; - int r_hup = 0, r_write = 0, r_read = 0, r; - - /* - * Whenever we encounter I/O errors, we have to make sure to drain the - * input queue first, before we handle any HUP. A child might send us - * a message and immediately close the queue. We must not handle the - * HUP first or we loose data. - * Therefore, if we read a message successfully, we always return - * success and wait for the next event-loop iteration. Furthermore, - * whenever there is a write-error, we must try reading from the input - * queue even if EPOLLIN is not set. The input might have arrived in - * between epoll_wait() and write(). Therefore, write-errors are only - * ever handled if the input-queue is empty. In all other cases they - * are ignored until either reading fails or the input queue is empty. - */ - - if (revents & (EPOLLHUP | EPOLLERR)) - r_hup = -EPIPE; - - if (revents & EPOLLOUT) - r_write = pty_dispatch_write(pty); - - /* Awesome! Kernel signals HUP without IN but queues are not empty.. */ - if ((revents & EPOLLIN) || r_hup < 0 || r_write < 0) { - r_read = pty_dispatch_read(pty); - if (r_read > 0) - return 0; /* still data left to fetch next round */ - } - - if (r_hup < 0 || r_write < 0 || r_read < 0) { - /* PTY closed and input-queue drained */ - pty_close(pty); - r = pty->event_fn(pty, pty->event_fn_userdata, PTY_HUP, NULL, 0); - if (r < 0) - return r; - } - - return 0; -} - -static int pty_fd_prepare_fn(sd_event_source *source, void *userdata) { - Pty *pty = userdata; - int r; - - if (pty->needs_requeue) { - /* - * We're edge-triggered. In case we couldn't handle all events - * or in case new write-data is queued, we set needs_requeue. - * Before going asleep, we set the io-events *again*. sd-event - * notices that we're edge-triggered and forwards the call to - * the kernel even if the events didn't change. The kernel will - * check the events and re-queue us on the ready queue in case - * an event is pending. - */ - r = sd_event_source_set_io_events(source, EPOLLHUP | EPOLLERR | EPOLLIN | EPOLLOUT | EPOLLET); - if (r >= 0) - pty->needs_requeue = false; - } - - return 0; -} - -static int pty_child_fn(sd_event_source *source, const siginfo_t *si, void *userdata) { - Pty *pty = userdata; - int r; - - pty->child = 0; - - r = pty->event_fn(pty, pty->event_fn_userdata, PTY_CHILD, si, sizeof(*si)); - if (r < 0) - return r; - - return 0; -} - -int pty_attach_event(Pty *pty, sd_event *event, pty_event_t event_fn, void *event_fn_userdata) { - int r; - - assert_return(pty, -EINVAL); - assert_return(event, -EINVAL); - assert_return(event_fn, -EINVAL); - assert_return(pty_is_parent(pty), -EINVAL); - - pty_detach_event(pty); - - if (pty_is_open(pty)) { - r = sd_event_add_io(event, - &pty->fd_source, - pty->fd, - EPOLLHUP | EPOLLERR | EPOLLIN | EPOLLOUT | EPOLLET, - pty_fd_fn, - pty); - if (r < 0) - goto error; - - r = sd_event_source_set_prepare(pty->fd_source, pty_fd_prepare_fn); - if (r < 0) - goto error; - } - - if (pty_has_child(pty)) { - r = sd_event_add_child(event, - &pty->child_source, - pty->child, - WEXITED, - pty_child_fn, - pty); - if (r < 0) - goto error; - } - - pty->event_fn = event_fn; - pty->event_fn_userdata = event_fn_userdata; - - return 0; - -error: - pty_detach_event(pty); - return r; -} - -void pty_detach_event(Pty *pty) { - if (!pty) - return; - - pty->child_source = sd_event_source_unref(pty->child_source); - pty->fd_source = sd_event_source_unref(pty->fd_source); - pty->event_fn = NULL; - pty->event_fn_userdata = NULL; -} - -int pty_write(Pty *pty, const void *buf, size_t size) { - bool was_empty; - int r; - - assert_return(pty, -EINVAL); - assert_return(pty_is_open(pty), -ENODEV); - assert_return(pty_is_parent(pty), -ENODEV); - - if (size < 1) - return 0; - - /* - * Push @buf[0..@size] into the output ring-buffer. In case the - * ring-buffer wasn't empty beforehand, we're already waiting for - * EPOLLOUT and we're done. If it was empty, we have to re-queue the - * FD for EPOLLOUT as we're edge-triggered and wouldn't get any new - * EPOLLOUT event. - */ - - was_empty = ring_get_size(&pty->out_buf) < 1; - - r = ring_push(&pty->out_buf, buf, size); - if (r < 0) - return r; - - if (was_empty) - pty->needs_requeue = true; - - return 0; -} - -int pty_signal(Pty *pty, int sig) { - assert_return(pty, -EINVAL); - assert_return(pty_is_open(pty), -ENODEV); - assert_return(pty_is_parent(pty), -ENODEV); - - return ioctl(pty->fd, TIOCSIG, sig) < 0 ? -errno : 0; -} - -int pty_resize(Pty *pty, unsigned short term_width, unsigned short term_height) { - struct winsize ws = { - .ws_col = term_width, - .ws_row = term_height, - }; - - assert_return(pty, -EINVAL); - assert_return(pty_is_open(pty), -ENODEV); - assert_return(pty_is_parent(pty), -ENODEV); - - /* - * 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; -} - -pid_t pty_fork(Pty **out, sd_event *event, pty_event_t event_fn, void *event_fn_userdata, unsigned short initial_term_width, unsigned short initial_term_height) { - _pty_unref_ Pty *pty = NULL; - int r; - pid_t pid; - - assert_return(out, -EINVAL); - assert_return((event && event_fn) || (!event && !event_fn), -EINVAL); - - r = pty_new(&pty); - if (r < 0) - return r; - - r = pty_unlock(pty); - if (r < 0) - return r; - - pid = fork(); - if (pid < 0) - return -errno; - - if (pid == 0) { - /* child */ - - r = pty_make_child(pty); - if (r < 0) - _exit(-r); - - r = pty_setup_child(pty); - if (r < 0) - _exit(-r); - - /* sync with parent */ - if (!barrier_place_and_sync(&pty->barrier)) - _exit(1); - - /* fallthrough and return the child's PTY object */ - } else { - /* parent */ - - r = pty_make_parent(pty, pid); - if (r < 0) - goto parent_error; - - r = pty_resize(pty, initial_term_width, initial_term_height); - if (r < 0) - goto parent_error; - - if (event) { - r = pty_attach_event(pty, event, event_fn, event_fn_userdata); - if (r < 0) - goto parent_error; - } - - /* sync with child */ - if (!barrier_place_and_sync(&pty->barrier)) { - r = -ECHILD; - goto parent_error; - } - - /* fallthrough and return the parent's PTY object */ - } - - *out = pty; - pty = NULL; - return pid; - -parent_error: - barrier_abort(&pty->barrier); - waitpid(pty->child, NULL, 0); - pty->child = 0; - return r; -} diff --git a/src/shared/pty.h b/src/shared/pty.h deleted file mode 100644 index 63c7db2833..0000000000 --- a/src/shared/pty.h +++ /dev/null @@ -1,72 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 David Herrmann <dh.herrmann@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 <stdbool.h> -#include <unistd.h> - -#include "barrier.h" -#include "macro.h" -#include "sd-event.h" - -typedef struct Pty Pty; - -enum { - PTY_CHILD, - PTY_HUP, - PTY_DATA, -}; - -typedef int (*pty_event_t) (Pty *pty, void *userdata, unsigned int event, const void *ptr, size_t size); - -int pty_new(Pty **out); -Pty *pty_ref(Pty *pty); -Pty *pty_unref(Pty *pty); - -#define _pty_unref_ _cleanup_(pty_unrefp) -DEFINE_TRIVIAL_CLEANUP_FUNC(Pty*, pty_unref); - -Barrier *pty_get_barrier(Pty *pty); - -bool pty_is_unknown(Pty *pty); -bool pty_is_parent(Pty *pty); -bool pty_is_child(Pty *pty); -bool pty_has_child(Pty *pty); -pid_t pty_get_child(Pty *pty); - -bool pty_is_open(Pty *pty); -int pty_get_fd(Pty *pty); - -int pty_make_child(Pty *pty); -int pty_make_parent(Pty *pty, pid_t child); -int pty_unlock(Pty *pty); -int pty_setup_child(Pty *pty); -void pty_close(Pty *pty); - -int pty_attach_event(Pty *pty, sd_event *event, pty_event_t event_fn, void *event_fn_userdata); -void pty_detach_event(Pty *pty); - -int pty_write(Pty *pty, const void *buf, size_t size); -int pty_signal(Pty *pty, int sig); -int pty_resize(Pty *pty, unsigned short term_width, unsigned short term_height); - -pid_t pty_fork(Pty **out, sd_event *event, pty_event_t event_fn, void *event_fn_userdata, unsigned short initial_term_width, unsigned short initial_term_height); diff --git a/src/test/test-pty.c b/src/test/test-pty.c deleted file mode 100644 index fbab3d4ebe..0000000000 --- a/src/test/test-pty.c +++ /dev/null @@ -1,142 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2014 David Herrmann <dh.herrmann@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 <locale.h> -#include <string.h> -#include <sys/wait.h> -#include <unistd.h> - -#include "pty.h" -#include "util.h" -#include "signal-util.h" - -static const char sndmsg[] = "message\n"; -static const char rcvmsg[] = "message\r\n"; -static char rcvbuf[128]; -static size_t rcvsiz = 0; -static sd_event *event; - -static void run_child(Pty *pty) { - ssize_t r, l; - char buf[512]; - - r = read(0, buf, sizeof(buf)); - assert_se((size_t)r == strlen(sndmsg)); - assert_se(!strncmp(buf, sndmsg, r)); - - l = write(1, buf, r); - assert_se(l == r); -} - -static int pty_fn(Pty *pty, void *userdata, unsigned int ev, const void *ptr, size_t size) { - switch (ev) { - case PTY_DATA: - assert_se(rcvsiz < strlen(rcvmsg) * 2); - assert_se(rcvsiz + size < sizeof(rcvbuf)); - - memcpy(&rcvbuf[rcvsiz], ptr, size); - rcvsiz += size; - - if (rcvsiz >= strlen(rcvmsg) * 2) { - assert_se(rcvsiz == strlen(rcvmsg) * 2); - assert_se(!memcmp(rcvbuf, rcvmsg, strlen(rcvmsg))); - assert_se(!memcmp(&rcvbuf[strlen(rcvmsg)], rcvmsg, strlen(rcvmsg))); - } - - break; - case PTY_HUP: - /* This is guaranteed to appear _after_ the input queues are - * drained! */ - assert_se(rcvsiz == strlen(rcvmsg) * 2); - break; - case PTY_CHILD: - /* this may appear at any time */ - break; - default: - assert_se(0); - break; - } - - /* if we got HUP _and_ CHILD, exit */ - if (pty_get_fd(pty) < 0 && pty_get_child(pty) < 0) - sd_event_exit(event, 0); - - return 0; -} - -static void run_parent(Pty *pty) { - int r; - - /* write message to pty, ECHO mode guarantees that we get it back - * twice: once via ECHO, once from the run_child() fn */ - assert_se(pty_write(pty, sndmsg, strlen(sndmsg)) >= 0); - - r = sd_event_loop(event); - assert_se(r >= 0); -} - -static void test_pty(void) { - pid_t pid; - Pty *pty = NULL; - - rcvsiz = 0; - zero(rcvbuf); - - assert_se(sd_event_default(&event) >= 0); - - pid = pty_fork(&pty, event, pty_fn, NULL, 80, 25); - assert_se(pid >= 0); - - if (pid == 0) { - /* child */ - run_child(pty); - exit(0); - } - - /* parent */ - run_parent(pty); - - /* Make sure the PTY recycled the child; yeah, this is racy if the - * PID was already reused; but that seems fine for a test. */ - assert_se(waitpid(pid, NULL, WNOHANG) < 0 && errno == ECHILD); - - pty_unref(pty); - sd_event_unref(event); -} - -int main(int argc, char *argv[]) { - unsigned int i; - - log_parse_environment(); - log_open(); - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, -1) >= 0); - - /* Oh, there're ugly races in the TTY layer regarding HUP vs IN. Turns - * out they appear only 10% of the time. I fixed all of them and - * don't see them, anymore. But let's be safe and run this 1000 times - * so we catch any new ones, in case they appear again. */ - for (i = 0; i < 1000; ++i) - test_pty(); - - return 0; -} diff --git a/src/test/test-ring.c b/src/test/test-ring.c deleted file mode 100644 index cb8a5d4e9e..0000000000 --- a/src/test/test-ring.c +++ /dev/null @@ -1,130 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright 2014 David Herrmann <dh.herrmann@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 <string.h> - -#include "def.h" -#include "ring.h" - -static void test_ring(void) { - static const char buf[8192]; - Ring r; - size_t l; - struct iovec vec[2]; - int s; - - zero(r); - - l = ring_peek(&r, vec); - assert_se(l == 0); - - s = ring_push(&r, buf, 2048); - assert_se(!s); - assert_se(ring_get_size(&r) == 2048); - - l = ring_peek(&r, vec); - assert_se(l == 1); - assert_se(vec[0].iov_len == 2048); - assert_se(!memcmp(vec[0].iov_base, buf, vec[0].iov_len)); - assert_se(ring_get_size(&r) == 2048); - - ring_pull(&r, 2048); - assert_se(ring_get_size(&r) == 0); - - l = ring_peek(&r, vec); - assert_se(l == 0); - assert_se(ring_get_size(&r) == 0); - - s = ring_push(&r, buf, 2048); - assert_se(!s); - assert_se(ring_get_size(&r) == 2048); - - l = ring_peek(&r, vec); - assert_se(l == 1); - assert_se(vec[0].iov_len == 2048); - assert_se(!memcmp(vec[0].iov_base, buf, vec[0].iov_len)); - assert_se(ring_get_size(&r) == 2048); - - s = ring_push(&r, buf, 1); - assert_se(!s); - assert_se(ring_get_size(&r) == 2049); - - l = ring_peek(&r, vec); - assert_se(l == 2); - assert_se(vec[0].iov_len == 2048); - assert_se(vec[1].iov_len == 1); - assert_se(!memcmp(vec[0].iov_base, buf, vec[0].iov_len)); - assert_se(!memcmp(vec[1].iov_base, buf, vec[1].iov_len)); - assert_se(ring_get_size(&r) == 2049); - - ring_pull(&r, 2048); - assert_se(ring_get_size(&r) == 1); - - l = ring_peek(&r, vec); - assert_se(l == 1); - assert_se(vec[0].iov_len == 1); - assert_se(!memcmp(vec[0].iov_base, buf, vec[0].iov_len)); - assert_se(ring_get_size(&r) == 1); - - ring_pull(&r, 1); - assert_se(ring_get_size(&r) == 0); - - s = ring_push(&r, buf, 2048); - assert_se(!s); - assert_se(ring_get_size(&r) == 2048); - - s = ring_push(&r, buf, 2049); - assert_se(!s); - assert_se(ring_get_size(&r) == 4097); - - l = ring_peek(&r, vec); - assert_se(l == 1); - assert_se(vec[0].iov_len == 4097); - assert_se(!memcmp(vec[0].iov_base, buf, vec[0].iov_len)); - assert_se(ring_get_size(&r) == 4097); - - ring_pull(&r, 1); - assert_se(ring_get_size(&r) == 4096); - - s = ring_push(&r, buf, 4096); - assert_se(!s); - assert_se(ring_get_size(&r) == 8192); - - l = ring_peek(&r, vec); - assert_se(l == 2); - assert_se(vec[0].iov_len == 8191); - assert_se(vec[1].iov_len == 1); - assert_se(!memcmp(vec[0].iov_base, buf, vec[0].iov_len)); - assert_se(!memcmp(vec[1].iov_base, buf, vec[1].iov_len)); - assert_se(ring_get_size(&r) == 8192); - - ring_clear(&r); - assert_se(ring_get_size(&r) == 0); -} - -int main(int argc, char *argv[]) { - log_parse_environment(); - log_open(); - - test_ring(); - - return 0; -} |