Add socket handler & console client

This change adds a socket handler to the console-server, exposing a unix
domain socket for the UART data. We use this to replace the existing
stdio handler.

We also add a client for this socket, allowing multiple processes to
read/write data from & to the server.

Signed-off-by: Jeremy Kerr <jk@ozlabs.org>
diff --git a/Makefile b/Makefile
index ed46115..f076935 100644
--- a/Makefile
+++ b/Makefile
@@ -2,9 +2,12 @@
 CC = gcc
 CFLAGS = -Wall -Wextra -O2
 
-all: console-server
+all: console-server console-client
 
-console-server: console-server.o util.o stdio-handler.o log-handler.o
+console-server: console-server.o util.o \
+		log-handler.o socket-handler.o
+
+console-client: console-client.o util.o
 
 clean:
-	rm -f console-server *.o
+	rm -f console-server console-client *.o
diff --git a/console-client.c b/console-client.c
new file mode 100644
index 0000000..984ae13
--- /dev/null
+++ b/console-client.c
@@ -0,0 +1,223 @@
+
+#include <err.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "console-server.h"
+
+enum process_rc {
+	PROCESS_OK = 0,
+	PROCESS_ERR,
+	PROCESS_EXIT,
+};
+
+struct console_client {
+	int		console_sd;
+	int		fd_in;
+	int		fd_out;
+	bool		is_tty;
+	struct termios	orig_termios;
+	int		esc_str_pos;
+	bool		newline;
+};
+
+static const uint8_t esc_str[] = { '~', '.' };
+
+static enum process_rc process_tty(struct console_client *client)
+{
+	uint8_t e, buf[4096];
+	long i;
+	ssize_t len;
+	int rc;
+
+	len = read(client->fd_in, buf, sizeof(buf));
+	if (len < 0)
+		return PROCESS_ERR;
+	if (len == 0)
+		return PROCESS_EXIT;
+
+	/* check escape sequence status */
+	for (i = 0; i < len; i++) {
+		/* the escape string is only valid after a newline */
+		if (buf[i] == '\r') {
+			client->newline = true;
+			continue;
+		}
+
+		if (!client->newline)
+			continue;
+
+		e = esc_str[client->esc_str_pos];
+		if (buf[i] == e) {
+			client->esc_str_pos++;
+
+			/* have we hit the end of the escape string? */
+			if (client->esc_str_pos == ARRAY_SIZE(esc_str)) {
+
+				/* flush out any data before the escape */
+				if (i > client->esc_str_pos)
+					write_buf_to_fd(client->console_sd,
+						buf,
+						i - client->esc_str_pos);
+
+				return PROCESS_EXIT;
+			}
+		} else {
+			/* if we're partially the way through the escape
+			 * string, flush out the bytes we'd skipped */
+			if (client->esc_str_pos)
+				write_buf_to_fd(client->console_sd,
+						esc_str, client->esc_str_pos);
+			client->esc_str_pos = 0;
+			client->newline = false;
+		}
+	}
+
+	rc = write_buf_to_fd(client->console_sd, buf,
+			len - client->esc_str_pos);
+	if (rc < 0)
+		return PROCESS_ERR;
+
+	return PROCESS_OK;
+}
+
+
+static int process_console(struct console_client *client)
+{
+	uint8_t buf[4096];
+	int len, rc;
+
+	len = read(client->console_sd, buf, sizeof(buf));
+	if (len < 0) {
+		warn("Can't read from server");
+		return PROCESS_ERR;
+	}
+	if (len == 0) {
+		fprintf(stderr, "Connection closed\n");
+		return PROCESS_EXIT;
+	}
+
+	rc = write_buf_to_fd(client->fd_out, buf, len);
+	return rc ? PROCESS_ERR : PROCESS_OK;
+}
+
+/*
+ * Setup our local file descriptors for IO: use stdin/stdout, and if we're on a
+ * TTY, put it in canonical mode
+ */
+static int client_tty_init(struct console_client *client)
+{
+	struct termios termios;
+	int rc;
+
+	client->fd_in = STDIN_FILENO;
+	client->fd_out = STDOUT_FILENO;
+	client->is_tty = isatty(client->fd_in);
+
+	if (!client->is_tty)
+		return 0;
+
+	rc = tcgetattr(client->fd_in, &termios);
+	if (rc) {
+		warn("Can't get terminal attributes for console");
+		return -1;
+	}
+	memcpy(&client->orig_termios, &termios, sizeof(client->orig_termios));
+	cfmakeraw(&termios);
+
+	rc = tcsetattr(client->fd_in, TCSANOW, &termios);
+	if (rc) {
+		warn("Can't set terminal attributes for console");
+		return -1;
+	}
+
+	return 0;
+}
+
+static int client_init(struct console_client *client)
+{
+	struct sockaddr_un addr;
+	int rc;
+
+	client->console_sd = socket(AF_UNIX, SOCK_STREAM, 0);
+	if (!client->console_sd) {
+		warn("Can't open socket");
+		return -1;
+	}
+
+	memset(&addr, 0, sizeof(addr));
+	addr.sun_family = AF_UNIX;
+	memcpy(addr.sun_path, console_socket_path, console_socket_path_len);
+
+	rc = connect(client->console_sd, (struct sockaddr *)&addr,
+			sizeof(addr));
+	if (rc) {
+		warn("Can't connect to console server");
+		close(client->console_sd);
+		return -1;
+	}
+
+	return 0;
+}
+
+static void client_fini(struct console_client *client)
+{
+	if (client->is_tty)
+		tcsetattr(client->fd_in, TCSANOW, &client->orig_termios);
+	close(client->console_sd);
+}
+
+int main(void)
+{
+	struct console_client _client, *client;
+	struct pollfd pollfds[2];
+	enum process_rc prc;
+	int rc;
+
+	client = &_client;
+	memset(client, 0, sizeof(*client));
+
+	rc = client_init(client);
+	if (rc)
+		return EXIT_FAILURE;
+
+	rc = client_tty_init(client);
+	if (rc)
+		goto out_fini;
+
+	for (;;) {
+		pollfds[0].fd = client->fd_in;
+		pollfds[0].events = POLLIN;
+		pollfds[1].fd = client->console_sd;
+		pollfds[1].events = POLLIN;
+
+		rc = poll(pollfds, 2, -1);
+		if (rc < 0) {
+			warn("Poll failure");
+			break;
+		}
+
+		if (pollfds[0].revents)
+			prc = process_tty(client);
+
+		if (prc == PROCESS_OK && pollfds[1].revents)
+			prc = process_console(client);
+
+		rc = (prc == PROCESS_ERR) ? -1 : 0;
+		if (prc != PROCESS_OK)
+			break;
+	}
+
+out_fini:
+	client_fini(client);
+	return rc ? EXIT_FAILURE : EXIT_SUCCESS;
+}
+
diff --git a/console-server.h b/console-server.h
index 38cddef..292b54a 100644
--- a/console-server.h
+++ b/console-server.h
@@ -48,6 +48,11 @@
 
 void console_unregister_poller(struct console *console, struct poller *poller);
 
+/* socket paths */
+const char *console_socket_path;
+const size_t console_socket_path_len;
+const char *console_socket_path_readable;
+
 /* utils */
 int write_buf_to_fd(int fd, const uint8_t *buf, size_t len);
 
diff --git a/console-socket.c b/console-socket.c
new file mode 100644
index 0000000..c81c044
--- /dev/null
+++ b/console-socket.c
@@ -0,0 +1,5 @@
+
+const char console_socket_path[] = "\0obmc-uart-console";
+const size_t console_socket_len = sizeof(console_socket_path);
+const char *console_socket_path_readable = console_socket_path + 1;
+
diff --git a/socket-handler.c b/socket-handler.c
new file mode 100644
index 0000000..8e0cebc
--- /dev/null
+++ b/socket-handler.c
@@ -0,0 +1,177 @@
+
+#include <err.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+#include <endian.h>
+
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include "console-server.h"
+
+struct client {
+	struct poller	*poller;
+	int		fd;
+};
+
+struct socket_handler {
+	struct handler	handler;
+	struct console	*console;
+	int		sd;
+
+	struct client	*clients;
+	int		n_clients;
+};
+
+static struct socket_handler *to_socket_handler(struct handler *handler)
+{
+	return container_of(handler, struct socket_handler, handler);
+}
+
+static void client_close(struct socket_handler *sh, struct client *client)
+{
+	int idx;
+
+	close(client->fd);
+	if (client->poller)
+		console_unregister_poller(sh->console, client->poller);
+
+	idx = client - sh->clients;
+
+	sh->n_clients--;
+	memmove(&sh->clients[idx], &sh->clients[idx+1],
+			sizeof(*sh->clients) * (sh->n_clients - idx));
+	sh->clients = realloc(sh->clients, sizeof(sh->clients) * sh->n_clients);
+}
+
+static enum poller_ret client_poll(struct handler *handler,
+		int events, void *data)
+{
+	struct socket_handler *sh = to_socket_handler(handler);
+	struct client *client = data;
+	uint8_t buf[4096];
+	int rc;
+
+	if (!(events & POLLIN))
+		return POLLER_OK;
+
+	rc = read(client->fd, buf, sizeof(buf));
+	if (rc <= 0) {
+		client->poller = NULL;
+		client_close(sh, client);
+		return POLLER_REMOVE;
+	}
+
+	console_data_out(sh->console, buf, rc);
+
+	return POLLER_OK;
+}
+
+static void client_send_data(struct socket_handler *sh,
+		struct client *client, uint8_t *buf, size_t len)
+{
+	int rc;
+
+	rc = write_buf_to_fd(client->fd, buf, len);
+	if (rc)
+		client_close(sh, client);
+}
+
+static enum poller_ret socket_poll(struct handler *handler,
+		int events, void __attribute__((unused)) *data)
+{
+	struct socket_handler *sh = to_socket_handler(handler);
+	struct client *client;
+	int fd, n;
+
+	if (!(events & POLLIN))
+		return POLLER_OK;
+
+	fd = accept(sh->sd, NULL, NULL);
+	if (fd < 0)
+		return POLLER_OK;
+
+	n = sh->n_clients++;
+	sh->clients = realloc(sh->clients, sizeof(*client) * sh->n_clients);
+	client = &sh->clients[n];
+
+	client->fd = fd;
+	client->poller = console_register_poller(sh->console, handler,
+			client_poll, client->fd, POLLIN, client);
+
+	return POLLER_OK;
+
+}
+
+static int socket_init(struct handler *handler, struct console *console)
+{
+	struct socket_handler *sh = to_socket_handler(handler);
+	struct sockaddr_un addr;
+	int rc;
+
+	sh->console = console;
+	sh->clients = NULL;
+	sh->n_clients = 0;
+
+	sh->sd = socket(AF_UNIX, SOCK_STREAM, 0);
+	if(sh->sd < 0) {
+		warn("Can't create socket");
+		return -1;
+	}
+
+	memset(&addr, 0, sizeof(addr));
+	addr.sun_family = AF_UNIX;
+	memcpy(addr.sun_path, console_socket_path, console_socket_path_len);
+
+	rc = bind(sh->sd, (struct sockaddr *)&addr, sizeof(addr));
+	if (rc) {
+		warn("Can't bind to socket path %s",
+				console_socket_path_readable);
+		return -1;
+	}
+
+	rc = listen(sh->sd, 1);
+	if (rc) {
+		warn("Can't listen for incoming connections");
+		return -1;
+	}
+
+	console_register_poller(console, handler, socket_poll, sh->sd, POLLIN,
+			NULL);
+
+	return 0;
+}
+
+static int socket_data(struct handler *handler, uint8_t *buf, size_t len)
+{
+	struct socket_handler *sh = to_socket_handler(handler);
+	int i;
+
+	for (i = 0; i < sh->n_clients; i++) {
+		struct client *client = &sh->clients[i];
+		client_send_data(sh, client, buf, len);
+	}
+	return 0;
+}
+
+static void socket_fini(struct handler *handler)
+{
+	struct socket_handler *sh = to_socket_handler(handler);
+	close(sh->sd);
+}
+
+static struct socket_handler socket_handler = {
+	.handler = {
+		.name		= "socket",
+		.init		= socket_init,
+		.data_in	= socket_data,
+		.fini		= socket_fini,
+	},
+};
+
+console_register_handler(&socket_handler.handler);
+
diff --git a/stdio-handler.c b/stdio-handler.c
deleted file mode 100644
index d8a5a8a..0000000
--- a/stdio-handler.c
+++ /dev/null
@@ -1,150 +0,0 @@
-
-#include <err.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <termios.h>
-#include <unistd.h>
-
-#include "console-server.h"
-
-struct stdio_handler {
-	struct handler	handler;
-	struct console	*console;
-	struct poller	*poller;
-	int		fd_in;
-	int		fd_out;
-	bool		is_tty;
-	struct termios	orig_termios;
-	int		esc_str_pos;
-};
-
-static struct stdio_handler *to_stdio_handler(struct handler *handler)
-{
-	return container_of(handler, struct stdio_handler, handler);
-}
-
-
-static const uint8_t esc_str[] = { '\r', '~', '.' };
-
-
-static int process_input(struct stdio_handler *sh,
-		uint8_t *buf, size_t len)
-{
-	unsigned long i;
-	uint8_t e;
-
-	e = esc_str[sh->esc_str_pos];
-
-	for (i = 0; i < len; i++) {
-		if (buf[i] == e) {
-			sh->esc_str_pos++;
-			if (sh->esc_str_pos == ARRAY_SIZE(esc_str))
-				return 1;
-			e = esc_str[sh->esc_str_pos];
-		} else {
-			console_data_out(sh->console,
-					esc_str, sh->esc_str_pos);
-			sh->esc_str_pos = 0;
-		}
-	}
-	return 0;
-}
-
-static enum poller_ret stdio_poll(struct handler *handler,
-		int events, void __attribute__((unused)) *data)
-{
-	struct stdio_handler *sh = to_stdio_handler(handler);
-	uint8_t buf[4096];
-	ssize_t len;
-	int rc;
-
-	if (!(events & POLLIN))
-		return POLLER_OK;
-
-	len = read(sh->fd_in, buf, sizeof(buf));
-	if (len <= 0)
-		goto err;
-
-	rc = process_input(sh, buf, len);
-	if (rc)
-		return POLLER_EXIT;
-
-	rc = console_data_out(sh->console, buf, len);
-	if (rc < 0)
-		goto err;
-
-	return POLLER_OK;
-
-err:
-	sh->poller = NULL;
-	return POLLER_REMOVE;
-}
-
-
-
-/*
- * Setup our console channel for IO: use stdin/stdout, and if we're on a TTY,
- * put it in canonical mode
- */
-static int stdio_init(struct handler *handler, struct console *console)
-{
-	struct stdio_handler *sh = to_stdio_handler(handler);
-	struct termios termios;
-	int rc;
-
-	sh->console = console;
-	sh->fd_in = STDIN_FILENO;
-	sh->fd_out = STDOUT_FILENO;
-	sh->is_tty = isatty(sh->fd_in);
-
-	if (!sh->is_tty)
-		return 0;
-
-	rc = tcgetattr(sh->fd_in, &termios);
-	if (rc) {
-		warn("Can't get terminal attributes for console");
-		return -1;
-	}
-	memcpy(&sh->orig_termios, &termios, sizeof(sh->orig_termios));
-	cfmakeraw(&termios);
-
-	rc = tcsetattr(sh->fd_in, TCSANOW, &termios);
-	if (rc) {
-		warn("Can't set terminal attributes for console");
-		return -1;
-	}
-
-	sh->poller = console_register_poller(console, handler, stdio_poll,
-			sh->fd_in, POLLIN, NULL);
-
-	return 0;
-}
-
-static int stdio_data(struct handler *handler, uint8_t *buf, size_t len)
-{
-	struct stdio_handler *sh = to_stdio_handler(handler);
-	return write_buf_to_fd(sh->fd_out, buf, len);
-}
-
-static void stdio_fini(struct handler *handler)
-{
-	struct stdio_handler *sh = to_stdio_handler(handler);
-	if (sh->is_tty)
-		tcsetattr(sh->fd_in, TCSANOW, &sh->orig_termios);
-	if (sh->poller)
-		console_unregister_poller(sh->console, sh->poller);
-}
-
-static struct stdio_handler stdio_handler = {
-	.handler = {
-		.name		= "stdio",
-		.init		= stdio_init,
-		.data_in	= stdio_data,
-		.fini		= stdio_fini,
-	},
-};
-
-console_register_handler(&stdio_handler.handler);
-