summaryrefslogtreecommitdiff
path: root/autistdraw.c
diff options
context:
space:
mode:
Diffstat (limited to 'autistdraw.c')
-rw-r--r--autistdraw.c450
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;
}