From 1639235a48dbed75c2563c9a497b41c31a2a1bae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Eric=20Janouch?= Date: Mon, 8 Aug 2022 04:39:20 +0200 Subject: Start X11 and web frontends for xC For this, we needed a wire protocol. After surveying available options, it was decided to implement an XDR-like protocol code generator in portable AWK. It now has two backends, per each of: - xF, the X11 frontend, is in C, and is meant to be the primary user interface in the future. - xP, the web frontend, relies on a protocol proxy written in Go, and is meant for use on-the-go (no pun intended). They are very much work-in-progress proofs of concept right now, and the relay protocol is certain to change. --- xC.c | 785 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 742 insertions(+), 43 deletions(-) (limited to 'xC.c') diff --git a/xC.c b/xC.c index c80499c..e0c9e01 100644 --- a/xC.c +++ b/xC.c @@ -50,6 +50,7 @@ enum #include "common.c" #include "xD-replies.c" +#include "xC-proto.c" #include #include @@ -1526,6 +1527,7 @@ enum buffer_line_flags BUFFER_LINE_HIGHLIGHT = 1 << 2, ///< The user was highlighted by this }; +// NOTE: This sequence must match up with xC-proto, only one lower. enum buffer_line_rendition { BUFFER_LINE_BARE, ///< Unadorned @@ -1666,6 +1668,50 @@ buffer_destroy (struct buffer *self) REF_COUNTABLE_METHODS (buffer) #define buffer_ref do_not_use_dangerous +// ~~~ Relay ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +struct client +{ + LIST_HEADER (struct client) + struct app_context *ctx; ///< Application context + + // TODO: Convert this all to TLS, and only TLS, with required client cert. + // That means replacing plumbing functions with the /other/ set from xD. + + int socket_fd; ///< The TCP socket + struct str read_buffer; ///< Unprocessed input + struct str write_buffer; ///< Output yet to be sent out + + uint32_t event_seq; ///< Outgoing message counter + bool initialized; ///< Initial sync took place + + struct poller_fd socket_event; ///< The socket can be read/written to +}; + +static struct client * +client_new (void) +{ + struct client *self = xcalloc (1, sizeof *self); + self->socket_fd = -1; + self->read_buffer = str_make (); + self->write_buffer = str_make (); + return self; +} + +static void +client_destroy (struct client *self) +{ + if (!soft_assert (self->socket_fd == -1)) + xclose (self->socket_fd); + + str_free (&self->read_buffer); + str_free (&self->write_buffer); + free (self); +} + +static void client_kill (struct client *c); +static bool client_process_buffer (struct client *c); + // ~~~ Server ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // The only real purpose of this is to abstract away TLS @@ -2079,10 +2125,19 @@ struct app_context struct str_map servers; ///< Our servers + // Relay: + + int relay_fd; ///< Listening socket FD + struct client *clients; ///< Our relay clients + + /// A single message buffer to prepare all outcoming messages within + struct relay_event_message relay_message; + // Events: struct poller_fd tty_event; ///< Terminal input event struct poller_fd signal_event; ///< Signal FD event + struct poller_fd relay_event; ///< New relay connection available struct poller_timer flush_timer; ///< Flush all open files (e.g. logs) struct poller_timer date_chg_tmr; ///< Print a date change @@ -2129,6 +2184,8 @@ struct app_context char *editor_filename; ///< The file being edited by user int terminal_suspended; ///< Terminal suspension level + // Plugins: + struct plugin *plugins; ///< Loaded plugins struct hook *input_hooks; ///< Input hooks struct hook *irc_hooks; ///< IRC hooks @@ -2197,6 +2254,8 @@ app_context_init (struct app_context *self) self->config = config_make (); poller_init (&self->poller); + self->relay_fd = -1; + self->servers = str_map_make ((str_map_free_fn) server_unref); self->servers.key_xfrm = tolower_ascii_strxfrm; @@ -2222,6 +2281,17 @@ app_context_init (struct app_context *self) filter_color_cube_for_acceptable_nick_colors (&self->nick_palette_len); } +static void +app_context_relay_stop (struct app_context *self) +{ + if (self->relay_fd != -1) + { + poller_fd_reset (&self->relay_event); + xclose (self->relay_fd); + self->relay_fd = -1; + } +} + static void app_context_free (struct app_context *self) { @@ -2247,6 +2317,11 @@ app_context_free (struct app_context *self) } str_map_free (&self->buffers_by_name); + app_context_relay_stop (self); + LIST_FOR_EACH (struct client, c, self->clients) + client_kill (c); + relay_event_message_free (&self->relay_message); + str_map_free (&self->servers); poller_free (&self->poller); @@ -2285,6 +2360,7 @@ on_config_show_all_prefixes_change (struct config_item *item) refresh_prompt (ctx); } +static void on_config_relay_bind_change (struct config_item *item); static void on_config_backlog_limit_change (struct config_item *item); static void on_config_attribute_change (struct config_item *item); static void on_config_logging_change (struct config_item *item); @@ -2479,6 +2555,11 @@ static struct config_schema g_config_general[] = .comment = "Plugins to automatically load on start", .type = CONFIG_ITEM_STRING_ARRAY, .validate = config_validate_nonjunk_string }, + { .name = "relay_bind", + .comment = "Address to bind to for a user interface relay point", + .type = CONFIG_ITEM_STRING, + .validate = config_validate_nonjunk_string, + .on_change = on_config_relay_bind_change }, // Buffer history: { .name = "backlog_limit", @@ -2681,6 +2762,418 @@ serialize_configuration (struct config_item *root, struct str *output) config_item_write (root, true, output); } +// --- Relay plumbing ---------------------------------------------------------- + +static void +client_kill (struct client *c) +{ + struct app_context *ctx = c->ctx; + poller_fd_reset (&c->socket_event); + xclose (c->socket_fd); + c->socket_fd = -1; + + LIST_UNLINK (ctx->clients, c); + client_destroy (c); +} + +static bool +client_try_read (struct client *c) +{ + struct str *buf = &c->read_buffer; + ssize_t n_read; + + while ((n_read = read (c->socket_fd, buf->str + buf->len, + buf->alloc - buf->len - 1 /* null byte */)) > 0) + { + buf->len += n_read; + if (!client_process_buffer (c)) + break; + str_reserve (buf, 512); + } + + if (n_read < 0) + { + if (errno == EAGAIN || errno == EINTR) + return true; + + print_debug ("%s: %s: %s", __func__, "read", strerror (errno)); + } + + client_kill (c); + return false; +} + +static bool +client_try_write (struct client *c) +{ + struct str *buf = &c->write_buffer; + ssize_t n_written; + + while (buf->len) + { + n_written = write (c->socket_fd, buf->str, buf->len); + if (n_written >= 0) + { + str_remove_slice (buf, 0, n_written); + continue; + } + if (errno == EAGAIN || errno == EINTR) + return true; + + print_debug ("%s: %s: %s", __func__, "write", strerror (errno)); + client_kill (c); + return false; + } + return true; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +static void +client_update_poller (struct client *c, const struct pollfd *pfd) +{ + int new_events = POLLIN; + if (c->write_buffer.len) + new_events |= POLLOUT; + + hard_assert (new_events != 0); + if (!pfd || pfd->events != new_events) + poller_fd_set (&c->socket_event, new_events); +} + +static void +on_client_ready (const struct pollfd *pfd, void *user_data) +{ + struct client *c = user_data; + if (client_try_read (c) && client_try_write (c)) + client_update_poller (c, pfd); +} + +static bool +relay_try_fetch_client (struct app_context *ctx, int listen_fd) +{ + // XXX: `struct sockaddr_storage' is not the most portable thing + struct sockaddr_storage peer; + socklen_t peer_len = sizeof peer; + + int fd = accept (listen_fd, (struct sockaddr *) &peer, &peer_len); + if (fd == -1) + { + if (errno == EAGAIN || errno == EWOULDBLOCK) + return false; + if (errno == EINTR) + return true; + + if (accept_error_is_transient (errno)) + print_warning ("%s: %s", "accept", strerror (errno)); + else + print_fatal ("%s: %s", "accept", strerror (errno)); + return true; + } + + hard_assert (peer_len <= sizeof peer); + set_blocking (fd, false); + set_cloexec (fd); + + // We already buffer our output, so reduce latencies. + int yes = 1; + soft_assert (setsockopt (fd, IPPROTO_TCP, TCP_NODELAY, + &yes, sizeof yes) != -1); + + struct client *c = client_new (); + c->ctx = ctx; + c->socket_fd = fd; + LIST_PREPEND (ctx->clients, c); + + c->socket_event = poller_fd_make (&c->ctx->poller, c->socket_fd); + c->socket_event.dispatcher = (poller_fd_fn) on_client_ready; + c->socket_event.user_data = c; + + client_update_poller (c, NULL); + return true; +} + +static void +on_relay_client_available (const struct pollfd *pfd, void *user_data) +{ + struct app_context *ctx = user_data; + while (relay_try_fetch_client (ctx, pfd->fd)) + ; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +static int +relay_listen (struct addrinfo *ai, struct error **e) +{ + int fd = socket (ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (fd == -1) + { + error_set (e, "socket: %s", strerror (errno)); + return -1; + } + + set_cloexec (fd); + + int yes = 1; + soft_assert (setsockopt (fd, SOL_SOCKET, SO_KEEPALIVE, + &yes, sizeof yes) != -1); + soft_assert (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, + &yes, sizeof yes) != -1); + + if (bind (fd, ai->ai_addr, ai->ai_addrlen)) + error_set (e, "bind: %s", strerror (errno)); + else if (listen (fd, 16 /* arbitrary number */)) + error_set (e, "listen: %s", strerror (errno)); + else + return fd; + + xclose (fd); + return -1; +} + +static int +relay_listen_with_context (struct addrinfo *ai, struct error **e) +{ + char *address = gai_reconstruct_address (ai); + print_debug ("binding to `%s'", address); + + struct error *error = NULL; + int fd = relay_listen (ai, &error); + if (fd == -1) + { + error_set (e, "binding to `%s' failed: %s", address, error->message); + error_free (error); + } + free (address); + return fd; +} + +static bool +relay_start (struct app_context *ctx, char *address, struct error **e) +{ + const char *port = NULL, *host = tokenize_host_port (address, &port); + if (!port || !*port) + return error_set (e, "missing port"); + + struct addrinfo hints = {}, *result = NULL; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + + int err = getaddrinfo (*host ? host : NULL, port, &hints, &result); + if (err) + { + return error_set (e, "failed to resolve `%s', port `%s': %s: %s", + host, port, "getaddrinfo", gai_strerror (err)); + } + + // Just try the first one, disregarding IPv4/IPv6 ordering. + int fd = relay_listen_with_context (result, e); + freeaddrinfo (result); + if (fd == -1) + return false; + + set_blocking (fd, false); + + struct poller_fd *event = &ctx->relay_event; + *event = poller_fd_make (&ctx->poller, fd); + event->dispatcher = (poller_fd_fn) on_relay_client_available; + event->user_data = ctx; + + ctx->relay_fd = fd; + poller_fd_set (event, POLLIN); + return true; +} + +static void +on_config_relay_bind_change (struct config_item *item) +{ + struct app_context *ctx = item->user_data; + char *value = item->value.string.str; + app_context_relay_stop (ctx); + if (!value) + return; + + struct error *e = NULL; + char *address = xstrdup (value); + if (!relay_start (ctx, address, &e)) + { + // TODO: Try to make sure this finds its way to the global buffer. + print_error ("%s: %s", item->schema->name, e->message); + error_free (e); + } + free (address); +} + +// --- Relay output ------------------------------------------------------------ + +static void +relay_send (struct client *c) +{ + struct relay_event_message *m = &c->ctx->relay_message; + m->event_seq = c->event_seq++; + + // TODO: Also don't try sending anything if half-closed. + if (!c->initialized || c->socket_fd == -1) + return; + + // liberty has msg_{reader,writer} already, but they use 8-byte lengths. + size_t frame_len_pos = c->write_buffer.len, frame_len = 0; + str_pack_u32 (&c->write_buffer, 0); + if (!relay_event_message_serialize (m, &c->write_buffer) + || (frame_len = c->write_buffer.len - frame_len_pos - 4) > UINT32_MAX) + { + print_error ("serialization failed, killing client"); + client_kill (c); + return; + } + + uint32_t len = htonl (frame_len); + memcpy (c->write_buffer.str + frame_len_pos, &len, sizeof len); + client_update_poller (c, NULL); +} + +static void +relay_broadcast (struct app_context *ctx) +{ + LIST_FOR_EACH (struct client, c, ctx->clients) + relay_send (c); +} + +static struct relay_event_message * +relay_prepare (struct app_context *ctx) +{ + struct relay_event_message *m = &ctx->relay_message; + relay_event_message_free (m); + memset (m, 0, sizeof *m); + return m; +} + +static void +relay_prepare_ping (struct app_context *ctx) +{ + relay_prepare (ctx)->data.event = RELAY_EVENT_PING; +} + +static void +relay_prepare_buffer_update (struct app_context *ctx, struct buffer *buffer) +{ + struct relay_event_message *m = relay_prepare (ctx); + struct relay_event_data_buffer_update *e = &m->data.buffer_update; + e->event = RELAY_EVENT_BUFFER_UPDATE; + e->buffer_name = str_from_cstr (buffer->name); +} + +static void +relay_prepare_buffer_rename (struct app_context *ctx, struct buffer *buffer, + const char *new_name) +{ + struct relay_event_message *m = relay_prepare (ctx); + struct relay_event_data_buffer_rename *e = &m->data.buffer_rename; + e->event = RELAY_EVENT_BUFFER_RENAME; + e->buffer_name = str_from_cstr (buffer->name); + e->new = str_from_cstr (new_name); +} + +static void +relay_prepare_buffer_remove (struct app_context *ctx, struct buffer *buffer) +{ + struct relay_event_message *m = relay_prepare (ctx); + struct relay_event_data_buffer_remove *e = &m->data.buffer_remove; + e->event = RELAY_EVENT_BUFFER_REMOVE; + e->buffer_name = str_from_cstr (buffer->name); +} + +static void +relay_prepare_buffer_activate (struct app_context *ctx, struct buffer *buffer) +{ + struct relay_event_message *m = relay_prepare (ctx); + struct relay_event_data_buffer_activate *e = &m->data.buffer_activate; + e->event = RELAY_EVENT_BUFFER_ACTIVATE; + e->buffer_name = str_from_cstr (buffer->name); +} + +static void +relay_prepare_buffer_line (struct app_context *ctx, struct buffer *buffer, + struct buffer_line *line) +{ + struct relay_event_message *m = relay_prepare (ctx); + struct relay_event_data_buffer_line *e = &m->data.buffer_line; + e->event = RELAY_EVENT_BUFFER_LINE; + e->buffer_name = str_from_cstr (buffer->name); + e->is_unimportant = !!(line->flags & BUFFER_LINE_UNIMPORTANT); + e->is_highlight = !!(line->flags & BUFFER_LINE_HIGHLIGHT); + e->rendition = 1 + line->r; + e->when = line->when; + + size_t len = 0; + for (size_t i = 0; line->items[i].type; i++) + len++; + + // XXX: This way helps xP's JSON conversion, but is super annoying for us. + union relay_item_data *p = e->items = xcalloc (len * 6, sizeof *e->items); + for (struct formatter_item *i = line->items; len--; i++) + { + switch (i->type) + { + case FORMATTER_ITEM_TEXT: + p->text.text = str_from_cstr (i->text); + (p++)->kind = RELAY_ITEM_TEXT; + break; + case FORMATTER_ITEM_ATTR: + // For future consideration. + (p++)->kind = RELAY_ITEM_RESET; + break; + case FORMATTER_ITEM_FG_COLOR: + p->fg_color.color = i->color; + (p++)->kind = RELAY_ITEM_FG_COLOR; + break; + case FORMATTER_ITEM_BG_COLOR: + p->bg_color.color = i->color; + (p++)->kind = RELAY_ITEM_BG_COLOR; + break; + case FORMATTER_ITEM_SIMPLE: + if (i->attribute & TEXT_BOLD) + (p++)->kind = RELAY_ITEM_FLIP_BOLD; + if (i->attribute & TEXT_ITALIC) + (p++)->kind = RELAY_ITEM_FLIP_ITALIC; + if (i->attribute & TEXT_UNDERLINE) + (p++)->kind = RELAY_ITEM_FLIP_UNDERLINE; + if (i->attribute & TEXT_INVERSE) + (p++)->kind = RELAY_ITEM_FLIP_INVERSE; + if (i->attribute & TEXT_CROSSED_OUT) + (p++)->kind = RELAY_ITEM_FLIP_CROSSED_OUT; + if (i->attribute & TEXT_MONOSPACE) + (p++)->kind = RELAY_ITEM_FLIP_MONOSPACE; + break; + default: + break; + } + } + + e->items_len = p - e->items; +} + +static void +relay_prepare_buffer_clear (struct app_context *ctx, + struct buffer *buffer) +{ + struct relay_event_message *m = relay_prepare (ctx); + struct relay_event_data_buffer_clear *e = &m->data.buffer_clear; + e->event = RELAY_EVENT_BUFFER_CLEAR; + e->buffer_name = str_from_cstr (buffer->name); +} + +static void +relay_prepare_error (struct app_context *ctx, uint32_t seq, const char *message) +{ + struct relay_event_message *m = relay_prepare (ctx); + struct relay_event_data_error *e = &m->data.error; + e->event = RELAY_EVENT_ERROR; + e->command_seq = seq; + e->error = str_from_cstr (message); +} + // --- Terminal output --------------------------------------------------------- /// Default colour pair @@ -4089,6 +4582,9 @@ log_formatter (struct app_context *ctx, struct buffer *buffer, if (buffer->log_file) buffer_line_write_to_log (ctx, line, buffer->log_file); + relay_prepare_buffer_line (ctx, buffer, line); + relay_broadcast (ctx); + bool unseen_pm = buffer->type == BUFFER_PM && buffer != ctx->current_buffer && !(flags & BUFFER_LINE_UNIMPORTANT); @@ -4302,6 +4798,9 @@ buffer_add (struct app_context *ctx, struct buffer *buffer) buffer_open_log_file (ctx, buffer); + relay_prepare_buffer_update (ctx, buffer); + relay_broadcast (ctx); + // Normally this doesn't cause changes in the prompt but a prompt hook // could decide to show some information for all buffers nonetheless refresh_prompt (ctx); @@ -4328,6 +4827,9 @@ buffer_remove (struct app_context *ctx, struct buffer *buffer) if (buffer->type == BUFFER_SERVER) buffer->server->buffer = NULL; + relay_prepare_buffer_remove (ctx, buffer); + relay_broadcast (ctx); + str_map_set (&ctx->buffers_by_name, buffer->name, NULL); LIST_UNLINK_WITH_TAIL (ctx->buffers, ctx->buffers_tail, buffer); buffer_unref (buffer); @@ -4457,6 +4959,9 @@ buffer_activate (struct app_context *ctx, struct buffer *buffer) ctx->last_buffer = ctx->current_buffer; ctx->current_buffer = buffer; + relay_prepare_buffer_activate (ctx, buffer); + relay_broadcast (ctx); + refresh_prompt (ctx); } @@ -4491,12 +4996,19 @@ buffer_merge (struct app_context *ctx, merged->lines_tail = start->prev; merged->lines_count -= n; - // And append them to current lines in the buffer + // Append them to current lines in the buffer buffer->lines_tail->next = start; start->prev = buffer->lines_tail; buffer->lines_tail = tail; buffer->lines_count += n; + // And since there is no log_*() call, send them to relays manually + LIST_FOR_EACH (struct buffer_line, line, start) + { + relay_prepare_buffer_line (ctx, buffer, line); + relay_broadcast (ctx); + } + log_full (ctx, NULL, buffer, BUFFER_LINE_SKIP_FILE, BUFFER_LINE_STATUS, "End of merged content"); } @@ -4511,6 +5023,9 @@ buffer_rename (struct app_context *ctx, hard_assert (!collision); + relay_prepare_buffer_rename (ctx, buffer, new_name); + relay_broadcast (ctx); + str_map_set (&ctx->buffers_by_name, buffer->name, NULL); str_map_set (&ctx->buffers_by_name, new_name, buffer); @@ -4524,13 +5039,16 @@ buffer_rename (struct app_context *ctx, } static void -buffer_clear (struct buffer *buffer) +buffer_clear (struct app_context *ctx, struct buffer *buffer) { LIST_FOR_EACH (struct buffer_line, iter, buffer->lines) buffer_line_destroy (iter); buffer->lines = buffer->lines_tail = NULL; buffer->lines_count = 0; + + relay_prepare_buffer_clear (ctx, buffer); + relay_broadcast (ctx); } static struct buffer * @@ -5947,29 +6465,6 @@ irc_finish_connection (struct server *s, int socket, const char *hostname) refresh_prompt (s->ctx); } -/// Unwrap IPv6 addresses in format_host_port_pair() format -static void -irc_split_host_port (char *s, char **host, char **port) -{ - *host = s; - *port = "6667"; - - char *right_bracket = strchr (s, ']'); - if (s[0] == '[' && right_bracket) - { - *right_bracket = '\0'; - *host = s + 1; - s = right_bracket + 1; - } - - char *colon = strchr (s, ':'); - if (colon) - { - *colon = '\0'; - *port = colon + 1; - } -} - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static void @@ -6019,8 +6514,8 @@ irc_setup_connector (struct server *s, const struct strv *addresses) for (size_t i = 0; i < addresses->len; i++) { - char *host, *port; - irc_split_host_port (addresses->vector[i], &host, &port); + const char *port = "6667", + *host = tokenize_host_port (addresses->vector[i], &port); connector_add_target (connector, host, port); } } @@ -6062,9 +6557,8 @@ irc_setup_connector_socks (struct server *s, const struct strv *addresses, for (size_t i = 0; i < addresses->len; i++) { - char *host, *port; - irc_split_host_port (addresses->vector[i], &host, &port); - + const char *port = "6667", + *host = tokenize_host_port (addresses->vector[i], &port); if (!socks_connector_add_target (connector, host, port, e)) return false; } @@ -7644,7 +8138,7 @@ irc_on_registered (struct server *s, const char *nickname) if (command) { log_server_debug (s, "Executing \"#s\"", command); - process_input_utf8 (s->ctx, s->buffer, command, 0); + (void) process_input_utf8 (s->ctx, s->buffer, command, 0); } int64_t command_delay = get_config_integer (s->config, "command_delay"); @@ -8229,6 +8723,24 @@ irc_handle_rpl_isupport (struct server *s, const struct irc_message *msg) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +static void +irc_adjust_motd (char **motd) +{ + // Heuristic, force MOTD to be monospace in graphical frontends. + if (!strchr (*motd, '\x11')) + { + struct str s = str_make (); + str_append_c (&s, '\x11'); + for (const char *p = *motd; *p; p++) + { + str_append_c (&s, *p); + if (*p == '\x0f') + str_append_c (&s, '\x11'); + } + cstr_set (motd, str_steal (&s)); + } +} + static void irc_process_numeric (struct server *s, const struct irc_message *msg, unsigned long numeric) @@ -8251,6 +8763,10 @@ irc_process_numeric (struct server *s, if (msg->params.len == 2) irc_try_parse_welcome_for_userhost (s, msg->params.vector[1]); break; + case IRC_RPL_MOTD: + if (copy.len) + irc_adjust_motd (©.vector[0]); + break; case IRC_RPL_ISUPPORT: irc_handle_rpl_isupport (s, msg); break; @@ -9248,7 +9764,7 @@ lua_buffer_execute (lua_State *L) struct lua_weak *wrapper = lua_weak_deref (L, &lua_buffer_info); struct buffer *buffer = wrapper->object; const char *line = lua_plugin_check_utf8 (L, 2); - process_input_utf8 (wrapper->plugin->ctx, buffer, line, 0); + (void) process_input_utf8 (wrapper->plugin->ctx, buffer, line, 0); return 0; } @@ -11304,7 +11820,7 @@ handle_command_buffer (struct handler_args *a) show_buffers_list (ctx); else if (!strcasecmp_ascii (action, "clear")) { - buffer_clear (a->buffer); + buffer_clear (ctx, a->buffer); if (a->buffer == ctx->current_buffer) buffer_print_backlog (ctx, a->buffer); } @@ -12926,8 +13442,8 @@ complete_set_value_array (struct config_item *item, const char *word, cstr_split (item->value.string.str, ",", false, &items); for (size_t i = 0; i < items.len; i++) { - struct str wrapped = str_make (), serialized = str_make (); - str_append (&wrapped, items.vector[i]); + struct str wrapped = str_from_cstr (items.vector[i]); + struct str serialized = str_make (); config_item_write_string (&serialized, &wrapped); str_free (&wrapped); @@ -13546,6 +14062,25 @@ on_display_backlog_nowrap (int count, int key, void *user_data) return display_backlog (user_data, FLUSH_OPT_NOWRAP); } +static FILE * +open_log_path (struct app_context *ctx, struct buffer *buffer, const char *path) +{ + FILE *fp = fopen (path, "rb"); + if (!fp) + { + log_global_error (ctx, + "Failed to open `#l': #l", path, strerror (errno)); + return NULL; + } + + if (buffer->log_file) + // The regular flush will log any error eventually + (void) fflush (buffer->log_file); + + set_cloexec (fileno (fp)); + return fp; +} + static bool on_display_full_log (int count, int key, void *user_data) { @@ -13555,20 +14090,13 @@ on_display_full_log (int count, int key, void *user_data) struct buffer *buffer = ctx->current_buffer; char *path = buffer_get_log_path (buffer); - FILE *full_log = fopen (path, "rb"); + FILE *full_log = open_log_path (ctx, buffer, path); if (!full_log) { - log_global_error (ctx, "Failed to open log file for #s: #l", - ctx->current_buffer->name, strerror (errno)); free (path); return false; } - if (buffer->log_file) - // The regular flush will log any error eventually - (void) fflush (buffer->log_file); - - set_cloexec (fileno (full_log)); launch_pager (ctx, fileno (full_log), buffer->name, path); fclose (full_log); free (path); @@ -14601,6 +15129,177 @@ init_poller_events (struct app_context *ctx) ctx->input_event.user_data = ctx; } +// --- Relay processing -------------------------------------------------------- + +// XXX: This could be below completion code if reset_autoaway() was higher up. + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +static void +client_resync (struct client *c) +{ + LIST_FOR_EACH (struct buffer, buffer, c->ctx->buffers) + { + relay_prepare_buffer_update (c->ctx, buffer); + relay_send (c); + + LIST_FOR_EACH (struct buffer_line, line, buffer->lines) + { + relay_prepare_buffer_line (c->ctx, buffer, line); + relay_send (c); + } + } + + relay_prepare_buffer_activate (c->ctx, c->ctx->current_buffer); + relay_send (c); +} + +static const char * +client_message_buffer_name (const struct relay_command_message *m) +{ + switch (m->data.command) + { + case RELAY_COMMAND_BUFFER_COMPLETE: + return m->data.buffer_input.buffer_name.str; + case RELAY_COMMAND_BUFFER_INPUT: + return m->data.buffer_input.buffer_name.str; + case RELAY_COMMAND_BUFFER_ACTIVATE: + return m->data.buffer_activate.buffer_name.str; + case RELAY_COMMAND_BUFFER_LOG: + return m->data.buffer_log.buffer_name.str; + default: + return NULL; + } +} + +static void +client_process_buffer_log + (struct client *c, uint32_t seq, struct buffer *buffer) +{ + struct relay_event_message *m = relay_prepare (c->ctx); + struct relay_event_data_response *e = &m->data.response; + e->event = RELAY_EVENT_RESPONSE; + e->command_seq = seq; + e->data.command = RELAY_COMMAND_BUFFER_LOG; + + char *path = buffer_get_log_path (buffer); + FILE *fp = open_log_path (c->ctx, buffer, path); + if (fp) + { + struct str log = str_make (); + char buf[BUFSIZ]; + size_t len; + while ((len = fread (buf, 1, sizeof buf, fp))) + str_append_data (&log, buf, len); + if (ferror (fp)) + log_global_error (c->ctx, "Failed to read `#l': #l", + path, strerror (errno)); + + // On overflow, it will later fail serialization. + e->data.buffer_log.log_len = MIN (UINT32_MAX, log.len); + e->data.buffer_log.log = (uint8_t *) str_steal (&log); + fclose (fp); + } + + // XXX: We log failures to the global buffer, + // so the client just receives nothing if there is no log file. + + free (path); + relay_send (c); +} + +static bool +client_process_message (struct client *c, + struct msg_unpacker *r, struct relay_command_message *m) +{ + if (!relay_command_message_deserialize (m, r) + || msg_unpacker_get_available (r)) + { + print_error ("deserialization failed, killing client"); + return false; + } + + const char *buffer_name = client_message_buffer_name (m); + struct buffer *buffer = NULL; + if (buffer_name && !(buffer = buffer_by_name (c->ctx, buffer_name))) + { + relay_prepare_error (c->ctx, m->command_seq, "Unknown buffer"); + relay_send (c); + return true; + } + + switch (m->data.command) + { + case RELAY_COMMAND_HELLO: + if (m->data.hello.version != RELAY_VERSION) + { + // TODO: This should send back an error message and shut down. + print_error ("protocol version mismatch, killing client"); + return false; + } + c->initialized = true; + client_resync (c); + break; + case RELAY_COMMAND_PING: + relay_prepare_ping (c->ctx); + relay_send (c); + break; + case RELAY_COMMAND_ACTIVE: + reset_autoaway (c->ctx); + break; + case RELAY_COMMAND_BUFFER_COMPLETE: + // TODO: Run the completion machinery. + relay_prepare_error (c->ctx, m->command_seq, "Not implemented"); + relay_send (c); + break; + case RELAY_COMMAND_BUFFER_INPUT: + (void) process_input_utf8 (c->ctx, + buffer, m->data.buffer_input.text.str, 0); + break; + case RELAY_COMMAND_BUFFER_ACTIVATE: + buffer_activate (c->ctx, buffer); + break; + case RELAY_COMMAND_BUFFER_LOG: + client_process_buffer_log (c, m->command_seq, buffer); + break; + default: + print_warning ("unhandled client command"); + relay_prepare_error (c->ctx, m->command_seq, "Unknown command"); + relay_send (c); + } + return true; +} + +static bool +client_process_buffer (struct client *c) +{ + struct str *buf = &c->read_buffer; + size_t offset = 0; + while (true) + { + uint32_t frame_len = 0; + struct msg_unpacker r = + msg_unpacker_make (buf->str + offset, buf->len - offset); + if (!msg_unpacker_u32 (&r, &frame_len)) + break; + + r.len = MIN (r.len, sizeof frame_len + frame_len); + if (msg_unpacker_get_available (&r) < frame_len) + break; + + struct relay_command_message m = {}; + bool ok = client_process_message (c, &r, &m); + relay_command_message_free (&m); + if (!ok) + return false; + + offset += r.offset; + } + + str_remove_slice (buf, 0, offset); + return true; +} + // --- Tests ------------------------------------------------------------------- // The application is quite monolithic and can only be partially unit-tested. -- cgit v1.2.3