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);
-