From 89997b97a0fb7f0fe0fa06de5409a03ed9ee91a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 3 Nov 2019 00:22:22 +0100 Subject: [PATCH 1/6] fdm: add fdm_event_add() and fdm_event_del() These functions allow users to modify already registered FDs, to add or remove events they are interested in. --- fdm.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++++- fdm.h | 3 +++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/fdm.c b/fdm.c index e626b47f..cc0148e1 100644 --- a/fdm.c +++ b/fdm.c @@ -14,6 +14,7 @@ struct handler { int fd; + int events; fdm_handler_t callback; void *callback_data; bool deleted; @@ -78,6 +79,7 @@ fdm_add(struct fdm *fdm, int fd, int events, fdm_handler_t handler, void *data) struct handler *fd_data = malloc(sizeof(*fd_data)); *fd_data = (struct handler) { .fd = fd, + .events = events, .callback = handler, .callback_data = data, .deleted = false, @@ -91,7 +93,7 @@ fdm_add(struct fdm *fdm, int fd, int events, fdm_handler_t handler, void *data) }; if (epoll_ctl(fdm->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) { - LOG_ERRNO("failed to register FD with epoll"); + LOG_ERRNO("failed to register FD=%d with epoll", fd); free(fd_data); tll_pop_back(fdm->fds); return false; @@ -141,6 +143,55 @@ fdm_del_no_close(struct fdm *fdm, int fd) return fdm_del_internal(fdm, fd, false); } +static bool +event_modify(struct fdm *fdm, struct handler *fd, int new_events) +{ + if (new_events == fd->events) + return true; + + struct epoll_event ev = { + .events = new_events, + .data = {.ptr = fd}, + }; + + if (epoll_ctl(fdm->epoll_fd, EPOLL_CTL_MOD, fd->fd, &ev) < 0) { + LOG_ERRNO("failed to modify FD=%d with epoll (events 0x%08x -> 0x%08x)", + fd->fd, fd->events, new_events); + return false; + } + + fd->events = new_events; + return true; +} + +bool +fdm_event_add(struct fdm *fdm, int fd, int events) +{ + tll_foreach(fdm->fds, it) { + if (it->item->fd != fd) + continue; + + return event_modify(fdm, it->item, it->item->events | events); + } + + LOG_ERR("FD=%d not registered with the FDM", fd); + return false; +} + +bool +fdm_event_del(struct fdm *fdm, int fd, int events) +{ + tll_foreach(fdm->fds, it) { + if (it->item->fd != fd) + continue; + + return event_modify(fdm, it->item, it->item->events & ~events); + } + + LOG_ERR("FD=%d not registered with the FDM", fd); + return false; +} + bool fdm_poll(struct fdm *fdm) { diff --git a/fdm.h b/fdm.h index fb12bffb..62d73ded 100644 --- a/fdm.h +++ b/fdm.h @@ -13,4 +13,7 @@ bool fdm_add(struct fdm *fdm, int fd, int events, fdm_handler_t handler, void *d bool fdm_del(struct fdm *fdm, int fd); bool fdm_del_no_close(struct fdm *fdm, int fd); +bool fdm_event_add(struct fdm *fdm, int fd, int events); +bool fdm_event_del(struct fdm *fdm, int fd, int events); + bool fdm_poll(struct fdm *fdm); From 79c3121aa34dbee4c3557fa058a864f229f8e313 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 3 Nov 2019 00:25:17 +0100 Subject: [PATCH 2/6] misc: fdm already logs failures --- server.c | 5 +---- terminal.c | 22 ++++------------------ wayland.c | 11 +++-------- 3 files changed, 8 insertions(+), 30 deletions(-) diff --git a/server.c b/server.c index 4ca2ada6..42b7ec2b 100644 --- a/server.c +++ b/server.c @@ -196,7 +196,6 @@ fdm_server(struct fdm *fdm, int fd, int events, void *data) }; if (!fdm_add(server->fdm, client_fd, EPOLLIN, &fdm_client, client)) { - LOG_ERR("client FD=%d: failed to add client to FDM", client_fd); close(client_fd); free(client); return false; @@ -321,10 +320,8 @@ server_init(const struct config *conf, struct fdm *fdm, struct wayland *wayl) .clients = tll_init(), }; - if (!fdm_add(fdm, fd, EPOLLIN, &fdm_server, server)) { - LOG_ERR("failed to add server FD to the FDM"); + if (!fdm_add(fdm, fd, EPOLLIN, &fdm_server, server)) goto err; - } return server; diff --git a/terminal.c b/terminal.c index ccc6ec41..2a362b70 100644 --- a/terminal.c +++ b/terminal.c @@ -328,25 +328,12 @@ term_init(const struct config *conf, struct fdm *fdm, struct wayland *wayl, goto close_fds; } - if (!fdm_add(fdm, ptmx, EPOLLIN, &fdm_ptmx, term)) { - LOG_ERR("failed to add ptmx to FDM"); - goto err; - } - - if (!fdm_add(fdm, flash_fd, EPOLLIN, &fdm_flash, term)) { - LOG_ERR("failed to add flash timer FD to FDM"); - goto err; - } - - if (!fdm_add(fdm, blink_fd, EPOLLIN, &fdm_blink, term)) { - LOG_ERR("failed to add blink tiemr FD to FDM"); - goto err; - } - - if (!fdm_add(fdm, delay_lower_fd, EPOLLIN, &fdm_delayed_render, term) || + if (!fdm_add(fdm, ptmx, EPOLLIN, &fdm_ptmx, term) || + !fdm_add(fdm, flash_fd, EPOLLIN, &fdm_flash, term) || + !fdm_add(fdm, blink_fd, EPOLLIN, &fdm_blink, term) || + !fdm_add(fdm, delay_lower_fd, EPOLLIN, &fdm_delayed_render, term) || !fdm_add(fdm, delay_upper_fd, EPOLLIN, &fdm_delayed_render, term)) { - LOG_ERR("failed to add delayed rendering timer FDs to FDM"); goto err; } @@ -538,7 +525,6 @@ term_shutdown(struct terminal *term) } if (!fdm_add(term->fdm, event_fd, EPOLLIN, &fdm_shutdown, term)) { - LOG_ERR("failed to add terminal shutdown event FD to the FDM"); close(event_fd); return false; } diff --git a/wayland.c b/wayland.c index 2bbf8c2e..f4cba71c 100644 --- a/wayland.c +++ b/wayland.c @@ -522,14 +522,9 @@ wayl_init(struct fdm *fdm) goto out; } - int wl_fd = wl_display_get_fd(wayl->display); - if (!fdm_add(fdm, wl_fd, EPOLLIN, &fdm_wayl, wayl)) { - LOG_ERR("failed to register Wayland connection with the FDM"); - goto out; - } - - if (!fdm_add(fdm, wayl->kbd.repeat.fd, EPOLLIN, &fdm_repeat, wayl)) { - LOG_ERR("failed to register keyboard repeat timer with the FDM"); + if (!fdm_add(fdm, wl_display_get_fd(wayl->display), EPOLLIN, &fdm_wayl, wayl) || + !fdm_add(fdm, wayl->kbd.repeat.fd, EPOLLIN, &fdm_repeat, wayl)) + { goto out; } From 9f1525aef778508e4fb12292693be5649c4c8c5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 3 Nov 2019 00:27:39 +0100 Subject: [PATCH 3/6] Rename: vt_to_slave() -> term_to_slave() --- csi.c | 8 ++++---- input.c | 8 ++++---- osc.c | 18 +++++++++--------- selection.c | 10 +++++----- terminal.c | 26 +++++++++++++++++++++++--- terminal.h | 1 + vt.c | 20 -------------------- vt.h | 1 - 8 files changed, 46 insertions(+), 46 deletions(-) diff --git a/csi.c b/csi.c index a7892e9a..a5e19290 100644 --- a/csi.c +++ b/csi.c @@ -325,7 +325,7 @@ csi_dispatch(struct terminal *term, uint8_t final) case 0: { switch (final) { case 'c': - vt_to_slave(term, "\033[?6c", 5); + term_to_slave(term, "\033[?6c", 5); break; case 'd': { @@ -690,7 +690,7 @@ csi_dispatch(struct terminal *term, uint8_t final) snprintf(reply, sizeof(reply), "\x1b[%d;%dR", term->cursor.row + 1, term->cursor.col + 1); - vt_to_slave(term, reply, strlen(reply)); + term_to_slave(term, reply, strlen(reply)); break; } @@ -910,7 +910,7 @@ csi_dispatch(struct terminal *term, uint8_t final) */ char reply[32]; snprintf(reply, sizeof(reply), "\033[?%u;2$y", param); - vt_to_slave(term, reply, strlen(reply)); + term_to_slave(term, reply, strlen(reply)); break; } @@ -965,7 +965,7 @@ csi_dispatch(struct terminal *term, uint8_t final) break; } - vt_to_slave(term, "\033[>41;347;0c", 12); + term_to_slave(term, "\033[>41;347;0c", 12); break; } diff --git a/input.c b/input.c index bee51244..40cfa613 100644 --- a/input.c +++ b/input.c @@ -258,7 +258,7 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, info->keypad_keys_mode != term->keypad_keys_mode) continue; - vt_to_slave(term, info->seq, strlen(info->seq)); + term_to_slave(term, info->seq, strlen(info->seq)); found_map = true; term_reset_view(term); @@ -314,14 +314,14 @@ keyboard_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, char reply[1024]; snprintf(reply, sizeof(reply), "\x1b[27;%d;%d~", modify_param, sym); - vt_to_slave(term, reply, strlen(reply)); + term_to_slave(term, reply, strlen(reply)); } else { if (effective_mods & alt) - vt_to_slave(term, "\x1b", 1); + term_to_slave(term, "\x1b", 1); - vt_to_slave(term, buf, count); + term_to_slave(term, buf, count); } term_reset_view(term); diff --git a/osc.c b/osc.c index e860edcd..6b86be03 100644 --- a/osc.c +++ b/osc.c @@ -78,7 +78,7 @@ from_clipboard_cb(const char *text, size_t size, void *user) assert(chunk != NULL); assert(strlen(chunk) == 4); - vt_to_slave(term, chunk, 4); + term_to_slave(term, chunk, 4); free(chunk); ctx->idx = 0; @@ -98,7 +98,7 @@ from_clipboard_cb(const char *text, size_t size, void *user) char *chunk = base64_encode((const uint8_t *)t, left / 3 * 3); assert(chunk != NULL); assert(strlen(chunk) % 4 == 0); - vt_to_slave(term, chunk, strlen(chunk)); + term_to_slave(term, chunk, strlen(chunk)); free(chunk); } @@ -120,9 +120,9 @@ osc_from_clipboard(struct terminal *term, const char *source) if (src == 0) return; - vt_to_slave(term, "\033]52;", 5); - vt_to_slave(term, &src, 1); - vt_to_slave(term, ";", 1); + term_to_slave(term, "\033]52;", 5); + term_to_slave(term, &src, 1); + term_to_slave(term, ";", 1); struct clip_context ctx = { .term = term, @@ -141,10 +141,10 @@ osc_from_clipboard(struct terminal *term, const char *source) if (ctx.idx > 0) { char res[4]; base64_encode_final(ctx.buf, ctx.idx, res); - vt_to_slave(term, res, 4); + term_to_slave(term, res, 4); } - vt_to_slave(term, "\033\\", 2); + term_to_slave(term, "\033\\", 2); } static void @@ -343,7 +343,7 @@ osc_dispatch(struct terminal *term) char reply[32]; snprintf(reply, sizeof(reply), "\033]4;%u;rgb:%02x/%02x/%02x\033\\", idx, r, g, b); - vt_to_slave(term, reply, strlen(reply)); + term_to_slave(term, reply, strlen(reply)); break; } @@ -377,7 +377,7 @@ osc_dispatch(struct terminal *term) reply, sizeof(reply), "\033]%u;rgb:%02x/%02x/%02x\033\\", param, r, g, b); - vt_to_slave(term, reply, strlen(reply)); + term_to_slave(term, reply, strlen(reply)); break; } diff --git a/selection.c b/selection.c index d2ed7354..6490fb06 100644 --- a/selection.c +++ b/selection.c @@ -533,7 +533,7 @@ static void from_clipboard_cb(const char *data, size_t size, void *user) { struct terminal *term = user; - vt_to_slave(term, data, size); + term_to_slave(term, data, size); } void @@ -544,12 +544,12 @@ selection_from_clipboard(struct terminal *term, uint32_t serial) return; if (term->bracketed_paste) - vt_to_slave(term, "\033[200~", 6); + term_to_slave(term, "\033[200~", 6); text_from_clipboard(term, serial, &from_clipboard_cb, term); if (term->bracketed_paste) - vt_to_slave(term, "\033[201~", 6); + term_to_slave(term, "\033[201~", 6); } bool @@ -675,12 +675,12 @@ selection_from_primary(struct terminal *term) return; if (term->bracketed_paste) - vt_to_slave(term, "\033[200~", 6); + term_to_slave(term, "\033[200~", 6); text_from_primary(term, &from_clipboard_cb, term); if (term->bracketed_paste) - vt_to_slave(term, "\033[201~", 6); + term_to_slave(term, "\033[201~", 6); } #if 0 diff --git a/terminal.c b/terminal.c index 2a362b70..98a5bcee 100644 --- a/terminal.c +++ b/terminal.c @@ -768,6 +768,26 @@ term_reset(struct terminal *term, bool hard) term_damage_all(term); } +bool +term_to_slave(struct terminal *term, const void *_data, size_t len) +{ + const uint8_t *data = _data; + size_t left = len; + + while (left > 0) { + ssize_t ret = write(term->ptmx, data, left); + if (ret < 0) { + LOG_ERRNO("failed to write to client"); + return false; + } + + data += ret; + left -= ret; + } + + return true; +} + void term_damage_rows(struct terminal *term, int start, int end) { @@ -1056,7 +1076,7 @@ term_focus_in(struct terminal *term) { if (!term->focus_events) return; - vt_to_slave(term, "\033[I", 3); + term_to_slave(term, "\033[I", 3); } void @@ -1064,7 +1084,7 @@ term_focus_out(struct terminal *term) { if (!term->focus_events) return; - vt_to_slave(term, "\033[O", 3); + term_to_slave(term, "\033[O", 3); } static int @@ -1138,7 +1158,7 @@ report_mouse_click(struct terminal *term, int encoded_button, int row, int col, return; } - vt_to_slave(term, response, strlen(response)); + term_to_slave(term, response, strlen(response)); } static void diff --git a/terminal.h b/terminal.h index 11571519..22eff5a9 100644 --- a/terminal.h +++ b/terminal.h @@ -303,6 +303,7 @@ bool term_shutdown(struct terminal *term); int term_destroy(struct terminal *term); void term_reset(struct terminal *term, bool hard); +bool term_to_slave(struct terminal *term, const void *data, size_t len); void term_damage_rows(struct terminal *term, int start, int end); void term_damage_rows_in_view(struct terminal *term, int start, int end); diff --git a/vt.c b/vt.c index a2ac1faf..41524857 100644 --- a/vt.c +++ b/vt.c @@ -1015,23 +1015,3 @@ vt_from_slave(struct terminal *term, const uint8_t *data, size_t len) } } } - -bool -vt_to_slave(struct terminal *term, const void *_data, size_t len) -{ - const uint8_t *data = _data; - size_t left = len; - - while (left > 0) { - ssize_t ret = write(term->ptmx, data, left); - if (ret < 0) { - LOG_ERRNO("failed to write to client"); - return false; - } - - data += ret; - left -= ret; - } - - return true; -} diff --git a/vt.h b/vt.h index 302dc6dc..0e85a75b 100644 --- a/vt.h +++ b/vt.h @@ -6,7 +6,6 @@ #include "terminal.h" void vt_from_slave(struct terminal *term, const uint8_t *data, size_t len); -bool vt_to_slave(struct terminal *term, const void *data, size_t len); static inline int vt_param_get(const struct terminal *term, size_t idx, int default_value) From 777d851282a59fcfacc19b9232b9ad80c412efdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 3 Nov 2019 00:42:34 +0100 Subject: [PATCH 4/6] fdm: invert check to get rid of one level of indentation --- fdm.c | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/fdm.c b/fdm.c index cc0148e1..72e32328 100644 --- a/fdm.c +++ b/fdm.c @@ -109,22 +109,23 @@ fdm_del_internal(struct fdm *fdm, int fd, bool close_fd) return true; tll_foreach(fdm->fds, it) { - if (it->item->fd == fd) { - if (epoll_ctl(fdm->epoll_fd, EPOLL_CTL_DEL, fd, NULL) < 0) - LOG_ERRNO("failed to unregister FD=%d from epoll", fd); + if (it->item->fd != fd) + continue; - if (close_fd) - close(it->item->fd); + if (epoll_ctl(fdm->epoll_fd, EPOLL_CTL_DEL, fd, NULL) < 0) + LOG_ERRNO("failed to unregister FD=%d from epoll", fd); - it->item->deleted = true; - if (fdm->is_polling) - tll_push_back(fdm->deferred_delete, it->item); - else - free(it->item); + if (close_fd) + close(it->item->fd); - tll_remove(fdm->fds, it); - return true; - } + it->item->deleted = true; + if (fdm->is_polling) + tll_push_back(fdm->deferred_delete, it->item); + else + free(it->item); + + tll_remove(fdm->fds, it); + return true; } LOG_ERR("no such FD: %d", fd); From f00c5fdac6e395859cf4971e5b7fa778f24336cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 3 Nov 2019 01:03:52 +0100 Subject: [PATCH 5/6] term: asynchronous writes to slave Make ptmx non-blocking. Then, when writing data to the slave would have blocked, use the FDM to asynchronously write the remaining data. This is done by enabling EPOLLOUT on ptmx, and enqueueing all outgoing data. The FDM handler will go through the enqueued data, and once all of it has been written, we turn off EPOLLOUT again (thus switching back to synchronous writes) --- terminal.c | 119 +++++++++++++++++++++++++++++++++++++++++++---------- terminal.h | 8 ++++ 2 files changed, 106 insertions(+), 21 deletions(-) diff --git a/terminal.c b/terminal.c index 98a5bcee..8174f0bc 100644 --- a/terminal.c +++ b/terminal.c @@ -26,15 +26,99 @@ #define min(x, y) ((x) < (y) ? (x) : (y)) #define max(x, y) ((x) > (y) ? (x) : (y)) +bool +term_to_slave(struct terminal *term, const void *_data, size_t len) +{ + if (tll_length(term->ptmx_buffer) > 0) + goto enqueue_data; + + const uint8_t *data = _data; + size_t left = len; + + while (left > 0) { + ssize_t ret = write(term->ptmx, data, left); + if (ret < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + LOG_ERRNO("failed to write to client"); + return false; + } + + if (!fdm_event_add(term->fdm, term->ptmx, EPOLLOUT)) + return false; + + goto enqueue_data; + } + + data += ret; + left -= ret; + } + + return true; + +enqueue_data: + { + void *copy = malloc(len); + memcpy(copy, _data, len); + + struct ptmx_buffer queued = { + .data = copy, + .len = len, + .idx = 0, + }; + tll_push_back(term->ptmx_buffer, queued); + } + + return true; +} + +static bool +fdm_ptmx_out(struct fdm *fdm, int fd, int events, void *data) +{ + struct terminal *term = data; + assert(tll_length(term->ptmx_buffer) > 0); + + tll_foreach(term->ptmx_buffer, it) { + const uint8_t *const data = it->item.data; + size_t left = it->item.len - it->item.idx; + + while (left > 0) { + ssize_t ret = write(term->ptmx, &data[it->item.idx], left); + if (ret < 0) { + if (errno != EAGAIN && errno != EWOULDBLOCK) { + LOG_ERRNO("failed to write to client"); + return false; + } + + return true; + } + + it->item.idx += ret; + left -= ret; + } + + free(it->item.data); + tll_remove(term->ptmx_buffer, it); + } + + fdm_event_del(term->fdm, term->ptmx, EPOLLOUT); + return true; +} + static bool fdm_ptmx(struct fdm *fdm, int fd, int events, void *data) { struct terminal *term = data; + if (events & EPOLLOUT) { + if (!fdm_ptmx_out(fdm, fd, events, data)) + return false; + } + if ((events & EPOLLHUP) && !(events & EPOLLIN)) return term_shutdown(term); - assert(events & EPOLLIN); + if (!(events & EPOLLIN)) + return true; uint8_t buf[24 * 1024]; ssize_t count = read(term->ptmx, buf, sizeof(buf)); @@ -328,6 +412,14 @@ term_init(const struct config *conf, struct fdm *fdm, struct wayland *wayl, goto close_fds; } + int ptmx_flags; + if ((ptmx_flags = fcntl(ptmx, F_GETFL)) < 0 || + fcntl(ptmx, F_SETFL, ptmx_flags | O_NONBLOCK) < 0) + { + LOG_ERRNO("failed to configure ptmx as non-blocking"); + goto err; + } + if (!fdm_add(fdm, ptmx, EPOLLIN, &fdm_ptmx, term) || !fdm_add(fdm, flash_fd, EPOLLIN, &fdm_flash, term) || !fdm_add(fdm, blink_fd, EPOLLIN, &fdm_blink, term) || @@ -342,6 +434,7 @@ term_init(const struct config *conf, struct fdm *fdm, struct wayland *wayl, .fdm = fdm, .quit = false, .ptmx = ptmx, + .ptmx_buffer = tll_init(), .cursor_keys_mode = CURSOR_KEYS_NORMAL, .keypad_keys_mode = KEYPAD_NUMERICAL, .auto_margin = true, @@ -615,6 +708,10 @@ term_destroy(struct terminal *term) assert(tll_length(term->render.workers.queue) == 0); tll_free(term->render.workers.queue); + tll_foreach(term->ptmx_buffer, it) + free(it->item.data); + tll_free(term->ptmx_buffer); + int ret = EXIT_SUCCESS; if (term->slave > 0) { @@ -768,26 +865,6 @@ term_reset(struct terminal *term, bool hard) term_damage_all(term); } -bool -term_to_slave(struct terminal *term, const void *_data, size_t len) -{ - const uint8_t *data = _data; - size_t left = len; - - while (left > 0) { - ssize_t ret = write(term->ptmx, data, left); - if (ret < 0) { - LOG_ERRNO("failed to write to client"); - return false; - } - - data += ret; - left -= ret; - } - - return true; -} - void term_damage_rows(struct terminal *term, int start, int end) { diff --git a/terminal.h b/terminal.h index 22eff5a9..0ed83877 100644 --- a/terminal.h +++ b/terminal.h @@ -145,6 +145,12 @@ enum mouse_reporting { enum cursor_style { CURSOR_BLOCK, CURSOR_UNDERLINE, CURSOR_BAR }; +struct ptmx_buffer { + void *data; + size_t len; + size_t idx; +}; + struct terminal { struct fdm *fdm; @@ -152,6 +158,8 @@ struct terminal { int ptmx; bool quit; + tll(struct ptmx_buffer) ptmx_buffer; + enum cursor_keys cursor_keys_mode; enum keypad_keys keypad_keys_mode; bool reverse; From cba1551b03254ad3efa87026d5ef2477f5eb652d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 3 Nov 2019 01:25:41 +0100 Subject: [PATCH 6/6] terminal: cleanup asynchronous ptmx output handling Break out actual writing to a separate function, and call this function both from the synchronous and the asynchronous code paths. --- terminal.c | 84 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 44 insertions(+), 40 deletions(-) diff --git a/terminal.c b/terminal.c index 8174f0bc..a34df466 100644 --- a/terminal.c +++ b/terminal.c @@ -26,34 +26,49 @@ #define min(x, y) ((x) < (y) ? (x) : (y)) #define max(x, y) ((x) > (y) ? (x) : (y)) +enum ptmx_write_status {PTMX_WRITE_DONE, PTMX_WRITE_REMAIN, PTMX_WRITE_ERR}; + +static enum ptmx_write_status +to_slave(struct terminal *term, const void *_data, size_t len, size_t *idx) +{ + const uint8_t *const data = _data; + size_t left = len - *idx; + + while (left > 0) { + ssize_t ret = write(term->ptmx, &data[*idx], left); + + if (ret < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) + return PTMX_WRITE_REMAIN; + + LOG_ERRNO("failed to write to client"); + return PTMX_WRITE_ERR; + } + + *idx += ret; + left -= left; + } + + return PTMX_WRITE_DONE; +} + bool term_to_slave(struct terminal *term, const void *_data, size_t len) { - if (tll_length(term->ptmx_buffer) > 0) + if (tll_length(term->ptmx_buffer) > 0) { + /* With a non-empty queue, EPOLLOUT has already been enabled */ goto enqueue_data; - - const uint8_t *data = _data; - size_t left = len; - - while (left > 0) { - ssize_t ret = write(term->ptmx, data, left); - if (ret < 0) { - if (errno != EAGAIN && errno != EWOULDBLOCK) { - LOG_ERRNO("failed to write to client"); - return false; - } - - if (!fdm_event_add(term->fdm, term->ptmx, EPOLLOUT)) - return false; - - goto enqueue_data; - } - - data += ret; - left -= ret; } - return true; + switch (to_slave(term, _data, len, &(size_t){0})) { + case PTMX_WRITE_REMAIN: + if (!fdm_event_add(term->fdm, term->ptmx, EPOLLOUT)) + return false; + goto enqueue_data; + + case PTMX_WRITE_DONE: return true; + case PTMX_WRITE_ERR: return false; + } enqueue_data: { @@ -78,26 +93,15 @@ fdm_ptmx_out(struct fdm *fdm, int fd, int events, void *data) assert(tll_length(term->ptmx_buffer) > 0); tll_foreach(term->ptmx_buffer, it) { - const uint8_t *const data = it->item.data; - size_t left = it->item.len - it->item.idx; + switch (to_slave(term, it->item.data, it->item.len, &it->item.idx)) { + case PTMX_WRITE_DONE: + free(it->item.data); + tll_remove(term->ptmx_buffer, it); + break; - while (left > 0) { - ssize_t ret = write(term->ptmx, &data[it->item.idx], left); - if (ret < 0) { - if (errno != EAGAIN && errno != EWOULDBLOCK) { - LOG_ERRNO("failed to write to client"); - return false; - } - - return true; - } - - it->item.idx += ret; - left -= ret; + case PTMX_WRITE_REMAIN: return true; /* to_slave() updated it->item.idx */ + case PTMX_WRITE_ERR: return false; } - - free(it->item.data); - tll_remove(term->ptmx_buffer, it); } fdm_event_del(term->fdm, term->ptmx, EPOLLOUT);