-/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
-
+/* SPDX-License-Identifier: LGPL-2.1+ */
/***
This file is part of systemd.
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
+#include <errno.h>
+#include <limits.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
#include <sys/epoll.h>
-#include <sys/signalfd.h>
#include <sys/ioctl.h>
-#include <limits.h>
+#include <sys/time.h>
#include <termios.h>
+#include <unistd.h>
-#include "util.h"
+#include "sd-event.h"
+
+#include "alloc-util.h"
+#include "fd-util.h"
+#include "log.h"
+#include "macro.h"
#include "ptyfwd.h"
+#include "time-util.h"
struct PTYForward {
sd_event *event;
int master;
+ PTYForwardFlags flags;
+
sd_event_source *stdin_event_source;
sd_event_source *stdout_event_source;
sd_event_source *master_event_source;
bool master_writable:1;
bool master_hangup:1;
- /* Continue reading after hangup? */
- bool ignore_vhangup:1;
+ bool read_from_master:1;
+
+ bool done:1;
+ bool drain:1;
bool last_char_set:1;
char last_char;
usec_t escape_timestamp;
unsigned escape_counter;
+
+ PTYForwardHandler handler;
+ void *userdata;
};
#define ESCAPE_USEC (1*USEC_PER_SEC)
+static void pty_forward_disconnect(PTYForward *f) {
+
+ if (f) {
+ f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
+ f->stdout_event_source = sd_event_source_unref(f->stdout_event_source);
+
+ f->master_event_source = sd_event_source_unref(f->master_event_source);
+ f->sigwinch_event_source = sd_event_source_unref(f->sigwinch_event_source);
+ f->event = sd_event_unref(f->event);
+
+ if (f->saved_stdout)
+ tcsetattr(STDOUT_FILENO, TCSANOW, &f->saved_stdout_attr);
+ if (f->saved_stdin)
+ tcsetattr(STDIN_FILENO, TCSANOW, &f->saved_stdin_attr);
+
+ f->saved_stdout = f->saved_stdin = false;
+ }
+
+ /* STDIN/STDOUT should not be nonblocking normally, so let's unconditionally reset it */
+ fd_nonblock(STDIN_FILENO, false);
+ fd_nonblock(STDOUT_FILENO, false);
+}
+
+static int pty_forward_done(PTYForward *f, int rcode) {
+ _cleanup_(sd_event_unrefp) sd_event *e = NULL;
+ assert(f);
+
+ if (f->done)
+ return 0;
+
+ e = sd_event_ref(f->event);
+
+ f->done = true;
+ pty_forward_disconnect(f);
+
+ if (f->handler)
+ return f->handler(f, rcode, f->userdata);
+ else
+ return sd_event_exit(e, rcode < 0 ? EXIT_FAILURE : rcode);
+}
+
static bool look_for_escape(PTYForward *f, const char *buffer, size_t n) {
const char *p;
return false;
}
+static bool ignore_vhangup(PTYForward *f) {
+ assert(f);
+
+ if (f->flags & PTY_FORWARD_IGNORE_VHANGUP)
+ return true;
+
+ if ((f->flags & PTY_FORWARD_IGNORE_INITIAL_VHANGUP) && !f->read_from_master)
+ return true;
+
+ return false;
+}
+
static int shovel(PTYForward *f) {
ssize_t k;
if (errno == EAGAIN)
f->stdin_readable = false;
- else if (errno == EIO || errno == EPIPE || errno == ECONNRESET) {
+ else if (IN_SET(errno, EIO, EPIPE, ECONNRESET)) {
f->stdin_readable = false;
f->stdin_hangup = true;
f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
} else {
log_error_errno(errno, "read(): %m");
- return sd_event_exit(f->event, EXIT_FAILURE);
+ return pty_forward_done(f, -errno);
}
} else if (k == 0) {
/* EOF on stdin */
f->stdin_event_source = sd_event_source_unref(f->stdin_event_source);
} else {
- /* Check if ^] has been
- * pressed three times within
- * one second. If we get this
- * we quite immediately. */
+ /* Check if ^] has been pressed three times within one second. If we get this we quite
+ * immediately. */
if (look_for_escape(f, f->in_buffer + f->in_buffer_full, k))
- return sd_event_exit(f->event, EXIT_FAILURE);
+ return pty_forward_done(f, -ECANCELED);
f->in_buffer_full += (size_t) k;
}
k = write(f->master, f->in_buffer, f->in_buffer_full);
if (k < 0) {
- if (errno == EAGAIN || errno == EIO)
+ if (IN_SET(errno, EAGAIN, EIO))
f->master_writable = false;
- else if (errno == EPIPE || errno == ECONNRESET) {
+ else if (IN_SET(errno, EPIPE, ECONNRESET)) {
f->master_writable = f->master_readable = false;
f->master_hangup = true;
f->master_event_source = sd_event_source_unref(f->master_event_source);
} else {
log_error_errno(errno, "write(): %m");
- return sd_event_exit(f->event, EXIT_FAILURE);
+ return pty_forward_done(f, -errno);
}
} else {
assert(f->in_buffer_full >= (size_t) k);
* EAGAIN here and try again, unless
* ignore_vhangup is off. */
- if (errno == EAGAIN || (errno == EIO && f->ignore_vhangup))
+ if (errno == EAGAIN || (errno == EIO && ignore_vhangup(f)))
f->master_readable = false;
- else if (errno == EPIPE || errno == ECONNRESET || errno == EIO) {
+ else if (IN_SET(errno, EPIPE, ECONNRESET, EIO)) {
f->master_readable = f->master_writable = false;
f->master_hangup = true;
f->master_event_source = sd_event_source_unref(f->master_event_source);
} else {
log_error_errno(errno, "read(): %m");
- return sd_event_exit(f->event, EXIT_FAILURE);
+ return pty_forward_done(f, -errno);
}
- } else
+ } else {
+ f->read_from_master = true;
f->out_buffer_full += (size_t) k;
+ }
}
if (f->stdout_writable && f->out_buffer_full > 0) {
if (errno == EAGAIN)
f->stdout_writable = false;
- else if (errno == EIO || errno == EPIPE || errno == ECONNRESET) {
+ else if (IN_SET(errno, EIO, EPIPE, ECONNRESET)) {
f->stdout_writable = false;
f->stdout_hangup = true;
f->stdout_event_source = sd_event_source_unref(f->stdout_event_source);
} else {
log_error_errno(errno, "write(): %m");
- return sd_event_exit(f->event, EXIT_FAILURE);
+ return pty_forward_done(f, -errno);
}
} else {
if ((f->out_buffer_full <= 0 || f->stdout_hangup) &&
(f->in_buffer_full <= 0 || f->master_hangup))
- return sd_event_exit(f->event, EXIT_SUCCESS);
+ return pty_forward_done(f, 0);
}
+ /* If we were asked to drain, and there's nothing more to handle from the master, then call the callback
+ * too. */
+ if (f->drain && f->out_buffer_full == 0 && !f->master_readable)
+ return pty_forward_done(f, 0);
+
return 0;
}
/* The window size changed, let's forward that. */
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0)
- (void)ioctl(f->master, TIOCSWINSZ, &ws);
+ (void) ioctl(f->master, TIOCSWINSZ, &ws);
return 0;
}
-int pty_forward_new(sd_event *event, int master, bool ignore_vhangup, PTYForward **ret) {
+int pty_forward_new(
+ sd_event *event,
+ int master,
+ PTYForwardFlags flags,
+ PTYForward **ret) {
+
_cleanup_(pty_forward_freep) PTYForward *f = NULL;
struct winsize ws;
int r;
if (!f)
return -ENOMEM;
- f->ignore_vhangup = ignore_vhangup;
+ f->flags = flags;
if (event)
f->event = sd_event_ref(event);
return r;
}
- r = fd_nonblock(STDIN_FILENO, true);
- if (r < 0)
- return r;
+ if (!(flags & PTY_FORWARD_READ_ONLY)) {
+ r = fd_nonblock(STDIN_FILENO, true);
+ if (r < 0)
+ return r;
- r = fd_nonblock(STDOUT_FILENO, true);
- if (r < 0)
- return r;
+ r = fd_nonblock(STDOUT_FILENO, true);
+ if (r < 0)
+ return r;
+ }
r = fd_nonblock(master, true);
if (r < 0)
f->master = master;
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0)
- (void)ioctl(master, TIOCSWINSZ, &ws);
+ (void) ioctl(master, TIOCSWINSZ, &ws);
- if (tcgetattr(STDIN_FILENO, &f->saved_stdin_attr) >= 0) {
- struct termios raw_stdin_attr;
+ if (!(flags & PTY_FORWARD_READ_ONLY)) {
+ if (tcgetattr(STDIN_FILENO, &f->saved_stdin_attr) >= 0) {
+ struct termios raw_stdin_attr;
- f->saved_stdin = true;
+ f->saved_stdin = true;
- raw_stdin_attr = f->saved_stdin_attr;
- cfmakeraw(&raw_stdin_attr);
- raw_stdin_attr.c_oflag = f->saved_stdin_attr.c_oflag;
- tcsetattr(STDIN_FILENO, TCSANOW, &raw_stdin_attr);
- }
+ raw_stdin_attr = f->saved_stdin_attr;
+ cfmakeraw(&raw_stdin_attr);
+ raw_stdin_attr.c_oflag = f->saved_stdin_attr.c_oflag;
+ tcsetattr(STDIN_FILENO, TCSANOW, &raw_stdin_attr);
+ }
- if (tcgetattr(STDOUT_FILENO, &f->saved_stdout_attr) >= 0) {
- struct termios raw_stdout_attr;
+ if (tcgetattr(STDOUT_FILENO, &f->saved_stdout_attr) >= 0) {
+ struct termios raw_stdout_attr;
- f->saved_stdout = true;
+ f->saved_stdout = true;
- raw_stdout_attr = f->saved_stdout_attr;
- cfmakeraw(&raw_stdout_attr);
- raw_stdout_attr.c_iflag = f->saved_stdout_attr.c_iflag;
- raw_stdout_attr.c_lflag = f->saved_stdout_attr.c_lflag;
- tcsetattr(STDOUT_FILENO, TCSANOW, &raw_stdout_attr);
- }
+ raw_stdout_attr = f->saved_stdout_attr;
+ cfmakeraw(&raw_stdout_attr);
+ raw_stdout_attr.c_iflag = f->saved_stdout_attr.c_iflag;
+ raw_stdout_attr.c_lflag = f->saved_stdout_attr.c_lflag;
+ tcsetattr(STDOUT_FILENO, TCSANOW, &raw_stdout_attr);
+ }
- r = sd_event_add_io(f->event, &f->master_event_source, master, EPOLLIN|EPOLLOUT|EPOLLET, on_master_event, f);
- if (r < 0)
- return r;
+ r = sd_event_add_io(f->event, &f->stdin_event_source, STDIN_FILENO, EPOLLIN|EPOLLET, on_stdin_event, f);
+ if (r < 0 && r != -EPERM)
+ return r;
- r = sd_event_add_io(f->event, &f->stdin_event_source, STDIN_FILENO, EPOLLIN|EPOLLET, on_stdin_event, f);
- if (r < 0 && r != -EPERM)
- return r;
+ if (r >= 0)
+ (void) sd_event_source_set_description(f->stdin_event_source, "ptyfwd-stdin");
+ }
r = sd_event_add_io(f->event, &f->stdout_event_source, STDOUT_FILENO, EPOLLOUT|EPOLLET, on_stdout_event, f);
if (r == -EPERM)
f->stdout_writable = true;
else if (r < 0)
return r;
+ else
+ (void) sd_event_source_set_description(f->stdout_event_source, "ptyfwd-stdout");
+
+ r = sd_event_add_io(f->event, &f->master_event_source, master, EPOLLIN|EPOLLOUT|EPOLLET, on_master_event, f);
+ if (r < 0)
+ return r;
+
+ (void) sd_event_source_set_description(f->master_event_source, "ptyfwd-master");
r = sd_event_add_signal(f->event, &f->sigwinch_event_source, SIGWINCH, on_sigwinch_event, f);
+ if (r < 0)
+ return r;
+
+ (void) sd_event_source_set_description(f->sigwinch_event_source, "ptyfwd-sigwinch");
*ret = f;
f = NULL;
}
PTYForward *pty_forward_free(PTYForward *f) {
-
- if (f) {
- sd_event_source_unref(f->stdin_event_source);
- sd_event_source_unref(f->stdout_event_source);
- sd_event_source_unref(f->master_event_source);
- sd_event_unref(f->event);
-
- if (f->saved_stdout)
- tcsetattr(STDOUT_FILENO, TCSANOW, &f->saved_stdout_attr);
- if (f->saved_stdin)
- tcsetattr(STDIN_FILENO, TCSANOW, &f->saved_stdin_attr);
-
- free(f);
- }
-
- /* STDIN/STDOUT should not be nonblocking normally, so let's
- * unconditionally reset it */
- fd_nonblock(STDIN_FILENO, false);
- fd_nonblock(STDOUT_FILENO, false);
-
- return NULL;
+ pty_forward_disconnect(f);
+ return mfree(f);
}
int pty_forward_get_last_char(PTYForward *f, char *ch) {
return 0;
}
-int pty_forward_set_ignore_vhangup(PTYForward *f, bool ignore_vhangup) {
+int pty_forward_set_ignore_vhangup(PTYForward *f, bool b) {
int r;
assert(f);
- if (f->ignore_vhangup == ignore_vhangup)
+ if (!!(f->flags & PTY_FORWARD_IGNORE_VHANGUP) == b)
return 0;
- f->ignore_vhangup = ignore_vhangup;
- if (!f->ignore_vhangup) {
+ SET_FLAG(f->flags, PTY_FORWARD_IGNORE_VHANGUP, b);
+
+ if (!ignore_vhangup(f)) {
/* We shall now react to vhangup()s? Let's check
* immediately if we might be in one */
return 0;
}
-int pty_forward_get_ignore_vhangup(PTYForward *f) {
+bool pty_forward_get_ignore_vhangup(PTYForward *f) {
assert(f);
- return f->ignore_vhangup;
+ return !!(f->flags & PTY_FORWARD_IGNORE_VHANGUP);
+}
+
+bool pty_forward_is_done(PTYForward *f) {
+ assert(f);
+
+ return f->done;
+}
+
+void pty_forward_set_handler(PTYForward *f, PTYForwardHandler cb, void *userdata) {
+ assert(f);
+
+ f->handler = cb;
+ f->userdata = userdata;
+}
+
+bool pty_forward_drain(PTYForward *f) {
+ assert(f);
+
+ /* Starts draining the forwarder. Specifically:
+ *
+ * - Returns true if there are no unprocessed bytes from the pty, false otherwise
+ *
+ * - Makes sure the handler function is called the next time the number of unprocessed bytes hits zero
+ */
+
+ f->drain = true;
+
+ return f->out_buffer_full == 0 && !f->master_readable;
}