From a28528d260ea51883a3325be4f4682f4c2f66da8 Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Sat, 8 Aug 2015 19:36:34 +0200
Subject: degesch: add backlog/scrollback functionality
Finally! I went with possibly the simplest solution, which is to
run less, instead of badly reimplementing its functionality.
---
degesch.c | 223 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 200 insertions(+), 23 deletions(-)
diff --git a/degesch.c b/degesch.c
index b3747ec..b7c9434 100644
--- a/degesch.c
+++ b/degesch.c
@@ -1364,6 +1364,8 @@ struct app_context
bool awaiting_mirc_escape; ///< Awaiting a mIRC attribute escape
char char_buf[MB_LEN_MAX + 1]; ///< Buffered multibyte char
size_t char_buf_len; ///< How much of an MB char is buffered
+
+ bool running_backlog_helper; ///< Running a backlog helper
}
*g_ctx;
@@ -1640,6 +1642,10 @@ static struct config_schema g_config_behaviour[] =
.type = CONFIG_ITEM_BOOLEAN,
.default_ = "off",
.on_change = on_config_logging_change },
+ { .name = "backlog_helper",
+ .comment = "Shell command to display a buffer's history",
+ .type = CONFIG_ITEM_STRING,
+ .default_ = "\"LESSSECURE=1 less -M -R +G\"" },
{ .name = "save_on_quit",
.comment = "Save configuration before quitting",
.type = CONFIG_ITEM_BOOLEAN,
@@ -2839,11 +2845,18 @@ log_formatter (struct app_context *ctx,
&& buffer == ctx->current_buffer->server->buffer))
can_leak = true;
- if (buffer == ctx->current_buffer)
+ bool displayed = true;
+ if (ctx->running_backlog_helper)
+ // Another process is using the terminal
+ displayed = false;
+ else if (buffer == ctx->current_buffer)
buffer_line_display (ctx, line, false);
else if (!ctx->isolate_buffers && can_leak)
buffer_line_display (ctx, line, true);
else
+ displayed = false;
+
+ if (!displayed)
{
buffer->unseen_messages_count++;
if (flags & BUFFER_LINE_HIGHLIGHT)
@@ -2957,6 +2970,8 @@ buffer_open_log_file (struct app_context *ctx, struct buffer *buffer)
if (!(buffer->log_file = fopen (path.str, "ab")))
log_global_error (ctx, "Couldn't open log file `#s': #s",
path.str, strerror (errno));
+ else
+ set_cloexec (fileno (buffer->log_file));
str_free (&path);
}
@@ -8807,6 +8822,38 @@ make_completions (struct app_context *ctx, char *line, int start, int end)
// --- Common code for user actions --------------------------------------------
+static void
+suspend_terminal (struct app_context *ctx)
+{
+#ifdef HAVE_READLINE
+ rl_deprep_terminal ();
+#elif defined (HAVE_EDITLINE)
+ el_set (ctx->input.editline, EL_PREP_TERM, 0);
+#endif
+
+ input_hide (&ctx->input);
+ poller_fd_reset (&ctx->tty_event);
+ // TODO: also disable the date change timer
+}
+
+static void
+resume_terminal (struct app_context *ctx)
+{
+#ifdef HAVE_READLINE
+ rl_prep_terminal (true);
+#elif defined (HAVE_EDITLINE)
+ el_set (ctx->input.editline, EL_PREP_TERM, 1);
+#endif
+
+ // In theory we could just print all unseen messages but this is safer
+ buffer_print_backlog (ctx, ctx->current_buffer);
+ // Now it's safe to process any user input
+ poller_fd_set (&ctx->tty_event, POLLIN);
+ input_show (&ctx->input);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
static bool
redraw_screen (struct app_context *ctx)
{
@@ -8849,6 +8896,59 @@ await_mirc_escape (struct app_context *ctx)
ctx->char_buf_len = 0;
}
+static void
+exec_backlog_helper (const char *command, FILE *backlog)
+{
+ dup2 (fileno (backlog), STDIN_FILENO);
+
+ // Put the child into a new foreground process group
+ hard_assert (setpgid (0, 0)!= -1);
+ hard_assert (tcsetpgrp (STDOUT_FILENO, getpgid (0)) != -1);
+
+ execl ("/bin/sh", "/bin/sh", "-c", command, NULL);
+ print_error ("%s: %s", "Failed to launch backlog helper", strerror (errno));
+ _exit (EXIT_FAILURE);
+}
+
+static void
+display_backlog (struct app_context *ctx)
+{
+ hard_assert (!ctx->running_backlog_helper);
+
+ FILE *backlog = tmpfile ();
+ set_cloexec (fileno (backlog));
+
+ // TODO: retrieve the lines with formatting
+ for (struct buffer_line *line = ctx->current_buffer->lines;
+ line; line = line->next)
+ buffer_line_write_to_log (ctx, line, backlog);
+
+ rewind (backlog);
+ suspend_terminal (ctx);
+
+ pid_t child = fork ();
+ if (child == 0)
+ exec_backlog_helper (get_config_string
+ (ctx->config.root, "behaviour.backlog_helper"), backlog);
+
+ if (child == -1)
+ {
+ int saved_errno = errno;
+ resume_terminal (ctx);
+ log_global_error (ctx, "#s: #s",
+ "Failed to launch backlog helper", strerror (saved_errno));
+ }
+ else
+ {
+ // Make sure the child has its own process group
+ (void) setpgid (child, child);
+
+ ctx->running_backlog_helper = true;
+ }
+
+ fclose (backlog);
+}
+
static void
bind_common_keys (struct app_context *ctx)
{
@@ -8866,6 +8966,8 @@ bind_common_keys (struct app_context *ctx)
input_bind (self, key_f5, "previous-buffer");
if (key_f6)
input_bind (self, key_f6, "next-buffer");
+ if (key_ppage)
+ input_bind (self, key_ppage, "display-backlog");
if (clear_screen)
input_bind_control (self, 'l', "redraw-screen");
@@ -8906,6 +9008,17 @@ on_readline_next_buffer (int count, int key)
return 0;
}
+static int
+on_readline_display_backlog (int count, int key)
+{
+ (void) count;
+ (void) key;
+
+ struct app_context *ctx = g_ctx;
+ display_backlog (ctx);
+ return 0;
+}
+
static int
on_readline_redraw_screen (int count, int key)
{
@@ -8998,6 +9111,7 @@ app_readline_init (void)
rl_add_defun ("previous-buffer", on_readline_previous_buffer, -1);
rl_add_defun ("next-buffer", on_readline_next_buffer, -1);
rl_add_defun ("goto-buffer", on_readline_goto_buffer, -1);
+ rl_add_defun ("display-backlog", on_readline_display_backlog, -1);
rl_add_defun ("redraw-screen", on_readline_redraw_screen, -1);
rl_add_defun ("insert-attribute", on_readline_insert_attribute, -1);
rl_add_defun ("send-line", on_readline_return, -1);
@@ -9057,6 +9171,15 @@ on_editline_next_buffer (EditLine *editline, int key)
return CC_NORM;
}
+static unsigned char
+on_editline_display_backlog (EditLine *editline, int key)
+{
+ (void) editline;
+ (void) key;
+
+ display_backlog (g_ctx);
+}
+
static unsigned char
on_editline_redraw_screen (EditLine *editline, int key)
{
@@ -9173,6 +9296,7 @@ app_editline_init (struct input *self)
{ "goto-buffer", "Go to buffer", on_editline_goto_buffer },
{ "previous-buffer", "Previous buffer", on_editline_previous_buffer },
{ "next-buffer", "Next buffer", on_editline_next_buffer },
+ { "display-backlog", "Display backlog", on_editline_display_backlog },
{ "redraw-screen", "Redraw screen", on_editline_redraw_screen },
{ "insert-attribute", "mIRC formatting", on_editline_insert_attribute },
{ "send-line", "Send line", on_editline_return },
@@ -9376,29 +9500,34 @@ static volatile sig_atomic_t g_termination_requested;
static volatile sig_atomic_t g_winch_received;
static void
-sigterm_handler (int signum)
+postpone_signal_handling (char id)
{
- (void) signum;
-
- g_termination_requested = true;
-
int original_errno = errno;
- if (write (g_signal_pipe[1], "t", 1) == -1)
+ if (write (g_signal_pipe[1], &id, 1) == -1)
soft_assert (errno == EAGAIN);
errno = original_errno;
}
static void
-sigwinch_handler (int signum)
+signal_superhandler (int signum)
{
- (void) signum;
-
- g_winch_received = true;
-
- int original_errno = errno;
- if (write (g_signal_pipe[1], "w", 1) == -1)
- soft_assert (errno == EAGAIN);
- errno = original_errno;
+ switch (signum)
+ {
+ case SIGWINCH:
+ g_winch_received = true;
+ postpone_signal_handling ('w');
+ break;
+ case SIGINT:
+ case SIGTERM:
+ g_termination_requested = true;
+ postpone_signal_handling ('t');
+ break;
+ case SIGCHLD:
+ postpone_signal_handling ('c');
+ break;
+ default:
+ hard_assert (!"unhandled signal");
+ }
}
static void
@@ -9418,28 +9547,76 @@ setup_signal_handlers (void)
signal (SIGPIPE, SIG_IGN);
+ // So that we can write to the terminal while we're running a backlog
+ // helper. This is also inherited by the child so that it doesn't stop
+ // when it calls tcsetpgrp().
+ signal (SIGTTOU, SIG_IGN);
+
struct sigaction sa;
sa.sa_flags = SA_RESTART;
- sa.sa_handler = sigwinch_handler;
+ sa.sa_handler = signal_superhandler;
sigemptyset (&sa.sa_mask);
- if (sigaction (SIGWINCH, &sa, NULL) == -1)
- exit_fatal ("sigaction: %s", strerror (errno));
-
- sa.sa_handler = sigterm_handler;
- if (sigaction (SIGINT, &sa, NULL) == -1
- || sigaction (SIGTERM, &sa, NULL) == -1)
+ if (sigaction (SIGWINCH, &sa, NULL) == -1
+ || sigaction (SIGINT, &sa, NULL) == -1
+ || sigaction (SIGTERM, &sa, NULL) == -1
+ || sigaction (SIGCHLD, &sa, NULL) == -1)
exit_fatal ("sigaction: %s", strerror (errno));
}
// --- I/O event handlers ------------------------------------------------------
+static bool
+try_reap_child (struct app_context *ctx)
+{
+ int status;
+ pid_t zombie = waitpid (-1, &status, WNOHANG | WUNTRACED);
+
+ if (zombie == -1)
+ {
+ if (errno == ECHILD) return false;
+ if (errno == EINTR) return true;
+ exit_fatal ("%s: %s", "waitpid", strerror (errno));
+ }
+ if (!zombie)
+ return false;
+
+ if (!ctx->running_backlog_helper)
+ {
+ print_debug ("an unknown child has died");
+ return true;
+ }
+ if (WIFSTOPPED (status))
+ {
+ // We could also send SIGCONT but what's the point
+ kill (-zombie, SIGKILL);
+ return true;
+ }
+
+ ctx->running_backlog_helper = false;
+ hard_assert (tcsetpgrp (STDOUT_FILENO, getpgid (0)) != -1);
+ resume_terminal (ctx);
+
+ if (WIFSIGNALED (status))
+ log_global_error (ctx,
+ "Child died from signal #d", WTERMSIG (status));
+ else if (WIFEXITED (status) && WEXITSTATUS (status) != 0)
+ log_global_error (ctx,
+ "Child returned status #d", WEXITSTATUS (status));
+ return true;
+}
+
static void
on_signal_pipe_readable (const struct pollfd *fd, struct app_context *ctx)
{
char dummy;
(void) read (fd->fd, &dummy, 1);
+ // Reap all dead children (since the signal pipe may overflow etc. we run
+ // waitpid() in a loop to return all the zombies it knows about).
+ while (try_reap_child (ctx))
+ ;
+
if (g_termination_requested && !ctx->quitting)
// TODO: this way we don't send a QUIT message but just close the
// connection from our side and wait for a full close.
--
cgit v1.2.3-70-g09d2