server: use ringbuffer for all handlers

Currently, we use the a ringbuffer within the socket handler to manage
bursts of data to slower clients.

However, we're also seeing cases where the local tty handler becomes
blocking as well. So, we want to implement a buffer within the tty
handler too.

This change moves the ringbuffer 'up a layer' - from the socket handler
to the core console code.

We remove the ->data_in callback from handlers, and work on the
assumption that handlers have registered their own consumer on the
console's ringbuffer (through a new helper function,
console_ringbuffer_consumer_register()).

Change-Id: Ie8f02d6632578c50bb5e2dfb9bee6ece86432135
Signed-off-by: Jeremy Kerr <jk@ozlabs.org>
diff --git a/tty-handler.c b/tty-handler.c
index 523743b..22f9a75 100644
--- a/tty-handler.c
+++ b/tty-handler.c
@@ -18,6 +18,7 @@
 
 #include <assert.h>
 #include <err.h>
+#include <errno.h>
 #include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -28,10 +29,11 @@
 #include "console-server.h"
 
 struct tty_handler {
-	struct handler	handler;
-	struct console	*console;
-	struct poller	*poller;
-	int		fd;
+	struct handler			handler;
+	struct console			*console;
+	struct ringbuffer_consumer	*rbc;
+	struct poller			*poller;
+	int				fd;
 };
 
 struct terminal_speed_name {
@@ -44,24 +46,98 @@
 	return container_of(handler, struct tty_handler, handler);
 }
 
+static int tty_drain_queue(struct tty_handler *th, size_t force_len)
+{
+	size_t len, total_len;
+	ssize_t wlen;
+	uint8_t *buf;
+	int flags;
+
+	/* if we're forcing data, we need to clear non-blocking mode */
+	if (force_len) {
+		flags = fcntl(th->fd, F_GETFL, 0);
+		if (flags & O_NONBLOCK) {
+			flags &= ~O_NONBLOCK;
+			fcntl(th->fd, F_SETFL, flags);
+		}
+	}
+
+	total_len = 0;
+
+	for (;;) {
+		len = ringbuffer_dequeue_peek(th->rbc, total_len, &buf);
+		if (!len)
+			break;
+
+		/* write as little as possible while blocking */
+		if (force_len && force_len < total_len + len)
+			len = force_len - total_len;
+
+		wlen = write(th->fd, buf, len);
+		if (wlen < 0) {
+			if (errno == EINTR)
+				continue;
+			if ((errno == EAGAIN || errno == EWOULDBLOCK)
+					&& !force_len)
+				break;
+			warn("failed writing to local tty; disabling");
+			return -1;
+		}
+
+		total_len += wlen;
+
+		if (force_len && total_len >= force_len)
+			break;
+	}
+
+	ringbuffer_dequeue_commit(th->rbc, total_len);
+
+	if (force_len)
+		fcntl(th->fd, F_SETFL, flags | O_NONBLOCK);
+
+	return 0;
+}
+
+static enum ringbuffer_poll_ret tty_ringbuffer_poll(void *arg, size_t force_len)
+{
+	struct tty_handler *th = arg;
+	int rc;
+
+	rc = tty_drain_queue(th, force_len);
+	if (rc) {
+		console_poller_unregister(th->console, th->poller);
+		return RINGBUFFER_POLL_REMOVE;
+	}
+
+	return RINGBUFFER_POLL_OK;
+}
+
 static enum poller_ret tty_poll(struct handler *handler,
 		int events, void __attribute__((unused)) *data)
 {
 	struct tty_handler *th = to_tty_handler(handler);
 	uint8_t buf[4096];
 	ssize_t len;
+	int rc;
 
-	if (!(events & POLLIN))
-		return POLLER_OK;
+	if (events & POLLIN) {
+		len = read(th->fd, buf, sizeof(buf));
+		if (len <= 0) {
+			th->poller = NULL;
+			close(th->fd);
+			return POLLER_REMOVE;
+		}
 
-	len = read(th->fd, buf, sizeof(buf));
-	if (len <= 0) {
-		th->poller = NULL;
-		close(th->fd);
-		return POLLER_REMOVE;
+		console_data_out(th->console, buf, len);
 	}
 
-	console_data_out(th->console, buf, len);
+	if (events & POLLOUT) {
+		rc = tty_drain_queue(th, 0);
+		if (rc) {
+			ringbuffer_consumer_unregister(th->rbc);
+			return POLLER_REMOVE;
+		}
+	}
 
 	return POLLER_OK;
 }
@@ -202,16 +278,12 @@
 	th->poller = console_poller_register(console, handler, tty_poll,
 			th->fd, POLLIN, NULL);
 	th->console = console;
+	th->rbc = console_ringbuffer_consumer_register(console,
+			tty_ringbuffer_poll, th);
 
 	return 0;
 }
 
-static int tty_data(struct handler *handler, uint8_t *buf, size_t len)
-{
-	struct tty_handler *th = to_tty_handler(handler);
-	return write_buf_to_fd(th->fd, buf, len);
-}
-
 static void tty_fini(struct handler *handler)
 {
 	struct tty_handler *th = to_tty_handler(handler);
@@ -224,7 +296,6 @@
 	.handler = {
 		.name		= "tty",
 		.init		= tty_init,
-		.data_in	= tty_data,
 		.fini		= tty_fini,
 	},
 };