diff options
Diffstat (limited to 'autistdraw.c')
-rw-r--r-- | autistdraw.c | 450 |
1 files changed, 266 insertions, 184 deletions
diff --git a/autistdraw.c b/autistdraw.c index 41a2942..ce07540 100644 --- a/autistdraw.c +++ b/autistdraw.c @@ -19,15 +19,37 @@ */ #include <stdio.h> -#include <unistd.h> +#include <stdlib.h> #include <locale.h> #include <stdarg.h> +#include <stdint.h> #include <stdbool.h> #include <string.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> #include <signal.h> -#include <uv.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <netinet/in.h> +#include <netdb.h> + +#ifndef NI_MAXHOST +#define NI_MAXHOST 1025 +#endif // ! NI_MAXHOST + +#ifndef NI_MAXSERV +#define NI_MAXSERV 32 +#endif // ! NI_MAXSERV + +#include <termios.h> +#ifndef TIOCGWINSZ +#include <sys/ioctl.h> +#endif // ! TIOCGWINSZ + +#include <ev.h> #include <curses.h> #include "termo.h" @@ -58,20 +80,15 @@ enum network_mode NETWORK_MODE_CLIENT ///< We're a client }; -typedef struct write_req write_req_t; -struct write_req -{ - uv_write_t req; ///< libuv write request - uv_buf_t buf; ///< The data to be written -}; - typedef struct client client_t; struct client { LIST_HEADER (client_t) - uv_tcp_t handle; ///< TCP connection handle + int fd; ///< Client connection + ev_io watcher; ///< Client connection watcher struct msg_reader msg_reader; ///< Client message reader + write_queue_t write_queue; ///< Write queue }; #define BITMAP_PIXEL(app, x, y) (app)->bitmap[(y) * (app)->bitmap_w + (x)] @@ -81,20 +98,21 @@ struct app_context { termo_t *tk; ///< Termo instance - uv_tty_t tty; ///< TTY - uv_poll_t tty_watcher; ///< TTY input watcher - uv_timer_t tty_timer; ///< TTY timeout timer - uv_signal_t winch_watcher; ///< SIGWINCH watcher + ev_io tty_watcher; ///< TTY input watcher + ev_timer tty_timer; ///< TTY timeout timer + ev_signal winch_watcher; ///< SIGWINCH watcher network_mode_t mode; ///< Networking mode - char read_buf[8192]; ///< Global read buffer for libuv // Client: - uv_tcp_t server_fd; ///< Connection to the server + int server_fd; ///< Server connection + ev_io server_watcher; ///< Server connection watcher struct msg_reader msg_reader; ///< Server message reader + write_queue_t write_queue; ///< Server write queue // Server: - uv_tcp_t listen_fd; ///< Listening FD + int listen_fd; ///< Listening FD + ev_io listen_watcher; ///< Listening FD watcher client_t *clients; ///< Client connections chtype palette[2 * 9]; ///< Attribute palette @@ -128,6 +146,7 @@ app_init (app_context_t *self) { memset (self, 0, sizeof *self); msg_reader_init (&self->msg_reader); + write_queue_init (&self->write_queue); } static void @@ -141,66 +160,88 @@ app_free (app_context_t *self) free (self->bitmap); msg_reader_free (&self->msg_reader); + write_queue_free (&self->write_queue); } // --- Server-client messaging ------------------------------------------------- -static write_req_t * -flush_writer (struct msg_writer *writer) +static bool +read_loop (EV_P_ ev_io *watcher, + bool (*cb) (EV_P_ ev_io *, const void *, ssize_t)) { - size_t len; - void *data = msg_writer_flush (writer, &len); - write_req_t *req = xcalloc (1, sizeof *req); - req->buf = uv_buf_init (data, len); - return req; + char buf[8192]; + while (true) + { + ssize_t n_read = recv (watcher->fd, buf, sizeof buf, 0); + if (n_read < 0) + { + if (errno == EAGAIN) + break; + if (errno == EINTR) + continue; + } + if (n_read <= 0 || !cb (EV_A_ watcher, buf, n_read)) + return false; + } + return true; } -static void -on_data_written_to_client (uv_write_t *req, int status) +static bool +flush_queue (write_queue_t *queue, ev_io *watcher) { - write_req_t *wr = (write_req_t *) req; - app_context_t *app = req->handle->loop->data; - client_t *client = req->data; + struct iovec vec[queue->len], *vec_iter = vec; + for (write_req_t *iter = queue->head; iter; iter = iter->next) + *vec_iter++ = iter->data; + + int new_events = EV_READ; + ssize_t written; +again: + written = writev (watcher->fd, vec, N_ELEMENTS (vec)); + if (written < 0) + { + if (errno == EAGAIN) + goto skip; + if (errno == EINTR) + goto again; + return false; + } - if (status) - // Write failed - remove_client (app, client); + write_queue_processed (queue, written); - free (wr->buf.base); - free (wr); +skip: + if (!write_queue_is_empty (queue)) + new_events |= EV_WRITE; + + ev_io_stop (EV_DEFAULT_ watcher); + ev_io_set (watcher, watcher->fd, new_events); + ev_io_start (EV_DEFAULT_ watcher); + return true; } -static void -flush_writer_to_client (struct msg_writer *writer, client_t *client) +static write_req_t * +flush_writer (struct msg_writer *writer) { - write_req_t *wr = flush_writer (writer); - wr->req.data = client; - (void) uv_write (&wr->req, (uv_stream_t *) &client->handle, - &wr->buf, 1, on_data_written_to_client); - // XXX: should we put the request on a list so that we can get rid of it? + write_req_t *req = xcalloc (1, sizeof *req); + req->data.iov_base = msg_writer_flush (writer, &req->data.iov_len); + return req; } static void -on_data_written_to_server (uv_write_t *req, int status) +flush_writer_to_client (struct msg_writer *writer, client_t *client) { - write_req_t *wr = (write_req_t *) req; - app_context_t *app = req->handle->loop->data; - - if (status) - // Write failed - on_server_disconnected (app); - - free (wr->buf.base); - free (wr); + write_queue_add (&client->write_queue, flush_writer (writer)); + ev_io_stop (EV_DEFAULT_ &client->watcher); + ev_io_set (&client->watcher, client->fd, EV_READ | EV_WRITE); + ev_io_start (EV_DEFAULT_ &client->watcher); } static void flush_writer_to_server (struct msg_writer *writer, app_context_t *app) { - write_req_t *wr = flush_writer (writer); - (void) uv_write (&wr->req, (uv_stream_t *) &app->server_fd, - &wr->buf, 1, on_data_written_to_server); - // XXX: should we put the request on a list so that we can get rid of it? + write_queue_add (&app->write_queue, flush_writer (writer)); + ev_io_stop (EV_DEFAULT_ &app->server_watcher); + ev_io_set (&app->server_watcher, app->server_fd, EV_READ | EV_WRITE); + ev_io_start (EV_DEFAULT_ &app->server_watcher); } static void @@ -873,26 +914,27 @@ on_key (app_context_t *app, termo_key_t *key) } static void -on_winch (uv_signal_t *handle, int signum) +on_winch (EV_P_ ev_signal *handle, int revents) { - app_context_t *app = handle->loop->data; - (void) signum; + app_context_t *app = ev_userdata (loop); + (void) handle; + (void) revents; -#ifdef HAVE_RESIZETERM - int w, h; - if (!uv_tty_get_winsize (&app->tty, &w, &h)) +#if defined (HAVE_RESIZETERM) && defined (TIOCGWINSZ) + struct winsize size; + if (!ioctl (STDOUT_FILENO, TIOCGWINSZ, (char *) &size)) { char *row = getenv ("LINES"); char *col = getenv ("COLUMNS"); unsigned long tmp; resizeterm ( - (row && xstrtoul (&tmp, row, 10)) ? (int) tmp : h, - (col && xstrtoul (&tmp, col, 10)) ? (int) tmp : w); + (row && xstrtoul (&tmp, row, 10)) ? (int) tmp : size.ws_row, + (col && xstrtoul (&tmp, col, 10)) ? (int) tmp : size.ws_col); } -#else // ! HAVE_RESIZETERM +#else // ! HAVE_RESIZE_TERM || ! TIOCGWINSZ endwin (); refresh (); -#endif // ! HAVE_RESIZETERM +#endif // ! HAVE_RESIZE_TERM || ! TIOCGWINSZ update_canvas_for_screen (app); redraw (app); @@ -900,60 +942,51 @@ on_winch (uv_signal_t *handle, int signum) } static void -on_key_timer (uv_timer_t *handle) +on_key_timer (EV_P_ ev_timer *handle, int revents) { - app_context_t *app = handle->loop->data; + app_context_t *app = ev_userdata (loop); + (void) handle; + (void) revents; termo_key_t key; if (termo_getkey_force (app->tk, &key) == TERMO_RES_KEY) if (!on_key (app, &key)) - uv_stop (handle->loop); + ev_break (EV_A_ EVBREAK_ONE); } static void -on_tty_readable (uv_poll_t *handle, int status, int events) +on_tty_readable (EV_P_ ev_io *handle, int revents) { // Ignoring and hoping for the best - (void) status; - (void) events; + (void) handle; + (void) revents; - app_context_t *app = handle->loop->data; + app_context_t *app = ev_userdata (loop); - uv_timer_stop (&app->tty_timer); + ev_timer_stop (EV_A_ &app->tty_timer); termo_advisereadable (app->tk); termo_key_t key; termo_result_t ret; while ((ret = termo_getkey (app->tk, &key)) == TERMO_RES_KEY) if (!on_key (app, &key)) - uv_stop (handle->loop); + ev_break (EV_A_ EVBREAK_ONE); if (ret == TERMO_RES_AGAIN) - uv_timer_start (&app->tty_timer, - on_key_timer, termo_get_waittime (app->tk), 0); -} - -static void -app_uv_allocator (uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) -{ - // Let's just use a single "global" buffer - (void) suggested_size; - - app_context_t *app = handle->loop->data; - buf->base = app->read_buf; - buf->len = sizeof app->read_buf; + ev_timer_start (EV_A_ &app->tty_timer); } // --- Client-specific stuff --------------------------------------------------- -typedef void (*server_handler_fn) (app_context_t *, struct msg_unpacker *); +typedef bool (*server_handler_fn) (app_context_t *, struct msg_unpacker *); static void on_server_disconnected (app_context_t *app) { // TODO: cancel any write requests? // XXX: should we unref it? - uv_close ((uv_handle_t *) &app->server_fd, NULL); + xclose (app->server_fd); + ev_io_stop (EV_DEFAULT_ &app->server_watcher); display ("Disconnected!"); beep (); // Beep beep! Made a boo-boo. @@ -963,21 +996,20 @@ on_server_disconnected (app_context_t *app) app->mode = NETWORK_MODE_STANDALONE; } -static void +static bool on_server_hello (app_context_t *app, struct msg_unpacker *unpacker) { (void) app; uint8_t version; if (!msg_unpacker_u8 (unpacker, &version)) - return; - + return false; // Not enough data if (version != PROTOCOL_VERSION) - // XXX: possibly incompatible version, disconnect? - return; + return false; // Incompatible version + return true; } -static void +static bool on_server_get_bitmap (app_context_t *app, struct msg_unpacker *unpacker) { int32_t x, y; @@ -986,11 +1018,11 @@ on_server_get_bitmap (app_context_t *app, struct msg_unpacker *unpacker) || !msg_unpacker_i32 (unpacker, &y) || !msg_unpacker_u64 (unpacker, &w) || !msg_unpacker_u64 (unpacker, &h)) - return; + return false; // Not enough data size_t size = w * h; if ((h && w > SIZE_MAX / h) || w > SIZE_MAX || h > SIZE_MAX) - return; + return false; // The server is flooding us uint8_t *bitmap = xcalloc (size, sizeof *app->bitmap); @@ -1015,9 +1047,10 @@ on_server_get_bitmap (app_context_t *app, struct msg_unpacker *unpacker) app->bitmap_h = h; redraw_canvas (app); + return true; } -static void +static bool on_server_put_point (app_context_t *app, struct msg_unpacker *unpacker) { int32_t x, y; @@ -1026,22 +1059,19 @@ on_server_put_point (app_context_t *app, struct msg_unpacker *unpacker) if (!msg_unpacker_i32 (unpacker, &x) || !msg_unpacker_i32 (unpacker, &y) || !msg_unpacker_u8 (unpacker, &color)) - return; + return false; // Not enough data draw_point (app, x, y, color); + return true; } -static void -on_server_data (uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) +static bool +on_server_data (EV_P_ ev_io *watcher, const void *buf, ssize_t n_read) { - app_context_t *app = stream->loop->data; - if (nread == UV_EOF || nread < 0) - { - on_server_disconnected (app); - return; - } + app_context_t *app = ev_userdata (loop); + (void) watcher; - msg_reader_feed (&app->msg_reader, buf->base, nread); + msg_reader_feed (&app->msg_reader, buf, n_read); static const server_handler_fn handlers[MESSAGE_COUNT] = { @@ -1060,20 +1090,34 @@ on_server_data (uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) uint8_t type; if (!msg_unpacker_u8 (&unpacker, &type) || type >= MESSAGE_COUNT) - // XXX: unknown message, disconnect? - continue; + return false; // Unknown message server_handler_fn handler = handlers[type]; if (!handler) - // XXX: unknown message, disconnect? - continue; - - handler (app, &unpacker); - + return false; // Unknown message + if (!handler (app, &unpacker)) + return false; // Invalid message if (msg_unpacker_get_available (&unpacker) > 0) - // XXX: overlong message, disconnect? - continue; + return false; // Overlong message } + return true; +} + +static void +on_server_ready (EV_P_ ev_io *watcher, int revents) +{ + app_context_t *app = ev_userdata (loop); + + if (revents & EV_READ) + if (!read_loop (EV_A_ watcher, on_server_data)) + goto error; + if (revents & EV_WRITE) + if (!flush_queue (&app->write_queue, watcher)) + goto error; + return; + +error: + on_server_disconnected (app); } // --- Server-specific stuff --------------------------------------------------- @@ -1084,11 +1128,13 @@ typedef bool (*client_handler_fn) static void remove_client (app_context_t *app, client_t *client) { + // TODO: stop any watchers? // TODO: cancel any write requests? // XXX: should we unref it? - uv_close ((uv_handle_t *) &client->handle, NULL); + xclose (client->fd); LIST_UNLINK (app->clients, client); msg_reader_free (&client->msg_reader); + write_queue_free (&client->write_queue); free (client); } @@ -1137,15 +1183,13 @@ on_client_put_point (app_context_t *app, client_t *client, return true; } -static void -on_client_data (uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) +static bool +on_client_data (EV_P_ ev_io *watcher, const void *buf, ssize_t n_read) { - app_context_t *app = stream->loop->data; - client_t *client = stream->data; - if (nread == UV_EOF || nread < 0) - goto disconnect; // Connection closed or error + app_context_t *app = ev_userdata (loop); + client_t *client = watcher->data; - msg_reader_feed (&client->msg_reader, buf->base, nread); + msg_reader_feed (&client->msg_reader, buf, n_read); static const client_handler_fn handlers[MESSAGE_COUNT] = { @@ -1163,51 +1207,74 @@ on_client_data (uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) uint8_t type; if (!msg_unpacker_u8 (&unpacker, &type)) - goto disconnect; // Invalid message + return false; // Invalid message if (type >= MESSAGE_COUNT) - goto disconnect; // Unknown message + return false; // Unknown message client_handler_fn handler = handlers[type]; if (!handler) - goto disconnect; // Unknown message + return false; // Unknown message if (!handler (app, client, &unpacker)) - goto disconnect; // Invalid message + return false; // Invalid message if (msg_unpacker_get_available (&unpacker) > 0) - goto disconnect; // Overlong message data + return false; // Overlong message data } + return true; +} + +static void +on_client_ready (EV_P_ ev_io *watcher, int revents) +{ + app_context_t *app = ev_userdata (loop); + client_t *client = watcher->data; + + if (revents & EV_READ) + if (!read_loop (EV_A_ watcher, on_client_data)) + goto error; + if (revents & EV_WRITE) + if (!flush_queue (&client->write_queue, watcher)) + goto error; return; -disconnect: +error: remove_client (app, client); } static void -on_new_client (uv_stream_t *server, int status) +on_new_client (EV_P_ ev_io *watcher, int revents) { - app_context_t *app = server->loop->data; - if (status) - return; + app_context_t *app = ev_userdata (loop); + (void) revents; - int err; - client_t *client = xcalloc (1, sizeof *client); - if ((err = uv_tcp_init (server->loop, &client->handle))) - goto free_client; - if ((err = uv_accept (server, (uv_stream_t *) &client->handle)) - || (err = uv_read_start ((uv_stream_t *) &client->handle, - app_uv_allocator, on_client_data))) - // XXX: do we need to un-accept? - goto free_handle; - - client->handle.data = client; - msg_reader_init (&client->msg_reader); - LIST_PREPEND (app->clients, client); - return; + while (true) + { + int sock_fd = accept (watcher->fd, NULL, NULL); + if (sock_fd == -1) + { + if (errno == EAGAIN) + break; + if (errno == EINTR + || errno == ECONNABORTED) + continue; + + // Stop accepting connections to prevent busy looping + // TODO: indicate the error to the user + ev_io_stop (EV_A_ watcher); + break; + } -free_handle: - uv_close ((uv_handle_t *) &client->handle, NULL); - // XXX: should we unref it? -free_client: - free (client); + client_t *client = xcalloc (1, sizeof *client); + client->fd = sock_fd; + msg_reader_init (&client->msg_reader); + write_queue_init (&client->write_queue); + + set_blocking (sock_fd, false); + ev_io_init (&client->watcher, on_client_ready, sock_fd, EV_READ); + client->watcher.data = client; + ev_io_start (EV_A_ &client->watcher); + + LIST_PREPEND (app->clients, client); + } } // --- Program startup --------------------------------------------------------- @@ -1388,17 +1455,13 @@ initialize_client (app_context_t *app, struct addrinfo *address) exit (EXIT_FAILURE); } + int yes = 1; + (void) setsockopt (sock_fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof yes); + set_blocking (sock_fd, false); - if ((err = uv_tcp_init (uv_default_loop (), &app->server_fd)) - || (err = uv_tcp_open (&app->server_fd, sock_fd)) - || (err = uv_tcp_keepalive (&app->server_fd, true, 30)) - || (err = uv_read_start ((uv_stream_t *) &app->server_fd, - app_uv_allocator, on_server_data))) - { - fprintf (stderr, "%s: %s: %s\n", - "error", "initialization failed", uv_strerror (err)); - exit (EXIT_FAILURE); - } + app->server_fd = sock_fd; + ev_io_init (&app->server_watcher, on_server_ready, sock_fd, EV_READ); + ev_io_start (EV_DEFAULT_ &app->server_watcher); send_hello_request (app); send_get_bitmap_request (app); @@ -1409,15 +1472,29 @@ initialize_server (app_context_t *app, struct addrinfo *address) { app->mode = NETWORK_MODE_SERVER; - int err; - if ((err = uv_tcp_init (uv_default_loop (), &app->listen_fd)) - || (err = uv_tcp_bind (&app->listen_fd, address->ai_addr, 0)) - || (err = uv_listen ((uv_stream_t *) &app->listen_fd, 10, on_new_client))) - { - fprintf (stderr, "%s: %s: %s\n", - "error", "initialization failed", uv_strerror (err)); - exit (EXIT_FAILURE); - } + int sock_fd = socket (address->ai_family, + address->ai_socktype, address->ai_protocol); + if (sock_fd == -1) + goto fail; + + if (bind (sock_fd, address->ai_addr, address->ai_addrlen) + || listen (sock_fd, 10)) + goto fail; + + int yes = 1; + (void) setsockopt (sock_fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof yes); + (void) setsockopt (sock_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes); + + set_blocking (sock_fd, false); + app->listen_fd = sock_fd; + ev_io_init (&app->listen_watcher, on_new_client, sock_fd, EV_READ); + ev_io_start (EV_DEFAULT_ &app->listen_watcher); + return; + +fail: + fprintf (stderr, "%s: %s: %s\n", + "error", "initialization failed", strerror (errno)); + exit (EXIT_FAILURE); } int @@ -1429,6 +1506,13 @@ main (int argc, char *argv[]) app_context_t app; app_init (&app); + struct ev_loop *loop = EV_DEFAULT; + if (!loop) + { + fprintf (stderr, "%s: %s\n", "error", "cannot initialize libev"); + exit (EXIT_FAILURE); + } + app_options_t options; app_options_init (&options); parse_program_arguments (&options, argc, argv); @@ -1461,29 +1545,27 @@ main (int argc, char *argv[]) exit (EXIT_FAILURE); } - uv_loop_t *loop = uv_default_loop (); - loop->data = &app; - - uv_signal_init (loop, &app.winch_watcher); - uv_signal_start (&app.winch_watcher, on_winch, SIGWINCH); + ev_set_userdata (loop, &app); - uv_tty_init (loop, &app.tty, STDOUT_FILENO, false); + ev_signal_init (&app.winch_watcher, on_winch, SIGWINCH); + ev_signal_start (EV_DEFAULT_ &app.winch_watcher); - uv_poll_init (loop, &app.tty_watcher, STDIN_FILENO); - uv_poll_start (&app.tty_watcher, UV_READABLE, on_tty_readable); + ev_io_init (&app.tty_watcher, on_tty_readable, STDIN_FILENO, EV_READ); + ev_io_start (EV_DEFAULT_ &app.tty_watcher); - uv_timer_init (loop, &app.tty_timer); + ev_timer_init (&app.tty_timer, on_key_timer, + termo_get_waittime (app.tk) / 1000., 0); init_palette (&app); update_canvas_for_screen (&app); redraw (&app); redraw_canvas (&app); - uv_run (loop, UV_RUN_DEFAULT); + ev_run (loop, 0); endwin (); app_free (&app); - uv_loop_close (loop); + ev_loop_destroy (loop); return 0; } |