Rewrite to use libev
libuv is too immature so far and I'm not in the mood to try and link it statically via some horrible hack (no CMake support). Also libev is much closer to my understanding of event loops. The messaging model stays for when/if I want to return to libuv.
This commit is contained in:
parent
606c5f43af
commit
74965b0f66
|
@ -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_MINOR}")
|
||||||
set (project_VERSION "${project_VERSION}.${project_VERSION_PATCH}")
|
set (project_VERSION "${project_VERSION}.${project_VERSION_PATCH}")
|
||||||
|
|
||||||
|
# For custom modules
|
||||||
|
set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
|
||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
find_package (PkgConfig REQUIRED)
|
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)
|
if (USE_SYSTEM_TERMO)
|
||||||
find_package (Termo REQUIRED)
|
find_package (Termo REQUIRED)
|
||||||
|
@ -38,11 +42,12 @@ else (USE_SYSTEM_TERMO)
|
||||||
set (Termo_LIBRARIES termo-static)
|
set (Termo_LIBRARIES termo-static)
|
||||||
endif (USE_SYSTEM_TERMO)
|
endif (USE_SYSTEM_TERMO)
|
||||||
|
|
||||||
include_directories (${dependencies_INCLUDE_DIRS} ${Termo_INCLUDE_DIRS})
|
include_directories (${ncursesw_INCLUDE_DIRS}
|
||||||
|
${LIBEV_INCLUDE_DIRS} ${Termo_INCLUDE_DIRS})
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
include (CheckFunctionExists)
|
include (CheckFunctionExists)
|
||||||
set (CMAKE_REQUIRED_LIBRARIES ${dependencies_LIBRARIES})
|
set (CMAKE_REQUIRED_LIBRARIES ${ncursesw_LIBRARIES})
|
||||||
CHECK_FUNCTION_EXISTS ("resizeterm" HAVE_RESIZETERM)
|
CHECK_FUNCTION_EXISTS ("resizeterm" HAVE_RESIZETERM)
|
||||||
|
|
||||||
# Project source files
|
# Project source files
|
||||||
|
@ -50,7 +55,7 @@ set (project_sources ${PROJECT_NAME}.c)
|
||||||
set (project_headers ${PROJECT_BINARY_DIR}/config.h)
|
set (project_headers ${PROJECT_BINARY_DIR}/config.h)
|
||||||
|
|
||||||
# Project libraries
|
# Project libraries
|
||||||
set (project_libraries ${dependencies_LIBRARIES} termo-static)
|
set (project_libraries ${ncursesw_LIBRARIES} ${LIBEV_LIBRARIES} termo-static)
|
||||||
|
|
||||||
# Generate a configuration file
|
# Generate a configuration file
|
||||||
configure_file (${PROJECT_SOURCE_DIR}/config.h.in ${PROJECT_BINARY_DIR}/config.h)
|
configure_file (${PROJECT_SOURCE_DIR}/config.h.in ${PROJECT_BINARY_DIR}/config.h)
|
||||||
|
|
2
README
2
README
|
@ -5,7 +5,7 @@ autistdraw
|
||||||
|
|
||||||
Building and Running
|
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 clone https://github.com/pjanouch/autistdraw.git
|
||||||
$ git submodule init
|
$ git submodule init
|
||||||
|
|
448
autistdraw.c
448
autistdraw.c
|
@ -19,15 +19,37 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <unistd.h>
|
#include <stdlib.h>
|
||||||
#include <locale.h>
|
#include <locale.h>
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
|
#include <stdint.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
#include <signal.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 <curses.h>
|
||||||
#include "termo.h"
|
#include "termo.h"
|
||||||
|
|
||||||
|
@ -58,20 +80,15 @@ enum network_mode
|
||||||
NETWORK_MODE_CLIENT ///< We're a client
|
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;
|
typedef struct client client_t;
|
||||||
struct client
|
struct client
|
||||||
{
|
{
|
||||||
LIST_HEADER (client_t)
|
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
|
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)]
|
#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
|
termo_t *tk; ///< Termo instance
|
||||||
|
|
||||||
uv_tty_t tty; ///< TTY
|
ev_io tty_watcher; ///< TTY input watcher
|
||||||
uv_poll_t tty_watcher; ///< TTY input watcher
|
ev_timer tty_timer; ///< TTY timeout timer
|
||||||
uv_timer_t tty_timer; ///< TTY timeout timer
|
ev_signal winch_watcher; ///< SIGWINCH watcher
|
||||||
uv_signal_t winch_watcher; ///< SIGWINCH watcher
|
|
||||||
|
|
||||||
network_mode_t mode; ///< Networking mode
|
network_mode_t mode; ///< Networking mode
|
||||||
char read_buf[8192]; ///< Global read buffer for libuv
|
|
||||||
|
|
||||||
// Client:
|
// 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
|
struct msg_reader msg_reader; ///< Server message reader
|
||||||
|
write_queue_t write_queue; ///< Server write queue
|
||||||
|
|
||||||
// Server:
|
// 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
|
client_t *clients; ///< Client connections
|
||||||
|
|
||||||
chtype palette[2 * 9]; ///< Attribute palette
|
chtype palette[2 * 9]; ///< Attribute palette
|
||||||
|
@ -128,6 +146,7 @@ app_init (app_context_t *self)
|
||||||
{
|
{
|
||||||
memset (self, 0, sizeof *self);
|
memset (self, 0, sizeof *self);
|
||||||
msg_reader_init (&self->msg_reader);
|
msg_reader_init (&self->msg_reader);
|
||||||
|
write_queue_init (&self->write_queue);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -141,66 +160,88 @@ app_free (app_context_t *self)
|
||||||
|
|
||||||
free (self->bitmap);
|
free (self->bitmap);
|
||||||
msg_reader_free (&self->msg_reader);
|
msg_reader_free (&self->msg_reader);
|
||||||
|
write_queue_free (&self->write_queue);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Server-client messaging -------------------------------------------------
|
// --- 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 *
|
static write_req_t *
|
||||||
flush_writer (struct msg_writer *writer)
|
flush_writer (struct msg_writer *writer)
|
||||||
{
|
{
|
||||||
size_t len;
|
|
||||||
void *data = msg_writer_flush (writer, &len);
|
|
||||||
write_req_t *req = xcalloc (1, sizeof *req);
|
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;
|
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
|
static void
|
||||||
flush_writer_to_client (struct msg_writer *writer, client_t *client)
|
flush_writer_to_client (struct msg_writer *writer, client_t *client)
|
||||||
{
|
{
|
||||||
write_req_t *wr = flush_writer (writer);
|
write_queue_add (&client->write_queue, flush_writer (writer));
|
||||||
wr->req.data = client;
|
ev_io_stop (EV_DEFAULT_ &client->watcher);
|
||||||
(void) uv_write (&wr->req, (uv_stream_t *) &client->handle,
|
ev_io_set (&client->watcher, client->fd, EV_READ | EV_WRITE);
|
||||||
&wr->buf, 1, on_data_written_to_client);
|
ev_io_start (EV_DEFAULT_ &client->watcher);
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
flush_writer_to_server (struct msg_writer *writer, app_context_t *app)
|
flush_writer_to_server (struct msg_writer *writer, app_context_t *app)
|
||||||
{
|
{
|
||||||
write_req_t *wr = flush_writer (writer);
|
write_queue_add (&app->write_queue, flush_writer (writer));
|
||||||
(void) uv_write (&wr->req, (uv_stream_t *) &app->server_fd,
|
ev_io_stop (EV_DEFAULT_ &app->server_watcher);
|
||||||
&wr->buf, 1, on_data_written_to_server);
|
ev_io_set (&app->server_watcher, app->server_fd, EV_READ | EV_WRITE);
|
||||||
// XXX: should we put the request on a list so that we can get rid of it?
|
ev_io_start (EV_DEFAULT_ &app->server_watcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -873,26 +914,27 @@ on_key (app_context_t *app, termo_key_t *key)
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
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;
|
app_context_t *app = ev_userdata (loop);
|
||||||
(void) signum;
|
(void) handle;
|
||||||
|
(void) revents;
|
||||||
|
|
||||||
#ifdef HAVE_RESIZETERM
|
#if defined (HAVE_RESIZETERM) && defined (TIOCGWINSZ)
|
||||||
int w, h;
|
struct winsize size;
|
||||||
if (!uv_tty_get_winsize (&app->tty, &w, &h))
|
if (!ioctl (STDOUT_FILENO, TIOCGWINSZ, (char *) &size))
|
||||||
{
|
{
|
||||||
char *row = getenv ("LINES");
|
char *row = getenv ("LINES");
|
||||||
char *col = getenv ("COLUMNS");
|
char *col = getenv ("COLUMNS");
|
||||||
unsigned long tmp;
|
unsigned long tmp;
|
||||||
resizeterm (
|
resizeterm (
|
||||||
(row && xstrtoul (&tmp, row, 10)) ? (int) tmp : h,
|
(row && xstrtoul (&tmp, row, 10)) ? (int) tmp : size.ws_row,
|
||||||
(col && xstrtoul (&tmp, col, 10)) ? (int) tmp : w);
|
(col && xstrtoul (&tmp, col, 10)) ? (int) tmp : size.ws_col);
|
||||||
}
|
}
|
||||||
#else // ! HAVE_RESIZETERM
|
#else // ! HAVE_RESIZE_TERM || ! TIOCGWINSZ
|
||||||
endwin ();
|
endwin ();
|
||||||
refresh ();
|
refresh ();
|
||||||
#endif // ! HAVE_RESIZETERM
|
#endif // ! HAVE_RESIZE_TERM || ! TIOCGWINSZ
|
||||||
|
|
||||||
update_canvas_for_screen (app);
|
update_canvas_for_screen (app);
|
||||||
redraw (app);
|
redraw (app);
|
||||||
|
@ -900,60 +942,51 @@ on_winch (uv_signal_t *handle, int signum)
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
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;
|
termo_key_t key;
|
||||||
if (termo_getkey_force (app->tk, &key) == TERMO_RES_KEY)
|
if (termo_getkey_force (app->tk, &key) == TERMO_RES_KEY)
|
||||||
if (!on_key (app, &key))
|
if (!on_key (app, &key))
|
||||||
uv_stop (handle->loop);
|
ev_break (EV_A_ EVBREAK_ONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
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
|
// Ignoring and hoping for the best
|
||||||
(void) status;
|
(void) handle;
|
||||||
(void) events;
|
(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_advisereadable (app->tk);
|
||||||
|
|
||||||
termo_key_t key;
|
termo_key_t key;
|
||||||
termo_result_t ret;
|
termo_result_t ret;
|
||||||
while ((ret = termo_getkey (app->tk, &key)) == TERMO_RES_KEY)
|
while ((ret = termo_getkey (app->tk, &key)) == TERMO_RES_KEY)
|
||||||
if (!on_key (app, &key))
|
if (!on_key (app, &key))
|
||||||
uv_stop (handle->loop);
|
ev_break (EV_A_ EVBREAK_ONE);
|
||||||
|
|
||||||
if (ret == TERMO_RES_AGAIN)
|
if (ret == TERMO_RES_AGAIN)
|
||||||
uv_timer_start (&app->tty_timer,
|
ev_timer_start (EV_A_ &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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Client-specific stuff ---------------------------------------------------
|
// --- 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
|
static void
|
||||||
on_server_disconnected (app_context_t *app)
|
on_server_disconnected (app_context_t *app)
|
||||||
{
|
{
|
||||||
// TODO: cancel any write requests?
|
// TODO: cancel any write requests?
|
||||||
// XXX: should we unref it?
|
// 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!");
|
display ("Disconnected!");
|
||||||
beep (); // Beep beep! Made a boo-boo.
|
beep (); // Beep beep! Made a boo-boo.
|
||||||
|
@ -963,21 +996,20 @@ on_server_disconnected (app_context_t *app)
|
||||||
app->mode = NETWORK_MODE_STANDALONE;
|
app->mode = NETWORK_MODE_STANDALONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static bool
|
||||||
on_server_hello (app_context_t *app, struct msg_unpacker *unpacker)
|
on_server_hello (app_context_t *app, struct msg_unpacker *unpacker)
|
||||||
{
|
{
|
||||||
(void) app;
|
(void) app;
|
||||||
|
|
||||||
uint8_t version;
|
uint8_t version;
|
||||||
if (!msg_unpacker_u8 (unpacker, &version))
|
if (!msg_unpacker_u8 (unpacker, &version))
|
||||||
return;
|
return false; // Not enough data
|
||||||
|
|
||||||
if (version != PROTOCOL_VERSION)
|
if (version != PROTOCOL_VERSION)
|
||||||
// XXX: possibly incompatible version, disconnect?
|
return false; // Incompatible version
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static bool
|
||||||
on_server_get_bitmap (app_context_t *app, struct msg_unpacker *unpacker)
|
on_server_get_bitmap (app_context_t *app, struct msg_unpacker *unpacker)
|
||||||
{
|
{
|
||||||
int32_t x, y;
|
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_i32 (unpacker, &y)
|
||||||
|| !msg_unpacker_u64 (unpacker, &w)
|
|| !msg_unpacker_u64 (unpacker, &w)
|
||||||
|| !msg_unpacker_u64 (unpacker, &h))
|
|| !msg_unpacker_u64 (unpacker, &h))
|
||||||
return;
|
return false; // Not enough data
|
||||||
|
|
||||||
size_t size = w * h;
|
size_t size = w * h;
|
||||||
if ((h && w > SIZE_MAX / h) || w > SIZE_MAX || h > SIZE_MAX)
|
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);
|
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;
|
app->bitmap_h = h;
|
||||||
|
|
||||||
redraw_canvas (app);
|
redraw_canvas (app);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static bool
|
||||||
on_server_put_point (app_context_t *app, struct msg_unpacker *unpacker)
|
on_server_put_point (app_context_t *app, struct msg_unpacker *unpacker)
|
||||||
{
|
{
|
||||||
int32_t x, y;
|
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)
|
if (!msg_unpacker_i32 (unpacker, &x)
|
||||||
|| !msg_unpacker_i32 (unpacker, &y)
|
|| !msg_unpacker_i32 (unpacker, &y)
|
||||||
|| !msg_unpacker_u8 (unpacker, &color))
|
|| !msg_unpacker_u8 (unpacker, &color))
|
||||||
return;
|
return false; // Not enough data
|
||||||
|
|
||||||
draw_point (app, x, y, color);
|
draw_point (app, x, y, color);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static bool
|
||||||
on_server_data (uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf)
|
on_server_data (EV_P_ ev_io *watcher, const void *buf, ssize_t n_read)
|
||||||
{
|
{
|
||||||
app_context_t *app = stream->loop->data;
|
app_context_t *app = ev_userdata (loop);
|
||||||
if (nread == UV_EOF || nread < 0)
|
(void) watcher;
|
||||||
{
|
|
||||||
on_server_disconnected (app);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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] =
|
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;
|
uint8_t type;
|
||||||
if (!msg_unpacker_u8 (&unpacker, &type)
|
if (!msg_unpacker_u8 (&unpacker, &type)
|
||||||
|| type >= MESSAGE_COUNT)
|
|| type >= MESSAGE_COUNT)
|
||||||
// XXX: unknown message, disconnect?
|
return false; // Unknown message
|
||||||
continue;
|
|
||||||
|
|
||||||
server_handler_fn handler = handlers[type];
|
server_handler_fn handler = handlers[type];
|
||||||
if (!handler)
|
if (!handler)
|
||||||
// XXX: unknown message, disconnect?
|
return false; // Unknown message
|
||||||
continue;
|
if (!handler (app, &unpacker))
|
||||||
|
return false; // Invalid message
|
||||||
handler (app, &unpacker);
|
|
||||||
|
|
||||||
if (msg_unpacker_get_available (&unpacker) > 0)
|
if (msg_unpacker_get_available (&unpacker) > 0)
|
||||||
// XXX: overlong message, disconnect?
|
return false; // Overlong message
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
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 ---------------------------------------------------
|
// --- Server-specific stuff ---------------------------------------------------
|
||||||
|
@ -1084,11 +1128,13 @@ typedef bool (*client_handler_fn)
|
||||||
static void
|
static void
|
||||||
remove_client (app_context_t *app, client_t *client)
|
remove_client (app_context_t *app, client_t *client)
|
||||||
{
|
{
|
||||||
|
// TODO: stop any watchers?
|
||||||
// TODO: cancel any write requests?
|
// TODO: cancel any write requests?
|
||||||
// XXX: should we unref it?
|
// XXX: should we unref it?
|
||||||
uv_close ((uv_handle_t *) &client->handle, NULL);
|
xclose (client->fd);
|
||||||
LIST_UNLINK (app->clients, client);
|
LIST_UNLINK (app->clients, client);
|
||||||
msg_reader_free (&client->msg_reader);
|
msg_reader_free (&client->msg_reader);
|
||||||
|
write_queue_free (&client->write_queue);
|
||||||
free (client);
|
free (client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1137,15 +1183,13 @@ on_client_put_point (app_context_t *app, client_t *client,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static bool
|
||||||
on_client_data (uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf)
|
on_client_data (EV_P_ ev_io *watcher, const void *buf, ssize_t n_read)
|
||||||
{
|
{
|
||||||
app_context_t *app = stream->loop->data;
|
app_context_t *app = ev_userdata (loop);
|
||||||
client_t *client = stream->data;
|
client_t *client = watcher->data;
|
||||||
if (nread == UV_EOF || nread < 0)
|
|
||||||
goto disconnect; // Connection closed or error
|
|
||||||
|
|
||||||
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] =
|
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;
|
uint8_t type;
|
||||||
if (!msg_unpacker_u8 (&unpacker, &type))
|
if (!msg_unpacker_u8 (&unpacker, &type))
|
||||||
goto disconnect; // Invalid message
|
return false; // Invalid message
|
||||||
if (type >= MESSAGE_COUNT)
|
if (type >= MESSAGE_COUNT)
|
||||||
goto disconnect; // Unknown message
|
return false; // Unknown message
|
||||||
|
|
||||||
client_handler_fn handler = handlers[type];
|
client_handler_fn handler = handlers[type];
|
||||||
if (!handler)
|
if (!handler)
|
||||||
goto disconnect; // Unknown message
|
return false; // Unknown message
|
||||||
if (!handler (app, client, &unpacker))
|
if (!handler (app, client, &unpacker))
|
||||||
goto disconnect; // Invalid message
|
return false; // Invalid message
|
||||||
if (msg_unpacker_get_available (&unpacker) > 0)
|
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;
|
return;
|
||||||
|
|
||||||
disconnect:
|
error:
|
||||||
remove_client (app, client);
|
remove_client (app, client);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
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;
|
app_context_t *app = ev_userdata (loop);
|
||||||
if (status)
|
(void) revents;
|
||||||
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;
|
||||||
|
}
|
||||||
|
|
||||||
int err;
|
|
||||||
client_t *client = xcalloc (1, sizeof *client);
|
client_t *client = xcalloc (1, sizeof *client);
|
||||||
if ((err = uv_tcp_init (server->loop, &client->handle)))
|
client->fd = sock_fd;
|
||||||
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);
|
msg_reader_init (&client->msg_reader);
|
||||||
LIST_PREPEND (app->clients, client);
|
write_queue_init (&client->write_queue);
|
||||||
return;
|
|
||||||
|
|
||||||
free_handle:
|
set_blocking (sock_fd, false);
|
||||||
uv_close ((uv_handle_t *) &client->handle, NULL);
|
ev_io_init (&client->watcher, on_client_ready, sock_fd, EV_READ);
|
||||||
// XXX: should we unref it?
|
client->watcher.data = client;
|
||||||
free_client:
|
ev_io_start (EV_A_ &client->watcher);
|
||||||
free (client);
|
|
||||||
|
LIST_PREPEND (app->clients, client);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Program startup ---------------------------------------------------------
|
// --- Program startup ---------------------------------------------------------
|
||||||
|
@ -1388,17 +1455,13 @@ initialize_client (app_context_t *app, struct addrinfo *address)
|
||||||
exit (EXIT_FAILURE);
|
exit (EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int yes = 1;
|
||||||
|
(void) setsockopt (sock_fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof yes);
|
||||||
|
|
||||||
set_blocking (sock_fd, false);
|
set_blocking (sock_fd, false);
|
||||||
if ((err = uv_tcp_init (uv_default_loop (), &app->server_fd))
|
app->server_fd = sock_fd;
|
||||||
|| (err = uv_tcp_open (&app->server_fd, sock_fd))
|
ev_io_init (&app->server_watcher, on_server_ready, sock_fd, EV_READ);
|
||||||
|| (err = uv_tcp_keepalive (&app->server_fd, true, 30))
|
ev_io_start (EV_DEFAULT_ &app->server_watcher);
|
||||||
|| (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);
|
|
||||||
}
|
|
||||||
|
|
||||||
send_hello_request (app);
|
send_hello_request (app);
|
||||||
send_get_bitmap_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;
|
app->mode = NETWORK_MODE_SERVER;
|
||||||
|
|
||||||
int err;
|
int sock_fd = socket (address->ai_family,
|
||||||
if ((err = uv_tcp_init (uv_default_loop (), &app->listen_fd))
|
address->ai_socktype, address->ai_protocol);
|
||||||
|| (err = uv_tcp_bind (&app->listen_fd, address->ai_addr, 0))
|
if (sock_fd == -1)
|
||||||
|| (err = uv_listen ((uv_stream_t *) &app->listen_fd, 10, on_new_client)))
|
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",
|
fprintf (stderr, "%s: %s: %s\n",
|
||||||
"error", "initialization failed", uv_strerror (err));
|
"error", "initialization failed", strerror (errno));
|
||||||
exit (EXIT_FAILURE);
|
exit (EXIT_FAILURE);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
|
@ -1429,6 +1506,13 @@ main (int argc, char *argv[])
|
||||||
app_context_t app;
|
app_context_t app;
|
||||||
app_init (&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_t options;
|
||||||
app_options_init (&options);
|
app_options_init (&options);
|
||||||
parse_program_arguments (&options, argc, argv);
|
parse_program_arguments (&options, argc, argv);
|
||||||
|
@ -1461,29 +1545,27 @@ main (int argc, char *argv[])
|
||||||
exit (EXIT_FAILURE);
|
exit (EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
||||||
uv_loop_t *loop = uv_default_loop ();
|
ev_set_userdata (loop, &app);
|
||||||
loop->data = &app;
|
|
||||||
|
|
||||||
uv_signal_init (loop, &app.winch_watcher);
|
ev_signal_init (&app.winch_watcher, on_winch, SIGWINCH);
|
||||||
uv_signal_start (&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);
|
ev_timer_init (&app.tty_timer, on_key_timer,
|
||||||
uv_poll_start (&app.tty_watcher, UV_READABLE, on_tty_readable);
|
termo_get_waittime (app.tk) / 1000., 0);
|
||||||
|
|
||||||
uv_timer_init (loop, &app.tty_timer);
|
|
||||||
|
|
||||||
init_palette (&app);
|
init_palette (&app);
|
||||||
update_canvas_for_screen (&app);
|
update_canvas_for_screen (&app);
|
||||||
redraw (&app);
|
redraw (&app);
|
||||||
redraw_canvas (&app);
|
redraw_canvas (&app);
|
||||||
|
|
||||||
uv_run (loop, UV_RUN_DEFAULT);
|
ev_run (loop, 0);
|
||||||
endwin ();
|
endwin ();
|
||||||
|
|
||||||
app_free (&app);
|
app_free (&app);
|
||||||
uv_loop_close (loop);
|
ev_loop_destroy (loop);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
91
utils.c
91
utils.c
|
@ -104,6 +104,24 @@ xrealloc (void *o, size_t n)
|
||||||
(link)->next->prev = (link)->prev; \
|
(link)->next->prev = (link)->prev; \
|
||||||
BLOCK_END
|
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 -------------------------------------------
|
// --- Dynamically allocated strings -------------------------------------------
|
||||||
|
|
||||||
// Basically a string builder to abstract away manual memory management.
|
// Basically a string builder to abstract away manual memory management.
|
||||||
|
@ -236,11 +254,14 @@ static bool
|
||||||
set_blocking (int fd, bool blocking)
|
set_blocking (int fd, bool blocking)
|
||||||
{
|
{
|
||||||
int flags = fcntl (fd, F_GETFL);
|
int flags = fcntl (fd, F_GETFL);
|
||||||
|
|
||||||
bool prev = !(flags & O_NONBLOCK);
|
bool prev = !(flags & O_NONBLOCK);
|
||||||
if (blocking)
|
if (blocking)
|
||||||
flags &= ~O_NONBLOCK;
|
flags &= ~O_NONBLOCK;
|
||||||
else
|
else
|
||||||
flags |= O_NONBLOCK;
|
flags |= O_NONBLOCK;
|
||||||
|
|
||||||
|
(void) fcntl (fd, F_SETFL, flags);
|
||||||
return prev;
|
return prev;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -284,6 +305,76 @@ xstrtoul (unsigned long *out, const char *s, int base)
|
||||||
return errno == 0 && !*end && end != s;
|
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 ----------------------------------------------------------
|
// --- Message reader ----------------------------------------------------------
|
||||||
|
|
||||||
struct msg_reader
|
struct msg_reader
|
||||||
|
|
Loading…
Reference in New Issue