mboxd: Add backend DBus interface and commandline options

Also implement a backend commandline option to mboxctl: `mboxctl
--backend ...`, to allow easy run-time switching of the backend from the
commandline.

Switching between VPNOR and file backends via mboxctl was tested on
Witherspoon, and MTD and file backends on Romulus.

Change-Id: Iaf0e27ecf1d5cdd9e3a31729fb179096bbc37408
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
diff --git a/backend.h b/backend.h
index 5b043ec..f7b56b4 100644
--- a/backend.h
+++ b/backend.h
@@ -6,6 +6,7 @@
 #define BACKEND_H
 
 #include <assert.h>
+#include <errno.h>
 #include <stdbool.h>
 #include <stdint.h>
 #include <mtd/mtd-abi.h>
@@ -16,10 +17,6 @@
 /* Estimate as to how long (milliseconds) it takes to access a MB from flash */
 #define FLASH_ACCESS_MS_PER_MB		8000
 
-struct backend backend_get_mtd(void);
-struct backend backend_get_file(void);
-struct backend backend_get_vpnor(void);
-
 enum backend_reset_mode { reset_lpc_flash, reset_lpc_memory };
 
 struct backend_ops;
@@ -146,7 +143,8 @@
 	master->block_size_shift = 34;
 #endif
 
-	assert(master->ops->init);
+	if (!master->ops->init)
+		return -ENOTSUP;
 
 	rc = master->ops->init(master, data);
 	if (rc < 0)
@@ -224,11 +222,32 @@
 	return backend->ops->reset(backend, buf, count);
 }
 
+struct backend backend_get_mtd(void);
 int backend_probe_mtd(struct backend *master, const char *path);
+
+struct backend backend_get_file(void);
 int backend_probe_file(struct backend *master, const char *path);
+
 /* Avoid dependency on vpnor/mboxd_pnor_partition_table.h */
 struct vpnor_partition_paths;
+#ifdef VIRTUAL_PNOR_ENABLED
+struct backend backend_get_vpnor(void);
+
 int backend_probe_vpnor(struct backend *master,
                         const struct vpnor_partition_paths *paths);
+#else
+static inline struct backend backend_get_vpnor(void)
+{
+	struct backend be = { 0 };
+
+	return be;
+}
+
+static inline int backend_probe_vpnor(struct backend *master,
+				      const struct vpnor_partition_paths *paths)
+{
+	return -ENOTSUP;
+}
+#endif
 
 #endif /* BACKEND_H */
diff --git a/control.c b/control.c
index acca924..4b73efa 100644
--- a/control.c
+++ b/control.c
@@ -3,12 +3,12 @@
 #include <errno.h>
 #include <stdlib.h>
 
+#include "backend.h"
 #include "common.h"
 #include "dbus.h"
-#include "mboxd.h"
-#include "backend.h"
 #include "lpc.h"
-#include "transport_mbox.h"
+#include "mboxd.h"
+#include "protocol.h"
 #include "windows.h"
 
 int control_ping(struct mbox_context *context)
@@ -116,3 +116,29 @@
 
 	return rc;
 }
+
+int control_set_backend(struct mbox_context *context, struct backend *backend,
+			void *data)
+{
+	int rc;
+
+	if (context->state & STATE_SUSPENDED)
+		return -EINVAL;
+
+	rc = protocol_events_clear(context, BMC_EVENT_DAEMON_READY);
+	if (rc < 0)
+		return rc;
+
+	backend_free(&context->backend);
+
+	rc = backend_init(&context->backend, backend, data);
+	if (rc < 0)
+		return rc;
+
+	rc = __protocol_reset(context);
+	if (rc < 0)
+		return rc;
+
+	return protocol_events_set(context,
+			BMC_EVENT_DAEMON_READY | BMC_EVENT_PROTOCOL_RESET);
+}
diff --git a/control_dbus.c b/control_dbus.c
index c8c6463..e22d736 100644
--- a/control_dbus.c
+++ b/control_dbus.c
@@ -2,12 +2,14 @@
 // Copyright (C) 2018 IBM Corp.
 #include <assert.h>
 #include <errno.h>
+#include <stdlib.h>
 #include <systemd/sd-bus.h>
 
 #include "common.h"
 #include "dbus.h"
 #include "control_dbus.h"
 #include "mboxd.h"
+#include "vpnor/mboxd_pnor_partition_table.h"
 
 typedef int (*control_action)(struct mbox_context *context);
 
@@ -108,6 +110,90 @@
 	return sd_bus_send(NULL, n, NULL);
 }
 
+static int control_dbus_set_backend(sd_bus_message *m, void *userdata,
+				    sd_bus_error *ret_error)
+{
+	struct mbox_context *context;
+	struct backend backend;
+	sd_bus_message *n;
+	const char *name;
+	int rc;
+
+	context = (struct mbox_context *) userdata;
+	if (!context) {
+		MSG_ERR("DBUS Internal Error\n");
+		return -EINVAL;
+	}
+
+	rc = sd_bus_message_read_basic(m, 's', &name);
+	if (rc < 0) {
+		MSG_ERR("DBUS error reading message: %s\n", strerror(-rc));
+		return rc;
+	}
+
+	if (!strcmp(name, "vpnor")) {
+		struct vpnor_partition_paths paths;
+
+		vpnor_default_paths(&paths);
+		backend = backend_get_vpnor();
+		rc = control_set_backend(context, &backend, &paths);
+		if (rc < 0)
+			return rc;
+	} else if (!strcmp(name, "mtd")) {
+		char **paths = NULL;
+		char *path = NULL;
+
+		rc = sd_bus_message_read_strv(m, &paths);
+		if (rc < 0)
+			return rc;
+
+		if (paths && *paths)
+			path = *paths;
+		else
+			path = get_dev_mtd();
+
+		backend = backend_get_mtd();
+
+		rc = control_set_backend(context, &backend, path);
+		if (rc < 0)
+			return rc;
+
+		free(path);
+		free(paths);
+	} else if (!strcmp(name, "file")) {
+		char **paths = NULL;
+		char *path = NULL;
+
+		rc = sd_bus_message_read_strv(m, &paths);
+		if (rc < 0)
+			return rc;
+
+		if (!(paths && *paths))
+			return -EINVAL;
+
+		path = *paths;
+
+		backend = backend_get_file();
+
+		rc = control_set_backend(context, &backend, path);
+		if (rc < 0)
+			return rc;
+
+		free(path);
+		free(paths);
+	} else {
+		return -EINVAL;
+	}
+
+	rc = sd_bus_message_new_method_return(m, &n);
+	if (rc < 0) {
+		MSG_ERR("sd_bus_message_new_method_return failed: %d\n", rc);
+		return rc;
+	}
+
+	return sd_bus_send(NULL, n, NULL);
+}
+
 static int control_dbus_get_u8(sd_bus *bus, const char *path,
 			       const char *interface, const char *property,
 			       sd_bus_message *reply, void *userdata,
@@ -144,6 +230,8 @@
 		      SD_BUS_VTABLE_UNPRIVILEGED),
 	SD_BUS_METHOD("Resume", "b", NULL, &control_dbus_resume,
 		      SD_BUS_VTABLE_UNPRIVILEGED),
+	SD_BUS_METHOD("SetBackend", "sas", NULL, &control_dbus_set_backend,
+		      SD_BUS_VTABLE_UNPRIVILEGED),
 	SD_BUS_PROPERTY("DaemonState", "y", &control_dbus_get_u8, 0,
 		        SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
 	SD_BUS_PROPERTY("LpcState", "y", &control_dbus_get_u8, 0,
diff --git a/control_dbus.h b/control_dbus.h
index 17e4dec..16661c9 100644
--- a/control_dbus.h
+++ b/control_dbus.h
@@ -2,6 +2,7 @@
 #define DBUS_CONTROL_H
 
 struct mbox_context;
+struct backend;
 
 int control_dbus_init(struct mbox_context *context);
 void control_dbus_free(struct mbox_context *context);
@@ -18,5 +19,6 @@
 int control_modified(struct mbox_context *context);
 int control_suspend(struct mbox_context *context);
 int control_resume(struct mbox_context *context, bool modified);
+int control_set_backend(struct mbox_context *context, struct backend *backend, void *data);
 
 #endif
diff --git a/mboxctl.c b/mboxctl.c
index 9dbc149..b001fb9 100644
--- a/mboxctl.c
+++ b/mboxctl.c
@@ -4,6 +4,7 @@
 
 #include <errno.h>
 #include <getopt.h>
+#include <limits.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -25,7 +26,8 @@
 "\t\t--suspend\t\t- suspend the daemon to inhibit flash accesses (0)\n" \
 "\t\t--resume\t\t- resume the daemon (1)\n" \
 "\t\t\targ[0]: < \"clean\" | \"modified\" >\n" \
-"\t\t--clear-cache\t- tell the daemon to discard any caches (0)\n"
+"\t\t--clear-cache\t- tell the daemon to discard any caches (0)\n" \
+"\t\t--backend <vpnor|mtd[:PATH]|file:PATH>\n"
 
 #define NAME		"Mailbox Control"
 
@@ -269,6 +271,95 @@
 	return rc;
 }
 
+static int handle_cmd_backend(struct mboxctl_context *context, char *sarg)
+{
+	sd_bus_error error = SD_BUS_ERROR_NULL;
+	sd_bus_message *m = NULL, *n = NULL;
+	char *delim = NULL;
+	char *strv[2];
+	int rc;
+
+	if (!sarg) {
+		MSG_ERR("Backend command takes an argument\n");
+		return -EINVAL;
+	}
+
+	rc = sd_bus_message_new_method_call(context->bus, &m,
+					    MBOX_DBUS_NAME,
+					    MBOX_DBUS_OBJECT,
+					    MBOX_DBUS_CONTROL_IFACE,
+					    "SetBackend");
+	if (rc < 0) {
+		MSG_ERR("Failed to init method call: %s\n",
+			strerror(-rc));
+		goto out;
+	}
+
+	if (!strncmp(sarg, "vpnor", strlen("vpnor"))) {
+		if (strchr(sarg, ':')) {
+			MSG_ERR("Path parameter not supported for vpnor\n");
+			rc = -EINVAL;
+			goto out;
+		}
+
+		rc = sd_bus_message_append(m, "s", "vpnor");
+		if (rc < 0)
+			goto out;
+	} else if (!strncmp(sarg, "mtd", strlen("mtd"))) {
+		rc = sd_bus_message_append(m, "s", "mtd");
+		if (rc < 0)
+			goto out;
+	} else if (!strncmp(sarg, "file", strlen("file"))) {
+		rc = sd_bus_message_append(m, "s", "file");
+		if (rc < 0)
+			goto out;
+	} else {
+		rc = -EINVAL;
+		goto out;
+	}
+
+	delim = strchr(sarg, ':');
+	if (delim) {
+		char *path;
+
+		path = realpath(delim + 1, NULL);
+		if (!path) {
+			MSG_ERR("Failed to resolve path: %s\n",
+					strerror(errno));
+			rc = -errno;
+			goto out;
+		}
+
+		strv[0] = path;
+		strv[1] = NULL;
+
+		rc = sd_bus_message_append_strv(m, &strv[0]);
+		free(path);
+		if (rc < 0)
+			goto out;
+	} else {
+		strv[0] = NULL;
+		strv[1] = NULL;
+		rc = sd_bus_message_append_strv(m, &strv[0]);
+		if (rc < 0)
+			goto out;
+	}
+
+	rc = sd_bus_call(context->bus, m, 0, &error, &n);
+	if (rc < 0) {
+		MSG_ERR("Failed to post message: %s\n", strerror(-rc));
+		goto out;
+	}
+
+	MSG_OUT("SetBackend: %s\n", rc < 0 ? strerror(-rc) : "Success");
+
+out:
+	sd_bus_error_free(&error);
+	sd_bus_message_unref(m);
+
+	return rc < 0 ? rc : 0;
+}
+
 static int parse_cmdline(struct mboxctl_context *context, int argc, char **argv)
 {
 	int opt, rc = -1;
@@ -284,6 +375,7 @@
 		{ "suspend",		no_argument,		0, 'u' },
 		{ "resume",		required_argument,	0, 'e' },
 		{ "clear-cache",	no_argument,		0, 'c' },
+		{ "backend",		required_argument,	0, 'b' },
 		{ "version",		no_argument,		0, 'v' },
 		{ "help",		no_argument,		0, 'h' },
 		{ 0,			0,			0, 0   }
@@ -325,6 +417,9 @@
 		case 'c':
 			rc = handle_cmd_modified(context);
 			break;
+		case 'b':
+			rc = handle_cmd_backend(context, optarg);
+			break;
 		case 'v':
 			MSG_OUT("%s V%s\n", NAME, PACKAGE_VERSION);
 			rc = 0;
diff --git a/mboxd.c b/mboxd.c
index 85e1d3f..48624c8 100644
--- a/mboxd.c
+++ b/mboxd.c
@@ -44,9 +44,9 @@
 	"\t\t[-w | --window-size <size>M]\n"
 	"\t\t-f | --flash <size>[K|M]\n"
 #ifdef VIRTUAL_PNOR_ENABLED
-	"\t\t-s | --source <vpnor|path>\n\n"
+	"\t\t-b | --backend <vpnor|mtd[:PATH]|file:PATH>\n"
 #else
-	"\t\t-s | --source <path>\n\n"
+	"\t\t-b | --backend <mtd[:PATH]|file:PATH>\n"
 #endif
 	"\t-v | --verbose\t\tBe [more] verbose\n"
 	"\t-s | --syslog\t\tLog output to syslog (pointless without -v)\n"
@@ -272,7 +272,7 @@
 			}
 			break;
 		case 'b':
-			context->path = optarg;
+			context->source = optarg;
 			break;
 		case 'n':
 			context->windows.num = strtol(argv[optind], &endptr,
@@ -316,9 +316,6 @@
 		}
 	}
 
-	if (!context->path) {
-		context->path = get_dev_mtd();
-	}
 	if (!context->backend.flash_size) {
 		fprintf(stderr, "Must specify a non-zero flash size\n");
 		return false;
@@ -336,23 +333,44 @@
 
 static int mboxd_backend_init(struct mbox_context *context)
 {
+	const char *delim;
+	const char *path;
 	int rc;
 
-#ifdef VIRTUAL_PNOR_ENABLED
-	struct vpnor_partition_paths paths;
-	vpnor_default_paths(&paths);
+	if (!context->source) {
+		struct vpnor_partition_paths paths;
+		vpnor_default_paths(&paths);
 
-	rc = backend_probe_vpnor(&context->backend, &paths);
-	if(rc)
-#endif
-	{
-		rc = backend_probe_mtd(&context->backend, context->path);
-		if (rc) {
-			rc = backend_probe_file(&context->backend,
-						context->path);
-		}
+		rc = backend_probe_vpnor(&context->backend, &paths);
+		if(rc < 0)
+			rc = backend_probe_mtd(&context->backend, NULL);
+
+		return rc;
 	}
 
+	delim = strchr(context->source, ':');
+	path = delim ? delim + 1 : NULL;
+
+	if (!strncmp(context->source, "vpnor", strlen("vpnor"))) {
+		struct vpnor_partition_paths paths;
+
+		if (path) {
+			rc = -EINVAL;
+		} else {
+			vpnor_default_paths(&paths);
+			rc = backend_probe_vpnor(&context->backend, &paths);
+		}
+	} else if (!strncmp(context->source, "mtd", strlen("mtd"))) {
+		rc = backend_probe_mtd(&context->backend, path);
+	} else if (!strncmp(context->source, "file", strlen("file"))) {
+		rc = backend_probe_file(&context->backend, path);
+	} else {
+		rc = -EINVAL;
+	}
+
+	if (rc < 0)
+		MSG_ERR("Invalid backend argument: %s\n", context->source);
+
 	return rc;
 }
 
diff --git a/mboxd.h b/mboxd.h
index 7930242..ef4f5be 100644
--- a/mboxd.h
+++ b/mboxd.h
@@ -76,7 +76,7 @@
 	struct backend backend;
 
 	/* Commandline parameters */
-	const char *path;
+	const char *source;
 
 /* System State */
 	enum mbox_state state;
diff --git a/test/Makefile.am.include b/test/Makefile.am.include
index 2b7ba75..addf2f9 100644
--- a/test/Makefile.am.include
+++ b/test/Makefile.am.include
@@ -35,7 +35,7 @@
 	%reldir%/mbox.c \
 	%reldir%/system.c
 
-TEST_MOCK_SRCS = %reldir%/backend.c $(TEST_MOCK_CORE)
+TEST_MOCK_SRCS = $(TEST_MOCK_CORE)
 
 test_get_mbox_info_v2_SOURCES = %reldir%/get_mbox_info_v2.c \
 				$(TEST_MBOX_SRCS) $(TEST_MOCK_SRCS)
diff --git a/test/backend.c b/test/backend.c
deleted file mode 100644
index 7b9fec8..0000000
--- a/test/backend.c
+++ /dev/null
@@ -1,11 +0,0 @@
-#include "backend.h"
-#include "mboxd.h"
-
-#include <errno.h>
-
-struct backend backend_get_vpnor(void)
-{
-	struct backend be = {0};
-
-	return be;
-}
diff --git a/vpnor/mboxd_pnor_partition_table.h b/vpnor/mboxd_pnor_partition_table.h
index e325775..b76aa98 100644
--- a/vpnor/mboxd_pnor_partition_table.h
+++ b/vpnor/mboxd_pnor_partition_table.h
@@ -2,8 +2,6 @@
 /* Copyright (C) 2018 IBM Corp. */
 #pragma once
 
-#ifdef VIRTUAL_PNOR_ENABLED
-
 #include <limits.h>
 #include "pnor_partition_defs.h"
 #include "backend.h"
@@ -34,8 +32,16 @@
  *
  *  Returns 0 if the call succeeds, else a negative error code.
  */
+#ifdef VIRTUAL_PNOR_ENABLED
 void vpnor_default_paths(struct vpnor_partition_paths *paths);
+#else
+static inline void vpnor_default_paths(struct vpnor_partition_paths *paths)
+{
+    memset(paths, 0, sizeof(*paths));
+}
+#endif
 
+#ifdef VIRTUAL_PNOR_ENABLED
 /** @brief Create a virtual PNOR partition table.
  *
  *  @param[in] backend - The backend context pointer