diff --git a/CMakeLists.txt b/CMakeLists.txt index 88a01c8..ca80edf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,9 +20,13 @@ set (project_VERSION "${project_VERSION_MAJOR}") set (project_VERSION "${project_VERSION}.${project_VERSION_MINOR}") set (project_VERSION "${project_VERSION}.${project_VERSION_PATCH}") +# For custom modules +set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) + # Dependencies find_package (PkgConfig REQUIRED) -pkg_check_modules (dependencies REQUIRED ncursesw libuv>=0.11) +pkg_check_modules (ncursesw REQUIRED ncursesw) +find_package (LibEV REQUIRED) if (USE_SYSTEM_TERMO) find_package (Termo REQUIRED) @@ -38,11 +42,12 @@ else (USE_SYSTEM_TERMO) set (Termo_LIBRARIES termo-static) endif (USE_SYSTEM_TERMO) -include_directories (${dependencies_INCLUDE_DIRS} ${Termo_INCLUDE_DIRS}) +include_directories (${ncursesw_INCLUDE_DIRS} + ${LIBEV_INCLUDE_DIRS} ${Termo_INCLUDE_DIRS}) # Configuration include (CheckFunctionExists) -set (CMAKE_REQUIRED_LIBRARIES ${dependencies_LIBRARIES}) +set (CMAKE_REQUIRED_LIBRARIES ${ncursesw_LIBRARIES}) CHECK_FUNCTION_EXISTS ("resizeterm" HAVE_RESIZETERM) # Project source files @@ -50,7 +55,7 @@ set (project_sources ${PROJECT_NAME}.c) set (project_headers ${PROJECT_BINARY_DIR}/config.h) # Project libraries -set (project_libraries ${dependencies_LIBRARIES} termo-static) +set (project_libraries ${ncursesw_LIBRARIES} ${LIBEV_LIBRARIES} termo-static) # Generate a configuration file configure_file (${PROJECT_SOURCE_DIR}/config.h.in ${PROJECT_BINARY_DIR}/config.h) diff --git a/README b/README index 2ee09f8..c1956aa 100644 --- a/README +++ b/README @@ -5,7 +5,7 @@ autistdraw Building and Running -------------------- -Build dependencies: CMake, pkg-config, ncursesw, libuv>=0.11.x, termo (included) +Build dependencies: CMake, pkg-config, ncursesw, libev, termo (included) $ git clone https://github.com/pjanouch/autistdraw.git $ git submodule init diff --git a/autistdraw.c b/autistdraw.c index 41a2942..ce07540 100644 --- a/autistdraw.c +++ b/autistdraw.c @@ -19,15 +19,37 @@ */ #include -#include +#include #include #include +#include #include #include +#include +#include +#include #include -#include +#include +#include +#include +#include + +#ifndef NI_MAXHOST +#define NI_MAXHOST 1025 +#endif // ! NI_MAXHOST + +#ifndef NI_MAXSERV +#define NI_MAXSERV 32 +#endif // ! NI_MAXSERV + +#include +#ifndef TIOCGWINSZ +#include +#endif // ! TIOCGWINSZ + +#include #include #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 bool +read_loop (EV_P_ ev_io *watcher, + bool (*cb) (EV_P_ ev_io *, const void *, ssize_t)) +{ + 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 bool +flush_queue (write_queue_t *queue, ev_io *watcher) +{ + 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; + } + + write_queue_processed (queue, written); + +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 write_req_t * flush_writer (struct msg_writer *writer) { - 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); + req->data.iov_base = msg_writer_flush (writer, &req->data.iov_len); return req; } -static void -on_data_written_to_client (uv_write_t *req, int status) -{ - write_req_t *wr = (write_req_t *) req; - app_context_t *app = req->handle->loop->data; - client_t *client = req->data; - - if (status) - // Write failed - remove_client (app, client); - - free (wr->buf.base); - free (wr); -} - static void flush_writer_to_client (struct msg_writer *writer, client_t *client) { - 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? -} - -static void -on_data_written_to_server (uv_write_t *req, int status) -{ - 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; + while (true) + { + int sock_fd = accept (watcher->fd, NULL, NULL); + if (sock_fd == -1) + { + if (errno == EAGAIN) + break; + if (errno == EINTR + || errno == ECONNABORTED) + continue; - client->handle.data = client; - msg_reader_init (&client->msg_reader); - LIST_PREPEND (app->clients, client); - return; + // 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; + ev_set_userdata (loop, &app); - uv_signal_init (loop, &app.winch_watcher); - uv_signal_start (&app.winch_watcher, on_winch, SIGWINCH); + ev_signal_init (&app.winch_watcher, on_winch, SIGWINCH); + ev_signal_start (EV_DEFAULT_ &app.winch_watcher); - uv_tty_init (loop, &app.tty, STDOUT_FILENO, false); + ev_io_init (&app.tty_watcher, on_tty_readable, STDIN_FILENO, EV_READ); + ev_io_start (EV_DEFAULT_ &app.tty_watcher); - uv_poll_init (loop, &app.tty_watcher, STDIN_FILENO); - uv_poll_start (&app.tty_watcher, UV_READABLE, on_tty_readable); - - 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; } diff --git a/cmake/FindLibEV.cmake b/cmake/FindLibEV.cmake new file mode 100644 index 0000000..cdc67dc --- /dev/null +++ b/cmake/FindLibEV.cmake @@ -0,0 +1,18 @@ +# Public Domain + +# The author of libev is a dick and doesn't want to add support for pkg-config, +# forcing us to include this pointless file in the distribution. + +# Some distributions do add it, though +find_package (PkgConfig REQUIRED) +pkg_check_modules (LIBEV libev) + +if (NOT LIBEV_FOUND) + find_path (LIBEV_INCLUDE_DIRS ev.h) + find_library (LIBEV_LIBRARIES NAMES ev) + + if (LIBEV_INCLUDE_DIRS AND LIBEV_LIBRARIES) + set (LIBEV_FOUND TRUE) + endif (LIBEV_INCLUDE_DIRS AND LIBEV_LIBRARIES) +endif (NOT LIBEV_FOUND) + diff --git a/utils.c b/utils.c index df16559..23b95a4 100644 --- a/utils.c +++ b/utils.c @@ -104,6 +104,24 @@ xrealloc (void *o, size_t n) (link)->next->prev = (link)->prev; \ BLOCK_END +#define LIST_APPEND_WITH_TAIL(head, tail, link) \ + BLOCK_START \ + (link)->prev = (tail); \ + (link)->next = NULL; \ + if ((link)->prev) \ + (link)->prev->next = (link); \ + else \ + (head) = (link); \ + (tail) = (link); \ + BLOCK_END + +#define LIST_UNLINK_WITH_TAIL(head, tail, link) \ + BLOCK_START \ + if ((tail) == (link)) \ + (tail) = (link)->prev; \ + LIST_UNLINK ((head), (link)); \ + BLOCK_END + // --- Dynamically allocated strings ------------------------------------------- // Basically a string builder to abstract away manual memory management. @@ -236,11 +254,14 @@ static bool set_blocking (int fd, bool blocking) { int flags = fcntl (fd, F_GETFL); + bool prev = !(flags & O_NONBLOCK); if (blocking) flags &= ~O_NONBLOCK; else flags |= O_NONBLOCK; + + (void) fcntl (fd, F_SETFL, flags); return prev; } @@ -284,6 +305,76 @@ xstrtoul (unsigned long *out, const char *s, int base) return errno == 0 && !*end && end != s; } +// --- libuv-style write adaptor ----------------------------------------------- + +// Makes it possible to use iovec to write multiple data chunks at once. + +typedef struct write_req write_req_t; +struct write_req +{ + LIST_HEADER (write_req_t) + struct iovec data; ///< Data to be written +}; + +typedef struct write_queue write_queue_t; +struct write_queue +{ + write_req_t *head; ///< The head of the queue + write_req_t *tail; ///< The tail of the queue + size_t head_offset; ///< Offset into the head + size_t len; +}; + +static void +write_queue_init (struct write_queue *self) +{ + self->head = self->tail = NULL; + self->head_offset = 0; + self->len = 0; +} + +static void +write_queue_free (struct write_queue *self) +{ + for (write_req_t *iter = self->head, *next; iter; iter = next) + { + next = iter->next; + free (iter->data.iov_base); + free (iter); + } +} + +static void +write_queue_add (struct write_queue *self, write_req_t *req) +{ + LIST_APPEND_WITH_TAIL (self->head, self->tail, req); + self->len++; +} + +static void +write_queue_processed (struct write_queue *self, size_t len) +{ + while (self->head + && self->head_offset + len >= self->head->data.iov_len) + { + write_req_t *head = self->head; + len -= (head->data.iov_len - self->head_offset); + self->head_offset = 0; + + LIST_UNLINK_WITH_TAIL (self->head, self->tail, head); + self->len--; + free (head->data.iov_base); + free (head); + } + self->head_offset += len; +} + +static bool +write_queue_is_empty (struct write_queue *self) +{ + return self->head == NULL; +} + // --- Message reader ---------------------------------------------------------- struct msg_reader