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/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;
+}
+