console-server: allow separate handler instances

Currently, each handler (socket-handler, tty-handler and log-handler)
provides a statically-allocated instance of a handler, which gets
initialized for a console through the ->init callback.

We have upcoming changes that may create more than one console object,
in which case means we will need multiple instances of each handler
type.

This change splits the handler type from the handler instance; the
former is now struct handler_type, with struct handler being the
instance. Handler modules define a (const) struct handler_type, and
->init() now returns a newly-allocated instance of a handler of that
type.

This allows multiple handlers of each type.

Because the handler instances are allocated by type->init, we now
require both ->init and ->fini to be present on registered handlers.

We no longer need the `bool active` member of the handler, as instances
are always active.

Change-Id: Id97f15bd6445e17786f5883b849de8559c5ea434
Signed-off-by: Jeremy Kerr <jk@codeconstruct.com.au>
diff --git a/console-dbus.c b/console-dbus.c
index dd53908..91d99c7 100644
--- a/console-dbus.c
+++ b/console-dbus.c
@@ -33,22 +33,25 @@
 
 static void tty_change_baudrate(struct console *console)
 {
-	struct handler *handler;
 	int i;
 	int rc;
 
 	tty_init_termios(console);
 
 	for (i = 0; i < console->n_handlers; i++) {
+		const struct handler_type *type;
+		struct handler *handler;
+
 		handler = console->handlers[i];
-		if (!handler->baudrate) {
+		type = handler->type;
+		if (!type->baudrate) {
 			continue;
 		}
 
-		rc = handler->baudrate(handler, console->tty.uart.baud);
+		rc = type->baudrate(handler, console->tty.uart.baud);
 		if (rc) {
 			warnx("Can't set terminal baudrate for handler %s",
-			      handler->name);
+			      type->name);
 		}
 	}
 }
diff --git a/console-server.c b/console-server.c
index d2c2419..4ff3a4e 100644
--- a/console-server.c
+++ b/console-server.c
@@ -533,32 +533,46 @@
 static void handlers_init(struct console *console, struct config *config)
 {
 	/* NOLINTBEGIN(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp) */
-	extern struct handler *__start_handlers;
-	extern struct handler *__stop_handlers;
+	extern const struct handler_type *const __start_handlers;
+	extern const struct handler_type *const __stop_handlers;
 	/* NOLINTEND(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp) */
-	struct handler *handler;
-	int i;
-	int rc;
+	size_t n_types;
+	int j = 0;
+	size_t i;
 
-	console->n_handlers = &__stop_handlers - &__start_handlers;
-	console->handlers = &__start_handlers;
+	n_types = &__stop_handlers - &__start_handlers;
+	console->handlers = calloc(n_types, sizeof(struct handler *));
+	if (!console->handlers) {
+		err(EXIT_FAILURE, "malloc(handlers)");
+	}
 
-	printf("%ld handler%s\n", console->n_handlers,
-	       console->n_handlers == 1 ? "" : "s");
+	printf("%ld handler type%s\n", n_types, n_types == 1 ? "" : "s");
 
-	for (i = 0; i < console->n_handlers; i++) {
-		handler = console->handlers[i];
+	for (i = 0; i < n_types; i++) {
+		const struct handler_type *type = &__start_handlers[i];
+		struct handler *handler;
 
-		rc = 0;
-		if (handler->init) {
-			rc = handler->init(handler, console, config);
+		/* Should be picked up at build time by
+		 * console_handler_register, but check anyway
+		 */
+		if (!type->init || !type->fini) {
+			errx(EXIT_FAILURE,
+			     "invalid handler type %s: no init() / fini()",
+			     type->name);
 		}
 
-		handler->active = rc == 0;
+		handler = type->init(type, console, config);
 
-		printf("  %s [%sactive]\n", handler->name,
-		       handler->active ? "" : "in");
+		printf("  console '%s': handler %s [%sactive]\n",
+		       console->console_id, type->name, handler ? "" : "in");
+
+		if (handler) {
+			handler->type = type;
+			console->handlers[j++] = handler;
+		}
 	}
+
+	console->n_handlers = j;
 }
 
 static void handlers_fini(struct console *console)
@@ -568,10 +582,12 @@
 
 	for (i = 0; i < console->n_handlers; i++) {
 		handler = console->handlers[i];
-		if (handler->fini && handler->active) {
-			handler->fini(handler);
-		}
+		handler->type->fini(handler);
 	}
+
+	free(console->handlers);
+	console->handlers = NULL;
+	console->n_handlers = 0;
 }
 
 static int get_current_time(struct timeval *tv)
diff --git a/console-server.h b/console-server.h
index efbfce8..3fd572c 100644
--- a/console-server.h
+++ b/console-server.h
@@ -44,23 +44,35 @@
  * If a handler needs to monitor a separate file descriptor for events, use the
  * poller API, through console_poller_register().
  */
-struct handler {
+struct handler;
+
+struct handler_type {
 	const char *name;
-	int (*init)(struct handler *handler, struct console *console,
-		    struct config *config);
+	struct handler *(*init)(const struct handler_type *type,
+				struct console *console, struct config *config);
 	void (*fini)(struct handler *handler);
 	int (*baudrate)(struct handler *handler, speed_t baudrate);
-	bool active;
+};
+
+struct handler {
+	const struct handler_type *type;
 };
 
 /* NOLINTBEGIN(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp) */
 #define __handler_name(n) __handler_##n
 #define _handler_name(n)  __handler_name(n)
 
+#ifndef __clang__
+#define handler_type_check(h) BUILD_ASSERT_OR_ZERO((h)->init && (h)->fini)
+#else
+/* clang doesn't seem to be able to constify the type ops */
+#define handler_type_check(h) 0
+#endif
+
 #define console_handler_register(h)                                            \
 	static const __attribute__((section("handlers")))                      \
-	__attribute__((used)) struct handler *                                 \
-	_handler_name(__COUNTER__) = h
+	__attribute__((used)) struct handler_type *                            \
+	_handler_name(__COUNTER__) = (h) + handler_type_check(h)
 /* NOLINTEND(bugprone-reserved-identifier,cert-dcl37-c,cert-dcl51-cpp) */
 
 int console_data_out(struct console *console, const uint8_t *data, size_t len);
diff --git a/log-handler.c b/log-handler.c
index d93b679..4109d8b 100644
--- a/log-handler.c
+++ b/log-handler.c
@@ -149,15 +149,21 @@
 	return 0;
 }
 
-static int log_init(struct handler *handler, struct console *console,
-		    struct config *config)
+static struct handler *log_init(const struct handler_type *type
+				__attribute__((unused)),
+				struct console *console, struct config *config)
 {
-	struct log_handler *lh = to_log_handler(handler);
+	struct log_handler *lh;
 	const char *filename;
 	const char *logsize_str;
 	size_t logsize = default_logsize;
 	int rc;
 
+	lh = malloc(sizeof(*lh));
+	if (!lh) {
+		return NULL;
+	}
+
 	lh->console = console;
 	lh->pagesize = 4096;
 	lh->size = 0;
@@ -183,17 +189,22 @@
 	rc = asprintf(&lh->rotate_filename, "%s.1", filename);
 	if (rc < 0) {
 		warn("Failed to construct rotate filename");
-		return -1;
+		goto err_free;
 	}
 
 	rc = log_create(lh);
 	if (rc < 0) {
-		return -1;
+		goto err_free;
 	}
 	lh->rbc = console_ringbuffer_consumer_register(console,
 						       log_ringbuffer_poll, lh);
 
-	return 0;
+	return &lh->handler;
+
+err_free:
+	free(lh->log_filename);
+	free(lh);
+	return NULL;
 }
 
 static void log_fini(struct handler *handler)
@@ -203,14 +214,13 @@
 	close(lh->fd);
 	free(lh->log_filename);
 	free(lh->rotate_filename);
+	free(lh);
 }
 
-static struct log_handler log_handler = {
-	.handler = {
-		.name		= "log",
-		.init		= log_init,
-		.fini		= log_fini,
-	},
+static const struct handler_type log_handler = {
+	.name = "log",
+	.init = log_init,
+	.fini = log_fini,
 };
 
-console_handler_register(&log_handler.handler);
+console_handler_register(&log_handler);
diff --git a/socket-handler.c b/socket-handler.c
index 5f9d383..3abdc97 100644
--- a/socket-handler.c
+++ b/socket-handler.c
@@ -355,7 +355,7 @@
 	int n;
 
 	for (i = 0; i < console->n_handlers; i++) {
-		if (strcmp(console->handlers[i]->name, "socket") == 0) {
+		if (strcmp(console->handlers[i]->type->name, "socket") == 0) {
 			sh = to_socket_handler(console->handlers[i]);
 			break;
 		}
@@ -416,15 +416,23 @@
 	return rc;
 }
 
-static int socket_init(struct handler *handler, struct console *console,
-		       struct config *config __attribute__((unused)))
+static struct handler *socket_init(const struct handler_type *type
+				   __attribute__((unused)),
+				   struct console *console,
+				   struct config *config
+				   __attribute__((unused)))
 {
-	struct socket_handler *sh = to_socket_handler(handler);
+	struct socket_handler *sh;
 	struct sockaddr_un addr;
 	size_t addrlen;
 	ssize_t len;
 	int rc;
 
+	sh = malloc(sizeof(*sh));
+	if (!sh) {
+		return NULL;
+	}
+
 	sh->console = console;
 	sh->clients = NULL;
 	sh->n_clients = 0;
@@ -438,7 +446,7 @@
 		} else {
 			warn("Socket name length exceeds buffer limits");
 		}
-		return -1;
+		goto err_free;
 	}
 
 	/* Try to take a socket from systemd first */
@@ -450,7 +458,7 @@
 		sh->sd = socket(AF_UNIX, SOCK_STREAM, 0);
 		if (sh->sd < 0) {
 			warn("Can't create socket");
-			return -1;
+			goto err_free;
 		}
 
 		addrlen = sizeof(addr) - sizeof(addr.sun_path) + len;
@@ -461,23 +469,26 @@
 			console_socket_path_readable(&addr, addrlen, name);
 			warn("Can't bind to socket path %s (terminated at first null)",
 			     name);
-			goto cleanup;
+			goto err_close;
 		}
 
 		rc = listen(sh->sd, 1);
 		if (rc) {
 			warn("Can't listen for incoming connections");
-			goto cleanup;
+			goto err_close;
 		}
 	}
 
-	sh->poller = console_poller_register(console, handler, socket_poll,
+	sh->poller = console_poller_register(console, &sh->handler, socket_poll,
 					     NULL, sh->sd, POLLIN, NULL);
 
-	return 0;
-cleanup:
+	return &sh->handler;
+
+err_close:
 	close(sh->sd);
-	return -1;
+err_free:
+	free(sh);
+	return NULL;
 }
 
 static void socket_fini(struct handler *handler)
@@ -493,14 +504,13 @@
 	}
 
 	close(sh->sd);
+	free(sh);
 }
 
-static struct socket_handler socket_handler = {
-	.handler = {
-		.name		= "socket",
-		.init		= socket_init,
-		.fini		= socket_fini,
-	},
+static const struct handler_type socket_handler = {
+	.name = "socket",
+	.init = socket_init,
+	.fini = socket_fini,
 };
 
-console_handler_register(&socket_handler.handler);
+console_handler_register(&socket_handler);
diff --git a/tty-handler.c b/tty-handler.c
index 3976aec..734f81e 100644
--- a/tty-handler.c
+++ b/tty-handler.c
@@ -236,10 +236,12 @@
 	return 0;
 }
 
-static int tty_init(struct handler *handler, struct console *console,
-		    struct config *config __attribute__((unused)))
+static struct handler *tty_init(const struct handler_type *type
+				__attribute__((unused)),
+				struct console *console,
+				struct config *config __attribute__((unused)))
 {
-	struct tty_handler *th = to_tty_handler(handler);
+	struct tty_handler *th;
 	speed_t desired_speed;
 	const char *tty_name;
 	const char *tty_baud;
@@ -248,19 +250,25 @@
 
 	tty_name = config_get_value(config, "local-tty");
 	if (!tty_name) {
-		return -1;
+		return NULL;
 	}
 
 	rc = asprintf(&tty_path, "/dev/%s", tty_name);
 	if (!rc) {
-		return -1;
+		return NULL;
+	}
+
+	th = malloc(sizeof(*th));
+	if (!th) {
+		return NULL;
 	}
 
 	th->fd = open(tty_path, O_RDWR | O_NONBLOCK);
 	if (th->fd < 0) {
 		warn("Can't open %s; disabling local tty", tty_name);
 		free(tty_path);
-		return -1;
+		free(th);
+		return NULL;
 	}
 
 	free(tty_path);
@@ -286,13 +294,13 @@
 		fprintf(stderr, "Couldn't make %s a raw terminal\n", tty_name);
 	}
 
-	th->poller = console_poller_register(console, handler, tty_poll, NULL,
-					     th->fd, POLLIN, NULL);
+	th->poller = console_poller_register(console, &th->handler, tty_poll,
+					     NULL, th->fd, POLLIN, NULL);
 	th->console = console;
 	th->rbc = console_ringbuffer_consumer_register(console,
 						       tty_ringbuffer_poll, th);
 
-	return 0;
+	return &th->handler;
 }
 
 static void tty_fini(struct handler *handler)
@@ -302,6 +310,7 @@
 		console_poller_unregister(th->console, th->poller);
 	}
 	close(th->fd);
+	free(th);
 }
 
 static int tty_baudrate(struct handler *handler, speed_t baudrate)
@@ -321,13 +330,11 @@
 	return 0;
 }
 
-static struct tty_handler tty_handler = {
-	.handler = {
-		.name		= "tty",
-		.init		= tty_init,
-		.fini		= tty_fini,
-		.baudrate	= tty_baudrate,
-	},
+static const struct handler_type tty_handler = {
+	.name = "tty",
+	.init = tty_init,
+	.fini = tty_fini,
+	.baudrate = tty_baudrate,
 };
 
-console_handler_register(&tty_handler.handler);
+console_handler_register(&tty_handler);